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
|
||||
};
|
||||
Reference in New Issue
Block a user