diff --git a/src/components/nearle_components/LocationAutocomplete.js b/src/components/nearle_components/LocationAutocomplete.js index dc8eaca..b03d3f8 100644 --- a/src/components/nearle_components/LocationAutocomplete.js +++ b/src/components/nearle_components/LocationAutocomplete.js @@ -52,28 +52,26 @@ const LocationAutocomplete = forwardRef( // Helpers (only used by pill variant) — match the deliveries page's // token shorthand so the same opacity ramp is applied here. const a = (suffix) => `${accentColor}${suffix}`; - const tint = a('08'); const ring = a('26'); - const edge = a('55'); const soft = a('18'); const pillSx = pill ? { cursor: 'pointer', '& .MuiOutlinedInput-root': { - borderRadius: '999px', - bgcolor: tint, + borderRadius: '10px', + bgcolor: '#ffffff', fontWeight: 600, color: '#0f172a', paddingRight: '8px', cursor: 'pointer', - transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.2s', - '& fieldset': { borderColor: edge, borderWidth: 1.5 }, - '&:hover fieldset': { borderColor: accentColor }, + transition: 'border-color 0.15s, box-shadow 0.15s', + '& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 }, + '&:hover fieldset': { borderColor: '#cbd5e1' }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring}` }, - '&.Mui-focused fieldset': { borderColor: accentColor, borderWidth: 2 } + '&.Mui-focused fieldset': { borderColor: accentColor, borderWidth: 1.5 } }, - '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: accentColor } + '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: '#94a3b8' } } : {}; diff --git a/src/components/nearle_components/PageHeader.js b/src/components/nearle_components/PageHeader.js new file mode 100644 index 0000000..1e8c293 --- /dev/null +++ b/src/components/nearle_components/PageHeader.js @@ -0,0 +1,51 @@ +import PropTypes from 'prop-types'; +import { Box, Stack, Typography } from '@mui/material'; + +// ==============================|| PAGE HEADER (Doormile-style clean title) ||============================== // +// A plain, un-boxed page title + optional live subtitle + an action slot +// (zone selector, primary button, etc.). Replaces the heavy boxed/gradient +// header so every operator page opens with a calm, corporate heading. + +export default function PageHeader({ title, subtitle, live = false, action }) { + return ( + + + + {title} + + {subtitle && ( + + {live && ( + + )} + + {subtitle} + + + )} + + {action && {action}} + + ); +} + +PageHeader.propTypes = { + title: PropTypes.node, + subtitle: PropTypes.node, + live: PropTypes.bool, + action: PropTypes.node +}; diff --git a/src/components/nearle_components/StatCard.js b/src/components/nearle_components/StatCard.js new file mode 100644 index 0000000..b99598d --- /dev/null +++ b/src/components/nearle_components/StatCard.js @@ -0,0 +1,77 @@ +import PropTypes from 'prop-types'; +import { Avatar, Box, Card, CardContent, Skeleton, Stack, Typography } from '@mui/material'; + +// ==============================|| STAT / KPI CARD (Doormile-style) ||============================== // +// Clean metric card: muted label, large value, a single soft-tinted rounded +// avatar holding the icon. No coloured top-stripe, no rainbow — colour appears +// only in the small avatar so a row of cards reads calm and corporate. +// +// `icon` is a rendered node, e.g. icon={}. +// `color` is a hex accent (defaults to brand purple); `caption` is the small +// muted line under the value (e.g. "96% of total"). + +export default function StatCard({ title, value, icon, color = '#662582', caption, loading = false }) { + return ( + + + + + + {title} + + {loading ? ( + + ) : ( + + {value} + + )} + {caption && ( + + {caption} + + )} + + {icon && ( + + {icon} + + )} + + + + ); +} + +StatCard.propTypes = { + title: PropTypes.node, + value: PropTypes.node, + icon: PropTypes.node, + color: PropTypes.string, + caption: PropTypes.node, + loading: PropTypes.bool +}; diff --git a/src/components/nearle_components/StatusChip.js b/src/components/nearle_components/StatusChip.js new file mode 100644 index 0000000..6950687 --- /dev/null +++ b/src/components/nearle_components/StatusChip.js @@ -0,0 +1,74 @@ +import PropTypes from 'prop-types'; +import { Chip } from '@mui/material'; + +// ==============================|| STATUS CHIP (Doormile-style soft badge) ||============================== // +// One consistent soft-filled chip for every lifecycle status across orders, +// deliveries, riders, tenants, invoices. Colour encodes meaning; the chip stays +// quiet (soft bg + readable dark text) so tables don't turn into a rainbow. + +// Soft background + readable foreground per semantic tone. +const TONE = { + amber: { bg: '#FEF3C7', fg: '#92400E' }, + indigo: { bg: '#E0E7FF', fg: '#3730A3' }, + cyan: { bg: '#CFFAFE', fg: '#155E75' }, + violet: { bg: '#EDE9FE', fg: '#5B21B6' }, + teal: { bg: '#CCFBF1', fg: '#115E59' }, + emerald: { bg: '#D1FAE5', fg: '#065F46' }, + red: { bg: '#FEE2E2', fg: '#991B1B' }, + orange: { bg: '#FFEDD5', fg: '#9A3412' }, + sky: { bg: '#E0F2FE', fg: '#075985' }, + slate: { bg: '#F1F5F9', fg: '#475569' }, + brand: { bg: '#F1E6F7', fg: '#5B1F73' } +}; + +// Status keyword -> tone + display label. +const MAP = { + pending: { tone: 'amber', label: 'Pending' }, + created: { tone: 'sky', label: 'Created' }, + assigned: { tone: 'indigo', label: 'Assigned' }, + accepted: { tone: 'indigo', label: 'Accepted' }, + arrived: { tone: 'cyan', label: 'Arrived' }, + picked: { tone: 'violet', label: 'Picked' }, + 'picked-up':{ tone: 'violet', label: 'Picked Up' }, + started: { tone: 'cyan', label: 'Started' }, + active: { tone: 'teal', label: 'Active' }, + 'in-transit': { tone: 'teal', label: 'In Transit' }, + delivered: { tone: 'emerald', label: 'Delivered' }, + completed: { tone: 'emerald', label: 'Completed' }, + skipped: { tone: 'orange', label: 'Skipped' }, + failed: { tone: 'red', label: 'Failed' }, + cancelled: { tone: 'red', label: 'Cancelled' }, + // riders / tenants + online: { tone: 'emerald', label: 'Online' }, + offline: { tone: 'slate', label: 'Offline' }, + inactive: { tone: 'slate', label: 'Inactive' }, + idle: { tone: 'amber', label: 'Idle' }, + unknown: { tone: 'slate', label: 'Unknown' }, + // invoices + paid: { tone: 'emerald', label: 'Paid' }, + unpaid: { tone: 'amber', label: 'Unpaid' }, + open: { tone: 'sky', label: 'Open' }, + overdue: { tone: 'red', label: 'Overdue' }, + prepaid: { tone: 'emerald', label: 'Prepaid' }, + cod: { tone: 'amber', label: 'COD' } +}; + +export default function StatusChip({ status, label, size = 'small', sx }) { + const key = String(status || '').toLowerCase().trim().replace(/\s+/g, '-'); + const cfg = MAP[key] || { tone: 'slate', label: status || '—' }; + const tone = TONE[cfg.tone] || TONE.slate; + return ( + + ); +} + +StatusChip.propTypes = { + status: PropTypes.string, + label: PropTypes.node, + size: PropTypes.string, + sx: PropTypes.object +}; diff --git a/src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js b/src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js index 881858a..3cd1a24 100644 --- a/src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js +++ b/src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavItem.js @@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux'; // material-ui import { useTheme } from '@mui/material/styles'; -import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography, useMediaQuery } from '@mui/material'; +import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Tooltip, Typography, useMediaQuery } from '@mui/material'; // project import import Dot from 'components/@extended/Dot'; @@ -80,13 +80,23 @@ const NavItem = ({ item, level }) => { return ( <> {menuOrientation === MenuOrientation.VERTICAL || downLG ? ( - { - // dispatch(setSelectedMenu(item)); + + { + // dispatch(setSelectedMenu(item)); + }} sx={{ zIndex: 1201, pl: drawerOpen ? `${level * 28}px` : 1.5, @@ -168,7 +178,8 @@ const NavItem = ({ item, level }) => { avatar={item.chip.avatar && {item.chip.avatar}} /> )} - + + ) : ( `${c}${suffix}`; const tint = (c) => a(c, '08'); @@ -61,7 +70,6 @@ const ring = (c) => a(c, '26'); const edge = (c) => a(c, '55'); const BRAND = '#662582'; -const BRAND_LIGHT = '#9255AB'; const SoftPaper = (props) => ( const formatDecimal = (value) => new Intl.NumberFormat('en-IN', { minimumFractionDigits: 2 }).format(Number(value) || 0); -// Soft pill for metric cells in the table. -const MetricPill = ({ color, icon, label, width }) => ( +// Numeric table value — plain, strong, right-readable text. The old version +// wrapped every cell in a coloured bordered pill, which made the table read +// like a rainbow; corporate data tables keep figures as quiet typography and +// let the column header carry the meaning. +const MetricPill = ({ label }) => ( + + {label} + +); + +// Subtle neutral category chip (zone / slab) — one quiet style, muted icon. +const CategoryChip = ({ icon, label }) => ( ( gap: 0.5, px: 1, py: 0.375, - borderRadius: 999, - bgcolor: tint(color), - border: `1px solid ${edge(color)}`, - color, - fontSize: 11, - fontWeight: 800, - minWidth: width, - justifyContent: 'center' + borderRadius: 8, + bgcolor: DT.surfaceAlt, + border: `1px solid ${DT.borderSubtle}`, + color: DT.textPrimary, + fontSize: 12, + fontWeight: 600, + whiteSpace: 'nowrap' }} > - {icon} + + {icon} + {label} ); @@ -165,10 +187,10 @@ const ClientsPricing = () => { }, [pricing]); const KPI_META = [ - { key: 'total', label: 'Total Pricing Slabs', color: BRAND, icon: MdLocalOffer, value: stats.total }, - { key: 'tenants', label: 'Tenants Priced', color: '#0ea5e9', icon: MdGroups, value: stats.tenants }, - { key: 'avg', label: 'Avg Base Price', color: '#f59e0b', icon: MdAttachMoney, value: formatRupees(stats.avgBase) }, - { key: 'zone', label: 'Active Zone', color: '#10b981', icon: MdPlace, value: locaName || 'All Zones' } + { key: 'total', label: 'Total Pricing Slabs', color: BRAND, icon: MdOutlineLocalOffer, value: stats.total }, + { key: 'tenants', label: 'Tenants Priced', color: '#0ea5e9', icon: MdOutlineGroups, value: stats.tenants }, + { key: 'avg', label: 'Avg Base Price', color: '#f59e0b', icon: MdOutlineAttachMoney, value: formatRupees(stats.avgBase) }, + { key: 'zone', label: 'Active Zone', color: '#10b981', icon: MdOutlinePlace, value: locaName || 'All Zones' } ]; return ( @@ -176,64 +198,11 @@ const ClientsPricing = () => { {isLoading && } {/* ============================================= || Header || ============================================= */} - - - - - - - - - Pricing - - - - - Live · {locaName || 'All Zones'} - - - - + { paperComponent={SoftPaper} sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} /> - - + } + /> {/* ============================================= || KPI Cards || ============================================= */} - + {KPI_META.map((item) => { const Icon = item.icon; return ( - - - - - - {item.label} - - - {item.value} - - - - - - - + } color={item.color} /> ); })} @@ -379,11 +276,11 @@ const ClientsPricing = () => { sx={{ m: 0, width: '100%', - borderRadius: 999, - bgcolor: tint(BRAND), - '& fieldset': { borderColor: edge(BRAND), borderWidth: 1.5 }, - '&:hover fieldset': { borderColor: BRAND }, - '&.Mui-focused fieldset': { borderColor: BRAND, borderWidth: 2 }, + borderRadius: DT.radiusField + 'px', + bgcolor: DT.surface, + '& fieldset': { borderColor: DT.borderSubtle, borderWidth: 1 }, + '&:hover fieldset': { borderColor: DT.borderHover }, + '&.Mui-focused fieldset': { borderColor: BRAND, borderWidth: 1.5 }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` } }} /> @@ -633,46 +530,14 @@ const ClientsPricing = () => { {row.applocation ? ( - - {row.applocation} - + } label={row.applocation} /> ) : ( )} - - {row.slab || '—'} - + } label={row.slab || '—'} /> diff --git a/src/pages/nearle/clients/Tenants.js b/src/pages/nearle/clients/Tenants.js index 502cbc9..b9bd26d 100644 --- a/src/pages/nearle/clients/Tenants.js +++ b/src/pages/nearle/clients/Tenants.js @@ -64,6 +64,9 @@ import { MdCheckCircle, MdHourglassEmpty, MdCancel, + MdOutlineCheckCircle, + MdOutlinePendingActions, + MdOutlineCancel, MdMyLocation, MdPersonPin } from 'react-icons/md'; @@ -71,6 +74,8 @@ import { LuMail } from 'react-icons/lu'; import { BiUser } from 'react-icons/bi'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar'; +import PageHeader from 'components/nearle_components/PageHeader'; +import StatCard from 'components/nearle_components/StatCard'; import { useQuery } from '@tanstack/react-query'; import { getalltenants, gettenantsummary } from 'pages/api/api'; import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; @@ -81,11 +86,11 @@ import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; // ============================================================================ const DT = { radiusPill: 999, - radiusCard: 16, + radiusCard: 14, radiusInner: 12, - 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)', + shadowSoft: '0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.05)', + shadowMd: '0 1px 3px rgba(15, 23, 42, 0.06)', + shadowPop: '0 12px 32px rgba(15, 23, 42, 0.12)', textPrimary: '#0f172a', textSecondary: '#64748b', textMuted: '#94a3b8', @@ -104,17 +109,17 @@ const edge = (c) => a(c, '55'); const pillFieldSx = (color) => ({ cursor: 'pointer', '& .MuiOutlinedInput-root': { - borderRadius: DT.radiusPill + 'px', - bgcolor: tint(color), + borderRadius: '10px', + bgcolor: '#ffffff', fontWeight: 600, color: DT.textPrimary, paddingRight: '8px', cursor: 'pointer', transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.2s', - '& fieldset': { borderColor: edge(color), borderWidth: 1.5 }, - '&:hover fieldset': { borderColor: color }, + '& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 }, + '&:hover fieldset': { borderColor: '#cbd5e1' }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(color)}` }, - '&.Mui-focused fieldset': { borderColor: color, borderWidth: 2 } + '&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 } }, '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: color } }); @@ -133,9 +138,9 @@ const STATUS_TABS = [ ]; const KPI_META = [ - { key: 'active', label: 'Active Tenants', color: '#10b981', icon: MdCheckCircle, countKey: 'active' }, - { key: 'pending', label: 'Pending Approval', color: '#f59e0b', icon: MdHourglassEmpty, countKey: 'pending' }, - { key: 'inactive', label: 'Inactive Tenants', color: '#ef4444', icon: MdCancel, countKey: 'inactive' } + { key: 'active', label: 'Active Tenants', color: '#10b981', icon: MdOutlineCheckCircle, countKey: 'active' }, + { key: 'pending', label: 'Pending Approval', color: '#f59e0b', icon: MdOutlinePendingActions, countKey: 'pending' }, + { key: 'inactive', label: 'Inactive Tenants', color: '#ef4444', icon: MdOutlineCancel, countKey: 'inactive' } ]; const SoftPaper = (props) => ( @@ -579,64 +584,11 @@ const Clients1 = () => { <> {(getalltenantsIsLoading || summaryDataIsLoading || isloader) && } {/* ============================================= || Header | ============================================= */} - - - - - - - - - Tenants - - - - - Live · {locaName || 'All Zones'} - - - - + { setLocoName={setLocoName} setPage={setPage} pill - accentColor="#6366f1" + accentColor="#662582" icon={} placeholder="Select Zone" paperComponent={SoftPaper} sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} /> - - + } + /> {/* ============================================= || KPI Cards | ============================================= */} - - {KPI_META.map((item) => { + + {KPI_META.map((item, idx) => { const Icon = item.icon; const value = summaryData?.[item.countKey] ?? 0; return ( - { - const idx = STATUS_TABS.findIndex((t) => t.countKey === item.countKey); if (idx >= 0) handleChange(null, idx); }} + sx={{ cursor: 'pointer', height: '100%' }} > - } + color={idx === 0 ? '#662582' : item.color} + loading={summaryDataIsLoading} /> - - - - {item.label} - - {summaryDataIsLoading ? ( - - ) : ( - - {value} - - )} - - - - - - + ); })} @@ -800,16 +687,16 @@ const Clients1 = () => { py: 0.5, flexShrink: 0, cursor: 'pointer', - borderRadius: 999, - border: `1.5px solid ${active ? meta.color : edge(meta.color)}`, - bgcolor: active ? meta.color : tint(meta.color), - color: active ? '#fff' : meta.color, - fontWeight: 700, - boxShadow: active ? `0 6px 18px ${ring(meta.color)}` : 'none', - transition: 'all 0.18s', + borderRadius: '10px', + border: `1px solid ${active ? meta.color : '#e2e8f0'}`, + bgcolor: active ? meta.color : '#fff', + color: active ? '#fff' : '#64748b', + fontWeight: 600, + boxShadow: 'none', + transition: 'background-color 0.15s, border-color 0.15s, color 0.15s', '&:hover': { - borderColor: meta.color, - boxShadow: active ? `0 6px 18px ${ring(meta.color)}` : `0 0 0 3px ${ring(meta.color)}` + borderColor: active ? meta.color : '#cbd5e1', + bgcolor: active ? meta.color : '#f8fafc' } }} > @@ -825,7 +712,7 @@ const Clients1 = () => { {meta.label} @@ -839,10 +726,10 @@ const Clients1 = () => { justifyContent: 'center', borderRadius: 999, fontSize: { xs: 10, md: 11 }, - fontWeight: 800, - bgcolor: active ? 'rgba(255,255,255,0.22)' : '#fff', - color: active ? '#fff' : meta.color, - border: active ? 'none' : `1px solid ${edge(meta.color)}` + fontWeight: 700, + bgcolor: active ? 'rgba(255,255,255,0.22)' : '#f8fafc', + color: active ? '#fff' : '#64748b', + border: 'none' }} > {summaryDataIsLoading ? : count} @@ -862,11 +749,11 @@ const Clients1 = () => { m: 0, width: '100%', borderRadius: 999, - bgcolor: tint('#6366f1'), - '& fieldset': { borderColor: edge('#6366f1'), borderWidth: 1.5 }, - '&:hover fieldset': { borderColor: '#6366f1' }, - '&.Mui-focused fieldset': { borderColor: '#6366f1', borderWidth: 2 }, - '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#6366f1')}` } + bgcolor: tint('#662582'), + '& fieldset': { borderColor: edge('#662582'), borderWidth: 1.5 }, + '&:hover fieldset': { borderColor: '#662582' }, + '&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 2 }, + '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#662582')}` } }} /> @@ -893,9 +780,9 @@ const Clients1 = () => { maxHeight: { xs: 'calc(100vh - 220px)', md: 'calc(100vh - 190px)' }, '&::-webkit-scrollbar': { width: '10px', height: '10px' }, '&::-webkit-scrollbar-thumb': { - backgroundColor: edge('#6366f1'), + backgroundColor: edge('#662582'), borderRadius: '8px', - '&:hover': { backgroundColor: '#6366f1' } + '&:hover': { backgroundColor: '#662582' } }, '&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt } }} @@ -996,7 +883,7 @@ const Clients1 = () => { - + @@ -1109,10 +996,10 @@ const Clients1 = () => { { setSelectedCustomer(row); @@ -2026,7 +1913,7 @@ const Clients1 = () => { header={ - + @@ -2125,10 +2012,10 @@ const Clients1 = () => { { setSelectedCustomer(row); diff --git a/src/pages/nearle/customers/customers.js b/src/pages/nearle/customers/customers.js index a8e57d1..fa57360 100644 --- a/src/pages/nearle/customers/customers.js +++ b/src/pages/nearle/customers/customers.js @@ -30,7 +30,6 @@ import { useTheme } from '@mui/material'; import { - MdPeopleAlt, MdMyLocation, MdPersonPin, MdPhone, @@ -38,7 +37,10 @@ import { MdEdit, MdGroups, MdHowToReg, - MdPlace + MdPlace, + MdOutlineGroups, + MdOutlineHowToReg, + MdOutlinePlace } from 'react-icons/md'; import Geocode from 'react-geocode'; import LocationOnIcon from '@mui/icons-material/LocationOn'; @@ -48,6 +50,8 @@ import { debounce } from '@mui/material/utils'; import Loader from 'components/Loader'; import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; +import PageHeader from 'components/nearle_components/PageHeader'; +import StatCard from 'components/nearle_components/StatCard'; import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { getallcustomers, getcustomersummary } from 'pages/api/api'; import { OpenToast } from 'components/third-party/OpenToast'; @@ -61,10 +65,10 @@ import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'compon // ============================================================================ 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)', + radiusCard: 14, + shadowSoft: '0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.05)', + shadowMd: '0 1px 3px rgba(15, 23, 42, 0.06)', + shadowPop: '0 12px 32px rgba(15, 23, 42, 0.12)', textPrimary: '#0f172a', textSecondary: '#64748b', textMuted: '#94a3b8', @@ -392,9 +396,9 @@ export default function Customers() { } }; const KPI_META = [ - { key: 'total', label: 'Total Customers', color: '#662582', icon: MdGroups, value: pageCount?.Total ?? 0 }, - { key: 'loaded', label: 'Loaded in View', color: '#0ea5e9', icon: MdHowToReg, value: rows.length }, - { key: 'zone', label: 'Active Zone', color: '#10b981', icon: MdPlace, value: locaName || 'All Zones' } + { key: 'total', label: 'Total Customers', color: '#662582', icon: MdOutlineGroups, value: pageCount?.Total ?? 0 }, + { key: 'loaded', label: 'Loaded in View', color: '#0ea5e9', icon: MdOutlineHowToReg, value: rows.length }, + { key: 'zone', label: 'Active Zone', color: '#10b981', icon: MdOutlinePlace, value: locaName || 'All Zones' } ]; return ( @@ -402,64 +406,11 @@ export default function Customers() { {(customerSummaryIsLoading || customersIsLoading) && } {/* ============================================= || Header | ============================================= */} - - - - - - - - - Customers - - - - - Live · {locaName || 'All Zones'} - - - - + - - + } + /> {/* ============================================= || KPI Cards | ============================================= */} - + {KPI_META.map((item) => { const Icon = item.icon; return ( - - - - - - {item.label} - - - {item.value} - - - - - - - + } + color={item.color} + loading={customerSummaryIsLoading} + /> ); })} diff --git a/src/pages/nearle/deliveries/deliveries.js b/src/pages/nearle/deliveries/deliveries.js index e636bc6..3040d1a 100644 --- a/src/pages/nearle/deliveries/deliveries.js +++ b/src/pages/nearle/deliveries/deliveries.js @@ -29,7 +29,11 @@ import { MdRoute, MdSkipNext, MdTune, - MdMyLocation + MdMyLocation, + MdOutlineLocalShipping, + MdOutlinePendingActions, + MdOutlineCheckCircle, + MdOutlineCancel } from 'react-icons/md'; import { useQuery, useMutation, useInfiniteQuery } from '@tanstack/react-query'; @@ -57,7 +61,6 @@ import { Autocomplete, TextField, TableContainer, - Skeleton, Backdrop, MenuItem, Menu, @@ -97,6 +100,8 @@ import { OpenToast } from 'components/third-party/OpenToast'; import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; import LoaderWithImage from 'components/nearle_components/LoaderWithImage'; +import PageHeader from 'components/nearle_components/PageHeader'; +import StatCard from 'components/nearle_components/StatCard'; import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; // ============================================================================ @@ -106,18 +111,22 @@ import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'compon // ============================================================================ const DT = { radiusPill: 999, - radiusCard: 16, - radiusInner: 12, - 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)', + radiusCard: 14, + radiusInner: 10, + radiusField: 10, + // Restrained, low-contrast elevation — corporate (Linear/Stripe), not flashy. + shadowSoft: '0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.05)', + shadowMd: '0 1px 3px rgba(15, 23, 42, 0.06)', + shadowPop: '0 12px 32px rgba(15, 23, 42, 0.12)', textPrimary: '#0f172a', textSecondary: '#64748b', textMuted: '#94a3b8', borderSubtle: '#e2e8f0', + borderHover: '#cbd5e1', divider: '#f1f5f9', surface: '#ffffff', - surfaceAlt: '#f8fafc' + surfaceAlt: '#f8fafc', + brand: '#662582' }; // Quick alpha helpers (hex + percentage suffix). Mirrors the batch-dropdown @@ -131,22 +140,26 @@ const edge = (c) => a(c, '55'); // resting border // Pill input sx — used by every filter Autocomplete/TextField on the page. // Accepts the accent color and returns sx for the outer TextField. Width is // driven by parent flex/grid so this helper stays width-agnostic. -const pillFieldSx = (color) => ({ +// Neutral, corporate filter field: white surface, hairline border, brand focus +// ring. Colour is no longer used to tint the whole control (that produced the +// "rainbow" filter bar) — accent now lives only in the small start-adornment +// icon, which aids scanning without flooding the surface. +const pillFieldSx = () => ({ cursor: 'pointer', '& .MuiOutlinedInput-root': { - borderRadius: DT.radiusPill + 'px', - bgcolor: tint(color), + borderRadius: DT.radiusField + 'px', + bgcolor: DT.surface, fontWeight: 600, color: DT.textPrimary, paddingRight: '8px', cursor: 'pointer', - transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.2s', - '& 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 } + transition: 'border-color 0.15s, box-shadow 0.15s', + '& fieldset': { borderColor: DT.borderSubtle, borderWidth: 1 }, + '&:hover fieldset': { borderColor: DT.borderHover }, + '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(DT.brand)}` }, + '&.Mui-focused fieldset': { borderColor: DT.brand, borderWidth: 1.5 } }, - '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: color } + '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: DT.textMuted } }); // Status palette — drives tab pills, row status badges, dialogs. @@ -177,10 +190,10 @@ const STATUS_TABS = [ // KPI palette + icons — mirrors the four cards across the top of the page. const KPI_META = [ - { key: 'created', label: 'Created Orders', color: '#0ea5e9', icon: MdLocalShipping }, - { key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdHourglassEmpty }, - { key: 'delivered', label: 'Delivered Orders', color: '#10b981', icon: MdCheckCircle }, - { key: 'cancelled', label: 'Cancelled Orders', color: '#ef4444', icon: MdCancel } + { key: 'created', label: 'Created Orders', color: '#0ea5e9', icon: MdOutlineLocalShipping }, + { key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdOutlinePendingActions }, + { key: 'delivered', label: 'Delivered Orders', color: '#10b981', icon: MdOutlineCheckCircle }, + { key: 'cancelled', label: 'Cancelled Orders', color: '#ef4444', icon: MdOutlineCancel } ]; // Batches mirror the dispatch page's slot definitions so an operator who @@ -887,64 +900,11 @@ const Deliveries = () => { } {/* ============================================= || Header | ============================================= */} - - - - - - - - - Deliveries - - - - - Live · {locaName || 'All Zones'} - - - - + { setLocoName={setLocoName} setPage={setPage} pill - accentColor="#6366f1" + accentColor="#662582" icon={} placeholder="Select Zone" paperComponent={SoftPaper} sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} /> - - + } + /> {/* ============================================= || KPI Cards | ============================================= */} - + {[ { ...KPI_META[0], value: batchTotals.all, percentage: null }, { ...KPI_META[1], value: countData?.uncoveredLength, percentage: countData?.total ? Math.round((countData.uncoveredLength / countData.total) * 100) : 0 }, @@ -974,107 +934,14 @@ const Deliveries = () => { const hasPct = !Number.isNaN(pct) && item.percentage !== undefined && item.percentage !== null; return ( - - - - - - {item.label} - - {fetchCountIsLoading ? ( - - ) : ( - - {item.value ?? 0} - - )} - {hasPct && ( - - - {Math.abs(pct)}% - - - of total - - - )} - - - - - - + } + color={item.color} + loading={fetchCountIsLoading} + caption={hasPct ? `${Math.abs(pct)}% of total` : undefined} + /> ); })} @@ -1095,7 +962,7 @@ const Deliveries = () => { }} > - + @@ -1249,29 +1116,19 @@ const Deliveries = () => { minWidth: { xs: 180, sm: 220, md: 240 }, cursor: 'pointer', '& .MuiOutlinedInput-root': { - borderRadius: '999px', - bgcolor: `${activeBatch.color}08`, - fontWeight: 700, - color: activeBatch.color, + borderRadius: '10px', + bgcolor: '#ffffff', + fontWeight: 600, + color: '#0f172a', paddingRight: '8px', cursor: 'pointer', - transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.2s', - '& fieldset': { - borderColor: `${activeBatch.color}55`, - borderWidth: 1.5 - }, - '&:hover fieldset': { borderColor: activeBatch.color }, - '&.Mui-focused': { - boxShadow: `0 0 0 3px ${activeBatch.color}26` - }, - '&.Mui-focused fieldset': { - borderColor: activeBatch.color, - borderWidth: 2 - } + transition: 'border-color 0.15s, box-shadow 0.15s', + '& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 }, + '&:hover fieldset': { borderColor: '#cbd5e1' }, + '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#662582')}` }, + '&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 } }, - '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { - color: activeBatch.color - } + '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: '#94a3b8' } }} /> )} @@ -1291,18 +1148,18 @@ const Deliveries = () => { pl: 0.5, pr: 1.25, py: 0.5, - borderRadius: 999, + borderRadius: '10px', cursor: 'pointer', - bgcolor: tint('#f59e0b'), - border: `1.5px solid ${edge('#f59e0b')}`, - transition: 'border-color 0.15s, box-shadow 0.15s', - '&:hover': { borderColor: '#f59e0b', boxShadow: `0 0 0 3px ${ring('#f59e0b')}` } + bgcolor: '#ffffff', + border: '1px solid #e2e8f0', + transition: 'border-color 0.15s', + '&:hover': { borderColor: '#cbd5e1' } }} > - + - + {dayjs(startdate).format('DD MMM')} – {dayjs(enddate).format('DD MMM')} @@ -1346,7 +1203,7 @@ const Deliveries = () => { ...params.InputProps, startAdornment: ( - + ) }} @@ -1390,7 +1247,7 @@ const Deliveries = () => { ...params.InputProps, startAdornment: ( - + ) }} @@ -1424,7 +1281,7 @@ const Deliveries = () => { ...params.InputProps, startAdornment: ( - + ) }} @@ -1486,33 +1343,33 @@ const Deliveries = () => { py: 0.5, flexShrink: 0, cursor: 'pointer', - borderRadius: 999, - border: `1.5px solid ${active ? meta.color : edge(meta.color)}`, - bgcolor: active ? meta.color : tint(meta.color), - color: active ? '#fff' : meta.color, - fontWeight: 700, - boxShadow: active ? `0 6px 18px ${ring(meta.color)}` : 'none', - transition: 'all 0.18s', + borderRadius: DT.radiusField + 'px', + border: `1px solid ${active ? meta.color : DT.borderSubtle}`, + bgcolor: active ? meta.color : DT.surface, + color: active ? '#fff' : DT.textSecondary, + fontWeight: 600, + boxShadow: 'none', + transition: 'background-color 0.15s, border-color 0.15s, color 0.15s', '&:hover': { - borderColor: meta.color, - boxShadow: active ? `0 6px 18px ${ring(meta.color)}` : `0 0 0 3px ${ring(meta.color)}` + borderColor: active ? meta.color : DT.borderHover, + bgcolor: active ? meta.color : DT.surfaceAlt } }} > - + { {count} @@ -1551,12 +1408,12 @@ const Deliveries = () => { sx={{ m: 0, width: '100%', - borderRadius: 999, - bgcolor: tint('#6366f1'), - '& fieldset': { borderColor: edge('#6366f1'), borderWidth: 1.5 }, - '&:hover fieldset': { borderColor: '#6366f1' }, - '&.Mui-focused fieldset': { borderColor: '#6366f1', borderWidth: 2 }, - '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#6366f1')}` } + borderRadius: DT.radiusField + 'px', + bgcolor: DT.surface, + '& fieldset': { borderColor: DT.borderSubtle, borderWidth: 1 }, + '&:hover fieldset': { borderColor: DT.borderHover }, + '&.Mui-focused fieldset': { borderColor: DT.brand, borderWidth: 1.5 }, + '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(DT.brand)}` } }} /> @@ -1585,10 +1442,10 @@ const Deliveries = () => { overflowX: 'auto', '&::-webkit-scrollbar': { width: '10px', height: '10px', cursor: 'pointer' }, '&::-webkit-scrollbar-thumb': { - backgroundColor: edge('#6366f1'), + backgroundColor: edge('#662582'), borderRadius: '8px', cursor: 'pointer', - '&:hover': { backgroundColor: '#6366f1' } + '&:hover': { backgroundColor: '#662582' } }, '&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt } }} @@ -1632,7 +1489,7 @@ const Deliveries = () => { const RowStatusIcon = rowStatusMeta.icon; const isSelected = !!deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid); const isOpen = productCollapse?.orderid === row?.orderid; - const chipSx = (c) => ({ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', px: 1, py: 0.25, borderRadius: 999, bgcolor: tint(c), color: c, fontWeight: 700, fontSize: 11, border: `1px solid ${edge(c)}`, whiteSpace: 'nowrap' }); + const chipSx = (c) => ({ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: c, fontWeight: 700, fontSize: 12, whiteSpace: 'nowrap' }); return ( { handleMenuOpen(e, row)} - sx={{ borderRadius: 999, bgcolor: tint('#6366f1'), color: '#6366f1', border: `1px solid ${edge('#6366f1')}`, '&:hover': { bgcolor: soft('#6366f1') } }} + sx={{ borderRadius: 999, bgcolor: tint('#662582'), color: '#662582', border: `1px solid ${edge('#662582')}`, '&:hover': { bgcolor: soft('#662582') } }} > @@ -1793,7 +1650,7 @@ const Deliveries = () => { {row.step ? ( - {row.step} + {row.step} ) : ( )} @@ -1807,8 +1664,8 @@ const Deliveries = () => { {isOpen && ( - - + + Product Details @@ -2208,9 +2065,9 @@ const Deliveries = () => { height: 24, px: 0.875, borderRadius: 999, - bgcolor: tint('#6366f1'), - border: `1px solid ${edge('#6366f1')}`, - color: '#6366f1', + bgcolor: tint('#662582'), + border: `1px solid ${edge('#662582')}`, + color: '#662582', fontWeight: 800, fontSize: 11 }} @@ -2276,10 +2133,10 @@ const Deliveries = () => { onClick={(e) => handleMenuOpen(e, row)} sx={{ borderRadius: 999, - bgcolor: tint('#6366f1'), - color: '#6366f1', - border: `1px solid ${edge('#6366f1')}`, - '&:hover': { bgcolor: soft('#6366f1') } + bgcolor: tint('#662582'), + color: '#662582', + border: `1px solid ${edge('#662582')}`, + '&:hover': { bgcolor: soft('#662582') } }} > @@ -2311,9 +2168,9 @@ const Deliveries = () => { background: '#fff' }} > - - - + + + Product Details @@ -2921,14 +2778,14 @@ const Deliveries = () => { borderRadius: 2, bgcolor: '#fff', '& fieldset': { borderColor: DT.borderSubtle }, - '&:hover fieldset': { borderColor: '#6366f1' }, - '&.Mui-focused fieldset': { borderColor: '#6366f1', borderWidth: 2 }, - '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#6366f1')}` } + '&:hover fieldset': { borderColor: '#662582' }, + '&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 2 }, + '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#662582')}` } } }} > {['pending','accepted','started','arrived','delivered','cancelled'].map((s) => { - const m = STATUS_META[s] || { label: s, color: '#6366f1', icon: MdHistoryToggleOff }; + const m = STATUS_META[s] || { label: s, color: '#662582', icon: MdHistoryToggleOff }; const Ic = m.icon; return ( @@ -2955,9 +2812,9 @@ const Deliveries = () => { borderRadius: 2, bgcolor: '#fff', '& fieldset': { borderColor: DT.borderSubtle }, - '&:hover fieldset': { borderColor: '#6366f1' }, - '&.Mui-focused fieldset': { borderColor: '#6366f1', borderWidth: 2 }, - '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#6366f1')}` } + '&:hover fieldset': { borderColor: '#662582' }, + '&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 2 }, + '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#662582')}` } } }} /> diff --git a/src/pages/nearle/dispatch/Dispatch.css b/src/pages/nearle/dispatch/Dispatch.css index 98f74c1..b6d2be1 100644 --- a/src/pages/nearle/dispatch/Dispatch.css +++ b/src/pages/nearle/dispatch/Dispatch.css @@ -3,11 +3,11 @@ --bg-sub: #f8fafc; --bg-card: #ffffff; --border: #e2e8f0; - --border-active: #3b82f6; + --border-active: #9255AB; --text: #1e293b; --text-muted: #64748b; - --accent: #3b82f6; - --accent-soft: rgba(59, 130, 246, 0.08); + --accent: #9255AB; + --accent-soft: rgba(146, 85, 171, 0.08); --kitchen: #f59e0b; --kitchen-soft: rgba(245, 158, 11, 0.1); --success: #22c55e; @@ -68,7 +68,7 @@ width: 32px; height: 32px; border-radius: 8px; - background: linear-gradient(135deg, #3b82f6, #2563eb); + background: linear-gradient(135deg, #9255AB, #2563eb); display: flex; align-items: center; justify-content: center; @@ -308,7 +308,7 @@ background: var(--accent); border-color: var(--accent); color: #fff; - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.25); + box-shadow: 0 4px 12px rgba(146, 85, 171, 0.25); } /* SVG icon slot inside each tab button — fixed square, color inherits from button @@ -389,7 +389,7 @@ .dispatch-container .strat-stat-orders { background: var(--accent-soft); - border-color: rgba(59, 130, 246, 0.25); + border-color: rgba(146, 85, 171, 0.25); } .dispatch-container .strat-stat-orders .strat-stat-value { @@ -511,22 +511,22 @@ } .dispatch-container .date-chip.is-open { - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2), - 0 12px 30px rgba(99, 102, 241, 0.22); + border-color: #662582; + box-shadow: 0 0 0 3px rgba(102, 37, 130, 0.2), + 0 12px 30px rgba(102, 37, 130, 0.22); } .dispatch-container .date-chip:hover { - border-color: rgba(99, 102, 241, 0.45); + border-color: rgba(102, 37, 130, 0.45); box-shadow: 0 2px 4px rgba(15, 23, 42, 0.06), - 0 8px 22px rgba(99, 102, 241, 0.15); + 0 8px 22px rgba(102, 37, 130, 0.15); transform: translateY(-1px); } .dispatch-container .date-chip:focus-within { - border-color: #6366f1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2), - 0 8px 22px rgba(99, 102, 241, 0.22); + border-color: #662582; + box-shadow: 0 0 0 3px rgba(102, 37, 130, 0.2), + 0 8px 22px rgba(102, 37, 130, 0.22); } /* Center card — visible chrome the operator reads. Renders as a