updated the ui

This commit is contained in:
2026-06-11 18:09:10 +05:30
parent 251be9afd2
commit f50b4b010c
33 changed files with 1527 additions and 2302 deletions

View File

@@ -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' }
}
: {};

View File

@@ -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 (
<Stack
direction={{ xs: 'column', sm: 'row' }}
justifyContent="space-between"
alignItems={{ xs: 'flex-start', sm: 'center' }}
spacing={1.5}
sx={{ mb: { xs: 2, md: 2.5 } }}
>
<Box sx={{ minWidth: 0 }}>
<Typography variant="h3" sx={{ fontWeight: 700, letterSpacing: '-0.02em', color: 'grey.800' }}>
{title}
</Typography>
{subtitle && (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
{live && (
<Box
sx={{
width: 7,
height: 7,
borderRadius: '50%',
bgcolor: 'success.main',
boxShadow: '0 0 0 3px rgba(16,185,129,0.18)'
}}
/>
)}
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 500 }}>
{subtitle}
</Typography>
</Stack>
)}
</Box>
{action && <Box sx={{ flexShrink: 0, width: { xs: '100%', sm: 'auto' } }}>{action}</Box>}
</Stack>
);
}
PageHeader.propTypes = {
title: PropTypes.node,
subtitle: PropTypes.node,
live: PropTypes.bool,
action: PropTypes.node
};

View File

@@ -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={<MdLocalShipping size={20} />}.
// `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 (
<Card sx={{ height: '100%' }}>
<CardContent sx={{ p: { xs: 1.75, md: 2 }, '&:last-child': { pb: { xs: 1.75, md: 2 } } }}>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={1}>
<Box sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="body2"
sx={{
fontWeight: 500,
color: 'text.secondary',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{title}
</Typography>
{loading ? (
<Skeleton variant="rounded" sx={{ width: 72, height: 30, mt: 0.5 }} animation="wave" />
) : (
<Typography
sx={{
mt: 0.25,
fontWeight: 700,
letterSpacing: '-0.015em',
color: 'grey.800',
lineHeight: 1.15,
fontSize: { xs: '1.375rem', md: '1.5rem' },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{value}
</Typography>
)}
{caption && (
<Typography variant="caption" sx={{ display: 'block', mt: 0.5, color: 'text.secondary' }}>
{caption}
</Typography>
)}
</Box>
{icon && (
<Avatar
variant="rounded"
sx={{ width: 42, height: 42, borderRadius: 2, bgcolor: `${color}14`, color, flexShrink: 0 }}
>
{icon}
</Avatar>
)}
</Stack>
</CardContent>
</Card>
);
}
StatCard.propTypes = {
title: PropTypes.node,
value: PropTypes.node,
icon: PropTypes.node,
color: PropTypes.string,
caption: PropTypes.node,
loading: PropTypes.bool
};

View File

@@ -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 (
<Chip
size={size}
label={label || cfg.label}
sx={{ bgcolor: tone.bg, color: tone.fg, border: 'none', fontWeight: 600, ...sx }}
/>
);
}
StatusChip.propTypes = {
status: PropTypes.string,
label: PropTypes.node,
size: PropTypes.string,
sx: PropTypes.object
};

View File

@@ -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 ? (
<ListItemButton
{...listItemProps}
disabled={item.disabled}
selected={isSelected}
onClick={() => {
// dispatch(setSelectedMenu(item));
<Tooltip
title={!drawerOpen && level === 1 ? item.title : ''}
placement="right"
arrow
disableInteractive
componentsProps={{
tooltip: { sx: { bgcolor: '#0f172a', fontSize: 12, fontWeight: 600, px: 1.25, py: 0.75, borderRadius: 1.5 } },
arrow: { sx: { color: '#0f172a' } }
}}
>
<ListItemButton
{...listItemProps}
disabled={item.disabled}
selected={isSelected}
onClick={() => {
// 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 && <Avatar>{item.chip.avatar}</Avatar>}
/>
)}
</ListItemButton>
</ListItemButton>
</Tooltip>
) : (
<ListItemButton
{...listItemProps}

View File

@@ -24,13 +24,19 @@ import {
MdSpeed,
MdPriceCheck,
MdStraighten,
MdReceiptLong
MdReceiptLong,
MdOutlineLocalOffer,
MdOutlineGroups,
MdOutlineAttachMoney,
MdOutlinePlace
} from 'react-icons/md';
import { useQuery } from '@tanstack/react-query';
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 { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import { getallpricing } from 'pages/api/api';
@@ -42,17 +48,20 @@ import { getallpricing } from 'pages/api/api';
// ============================================================================
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,
radiusField: 10,
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'
};
const a = (c, suffix) => `${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) => (
<Paper
@@ -101,8 +109,21 @@ const formatRupees = (value) =>
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 }) => (
<Typography
component="span"
sx={{ fontSize: 13.5, fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' }}
>
{label}
</Typography>
);
// Subtle neutral category chip (zone / slab) — one quiet style, muted icon.
const CategoryChip = ({ icon, label }) => (
<Box
sx={{
display: 'inline-flex',
@@ -110,17 +131,18 @@ const MetricPill = ({ color, icon, label, width }) => (
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}
<Box component="span" sx={{ display: 'inline-flex', color: DT.textMuted }}>
{icon}
</Box>
{label}
</Box>
);
@@ -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 && <Loader />}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdLocalOffer size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Pricing
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Pricing"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<LocationAutocomplete
locaName={locaName}
setAppId={setAppId}
@@ -245,88 +214,16 @@ const ClientsPricing = () => {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.value}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard title={item.label} value={item.value} icon={<Icon size={20} />} color={item.color} />
</Grid>
);
})}
@@ -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 = () => {
<TableCell>
{row.applocation ? (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint('#10b981'),
border: `1px solid ${edge('#10b981')}`,
color: '#10b981',
fontSize: 11,
fontWeight: 800
}}
>
<MdPlace size={12} /> {row.applocation}
</Box>
<CategoryChip icon={<MdPlace size={12} />} label={row.applocation} />
) : (
<Typography variant="caption" sx={{ color: DT.textMuted }}></Typography>
)}
</TableCell>
<TableCell>
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint('#0ea5e9'),
border: `1px solid ${edge('#0ea5e9')}`,
color: '#0ea5e9',
fontSize: 11,
fontWeight: 800
}}
>
<MdSpeed size={12} /> {row.slab || '—'}
</Box>
<CategoryChip icon={<MdSpeed size={12} />} label={row.slab || '—'} />
</TableCell>
<TableCell align="center">

View File

@@ -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) && <Loader />}
{/* ============================================= || Header | ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint('#6366f1')} 0%, ${tint('#0ea5e9')} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: '#6366f1',
color: '#fff',
boxShadow: '0 6px 18px rgba(99,102,241,0.32)'
}}
>
<MdGroups size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Tenants
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Tenants"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<LocationAutocomplete
locations={locations}
locaName={locaName}
@@ -644,101 +596,36 @@ const Clients1 = () => {
setLocoName={setLocoName}
setPage={setPage}
pill
accentColor="#6366f1"
accentColor="#662582"
icon={<MdMyLocation size={14} />}
placeholder="Select Zone"
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards | ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
{KPI_META.map((item) => {
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item, idx) => {
const Icon = item.icon;
const value = summaryData?.[item.countKey] ?? 0;
return (
<Grid item key={item.key} xs={12} sm={4}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
cursor: 'pointer',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
<Box
onClick={() => {
const idx = STATUS_TABS.findIndex((t) => t.countKey === item.countKey);
if (idx >= 0) handleChange(null, idx);
}}
sx={{ cursor: 'pointer', height: '100%' }}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
<StatCard
title={item.label}
value={value ?? 0}
icon={<Icon size={20} />}
color={idx === 0 ? '#662582' : item.color}
loading={summaryDataIsLoading}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
{summaryDataIsLoading ? (
<Skeleton sx={{ width: 70, height: { xs: 28, md: 36 } }} animation="wave" />
) : (
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.5rem', sm: '1.75rem', md: '2rem' }
}}
>
{value}
</Typography>
)}
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
</Box>
</Grid>
);
})}
@@ -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 = () => {
</Avatar>
<Typography
variant="caption"
sx={{ fontWeight: 800, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}
sx={{ fontWeight: 600, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}
>
{meta.label}
</Typography>
@@ -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 ? <Skeleton variant="text" width={14} height={10} /> : 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')}` }
}}
/>
</Box>
@@ -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 = () => {
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={1}>
<AccentAvatar color="#6366f1" size={32}>
<AccentAvatar color="#662582" size={32}>
<MdPersonPin size={16} />
</AccentAvatar>
<Stack>
@@ -1109,10 +996,10 @@ const Clients1 = () => {
<IconButton
size="small"
sx={{
bgcolor: openRowIndex1 === index ? '#6366f1' : soft('#6366f1'),
color: openRowIndex1 === index ? '#fff' : '#6366f1',
border: `1px solid ${edge('#6366f1')}`,
'&:hover': { bgcolor: '#6366f1', color: '#fff' }
bgcolor: openRowIndex1 === index ? '#662582' : soft('#662582'),
color: openRowIndex1 === index ? '#fff' : '#662582',
border: `1px solid ${edge('#662582')}`,
'&:hover': { bgcolor: '#662582', color: '#fff' }
}}
onClick={() => {
setSelectedCustomer(row);
@@ -2026,7 +1913,7 @@ const Clients1 = () => {
header={
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
<AccentAvatar color="#6366f1" size={36}>
<AccentAvatar color="#662582" size={36}>
<MdPersonPin size={18} />
</AccentAvatar>
<Stack sx={{ minWidth: 0 }}>
@@ -2125,10 +2012,10 @@ const Clients1 = () => {
<IconButton
size="small"
sx={{
bgcolor: expanded ? '#6366f1' : soft('#6366f1'),
color: expanded ? '#fff' : '#6366f1',
border: `1px solid ${edge('#6366f1')}`,
'&:hover': { bgcolor: '#6366f1', color: '#fff' }
bgcolor: expanded ? '#662582' : soft('#662582'),
color: expanded ? '#fff' : '#662582',
border: `1px solid ${edge('#662582')}`,
'&:hover': { bgcolor: '#662582', color: '#fff' }
}}
onClick={() => {
setSelectedCustomer(row);

View File

@@ -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) && <Loader />}
{/* ============================================= || Header | ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint('#662582')} 0%, ${tint('#9255AB')} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: '#662582',
color: '#fff',
boxShadow: '0 6px 18px rgba(99,102,241,0.32)'
}}
>
<MdPeopleAlt size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Customers
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Customers"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<LocationAutocomplete
locaName={locaName}
setAppId={setAppId}
@@ -471,88 +422,22 @@ export default function Customers() {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards | ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={4}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.value}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={customerSummaryIsLoading}
/>
</Grid>
);
})}

View File

@@ -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 = () => {
</Backdrop>
}
{/* ============================================= || Header | ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint('#6366f1')} 0%, ${tint('#0ea5e9')} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: '#6366f1',
color: '#fff',
boxShadow: '0 6px 18px rgba(99,102,241,0.32)'
}}
>
<MdLocalShipping size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Deliveries
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Deliveries"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<LocationAutocomplete
ref={locationRef}
locaName={locaName}
@@ -952,17 +912,17 @@ const Deliveries = () => {
setLocoName={setLocoName}
setPage={setPage}
pill
accentColor="#6366f1"
accentColor="#662582"
icon={<MdMyLocation size={14} />}
placeholder="Select Zone"
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards | ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{[
{ ...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 (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
cursor: 'pointer',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
{fetchCountIsLoading ? (
<Skeleton sx={{ width: 70, height: { xs: 28, md: 36 } }} animation="wave" />
) : (
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.5rem', sm: '1.75rem', md: '2rem' }
}}
>
{item.value ?? 0}
</Typography>
)}
{hasPct && (
<Stack direction="row" alignItems="center" spacing={0.5} sx={{ mt: 0.5, flexWrap: 'wrap' }}>
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.25,
px: 0.75,
py: 0.25,
borderRadius: 999,
bgcolor: soft(item.color),
color: item.color,
fontSize: { xs: 10, sm: 11 },
fontWeight: 800
}}
>
{Math.abs(pct)}%
</Box>
<Typography
variant="caption"
sx={{ color: DT.textMuted, display: { xs: 'none', sm: 'inline' } }}
>
of total
</Typography>
</Stack>
)}
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={fetchCountIsLoading}
caption={hasPct ? `${Math.abs(pct)}% of total` : undefined}
/>
</Grid>
);
})}
@@ -1095,7 +962,7 @@ const Deliveries = () => {
}}
>
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: { xs: 1, md: 1.5 }, color: DT.textSecondary }}>
<Avatar sx={{ width: 28, height: 28, bgcolor: soft('#6366f1'), color: '#6366f1' }}>
<Avatar sx={{ width: 28, height: 28, bgcolor: soft('#662582'), color: '#662582' }}>
<MdTune size={16} />
</Avatar>
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: 0.6, textTransform: 'uppercase', color: DT.textSecondary }}>
@@ -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' }
}}
>
<AccentAvatar color="#f59e0b">
<AccentAvatar color="#94a3b8">
<MdOutlineDateRange size={14} />
</AccentAvatar>
<Typography variant="caption" sx={{ fontWeight: 700, color: '#f59e0b', whiteSpace: 'nowrap' }}>
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569', whiteSpace: 'nowrap' }}>
{dayjs(startdate).format('DD MMM')} {dayjs(enddate).format('DD MMM')}
</Typography>
</Stack>
@@ -1346,7 +1203,7 @@ const Deliveries = () => {
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5, mr: 0.25, flexShrink: 0 }}>
<AccentAvatar color="#0ea5e9"><MdStorefront size={14} /></AccentAvatar>
<AccentAvatar color="#94a3b8"><MdStorefront size={14} /></AccentAvatar>
</Stack>
)
}}
@@ -1390,7 +1247,7 @@ const Deliveries = () => {
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5, mr: 0.25, flexShrink: 0 }}>
<AccentAvatar color="#14b8a6"><MdLocationOn size={14} /></AccentAvatar>
<AccentAvatar color="#94a3b8"><MdLocationOn size={14} /></AccentAvatar>
</Stack>
)
}}
@@ -1424,7 +1281,7 @@ const Deliveries = () => {
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5, mr: 0.25, flexShrink: 0 }}>
<AccentAvatar color="#8b5cf6"><MdDirectionsBike size={14} /></AccentAvatar>
<AccentAvatar color="#94a3b8"><MdDirectionsBike size={14} /></AccentAvatar>
</Stack>
)
}}
@@ -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
}
}}
>
<Avatar
sx={{
width: { xs: 22, md: 26 },
height: { xs: 22, md: 26 },
width: { xs: 20, md: 22 },
height: { xs: 20, md: 22 },
bgcolor: active ? 'rgba(255,255,255,0.22)' : soft(meta.color),
color: active ? '#fff' : meta.color
}}
>
<Icon size={13} />
<Icon size={12} />
</Avatar>
<Typography
variant="caption"
sx={{
fontWeight: 800,
fontWeight: 600,
fontSize: { xs: 11.5, md: 13 },
lineHeight: 1
}}
@@ -1521,18 +1378,18 @@ const Deliveries = () => {
</Typography>
<Box
sx={{
minWidth: { xs: 22, md: 26 },
height: { xs: 18, md: 22 },
minWidth: { xs: 20, md: 24 },
height: { xs: 18, md: 20 },
px: 0.625,
display: 'inline-flex',
alignItems: 'center',
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)' : DT.surfaceAlt,
color: active ? '#fff' : DT.textSecondary,
border: 'none'
}}
>
{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)}` }
}}
/>
</Box>
@@ -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 (
<MobileCard
key={row.orderheaderid ?? `${row.tenantname}-${index}`}
@@ -1705,7 +1562,7 @@ const Deliveries = () => {
<IconButton
size="small"
onClick={(e) => 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') } }}
>
<EditOutlined />
</IconButton>
@@ -1793,7 +1650,7 @@ const Deliveries = () => {
</MobileField>
<MobileField label="Step">
{row.step ? (
<Box sx={{ ...chipSx('#6366f1'), minWidth: 30, fontWeight: 800 }}>{row.step}</Box>
<Box sx={{ ...chipSx('#662582'), minWidth: 30, fontWeight: 800 }}>{row.step}</Box>
) : (
<Typography variant="caption" sx={{ color: DT.textMuted }}></Typography>
)}
@@ -1807,8 +1664,8 @@ const Deliveries = () => {
{isOpen && (
<Box sx={{ mt: 1.5, p: 1.25, borderRadius: 2, bgcolor: DT.surfaceAlt, border: `1px solid ${DT.divider}` }}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1 }}>
<AccentAvatar color="#6366f1" size={22}><MdInventory2 size={12} /></AccentAvatar>
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: 0.5, textTransform: 'uppercase', color: '#6366f1' }}>
<AccentAvatar color="#662582" size={22}><MdInventory2 size={12} /></AccentAvatar>
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: 0.5, textTransform: 'uppercase', color: '#662582' }}>
Product Details
</Typography>
</Stack>
@@ -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') }
}}
>
<EditOutlined />
@@ -2311,9 +2168,9 @@ const Deliveries = () => {
background: '#fff'
}}
>
<Stack direction="row" alignItems="center" spacing={1} sx={{ px: 2, py: 1.25, borderBottom: `1px solid ${DT.divider}`, bgcolor: tint('#6366f1') }}>
<AccentAvatar color="#6366f1"><MdInventory2 size={14} /></AccentAvatar>
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: 0.5, textTransform: 'uppercase', color: '#6366f1' }}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ px: 2, py: 1.25, borderBottom: `1px solid ${DT.divider}`, bgcolor: tint('#662582') }}>
<AccentAvatar color="#662582"><MdInventory2 size={14} /></AccentAvatar>
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: 0.5, textTransform: 'uppercase', color: '#662582' }}>
Product Details
</Typography>
</Stack>
@@ -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 (
<MenuItem key={s} value={s} sx={{ gap: 1 }}>
@@ -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')}` }
}
}}
/>

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,9 @@ import {
MdSpeed,
MdExplore,
MdAccessTime,
MdOutlineInventory2,
MdOutlineTwoWheeler,
MdOutlineAccessTime,
MdGpsFixed,
MdPower,
MdSearch,
@@ -72,7 +75,7 @@ import logger from '../../../utils/logger';
// emerald for the actual GPS trail (signals "live / real" data). Per-step
// distinction in Combined view is carried by the numbered drop pins, which
// keep STEP_PALETTE so the timeline link to a specific delivery survives.
const COMBINED_PLANNED_COLOR = '#6366f1';
const COMBINED_PLANNED_COLOR = '#662582';
const COMBINED_ACTUAL_COLOR = '#10b981';
const toNum = (v) => {
@@ -2453,16 +2456,16 @@ const Dispatch = ({
const estMeters = activeOrder ? calculateEstMeters(r.id, activeOrder) : null;
return (
<div key={r.id} className="rcard" onClick={() => handleRiderFocus(r)} style={{ animationDelay: `${i * 0.05}s` }}>
<div key={r.id} className="rcard" onClick={() => handleRiderFocus(r)} style={{ animationDelay: `${i * 0.05}s`, borderLeft: `3px solid ${r.color}` }}>
<div className="rcard-top">
<div className="rcard-emo" style={{ background: `${r.color}18`, borderColor: `${r.color}50`, color: r.color }}><MdTwoWheeler /></div>
<div className="rcard-emo" style={{ background: '#f1f5f9', borderColor: '#e2e8f0', color: '#64748b' }}><MdTwoWheeler /></div>
<div className="rcard-info">
<div className="rcard-name">{r.riderName}</div>
<div className="rcard-zone">{r.orders[0]?.zone_name || locationName || 'Local'} · {new Set(r.orders.map(o => o.trip_number || 1)).size} trips</div>
</div>
<div
className={`rcard-badge ${isDone ? 'is-done' : ''}`}
style={isDone ? undefined : { background: `${r.color}18`, color: r.color }}
style={isDone ? undefined : { background: '#f1f5f9', color: '#475569' }}
title={`${delivered} delivered of ${total} total`}
>
{delivered}/{total}
@@ -5115,7 +5118,7 @@ const Dispatch = ({
style={{
boxShadow: 'var(--shadow-lg)',
background: compareOpen
? 'linear-gradient(135deg, #6366f1, #3b82f6)'
? 'linear-gradient(135deg, #662582, #9255AB)'
: '#fff',
marginLeft: 8,
color: compareOpen ? '#fff' : undefined
@@ -5713,9 +5716,9 @@ const Dispatch = ({
to "was today big or small?" is the first thing visible. */}
<div className="da-section">
<div className="da-hero-row">
<div className="da-hero-card" style={{ borderTopColor: activeMeta.color }}>
<div className="da-hero-icon" style={{ background: `${activeMeta.color}22`, color: activeMeta.color }}>
<MdInventory2 />
<div className="da-hero-card">
<div className="da-hero-icon" style={{ background: '#6625821f', color: '#662582' }}>
<MdOutlineInventory2 />
</div>
<div className="da-hero-value">{analysisFormatNum(fleet.total_orders)}</div>
<div className="da-hero-label">Total Orders</div>
@@ -5725,9 +5728,9 @@ const Dispatch = ({
</div>
)}
</div>
<div className="da-hero-card" style={{ borderTopColor: '#0ea5e9' }}>
<div className="da-hero-icon" style={{ background: '#0ea5e922', color: '#0ea5e9' }}>
<MdTwoWheeler />
<div className="da-hero-card">
<div className="da-hero-icon" style={{ background: '#0ea5e91f', color: '#0ea5e9' }}>
<MdOutlineTwoWheeler />
</div>
<div className="da-hero-value">{analysisFormatNum(fleet.total_riders)}</div>
<div className="da-hero-label">Active Riders</div>
@@ -5737,9 +5740,9 @@ const Dispatch = ({
</div>
)}
</div>
<div className="da-hero-card" style={{ borderTopColor: '#8b5cf6' }}>
<div className="da-hero-icon" style={{ background: '#8b5cf622', color: '#8b5cf6' }}>
<MdAccessTime />
<div className="da-hero-card">
<div className="da-hero-icon" style={{ background: '#8b5cf61f', color: '#8b5cf6' }}>
<MdOutlineAccessTime />
</div>
<div className="da-hero-value">
{fleet.total_duration_minutes != null ? `${fleet.total_duration_minutes}` : '—'}

View File

@@ -35,12 +35,16 @@ import {
MdEventNote,
MdCurrencyRupee,
MdVisibility,
MdInventory2
MdInventory2,
MdOutlinePendingActions,
MdOutlineCheckCircle
} from 'react-icons/md';
import { fetchinvoiceinsight, fetchdeliverylist } from 'pages/api/api';
import Loader from 'components/Loader';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
@@ -50,10 +54,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',
@@ -69,7 +73,6 @@ const ring = (c) => a(c, '26');
const edge = (c) => a(c, '55');
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
const AccentAvatar = ({ color, selected, size = 24, children }) => (
<Avatar
@@ -199,9 +202,9 @@ const Invoice = () => {
const KPI_META = [
{ idx: 0, label: 'All Invoices', color: BRAND, icon: MdDashboard, value: insightdata?.totalcount ?? 0 },
{ idx: 1, label: 'Open', color: '#ef4444', icon: MdHourglassEmpty, value: insightdata?.pendingcount ?? 0 },
{ idx: 1, label: 'Open', color: '#ef4444', icon: MdOutlinePendingActions, value: insightdata?.pendingcount ?? 0 },
{ idx: 2, label: 'Overdue', color: '#f59e0b', icon: MdReportProblem, value: insightdata?.overduecount ?? 0 },
{ idx: 3, label: 'Paid', color: '#10b981', icon: MdCheckCircle, value: insightdata?.paidcount ?? 0 }
{ idx: 3, label: 'Paid', color: '#10b981', icon: MdOutlineCheckCircle, value: insightdata?.paidcount ?? 0 }
];
const activeMeta = STATUS_META[billStatus];
@@ -211,64 +214,11 @@ const Invoice = () => {
{(isloader || isLoading) && <Loader />}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdReceiptLong size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Invoices
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · Viewing {activeMeta.label.toLowerCase()} invoices
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Invoices"
subtitle={`Live · Viewing ${activeMeta.label.toLowerCase()} invoices`}
live
action={
<Box
sx={{
display: 'inline-flex',
@@ -277,7 +227,7 @@ const Invoice = () => {
px: 1.5,
py: 0.875,
borderRadius: 999,
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
border: `1.5px solid ${edge(BRAND)}`,
color: BRAND,
fontWeight: 800,
@@ -292,90 +242,30 @@ const Invoice = () => {
{formatNumberToRupees(grandTotal)}
</Typography>
</Box>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards (clickable filter) || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
const active = billStatus === item.idx;
return (
<Grid item key={item.idx} xs={6} sm={6} md={3}>
<Paper
elevation={0}
<Box
onClick={() => {
setBillStatus(item.idx);
setPage(0);
}}
sx={{
position: 'relative',
overflow: 'hidden',
cursor: 'pointer',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: active ? edge(item.color) : DT.borderSubtle,
background: active ? tint(item.color) : '#fff',
boxShadow: active ? `0 0 0 3px ${ring(item.color)}` : 'none',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s, background 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
sx={{ cursor: 'pointer', height: '100%' }}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
<StatCard
title={item.label}
value={item.value}
icon={<Icon size={20} />}
color={item.color}
loading={isInsightLoading}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
{item.value}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
</Box>
</Grid>
);
})}
@@ -438,32 +328,32 @@ const Invoice = () => {
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',
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 : '#cbd5e1',
bgcolor: active ? meta.color : DT.surfaceAlt
}
}}
>
<Avatar
sx={{
width: { xs: 22, md: 26 },
height: { xs: 22, md: 26 },
width: { xs: 20, md: 22 },
height: { xs: 20, md: 22 },
bgcolor: active ? 'rgba(255,255,255,0.22)' : soft(meta.color),
color: active ? '#fff' : meta.color
}}
>
<Icon size={13} />
<Icon size={12} />
</Avatar>
<Typography
variant="caption"
sx={{
fontWeight: 800,
fontWeight: 600,
fontSize: { xs: 11.5, md: 13 },
lineHeight: 1
}}
@@ -472,18 +362,18 @@ const Invoice = () => {
</Typography>
<Box
sx={{
minWidth: { xs: 22, md: 26 },
height: { xs: 18, md: 22 },
minWidth: { xs: 20, md: 24 },
height: { xs: 18, md: 20 },
px: 0.625,
display: 'inline-flex',
alignItems: 'center',
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)' : DT.surfaceAlt,
color: active ? '#fff' : DT.textSecondary,
border: 'none'
}}
>
{count}
@@ -503,10 +393,10 @@ const Invoice = () => {
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 },
bgcolor: '#ffffff',
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>
@@ -628,7 +518,7 @@ const Invoice = () => {
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
border: `1px solid ${edge(BRAND)}`,
color: BRAND,
fontSize: 11,
@@ -883,7 +773,7 @@ const Invoice = () => {
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
border: `1px solid ${edge(BRAND)}`,
color: BRAND,
fontSize: 11,
@@ -936,7 +826,7 @@ const Invoice = () => {
sx={{
px: 2,
py: 1,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`
background: '#ffffff'
}}
>
<Typography variant="caption" sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}>

View File

@@ -149,37 +149,123 @@ const Login = () => {
};
return (
<Box sx={{ height: '100vh', position: 'relative' }}>
<Box sx={{ minHeight: '100vh', display: 'flex', bgcolor: '#f8fafc' }}>
{loading && <Loader />}
<Stack
{/* ---- Left brand panel (hidden on small screens) ---- */}
<Box
sx={{
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
px: 2
display: { xs: 'none', md: 'flex' },
position: 'relative',
overflow: 'hidden',
flexBasis: '46%',
flexDirection: 'column',
justifyContent: 'space-between',
color: '#fff',
p: 6,
background: 'linear-gradient(150deg, #4D1C61 0%, #662582 52%, #9255AB 100%)'
}}
>
<Card
{/* decorative light glows */}
<Box
sx={{
width: '100%',
maxWidth: 420,
borderRadius: 2,
border: `1px solid ${theme.palette.divider}`,
boxShadow: 'none',
p: { xs: 2, sm: 3 }
position: 'absolute',
top: -120,
right: -80,
width: 360,
height: 360,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0) 70%)'
}}
>
{/* Logo */}
<Stack alignItems="center" mb={2}>
<img src={logo} alt="loginpagelogo" />
</Stack>
/>
<Box
sx={{
position: 'absolute',
bottom: -150,
left: -110,
width: 440,
height: 440,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0) 70%)'
}}
/>
{/* Title */}
<Typography variant="h3" textAlign="start" mb={3}>
Login
<Box sx={{ position: 'relative' }}>
<Box
sx={{
bgcolor: '#fff',
borderRadius: 2,
px: 1.5,
py: 0.75,
display: 'inline-flex',
boxShadow: '0 8px 20px rgba(0,0,0,0.18)'
}}
>
<img src={logo} alt="NearlExpress" style={{ height: 30, display: 'block' }} />
</Box>
</Box>
<Box sx={{ position: 'relative' }}>
<Typography sx={{ fontSize: 34, fontWeight: 700, lineHeight: 1.18, letterSpacing: '-0.02em', mb: 2 }}>
Operate your dispatch,
<br />
end to end.
</Typography>
<Typography sx={{ fontSize: 16, color: 'rgba(255,255,255,0.82)', maxWidth: 430, lineHeight: 1.6 }}>
Orders, AI route optimisation, live rider tracking and billing all in the NearlExpress operator console.
</Typography>
</Box>
<CardContent sx={{ p: 0 }}>
<Stack spacing={1.25} sx={{ position: 'relative' }}>
{['Real-time fleet visibility', 'AI-optimised dispatch routes', 'Tenant, pricing & invoice control'].map((t) => (
<Stack key={t} direction="row" spacing={1.25} alignItems="center">
<Box
sx={{
width: 22,
height: 22,
borderRadius: '50%',
bgcolor: 'rgba(255,255,255,0.18)',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 13,
fontWeight: 700
}}
>
</Box>
<Typography sx={{ fontSize: 14.5, color: 'rgba(255,255,255,0.9)' }}>{t}</Typography>
</Stack>
))}
</Stack>
</Box>
{/* ---- Right form panel ---- */}
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', p: { xs: 2.5, sm: 4 } }}>
<Box sx={{ width: '100%', maxWidth: 420 }}>
<Card
sx={{
width: '100%',
borderRadius: 3,
border: `1px solid ${theme.palette.divider}`,
boxShadow: '0 14px 40px rgba(15, 23, 42, 0.10)',
p: { xs: 2.5, sm: 4 }
}}
>
{/* Logo */}
<Stack alignItems="center" mb={2.5}>
<img src={logo} alt="loginpagelogo" style={{ maxHeight: 48 }} />
</Stack>
{/* Title */}
<Typography variant="h3" textAlign="center" sx={{ fontWeight: 700, mb: 0.5 }}>
Welcome back
</Typography>
<Typography variant="body2" textAlign="center" sx={{ color: '#64748b', mb: 3 }}>
Sign in to the NearlExpress console
</Typography>
<CardContent sx={{ p: 0 }}>
<form
noValidate
onSubmit={(e) => {
@@ -343,53 +429,41 @@ const Login = () => {
</AnimateButton>
</Stack>
</form>
</CardContent>
</Card>
</Stack>
{/* footer */}
<Stack
sx={{
position: 'absolute',
bottom: 10,
width: '100%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'end',
gap: 2,
p: 2
}}
>
<Typography
variant="subtitle1"
color="secondary"
component={Link}
href="https://nearle.in"
target="_blank"
sx={{ display: 'flex' }}
>
&copy; All rights reserved
</Typography>
<Typography
variant="subtitle1"
color="secondary"
component={Link}
href="https://nearle.in/terms"
target="_blank"
sx={{ display: 'flex' }}
>
Terms and Conditions
</Typography>
<Typography
variant="subtitle1"
color="secondary"
component={Link}
href="https://nearle.in/privacy"
target="_blank"
sx={{ display: 'flex' }}
>
Privacy Policy
</Typography>
</Stack>
</CardContent>
</Card>
{/* footer */}
<Stack direction="row" justifyContent="center" alignItems="center" flexWrap="wrap" useFlexGap spacing={2} sx={{ mt: 3 }}>
<Typography
variant="caption"
component={Link}
href="https://nearle.in"
target="_blank"
sx={{ color: '#94a3b8', textDecoration: 'none', fontWeight: 600, '&:hover': { color: '#662582' } }}
>
&copy; All rights reserved
</Typography>
<Typography
variant="caption"
component={Link}
href="https://nearle.in/terms"
target="_blank"
sx={{ color: '#94a3b8', textDecoration: 'none', fontWeight: 600, '&:hover': { color: '#662582' } }}
>
Terms and Conditions
</Typography>
<Typography
variant="caption"
component={Link}
href="https://nearle.in/privacy"
target="_blank"
sx={{ color: '#94a3b8', textDecoration: 'none', fontWeight: 600, '&:hover': { color: '#662582' } }}
>
Privacy Policy
</Typography>
</Stack>
</Box>
</Box>
</Box>
);
};

View File

@@ -696,7 +696,7 @@
.weight-card-btn.active.weight-heavy {
background: rgba(99, 102, 241, 0.04) !important;
border-color: #6366f1 !important;
border-color: #662582 !important;
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.15) !important;
}
@@ -707,7 +707,7 @@
left: 0;
right: 0;
height: 3px;
background: #6366f1;
background: #662582;
}
/* Premium Card Overrides */

View File

@@ -21,13 +21,16 @@ import {
MdHourglassEmpty,
MdCheckCircle,
MdCancel,
MdOutlineReceiptLong,
MdOutlinePendingActions,
MdOutlineCheckCircle,
MdOutlineCancel,
MdMyLocation,
MdGroups,
MdPlace,
MdStraighten,
MdCurrencyRupee,
MdInventory2,
MdReceiptLong,
MdHistoryToggleOff,
MdCalendarMonth
} from 'react-icons/md';
@@ -39,6 +42,8 @@ import MainCard from 'components/MainCard';
import { OpenToast } from 'components/third-party/OpenToast';
import { OrdersTableSkeleton } from './OrdersTableSkeleton';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import AiImage from '../../../assets/images/aiImage.png';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import EnergySavingsLeafIcon from '@mui/icons-material/EnergySavingsLeaf';
@@ -73,7 +78,6 @@ import {
DialogContent,
DialogTitle,
Tooltip,
Skeleton,
DialogActions,
Autocomplete,
TableContainer,
@@ -122,10 +126,10 @@ import Dispatch from '../dispatch/Dispatch';
// ============================================================================
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',
@@ -141,7 +145,6 @@ const dtRing = (c) => dtA(c, '26');
const dtEdge = (c) => dtA(c, '55');
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
const SoftPaper = (props) => (
<Paper
@@ -171,15 +174,15 @@ const DTAccentAvatar = ({ color, selected, size = 24, children }) => (
</Avatar>
);
const pillFieldSx = (color) => ({
const pillFieldSx = () => ({
'& .MuiOutlinedInput-root': {
borderRadius: DT.radiusPill + 'px',
bgcolor: dtTint(color),
borderRadius: '10px',
bgcolor: '#ffffff',
fontWeight: 600,
'& fieldset': { borderColor: dtEdge(color), borderWidth: 1.5 },
'&:hover fieldset': { borderColor: color },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${dtRing(color)}` },
'&.Mui-focused fieldset': { borderColor: color, borderWidth: 2 }
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${dtRing('#662582')}` },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 }
}
});
@@ -929,64 +932,11 @@ const Orders = () => {
</SpeedDial>
)}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${dtTint(BRAND)} 0%, ${dtTint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${dtRing(BRAND)}`
}}
>
<MdLocalShipping size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Orders
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'} · {datestatus}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Orders"
subtitle={`Live · ${locaName || 'All Zones'} · ${datestatus}`}
live
action={
<LocationAutocomplete
ref={locationRef}
locaName={locaName}
@@ -1000,95 +950,28 @@ const Orders = () => {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{[
{ key: 'created', label: 'Created Orders', color: '#0ea5e9', icon: MdLocalShipping, value: percentageData?.created, percentage: percentageData?.percentage1 },
{ key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdHourglassEmpty, value: percentageData?.uncoveredOrders, percentage: percentageData?.percentage2 },
{ key: 'delivered', label: 'Delivered Orders', color: '#10b981', icon: MdCheckCircle, value: percentageData?.coveredOrders, percentage: percentageData?.percentage3 },
{ key: 'cancelled', label: 'Cancelled Orders', color: '#ef4444', icon: MdCancel, value: percentageData?.cancelled, percentage: percentageData?.percentage4 }
{ key: 'created', label: 'Created Orders', color: '#662582', icon: MdOutlineReceiptLong, value: percentageData?.created, percentage: percentageData?.percentage1 },
{ key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdOutlinePendingActions, value: percentageData?.uncoveredOrders, percentage: percentageData?.percentage2 },
{ key: 'delivered', label: 'Delivered Orders', color: '#10b981', icon: MdOutlineCheckCircle, value: percentageData?.coveredOrders, percentage: percentageData?.percentage3 },
{ key: 'cancelled', label: 'Cancelled Orders', color: '#ef4444', icon: MdOutlineCancel, value: percentageData?.cancelled, percentage: percentageData?.percentage4 }
].map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: dtEdge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${dtSoft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
{fetchpercentageIsLoading ? <Skeleton sx={{ width: 40 }} animation="wave" /> : (item.value ?? 0)}
</Typography>
{item.percentage != null && (
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 700 }}>
{item.percentage}%
</Typography>
)}
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: dtSoft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${dtEdge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={fetchpercentageIsLoading}
caption={item.percentage != null ? `${item.percentage}% of total` : undefined}
/>
</Grid>
);
})}
@@ -1147,7 +1030,7 @@ const Orders = () => {
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5 }}>
<DTAccentAvatar color="#0ea5e9" size={22} selected>
<DTAccentAvatar color="#94a3b8" size={22}>
<MdGroups size={13} />
</DTAccentAvatar>
</Stack>
@@ -1198,7 +1081,7 @@ const Orders = () => {
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5 }}>
<DTAccentAvatar color="#10b981" size={22} selected>
<DTAccentAvatar color="#94a3b8" size={22}>
<MdPlace size={13} />
</DTAccentAvatar>
</Stack>
@@ -1218,20 +1101,21 @@ const Orders = () => {
display: 'inline-flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.75,
borderRadius: 999,
px: 1.5,
py: 0.875,
borderRadius: '10px',
cursor: 'pointer',
bgcolor: dtTint('#f59e0b'),
border: `1.5px solid ${dtEdge('#f59e0b')}`,
color: '#f59e0b',
fontWeight: 800,
bgcolor: '#ffffff',
border: '1px solid #e2e8f0',
color: '#475569',
fontWeight: 600,
fontSize: 12,
transition: 'all 0.18s',
'&:hover': { borderColor: '#f59e0b', boxShadow: `0 0 0 3px ${dtRing('#f59e0b')}` }
transition: 'border-color 0.15s, box-shadow 0.15s',
'&:hover': { borderColor: '#cbd5e1' },
'& svg': { color: '#94a3b8' }
}}
>
<MdCalendarMonth size={14} />
<MdCalendarMonth size={15} />
{dayjs(startdate).format('DD/MM/YY')} {dayjs(enddate).format('DD/MM/YY')}
</Box>
</Tooltip>
@@ -1242,15 +1126,14 @@ const Orders = () => {
gap: 0.5,
px: 1,
py: 0.5,
borderRadius: 999,
bgcolor: dtTint(BRAND),
border: `1px solid ${dtEdge(BRAND)}`,
color: BRAND,
borderRadius: '8px',
bgcolor: '#f1f5f9',
color: '#64748b',
fontSize: 11,
fontWeight: 800
fontWeight: 600
}}
>
<MdReceiptLong size={12} /> {datestatus}
{datestatus}
</Box>
</Stack>
</Grid>
@@ -1320,49 +1203,49 @@ const Orders = () => {
py: 0.5,
flexShrink: 0,
cursor: 'pointer',
borderRadius: 999,
border: `1.5px solid ${active ? t.color : dtEdge(t.color)}`,
bgcolor: active ? t.color : dtTint(t.color),
color: active ? '#fff' : t.color,
fontWeight: 700,
boxShadow: active ? `0 6px 18px ${dtRing(t.color)}` : 'none',
transition: 'all 0.18s',
borderRadius: DT.radiusField + 'px',
border: `1px solid ${active ? t.color : DT.borderSubtle}`,
bgcolor: active ? t.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: t.color,
boxShadow: active ? `0 6px 18px ${dtRing(t.color)}` : `0 0 0 3px ${dtRing(t.color)}`
borderColor: active ? t.color : DT.borderHover,
bgcolor: active ? t.color : DT.surfaceAlt
}
}}
>
<Avatar
sx={{
width: { xs: 22, md: 26 },
height: { xs: 22, md: 26 },
width: { xs: 20, md: 22 },
height: { xs: 20, md: 22 },
bgcolor: active ? 'rgba(255,255,255,0.22)' : dtSoft(t.color),
color: active ? '#fff' : t.color
}}
>
<Icon size={13} />
<Icon size={12} />
</Avatar>
<Typography
variant="caption"
sx={{ fontWeight: 800, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}
sx={{ fontWeight: 600, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}
>
{t.label}
</Typography>
<Box
sx={{
minWidth: { xs: 22, md: 26 },
height: { xs: 18, md: 22 },
minWidth: { xs: 20, md: 24 },
height: { xs: 18, md: 20 },
px: 0.625,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 999,
fontSize: { xs: 10, md: 11 },
fontWeight: 800,
bgcolor: active ? 'rgba(255,255,255,0.22)' : '#fff',
color: active ? '#fff' : t.color,
border: active ? 'none' : `1px solid ${dtEdge(t.color)}`
fontWeight: 700,
bgcolor: active ? 'rgba(255,255,255,0.22)' : DT.surfaceAlt,
color: active ? '#fff' : DT.textSecondary,
border: 'none'
}}
>
{count}
@@ -1384,8 +1267,8 @@ const Orders = () => {
borderRadius: 999,
bgcolor: dtTint(BRAND),
'& fieldset': { borderColor: dtEdge(BRAND), borderWidth: 1.5 },
'&:hover fieldset': { borderColor: BRAND },
'&.Mui-focused fieldset': { borderColor: BRAND, borderWidth: 2 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${dtRing(BRAND)}` }
}}
/>

View File

@@ -104,10 +104,10 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
// Numbered step icon as a data URL — drawn fresh per render so we can pass
// the step number into the SVG without juggling external assets. Color is
// a fixed indigo to match the planned-route polyline below.
// brand purple to match the planned-route polyline below.
const stepIcon = (n, isFocused) => {
const size = isFocused ? 38 : 32;
const color = isFocused ? '#4338ca' : '#6366f1';
const color = isFocused ? '#4D1C61' : '#662582';
const svg = encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="${size}" height="${size}">` +
`<circle cx="16" cy="16" r="14" fill="${color}" stroke="white" stroke-width="3"/>` +
@@ -126,7 +126,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
px: 2,
py: 1.25,
borderBottom: '1px solid rgba(15, 23, 42, 0.08)',
background: 'linear-gradient(135deg, #6366f1 0%, #3b82f6 100%)',
background: 'linear-gradient(135deg, #662582 0%, #9255AB 100%)',
color: '#fff',
flexShrink: 0
}}
@@ -207,12 +207,12 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
{/* Translucent backdrop so the route stays legible on busy tiles. */}
<Polyline
path={routePath}
options={{ strokeColor: '#6366f1', strokeOpacity: 0.25, strokeWeight: 8 }}
options={{ strokeColor: '#662582', strokeOpacity: 0.25, strokeWeight: 8 }}
/>
{/* Road-following planned route from the Directions API. */}
<Polyline
path={routePath}
options={{ strokeColor: '#6366f1', strokeOpacity: 0.95, strokeWeight: 4 }}
options={{ strokeColor: '#662582', strokeOpacity: 0.95, strokeWeight: 4 }}
/>
</>
) : (
@@ -221,7 +221,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
<Polyline
path={dropPath}
options={{
strokeColor: '#6366f1',
strokeColor: '#662582',
strokeOpacity: 0,
strokeWeight: 0,
icons: [
@@ -229,7 +229,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
icon: {
path: 'M 0,-1 0,1',
strokeOpacity: 0.6,
strokeColor: '#6366f1',
strokeColor: '#662582',
scale: 3
},
offset: '0',

View File

@@ -56,7 +56,11 @@ import {
MdCurrencyRupee,
MdMap,
MdNoteAlt,
MdClose
MdClose,
MdOutlineLocalShipping,
MdOutlineCheckCircle,
MdOutlinePendingActions,
MdOutlineCancel
} from 'react-icons/md';
import { FaCircleCheck } from 'react-icons/fa6';
@@ -70,6 +74,8 @@ import DateFilterDialog from 'components/DateFilterDialog';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import LoaderWithImage from 'components/nearle_components/LoaderWithImage';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import dayjs from 'dayjs';
import { OpenToast } from 'components/third-party/OpenToast';
import TableLoader from 'components/nearle_components/TableLoader';
@@ -90,10 +96,10 @@ const opentoast = (message, variant, time) => {
// ============================================================================
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',
@@ -141,13 +147,13 @@ const AccentAvatar = ({ color, selected, size = 24, children }) => (
const pillFieldSx = (color) => ({
'& .MuiOutlinedInput-root': {
borderRadius: DT.radiusPill + 'px',
bgcolor: tint(color),
borderRadius: '10px',
bgcolor: '#ffffff',
fontWeight: 600,
'& 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 }
}
});
@@ -178,7 +184,7 @@ const MetricPill = ({ color, icon, label, tooltip }) => (
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(color),
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 11,
@@ -763,10 +769,10 @@ export default function OrdersDetails() {
}, [errormessage]);
const KPI_META = [
{ key: 'total', label: 'Total Orders', color: BRAND, icon: MdLocalShipping, value: total },
{ key: 'delivered', label: 'Delivered', color: '#10b981', icon: MdCheckCircle, value: deliveredLenght },
{ key: 'pending', label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty, value: pendingLenght },
{ key: 'cancelled', label: 'Cancelled', color: '#ef4444', icon: MdCancel, value: cancelLenght }
{ key: 'total', label: 'Total Orders', color: BRAND, icon: MdOutlineLocalShipping, value: total },
{ key: 'delivered', label: 'Delivered', color: '#10b981', icon: MdOutlineCheckCircle, value: deliveredLenght },
{ key: 'pending', label: 'Pending', color: '#f59e0b', icon: MdOutlinePendingActions, value: pendingLenght },
{ key: 'cancelled', label: 'Cancelled', color: '#ef4444', icon: MdOutlineCancel, value: cancelLenght }
];
return (
@@ -792,64 +798,11 @@ export default function OrdersDetails() {
)}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdAssignment size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Orders Details
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'} · {datestatus}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Orders Details"
subtitle={`Live · ${locaName || 'All Zones'} · ${datestatus}`}
live
action={
<LocationAutocomplete
ref={locationRef}
locaName={locaName}
@@ -863,83 +816,22 @@ export default function OrdersDetails() {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
{item.value}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={isLoading}
/>
</Grid>
);
})}
@@ -1145,7 +1037,6 @@ export default function OrdersDetails() {
fontSize: 12,
textTransform: 'none',
background: `linear-gradient(135deg, ${BRAND} 0%, ${BRAND_LIGHT} 100%)`,
boxShadow: `0 6px 18px ${ring(BRAND)}`,
'&:hover': {
background: `linear-gradient(135deg, ${BRAND} 0%, ${BRAND_LIGHT} 100%)`,
boxShadow: `0 8px 22px ${ring(BRAND)}`
@@ -1213,15 +1104,15 @@ export default function OrdersDetails() {
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',
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 : '#cbd5e1',
bgcolor: active ? meta.color : DT.surfaceAlt
}
}}
>
@@ -1238,7 +1129,7 @@ export default function OrdersDetails() {
<Typography
variant="caption"
sx={{
fontWeight: 800,
fontWeight: 600,
fontSize: { xs: 11.5, md: 13 },
lineHeight: 1
}}
@@ -1255,10 +1146,10 @@ export default function OrdersDetails() {
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)' : DT.surfaceAlt,
color: active ? '#fff' : DT.textSecondary,
border: 'none'
}}
>
{count}
@@ -1278,10 +1169,10 @@ export default function OrdersDetails() {
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 },
bgcolor: '#ffffff',
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>

View File

@@ -40,9 +40,12 @@ import {
MdLocationOn,
MdMyLocation,
MdPerson,
MdReceiptLong,
MdStraighten,
MdStore
MdStore,
MdOutlineLocalShipping,
MdOutlinePendingActions,
MdOutlineCheckCircle,
MdOutlineCurrencyRupee
} from 'react-icons/md';
import dayjs from 'dayjs';
@@ -53,6 +56,8 @@ import { getreportlocationsummary, getreportsummary, gettenantlocations, getTena
import Loader from 'components/Loader';
import DateFilterDialog from 'components/DateFilterDialog';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
@@ -64,10 +69,10 @@ import { OpenToast } from 'components/third-party/OpenToast';
// ============================================================================
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',
@@ -124,7 +129,7 @@ const MetricPill = ({ color, icon, label, tooltip, minWidth = 80 }) => (
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(color),
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 11,
@@ -161,7 +166,7 @@ const CountCell = ({ value, color = '#ef4444', icon }) => {
px: 0.875,
py: 0.25,
borderRadius: 999,
bgcolor: tint(color),
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 12,
@@ -179,13 +184,13 @@ const CountCell = ({ value, color = '#ef4444', icon }) => {
// Pill-style filter inputs for the Tenant / Location autocompletes.
const pillFieldSx = (color) => ({
'& .MuiOutlinedInput-root': {
borderRadius: DT.radiusPill + 'px',
bgcolor: tint(color),
borderRadius: '10px',
bgcolor: '#ffffff',
fontWeight: 600,
'& 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 }
}
});
@@ -352,28 +357,28 @@ export default function OrdersReport() {
key: 'orders',
label: 'Total Orders',
color: BRAND,
icon: MdLocalShipping,
icon: MdOutlineLocalShipping,
value: stats.totalOrders
},
{
key: 'pending',
label: 'Orders Pending',
color: '#f59e0b',
icon: MdHourglassEmpty,
icon: MdOutlinePendingActions,
value: stats.orderPend
},
{
key: 'completed',
label: 'Orders Completed',
color: '#10b981',
icon: MdCheckCircle,
icon: MdOutlineCheckCircle,
value: stats.orderComplete
},
{
key: 'amount',
label: 'Total Amount',
color: '#0ea5e9',
icon: MdCurrencyRupee,
icon: MdOutlineCurrencyRupee,
value: formatNumberToRupees(stats.amount)
}
];
@@ -385,64 +390,11 @@ export default function OrdersReport() {
{(loading || isLoadingReports) && <Loader />}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdReceiptLong size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Orders Summary
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'} · {datestatus}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Orders Summary"
subtitle={`Live · ${locaName || 'All Zones'} · ${datestatus}`}
live
action={
<LocationAutocomplete
ref={locationRef}
locaName={locaName}
@@ -456,86 +408,22 @@ export default function OrdersReport() {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.1rem', sm: '1.4rem', md: '1.6rem' },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.value}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={isLoadingReports}
/>
</Grid>
);
})}
@@ -697,10 +585,10 @@ export default function OrdersReport() {
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 },
bgcolor: '#ffffff',
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>
@@ -1240,7 +1128,7 @@ export default function OrdersReport() {
sx={{
px: 2,
py: 1.25,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
background: '#ffffff',
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
@@ -1250,7 +1138,7 @@ export default function OrdersReport() {
<Typography
variant="caption"
sx={{
fontWeight: 800,
fontWeight: 700,
color: DT.textPrimary,
letterSpacing: 0.6,
textTransform: 'uppercase'
@@ -1428,7 +1316,7 @@ export default function OrdersReport() {
sx={{
px: 2,
py: 1.5,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`
background: '#ffffff'
}}
>
<Stack direction="row" alignItems="center" spacing={1} flexWrap="wrap">

View File

@@ -40,7 +40,10 @@ import {
MdExpandMore,
MdExpandLess,
MdGroups,
MdPerson
MdPerson,
MdOutlineLocalShipping,
MdOutlineCheckCircle,
MdOutlineCurrencyRupee
} from 'react-icons/md';
import dayjs from 'dayjs';
@@ -52,6 +55,8 @@ import Loader from 'components/Loader';
import DateFilterDialog from 'components/DateFilterDialog';
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 { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import RidersRoutes from './RidersRoutes';
import { OpenToast } from 'components/third-party/OpenToast';
@@ -63,10 +68,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',
@@ -123,7 +128,7 @@ const MetricPill = ({ color, icon, label, tooltip, minWidth = 80 }) => (
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(color),
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 11,
@@ -159,7 +164,7 @@ const CountCell = ({ value, color = '#ef4444', icon }) => {
px: 0.875,
py: 0.25,
borderRadius: 999,
bgcolor: tint(color),
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 12,
@@ -328,9 +333,9 @@ export default function RidersSummary() {
const KPI_META = [
{ key: 'riders', label: 'Active Riders', color: BRAND, icon: MdDirectionsBike, value: stats.riders },
{ key: 'orders', label: 'Total Orders', color: '#0ea5e9', icon: MdLocalShipping, value: stats.orders },
{ key: 'delivered', label: 'Delivered', color: '#10b981', icon: MdCheckCircle, value: stats.delivered },
{ key: 'amount', label: 'Total Amount', color: '#f59e0b', icon: MdCurrencyRupee, value: formatNumberToRupees(total) }
{ key: 'orders', label: 'Total Orders', color: '#0ea5e9', icon: MdOutlineLocalShipping, value: stats.orders },
{ key: 'delivered', label: 'Delivered', color: '#10b981', icon: MdOutlineCheckCircle, value: stats.delivered },
{ key: 'amount', label: 'Total Amount', color: '#f59e0b', icon: MdOutlineCurrencyRupee, value: formatNumberToRupees(total) }
];
return (
@@ -338,64 +343,11 @@ export default function RidersSummary() {
{(isLoadingReports || loading) && <Loader />}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdDirectionsBike size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Riders Summary
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'} · {datestatus}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Riders Summary"
subtitle={`Live · ${locaName || 'All Zones'} · ${datestatus}`}
live
action={
<LocationAutocomplete
locaName={locaName}
setAppId={setAppId}
@@ -407,86 +359,22 @@ export default function RidersSummary() {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.1rem', sm: '1.4rem', md: '1.6rem' },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.value}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={isLoadingReports}
/>
</Grid>
);
})}
@@ -565,10 +453,10 @@ export default function RidersSummary() {
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 },
bgcolor: '#ffffff',
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>
@@ -742,7 +630,7 @@ export default function RidersSummary() {
sx={{
px: 1.5,
py: 1,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
background: '#ffffff',
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
@@ -1049,7 +937,7 @@ export default function RidersSummary() {
sx={{
px: 2,
py: 1.25,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
background: '#ffffff',
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
@@ -1203,7 +1091,7 @@ export default function RidersSummary() {
sx={{
px: 2,
py: 1.5,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`
background: '#ffffff'
}}
>
<Typography

View File

@@ -28,12 +28,14 @@ var utc = require('dayjs/plugin/utc');
import dayjs from 'dayjs';
dayjs.extend(utc);
import {
MdDirectionsBike,
MdMyLocation,
MdPersonPin,
MdCheckCircle,
MdCancel,
MdGroups,
MdOutlineGroups,
MdOutlineCheckCircle,
MdOutlineCancel,
MdEdit,
MdKeyboardArrowDown,
MdKeyboardArrowUp,
@@ -47,6 +49,8 @@ import {
MdTwoWheeler
} from 'react-icons/md';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import CircularLoader from 'components/CircularLoader';
@@ -65,10 +69,10 @@ import axios from 'axios';
// ============================================================================
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',
@@ -84,7 +88,6 @@ const ring = (c) => a(c, '26');
const edge = (c) => a(c, '55');
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
const SoftPaper = (props) => (
<Paper
@@ -133,9 +136,9 @@ const TAB_META = [
];
const KPI_META = (summary) => [
{ key: 'total', label: 'Total Riders', color: BRAND, icon: MdGroups, value: summary?.total ?? 0 },
{ key: 'active', label: 'Active Riders', color: '#10b981', icon: MdCheckCircle, value: summary?.active ?? 0 },
{ key: 'inactive', label: 'Inactive Riders', color: '#ef4444', icon: MdCancel, value: summary?.inactive ?? 0 }
{ key: 'total', label: 'Total Riders', color: BRAND, icon: MdOutlineGroups, value: summary?.total ?? 0 },
{ key: 'active', label: 'Active Riders', color: '#10b981', icon: MdOutlineCheckCircle, value: summary?.active ?? 0 },
{ key: 'inactive', label: 'Inactive Riders', color: '#ef4444', icon: MdOutlineCancel, value: summary?.inactive ?? 0 }
];
const Riders = () => {
@@ -205,7 +208,7 @@ const Riders = () => {
getNextPageParam: (lastPage, pages) => (lastPage.details?.length ? pages.length + 1 : undefined)
});
const rows = allRidersData?.pages.flatMap((page) => page.details) || [];
const rows = (allRidersData?.pages.flatMap((page) => page.details || []) || []).filter(Boolean);
useEffect(() => {
if (!hasNextPage) return;
@@ -247,10 +250,10 @@ const Riders = () => {
// isn't in the palette (e.g. brand-new status string from the backend).
const getRowStatusMeta = (row) => {
if (tabvalue == 0) {
const key = (row.status || '').toLowerCase() === 'active' ? 'active' : 'inactive';
return STATUS_META[key];
const key = (row?.status || '').toLowerCase() === 'active' ? 'active' : 'inactive';
return STATUS_META[key] || STATUS_META.unknown;
}
const state = ridersStatus?.find((s) => s.userid === row.userid);
const state = ridersStatus?.find((s) => s.userid === row?.userid);
const key = (state?.status || 'unknown').toLowerCase();
return STATUS_META[key] || STATUS_META.unknown;
};
@@ -265,64 +268,11 @@ const Riders = () => {
</Backdrop>
{/* ============================================= || Header | ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdDirectionsBike size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Riders
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locaName || 'All Zones'}
</Typography>
</Stack>
</Stack>
</Stack>
<PageHeader
title="Riders"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<LocationAutocomplete
locaName={locaName}
setAppId={setAppId}
@@ -334,89 +284,22 @@ const Riders = () => {
paperComponent={SoftPaper}
sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }}
/>
</Stack>
</Paper>
}
/>
{/* ============================================= || KPI Cards | ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }} sx={{ mt: '1px' }}>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META(allRidersSummary).map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={12} sm={4}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
{riderSummarysLoading ? (
<Skeleton sx={{ width: 70, height: { xs: 28, md: 36 } }} animation="wave" />
) : (
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.5rem', sm: '1.75rem', md: '2rem' }
}}
>
{item.value}
</Typography>
)}
</Stack>
<Avatar
sx={{
width: { xs: 36, sm: 42, md: 48 },
height: { xs: 36, sm: 42, md: 48 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={20} />
</Avatar>
</Stack>
</Paper>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={riderSummarysLoading}
/>
</Grid>
);
})}
@@ -474,16 +357,16 @@ const Riders = () => {
py: 0.5,
flexShrink: 0,
cursor: 'pointer',
borderRadius: 999,
border: `1.5px solid ${active ? t.color : edge(t.color)}`,
bgcolor: active ? t.color : tint(t.color),
color: active ? '#fff' : t.color,
fontWeight: 700,
boxShadow: active ? `0 6px 18px ${ring(t.color)}` : 'none',
transition: 'all 0.18s',
borderRadius: '10px',
border: `1px solid ${active ? t.color : DT.borderSubtle}`,
bgcolor: active ? t.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: t.color,
boxShadow: active ? `0 6px 18px ${ring(t.color)}` : `0 0 0 3px ${ring(t.color)}`
borderColor: active ? t.color : '#cbd5e1',
bgcolor: active ? t.color : '#f8fafc'
}
}}
>
@@ -497,7 +380,7 @@ const Riders = () => {
>
<Icon size={13} />
</Avatar>
<Typography variant="caption" sx={{ fontWeight: 800, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}>
<Typography variant="caption" sx={{ fontWeight: 600, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}>
{t.label}
</Typography>
<Box
@@ -510,10 +393,10 @@ const Riders = () => {
justifyContent: 'center',
borderRadius: 999,
fontSize: { xs: 10, md: 11 },
fontWeight: 800,
bgcolor: active ? 'rgba(255,255,255,0.22)' : '#fff',
color: active ? '#fff' : t.color,
border: active ? 'none' : `1px solid ${edge(t.color)}`
fontWeight: 700,
bgcolor: active ? 'rgba(255,255,255,0.22)' : '#f8fafc',
color: active ? '#fff' : '#64748b',
border: 'none'
}}
>
{riderSummarysLoading ? <Skeleton variant="text" width={14} height={10} /> : count}
@@ -533,10 +416,10 @@ const Riders = () => {
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 },
bgcolor: '#ffffff',
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
'&:hover fieldset': { borderColor: '#cbd5e1' },
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>
@@ -625,7 +508,7 @@ const Riders = () => {
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
border: `1px solid ${edge(BRAND)}`,
color: BRAND,
fontWeight: 800,
@@ -794,7 +677,7 @@ const Riders = () => {
mt: 1.5,
p: 1.25,
borderRadius: 2,
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
border: `1px solid ${edge(BRAND)}`
}}
>
@@ -961,7 +844,7 @@ const Riders = () => {
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
border: `1px solid ${edge(BRAND)}`,
color: BRAND,
fontWeight: 800,
@@ -1162,7 +1045,7 @@ const Riders = () => {
<Box
sx={{
p: { xs: 1.5, md: 2 },
bgcolor: tint(BRAND),
bgcolor: '#ffffff',
borderTop: `1px solid ${edge(BRAND)}`
}}
>

View File

@@ -1,21 +1,113 @@
import { Grid, TextField, Typography, useMediaQuery } from '@mui/material';
import { Avatar, Box, Chip, Grid, Paper, Stack, Typography, useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { useQuery } from '@tanstack/react-query';
import {
MdBadge,
MdPerson,
MdLocationOn,
MdMail,
MdPhone,
MdPlace,
MdMyLocation,
MdLocationCity,
MdMap,
MdMarkunreadMailbox,
MdVerifiedUser
} from 'react-icons/md';
import CircularLoader from 'components/CircularLoader';
import Loader from 'components/Loader';
import MainCard from 'components/MainCard';
import { getusers } from 'pages/api/api';
// ---- shared design tokens (mirrors the DT block used across the console) ----
const DT = {
radiusCard: 16,
radiusInner: 12,
shadowSoft: '0 14px 40px rgba(15, 23, 42, 0.10)',
textPrimary: '#0f172a',
textSecondary: '#64748b',
textMuted: '#94a3b8',
borderSubtle: '#e2e8f0',
divider: '#f1f5f9',
surface: '#ffffff',
surfaceAlt: '#f8fafc'
};
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
const tint = (c) => `${c}08`;
const soft = (c) => `${c}18`;
const initialsOf = (name) =>
(name || '')
.trim()
.split(/\s+/)
.slice(0, 2)
.map((w) => w[0])
.join('')
.toUpperCase() || '';
// A single read-only field rendered as an icon + uppercase label + value.
const InfoField = ({ icon: Icon, label, value, accent = BRAND }) => (
<Stack direction="row" spacing={1.5} alignItems="flex-start" sx={{ minWidth: 0 }}>
<Avatar sx={{ width: 36, height: 36, bgcolor: soft(accent), color: accent, flexShrink: 0 }}>
<Icon size={18} />
</Avatar>
<Box sx={{ minWidth: 0 }}>
<Typography
variant="overline"
sx={{ display: 'block', color: DT.textMuted, lineHeight: 1.6, letterSpacing: '0.06em' }}
>
{label}
</Typography>
<Typography
variant="body1"
sx={{ fontWeight: 600, color: DT.textPrimary, wordBreak: 'break-word' }}
title={value || ''}
>
{value || '—'}
</Typography>
</Box>
</Stack>
);
// A titled grouping card.
const SectionCard = ({ title, icon: Icon, children }) => (
<Paper
elevation={0}
sx={{
borderRadius: DT.radiusCard + 'px',
border: `1px solid ${DT.borderSubtle}`,
boxShadow: DT.shadowSoft,
overflow: 'hidden',
height: '100%'
}}
>
<Stack
direction="row"
alignItems="center"
spacing={1.25}
sx={{ px: 2.5, py: 1.75, borderBottom: `1px solid ${DT.divider}`, bgcolor: DT.surfaceAlt }}
>
<Avatar sx={{ width: 28, height: 28, bgcolor: soft(BRAND), color: BRAND }}>
<Icon size={16} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
{title}
</Typography>
</Stack>
<Box sx={{ p: 2.5 }}>{children}</Box>
</Paper>
);
const ViewProfile = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const {
data: userData,
isLoading
} = useQuery({
const { data: userData, isLoading } = useQuery({
queryKey: ['getuser'],
queryFn: getusers
});
const fullname = userData?.fullname || userData?.firstname || 'User Profile';
return (
<>
{isLoading && (
@@ -25,151 +117,117 @@ const ViewProfile = () => {
</>
)}
<MainCard
contentSX={{ p: isMobile ? 2 : 3 }}
title={
<Typography variant="h3" sx={{ m: { xs: 1.5, md: 3 } }}>
User Profile
</Typography>
}
>
<Grid container spacing={{ xs: 2.5, md: 4 }}>
{/* ==============================|| userid ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'User ID'}
value={userData?.userid || ''}
InputProps={{
readOnly: true
<Box sx={{ p: { xs: 1.5, md: 0 } }}>
{/* ---- Gradient brand header ---- */}
<Paper
elevation={0}
sx={{
borderRadius: DT.radiusCard + 'px',
border: `1px solid ${DT.borderSubtle}`,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowSoft,
p: { xs: 2.5, md: 3.5 },
mb: { xs: 2.5, md: 3 }
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
spacing={{ xs: 2, sm: 3 }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
>
<Avatar
sx={{
width: { xs: 64, md: 76 },
height: { xs: 64, md: 76 },
bgcolor: BRAND,
color: '#fff',
fontSize: { xs: 24, md: 28 },
fontWeight: 700,
boxShadow: '0 10px 24px rgba(102, 37, 130, 0.35)'
}}
variant="standard"
/>
</Grid>
{/* ==============================|| Name ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'User Name'}
value={userData?.fullname || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| Location ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'App Location '}
value={userData?.applocation || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
>
{initialsOf(fullname)}
</Avatar>
<Box sx={{ minWidth: 0 }}>
<Typography variant="h3" sx={{ color: DT.textPrimary, fontWeight: 700, mb: 0.5 }}>
{fullname}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap alignItems="center">
{userData?.authname && (
<Chip
size="small"
icon={<MdVerifiedUser size={14} style={{ color: BRAND }} />}
label={userData.authname}
sx={{
bgcolor: soft(BRAND),
color: BRAND,
fontWeight: 700,
border: `1px solid ${BRAND}40`,
'& .MuiChip-icon': { ml: '6px' }
}}
/>
)}
{userData?.userid != null && userData?.userid !== '' && (
<Typography variant="body2" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
ID&nbsp;#{userData.userid}
</Typography>
)}
{userData?.applocation && (
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ color: DT.textSecondary }}>
<MdLocationOn size={15} />
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{userData.applocation}
</Typography>
</Stack>
)}
</Stack>
</Box>
</Stack>
</Paper>
{/* ---- Grouped detail cards ---- */}
<Grid container spacing={{ xs: 2.5, md: 3 }}>
<Grid item xs={12} md={6}>
<SectionCard title="Account" icon={MdBadge}>
<Stack spacing={2.5}>
<InfoField icon={MdPerson} label="User Name" value={userData?.fullname} />
<InfoField icon={MdBadge} label="User ID" value={userData?.userid} />
<InfoField icon={MdVerifiedUser} label="Auth Name" value={userData?.authname} />
<InfoField icon={MdLocationOn} label="App Location" value={userData?.applocation} />
</Stack>
</SectionCard>
</Grid>
{/* ==============================|| Authname ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'Auth Name'}
value={userData?.authname || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
<Grid item xs={12} md={6}>
<SectionCard title="Contact" icon={MdMail}>
<Stack spacing={2.5}>
<InfoField icon={MdPhone} label="Contact No" value={userData?.contactno} accent="#0ea5e9" />
<InfoField icon={MdMail} label="E-Mail" value={userData?.email} accent="#0ea5e9" />
<InfoField icon={MdPlace} label="Address" value={userData?.address} accent="#0ea5e9" />
</Stack>
</SectionCard>
</Grid>
{/* ==============================|| contactno ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'Contact No'}
value={userData?.contactno || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| email ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'E-Mail'}
value={userData?.email || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| address ||============================== */}
<Grid item xs={12}>
<TextField
fullWidth
label={'Address'}
value={userData?.address || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| Location ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'Location'}
value={userData?.suburb || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| city ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'City'}
value={userData?.city || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| state ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'State'}
value={userData?.state || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
</Grid>
{/* ==============================|| Postcode ||============================== */}
<Grid item xs={12} sm={6} md={4}>
<TextField
fullWidth
label={'Postcode'}
value={userData?.postcode || ''}
InputProps={{
readOnly: true
}}
variant="standard"
/>
<SectionCard title="Location" icon={MdMap}>
<Grid container spacing={isMobile ? 2.5 : 3}>
<Grid item xs={12} sm={6} md={3}>
<InfoField icon={MdMyLocation} label="Suburb" value={userData?.suburb} accent="#14b8a6" />
</Grid>
<Grid item xs={12} sm={6} md={3}>
<InfoField icon={MdLocationCity} label="City" value={userData?.city} accent="#14b8a6" />
</Grid>
<Grid item xs={12} sm={6} md={3}>
<InfoField icon={MdMap} label="State" value={userData?.state} accent="#14b8a6" />
</Grid>
<Grid item xs={12} sm={6} md={3}>
<InfoField icon={MdMarkunreadMailbox} label="Postcode" value={userData?.postcode} accent="#14b8a6" />
</Grid>
</Grid>
</SectionCard>
</Grid>
</Grid>
</MainCard>
</Box>
</>
);
};

View File

@@ -12,6 +12,37 @@ import Typography from './typography';
import CustomShadows from './shadows';
import componentsOverride from './overrides';
// Refined enterprise elevation ramp — soft, layered, low-contrast shadows
// (Stripe/Linear feel) instead of MUI's default hard grey drop-shadows.
// MUI requires exactly 25 entries (index 0 = 'none').
const softShadows = [
'none',
'0 1px 2px rgba(15, 23, 42, 0.04)',
'0 1px 3px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(15, 23, 42, 0.04)',
'0 2px 6px rgba(15, 23, 42, 0.06), 0 1px 2px rgba(15, 23, 42, 0.04)',
'0 4px 10px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.05)',
'0 6px 14px rgba(15, 23, 42, 0.07), 0 2px 4px rgba(15, 23, 42, 0.05)',
'0 8px 18px rgba(15, 23, 42, 0.08), 0 2px 5px rgba(15, 23, 42, 0.05)',
'0 10px 22px rgba(15, 23, 42, 0.08), 0 3px 6px rgba(15, 23, 42, 0.05)',
'0 12px 26px rgba(15, 23, 42, 0.09), 0 4px 8px rgba(15, 23, 42, 0.06)',
'0 14px 30px rgba(15, 23, 42, 0.10), 0 4px 9px rgba(15, 23, 42, 0.06)',
'0 16px 34px rgba(15, 23, 42, 0.10), 0 5px 10px rgba(15, 23, 42, 0.06)',
'0 18px 38px rgba(15, 23, 42, 0.11), 0 6px 11px rgba(15, 23, 42, 0.07)',
'0 20px 42px rgba(15, 23, 42, 0.11), 0 6px 12px rgba(15, 23, 42, 0.07)',
'0 22px 46px rgba(15, 23, 42, 0.12), 0 7px 13px rgba(15, 23, 42, 0.07)',
'0 24px 50px rgba(15, 23, 42, 0.12), 0 8px 14px rgba(15, 23, 42, 0.08)',
'0 26px 54px rgba(15, 23, 42, 0.13), 0 8px 15px rgba(15, 23, 42, 0.08)',
'0 28px 58px rgba(15, 23, 42, 0.13), 0 9px 16px rgba(15, 23, 42, 0.08)',
'0 30px 62px rgba(15, 23, 42, 0.14), 0 10px 17px rgba(15, 23, 42, 0.09)',
'0 32px 66px rgba(15, 23, 42, 0.14), 0 10px 18px rgba(15, 23, 42, 0.09)',
'0 34px 70px rgba(15, 23, 42, 0.15), 0 11px 19px rgba(15, 23, 42, 0.09)',
'0 36px 74px rgba(15, 23, 42, 0.15), 0 12px 20px rgba(15, 23, 42, 0.10)',
'0 38px 78px rgba(15, 23, 42, 0.16), 0 12px 21px rgba(15, 23, 42, 0.10)',
'0 40px 82px rgba(15, 23, 42, 0.16), 0 13px 22px rgba(15, 23, 42, 0.10)',
'0 42px 86px rgba(15, 23, 42, 0.17), 0 14px 23px rgba(15, 23, 42, 0.11)',
'0 44px 90px rgba(15, 23, 42, 0.18), 0 14px 24px rgba(15, 23, 42, 0.11)'
];
// ==============================|| DEFAULT THEME - MAIN ||============================== //
export default function ThemeCustomization({ children }) {
@@ -52,6 +83,9 @@ export default function ThemeCustomization({ children }) {
}
},
direction: themeDirection,
shape: {
borderRadius: 8
},
mixins: {
toolbar: {
minHeight: 60,
@@ -61,7 +95,8 @@ export default function ThemeCustomization({ children }) {
},
palette: theme.palette,
customShadows: themeCustomShadows,
typography: themeTypography
typography: themeTypography,
shadows: softShadows
}),
[themeDirection, theme, themeTypography, themeCustomShadows]
);
@@ -75,38 +110,37 @@ export default function ThemeCustomization({ children }) {
<CssBaseline />
<GlobalStyles
styles={{
// '*': { // this * access even the internal scroll bar,
// // scrollbarWidth: '100px', // works in Firefox only
// scrollbarColor: `${theme.palette.primary.main} ${theme.palette.secondary.lighter}`
// },
// '*::-webkit-scrollbar': {
// width: '22px', // vertical
// height: '22px' // horizontal
// },
// '*::-webkit-scrollbar-thumb': {
// backgroundColor: '#65387A'
// // borderRadius: '5px'
// },
// '*::-webkit-scrollbar-track': {
// backgroundColor: `${theme.palette.secondary.lighter}`
// }
overflow: 'auto',
'&::-webkit-scrollbar': {
width: '14px', // scroll bar width
cursor: 'pointer'
// Crisp font rendering across the app (enterprise polish).
body: {
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
textRendering: 'optimizeLegibility'
},
'&::-webkit-scrollbar-thumb': {
backgroundColor: theme.palette.primary.main, // thumb color
// borderRadius: '8px',
cursor: 'pointer'
// Thin, unobtrusive scrollbars (Stripe/Linear style) instead of the
// chunky 14px solid-purple bar. Track is transparent; the thumb is a
// soft slate that tints toward brand purple on hover.
'*': {
scrollbarWidth: 'thin', // Firefox
scrollbarColor: 'rgba(102, 37, 130, 0.28) transparent'
},
'&::-webkit-scrollbar-thumb:hover': {
backgroundColor: theme.palette.primary.dark, // hover color
cursor: 'pointer'
'*::-webkit-scrollbar': {
width: '10px',
height: '10px'
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme.palette.primary.lighter,
cursor: 'pointer'
'*::-webkit-scrollbar-track': {
backgroundColor: 'transparent'
},
'*::-webkit-scrollbar-thumb': {
backgroundColor: 'rgba(102, 37, 130, 0.26)',
borderRadius: '8px',
border: '2px solid transparent',
backgroundClip: 'padding-box'
},
'*::-webkit-scrollbar-thumb:hover': {
backgroundColor: 'rgba(102, 37, 130, 0.45)'
},
'*::-webkit-scrollbar-corner': {
backgroundColor: 'transparent'
}
}}
/>

View File

@@ -9,6 +9,27 @@ export default function Autocomplete() {
padding: '3px 9px'
}
},
// Soft, rounded popup with comfortable option rows — matches the
// SoftPaper used by the redesigned pages.
paper: {
borderRadius: 12,
boxShadow: '0 18px 50px rgba(15, 23, 42, 0.18)',
border: '1px solid #e2e8f0',
overflow: 'hidden'
},
listbox: {
padding: 6,
'& .MuiAutocomplete-option': {
borderRadius: 8,
margin: '1px 0',
'&[aria-selected="true"]': {
backgroundColor: 'rgba(102, 37, 130, 0.08)'
},
'&.Mui-focused': {
backgroundColor: 'rgba(102, 37, 130, 0.06)'
}
}
},
popupIndicator: {
width: 'auto',
height: 'auto'

View File

@@ -14,24 +14,21 @@ function getColorStyle({ variant, color, theme }) {
const buttonShadow = `${color}Button`;
const shadows = getShadow(theme, buttonShadow);
// Clean keyboard-focus ring (soft brand-tinted halo) — replaces the old
// flashy expanding ::after glow that read as "template / AI default".
const commonShadow = {
'&::after': {
boxShadow: `0 0 5px 5px ${alpha(main, 0.9)}`
},
'&:active::after': {
boxShadow: `0 0 0 0 ${alpha(main, 0.9)}`
},
'&:focus-visible': {
outline: `2px solid ${dark}`,
outlineOffset: 2
boxShadow: `0 0 0 3px ${alpha(main, 0.32)}`
}
};
switch (variant) {
case 'contained':
return {
boxShadow: 'none',
'&:hover': {
backgroundColor: dark
backgroundColor: dark,
boxShadow: 'none'
},
...commonShadow
};
@@ -103,28 +100,10 @@ export default function Button(theme) {
},
styleOverrides: {
root: {
fontWeight: 400,
'&::after': {
content: '""',
display: 'block',
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
borderRadius: 4,
opacity: 0,
transition: 'all 0.5s'
},
'&:active::after': {
position: 'absolute',
borderRadius: 4,
left: 0,
top: 0,
opacity: 1,
transition: '0s'
}
fontWeight: 600,
borderRadius: 8,
textTransform: 'none',
transition: 'background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease'
},
contained: {
...disabledStyle

View File

@@ -40,7 +40,8 @@ export default function Chip(theme) {
MuiChip: {
styleOverrides: {
root: {
borderRadius: 4,
borderRadius: 8,
fontWeight: 600,
'&:active': {
boxShadow: 'none'
},

View File

@@ -8,9 +8,15 @@ export default function Dialog() {
MuiDialog: {
styleOverrides: {
root: {
// Lighter, slightly blurred scrim — less heavy than a near-opaque black.
'& .MuiBackdrop-root': {
backgroundColor: alpha('#000', 0.7)
backgroundColor: alpha('#0f172a', 0.45),
backdropFilter: 'blur(2px)'
}
},
paper: {
borderRadius: 16,
boxShadow: '0 24px 60px rgba(15, 23, 42, 0.22), 0 8px 18px rgba(15, 23, 42, 0.12)'
}
}
}

View File

@@ -5,8 +5,9 @@ export default function DialogTitle() {
MuiDialogTitle: {
styleOverrides: {
root: {
fontSize: '1rem',
fontWeight: 500
fontSize: '1.0625rem',
fontWeight: 600,
letterSpacing: '-0.01em'
}
}
}

View File

@@ -7,13 +7,13 @@ export default function Tab(theme) {
root: {
minHeight: 46,
color: theme.palette.text.primary,
borderRadius: 4,
borderRadius: 8,
'&:hover': {
backgroundColor: theme.palette.primary.lighter + 60,
color: theme.palette.primary.main
},
'&:focus-visible': {
borderRadius: 4,
borderRadius: 8,
outline: `2px solid ${theme.palette.secondary.dark}`,
outlineOffset: -3
}

View File

@@ -1,42 +1,31 @@
// ==============================|| OVERRIDES - TABLE CELL ||============================== //
export default function TableCell(theme) {
const commonCell = {
'&:not(:last-of-type)': {
position: 'relative',
'&:after': {
position: 'absolute',
content: '""',
backgroundColor: theme.palette.divider,
width: 1,
height: 'calc(100% - 30px)',
right: 0,
top: 16
}
}
};
return {
MuiTableCell: {
styleOverrides: {
root: {
fontSize: '0.875rem',
padding: 12,
padding: '14px 12px',
borderColor: theme.palette.divider
},
sizeSmall: {
padding: 8
padding: '10px 8px'
},
// Clean enterprise header: small, muted, well-tracked uppercase labels.
// The old vertical divider rules between header cells were removed —
// modern data tables (Stripe/Linear) keep headers borderless and quiet.
head: {
fontSize: '0.75rem',
fontSize: '0.6875rem',
fontWeight: 700,
letterSpacing: '0.06em',
textTransform: 'uppercase',
...commonCell
color: theme.palette.text.secondary
},
footer: {
fontSize: '0.75rem',
textTransform: 'uppercase',
...commonCell
color: theme.palette.text.secondary
}
}
}

View File

@@ -1,4 +1,4 @@
// ==============================|| OVERRIDES - TABLE CELL ||============================== //
// ==============================|| OVERRIDES - TABLE HEAD ||============================== //
export default function TableHead(theme) {
return {
@@ -6,8 +6,11 @@ export default function TableHead(theme) {
styleOverrides: {
root: {
backgroundColor: theme.palette.grey[50],
borderTop: `1px solid ${theme.palette.divider}`,
borderBottom: `2px solid ${theme.palette.divider}`
borderTop: 'none',
borderBottom: `1px solid ${theme.palette.divider}`,
'& .MuiTableCell-root': {
borderBottom: `1px solid ${theme.palette.divider}`
}
}
}
}

View File

@@ -13,7 +13,7 @@ const CustomShadows = (theme) => ({
z1:
theme.palette.mode === ThemeMode.DARK
? `0px 1px 1px rgb(0 0 0 / 14%), 0px 2px 1px rgb(0 0 0 / 12%), 0px 1px 3px rgb(0 0 0 / 20%)`
: `0px 1px 4px ${alpha(theme.palette.grey[900], 0.08)}`,
: `0 1px 3px rgba(15, 23, 42, 0.06), 0 6px 16px rgba(15, 23, 42, 0.06)`,
primary: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.2)}`,
secondary: `0 0 0 2px ${alpha(theme.palette.secondary.main, 0.2)}`,
error: `0 0 0 2px ${alpha(theme.palette.error.main, 0.2)}`,

View File

@@ -1,5 +1,9 @@
// ==============================|| DEFAULT THEME - TYPOGRAPHY ||============================== //
// Refined enterprise type scale (Stripe / Linear feel):
// - Tight negative tracking on display headings so large text reads crisp, not loose.
// - Buttons are medium-weight and case-preserving (no shouty UPPERCASE / Capitalize).
// - Slightly looser tracking on the small uppercase eyebrow/overline labels for legibility.
const Typography = (fontFamily) => ({
htmlFontSize: 16,
fontFamily,
@@ -8,52 +12,62 @@ const Typography = (fontFamily) => ({
fontWeightMedium: 500,
fontWeightBold: 600,
h1: {
fontWeight: 600,
fontSize: '2.375rem',
lineHeight: 1.21
fontWeight: 700,
fontSize: '2.25rem',
lineHeight: 1.2,
letterSpacing: '-0.025em'
},
h2: {
fontWeight: 600,
fontWeight: 700,
fontSize: '1.875rem',
lineHeight: 1.27
lineHeight: 1.25,
letterSpacing: '-0.02em'
},
h3: {
fontWeight: 600,
fontSize: '1.5rem',
lineHeight: 1.33
lineHeight: 1.33,
letterSpacing: '-0.018em'
},
h4: {
fontWeight: 600,
fontSize: '1.25rem',
lineHeight: 1.4
lineHeight: 1.4,
letterSpacing: '-0.014em'
},
h5: {
fontWeight: 600,
fontSize: '1rem',
lineHeight: 1.5
lineHeight: 1.5,
letterSpacing: '-0.01em'
},
h6: {
fontWeight: 400,
fontWeight: 500,
fontSize: '0.875rem',
lineHeight: 1.57
lineHeight: 1.57,
letterSpacing: '-0.006em'
},
caption: {
fontWeight: 400,
fontSize: '0.75rem',
lineHeight: 1.66
lineHeight: 1.66,
letterSpacing: '0'
},
body1: {
fontSize: '0.875rem',
lineHeight: 1.57
lineHeight: 1.57,
letterSpacing: '-0.006em'
},
body2: {
fontSize: '0.75rem',
lineHeight: 1.66
lineHeight: 1.66,
letterSpacing: '0'
},
subtitle1: {
fontSize: '0.875rem',
fontWeight: 600,
lineHeight: 1.57
lineHeight: 1.57,
letterSpacing: '-0.006em'
},
subtitle2: {
fontSize: '0.75rem',
@@ -61,10 +75,16 @@ const Typography = (fontFamily) => ({
lineHeight: 1.66
},
overline: {
lineHeight: 1.66
fontWeight: 600,
fontSize: '0.6875rem',
lineHeight: 1.66,
letterSpacing: '0.06em',
textTransform: 'uppercase'
},
button: {
textTransform: 'capitalize'
fontWeight: 600,
letterSpacing: '-0.004em',
textTransform: 'none'
}
});

View File

@@ -6572,6 +6572,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"