diff --git a/src/App.jsx b/src/App.jsx index a50e7b9..32d0083 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -35,6 +35,19 @@ export default function App() { import('@/pages/Deliveries'))} /> + {/* Logistics Operating System — layer surfaces */} + import('@/pages/network/ThreeMile'))} /> + import('@/pages/coldchain/ColdChain'))} /> + import('@/pages/trust/TrustCompliance'))} /> + import('@/pages/hubs/HubNetwork'))} /> + import('@/pages/fleet/Fleet'))} /> + import('@/pages/dispatch/AiDispatch'))} /> + import('@/pages/tracking/LiveTracking'))} /> + import('@/pages/tracking/ShipmentJourney'))} /> + import('@/pages/tracking/ShipmentJourney'))} /> + import('@/pages/analytics/Analytics'))} /> + import('@/pages/integrations/Integrations'))} /> + import('@/pages/tenants/Tenants'))} /> import('@/pages/tenants/CreateClient'))} /> @@ -55,7 +68,6 @@ export default function App() { import('@/pages/invoice/Invoices'))} /> import('@/pages/invoice/InvoicePreview'))} /> - import('@/pages/Requests'))} /> import('@/pages/Profile'))} /> import('@/pages/Settings'))} /> diff --git a/src/components/FormDialog.jsx b/src/components/FormDialog.jsx new file mode 100644 index 0000000..ce93589 --- /dev/null +++ b/src/components/FormDialog.jsx @@ -0,0 +1,24 @@ +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, IconButton } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; + +// ==============================|| GENERIC FORM / DETAIL DIALOG ||============================== // + +export default function FormDialog({ open, onClose, title, children, onSubmit, submitLabel = 'Save', maxWidth = 'sm', hideActions = false }) { + return ( + + + {title} + + + + + {children} + {!hideActions && ( + + + {onSubmit && } + + )} + + ); +} diff --git a/src/components/LayerBanner.jsx b/src/components/LayerBanner.jsx new file mode 100644 index 0000000..1b27efd --- /dev/null +++ b/src/components/LayerBanner.jsx @@ -0,0 +1,83 @@ +import { Box, Stack, Typography } from '@mui/material'; +import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded'; + +// ==============================|| LAYER BANNER ||============================== // +// Clean section header for each operating-system page. Flat neutral surface with a +// single subtle accent on the icon tile (no gradients / hero bands). + +export default function LayerBanner({ no, title, subtitle, color = '#C01227', steps = [], icon: Icon }) { + return ( + + + + {Icon ? : {no}} + + + {no ? ( + + Layer {no} + + ) : null} + + {title} + + + {subtitle} + + + + + {steps.length > 0 && ( + + {steps.map((s, i) => ( + + + {s} + + {i < steps.length - 1 && } + + ))} + + )} + + ); +} + +const hexA = (hex, a) => { + const n = parseInt(hex.replace('#', ''), 16); + return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`; +}; diff --git a/src/components/StatusChip.jsx b/src/components/StatusChip.jsx index ce201e2..80499f1 100644 --- a/src/components/StatusChip.jsx +++ b/src/components/StatusChip.jsx @@ -28,7 +28,37 @@ const MAP = { open: { color: 'info', label: 'Open' }, overdue: { color: 'error', label: 'Overdue' }, prepaid: { color: 'success', label: 'Prepaid' }, - cod: { color: 'warning', label: 'COD' } + cod: { color: 'warning', label: 'COD' }, + // trust & risk (Layer 2) + cleared: { color: 'success', label: 'Cleared' }, + review: { color: 'warning', label: 'Review' }, + hold: { color: 'error', label: 'On Hold' }, + low: { color: 'success', label: 'Low' }, + medium: { color: 'warning', label: 'Medium' }, + high: { color: 'error', label: 'High' }, + // hub network (Layer 3) + busy: { color: 'warning', label: 'Busy' }, + scheduled: { color: 'info', label: 'Scheduled' }, + loading: { color: 'warning', label: 'Loading' }, + // fleet (Layer 4) + 'on-trip': { color: 'info', label: 'On Trip' }, + charging: { color: 'info', label: 'Charging' }, + idle: { color: 'default', label: 'Idle' }, + maintenance: { color: 'warning', label: 'Maintenance' }, + // AI dispatch (Layer 5) + matched: { color: 'info', label: 'Matched' }, + optimizing: { color: 'warning', label: 'Optimizing' }, + dispatched: { color: 'primary', label: 'Dispatched' }, + // execution (Layer 6) + 'picked-up': { color: 'primary', label: 'Picked Up' }, + exception: { color: 'error', label: 'Exception' }, + // integrations (Layer 8) + connected: { color: 'success', label: 'Connected' }, + degraded: { color: 'warning', label: 'Degraded' }, + // cold chain (pharma) + 'in-range': { color: 'success', label: 'In Range' }, + 'at-risk': { color: 'warning', label: 'At Risk' }, + breach: { color: 'error', label: 'Breach' } }; const TONE = { diff --git a/src/components/SystemPipeline.jsx b/src/components/SystemPipeline.jsx new file mode 100644 index 0000000..d682906 --- /dev/null +++ b/src/components/SystemPipeline.jsx @@ -0,0 +1,77 @@ +import { Box, Stack, Typography } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; + +import { systemLayers } from '@/data/mock'; + +// ==============================|| SYSTEM PIPELINE (8-layer operating-system strip) ||============================== // +// Restrained, monochrome cards — neutral surface, dark metrics, brand-red only on hover, +// a single semantic health dot. No rainbow borders / colored badges. + +const ROUTES = { + book: '/orders', + trust: '/trust', + hubs: '/hubs', + fleet: '/fleet', + dispatch: '/dispatch', + execution: '/tracking', + analytics: '/analytics', + integrations: '/integrations' +}; + +export default function SystemPipeline() { + const navigate = useNavigate(); + + return ( + + {systemLayers.map((l) => ( + navigate(ROUTES[l.key])} + sx={{ + cursor: 'pointer', + height: '100%', + borderRadius: 1.5, + p: 1.75, + bgcolor: 'background.paper', + border: '1px solid', + borderColor: 'grey.200', + transition: 'border-color .15s ease, box-shadow .15s ease', + '&:hover': { borderColor: 'primary.main', boxShadow: '0 1px 2px rgba(16,24,40,0.08)' } + }} + > + + + {l.no} + + + + {l.title} + {l.subtitle} + {l.metric} + {l.metricLabel} + + ))} + + ); +} diff --git a/src/components/ThreeMileStrip.jsx b/src/components/ThreeMileStrip.jsx new file mode 100644 index 0000000..e088b5b --- /dev/null +++ b/src/components/ThreeMileStrip.jsx @@ -0,0 +1,68 @@ +import { Box, Stack, Typography, LinearProgress } from '@mui/material'; +import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded'; +import WarehouseOutlinedIcon from '@mui/icons-material/WarehouseOutlined'; +import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'; +import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; + +import { threeMile } from '@/data/mock'; + +const ICONS = { first: WarehouseOutlinedIcon, mid: LocalShippingOutlinedIcon, last: HomeOutlinedIcon }; + +// ==============================|| THREE-MILE STRIP (First → Mid → Last Mile) ||============================== // +// Neutral cards, dark metrics, single grey progress — no per-stage rainbow colors. + +export default function ThreeMileStrip({ compact = false }) { + return ( + + {threeMile.map((s, i) => { + const Icon = ICONS[s.key]; + return ( + + + + + + + + {s.title} + {s.subtitle} + + + + {s.metric} + {s.metricLabel} + + {!compact && ( + <> + + On-time + {s.onTime}% + + + + {s.features.map((f) => ( + • {f} + ))} + + + )} + + {i < threeMile.length - 1 && ( + + )} + + ); + })} + + ); +} diff --git a/src/components/Toast.jsx b/src/components/Toast.jsx new file mode 100644 index 0000000..7bc3bef --- /dev/null +++ b/src/components/Toast.jsx @@ -0,0 +1,23 @@ +import { Snackbar, Alert } from '@mui/material'; + +// ==============================|| TOAST / SNACKBAR ||============================== // +// Usage: const [toast, showToast] = useToast(); ... showToast('Saved'); + +import { useState, useCallback } from 'react'; + +export function useToast() { + const [state, setState] = useState({ open: false, message: '', severity: 'success' }); + const show = useCallback((message, severity = 'success') => setState({ open: true, message, severity }), []); + const onClose = useCallback(() => setState((s) => ({ ...s, open: false })), []); + return [{ ...state, onClose }, show]; +} + +export default function Toast({ open, message, severity = 'success', onClose }) { + return ( + + + {message} + + + ); +} diff --git a/src/data/mock.js b/src/data/mock.js index 72b9b53..faef1a7 100644 --- a/src/data/mock.js +++ b/src/data/mock.js @@ -97,12 +97,6 @@ export const invoiceLineItems = [ { particulars: 'COD handling fee', unit: 'order', qty: 260, rate: 6, other: 0, amount: 1560 } ]; -export const requests = [ - { id: 1, requestor: 'Freshly Foods', bank: 'HDFC Bank', ifsc: 'HDFC0001234', refNo: 'RQ-88121', amount: 24500, reason: 'Weekly settlement payout', contact: 'Anil Gupta', address: 'Koramangala 4th Block', city: 'Bengaluru', zip: '560034', accountNo: '5012 3344 7788', pricing: [{ category: 'Standard', skill: 'Bike', cost: 9 }, { category: 'Express', skill: 'Bike', cost: 12 }] }, - { id: 2, requestor: 'UrbanCart', bank: 'ICICI Bank', ifsc: 'ICIC0004567', refNo: 'RQ-88122', amount: 18900, reason: 'Fuel reimbursement', contact: 'Meera Nair', address: 'Indiranagar', city: 'Bengaluru', zip: '560038', accountNo: '6022 1199 4455', pricing: [{ category: 'Standard', skill: 'Bike', cost: 8 }] }, - { id: 3, requestor: 'MediQuick Pharma', bank: 'Axis Bank', ifsc: 'UTIB0007788', refNo: 'RQ-88123', amount: 9700, reason: 'Adjustment - April', contact: 'Rohit Sen', address: 'Andheri West', city: 'Mumbai', zip: '400058', accountNo: '7033 5566 1122', pricing: [{ category: 'Standard', skill: 'Bike', cost: 10 }] } -]; - // reports export const ordersSummary = tenants.map((t, i) => ({ id: t.id, @@ -179,3 +173,247 @@ export const statusBreakdown = [ { label: 'Pending', value: 48, color: '#FFBF00' }, { label: 'Cancelled', value: 18, color: '#F04134' } ]; + +// ==============================|| THREE-MILE MODEL (First / Mid / Last Mile) ||============================== // +// Mirrors doormile.com's primary narrative: Origin → Hub → Hub → Doorstep. +export const threeMile = [ + { + key: 'first', title: 'First Mile', subtitle: 'Origin to Hub', color: '#EA580C', + metric: 184, metricLabel: 'pickups today', onTime: 98.1, + features: ['AI-scheduled pickups', 'Dynamic load consolidation', 'Yard & dock management', 'Pickup quality checks'] + }, + { + key: 'mid', title: 'Mid Mile', subtitle: 'Hub to Hub Transit', color: '#0E7C7B', + metric: 4, metricLabel: 'line-hauls live', onTime: 97.4, + features: ['Optimised line-haul routing', 'Cross-docking & sortation', 'Live SLA monitoring', 'EV-first corridors'] + }, + { + key: 'last', title: 'Last Mile', subtitle: 'Hub to Doorstep', color: '#1D4ED8', + metric: 96, metricLabel: 'out for delivery', onTime: 98.9, + features: ['Multi-stop route optimization', 'Precise delivery windows', 'Digital proof of delivery', 'Real-time customer updates'] + } +]; + +// ==============================|| END-TO-END SHIPMENT JOURNEY (hop-by-hop) ||============================== // +// Follows ONE parcel Chennai → Bengaluru through every node: agent → nearest hub → origin main hub +// → line-haul → destination main hub → sub hub → delivery agent → customer. With live monitoring & reroute. +export const shipmentJourney = { + id: 'DM-CHN-BLR-7741', + product: 'Documents & electronics · 3.2 kg', + from: { city: 'Chennai', area: 'T. Nagar', name: 'Suresh Kumar' }, + to: { city: 'Bengaluru', area: 'Koramangala', name: 'Riya Sharma' }, + client: 'TechNova Retail', + distance: 346, + mode: 'Standard · EV-first', + placed: '08 Jun, 09:12 AM', + eta: '09 Jun, 02:30 PM', + progress: 52, + currentStage: 'Line-Haul · Chennai → Bengaluru', + hops: [ + { key: 'booked', mile: 'First Mile', title: 'Shipment Booked', node: 'Chennai · T. Nagar', handler: 'Customer · Suresh Kumar', icon: 'order', time: '08 Jun, 09:12 AM', status: 'done', detail: 'Shipment ID generated · digital docs & e-waybill created' }, + { key: 'pickup', mile: 'First Mile', title: 'Agent Pickup', node: 'Chennai · T. Nagar', handler: 'Rider · Faisal Khan (EV 2W)', icon: 'agent', time: '08 Jun, 10:05 AM', status: 'done', detail: 'OTP verified · photo proof captured · tamper seal applied' }, + { key: 'nearhub', mile: 'First Mile', title: 'Nearest Hub — Check-in', node: 'Nungambakkam Micro Hub', handler: 'Hub operations', icon: 'hub', time: '08 Jun, 10:48 AM', status: 'done', detail: 'Scanned in · sorted for origin main hub' }, + { key: 'mainhub-out', mile: 'Mid Mile', title: 'Origin Main Hub — Dispatched', node: 'Guindy Regional Hub (Chennai)', handler: 'Hub operations', icon: 'hub', time: '08 Jun, 01:20 PM', status: 'done', detail: 'Consolidated & loaded onto line-haul EV truck' }, + { key: 'linehaul', mile: 'Mid Mile', title: 'Line-Haul In Transit', node: 'NH48 · Chennai → Bengaluru', handler: 'Driver · Imran Sheikh (EV Truck 4W)', icon: 'truck', time: '08 Jun, 01:35 PM', status: 'active', detail: 'En route · live GPS · 178 km to destination hub' }, + { key: 'desthub', mile: 'Mid Mile', title: 'Destination Main Hub — Arrival', node: 'Hoskote Regional Hub (Bengaluru)', handler: 'Hub operations', icon: 'hub', time: 'Est. 09 Jun, 06:30 AM', status: 'pending', detail: 'Inbound scan & sortation' }, + { key: 'subhub', mile: 'Last Mile', title: 'Sub Hub — Last-Mile Sort', node: 'Koramangala Micro Hub', handler: 'Hub operations', icon: 'hub', time: 'Est. 09 Jun, 10:15 AM', status: 'pending', detail: 'Routed to delivery agent zone' }, + { key: 'assigned', mile: 'Last Mile', title: 'Delivery Agent Assigned', node: 'Bengaluru · Koramangala', handler: 'MileTruth AI · auto-assign', icon: 'agent', time: 'Est. 09 Jun, 11:00 AM', status: 'pending', detail: 'Multi-stop route optimized for the agent' }, + { key: 'ofd', mile: 'Last Mile', title: 'Out for Delivery', node: 'Bengaluru · Koramangala', handler: 'Rider · auto-assigned (EV 2W)', icon: 'agent', time: 'Est. 09 Jun, 01:40 PM', status: 'pending', detail: 'Live tracking link shared with customer' }, + { key: 'delivered', mile: 'Last Mile', title: 'Delivered', node: 'Bengaluru · Koramangala', handler: 'Customer · Riya Sharma', icon: 'done', time: 'Est. 09 Jun, 02:30 PM', status: 'pending', detail: 'OTP + photo proof of delivery' } + ], + events: [ + { time: '08 Jun, 03:10 PM', type: 'reroute', title: 'Traffic on NH48 near Krishnagiri', detail: 'MileTruth AI rerouted via Hosur bypass — ETA protected (+0 min)' }, + { time: '08 Jun, 05:40 PM', type: 'monitor', title: 'Weather check · clear', detail: 'No disruption forecast on the corridor' }, + { time: '08 Jun, 08:15 PM', type: 'monitor', title: 'On schedule', detail: 'Vehicle at 62% battery · charging stop planned at Hosur' } + ] +}; + +// ==============================|| INDUSTRY VERTICALS ||============================== // +export const verticals = [ + { key: 'fmcg', label: 'FMCG', desc: 'High-volume, expiry-sensitive', shipments: 612, onTime: 98.9, color: '#EA580C' }, + { key: 'pharma', label: 'Pharma', desc: 'Cold-chain & compliance', shipments: 248, onTime: 99.4, color: '#0E7C7B' }, + { key: 'enterprise', label: 'Enterprise & B2B', desc: 'Appointment & white-glove', shipments: 542, onTime: 97.6, color: '#1D4ED8' } +]; + +// maps each business client to its industry vertical +export const tenantVertical = { + 'Freshly Foods': 'FMCG', + 'GreenLeaf Organics': 'FMCG', + 'BloomBox Florists': 'FMCG', + 'MediQuick Pharma': 'Pharma', + UrbanCart: 'Enterprise & B2B', + 'TechNova Retail': 'Enterprise & B2B' +}; +export const verticalOf = (tenant) => tenantVertical[tenant] || 'Enterprise & B2B'; + +// ==============================|| PHARMA COLD-CHAIN ||============================== // +export const coldChainSummary = { monitored: 248, inRange: 241, atRisk: 5, breaches: 2, compliance: 99.2, avgTemp: 4.2 }; +export const coldChainShipments = [ + { id: 'DM-CC-2041', product: 'Insulin vials', tenant: 'MediQuick Pharma', range: '2 – 8 °C', temp: 4.6, route: 'Andheri → Bandra', status: 'in-range', excursionMin: 0 }, + { id: 'DM-CC-2042', product: 'mRNA vaccine', tenant: 'MediQuick Pharma', range: '-20 – -15 °C', temp: -17.2, route: 'Hub HYD → BLR', status: 'in-range', excursionMin: 0 }, + { id: 'DM-CC-2043', product: 'Frozen seafood', tenant: 'Freshly Foods', range: '≤ -18 °C', temp: -12.4, route: 'Koramangala → HSR', status: 'breach', excursionMin: 14 }, + { id: 'DM-CC-2044', product: 'Dairy & yogurt', tenant: 'GreenLeaf Organics', range: '2 – 6 °C', temp: 7.1, route: 'Koregaon → Viman Nagar', status: 'at-risk', excursionMin: 3 }, + { id: 'DM-CC-2045', product: 'Blood samples', tenant: 'MediQuick Pharma', range: '2 – 8 °C', temp: 5.3, route: 'Connaught Pl → Saket', status: 'in-range', excursionMin: 0 }, + { id: 'DM-CC-2046', product: 'Antibiotics', tenant: 'MediQuick Pharma', range: '15 – 25 °C', temp: 21.4, route: 'Hitech City → Gachibowli', status: 'in-range', excursionMin: 0 } +]; + +// ==============================|| LOGISTICS OPERATING SYSTEM — LAYER DATA ||============================== // +// Mirrors the 8-layer end-to-end flow: Book → Trust → Hub → Fleet → MileTruth AI → Execution → Analytics → Integrations. + +// The 8 layers of the operating system — drives the System Overview pipeline strip. +export const systemLayers = [ + { no: 1, key: 'book', title: 'Book & Create', subtitle: 'Shipment intake', color: '#1E3A8A', metric: '1,402', metricLabel: 'shipments', health: 'healthy' }, + { no: 2, key: 'trust', title: 'Trust & Identity', subtitle: 'KYC · fraud · custody', color: '#5B5BD6', metric: '99.2%', metricLabel: 'cleared', health: 'healthy' }, + { no: 3, key: 'hubs', title: 'Hub Network', subtitle: 'Sort · line-haul', color: '#0E7C7B', metric: '12', metricLabel: 'hubs live', health: 'healthy' }, + { no: 4, key: 'fleet', title: 'Fleet & Riders', subtitle: 'EV-first capacity', color: '#15803D', metric: '48', metricLabel: 'riders online', health: 'healthy' }, + { no: 5, key: 'dispatch', title: 'MileTruth AI', subtitle: 'Dispatch · optimize', color: '#EA580C', metric: '34%', metricLabel: 'route savings', health: 'optimizing' }, + { no: 6, key: 'execution', title: 'Execution', subtitle: 'Track · proof', color: '#1D4ED8', metric: '96', metricLabel: 'in transit', health: 'healthy' }, + { no: 7, key: 'analytics', title: 'Analytics', subtitle: 'Insight · ML loop', color: '#7C3AED', metric: '98.6%', metricLabel: 'on-time', health: 'healthy' }, + { no: 8, key: 'integrations', title: 'Integrations', subtitle: 'APIs · partners', color: '#0F766E', metric: '24', metricLabel: 'connectors', health: 'healthy' } +]; + +// ---- Layer 2: Trust, Security & Compliance ---- +export const trustChecks = [ + { key: 'kyc', title: 'KYC Verification', desc: 'Verify senders & businesses', icon: 'kyc', passRate: 99.2, pending: 6, color: '#5B5BD6' }, + { key: 'id', title: 'ID Verification', desc: 'Aadhaar / GST / PAN validation', icon: 'id', passRate: 98.7, pending: 3, color: '#1D4ED8' }, + { key: 'fraud', title: 'Fraud Detection', desc: 'ML model flags risky shipments', icon: 'fraud', passRate: 97.4, pending: 9, color: '#C01227' }, + { key: 'tamper', title: 'Tamper Protection', desc: 'Secure packaging & seal tracking', icon: 'tamper', passRate: 99.8, pending: 1, color: '#EA580C' }, + { key: 'custody', title: 'Chain of Custody', desc: 'Immutable logs at every touchpoint', icon: 'custody', passRate: 100, pending: 0, color: '#0E7C7B' }, + { key: 'compliance', title: 'Compliance Engine', desc: 'Legal, tax & regulatory checks', icon: 'compliance', passRate: 99.5, pending: 2, color: '#15803D' } +]; + +export const trustQueue = [ + { id: 'DM-10244', entity: 'Arjun Mehta', type: 'Sender KYC', check: 'Aadhaar', risk: 'low', score: 12, status: 'cleared', flagged: 'ID match · address verified' }, + { id: 'DM-10246', entity: 'TechNova Retail', type: 'Business GST', check: 'GST', risk: 'medium', score: 48, status: 'review', flagged: 'High-value COD ₹4,999 · ID required' }, + { id: 'DM-10248', entity: 'Suresh Kumar', type: 'Sender KYC', check: 'PAN', risk: 'low', score: 18, status: 'cleared', flagged: 'Repeat customer' }, + { id: 'DM-10251', entity: 'QuickMart Express', type: 'Fraud screen', check: 'ML ID3', risk: 'high', score: 82, status: 'hold', flagged: 'Velocity anomaly · 14 orders / 5 min' }, + { id: 'DM-10242', entity: 'Karthik Rao', type: 'Tamper seal', check: 'Seal scan', risk: 'low', score: 5, status: 'cleared', flagged: 'Seal intact · photo logged' } +]; + +// ---- Layer 3: Intelligent Hub Network ---- +export const hubs = [ + { id: 'HUB-BLR-01', name: 'Koramangala Micro Hub', type: 'Micro Hub', city: 'Bengaluru', lat: 12.9352, lng: 77.6245, capacity: 1200, load: 940, inbound: 180, outbound: 210, dock: 4, status: 'online' }, + { id: 'HUB-BLR-02', name: 'Whitefield City Hub', type: 'City Hub', city: 'Bengaluru', lat: 12.9698, lng: 77.7499, capacity: 4000, load: 2860, inbound: 520, outbound: 610, dock: 10, status: 'online' }, + { id: 'HUB-BLR-RG', name: 'Hoskote Regional Hub', type: 'Regional Hub', city: 'Bengaluru', lat: 13.0707, lng: 77.7980, capacity: 12000, load: 7400, inbound: 1900, outbound: 2100, dock: 24, status: 'online' }, + { id: 'HUB-MUM-01', name: 'Andheri City Hub', type: 'City Hub', city: 'Mumbai', lat: 19.1197, lng: 72.8468, capacity: 4500, load: 3950, inbound: 700, outbound: 760, dock: 12, status: 'busy' }, + { id: 'HUB-HYD-01', name: 'Hitech Cross Dock', type: 'Cross Dock', city: 'Hyderabad', lat: 17.4435, lng: 78.3772, capacity: 3000, load: 1100, inbound: 340, outbound: 360, dock: 8, status: 'online' }, + { id: 'HUB-DEL-RG', name: 'Bilaspur Regional Hub', type: 'Regional Hub', city: 'Delhi NCR', lat: 28.4231, lng: 77.0490, capacity: 14000, load: 11200, inbound: 2400, outbound: 2600, dock: 28, status: 'busy' } +]; + +export const hubNetworkTypes = [ + { type: 'Micro Hubs (Urban)', count: 5, desc: 'Last-mile EV staging' }, + { type: 'City Hubs', count: 4, desc: 'Sort & local distribution' }, + { type: 'Regional Hubs', count: 2, desc: 'Aggregation & line-haul origin' }, + { type: 'Cross Dock Points', count: 1, desc: 'No-store transfer' } +]; + +export const lineHauls = [ + { id: 'LH-001', from: 'Hoskote Regional Hub', to: 'Bilaspur Regional Hub', corridor: 'BLR → DEL', distance: 2150, vehicle: 'EV Truck 4W', load: 86, eta: '34h', status: 'in-transit' }, + { id: 'LH-002', from: 'Andheri City Hub', to: 'Hoskote Regional Hub', corridor: 'MUM → BLR', distance: 980, vehicle: 'ICE Truck 6W', load: 72, eta: '18h', status: 'in-transit' }, + { id: 'LH-003', from: 'Hitech Cross Dock', to: 'Hoskote Regional Hub', corridor: 'HYD → BLR', distance: 570, vehicle: 'EV Truck 4W', load: 64, eta: '11h', status: 'scheduled' }, + { id: 'LH-004', from: 'Bilaspur Regional Hub', to: 'Andheri City Hub', corridor: 'DEL → MUM', distance: 1420, vehicle: 'ICE Truck 6W', load: 91, eta: '24h', status: 'loading' } +]; + +// ---- Layer 4: Fleet & Rider Operating System ---- +export const fleet = [ + { id: 'VH-EV-018', model: 'Tata Ace EV', type: 'EV 4W', powertrain: 'EV', battery: 78, range: 96, health: 94, capacityKg: 600, status: 'on-trip', rider: 'Mohan Das', hub: 'Koramangala Micro Hub', uptime: 98.2 }, + { id: 'VH-EV-022', model: 'Euler HiLoad EV', type: 'EV 3W', powertrain: 'EV', battery: 41, range: 38, health: 89, capacityKg: 688, status: 'charging', rider: '—', hub: 'Whitefield City Hub', uptime: 96.5 }, + { id: 'VH-2W-104', model: 'Ola S1 Pro', type: 'EV 2W', powertrain: 'EV', battery: 63, range: 71, health: 91, capacityKg: 20, status: 'on-trip', rider: 'Ravi Teja', hub: 'Hitech Cross Dock', uptime: 97.1 }, + { id: 'VH-IC-211', model: 'Mahindra Bolero', type: 'ICE 4W', powertrain: 'ICE', battery: null, range: 320, health: 82, capacityKg: 1500, status: 'idle', rider: '—', hub: 'Hoskote Regional Hub', uptime: 93.4 }, + { id: 'VH-2W-118', model: 'Honda Activa', type: 'ICE 2W', powertrain: 'ICE', battery: null, range: 180, health: 76, capacityKg: 25, status: 'maintenance', rider: '—', hub: 'Andheri City Hub', uptime: 88.9 }, + { id: 'VH-EV-031', model: 'Tata Ace EV', type: 'EV 4W', powertrain: 'EV', battery: 88, range: 108, health: 96, capacityKg: 600, status: 'idle', rider: '—', hub: 'Bilaspur Regional Hub', uptime: 99.0 } +]; + +export const fleetSummary = { + total: 124, ev: 86, ice: 38, onTrip: 52, charging: 14, idle: 41, maintenance: 17, + evShare: 69, co2SavedKg: 12840, avgBattery: 64, avgHealth: 90 +}; + +// ---- Layer 5: MileTruth AI Engine — dispatch & optimization ---- +export const dispatchQueue = [ + { id: 'DM-10246', pickup: 'Hitech City', drop: 'Gachibowli', priority: 'high', sla: '45 min', suggestedRider: 'Ravi Teja', confidence: 96, status: 'matched', etaMin: 22 }, + { id: 'DM-10244', pickup: 'Connaught Place', drop: 'Saket, Block C', priority: 'standard', sla: '90 min', suggestedRider: 'Sandeep Roy', confidence: 88, status: 'optimizing', etaMin: 41 }, + { id: 'DM-10248', pickup: 'T. Nagar', drop: 'Adyar', priority: 'standard', sla: '120 min', suggestedRider: 'Faisal Khan', confidence: 91, status: 'matched', etaMin: 33 }, + { id: 'DM-10242', pickup: 'Indiranagar Store', drop: 'Whitefield, Phase 1', priority: 'express', sla: '60 min', suggestedRider: 'Mohan Das', confidence: 94, status: 'dispatched', etaMin: 28 } +]; + +export const aiTriggers = [ + { key: 'new-order', label: 'New Order', count: 18, icon: 'order' }, + { key: 'rider-delay', label: 'Rider Delay / Exception', count: 3, icon: 'delay' }, + { key: 'traffic', label: 'Traffic Change', count: 7, icon: 'traffic' }, + { key: 'weather', label: 'Weather Change', count: 1, icon: 'weather' }, + { key: 'cancellation', label: 'Cancellation / Return', count: 2, icon: 'cancel' }, + { key: 'high-priority', label: 'High Priority Shipment', count: 4, icon: 'priority' } +]; + +export const aiPipeline = [ + { stage: 'Data Ingestion', items: ['Live orders', 'Rider GPS', 'Traffic / weather', 'Hub status'] }, + { stage: 'Data Processing', items: ['Cleaning', 'GPS smoothing', 'Geohash encoding', 'Feature eng.'] }, + { stage: 'Intelligence', items: ['Demand prediction', 'Delay prediction', 'Risk scoring (ID3)', 'Zone learning'] }, + { stage: 'Optimization', items: ['OR-Tools VRP', 'Multi-objective', 'Distance+Time+Cost', 'Re-optimization'] }, + { stage: 'Assignment', items: ['Rider matching', 'Batch creation', 'Route sequencing', 'Trip planning'] }, + { stage: 'Output', items: ['Optimized routes', 'ETA', 'Rider plans', 'Delivery windows'] } +]; + +export const aiMetrics = { routeSavings: 34, avgEtaAccuracy: 92, batchRate: 2.6, reoptToday: 41, delaysPredicted: 28, delaysAvoided: 23 }; + +// ---- Layer 6: Execution & Visibility ---- +export const executionFeed = [ + { id: 'DM-10242', stage: 'In-Transit', rider: 'Mohan Das', loc: 'HSR Layout', detail: 'On route · 4.2 km to drop', time: '10:42 AM', proof: null }, + { id: 'DM-10243', stage: 'Picked Up', rider: 'Imran Sheikh', loc: 'Andheri West', detail: 'OTP verified · photo proof logged', time: '10:38 AM', proof: 'pickup' }, + { id: 'DM-10247', stage: 'Delivered', rider: 'Ravi Teja', loc: 'Viman Nagar', detail: 'eSign captured · POD uploaded', time: '10:30 AM', proof: 'delivery' }, + { id: 'DM-10251', stage: 'Exception', rider: 'Faisal Khan', loc: 'Adyar', detail: 'Customer unavailable · reattempt scheduled', time: '10:21 AM', proof: null }, + { id: 'DM-10246', stage: 'Dispatched', rider: 'Sandeep Roy', loc: 'Hitech City', detail: 'Rider en route to pickup', time: '10:18 AM', proof: null } +]; + +export const executionStages = [ + { key: 'pickup', title: 'Pickup Execution', items: ['Rider reaches pickup', 'OTP / eSign', 'Photo proof', 'Proof of pickup'], count: 12 }, + { key: 'transit', title: 'In-Transit Tracking', items: ['Live GPS', 'Route monitoring', 'ETA updates', 'Geofencing'], count: 96 }, + { key: 'rerouting', title: 'Dynamic Re-routing', items: ['Avoid delays', 'Optimize live', 'Re-assign if needed', 'SLA protection'], count: 7 }, + { key: 'delivery', title: 'Delivery Execution', items: ['OTP / eSign', 'Photo proof', 'Proof of delivery', 'Feedback'], count: 1330 }, + { key: 'exception', title: 'Exception Handling', items: ['Delay mgmt', 'Reattempt', 'Damage / loss', 'Escalation'], count: 4 } +]; + +// ---- Layer 7: Analytics & Intelligence ---- +export const analyticsKpis = { + onTime: 98.6, successRate: 99.1, costPerDelivery: 74, fuelEnergyCost: 21, loadFactor: 82, + customerInsightsNps: 71, slaAchievement: 97.4 +}; + +export const lanePerformance = [ + { lane: 'BLR → BLR (intra)', shipments: 820, onTime: 99.2, costPer: 68, ev: 92 }, + { lane: 'BLR → DEL', shipments: 142, onTime: 96.8, costPer: 188, ev: 64 }, + { lane: 'MUM → BLR', shipments: 98, onTime: 97.5, costPer: 162, ev: 58 }, + { lane: 'HYD → BLR', shipments: 76, onTime: 98.1, costPer: 121, ev: 71 }, + { lane: 'DEL → MUM', shipments: 64, onTime: 95.4, costPer: 174, ev: 49 } +]; + +export const mlLoop = [ + { step: 'Data Logging', detail: 'Every shipment event captured' }, + { step: 'Model Retraining', detail: 'Nightly on fresh outcomes' }, + { step: 'A/B Testing', detail: 'Routing & pricing variants' }, + { step: 'Hyperparameter Tuning', detail: 'Auto-tuned weekly' } +]; + +export const outcomes = [ + { label: 'Faster Deliveries', value: '35%+', caption: 'improvement', color: '#00A854' }, + { label: 'Lower Operational Cost', value: '30–40%', caption: 'savings', color: '#0E7C7B' }, + { label: 'Higher Reliability', value: '99%+', caption: 'success rate', color: '#1D4ED8' }, + { label: 'Full Visibility', value: 'E2E', caption: 'end-to-end', color: '#7C3AED' }, + { label: 'Sustainable & Green', value: 'EV-First', caption: 'operations', color: '#15803D' } +]; + +// ---- Layer 8: Integrations & Ecosystem ---- +export const integrations = [ + { name: 'Order API', group: 'APIs & Integrations', desc: 'Create & manage shipments', status: 'connected', calls: '1.2M / day', icon: 'api' }, + { name: 'Tracking API', group: 'APIs & Integrations', desc: 'Real-time status & ETA', status: 'connected', calls: '4.8M / day', icon: 'api' }, + { name: 'Webhooks', group: 'APIs & Integrations', desc: 'Event push to partners', status: 'connected', calls: '900K / day', icon: 'api' }, + { name: 'SAP ERP / WMS', group: 'Enterprise Systems', desc: 'Warehouse & inventory sync', status: 'connected', calls: 'realtime', icon: 'erp' }, + { name: 'TMS', group: 'Enterprise Systems', desc: 'Transport management', status: 'connected', calls: 'realtime', icon: 'erp' }, + { name: 'CRM', group: 'Enterprise Systems', desc: 'Customer records', status: 'degraded', calls: 'realtime', icon: 'erp' }, + { name: 'Razorpay', group: 'Payment & Billing', desc: 'Prepaid / COD settlement', status: 'connected', calls: '320K / day', icon: 'pay' }, + { name: 'Corporate Billing', group: 'Payment & Billing', desc: 'Invoice & reconciliation', status: 'connected', calls: 'daily', icon: 'pay' }, + { name: '3PL Partners', group: 'Partners & Ecosystem', desc: 'Capacity overflow routing', status: 'connected', calls: '12 partners', icon: 'partner' }, + { name: 'Transport Partners', group: 'Partners & Ecosystem', desc: 'Line-haul carriers', status: 'connected', calls: '8 carriers', icon: 'partner' }, + { name: 'Warehouse Partners', group: 'Partners & Ecosystem', desc: 'Fulfilment nodes', status: 'pending', calls: '—', icon: 'partner' } +]; diff --git a/src/layout/MainLayout/Header.jsx b/src/layout/MainLayout/Header.jsx index 46a9e16..acefa4d 100644 --- a/src/layout/MainLayout/Header.jsx +++ b/src/layout/MainLayout/Header.jsx @@ -34,13 +34,14 @@ import DoneAllIcon from '@mui/icons-material/DoneAll'; import Logo from '@/components/Logo'; -const RED = '#C01227'; +const RED = '#C01227'; // brand accent (avatars, dots) +const BAR = '#8E1F2A'; // muted deep-brick top bar (toned down from vivid #C01227) const INITIAL_NOTIFICATIONS = [ { id: 1, icon: Inventory2OutlinedIcon, title: 'New order #ORD-10482 placed', time: '2 min ago', to: '/orders', read: false }, { id: 2, icon: TwoWheelerOutlinedIcon, title: 'Rider Imran went online', time: '18 min ago', to: '/riders', read: false }, { id: 3, icon: PaymentsOutlinedIcon, title: 'Invoice INV-2041 marked paid', time: '1 hr ago', to: '/invoice', read: false }, - { id: 4, icon: AssignmentOutlinedIcon, title: '3 new onboarding requests', time: '3 hrs ago', to: '/requests', read: true } + { id: 4, icon: AssignmentOutlinedIcon, title: 'MileTruth AI re-optimized 41 routes', time: '3 hrs ago', to: '/dispatch', read: true } ]; const MESSAGES = [ @@ -78,7 +79,7 @@ export default function Header({ onToggle }) { t.zIndex.drawer + 1, boxShadow: '0 1px 0 rgba(0,0,0,0.06)' }} + sx={{ bgcolor: BAR, color: '#fff', zIndex: (t) => t.zIndex.drawer + 1, boxShadow: '0 1px 0 rgba(0,0,0,0.06)' }} > @@ -194,7 +195,7 @@ export default function Header({ onToggle }) { ); })} - { closeNotif(); navigate('/requests'); }} sx={{ justifyContent: 'center', color: 'primary.main', fontWeight: 600 }}> + { closeNotif(); navigate('/dashboard'); }} sx={{ justifyContent: 'center', color: 'primary.main', fontWeight: 600 }}> View all activity diff --git a/src/layout/MainLayout/Sidebar.jsx b/src/layout/MainLayout/Sidebar.jsx index 1be9cab..6fbbd3f 100644 --- a/src/layout/MainLayout/Sidebar.jsx +++ b/src/layout/MainLayout/Sidebar.jsx @@ -19,10 +19,10 @@ import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'; import navItems from '@/menu/navItems'; import Logo from '@/components/Logo'; -export const DRAWER_WIDTH = 264; -export const MINI_WIDTH = 78; +export const DRAWER_WIDTH = 232; +export const MINI_WIDTH = 76; -const RED = '#C01227'; +const NAV_BG = '#8E1F2A'; // muted deep-brick brand red (toned down from vivid #C01227) function NavLeaf({ item, open, active, depth = 0, onClick }) { const Icon = item.icon; @@ -80,11 +80,31 @@ export default function Sidebar({ open, mobileOpen, onMobileClose, isMobile }) { }; const content = ( - + - + {navItems.map((grp) => ( {expanded && ( @@ -156,8 +176,11 @@ export default function Sidebar({ open, mobileOpen, onMobileClose, isMobile }) { {expanded && ( - - Doormile Console v1.0 + + Delivering Trust. + + + Beyond Boundaries · v1.0 )} diff --git a/src/menu/navItems.jsx b/src/menu/navItems.jsx index 571e8c5..79e61fc 100644 --- a/src/menu/navItems.jsx +++ b/src/menu/navItems.jsx @@ -7,34 +7,70 @@ import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined'; import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined'; import BarChartOutlinedIcon from '@mui/icons-material/BarChartOutlined'; import ReceiptLongOutlinedIcon from '@mui/icons-material/ReceiptLongOutlined'; -import AssignmentOutlinedIcon from '@mui/icons-material/AssignmentOutlined'; import SummarizeOutlinedIcon from '@mui/icons-material/SummarizeOutlined'; import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined'; import MapOutlinedIcon from '@mui/icons-material/MapOutlined'; +import SecurityOutlinedIcon from '@mui/icons-material/SecurityOutlined'; +import HubOutlinedIcon from '@mui/icons-material/HubOutlined'; +import ElectricRickshawOutlinedIcon from '@mui/icons-material/ElectricRickshawOutlined'; +import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined'; +import MyLocationOutlinedIcon from '@mui/icons-material/MyLocationOutlined'; +import TrendingUpOutlinedIcon from '@mui/icons-material/TrendingUpOutlined'; +import ApiOutlinedIcon from '@mui/icons-material/ApiOutlined'; +import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined'; +import AcUnitOutlinedIcon from '@mui/icons-material/AcUnitOutlined'; +import AltRouteOutlinedIcon from '@mui/icons-material/AltRouteOutlined'; -// ==============================|| DOORMILE - SIDEBAR NAV CONFIG ||============================== // +// ==============================|| DOORMILE - LOGISTICS OPERATING SYSTEM NAV ||============================== // +// Groups mirror the 8-layer end-to-end flow of the Doormile operating system. const navItems = [ { - group: 'Operations', + group: 'Overview', items: [ - { id: 'dashboard', title: 'Dashboard', url: '/dashboard', icon: DashboardOutlinedIcon }, - { id: 'orders', title: 'Orders', url: '/orders', icon: Inventory2OutlinedIcon }, - { id: 'deliveries', title: 'Deliveries', url: '/deliveries', icon: MopedOutlinedIcon } + { id: 'dashboard', title: 'System Overview', url: '/dashboard', icon: DashboardOutlinedIcon }, + { id: 'three-mile', title: 'Three-Mile Network', url: '/three-mile', icon: RouteOutlinedIcon } ] }, { - group: 'Network', + group: '1 · Book & Create', items: [ - { id: 'tenants', title: 'Tenants', url: '/tenants', icon: ApartmentOutlinedIcon }, - { id: 'pricing', title: 'Pricing', url: '/pricing', icon: PaymentsOutlinedIcon }, - { id: 'customers', title: 'Customers', url: '/customers', icon: GroupsOutlinedIcon }, + { id: 'orders', title: 'Shipments', url: '/orders', icon: Inventory2OutlinedIcon }, + { id: 'customers', title: 'Customers', url: '/customers', icon: GroupsOutlinedIcon } + ] + }, + { + group: '2 · Trust & Identity', + items: [{ id: 'trust', title: 'Trust & Compliance', url: '/trust', icon: SecurityOutlinedIcon }] + }, + { + group: '3 · Hub Network', + items: [{ id: 'hubs', title: 'Hub Network', url: '/hubs', icon: HubOutlinedIcon }] + }, + { + group: '4 · Fleet & Riders', + items: [ + { id: 'fleet', title: 'Fleet', url: '/fleet', icon: ElectricRickshawOutlinedIcon }, { id: 'riders', title: 'Riders', url: '/riders', icon: TwoWheelerOutlinedIcon } ] }, { - group: 'Finance & Insights', + group: '5 · MileTruth AI', + items: [{ id: 'dispatch', title: 'AI Dispatch', url: '/dispatch', icon: AutoAwesomeOutlinedIcon }] + }, + { + group: '6 · Execution', items: [ + { id: 'tracking', title: 'Live Tracking', url: '/tracking', icon: MyLocationOutlinedIcon }, + { id: 'journey', title: 'Shipment Journey', url: '/tracking/journey', icon: AltRouteOutlinedIcon }, + { id: 'cold-chain', title: 'Cold Chain', url: '/cold-chain', icon: AcUnitOutlinedIcon }, + { id: 'deliveries', title: 'Deliveries', url: '/deliveries', icon: MopedOutlinedIcon } + ] + }, + { + group: '7 · Analytics', + items: [ + { id: 'analytics', title: 'Analytics', url: '/analytics', icon: TrendingUpOutlinedIcon }, { id: 'reports', title: 'Reports', @@ -45,9 +81,21 @@ const navItems = [ { id: 'riders-summary', title: 'Riders Summary', url: '/reports/riders-summary', icon: TwoWheelerOutlinedIcon }, { id: 'riders-logs', title: 'Riders Logs', url: '/reports/riders-logs', icon: MapOutlinedIcon } ] - }, - { id: 'invoice', title: 'Invoice', url: '/invoice', icon: ReceiptLongOutlinedIcon }, - { id: 'requests', title: 'Requests', url: '/requests', icon: AssignmentOutlinedIcon } + } + ] + }, + { + group: '8 · Integrations', + items: [ + { id: 'integrations', title: 'Integrations', url: '/integrations', icon: ApiOutlinedIcon }, + { id: 'tenants', title: 'Clients', url: '/tenants', icon: ApartmentOutlinedIcon } + ] + }, + { + group: 'Finance', + items: [ + { id: 'pricing', title: 'Pricing', url: '/pricing', icon: PaymentsOutlinedIcon }, + { id: 'invoice', title: 'Invoice', url: '/invoice', icon: ReceiptLongOutlinedIcon } ] } ]; diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 838ea5b..65a6bf0 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -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 ( <> @@ -28,15 +38,35 @@ export default function Dashboard() { Bengaluru Mumbai - + } /> + {/* End-to-end operating-system pipeline */} + + + End-to-End Intelligent Logistics Flow + + + + + + + {/* Three-Mile model — First → Mid → Last */} + + + Three-Mile Network · One Connected System + + + + + + - - - + + + @@ -61,39 +91,42 @@ export default function Dashboard() { - - - - - - Order ID - Customer - Route - Status - Amount - - - - {orders.slice(0, 6).map((o) => ( - - {o.id} - {o.customer} - - {o.pickup} → {o.drop} - - - {inr(o.charges)} - - ))} - -
+ {/* MileTruth AI + Sustainability */} + + + + MileTruth AI Engine + + } + > + + + + + + - + + + + + {fleetSummary.evShare}% + EV fleet share + + + + {(fleetSummary.co2SavedKg / 1000).toFixed(1)}t CO₂ saved this month + + + + } spacing={0}> - {riders.slice(0, 5).map((r, i) => ( - + {riders.slice(0, 4).map((r, i) => ( + {i + 1} @@ -109,11 +142,84 @@ export default function Dashboard() { + + + + + {verticals.map((v) => ( + + + + + + {v.label} + {v.desc} + + + + {v.shipments} + {v.onTime}% on-time + + + + ))} + + + + + + + + + + Order ID + Customer + Vertical + Route + Status + Amount + + + + {orders.slice(0, 6).map((o) => { + const v = verticalOf(o.tenant); + return ( + + {o.id} + {o.customer} + + + + + {o.pickup} → {o.drop} + + + {inr(o.charges)} + + ); + })} + +
+
+
+ ); } +function AiStat({ icon: Icon, color, value, label }) { + return ( + + + + {value} + {label} + + + ); +} + function Legend({ color, label }) { return ( @@ -122,3 +228,8 @@ function Legend({ color, label }) { ); } + +const hexA = (hex, a) => { + const n = parseInt(hex.replace('#', ''), 16); + return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`; +}; diff --git a/src/pages/Requests.jsx b/src/pages/Requests.jsx deleted file mode 100644 index fe1a5b5..0000000 --- a/src/pages/Requests.jsx +++ /dev/null @@ -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 ( - <> - *': { borderBottom: open ? 'unset' : undefined } }}> - {index + 1} - {row.requestor} - {row.bank} - {row.ifsc} - {row.refNo} - {inr(row.amount)} - {row.reason} - - setOpen((o) => !o)}> - {open ? : } - - - - - - - - setTab(v)} sx={{ mb: 2 }}> - - - - - {tab === 0 && ( - - - Contact Name - {row.contact} - - - Address - {row.address} - - - City - {row.city} - - - Zip Code - {row.zip} - - - )} - - {tab === 1 && ( - - - - # - Category - Skill - Cost/Hr - - - - {row.pricing.map((p, i) => ( - - {i + 1} - {p.category} - {p.skill} - {inr(p.cost)} - - ))} - -
- )} -
-
-
-
- - ); -} - -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 ( - <> - } onClick={() => setOpen(true)}> - Create Request - - } - /> - - - - - - - # - Requestor - Bank - IFSC - Ref No - Amount - Reason - - - - - {paged.map((row, idx) => ( - - ))} - -
-
- setPage(p)} - rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25, 100]} - /> -
- - setOpen(false)} maxWidth="sm" fullWidth> - Create Request - - - - - - - - - - - - - - - - - - ); -} diff --git a/src/pages/analytics/Analytics.jsx b/src/pages/analytics/Analytics.jsx new file mode 100644 index 0000000..8e36229 --- /dev/null +++ b/src/pages/analytics/Analytics.jsx @@ -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 ( + <> + } onClick={() => showToast('Analytics report exported')}>Export Report} + /> + + + + + + + + + + + } + > + d.m)} + series={[ + { name: 'Orders', color: '#7C3AED', data: ordersTrend.map((d) => d.orders) }, + { name: 'Delivered', color: '#00A854', data: ordersTrend.map((d) => d.delivered) } + ]} + /> + + + + + + } spacing={0}> + {mlLoop.map((m) => ( + + + + + + {m.step} + {m.detail} + + + ))} + + + + + + + + + + Lane + Shipments + On-Time + Cost / Del + EV % + + + + {lanePerformance.map((l) => ( + + {l.lane} + {l.shipments.toLocaleString('en-IN')} + + + + 97 ? 'success' : 'warning'} sx={{ height: 6, borderRadius: 3 }} /> + + {l.onTime}% + + + {inr(l.costPer)} + 70 ? 'success.main' : 'text.secondary' }}>{l.ev}% + + ))} + +
+
+
+ + + + + {outcomes.map((o) => ( + + + + + {o.value} + + {o.label} + {o.caption} + + + ))} + + + +
+ + + ); +} + +function Legend({ color, label }) { + return ( + + + {label} + + ); +} diff --git a/src/pages/auth/Login.jsx b/src/pages/auth/Login.jsx index 6b98b22..9080ee3 100644 --- a/src/pages/auth/Login.jsx +++ b/src/pages/auth/Login.jsx @@ -49,18 +49,21 @@ export default function Login() { - - Move every parcel, -
on time, every time. + + One Connected System · One Promise Kept + + + Delivering Trust. +
Beyond Boundaries.
- 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. {[ - { 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) => ( diff --git a/src/pages/coldchain/ColdChain.jsx b/src/pages/coldchain/ColdChain.jsx new file mode 100644 index 0000000..4b95d38 --- /dev/null +++ b/src/pages/coldchain/ColdChain.jsx @@ -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 ( + <> + } onClick={() => showToast('Cold-chain compliance report generated')}>Compliance Report} + /> + + + + + + + + + + + + + + + Shipment + Product + Client + Required Range + Current Temp + Excursion + Status + + + + {coldChainShipments.map((s) => { + const color = TEMP_COLOR[s.status]; + return ( + + {s.id} + + {s.product} + {s.route} + + {s.tenant} + {s.range} + + + + + + {s.temp}°C + + + + {s.excursionMin > 0 ? ( + {s.excursionMin} min + ) : ( + + )} + + + + ); + })} + +
+
+
+ + + + + + + + + + + + + + {[ + { 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) => ( + + + + + {c.t} + + {c.d} + + + ))} + + + +
+ + + ); +} + +function Bar({ label, value, color }) { + return ( + + + {label} + {value}% + + + + ); +} + +const hexA = (hex, a) => { + const n = parseInt(hex.replace('#', ''), 16); + return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`; +}; diff --git a/src/pages/dispatch/AiDispatch.jsx b/src/pages/dispatch/AiDispatch.jsx new file mode 100644 index 0000000..5984f49 --- /dev/null +++ b/src/pages/dispatch/AiDispatch.jsx @@ -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 ( + <> + } onClick={runOptimization} disabled={running}>Run Optimization} + /> + + + + + + + + + + + + + + + Shipment + Route + Priority + SLA + AI-matched Rider + Confidence + Status + + + + {rows.map((d) => ( + + {d.id} + {d.pickup} → {d.drop} + + + + {d.sla} + {d.suggestedRider} + + + + 90 ? 'success' : 'warning'} sx={{ height: 6, borderRadius: 3 }} /> + + {d.confidence}% + + + + + ))} + +
+
+
+ + + + } spacing={0}> + {aiTriggers.map((t) => { + const Icon = TRIGGER_ICONS[t.icon] || FiberNewOutlinedIcon; + return ( + + + + + {t.label} + 5 ? 'warning' : 'default'} /> + + ); + })} + + + + + + + + {aiPipeline.map((stage, i) => ( + + + + {i + 1} + {stage.stage} + + + {stage.items.map((it) => ( + • {it} + ))} + + + + ))} + + + +
+ + + ); +} diff --git a/src/pages/fleet/Fleet.jsx b/src/pages/fleet/Fleet.jsx new file mode 100644 index 0000000..3c4bec7 --- /dev/null +++ b/src/pages/fleet/Fleet.jsx @@ -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 ( + <> + } onClick={() => setOpen(true)}>Add Vehicle} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vehicle + Type + Battery / Range + Health + Capacity + Assigned + Hub + Status + + + + {rows.map((v) => ( + + + + + {v.powertrain === 'EV' ? : } + + + {v.model} + {v.id} + + + + {v.type} + + {v.powertrain === 'EV' ? ( + <> + + {v.battery}% + {v.range} km + + + + ) : ( + Fuel · {v.range} km range + )} + + + + {v.health}% + + + {v.capacityKg} kg + {v.rider} + {v.hub} + + + ))} + +
+
+
+
+ + setOpen(false)} title="Add Vehicle" onSubmit={addVehicle} submitLabel="Add Vehicle"> + + + + + + EV + ICE + + + + + {['EV 2W', 'EV 3W', 'EV 4W', 'ICE 2W', 'ICE 4W'].map((t) => {t})} + + + + + + {['Koramangala Micro Hub', 'Whitefield City Hub', 'Hoskote Regional Hub', 'Andheri City Hub', 'Hitech Cross Dock', 'Bilaspur Regional Hub'].map((h) => {h})} + + + + + + + ); +} + +function Metric({ icon: Icon, color, label, value, bar }) { + return ( + + + + {label} + {value} + + + + ); +} diff --git a/src/pages/hubs/HubNetwork.jsx b/src/pages/hubs/HubNetwork.jsx new file mode 100644 index 0000000..6cbe8ce --- /dev/null +++ b/src/pages/hubs/HubNetwork.jsx @@ -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 ( + <> + } onClick={() => setOpen(true)}>Add Hub} + /> + + + + + + + l.status === 'in-transit').length} icon={LocalShippingOutlinedIcon} color="primary" caption="inter-city" /> + + + + + + + + Hub + Type + City + Load / Capacity + Docks + Status + + + + {rows.map((h) => { + const pct = Math.round((h.load / h.capacity) * 100); + return ( + + + {h.name} + {h.id} + + {h.type} + {h.city} + + + {h.load.toLocaleString('en-IN')} / {h.capacity.toLocaleString('en-IN')} + {pct}% + + 90 ? 'error' : pct > 75 ? 'warning' : 'success'} sx={{ height: 6, borderRadius: 3, mt: 0.5 }} /> + + {h.dock} + + + ); + })} + +
+
+
+ + + + } spacing={0}> + {hubNetworkTypes.map((t) => ( + + + + + + {t.type} + {t.desc} + + {t.count} + + ))} + + + + + + + + + + Corridor + Vehicle + Distance + Load + ETA + Status + + + + {lineHauls.map((l) => ( + + + {l.corridor} + {l.from} → {l.to} + + {l.vehicle} + {l.distance.toLocaleString('en-IN')} km + + + {l.load}% + + {l.eta} + + + ))} + +
+
+
+ + + + + + +
+ + setOpen(false)} title="Add Hub" onSubmit={addHub} submitLabel="Add Hub"> + + + + + {['Micro Hub', 'City Hub', 'Regional Hub', 'Cross Dock'].map((t) => {t})} + + + + + {['Bengaluru', 'Mumbai', 'Delhi NCR', 'Hyderabad', 'Chennai', 'Pune'].map((c) => {c})} + + + + + + + + + ); +} diff --git a/src/pages/integrations/Integrations.jsx b/src/pages/integrations/Integrations.jsx new file mode 100644 index 0000000..cb16393 --- /dev/null +++ b/src/pages/integrations/Integrations.jsx @@ -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 ( + <> + } onClick={() => setOpen(true)}>Add Integration} + /> + + + + + + + + + + + {groups.map((g) => { + const meta = GROUP_META[g] || { icon: HubOutlinedIcon, color: '#C01227' }; + const GIcon = meta.icon; + return ( + + + + + + {g} +
+ } + > + + {rows.filter((i) => i.group === g).map((i) => ( + + + + + + {i.name} + {i.desc} + + + + + + + + + + + ))} + +
+
+ ); + })} + + setOpen(false)} title="Add Integration" onSubmit={addIntegration} submitLabel="Add"> + + + + + {Object.keys(GROUP_META).map((g) => {g})} + + + + + + + + ); +} + +const hexA = (hex, a) => { + const n = parseInt(hex.replace('#', ''), 16); + return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`; +}; diff --git a/src/pages/network/ThreeMile.jsx b/src/pages/network/ThreeMile.jsx new file mode 100644 index 0000000..56e0b6e --- /dev/null +++ b/src/pages/network/ThreeMile.jsx @@ -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 ( + <> + } onClick={() => showToast('Three-mile report exported')}>Export} + /> + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/tracking/LiveTracking.jsx b/src/pages/tracking/LiveTracking.jsx new file mode 100644 index 0000000..2316290 --- /dev/null +++ b/src/pages/tracking/LiveTracking.jsx @@ -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 ( + <> + } onClick={() => setShare(true)}>Share Tracking} + /> + + + + + {executionStages.map((s) => ( + + + {s.count.toLocaleString('en-IN')} + {s.title} + + {s.items.slice(0, 3).map((it) => ( + • {it} + ))} + + + + ))} + + + + ({ + x: ['28%', '52%', '70%', '40%'][i], + y: ['44%', '30%', '58%', '66%'][i], + active: r.active + }))} + /> + + + + + + + {executionFeed.map((e, i) => ( + + + + {stageIcon(e.stage)} + + + + {e.id} + {e.time} + + + + {e.proof && } label="Proof" sx={{ bgcolor: 'grey.100' }} />} + + {e.rider} · {e.loc} + {e.detail} + + + + ))} + + + + + + + + + + Shipment + Rider + Current Stage + Location + Update + Tracking + + + + {executionFeed.map((e) => ( + + {e.id} + {e.rider} + + {e.loc} + {e.detail} + + + + + ))} + +
+
+
+
+ + {/* Tracking detail */} + setTrack(null)} + title={track ? `Tracking · ${track.id}` : 'Tracking'} + hideActions + > + {track && ( + + + + Updated {track.time} + + + Rider{track.rider} + Location{track.loc} + + {track.detail} + + + + + Journey + + {orderTimeline.map((t, i) => ( + + {t.done ? : } + {t.label} + {t.time} + + ))} + + + + + + + + + + )} + + + {/* Share tracking */} + setShare(false)} title="Share Live Tracking" hideActions> + + Anyone with this link can follow the shipment in real time. + + + + + ) + }} + /> + + + + + + + + + ); +} + +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 ; + if (stage === 'In-Transit' || stage === 'Dispatched') return ; + return ; +} diff --git a/src/pages/tracking/ShipmentJourney.jsx b/src/pages/tracking/ShipmentJourney.jsx new file mode 100644 index 0000000..c59cf27 --- /dev/null +++ b/src/pages/tracking/ShipmentJourney.jsx @@ -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 ( + <> + + + + + } + /> + + {/* Summary */} + + + + {j.id} · {j.client} + + + {j.from.city} + {j.from.area} + + + + + + {j.to.city} + {j.to.area} + + + {j.product} · {j.mode} + + + + + + Progress + {j.progress}% + + + + + + + {/* Hop-by-hop timeline */} + + + + {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 ( + + {newMile && ( + + {h.mile} + + )} + + {/* rail */} + + + {h.status === 'done' ? : } + + {!last && } + + {/* content */} + + + {h.title} + {h.status === 'active' && } + + {h.node} + {h.handler} + {h.detail} + {h.time} + + + + ); + })} + + + + + {/* Map + live monitoring */} + + + + + + + + + Live Monitoring & Reroute + + } + > + } spacing={0}> + {j.events.map((e, i) => ( + + + {e.type === 'reroute' ? : } + + + {e.title} + {e.detail} + {e.time} + + + ))} + + + + + + + + ); +} + +function Summary({ label, value, small }) { + return ( + + {label} + {value} + + ); +} diff --git a/src/pages/trust/TrustCompliance.jsx b/src/pages/trust/TrustCompliance.jsx new file mode 100644 index 0000000..5e1546b --- /dev/null +++ b/src/pages/trust/TrustCompliance.jsx @@ -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 ( + <> + } onClick={() => setAudit(true)}>Audit Log} + /> + + + + + + + + + + + + {trustChecks.map((c) => { + const Icon = ICONS[c.icon] || VerifiedUserOutlinedIcon; + return ( + + + + + + + + + {c.title} + {c.desc} + + + + Pass rate + {c.passRate}% + + + + {c.pending} pending review + + + + + ); + })} + + + + + + + + Shipment + Entity + Check + Risk + Risk Score + Signal + Status + + + + {trustQueue.map((r) => ( + + {r.id} + + {r.entity} + {r.type} + + {r.check} + + + + + 60 ? 'error' : r.score > 35 ? 'warning' : 'success'} + sx={{ height: 6, borderRadius: 3 }} + /> + + {r.score} + + + {r.flagged} + + + ))} + +
+
+
+ + setAudit(false)} title="Chain-of-Custody Audit Log" maxWidth="md" hideActions> + + Immutable, tamper-evident log of every verification & custody event. + + } spacing={0}> + {trustQueue.map((r, i) => ( + + + 10:{42 - i}:0{i} + + + {r.id} · {r.check} + {r.entity} — {r.flagged} + + + + 0x{(r.id.length * 7 + r.score).toString(16)}a{i}f3 + + + ))} + + + + ); +} + +const hexA = (hex, a) => { + const n = parseInt(hex.replace('#', ''), 16); + return `rgba(${n >> 16}, ${(n >> 8) & 255}, ${n & 255}, ${a})`; +};