updated the ui
This commit is contained in:
@@ -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' }
|
||||
}
|
||||
: {};
|
||||
|
||||
|
||||
51
src/components/nearle_components/PageHeader.js
Normal file
51
src/components/nearle_components/PageHeader.js
Normal 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
|
||||
};
|
||||
77
src/components/nearle_components/StatCard.js
Normal file
77
src/components/nearle_components/StatCard.js
Normal 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
|
||||
};
|
||||
74
src/components/nearle_components/StatusChip.js
Normal file
74
src/components/nearle_components/StatusChip.js
Normal 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
|
||||
};
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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
@@ -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}` : '—'}
|
||||
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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' }}
|
||||
>
|
||||
© 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' } }}
|
||||
>
|
||||
© 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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)}` }
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)}` }
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}`
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 #{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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,7 +40,8 @@ export default function Chip(theme) {
|
||||
MuiChip: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 4,
|
||||
borderRadius: 8,
|
||||
fontWeight: 600,
|
||||
'&:active': {
|
||||
boxShadow: 'none'
|
||||
},
|
||||
|
||||
@@ -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)'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ export default function DialogTitle() {
|
||||
MuiDialogTitle: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: '1rem',
|
||||
fontWeight: 500
|
||||
fontSize: '1.0625rem',
|
||||
fontWeight: 600,
|
||||
letterSpacing: '-0.01em'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)}`,
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user