updated the ui for the doormile

This commit is contained in:
2026-06-08 18:02:27 +05:30
parent a162fa89e5
commit 4ad40b2c6d
24 changed files with 2434 additions and 238 deletions

View File

@@ -1,9 +1,13 @@
import { Grid, Card, CardContent, Stack, Typography, Box, Button, Divider, Table, TableBody, TableCell, TableHead, TableRow, Avatar, MenuItem, TextField } from '@mui/material';
import { Grid, Stack, Typography, Box, Button, Divider, Table, TableBody, TableCell, TableHead, TableRow, MenuItem, TextField, Avatar, LinearProgress, Chip } from '@mui/material';
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined';
import CurrencyRupeeIcon from '@mui/icons-material/CurrencyRupee';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined';
import EnergySavingsLeafOutlinedIcon from '@mui/icons-material/EnergySavingsLeafOutlined';
import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined';
import SpeedOutlinedIcon from '@mui/icons-material/SpeedOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
@@ -12,14 +16,20 @@ import StatusChip from '@/components/StatusChip';
import AreaChart from '@/components/charts/AreaChart';
import DonutChart from '@/components/charts/DonutChart';
import UserAvatar from '@/components/UserAvatar';
import { ordersTrend, statusBreakdown, orders, riders } from '@/data/mock';
import SystemPipeline from '@/components/SystemPipeline';
import ThreeMileStrip from '@/components/ThreeMileStrip';
import Toast, { useToast } from '@/components/Toast';
import { ordersTrend, statusBreakdown, orders, riders, aiMetrics, fleetSummary, verticals, verticalOf } from '@/data/mock';
import { inr } from '@/utils/format';
const VERTICAL_COLOR = Object.fromEntries(verticals.map((v) => [v.label, v.color]));
export default function Dashboard() {
const [toast, showToast] = useToast();
return (
<>
<PageHeader
title="Dashboard"
title="System Overview"
breadcrumbs={[{ label: 'Dashboard' }]}
action={
<Stack direction="row" spacing={1.5}>
@@ -28,15 +38,35 @@ export default function Dashboard() {
<MenuItem value="blr">Bengaluru</MenuItem>
<MenuItem value="mum">Mumbai</MenuItem>
</TextField>
<Button variant="outlined" startIcon={<FileDownloadOutlinedIcon />}>Export</Button>
<Button variant="outlined" startIcon={<FileDownloadOutlinedIcon />} onClick={() => showToast('System overview exported as CSV')}>Export</Button>
</Stack>
}
/>
{/* End-to-end operating-system pipeline */}
<Box sx={{ mb: 1 }}>
<Typography variant="overline" color="text.secondary" sx={{ letterSpacing: '0.08em' }}>
End-to-End Intelligent Logistics Flow
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<SystemPipeline />
</Box>
{/* Three-Mile model — First → Mid → Last */}
<Box sx={{ mb: 0.5 }}>
<Typography variant="overline" color="text.secondary" sx={{ letterSpacing: '0.08em' }}>
Three-Mile Network · One Connected System
</Typography>
</Box>
<Box sx={{ mb: 3 }}>
<ThreeMileStrip compact />
</Box>
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Total Orders" value="1,402" icon={Inventory2OutlinedIcon} trend={8.4} caption="vs last month" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Delivered" value="1,330" icon={LocalShippingOutlinedIcon} color="success" trend={6.1} caption="vs last month" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Active Riders" value="48" icon={TwoWheelerOutlinedIcon} color="info" trend={-2.3} caption="vs last month" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Total Shipments" value="1,402" icon={Inventory2OutlinedIcon} trend={8.4} caption="vs last month" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Delivered" value="1,330" icon={LocalShippingOutlinedIcon} color="success" trend={6.1} caption="98.6% on-time" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Active Riders" value="48" icon={TwoWheelerOutlinedIcon} color="info" trend={-2.3} caption="of 124 fleet" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Revenue" value={inr(384200)} icon={CurrencyRupeeIcon} color="warning" trend={11.7} caption="vs last month" /></Grid>
<Grid item xs={12} lg={8}>
@@ -61,39 +91,42 @@ export default function Dashboard() {
</MainCard>
</Grid>
<Grid item xs={12} lg={7}>
<MainCard title="Recent Orders" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Order ID</TableCell>
<TableCell>Customer</TableCell>
<TableCell>Route</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{orders.slice(0, 6).map((o) => (
<TableRow key={o.id} hover>
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{o.id}</TableCell>
<TableCell>{o.customer}</TableCell>
<TableCell>
<Typography variant="caption" color="text.secondary">{o.pickup} {o.drop}</Typography>
</TableCell>
<TableCell><StatusChip status={o.status} /></TableCell>
<TableCell align="right" sx={{ fontWeight: 600 }}>{inr(o.charges)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{/* MileTruth AI + Sustainability */}
<Grid item xs={12} lg={5}>
<MainCard
title={
<Stack direction="row" spacing={1} alignItems="center">
<Avatar variant="rounded" sx={{ bgcolor: '#FFF1E6', color: '#EA580C', width: 32, height: 32 }}><AutoAwesomeOutlinedIcon fontSize="small" /></Avatar>
<Typography variant="h5">MileTruth AI Engine</Typography>
</Stack>
}
>
<Grid container spacing={2}>
<Grid item xs={6}><AiStat icon={RouteOutlinedIcon} color="#C01227" value={`${aiMetrics.routeSavings}%`} label="Route savings" /></Grid>
<Grid item xs={6}><AiStat icon={SpeedOutlinedIcon} color="#C01227" value={`${aiMetrics.avgEtaAccuracy}%`} label="ETA accuracy" /></Grid>
<Grid item xs={6}><AiStat icon={AutoAwesomeOutlinedIcon} color="#C01227" value={aiMetrics.reoptToday} label="Re-optimizations today" /></Grid>
<Grid item xs={6}><AiStat icon={LocalShippingOutlinedIcon} color="#C01227" value={`${aiMetrics.delaysAvoided}/${aiMetrics.delaysPredicted}`} label="Delays avoided" /></Grid>
</Grid>
</MainCard>
</Grid>
<Grid item xs={12} lg={5}>
<Grid item xs={12} lg={3}>
<MainCard title="EV-First Operations">
<Stack alignItems="center" spacing={1} sx={{ py: 1 }}>
<Avatar variant="rounded" sx={{ bgcolor: 'success.lighter', color: 'success.main', width: 48, height: 48 }}><EnergySavingsLeafOutlinedIcon /></Avatar>
<Typography variant="h2" sx={{ fontWeight: 800, color: 'success.main' }}>{fleetSummary.evShare}%</Typography>
<Typography variant="caption" color="text.secondary">EV fleet share</Typography>
<Box sx={{ width: '100%', mt: 1 }}>
<LinearProgress variant="determinate" value={fleetSummary.evShare} color="success" sx={{ height: 8, borderRadius: 4 }} />
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5 }}>{(fleetSummary.co2SavedKg / 1000).toFixed(1)}t CO₂ saved this month</Typography>
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Top Riders Today">
<Stack divider={<Divider />} spacing={0}>
{riders.slice(0, 5).map((r, i) => (
<Stack key={r.id} direction="row" spacing={2} alignItems="center" sx={{ py: 1.25 }}>
{riders.slice(0, 4).map((r, i) => (
<Stack key={r.id} direction="row" spacing={2} alignItems="center" sx={{ py: 1.1 }}>
<Typography variant="subtitle2" color="text.secondary" sx={{ width: 18 }}>{i + 1}</Typography>
<UserAvatar name={r.name} size={36} />
<Box sx={{ flexGrow: 1 }}>
@@ -109,11 +142,84 @@ export default function Dashboard() {
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="By Industry Vertical">
<Stack spacing={1.5}>
{verticals.map((v) => (
<Box key={v.key} sx={{ border: '1px solid', borderColor: 'grey.200', borderRadius: 2, p: 1.5 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="row" spacing={1} alignItems="center">
<Box sx={{ width: 10, height: 10, borderRadius: '3px', bgcolor: v.color }} />
<Box>
<Typography variant="subtitle2">{v.label}</Typography>
<Typography variant="caption" color="text.secondary">{v.desc}</Typography>
</Box>
</Stack>
<Box sx={{ textAlign: 'right' }}>
<Typography variant="h4" sx={{ fontWeight: 700, color: 'grey.900' }}>{v.shipments}</Typography>
<Typography variant="caption" color="text.secondary">{v.onTime}% on-time</Typography>
</Box>
</Stack>
</Box>
))}
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} lg={8}>
<MainCard title="Recent Shipments" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Order ID</TableCell>
<TableCell>Customer</TableCell>
<TableCell>Vertical</TableCell>
<TableCell>Route</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{orders.slice(0, 6).map((o) => {
const v = verticalOf(o.tenant);
return (
<TableRow key={o.id} hover>
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{o.id}</TableCell>
<TableCell>{o.customer}</TableCell>
<TableCell>
<Chip size="small" label={v} sx={{ bgcolor: hexA(VERTICAL_COLOR[v], 0.12), color: VERTICAL_COLOR[v], fontWeight: 600 }} />
</TableCell>
<TableCell>
<Typography variant="caption" color="text.secondary">{o.pickup} {o.drop}</Typography>
</TableCell>
<TableCell><StatusChip status={o.status} /></TableCell>
<TableCell align="right" sx={{ fontWeight: 600 }}>{inr(o.charges)}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</MainCard>
</Grid>
</Grid>
<Toast {...toast} />
</>
);
}
function AiStat({ icon: Icon, color, value, label }) {
return (
<Stack direction="row" spacing={1.25} alignItems="center">
<Avatar variant="rounded" sx={{ bgcolor: hexA(color, 0.12), color, width: 38, height: 38 }}><Icon fontSize="small" /></Avatar>
<Box>
<Typography variant="h4" sx={{ fontWeight: 700 }}>{value}</Typography>
<Typography variant="caption" color="text.secondary">{label}</Typography>
</Box>
</Stack>
);
}
function Legend({ color, label }) {
return (
<Stack direction="row" spacing={0.75} alignItems="center">
@@ -122,3 +228,8 @@ function Legend({ color, label }) {
</Stack>
);
}
const hexA = (hex, a) => {
const n = parseInt(hex.replace('#', ''), 16);
return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
};

View File

@@ -1,162 +0,0 @@
import { useState } from 'react';
import {
Card, Stack, Button, Box, Collapse, Tabs, Tab, Typography, Grid,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton,
TablePagination, Dialog, DialogTitle, DialogContent, DialogActions, TextField
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import PageHeader from '@/components/PageHeader';
import { requests } from '@/data/mock';
import { inr } from '@/utils/format';
function RequestRow({ row, index }) {
const [open, setOpen] = useState(false);
const [tab, setTab] = useState(0);
return (
<>
<TableRow hover sx={{ '& > *': { borderBottom: open ? 'unset' : undefined } }}>
<TableCell>{index + 1}</TableCell>
<TableCell sx={{ fontWeight: 600 }}>{row.requestor}</TableCell>
<TableCell>{row.bank}</TableCell>
<TableCell>{row.ifsc}</TableCell>
<TableCell>{row.refNo}</TableCell>
<TableCell align="right" sx={{ fontWeight: 600 }}>{inr(row.amount)}</TableCell>
<TableCell>{row.reason}</TableCell>
<TableCell align="center">
<IconButton size="small" onClick={() => setOpen((o) => !o)}>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
</TableRow>
<TableRow>
<TableCell sx={{ py: 0, borderBottom: open ? 1 : 0, borderColor: 'divider' }} colSpan={8}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ m: 2 }}>
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ mb: 2 }}>
<Tab label="Client Details" />
<Tab label="Client Pricing" />
</Tabs>
{tab === 0 && (
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={3}>
<Typography variant="caption" color="text.secondary">Contact Name</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>{row.contact}</Typography>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Typography variant="caption" color="text.secondary">Address</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>{row.address}</Typography>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Typography variant="caption" color="text.secondary">City</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>{row.city}</Typography>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Typography variant="caption" color="text.secondary">Zip Code</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>{row.zip}</Typography>
</Grid>
</Grid>
)}
{tab === 1 && (
<Table size="small">
<TableHead>
<TableRow sx={{ '& th': { bgcolor: 'grey.50', fontWeight: 700 } }}>
<TableCell>#</TableCell>
<TableCell>Category</TableCell>
<TableCell>Skill</TableCell>
<TableCell align="right">Cost/Hr</TableCell>
</TableRow>
</TableHead>
<TableBody>
{row.pricing.map((p, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{p.category}</TableCell>
<TableCell>{p.skill}</TableCell>
<TableCell align="right">{inr(p.cost)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</Box>
</Collapse>
</TableCell>
</TableRow>
</>
);
}
export default function Requests() {
const [page, setPage] = useState(0);
const [rpp, setRpp] = useState(10);
const [open, setOpen] = useState(false);
const paged = requests.slice(page * rpp, page * rpp + rpp);
return (
<>
<PageHeader
title="Requests"
breadcrumbs={[{ label: 'Requests' }]}
action={
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setOpen(true)}>
Create Request
</Button>
}
/>
<Card>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>#</TableCell>
<TableCell>Requestor</TableCell>
<TableCell>Bank</TableCell>
<TableCell>IFSC</TableCell>
<TableCell>Ref No</TableCell>
<TableCell align="right">Amount</TableCell>
<TableCell>Reason</TableCell>
<TableCell align="center" />
</TableRow>
</TableHead>
<TableBody>
{paged.map((row, idx) => (
<RequestRow key={row.id} row={row} index={page * rpp + idx} />
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div" count={requests.length} page={page} onPageChange={(_, p) => setPage(p)}
rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25, 100]}
/>
</Card>
<Dialog open={open} onClose={() => setOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>Create Request</DialogTitle>
<DialogContent>
<Grid container spacing={2.5} sx={{ mt: 0 }}>
<Grid item xs={12} sm={6}><TextField label="Reference No" type="number" fullWidth /></Grid>
<Grid item xs={12} sm={6}><TextField label="Requestor" fullWidth /></Grid>
<Grid item xs={12} sm={6}><TextField label="Bank Name" fullWidth /></Grid>
<Grid item xs={12} sm={6}><TextField label="Amount" type="number" fullWidth /></Grid>
<Grid item xs={12} sm={6}><TextField label="Account No" type="number" fullWidth /></Grid>
<Grid item xs={12} sm={6}><TextField label="IFSC Code" fullWidth /></Grid>
<Grid item xs={12}><TextField label="Reason" fullWidth multiline minRows={3} /></Grid>
</Grid>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={() => setOpen(false)} color="inherit">Close</Button>
<Button variant="contained" onClick={() => setOpen(false)}>Update</Button>
</DialogActions>
</Dialog>
</>
);
}

View File

@@ -0,0 +1,141 @@
import { Grid, Stack, Typography, Box, Avatar, LinearProgress, Table, TableBody, TableCell, TableHead, TableRow, Button, Divider } from '@mui/material';
import TrendingUpOutlinedIcon from '@mui/icons-material/TrendingUpOutlined';
import CurrencyRupeeIcon from '@mui/icons-material/CurrencyRupee';
import SsidChartOutlinedIcon from '@mui/icons-material/SsidChartOutlined';
import AutorenewOutlinedIcon from '@mui/icons-material/AutorenewOutlined';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import LayerBanner from '@/components/LayerBanner';
import AreaChart from '@/components/charts/AreaChart';
import Toast, { useToast } from '@/components/Toast';
import { analyticsKpis, lanePerformance, mlLoop, outcomes, ordersTrend } from '@/data/mock';
import { inr } from '@/utils/format';
export default function Analytics() {
const [toast, showToast] = useToast();
return (
<>
<PageHeader
title="Analytics & Intelligence Layer"
breadcrumbs={[{ label: 'Analytics' }]}
action={<Button variant="outlined" startIcon={<FileDownloadOutlinedIcon />} onClick={() => showToast('Analytics report exported')}>Export Report</Button>}
/>
<LayerBanner
no={7}
icon={TrendingUpOutlinedIcon}
color="#7C3AED"
title="Analytics & Intelligence"
subtitle="Operational, cost, business & predictive insight feeding a continuous ML improvement loop."
steps={['Operational Analytics', 'Cost Intelligence', 'Business Intelligence', 'Predictive Insights', 'Continuous Improvement']}
/>
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="On-Time Delivery" value={`${analyticsKpis.onTime}%`} icon={CheckCircleOutlineIcon} color="success" trend={1.2} caption="SLA achievement 97.4%" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Success Rate" value={`${analyticsKpis.successRate}%`} icon={TrendingUpOutlinedIcon} color="info" trend={0.6} caption="first-attempt" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Cost / Delivery" value={inr(analyticsKpis.costPerDelivery)} icon={CurrencyRupeeIcon} color="warning" trend={-4.1} caption="energy cost ₹21" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Load Factor" value={`${analyticsKpis.loadFactor}%`} icon={SsidChartOutlinedIcon} color="primary" trend={2.3} caption="capacity utilised" /></Grid>
<Grid item xs={12} lg={8}>
<MainCard
title="Cost vs Volume Trend"
action={<Stack direction="row" spacing={2}><Legend color="#7C3AED" label="Orders" /><Legend color="#00A854" label="Delivered" /></Stack>}
>
<AreaChart
labels={ordersTrend.map((d) => d.m)}
series={[
{ name: 'Orders', color: '#7C3AED', data: ordersTrend.map((d) => d.orders) },
{ name: 'Delivered', color: '#00A854', data: ordersTrend.map((d) => d.delivered) }
]}
/>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Continuous Improvement (ML Loop)">
<Stack divider={<Divider />} spacing={0}>
{mlLoop.map((m) => (
<Stack key={m.step} direction="row" spacing={1.5} alignItems="center" sx={{ py: 1.35 }}>
<Avatar variant="rounded" sx={{ bgcolor: '#F3E8FF', color: '#7C3AED', width: 38, height: 38 }}>
<AutorenewOutlinedIcon fontSize="small" />
</Avatar>
<Box>
<Typography variant="subtitle2">{m.step}</Typography>
<Typography variant="caption" color="text.secondary">{m.detail}</Typography>
</Box>
</Stack>
))}
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} lg={7}>
<MainCard title="Lane Performance" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Lane</TableCell>
<TableCell align="right">Shipments</TableCell>
<TableCell>On-Time</TableCell>
<TableCell align="right">Cost / Del</TableCell>
<TableCell>EV %</TableCell>
</TableRow>
</TableHead>
<TableBody>
{lanePerformance.map((l) => (
<TableRow key={l.lane} hover>
<TableCell><Typography variant="subtitle2">{l.lane}</Typography></TableCell>
<TableCell align="right">{l.shipments.toLocaleString('en-IN')}</TableCell>
<TableCell sx={{ minWidth: 110 }}>
<Stack direction="row" spacing={1} alignItems="center">
<Box sx={{ width: 54 }}>
<LinearProgress variant="determinate" value={l.onTime} color={l.onTime > 97 ? 'success' : 'warning'} sx={{ height: 6, borderRadius: 3 }} />
</Box>
<Typography variant="caption" sx={{ fontWeight: 600 }}>{l.onTime}%</Typography>
</Stack>
</TableCell>
<TableCell align="right">{inr(l.costPer)}</TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600, color: l.ev > 70 ? 'success.main' : 'text.secondary' }}>{l.ev}%</Typography></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</MainCard>
</Grid>
<Grid item xs={12} lg={5}>
<MainCard title="System Outcomes">
<Grid container spacing={1.5}>
{outcomes.map((o) => (
<Grid item xs={12} sm={6} key={o.label}>
<Box sx={{ border: '1px solid', borderColor: 'grey.200', borderRadius: 2, p: 1.75, height: '100%' }}>
<Stack direction="row" spacing={1} alignItems="center">
<CheckCircleOutlineIcon sx={{ color: 'success.main', fontSize: 20 }} />
<Typography variant="h4" sx={{ fontWeight: 800, color: 'grey.900' }}>{o.value}</Typography>
</Stack>
<Typography variant="subtitle2" sx={{ mt: 0.5 }}>{o.label}</Typography>
<Typography variant="caption" color="text.secondary">{o.caption}</Typography>
</Box>
</Grid>
))}
</Grid>
</MainCard>
</Grid>
</Grid>
<Toast {...toast} />
</>
);
}
function Legend({ color, label }) {
return (
<Stack direction="row" spacing={0.75} alignItems="center">
<Box sx={{ width: 10, height: 10, borderRadius: '3px', bgcolor: color }} />
<Typography variant="caption" color="text.secondary">{label}</Typography>
</Stack>
);
}

View File

@@ -49,18 +49,21 @@ export default function Login() {
<Box sx={{ position: 'absolute', width: 280, height: 280, borderRadius: '50%', bgcolor: 'rgba(255,255,255,0.06)', bottom: -80, left: -60 }} />
<Logo onDark />
<Box sx={{ position: 'relative' }}>
<Typography variant="h2" sx={{ color: '#fff', fontWeight: 800, lineHeight: 1.2 }}>
Move every parcel,
<br /> on time, every time.
<Typography variant="overline" sx={{ color: 'rgba(255,255,255,0.7)', letterSpacing: '0.14em' }}>
One Connected System · One Promise Kept
</Typography>
<Typography variant="h2" sx={{ color: '#fff', fontWeight: 800, lineHeight: 1.2, mt: 0.5 }}>
Delivering Trust.
<br /> Beyond Boundaries.
</Typography>
<Typography sx={{ color: 'rgba(255,255,255,0.85)', mt: 2, maxWidth: 420 }}>
The command center for your last-mile operation orders, riders, pricing and settlements in one corporate console.
The MileTruth AI command center for Connected Miles first-mile, mid-mile and last-mile delivery, unified in one intelligent console.
</Typography>
<Stack spacing={1.5} sx={{ mt: 4 }}>
{[
{ icon: BoltIcon, t: 'AI-assisted route optimisation' },
{ icon: LocalShippingOutlinedIcon, t: 'Real-time rider & delivery tracking' },
{ icon: VerifiedOutlinedIcon, t: 'Automated client invoicing & payouts' }
{ icon: BoltIcon, t: 'MileTruth™ AI route optimisation' },
{ icon: LocalShippingOutlinedIcon, t: 'EV-first real-time tracking & proof of delivery' },
{ icon: VerifiedOutlinedIcon, t: 'Cold-chain & chain-of-custody compliance' }
].map((f) => (
<Stack key={f.t} direction="row" spacing={1.5} alignItems="center">
<Box sx={{ width: 34, height: 34, borderRadius: 2, bgcolor: 'rgba(255,255,255,0.16)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>

View File

@@ -0,0 +1,146 @@
import { Grid, Stack, Typography, Box, Avatar, LinearProgress, Table, TableBody, TableCell, TableHead, TableRow, Button } from '@mui/material';
import AcUnitOutlinedIcon from '@mui/icons-material/AcUnitOutlined';
import DeviceThermostatOutlinedIcon from '@mui/icons-material/DeviceThermostatOutlined';
import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined';
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import Toast, { useToast } from '@/components/Toast';
import { coldChainSummary, coldChainShipments } from '@/data/mock';
const TEMP_COLOR = { 'in-range': '#00A854', 'at-risk': '#FFBF00', breach: '#F04134' };
export default function ColdChain() {
const [toast, showToast] = useToast();
return (
<>
<PageHeader
title="Pharma Cold-Chain"
breadcrumbs={[{ label: 'Cold Chain' }]}
action={<Button variant="outlined" startIcon={<FileDownloadOutlinedIcon />} onClick={() => showToast('Cold-chain compliance report generated')}>Compliance Report</Button>}
/>
<LayerBanner
no={0}
icon={AcUnitOutlinedIcon}
color="#0E7C7B"
title="Temperature-Monitored Logistics"
subtitle="Battery-aware EV cold-chain with live temperature monitoring, excursion alerts and regulatory compliance."
steps={['Sensor Telemetry', 'Live Monitoring', 'Excursion Alerts', 'Compliance Logs']}
/>
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Monitored Shipments" value={coldChainSummary.monitored} icon={AcUnitOutlinedIcon} color="info" caption="cold-chain active" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="In Range" value={coldChainSummary.inRange} icon={CheckCircleOutlineIcon} color="success" caption={`avg ${coldChainSummary.avgTemp}°C`} /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="At Risk / Breach" value={`${coldChainSummary.atRisk} / ${coldChainSummary.breaches}`} icon={WarningAmberOutlinedIcon} color="warning" caption="needs attention" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Compliance" value={`${coldChainSummary.compliance}%`} icon={VerifiedOutlinedIcon} color="success" trend={0.3} caption="regulatory pass" /></Grid>
<Grid item xs={12}>
<MainCard title="Live Temperature Monitoring" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Shipment</TableCell>
<TableCell>Product</TableCell>
<TableCell>Client</TableCell>
<TableCell>Required Range</TableCell>
<TableCell>Current Temp</TableCell>
<TableCell>Excursion</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{coldChainShipments.map((s) => {
const color = TEMP_COLOR[s.status];
return (
<TableRow key={s.id} hover>
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{s.id}</TableCell>
<TableCell>
<Typography variant="subtitle2">{s.product}</Typography>
<Typography variant="caption" color="text.secondary">{s.route}</Typography>
</TableCell>
<TableCell><Typography variant="caption">{s.tenant}</Typography></TableCell>
<TableCell>{s.range}</TableCell>
<TableCell>
<Stack direction="row" spacing={1} alignItems="center">
<Avatar variant="rounded" sx={{ bgcolor: hexA(color, 0.12), color, width: 30, height: 30 }}>
<DeviceThermostatOutlinedIcon sx={{ fontSize: 18 }} />
</Avatar>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color }}>{s.temp}°C</Typography>
</Stack>
</TableCell>
<TableCell>
{s.excursionMin > 0 ? (
<Typography variant="caption" sx={{ fontWeight: 600, color: 'error.main' }}>{s.excursionMin} min</Typography>
) : (
<Typography variant="caption" color="text.secondary"></Typography>
)}
</TableCell>
<TableCell><StatusChip status={s.status} /></TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</MainCard>
</Grid>
<Grid item xs={12} md={6}>
<MainCard title="Compliance Health">
<Stack spacing={2} sx={{ py: 0.5 }}>
<Bar label="In-range shipments" value={Math.round((coldChainSummary.inRange / coldChainSummary.monitored) * 100)} color="#00A854" />
<Bar label="Regulatory compliance" value={coldChainSummary.compliance} color="#0E7C7B" />
<Bar label="Excursion-free deliveries" value={97} color="#1D4ED8" />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} md={6}>
<MainCard title="Cold-Chain Capabilities">
<Grid container spacing={1.5}>
{[
{ t: 'Live sensor telemetry', d: 'IoT probes per shipment' },
{ t: 'Excursion alerts', d: 'Instant breach notification' },
{ t: 'Battery-aware EV routing', d: 'Reefer charge planning' },
{ t: 'Chain-of-custody logs', d: 'Immutable audit trail' }
].map((c) => (
<Grid item xs={12} sm={6} key={c.t}>
<Box sx={{ border: '1px solid', borderColor: 'grey.200', borderRadius: 2, p: 1.75, height: '100%' }}>
<Stack direction="row" spacing={1} alignItems="center">
<AcUnitOutlinedIcon sx={{ color: '#0E7C7B', fontSize: 20 }} />
<Typography variant="subtitle2">{c.t}</Typography>
</Stack>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>{c.d}</Typography>
</Box>
</Grid>
))}
</Grid>
</MainCard>
</Grid>
</Grid>
<Toast {...toast} />
</>
);
}
function Bar({ label, value, color }) {
return (
<Box>
<Stack direction="row" justifyContent="space-between" sx={{ mb: 0.5 }}>
<Typography variant="caption" color="text.secondary">{label}</Typography>
<Typography variant="caption" sx={{ fontWeight: 700, color }}>{value}%</Typography>
</Stack>
<LinearProgress variant="determinate" value={value} sx={{ height: 6, borderRadius: 3, bgcolor: hexA(color, 0.12), '& .MuiLinearProgress-bar': { bgcolor: color } }} />
</Box>
);
}
const hexA = (hex, a) => {
const n = parseInt(hex.replace('#', ''), 16);
return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
};

View File

@@ -0,0 +1,161 @@
import { useState } from 'react';
import { Grid, Card, CardContent, Stack, Typography, Box, Avatar, LinearProgress, Table, TableBody, TableCell, TableHead, TableRow, Button, Chip, Divider } from '@mui/material';
import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined';
import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined';
import SpeedOutlinedIcon from '@mui/icons-material/SpeedOutlined';
import InsightsOutlinedIcon from '@mui/icons-material/InsightsOutlined';
import PsychologyOutlinedIcon from '@mui/icons-material/PsychologyOutlined';
import FiberNewOutlinedIcon from '@mui/icons-material/FiberNewOutlined';
import TimerOutlinedIcon from '@mui/icons-material/TimerOutlined';
import TrafficOutlinedIcon from '@mui/icons-material/TrafficOutlined';
import ThunderstormOutlinedIcon from '@mui/icons-material/ThunderstormOutlined';
import ReplayOutlinedIcon from '@mui/icons-material/ReplayOutlined';
import PriorityHighOutlinedIcon from '@mui/icons-material/PriorityHighOutlined';
import PlayArrowOutlinedIcon from '@mui/icons-material/PlayArrowOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import Toast, { useToast } from '@/components/Toast';
import { dispatchQueue, aiTriggers, aiPipeline, aiMetrics } from '@/data/mock';
const TRIGGER_ICONS = {
order: FiberNewOutlinedIcon,
delay: TimerOutlinedIcon,
traffic: TrafficOutlinedIcon,
weather: ThunderstormOutlinedIcon,
cancel: ReplayOutlinedIcon,
priority: PriorityHighOutlinedIcon
};
const NEXT_STATUS = { optimizing: 'matched', matched: 'dispatched', dispatched: 'dispatched' };
export default function AiDispatch() {
const [rows, setRows] = useState(dispatchQueue);
const [running, setRunning] = useState(false);
const [toast, showToast] = useToast();
const runOptimization = () => {
setRunning(true);
setRows((q) =>
q.map((d) => ({
...d,
confidence: Math.min(99, d.confidence + 2 + ((d.id.charCodeAt(d.id.length - 1) % 4))),
etaMin: Math.max(12, d.etaMin - 3),
status: NEXT_STATUS[d.status] || d.status
}))
);
showToast('MileTruth AI re-optimized the queue · ETAs improved');
setRunning(false);
};
return (
<>
<PageHeader
title="MileTruth AI Engine"
breadcrumbs={[{ label: 'AI Dispatch' }]}
action={<Button variant="contained" startIcon={<PlayArrowOutlinedIcon />} onClick={runOptimization} disabled={running}>Run Optimization</Button>}
/>
<LayerBanner
no={5}
icon={AutoAwesomeOutlinedIcon}
color="#EA580C"
title="AI Dispatch & Optimization"
subtitle="Smart pricing, route optimization, delay prediction and dynamic re-routing in real time."
steps={['Data Ingestion', 'Processing', 'Intelligence', 'Optimization', 'Assignment', 'Output']}
/>
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Route Savings" value={`${aiMetrics.routeSavings}%`} icon={RouteOutlinedIcon} color="success" trend={2.8} caption="vs naïve routing" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="ETA Accuracy" value={`${aiMetrics.avgEtaAccuracy}%`} icon={SpeedOutlinedIcon} color="info" trend={1.4} caption="predicted vs actual" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Re-optimizations" value={aiMetrics.reoptToday} icon={PsychologyOutlinedIcon} color="warning" caption="triggered today" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Delays Avoided" value={`${aiMetrics.delaysAvoided}/${aiMetrics.delaysPredicted}`} icon={InsightsOutlinedIcon} color="primary" caption="predicted & prevented" /></Grid>
<Grid item xs={12} lg={8}>
<MainCard title="Live Dispatch Queue" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Shipment</TableCell>
<TableCell>Route</TableCell>
<TableCell>Priority</TableCell>
<TableCell>SLA</TableCell>
<TableCell>AI-matched Rider</TableCell>
<TableCell>Confidence</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((d) => (
<TableRow key={d.id} hover>
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{d.id}</TableCell>
<TableCell><Typography variant="caption" color="text.secondary">{d.pickup} {d.drop}</Typography></TableCell>
<TableCell>
<Chip size="small" label={d.priority} sx={{ textTransform: 'capitalize', bgcolor: d.priority === 'high' ? '#FEEAE9' : d.priority === 'express' ? '#FFF7E0' : '#F0F0F0', color: d.priority === 'high' ? '#A82216' : d.priority === 'express' ? '#8A6500' : '#595959' }} />
</TableCell>
<TableCell><Typography variant="caption">{d.sla}</Typography></TableCell>
<TableCell>{d.suggestedRider}</TableCell>
<TableCell sx={{ minWidth: 110 }}>
<Stack direction="row" spacing={1} alignItems="center">
<Box sx={{ width: 56 }}>
<LinearProgress variant="determinate" value={d.confidence} color={d.confidence > 90 ? 'success' : 'warning'} sx={{ height: 6, borderRadius: 3 }} />
</Box>
<Typography variant="caption" sx={{ fontWeight: 600 }}>{d.confidence}%</Typography>
</Stack>
</TableCell>
<TableCell><StatusChip status={d.status} /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Re-optimization Triggers">
<Stack divider={<Divider />} spacing={0}>
{aiTriggers.map((t) => {
const Icon = TRIGGER_ICONS[t.icon] || FiberNewOutlinedIcon;
return (
<Stack key={t.key} direction="row" spacing={1.5} alignItems="center" sx={{ py: 1.15 }}>
<Avatar variant="rounded" sx={{ bgcolor: 'warning.lighter', color: 'warning.dark', width: 36, height: 36 }}>
<Icon fontSize="small" />
</Avatar>
<Typography variant="body2" sx={{ flexGrow: 1 }}>{t.label}</Typography>
<Chip size="small" label={t.count} color={t.count > 5 ? 'warning' : 'default'} />
</Stack>
);
})}
</Stack>
</MainCard>
</Grid>
<Grid item xs={12}>
<MainCard title="MileTruth AI Pipeline">
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2} alignItems="stretch">
{aiPipeline.map((stage, i) => (
<Box key={stage.stage} sx={{ flex: 1, position: 'relative' }}>
<Box sx={{ border: '1px solid', borderColor: 'grey.200', borderRadius: 2, p: 1.75, height: '100%', bgcolor: 'grey.50' }}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
<Box sx={{ width: 22, height: 22, borderRadius: '50%', bgcolor: 'warning.main', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 700 }}>{i + 1}</Box>
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>{stage.stage}</Typography>
</Stack>
<Stack spacing={0.5}>
{stage.items.map((it) => (
<Typography key={it} variant="caption" color="text.secondary"> {it}</Typography>
))}
</Stack>
</Box>
</Box>
))}
</Stack>
</MainCard>
</Grid>
</Grid>
<Toast {...toast} />
</>
);
}

200
src/pages/fleet/Fleet.jsx Normal file
View File

@@ -0,0 +1,200 @@
import { useState } from 'react';
import { Grid, Card, CardContent, Stack, Typography, Box, Avatar, LinearProgress, Table, TableBody, TableCell, TableHead, TableRow, Button, Tooltip, TextField, MenuItem } from '@mui/material';
import ElectricRickshawOutlinedIcon from '@mui/icons-material/ElectricRickshawOutlined';
import BatteryChargingFullOutlinedIcon from '@mui/icons-material/BatteryChargingFullOutlined';
import EnergySavingsLeafOutlinedIcon from '@mui/icons-material/EnergySavingsLeafOutlined';
import HealthAndSafetyOutlinedIcon from '@mui/icons-material/HealthAndSafetyOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import AddOutlinedIcon from '@mui/icons-material/AddOutlined';
import BoltOutlinedIcon from '@mui/icons-material/BoltOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import DonutChart from '@/components/charts/DonutChart';
import FormDialog from '@/components/FormDialog';
import Toast, { useToast } from '@/components/Toast';
import { fleet, fleetSummary } from '@/data/mock';
const BLANK = { id: '', model: '', type: 'EV 4W', powertrain: 'EV', capacityKg: '', hub: 'Koramangala Micro Hub' };
export default function Fleet() {
const [rows, setRows] = useState(fleet);
const [open, setOpen] = useState(false);
const [form, setForm] = useState(BLANK);
const [toast, showToast] = useToast();
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
const addVehicle = () => {
if (!form.model.trim()) return showToast('Enter a vehicle model', 'warning');
const isEv = form.powertrain === 'EV';
const id = form.id.trim() || `VH-${isEv ? 'EV' : 'IC'}-${String(100 + rows.length).slice(-3)}`;
setRows((r) => [
{ id, model: form.model, type: form.type, powertrain: form.powertrain, battery: isEv ? 100 : null, range: isEv ? 110 : 300, health: 100, capacityKg: Number(form.capacityKg) || 500, status: 'idle', rider: '—', hub: form.hub, uptime: 100 },
...r
]);
setForm(BLANK);
setOpen(false);
showToast(`${form.model} added to fleet`);
};
const mix = [
{ label: 'EV', value: fleetSummary.ev, color: '#00A854' },
{ label: 'ICE', value: fleetSummary.ice, color: '#8C8C8C' }
];
const states = [
{ label: 'On Trip', value: fleetSummary.onTrip, color: '#00A2AE' },
{ label: 'Charging', value: fleetSummary.charging, color: '#1D4ED8' },
{ label: 'Idle', value: fleetSummary.idle, color: '#FFBF00' },
{ label: 'Maintenance', value: fleetSummary.maintenance, color: '#F04134' }
];
return (
<>
<PageHeader
title="Fleet & Rider Operating System"
breadcrumbs={[{ label: 'Fleet' }]}
action={<Button variant="contained" startIcon={<AddOutlinedIcon />} onClick={() => setOpen(true)}>Add Vehicle</Button>}
/>
<LayerBanner
no={4}
icon={ElectricRickshawOutlinedIcon}
color="#15803D"
title="Fleet Management · EV-First"
subtitle="Vehicle health, battery monitoring, capacity & load planning across an EV-native fleet."
steps={['Vehicle Health', 'Battery Monitoring', 'Capacity Check', 'Load Optimization', 'Multi-Trip Planning']}
/>
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Fleet Size" value={fleetSummary.total} icon={LocalShippingOutlinedIcon} color="info" caption={`${fleetSummary.onTrip} on trip now`} /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="EV Share" value={`${fleetSummary.evShare}%`} icon={EnergySavingsLeafOutlinedIcon} color="success" trend={4.2} caption="EV-first target 85%" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Avg Battery" value={`${fleetSummary.avgBattery}%`} icon={BatteryChargingFullOutlinedIcon} color="warning" caption={`${fleetSummary.charging} charging`} /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="CO₂ Saved" value={`${(fleetSummary.co2SavedKg / 1000).toFixed(1)}t`} icon={BoltOutlinedIcon} color="success" trend={9.6} caption="this month" /></Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Powertrain Mix">
<Box sx={{ py: 1 }}>
<DonutChart data={mix} centerValue={`${fleetSummary.evShare}%`} centerLabel="EV" />
</Box>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Fleet Status">
<Box sx={{ py: 1 }}>
<DonutChart data={states} centerValue={fleetSummary.total} centerLabel="Vehicles" />
</Box>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Operating Health">
<Stack spacing={2} sx={{ py: 0.5 }}>
<Metric icon={HealthAndSafetyOutlinedIcon} color="#00A854" label="Avg Vehicle Health" value={`${fleetSummary.avgHealth}%`} bar={fleetSummary.avgHealth} />
<Metric icon={BatteryChargingFullOutlinedIcon} color="#1D4ED8" label="Avg Battery (EV)" value={`${fleetSummary.avgBattery}%`} bar={fleetSummary.avgBattery} />
<Metric icon={EnergySavingsLeafOutlinedIcon} color="#15803D" label="EV Fleet Share" value={`${fleetSummary.evShare}%`} bar={fleetSummary.evShare} />
</Stack>
</MainCard>
</Grid>
<Grid item xs={12}>
<MainCard title="Vehicles" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Vehicle</TableCell>
<TableCell>Type</TableCell>
<TableCell>Battery / Range</TableCell>
<TableCell>Health</TableCell>
<TableCell align="right">Capacity</TableCell>
<TableCell>Assigned</TableCell>
<TableCell>Hub</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((v) => (
<TableRow key={v.id} hover>
<TableCell>
<Stack direction="row" spacing={1.25} alignItems="center">
<Avatar variant="rounded" sx={{ bgcolor: v.powertrain === 'EV' ? 'success.lighter' : 'grey.100', color: v.powertrain === 'EV' ? 'success.main' : 'grey.600', width: 36, height: 36 }}>
{v.powertrain === 'EV' ? <BoltOutlinedIcon fontSize="small" /> : <LocalShippingOutlinedIcon fontSize="small" />}
</Avatar>
<Box>
<Typography variant="subtitle2">{v.model}</Typography>
<Typography variant="caption" color="text.secondary">{v.id}</Typography>
</Box>
</Stack>
</TableCell>
<TableCell><Typography variant="caption">{v.type}</Typography></TableCell>
<TableCell sx={{ minWidth: 130 }}>
{v.powertrain === 'EV' ? (
<>
<Stack direction="row" justifyContent="space-between">
<Typography variant="caption" color="text.secondary">{v.battery}%</Typography>
<Typography variant="caption" color="text.secondary">{v.range} km</Typography>
</Stack>
<LinearProgress variant="determinate" value={v.battery} color={v.battery < 30 ? 'error' : v.battery < 50 ? 'warning' : 'success'} sx={{ height: 6, borderRadius: 3, mt: 0.5 }} />
</>
) : (
<Typography variant="caption" color="text.secondary">Fuel · {v.range} km range</Typography>
)}
</TableCell>
<TableCell>
<Tooltip title={`Uptime ${v.uptime}%`}>
<Typography variant="subtitle2" sx={{ color: v.health < 80 ? 'warning.main' : 'success.main' }}>{v.health}%</Typography>
</Tooltip>
</TableCell>
<TableCell align="right">{v.capacityKg} kg</TableCell>
<TableCell>{v.rider}</TableCell>
<TableCell><Typography variant="caption" color="text.secondary">{v.hub}</Typography></TableCell>
<TableCell><StatusChip status={v.status} /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</MainCard>
</Grid>
</Grid>
<FormDialog open={open} onClose={() => setOpen(false)} title="Add Vehicle" onSubmit={addVehicle} submitLabel="Add Vehicle">
<Grid container spacing={2} sx={{ mt: 0 }}>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Vehicle ID (optional)" value={form.id} onChange={set('id')} placeholder="auto-generated" /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Model" value={form.model} onChange={set('model')} placeholder="e.g. Tata Ace EV" /></Grid>
<Grid item xs={12} sm={6}>
<TextField select fullWidth size="small" label="Powertrain" value={form.powertrain} onChange={set('powertrain')}>
<MenuItem value="EV">EV</MenuItem>
<MenuItem value="ICE">ICE</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<TextField select fullWidth size="small" label="Type" value={form.type} onChange={set('type')}>
{['EV 2W', 'EV 3W', 'EV 4W', 'ICE 2W', 'ICE 4W'].map((t) => <MenuItem key={t} value={t}>{t}</MenuItem>)}
</TextField>
</Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" type="number" label="Capacity (kg)" value={form.capacityKg} onChange={set('capacityKg')} /></Grid>
<Grid item xs={12} sm={6}>
<TextField select fullWidth size="small" label="Home Hub" value={form.hub} onChange={set('hub')}>
{['Koramangala Micro Hub', 'Whitefield City Hub', 'Hoskote Regional Hub', 'Andheri City Hub', 'Hitech Cross Dock', 'Bilaspur Regional Hub'].map((h) => <MenuItem key={h} value={h}>{h}</MenuItem>)}
</TextField>
</Grid>
</Grid>
</FormDialog>
<Toast {...toast} />
</>
);
}
function Metric({ icon: Icon, color, label, value, bar }) {
return (
<Box>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
<Icon sx={{ fontSize: 18, color }} />
<Typography variant="caption" color="text.secondary" sx={{ flexGrow: 1 }}>{label}</Typography>
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>{value}</Typography>
</Stack>
<LinearProgress variant="determinate" value={bar} sx={{ height: 6, borderRadius: 3, '& .MuiLinearProgress-bar': { bgcolor: color } }} />
</Box>
);
}

View File

@@ -0,0 +1,199 @@
import { useState } from 'react';
import { Grid, Card, CardContent, Stack, Typography, Box, Avatar, LinearProgress, Table, TableBody, TableCell, TableHead, TableRow, Button, Divider, TextField, MenuItem } from '@mui/material';
import WarehouseOutlinedIcon from '@mui/icons-material/WarehouseOutlined';
import HubOutlinedIcon from '@mui/icons-material/HubOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined';
import AddLocationAltOutlinedIcon from '@mui/icons-material/AddLocationAltOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import MapPlaceholder from '@/components/MapPlaceholder';
import FormDialog from '@/components/FormDialog';
import Toast, { useToast } from '@/components/Toast';
import { hubs, hubNetworkTypes, lineHauls } from '@/data/mock';
const BLANK = { name: '', type: 'Micro Hub', city: 'Bengaluru', capacity: '', dock: '' };
export default function HubNetwork() {
const [rows, setRows] = useState(hubs);
const [open, setOpen] = useState(false);
const [form, setForm] = useState(BLANK);
const [toast, showToast] = useToast();
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
const addHub = () => {
if (!form.name.trim()) return showToast('Enter a hub name', 'warning');
const cityCode = (form.city.slice(0, 3) || 'HUB').toUpperCase();
setRows((r) => [
...r,
{ id: `HUB-${cityCode}-${String(10 + r.length).slice(-2)}`, name: form.name, type: form.type, city: form.city, lat: 0, lng: 0, capacity: Number(form.capacity) || 1000, load: 0, inbound: 0, outbound: 0, dock: Number(form.dock) || 4, status: 'online' }
]);
setForm(BLANK);
setOpen(false);
showToast(`${form.name} added to network`);
};
const totalCap = rows.reduce((s, h) => s + h.capacity, 0);
const totalLoad = rows.reduce((s, h) => s + h.load, 0);
const util = Math.round((totalLoad / totalCap) * 100);
return (
<>
<PageHeader
title="Hub & Network Orchestration"
breadcrumbs={[{ label: 'Hub Network' }]}
action={<Button variant="contained" startIcon={<AddLocationAltOutlinedIcon />} onClick={() => setOpen(true)}>Add Hub</Button>}
/>
<LayerBanner
no={3}
icon={HubOutlinedIcon}
color="#0E7C7B"
title="Intelligent Hub Network"
subtitle="MileTruth AI selects the optimal origin hub, then sorts, line-hauls and routes to destination."
steps={['Nearest Hub', 'Pickup Assignment', 'Hub Operations', 'Line-Haul Transfer', 'Destination Hub']}
/>
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Active Hubs" value={hubs.length} icon={WarehouseOutlinedIcon} color="info" caption="across 4 cities" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Network Utilisation" value={`${util}%`} icon={HubOutlinedIcon} color="warning" trend={3.1} caption="vs yesterday" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Line-Hauls Running" value={lineHauls.filter((l) => l.status === 'in-transit').length} icon={LocalShippingOutlinedIcon} color="primary" caption="inter-city" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Throughput Today" value={totalLoad.toLocaleString('en-IN')} icon={RouteOutlinedIcon} color="success" trend={6.4} caption="parcels sorted" /></Grid>
<Grid item xs={12} lg={8}>
<MainCard title="Hub Load & Capacity" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Hub</TableCell>
<TableCell>Type</TableCell>
<TableCell>City</TableCell>
<TableCell>Load / Capacity</TableCell>
<TableCell align="center">Docks</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((h) => {
const pct = Math.round((h.load / h.capacity) * 100);
return (
<TableRow key={h.id} hover>
<TableCell>
<Typography variant="subtitle2">{h.name}</Typography>
<Typography variant="caption" color="text.secondary">{h.id}</Typography>
</TableCell>
<TableCell><Typography variant="caption">{h.type}</Typography></TableCell>
<TableCell>{h.city}</TableCell>
<TableCell sx={{ minWidth: 160 }}>
<Stack direction="row" justifyContent="space-between">
<Typography variant="caption" color="text.secondary">{h.load.toLocaleString('en-IN')} / {h.capacity.toLocaleString('en-IN')}</Typography>
<Typography variant="caption" sx={{ fontWeight: 600 }}>{pct}%</Typography>
</Stack>
<LinearProgress variant="determinate" value={pct} color={pct > 90 ? 'error' : pct > 75 ? 'warning' : 'success'} sx={{ height: 6, borderRadius: 3, mt: 0.5 }} />
</TableCell>
<TableCell align="center">{h.dock}</TableCell>
<TableCell><StatusChip status={h.status} /></TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</MainCard>
</Grid>
<Grid item xs={12} lg={4}>
<MainCard title="Network Types">
<Stack divider={<Divider />} spacing={0}>
{hubNetworkTypes.map((t) => (
<Stack key={t.type} direction="row" spacing={2} alignItems="center" sx={{ py: 1.4 }}>
<Avatar variant="rounded" sx={{ bgcolor: 'info.lighter', color: 'info.main', width: 40, height: 40 }}>
<WarehouseOutlinedIcon fontSize="small" />
</Avatar>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="subtitle2">{t.type}</Typography>
<Typography variant="caption" color="text.secondary">{t.desc}</Typography>
</Box>
<Typography variant="h4" sx={{ fontWeight: 700, color: 'grey.800' }}>{t.count}</Typography>
</Stack>
))}
</Stack>
</MainCard>
</Grid>
<Grid item xs={12} lg={7}>
<MainCard title="Line-Haul Corridors" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Corridor</TableCell>
<TableCell>Vehicle</TableCell>
<TableCell align="right">Distance</TableCell>
<TableCell>Load</TableCell>
<TableCell>ETA</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{lineHauls.map((l) => (
<TableRow key={l.id} hover>
<TableCell>
<Typography variant="subtitle2">{l.corridor}</Typography>
<Typography variant="caption" color="text.secondary">{l.from} {l.to}</Typography>
</TableCell>
<TableCell><Typography variant="caption">{l.vehicle}</Typography></TableCell>
<TableCell align="right">{l.distance.toLocaleString('en-IN')} km</TableCell>
<TableCell sx={{ minWidth: 90 }}>
<LinearProgress variant="determinate" value={l.load} sx={{ height: 6, borderRadius: 3 }} />
<Typography variant="caption" color="text.secondary">{l.load}%</Typography>
</TableCell>
<TableCell>{l.eta}</TableCell>
<TableCell><StatusChip status={l.status} /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</MainCard>
</Grid>
<Grid item xs={12} lg={5}>
<MainCard title="Network Map">
<MapPlaceholder
height={320}
label="Hub Network"
showRoute={false}
pins={[
{ x: '24%', y: '60%', label: 'BLR', color: '#0E7C7B' },
{ x: '14%', y: '38%', label: 'MUM', color: '#1D4ED8' },
{ x: '40%', y: '34%', label: 'HYD', color: '#EA580C' },
{ x: '46%', y: '14%', label: 'DEL', color: '#C01227' }
]}
/>
</MainCard>
</Grid>
</Grid>
<FormDialog open={open} onClose={() => setOpen(false)} title="Add Hub" onSubmit={addHub} submitLabel="Add Hub">
<Grid container spacing={2} sx={{ mt: 0 }}>
<Grid item xs={12}><TextField fullWidth size="small" label="Hub Name" value={form.name} onChange={set('name')} placeholder="e.g. Electronic City Micro Hub" /></Grid>
<Grid item xs={12} sm={6}>
<TextField select fullWidth size="small" label="Type" value={form.type} onChange={set('type')}>
{['Micro Hub', 'City Hub', 'Regional Hub', 'Cross Dock'].map((t) => <MenuItem key={t} value={t}>{t}</MenuItem>)}
</TextField>
</Grid>
<Grid item xs={12} sm={6}>
<TextField select fullWidth size="small" label="City" value={form.city} onChange={set('city')}>
{['Bengaluru', 'Mumbai', 'Delhi NCR', 'Hyderabad', 'Chennai', 'Pune'].map((c) => <MenuItem key={c} value={c}>{c}</MenuItem>)}
</TextField>
</Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" type="number" label="Capacity (parcels)" value={form.capacity} onChange={set('capacity')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" type="number" label="Docks" value={form.dock} onChange={set('dock')} /></Grid>
</Grid>
</FormDialog>
<Toast {...toast} />
</>
);
}

View File

@@ -0,0 +1,133 @@
import { useState } from 'react';
import { Grid, Card, CardContent, Stack, Typography, Box, Avatar, Button, Chip, TextField, MenuItem } from '@mui/material';
import HubOutlinedIcon from '@mui/icons-material/HubOutlined';
import ApiOutlinedIcon from '@mui/icons-material/ApiOutlined';
import BusinessOutlinedIcon from '@mui/icons-material/BusinessOutlined';
import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined';
import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined';
import AddLinkOutlinedIcon from '@mui/icons-material/AddLinkOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import FormDialog from '@/components/FormDialog';
import Toast, { useToast } from '@/components/Toast';
import { integrations } from '@/data/mock';
const GROUP_META = {
'APIs & Integrations': { icon: ApiOutlinedIcon, color: '#0F766E' },
'Enterprise Systems': { icon: BusinessOutlinedIcon, color: '#1D4ED8' },
'Payment & Billing': { icon: PaymentsOutlinedIcon, color: '#15803D' },
'Partners & Ecosystem': { icon: GroupsOutlinedIcon, color: '#EA580C' }
};
const BLANK = { name: '', group: 'APIs & Integrations', desc: '' };
export default function Integrations() {
const [rows, setRows] = useState(integrations);
const [open, setOpen] = useState(false);
const [form, setForm] = useState(BLANK);
const [toast, showToast] = useToast();
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
const addIntegration = () => {
if (!form.name.trim()) return showToast('Enter an integration name', 'warning');
const meta = GROUP_META[form.group] || {};
setRows((r) => [...r, { name: form.name, group: form.group, desc: form.desc || 'Custom connector', status: 'pending', calls: '—', icon: form.group === 'Payment & Billing' ? 'pay' : form.group === 'Enterprise Systems' ? 'erp' : form.group === 'Partners & Ecosystem' ? 'partner' : 'api' }]);
setForm(BLANK);
setOpen(false);
showToast(`${form.name} added — pending connection`);
};
const groups = [...new Set(rows.map((i) => i.group))];
const connected = rows.filter((i) => i.status === 'connected').length;
const issues = rows.filter((i) => i.status !== 'connected').length;
return (
<>
<PageHeader
title="Integrations & Ecosystem"
breadcrumbs={[{ label: 'Integrations' }]}
action={<Button variant="contained" startIcon={<AddLinkOutlinedIcon />} onClick={() => setOpen(true)}>Add Integration</Button>}
/>
<LayerBanner
no={8}
icon={HubOutlinedIcon}
color="#0F766E"
title="Integrations & Ecosystem"
subtitle="One system that plugs into partner APIs, ERP/WMS, payments and the broader logistics ecosystem."
steps={['APIs & Integrations', 'Enterprise Systems', 'Payment & Billing', 'Partners & Ecosystem']}
/>
<Grid container spacing={2.5} sx={{ mb: 0.5 }}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Connectors" value={rows.length} icon={HubOutlinedIcon} color="info" caption="across 4 categories" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Connected" value={connected} icon={ApiOutlinedIcon} color="success" caption="healthy" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Attention" value={issues} icon={BusinessOutlinedIcon} color="warning" caption="degraded / pending" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="API Calls" value="7.2M" icon={PaymentsOutlinedIcon} color="primary" trend={5.8} caption="per day" /></Grid>
</Grid>
{groups.map((g) => {
const meta = GROUP_META[g] || { icon: HubOutlinedIcon, color: '#C01227' };
const GIcon = meta.icon;
return (
<Box key={g} sx={{ mt: 2.5 }}>
<MainCard
title={
<Stack direction="row" spacing={1.25} alignItems="center">
<Avatar variant="rounded" sx={{ bgcolor: hexA(meta.color, 0.12), color: meta.color, width: 34, height: 34 }}>
<GIcon fontSize="small" />
</Avatar>
<Typography variant="h5">{g}</Typography>
</Stack>
}
>
<Grid container spacing={2}>
{rows.filter((i) => i.group === g).map((i) => (
<Grid item xs={12} sm={6} lg={4} key={i.name}>
<Card variant="outlined" sx={{ height: '100%' }}>
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>{i.name}</Typography>
<Typography variant="caption" color="text.secondary">{i.desc}</Typography>
</Box>
<StatusChip status={i.status} />
</Stack>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mt: 1.5 }}>
<Chip size="small" label={i.calls} sx={{ bgcolor: 'grey.100' }} />
<Button size="small" onClick={() => showToast(i.status === 'pending' ? `Connecting ${i.name}` : `${i.name} settings opened`)}>
{i.status === 'pending' ? 'Connect' : 'Configure'}
</Button>
</Stack>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</MainCard>
</Box>
);
})}
<FormDialog open={open} onClose={() => setOpen(false)} title="Add Integration" onSubmit={addIntegration} submitLabel="Add">
<Grid container spacing={2} sx={{ mt: 0 }}>
<Grid item xs={12}><TextField fullWidth size="small" label="Integration Name" value={form.name} onChange={set('name')} placeholder="e.g. Shopify, Freshdesk" /></Grid>
<Grid item xs={12}>
<TextField select fullWidth size="small" label="Category" value={form.group} onChange={set('group')}>
{Object.keys(GROUP_META).map((g) => <MenuItem key={g} value={g}>{g}</MenuItem>)}
</TextField>
</Grid>
<Grid item xs={12}><TextField fullWidth size="small" label="Description" value={form.desc} onChange={set('desc')} placeholder="What does this connector do?" /></Grid>
</Grid>
</FormDialog>
<Toast {...toast} />
</>
);
}
const hexA = (hex, a) => {
const n = parseInt(hex.replace('#', ''), 16);
return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
};

View File

@@ -0,0 +1,60 @@
import { Grid, Stack, Typography, Box, Button } from '@mui/material';
import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined';
import WarehouseOutlinedIcon from '@mui/icons-material/WarehouseOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import LayerBanner from '@/components/LayerBanner';
import ThreeMileStrip from '@/components/ThreeMileStrip';
import MapPlaceholder from '@/components/MapPlaceholder';
import Toast, { useToast } from '@/components/Toast';
import { threeMile } from '@/data/mock';
export default function ThreeMile() {
const [toast, showToast] = useToast();
return (
<>
<PageHeader
title="Three-Mile Network"
breadcrumbs={[{ label: 'Three-Mile Network' }]}
action={<Button variant="outlined" startIcon={<FileDownloadOutlinedIcon />} onClick={() => showToast('Three-mile report exported')}>Export</Button>}
/>
<LayerBanner
no={0}
icon={RouteOutlinedIcon}
color="#C01227"
title="One Connected System · First → Mid → Last Mile"
subtitle="Doormile unifies first-mile pickup, mid-mile line-haul and last-mile delivery into one connected flow."
steps={['Origin to Hub', 'Hub to Hub Transit', 'Hub to Doorstep']}
/>
<Grid container spacing={2.5} sx={{ mb: 2.5 }}>
<Grid item xs={12} sm={4}><StatCard title="First Mile · Pickups" value={threeMile[0].metric} icon={WarehouseOutlinedIcon} color="warning" caption={`${threeMile[0].onTime}% on-time`} /></Grid>
<Grid item xs={12} sm={4}><StatCard title="Mid Mile · Line-Hauls" value={threeMile[1].metric} icon={LocalShippingOutlinedIcon} color="info" caption={`${threeMile[1].onTime}% on-time`} /></Grid>
<Grid item xs={12} sm={4}><StatCard title="Last Mile · Out for Delivery" value={threeMile[2].metric} icon={HomeOutlinedIcon} color="primary" caption={`${threeMile[2].onTime}% on-time`} /></Grid>
</Grid>
<Box sx={{ mb: 2.5 }}>
<ThreeMileStrip />
</Box>
<MainCard title="Connected Miles — Live Flow">
<MapPlaceholder
height={340}
label="First → Mid → Last Mile"
pins={[
{ x: '14%', y: '74%', label: 'Origin', color: '#EA580C' },
{ x: '44%', y: '46%', label: 'Hub', color: '#0E7C7B' },
{ x: '82%', y: '20%', label: 'Doorstep', color: '#1D4ED8' }
]}
/>
</MainCard>
<Toast {...toast} />
</>
);
}

View File

@@ -0,0 +1,234 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Grid, Stack, Typography, Box, Avatar, Table, TableBody, TableCell, TableHead, TableRow, Button, Chip, TextField, InputAdornment, Divider } from '@mui/material';
import MyLocationOutlinedIcon from '@mui/icons-material/MyLocationOutlined';
import PhotoCameraOutlinedIcon from '@mui/icons-material/PhotoCameraOutlined';
import AltRouteOutlinedIcon from '@mui/icons-material/AltRouteOutlined';
import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined';
import ContentCopyOutlinedIcon from '@mui/icons-material/ContentCopyOutlined';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import MapPlaceholder from '@/components/MapPlaceholder';
import FormDialog from '@/components/FormDialog';
import Toast, { useToast } from '@/components/Toast';
import { executionStages, executionFeed, ridersLive, orderTimeline } from '@/data/mock';
export default function LiveTracking() {
const navigate = useNavigate();
const [track, setTrack] = useState(null);
const [share, setShare] = useState(false);
const [toast, showToast] = useToast();
const trackLink = track ? `https://track.doormile.com/${track.id}` : 'https://track.doormile.com';
const copyLink = () => {
if (navigator.clipboard) navigator.clipboard.writeText(trackLink).catch(() => {});
showToast('Tracking link copied');
};
return (
<>
<PageHeader
title="Execution & Visibility Layer"
breadcrumbs={[{ label: 'Live Tracking' }]}
action={<Button variant="outlined" startIcon={<ShareOutlinedIcon />} onClick={() => setShare(true)}>Share Tracking</Button>}
/>
<LayerBanner
no={6}
icon={MyLocationOutlinedIcon}
color="#1D4ED8"
title="Execution & Visibility"
subtitle="Live GPS, photo proof of pickup & delivery, dynamic re-routing and exception handling."
steps={['Pickup Execution', 'In-Transit Tracking', 'Dynamic Re-routing', 'Delivery Execution', 'Exception Handling']}
/>
<Grid container spacing={2.5}>
{executionStages.map((s) => (
<Grid item xs={6} md={2.4} key={s.key}>
<Box sx={{ bgcolor: 'background.paper', border: '1px solid', borderColor: 'grey.200', borderRadius: 2, p: 2, height: '100%' }}>
<Typography variant="h3" sx={{ fontWeight: 700, color: 'grey.800' }}>{s.count.toLocaleString('en-IN')}</Typography>
<Typography variant="subtitle2" sx={{ mt: 0.25 }}>{s.title}</Typography>
<Stack spacing={0.25} sx={{ mt: 1 }}>
{s.items.slice(0, 3).map((it) => (
<Typography key={it} variant="caption" color="text.secondary"> {it}</Typography>
))}
</Stack>
</Box>
</Grid>
))}
<Grid item xs={12} lg={7}>
<MainCard title="Live Fleet Map">
<MapPlaceholder
height={380}
label="Live Tracking"
riders={ridersLive.slice(0, 4).map((r, i) => ({
x: ['28%', '52%', '70%', '40%'][i],
y: ['44%', '30%', '58%', '66%'][i],
active: r.active
}))}
/>
</MainCard>
</Grid>
<Grid item xs={12} lg={5}>
<MainCard title="Live Execution Feed" contentSx={{ p: 0 }}>
<Stack>
{executionFeed.map((e, i) => (
<Box key={e.id} sx={{ p: 2, borderTop: i === 0 ? 'none' : '1px solid', borderColor: 'grey.100' }}>
<Stack direction="row" spacing={1.5} alignItems="flex-start">
<Avatar variant="rounded" sx={{ width: 38, height: 38, ...stageStyle(e.stage) }}>
{stageIcon(e.stage)}
</Avatar>
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="subtitle2" sx={{ color: 'primary.main' }}>{e.id}</Typography>
<Typography variant="caption" color="text.secondary">{e.time}</Typography>
</Stack>
<Stack direction="row" spacing={0.75} alignItems="center" sx={{ my: 0.25 }}>
<StatusChip status={mapStage(e.stage)} label={e.stage} />
{e.proof && <Chip size="small" icon={<PhotoCameraOutlinedIcon sx={{ fontSize: 14 }} />} label="Proof" sx={{ bgcolor: 'grey.100' }} />}
</Stack>
<Typography variant="caption" color="text.secondary">{e.rider} · {e.loc}</Typography>
<Typography variant="caption" sx={{ display: 'block' }}>{e.detail}</Typography>
</Box>
</Stack>
</Box>
))}
</Stack>
</MainCard>
</Grid>
<Grid item xs={12}>
<MainCard title="Customer & Business Visibility" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Shipment</TableCell>
<TableCell>Rider</TableCell>
<TableCell>Current Stage</TableCell>
<TableCell>Location</TableCell>
<TableCell>Update</TableCell>
<TableCell align="right">Tracking</TableCell>
</TableRow>
</TableHead>
<TableBody>
{executionFeed.map((e) => (
<TableRow key={e.id} hover>
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{e.id}</TableCell>
<TableCell>{e.rider}</TableCell>
<TableCell><StatusChip status={mapStage(e.stage)} label={e.stage} /></TableCell>
<TableCell>{e.loc}</TableCell>
<TableCell><Typography variant="caption" color="text.secondary">{e.detail}</Typography></TableCell>
<TableCell align="right">
<Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => setTrack(e)}>Track</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</MainCard>
</Grid>
</Grid>
{/* Tracking detail */}
<FormDialog
open={Boolean(track)}
onClose={() => setTrack(null)}
title={track ? `Tracking · ${track.id}` : 'Tracking'}
hideActions
>
{track && (
<Stack spacing={2}>
<Stack direction="row" spacing={1} alignItems="center">
<StatusChip status={mapStage(track.stage)} label={track.stage} />
<Typography variant="caption" color="text.secondary">Updated {track.time}</Typography>
</Stack>
<Stack direction="row" spacing={3}>
<Box><Typography variant="caption" color="text.secondary">Rider</Typography><Typography variant="subtitle2">{track.rider}</Typography></Box>
<Box><Typography variant="caption" color="text.secondary">Location</Typography><Typography variant="subtitle2">{track.loc}</Typography></Box>
</Stack>
<Typography variant="body2" color="text.secondary">{track.detail}</Typography>
<MapPlaceholder height={180} label={track.id} />
<Box>
<Typography variant="subtitle2" sx={{ mb: 1 }}>Journey</Typography>
<Stack spacing={0}>
{orderTimeline.map((t, i) => (
<Stack key={t.label} direction="row" spacing={1.5} alignItems="center" sx={{ py: 0.75 }}>
{t.done ? <CheckCircleIcon sx={{ color: 'success.main', fontSize: 20 }} /> : <RadioButtonUncheckedIcon sx={{ color: 'grey.400', fontSize: 20 }} />}
<Typography variant="body2" sx={{ flexGrow: 1, fontWeight: t.done ? 600 : 400, color: t.done ? 'text.primary' : 'text.secondary' }}>{t.label}</Typography>
<Typography variant="caption" color="text.secondary">{t.time}</Typography>
</Stack>
))}
</Stack>
</Box>
<Divider />
<Stack direction="row" spacing={1.5} justifyContent="flex-end">
<Button startIcon={<ShareOutlinedIcon />} onClick={() => { setShare(true); }}>Share</Button>
<Button variant="contained" startIcon={<AltRouteOutlinedIcon />} onClick={() => navigate('/tracking/journey')}>View full journey</Button>
</Stack>
</Stack>
)}
</FormDialog>
{/* Share tracking */}
<FormDialog open={share} onClose={() => setShare(false)} title="Share Live Tracking" hideActions>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
Anyone with this link can follow the shipment in real time.
</Typography>
<TextField
fullWidth
size="small"
value={trackLink}
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<Button size="small" startIcon={<ContentCopyOutlinedIcon fontSize="small" />} onClick={copyLink}>Copy</Button>
</InputAdornment>
)
}}
/>
<Stack direction="row" spacing={1.5} sx={{ mt: 2 }}>
<Button variant="contained" onClick={() => { showToast('Tracking sent via SMS'); setShare(false); }}>Send SMS</Button>
<Button variant="outlined" onClick={() => { showToast('Tracking sent on WhatsApp'); setShare(false); }}>WhatsApp</Button>
</Stack>
</FormDialog>
<Toast {...toast} />
</>
);
}
const mapStage = (stage) => {
const k = { 'In-Transit': 'in-transit', 'Picked Up': 'picked-up', Delivered: 'delivered', Exception: 'exception', Dispatched: 'dispatched' };
return k[stage] || 'active';
};
function stageStyle(stage) {
const m = {
'In-Transit': { bgcolor: 'info.lighter', color: 'info.main' },
'Picked Up': { bgcolor: 'primary.lighter', color: 'primary.main' },
Delivered: { bgcolor: 'success.lighter', color: 'success.main' },
Exception: { bgcolor: 'error.lighter', color: 'error.main' },
Dispatched: { bgcolor: 'grey.100', color: 'grey.700' }
};
return m[stage] || m.Dispatched;
}
function stageIcon(stage) {
if (stage === 'Exception') return <WarningAmberOutlinedIcon fontSize="small" />;
if (stage === 'In-Transit' || stage === 'Dispatched') return <AltRouteOutlinedIcon fontSize="small" />;
return <PhotoCameraOutlinedIcon fontSize="small" />;
}

View File

@@ -0,0 +1,174 @@
import { Grid, Stack, Typography, Box, Avatar, Button, Chip, LinearProgress, Divider } from '@mui/material';
import ReceiptLongOutlinedIcon from '@mui/icons-material/ReceiptLongOutlined';
import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined';
import WarehouseOutlinedIcon from '@mui/icons-material/WarehouseOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
import CheckIcon from '@mui/icons-material/Check';
import AltRouteOutlinedIcon from '@mui/icons-material/AltRouteOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined';
import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined';
import PageHeader from '@/components/PageHeader';
import MainCard from '@/components/MainCard';
import MapPlaceholder from '@/components/MapPlaceholder';
import Toast, { useToast } from '@/components/Toast';
import { shipmentJourney as j } from '@/data/mock';
const HOP_ICONS = { order: ReceiptLongOutlinedIcon, agent: TwoWheelerOutlinedIcon, hub: WarehouseOutlinedIcon, truck: LocalShippingOutlinedIcon, done: HomeOutlinedIcon };
export default function ShipmentJourney() {
const [toast, showToast] = useToast();
const doneCount = j.hops.filter((h) => h.status === 'done').length;
return (
<>
<PageHeader
title="Shipment Journey"
breadcrumbs={[{ label: 'Live Tracking', to: '/tracking' }, { label: j.id }]}
action={
<Stack direction="row" spacing={1.5}>
<Button variant="outlined" startIcon={<AltRouteOutlinedIcon />} onClick={() => showToast('MileTruth AI re-evaluated the route — ETA protected')}>Re-optimize</Button>
<Button variant="contained" startIcon={<ShareOutlinedIcon />} onClick={() => showToast('Tracking link shared with customer')}>Share</Button>
</Stack>
}
/>
{/* Summary */}
<Box sx={{ borderRadius: 2, border: '1px solid', borderColor: 'grey.200', bgcolor: 'background.paper', p: { xs: 2, md: 2.5 }, mb: 2.5 }}>
<Grid container spacing={2} alignItems="center">
<Grid item xs={12} md={5}>
<Typography variant="overline" color="text.secondary">{j.id} · {j.client}</Typography>
<Stack direction="row" spacing={1.5} alignItems="center" sx={{ mt: 0.5 }}>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, color: 'grey.900' }}>{j.from.city}</Typography>
<Typography variant="caption" color="text.secondary">{j.from.area}</Typography>
</Box>
<Box sx={{ flexGrow: 1, height: 2, bgcolor: 'grey.200', position: 'relative', mx: 1, maxWidth: 90 }}>
<LocalShippingOutlinedIcon sx={{ fontSize: 18, color: 'primary.main', position: 'absolute', top: -9, left: `${j.progress}%`, transform: 'translateX(-50%)' }} />
</Box>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, color: 'grey.900' }}>{j.to.city}</Typography>
<Typography variant="caption" color="text.secondary">{j.to.area}</Typography>
</Box>
</Stack>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>{j.product} · {j.mode}</Typography>
</Grid>
<Grid item xs={6} md={2}><Summary label="Current stage" value={j.currentStage} small /></Grid>
<Grid item xs={6} md={2}><Summary label="ETA" value={j.eta} /></Grid>
<Grid item xs={6} md={1.5}><Summary label="Distance" value={`${j.distance} km`} /></Grid>
<Grid item xs={6} md={1.5}>
<Typography variant="caption" color="text.secondary">Progress</Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: 'grey.900' }}>{j.progress}%</Typography>
<LinearProgress variant="determinate" value={j.progress} color="primary" sx={{ height: 5, borderRadius: 3, mt: 0.5 }} />
</Grid>
</Grid>
</Box>
<Grid container spacing={2.5}>
{/* Hop-by-hop timeline */}
<Grid item xs={12} lg={7}>
<MainCard title={`Journey · ${doneCount}/${j.hops.length} stages complete`}>
<Box>
{j.hops.map((h, i) => {
const Icon = HOP_ICONS[h.icon] || WarehouseOutlinedIcon;
const last = i === j.hops.length - 1;
const newMile = i === 0 || j.hops[i - 1].mile !== h.mile;
return (
<Box key={h.key}>
{newMile && (
<Typography variant="overline" color="text.secondary" sx={{ display: 'block', mt: i === 0 ? 0 : 1.5, mb: 0.5, letterSpacing: '0.1em' }}>
{h.mile}
</Typography>
)}
<Stack direction="row" spacing={1.75}>
{/* rail */}
<Stack alignItems="center" sx={{ width: 36 }}>
<Avatar
sx={{
width: 34,
height: 34,
bgcolor: h.status === 'done' ? 'success.main' : h.status === 'active' ? 'primary.main' : 'grey.100',
color: h.status === 'pending' ? 'grey.500' : '#fff',
border: h.status === 'pending' ? '1px solid' : 'none',
borderColor: 'grey.300'
}}
>
{h.status === 'done' ? <CheckIcon sx={{ fontSize: 18 }} /> : <Icon sx={{ fontSize: 18 }} />}
</Avatar>
{!last && <Box sx={{ flexGrow: 1, width: 2, minHeight: 26, bgcolor: h.status === 'done' ? 'success.light' : 'grey.200', my: 0.25 }} />}
</Stack>
{/* content */}
<Box sx={{ pb: last ? 0 : 2, flexGrow: 1, minWidth: 0 }}>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: h.status === 'pending' ? 'text.secondary' : 'grey.900' }}>{h.title}</Typography>
{h.status === 'active' && <Chip size="small" label="In progress" sx={{ bgcolor: 'primary.lighter', color: 'primary.dark', fontWeight: 600 }} />}
</Stack>
<Typography variant="caption" sx={{ display: 'block', color: 'grey.800', fontWeight: 600 }}>{h.node}</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>{h.handler}</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25 }}>{h.detail}</Typography>
<Typography variant="caption" sx={{ color: h.status === 'pending' ? 'grey.400' : 'text.secondary', fontStyle: h.status === 'pending' ? 'italic' : 'normal' }}>{h.time}</Typography>
</Box>
</Stack>
</Box>
);
})}
</Box>
</MainCard>
</Grid>
{/* Map + live monitoring */}
<Grid item xs={12} lg={5}>
<Stack spacing={2.5}>
<MainCard title="Live Route">
<MapPlaceholder
height={240}
label={`${j.from.city}${j.to.city}`}
pins={[
{ x: '16%', y: '74%', label: 'Chennai', color: '#00A854' },
{ x: '48%', y: '50%', label: 'Line-haul', color: '#C01227' },
{ x: '80%', y: '24%', label: 'Bengaluru', color: '#595959' }
]}
/>
</MainCard>
<MainCard
title={
<Stack direction="row" spacing={1} alignItems="center">
<Avatar variant="rounded" sx={{ bgcolor: 'primary.lighter', color: 'primary.main', width: 30, height: 30 }}><AutoAwesomeOutlinedIcon sx={{ fontSize: 18 }} /></Avatar>
<Typography variant="h5">Live Monitoring & Reroute</Typography>
</Stack>
}
>
<Stack divider={<Divider />} spacing={0}>
{j.events.map((e, i) => (
<Stack key={i} direction="row" spacing={1.5} alignItems="flex-start" sx={{ py: 1.25 }}>
<Avatar variant="rounded" sx={{ width: 32, height: 32, bgcolor: e.type === 'reroute' ? 'warning.lighter' : 'grey.100', color: e.type === 'reroute' ? 'warning.dark' : 'grey.600' }}>
{e.type === 'reroute' ? <AltRouteOutlinedIcon sx={{ fontSize: 18 }} /> : <VisibilityOutlinedIcon sx={{ fontSize: 18 }} />}
</Avatar>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>{e.title}</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>{e.detail}</Typography>
<Typography variant="caption" color="text.secondary">{e.time}</Typography>
</Box>
</Stack>
))}
</Stack>
</MainCard>
</Stack>
</Grid>
</Grid>
<Toast {...toast} />
</>
);
}
function Summary({ label, value, small }) {
return (
<Box>
<Typography variant="caption" color="text.secondary">{label}</Typography>
<Typography variant={small ? 'subtitle2' : 'h5'} sx={{ fontWeight: 700, color: 'grey.900', lineHeight: 1.2 }}>{value}</Typography>
</Box>
);
}

View File

@@ -0,0 +1,169 @@
import { useState } from 'react';
import { Grid, Card, CardContent, Stack, Typography, Box, Avatar, LinearProgress, Table, TableBody, TableCell, TableHead, TableRow, Button, Divider } from '@mui/material';
import VerifiedUserOutlinedIcon from '@mui/icons-material/VerifiedUserOutlined';
import BadgeOutlinedIcon from '@mui/icons-material/BadgeOutlined';
import GppMaybeOutlinedIcon from '@mui/icons-material/GppMaybeOutlined';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import LinkOutlinedIcon from '@mui/icons-material/LinkOutlined';
import GavelOutlinedIcon from '@mui/icons-material/GavelOutlined';
import SecurityOutlinedIcon from '@mui/icons-material/SecurityOutlined';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import PageHeader from '@/components/PageHeader';
import StatCard from '@/components/StatCard';
import MainCard from '@/components/MainCard';
import StatusChip from '@/components/StatusChip';
import LayerBanner from '@/components/LayerBanner';
import FormDialog from '@/components/FormDialog';
import { trustChecks, trustQueue } from '@/data/mock';
const ICONS = {
kyc: VerifiedUserOutlinedIcon,
id: BadgeOutlinedIcon,
fraud: GppMaybeOutlinedIcon,
tamper: LockOutlinedIcon,
custody: LinkOutlinedIcon,
compliance: GavelOutlinedIcon
};
export default function TrustCompliance() {
const [audit, setAudit] = useState(false);
const cleared = trustQueue.filter((t) => t.status === 'cleared').length;
const review = trustQueue.filter((t) => t.status === 'review').length;
const hold = trustQueue.filter((t) => t.status === 'hold').length;
return (
<>
<PageHeader
title="Trust & Identity Layer"
breadcrumbs={[{ label: 'Trust & Identity' }]}
action={<Button variant="outlined" startIcon={<FileDownloadOutlinedIcon />} onClick={() => setAudit(true)}>Audit Log</Button>}
/>
<LayerBanner
no={2}
icon={SecurityOutlinedIcon}
color="#5B5BD6"
title="Trust, Security & Compliance"
subtitle="Every shipment is verified, screened and sealed before it moves."
steps={['KYC Verification', 'ID Verification', 'Fraud Detection', 'Tamper Protection', 'Chain of Custody', 'Compliance Engine']}
/>
<Grid container spacing={2.5} sx={{ mb: 0.5 }}>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Cleared Rate" value="99.2%" icon={VerifiedUserOutlinedIcon} color="success" trend={0.4} caption="this week" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="In Review" value={review} icon={BadgeOutlinedIcon} color="warning" caption="awaiting verification" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="On Hold" value={hold} icon={GppMaybeOutlinedIcon} color="error" caption="fraud flags" /></Grid>
<Grid item xs={12} sm={6} lg={3}><StatCard title="Cleared Today" value={cleared} icon={LockOutlinedIcon} color="info" caption="seals logged" /></Grid>
</Grid>
<Grid container spacing={2.5} sx={{ mt: 0 }}>
{trustChecks.map((c) => {
const Icon = ICONS[c.icon] || VerifiedUserOutlinedIcon;
return (
<Grid item xs={12} sm={6} lg={4} key={c.key}>
<Card sx={{ height: '100%' }}>
<CardContent>
<Stack direction="row" spacing={1.5} alignItems="center" sx={{ mb: 1.5 }}>
<Avatar variant="rounded" sx={{ bgcolor: 'primary.lighter', color: 'primary.main', width: 44, height: 44 }}>
<Icon fontSize="small" />
</Avatar>
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>{c.title}</Typography>
<Typography variant="caption" color="text.secondary">{c.desc}</Typography>
</Box>
</Stack>
<Stack direction="row" justifyContent="space-between" sx={{ mb: 0.5 }}>
<Typography variant="caption" color="text.secondary">Pass rate</Typography>
<Typography variant="caption" sx={{ fontWeight: 700, color: 'grey.900' }}>{c.passRate}%</Typography>
</Stack>
<LinearProgress
variant="determinate"
value={c.passRate}
sx={{ height: 6, borderRadius: 3, bgcolor: 'grey.100', '& .MuiLinearProgress-bar': { bgcolor: 'grey.800' } }}
/>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
{c.pending} pending review
</Typography>
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
<Box sx={{ mt: 2.5 }}>
<MainCard title="Verification & Fraud Queue" noPadding>
<Table>
<TableHead>
<TableRow>
<TableCell>Shipment</TableCell>
<TableCell>Entity</TableCell>
<TableCell>Check</TableCell>
<TableCell>Risk</TableCell>
<TableCell>Risk Score</TableCell>
<TableCell>Signal</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{trustQueue.map((r) => (
<TableRow key={r.id} hover>
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{r.id}</TableCell>
<TableCell>
<Typography variant="subtitle2">{r.entity}</Typography>
<Typography variant="caption" color="text.secondary">{r.type}</Typography>
</TableCell>
<TableCell>{r.check}</TableCell>
<TableCell><StatusChip status={r.risk} /></TableCell>
<TableCell>
<Stack direction="row" spacing={1} alignItems="center">
<Box sx={{ width: 60 }}>
<LinearProgress
variant="determinate"
value={r.score}
color={r.score > 60 ? 'error' : r.score > 35 ? 'warning' : 'success'}
sx={{ height: 6, borderRadius: 3 }}
/>
</Box>
<Typography variant="caption" sx={{ fontWeight: 600 }}>{r.score}</Typography>
</Stack>
</TableCell>
<TableCell><Typography variant="caption" color="text.secondary">{r.flagged}</Typography></TableCell>
<TableCell><StatusChip status={r.status} /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</MainCard>
</Box>
<FormDialog open={audit} onClose={() => setAudit(false)} title="Chain-of-Custody Audit Log" maxWidth="md" hideActions>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
Immutable, tamper-evident log of every verification & custody event.
</Typography>
<Stack divider={<Divider />} spacing={0}>
{trustQueue.map((r, i) => (
<Stack key={r.id} direction="row" spacing={1.5} alignItems="center" sx={{ py: 1.1 }}>
<Box sx={{ width: 70 }}>
<Typography variant="caption" color="text.secondary">10:{42 - i}:0{i}</Typography>
</Box>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="subtitle2">{r.id} · {r.check}</Typography>
<Typography variant="caption" color="text.secondary">{r.entity} {r.flagged}</Typography>
</Box>
<StatusChip status={r.status} />
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: 'grey.500', display: { xs: 'none', sm: 'block' } }}>
0x{(r.id.length * 7 + r.score).toString(16)}a{i}f3
</Typography>
</Stack>
))}
</Stack>
</FormDialog>
</>
);
}
const hexA = (hex, a) => {
const n = parseInt(hex.replace('#', ''), 16);
return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
};