// ============================================================================ // ordersDesign.js — shared design-system primitives for the operator pages. // // Source of truth for the look-and-feel established on the Orders Details // screen (src/pages/nearle/reports/ordersDetails.js). All other report and // list pages import from here so the entire admin panel reads as one product. // // Exports: // DT, BRAND, BRAND_LIGHT — palette tokens // tint / soft / ring / edge — alpha helpers (08 / 18 / 26 / 55 hex) // formatNumberToRupees — INR currency formatter // STATUS_META — semantic per-status meta (label/color/icon) // StatusBadge — filled pill, white text on solid color // TimelineCell — time-dominant (bold) / date-secondary (muted) // MetricPill — small money/number pill for table cells // SoftPaper — autocomplete dropdown surface // AccentAvatar — colored circular icon avatar // pillFieldSx — rounded pill text-field styling // PrimaryButtonSx / SecondaryButtonSx — unified 32-px action-button system // ============================================================================ import React from 'react'; import { Avatar, Box, Paper, Stack, Typography } from '@mui/material'; import { MdLocalShipping, MdHourglassEmpty, MdCheckCircle, MdCancel, MdAccessTime, MdHistoryToggleOff, MdAssignmentTurnedIn } from 'react-icons/md'; import dayjs from 'dayjs'; // ---------------------------------------------------------------------------- // Design tokens // ---------------------------------------------------------------------------- export const DT = { radiusPill: 999, radiusCard: 16, shadowSoft: '0 14px 40px rgba(15, 23, 42, 0.10)', shadowMd: '0 8px 24px rgba(15, 23, 42, 0.08)', shadowPop: '0 18px 50px rgba(15, 23, 42, 0.18)', textPrimary: '#0f172a', textSecondary: '#64748b', textMuted: '#94a3b8', borderSubtle: '#e2e8f0', divider: '#f1f5f9', surface: '#ffffff', surfaceAlt: '#f8fafc' }; export const BRAND = '#662582'; export const BRAND_LIGHT = '#9255AB'; const dtA = (c, suffix) => `${c}${suffix}`; export const tint = (c) => dtA(c, '08'); export const soft = (c) => dtA(c, '18'); export const ring = (c) => dtA(c, '26'); export const edge = (c) => dtA(c, '55'); // ---------------------------------------------------------------------------- // Money formatter // ---------------------------------------------------------------------------- export function formatNumberToRupees(value) { return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 2 }).format(value || 0); } // ---------------------------------------------------------------------------- // Status meta — colors per brand standard: // green=delivered, amber=pending, blue=created/processing, // red=cancelled, dark-red=failed, purple=on-hold. // ---------------------------------------------------------------------------- export const STATUS_META = { created: { label: 'Created', color: '#3b82f6', icon: MdLocalShipping }, pending: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty }, accepted: { label: 'Accepted', color: '#6366f1', icon: MdAssignmentTurnedIn }, ready: { label: 'Accepted', color: '#6366f1', icon: MdAssignmentTurnedIn }, arrived: { label: 'Arrived', color: '#06b6d4', icon: MdCheckCircle }, picked: { label: 'Picked', color: '#8b5cf6', icon: MdLocalShipping }, active: { label: 'Active', color: '#0ea5e9', icon: MdLocalShipping }, modified: { label: 'Confirmed', color: '#10b981', icon: MdCheckCircle }, confirmed: { label: 'Confirmed', color: '#10b981', icon: MdCheckCircle }, processing: { label: 'Processing', color: '#3b82f6', icon: MdAccessTime }, onhold: { label: 'On Hold', color: '#8b5cf6', icon: MdHistoryToggleOff }, 'on hold': { label: 'On Hold', color: '#8b5cf6', icon: MdHistoryToggleOff }, closed: { label: 'Closed', color: '#06b6d4', icon: MdCheckCircle }, completed: { label: 'Completed', color: '#10b981', icon: MdCheckCircle }, delivered: { label: 'Delivered', color: '#10b981', icon: MdCheckCircle }, skipped: { label: 'Skipped', color: '#f97316', icon: MdCancel }, failed: { label: 'Failed', color: '#991b1b', icon: MdCancel }, cancelled: { label: 'Cancelled', color: '#ef4444', icon: MdCancel } }; // ---------------------------------------------------------------------------- // StatusBadge — filled pill, white text on solid color, high-contrast. // ---------------------------------------------------------------------------- export const StatusBadge = ({ status, minWidth = 86 }) => { if (!status) return null; const meta = STATUS_META[String(status).toLowerCase()] || { label: status, color: DT.textMuted, icon: MdHistoryToggleOff }; const Icon = meta.icon; return ( {meta.label} ); }; // ---------------------------------------------------------------------------- // TimelineCell — time large/bold/high-contrast, date small/muted/secondary. // No decorative dot. // ---------------------------------------------------------------------------- export const TimelineCell = ({ value, utc: useUtc = false }) => { if (!value) { return ( ); } const d = useUtc ? dayjs(value).utc() : dayjs(value); return ( {d.format('hh:mm A')} {d.format('DD MMM YYYY')} ); }; // ---------------------------------------------------------------------------- // MetricPill — small money / number pill used in table cells. // ---------------------------------------------------------------------------- export const MetricPill = ({ value, color, icon, isMoney = false }) => { const n = Number(value); const display = isMoney ? formatNumberToRupees(n) : Number.isFinite(n) ? n : value || 0; const isZero = !Number.isFinite(n) || n === 0; if (isZero) { return ( {display} ); } return ( {icon} {display} ); }; // ---------------------------------------------------------------------------- // SoftPaper — autocomplete dropdown surface. // ---------------------------------------------------------------------------- export const SoftPaper = (props) => ( ); // ---------------------------------------------------------------------------- // AccentAvatar — colored circular icon used inside filter chips & headers. // ---------------------------------------------------------------------------- export const AccentAvatar = ({ color, selected, size = 24, children }) => ( {children} ); // ---------------------------------------------------------------------------- // pillFieldSx — rounded pill style for Autocomplete/TextField inputs. // ---------------------------------------------------------------------------- export const pillFieldSx = (color) => ({ '& .MuiOutlinedInput-root': { borderRadius: DT.radiusPill + 'px', bgcolor: tint(color), fontWeight: 600, '& fieldset': { borderColor: edge(color), borderWidth: 1.5 }, '&:hover fieldset': { borderColor: color }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(color)}` }, '&.Mui-focused fieldset': { borderColor: color, borderWidth: 2 } } }); // ---------------------------------------------------------------------------- // Unified action-button styles — both render at 32px height with same typography. // SecondaryButtonSx → outlined (white bg, neutral border) // PrimaryButtonSx → filled brand (purple bg, white text) // ---------------------------------------------------------------------------- export const SecondaryButtonSx = (active = false) => ({ height: 32, px: 1.25, borderRadius: 1.5, textTransform: 'none', fontSize: 12.5, fontWeight: 700, letterSpacing: 0.1, bgcolor: '#fff', borderColor: active ? edge(BRAND) : DT.borderSubtle, color: active ? BRAND : DT.textPrimary, '&:hover': { bgcolor: '#fff', borderColor: BRAND, color: BRAND, boxShadow: `0 0 0 3px ${ring(BRAND)}` }, '&:focus-visible': { boxShadow: `0 0 0 3px ${ring(BRAND)}` } }); export const PrimaryButtonSx = { height: 32, minHeight: 32, px: 1.5, borderRadius: 1.5, bgcolor: BRAND, color: '#fff', textTransform: 'none', fontSize: 12.5, fontWeight: 700, letterSpacing: 0.1, boxShadow: `0 2px 6px ${ring(BRAND)}`, '&:hover': { bgcolor: BRAND_LIGHT, boxShadow: `0 4px 12px ${ring(BRAND)}` }, '&:focus-visible': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }, '& .MuiButton-startIcon': { mr: 0.75, '& svg': { fontSize: 16 } } }; // ---------------------------------------------------------------------------- // PageHeaderShellSx — the gradient header strip used by every operator page. // Spread onto the outer element. // ---------------------------------------------------------------------------- export const PageHeaderShellSx = { mb: { xs: 1, md: 1.25 }, px: { xs: 1.5, sm: 2 }, py: { xs: 1, sm: 1.25 }, borderRadius: 2, border: '1px solid', borderColor: DT.borderSubtle, background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`, boxShadow: DT.shadowMd }; // ---------------------------------------------------------------------------- // CompactKpiCardSx — visual envelope for a compact KPI card. // Caller supplies the colored left bar via the `accent` arg. // ---------------------------------------------------------------------------- export const CompactKpiCardSx = (accentColor) => ({ position: 'relative', overflow: 'hidden', px: { xs: 1.25, sm: 1.5 }, py: { xs: 0.875, sm: 1.125 }, borderRadius: 2, border: '1px solid', borderColor: DT.borderSubtle, background: '#fff', transition: 'transform 0.15s, box-shadow 0.15s, border-color 0.15s', '&:hover': { transform: 'translateY(-1px)', boxShadow: DT.shadowMd, borderColor: edge(accentColor) } }); // MdAccessTime re-exported so callers can use the inline time icon // without an extra react-icons import. export { MdAccessTime };