updates on the api integration and the design changes on the whole website

This commit is contained in:
2026-08-11 11:18:40 +05:30
parent d144ff71a9
commit bf28249528
48 changed files with 2888 additions and 6361 deletions

View File

@@ -24,8 +24,20 @@ import {
useMediaQuery,
useTheme
} from '@mui/material';
import { MdPeopleAlt, MdOutlinePeopleAlt, MdEdit, MdPhone, MdMail, MdOutlineMailOutline, MdOutlinePhoneAndroid } from 'react-icons/md';
import {
MdPeopleAlt,
MdOutlinePeopleAlt,
MdEdit,
MdPhone,
MdMail,
MdOutlineMailOutline,
MdOutlinePhoneAndroid,
MdPersonAddAlt1,
MdOutlineReceiptLong
} from 'react-icons/md';
import { FaRegEdit } from 'react-icons/fa';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router';
import Loader from 'components/Loader';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
@@ -63,6 +75,8 @@ const soft = (c) => a(c, '18');
const ring = (c) => a(c, '26');
const edge = (c) => a(c, '55');
const BRAND = '#C01227';
const BRAND_LIGHT = '#D25463';
const tint = (c) => a(c, '08');
const AccentAvatar = ({ color, size = 24, children }) => (
<Avatar sx={{ width: size, height: size, bgcolor: soft(color), color }}>{children}</Avatar>
@@ -77,6 +91,7 @@ const custBookings = (row) => row.totalbookings ?? 0;
const Customers = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const navigate = useNavigate();
const queryClient = useQueryClient();
const [searchword, setSearchword] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
@@ -96,7 +111,8 @@ const Customers = () => {
const stats = useMemo(() => {
const total = customers.length;
const withEmail = customers.filter((c) => c.email).length;
return { total, withEmail };
const totalBookings = customers.reduce((sum, c) => sum + (Number(c.totalbookings) || 0), 0);
return { total, withEmail, totalBookings };
}, [customers]);
const opentoast = (message, variant = 'error') =>
@@ -129,7 +145,23 @@ const Customers = () => {
<>
{isLoading && <Loader />}
<PageHeader title="Customers" subtitle="Live · B2C Directory" live />
<PageHeader
title="Customers"
subtitle="Live · B2C Directory"
live
action={
<Tooltip title="Adds a contact to your tenant's own customer book (used when creating orders) — separate from the order-derived B2C directory below">
<Button
variant="contained"
startIcon={<MdPersonAddAlt1 size={16} />}
onClick={() => navigate('/doormile/customer/create')}
sx={{ bgcolor: BRAND, '&:hover': { bgcolor: BRAND } }}
>
Add Customer
</Button>
</Tooltip>
}
/>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
<Grid item xs={6} md={3}>
@@ -138,6 +170,9 @@ const Customers = () => {
<Grid item xs={6} md={3}>
<StatCard title="With Email" value={stats.withEmail} icon={<MdOutlineMailOutline size={20} />} color="#0ea5e9" />
</Grid>
<Grid item xs={6} md={3}>
<StatCard title="Total Bookings" value={stats.totalBookings} icon={<MdOutlineReceiptLong size={20} />} color="#10b981" />
</Grid>
</Grid>
<Paper
@@ -154,9 +189,19 @@ const Customers = () => {
}}
>
<Stack direction={{ xs: 'column', sm: 'row' }} alignItems={{ xs: 'stretch', sm: 'center' }} justifyContent="space-between" spacing={1.25}>
<Typography variant="body2" sx={{ color: DT.textPrimary, fontWeight: 700 }}>
{customers.length} total · {rows.length} shown
</Typography>
<Stack direction="row" alignItems="center" spacing={1.25}>
<AccentAvatar color={BRAND} size={32}>
<MdPeopleAlt size={18} />
</AccentAvatar>
<Stack>
<Typography variant="caption" sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}>
Directory
</Typography>
<Typography variant="body2" sx={{ color: DT.textPrimary, fontWeight: 700 }}>
{customers.length} total · {rows.length} shown
</Typography>
</Stack>
</Stack>
<Box sx={{ width: { xs: '100%', sm: 280 } }}>
<DebounceSearchBar
value={searchword}
@@ -166,11 +211,11 @@ const Customers = () => {
sx={{
m: 0,
width: '100%',
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 },
borderRadius: 999,
bgcolor: tint(BRAND),
'& fieldset': { borderColor: edge(BRAND), borderWidth: 1.5 },
'&:hover fieldset': { borderColor: BRAND },
'&.Mui-focused fieldset': { borderColor: BRAND, borderWidth: 2 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>
@@ -207,15 +252,20 @@ const Customers = () => {
accent={BRAND}
header={
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
<AccentAvatar color={BRAND} size={36}>
<MdPeopleAlt size={18} />
</AccentAvatar>
<Typography variant="subtitle2" sx={{ fontWeight: 700 }} noWrap>
{custName(row)}
</Typography>
<Stack sx={{ minWidth: 0 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700 }} noWrap>
{custName(row)}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
ID #{custId(row) ?? '—'}
</Typography>
</Stack>
</Stack>
<IconButton size="small" onClick={() => openEdit(row)} sx={{ color: BRAND }}>
<IconButton size="small" onClick={() => openEdit(row)} sx={{ color: BRAND, flexShrink: 0 }}>
<MdEdit size={14} />
</IconButton>
</Stack>
@@ -256,9 +306,8 @@ const Customers = () => {
}}
>
<TableCell>#</TableCell>
<TableCell>Name</TableCell>
<TableCell>Phone</TableCell>
<TableCell>Email</TableCell>
<TableCell>Customer</TableCell>
<TableCell>Contact</TableCell>
<TableCell align="center">Bookings</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
@@ -267,7 +316,7 @@ const Customers = () => {
{isLoading && <OrdersTableSkeleton col={4} />}
{rows.length === 0 && !isLoading ? (
<TableRow>
<TableCell colSpan={6} sx={{ py: 6 }}>
<TableCell colSpan={5} sx={{ py: 6 }}>
<Stack alignItems="center" spacing={1.5}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdPeopleAlt size={28} />
@@ -284,25 +333,42 @@ const Customers = () => {
key={custId(row) || index}
sx={{ '& td': { borderBottom: `1px solid ${DT.divider}` }, '&:hover': { backgroundColor: DT.surfaceAlt } }}
>
<TableCell>{index + 1}</TableCell>
<TableCell>
<Typography variant="caption" sx={{ fontWeight: 700, color: DT.textMuted }}>
{String(index + 1).padStart(2, '0')}
</Typography>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={1}>
<AccentAvatar color={BRAND} size={32}>
<MdPeopleAlt size={16} />
</AccentAvatar>
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{custName(row)}
</Typography>
<Stack>
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{custName(row)}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
ID #{custId(row) ?? '—'}
</Typography>
</Stack>
</Stack>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={0.5}>
<MdPhone size={13} color={DT.textMuted} /> <Typography variant="body2">{custPhone(row)}</Typography>
</Stack>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={0.5}>
<MdMail size={13} color={DT.textMuted} /> <Typography variant="body2">{custEmail(row)}</Typography>
<Stack>
<Stack direction="row" alignItems="center" spacing={0.5}>
<MdPhone size={12} color={DT.textMuted} />
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{custPhone(row)}
</Typography>
</Stack>
{row.email && (
<Stack direction="row" alignItems="center" spacing={0.5}>
<MdMail size={12} color={DT.textMuted} />
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{row.email}
</Typography>
</Stack>
)}
</Stack>
</TableCell>
<TableCell align="center">{custBookings(row)}</TableCell>
@@ -322,31 +388,69 @@ const Customers = () => {
)}
</Paper>
<Dialog open={!!editRow} onClose={() => setEditRow(null)} maxWidth="xs" fullWidth>
<DialogTitle sx={{ fontWeight: 700 }}>Edit Customer</DialogTitle>
<Dialog open={!!editRow} onClose={() => setEditRow(null)} maxWidth="xs" fullWidth PaperProps={{ sx: { borderRadius: 3 } }}>
<DialogTitle
sx={{
background: `linear-gradient(135deg, ${BRAND} 0%, ${BRAND_LIGHT} 100%)`,
color: '#fff',
py: 2
}}
>
<Stack direction="row" alignItems="center" spacing={1.5}>
<Avatar sx={{ width: 36, height: 36, bgcolor: 'rgba(255,255,255,0.2)', color: '#fff' }}>
<FaRegEdit style={{ fontSize: 18 }} />
</Avatar>
<Stack>
<Typography sx={{ fontSize: 11, fontWeight: 700, opacity: 0.85, letterSpacing: 0.6, textTransform: 'uppercase', lineHeight: 1 }}>
Customer
</Typography>
<Typography sx={{ fontWeight: 800, fontSize: '1.1rem', lineHeight: 1.2, mt: 0.25 }}>
Edit {editRow ? custName(editRow) : 'Customer'}
</Typography>
</Stack>
</Stack>
</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ mt: 1 }}>
<Stack spacing={2} sx={{ mt: 2 }}>
<Stack spacing={1}>
<InputLabel>Name</InputLabel>
<TextField value={form.name || ''} onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))} />
<TextField fullWidth value={form.name || ''} onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))} />
</Stack>
<Stack spacing={1}>
<InputLabel>Phone</InputLabel>
<TextField
value={form.phone || ''}
onChange={(e) => setForm((f) => ({ ...f, phone: e.target.value }))}
InputProps={{ startAdornment: <MdOutlinePhoneAndroid size={14} style={{ marginRight: 6, color: DT.textMuted }} /> }}
/>
<InputLabel>Phone Number</InputLabel>
<Stack direction="row" spacing={1}>
<TextField value="+91" disabled sx={{ width: 64, cursor: 'not-allowed' }} />
<TextField
fullWidth
value={form.phone || ''}
inputProps={{ maxLength: 10, inputMode: 'numeric', pattern: '[0-9]*' }}
onChange={(e) => setForm((f) => ({ ...f, phone: e.target.value.replace(/\D/g, '') }))}
InputProps={{ startAdornment: <MdOutlinePhoneAndroid size={14} style={{ marginRight: 6, color: DT.textMuted }} /> }}
/>
</Stack>
</Stack>
<Stack spacing={1}>
<InputLabel>Email</InputLabel>
<TextField value={form.email || ''} onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))} />
<TextField
fullWidth
type="email"
value={form.email || ''}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
InputProps={{ startAdornment: <MdMail size={14} style={{ marginRight: 6, color: DT.textMuted }} /> }}
/>
</Stack>
</Stack>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={() => setEditRow(null)}>Cancel</Button>
<Button variant="contained" onClick={() => saveMutation.mutate()} disabled={saveMutation.isPending} sx={{ bgcolor: BRAND }}>
<Button onClick={() => setEditRow(null)} sx={{ color: DT.textSecondary }}>
Cancel
</Button>
<Button
variant="contained"
onClick={() => saveMutation.mutate()}
disabled={saveMutation.isPending}
sx={{ bgcolor: BRAND, '&:hover': { bgcolor: BRAND } }}
>
Save Changes
</Button>
</DialogActions>