Merge conflicts resolved

This commit is contained in:
2026-06-22 17:48:57 +05:30
7 changed files with 368 additions and 228 deletions

View File

@@ -4,8 +4,9 @@ import {
Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab, Grid,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton,
TablePagination, Typography, Collapse, CircularProgress, Alert, Tooltip, Chip, Divider, Link,
Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions
useMediaQuery, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import SearchIcon from '@mui/icons-material/Search';
import AddIcon from '@mui/icons-material/Add';
import RefreshIcon from '@mui/icons-material/Refresh';
@@ -129,6 +130,202 @@ function Metric({ label, value, color = 'grey.800' }) {
);
}
// The expandable detail panel — shared by the desktop table row and the mobile card.
function ClientDetail({ row, onEdit }) {
return (
<Box sx={{ m: 2, borderRadius: 2.5, border: 1, borderColor: 'divider', overflow: 'hidden', boxShadow: '0 4px 16px rgba(0,0,0,0.06)' }}>
<Stack
direction={{ xs: 'column', sm: 'row' }}
justifyContent="space-between"
alignItems={{ xs: 'flex-start', sm: 'center' }}
spacing={1.5}
sx={{
px: 2.5, py: 1.75, borderBottom: 1, borderColor: 'divider',
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}88 0%, ${theme.palette.background.paper} 75%)`
}}
>
<Stack direction="row" spacing={1.5} alignItems="center">
<UserAvatar name={row.name} size={40} />
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.2 }}>{row.name}</Typography>
<Stack direction="row" spacing={0.75} alignItems="center" sx={{ mt: 0.25 }}>
<BadgeOutlinedIcon sx={{ fontSize: 14, color: 'grey.400' }} />
<Typography variant="caption" color="text.secondary">{row.clientId}</Typography>
</Stack>
</Box>
<StatusChip status={row.status} sx={{ ml: 0.5 }} />
</Stack>
<Button size="small" variant="contained" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(row)} sx={{ width: { xs: '100%', sm: 'auto' } }}>Edit Client</Button>
</Stack>
<Box sx={{ p: { xs: 1.5, sm: 2.5 }, bgcolor: 'grey.50' }}>
<Grid container spacing={2.5} alignItems="stretch">
<Grid item xs={12} md={4}>
<SectionCard icon={StorefrontOutlinedIcon} title="Business" accent="primary">
<Stack direction="row" spacing={1.5}>
<StatTile label="Parcel Volume" value={Number(row.parcelVolume).toLocaleString()} icon={Inventory2OutlinedIcon} color="primary" />
<StatTile label="Active Contracts" value={row.activeContracts} icon={HandshakeOutlinedIcon} color="primary" />
</Stack>
<Field label="Client ID">
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontWeight: 600, color: 'grey.800' }}>{row.clientId}</Typography>
</Field>
<Field label="Business Type"><Pill label={row.businessType && titleCase(row.businessType)} color="primary" /></Field>
<Field label="Order Frequency"><Pill label={row.frequency && titleCase(row.frequency)} color="info" /></Field>
</SectionCard>
</Grid>
<Grid item xs={12} md={4}>
<SectionCard icon={LocalShippingOutlinedIcon} title="Logistics" accent="primary">
<Field label="Logistics Segment">
{row.logisticsSegment ? (
<Stack direction="row" spacing={0.75} flexWrap="wrap" useFlexGap>
{String(row.logisticsSegment).split(/[,/]/).map((seg) => seg.trim()).filter(Boolean).map((seg) => (
<Pill key={seg} label={titleCase(seg)} color="primary" />
))}
</Stack>
) : <Pill label={null} />}
</Field>
<ReadField label="Current Provider" value={row.provider} />
<Field label="Efficiency"><Pill label={row.efficiency && titleCase(row.efficiency)} color={efficiencyTone(row.efficiency)} /></Field>
<Field label="Transit Route">
{(() => {
const stops = [row.transitFrom, ...String(row.transitTo || '').split(',')]
.map((s) => s.trim()).filter(Boolean);
if (!stops.length) return <Pill label={null} />;
return (
<Stack direction="row" spacing={0.75} alignItems="center" flexWrap="wrap" useFlexGap>
{stops.map((stop, idx) => (
<Fragment key={`${stop}-${idx}`}>
{idx > 0 && <ArrowRightAltIcon sx={{ fontSize: 20, color: 'primary.main' }} />}
<Pill label={titleCase(stop)} color={idx === 0 ? 'default' : 'info'} />
</Fragment>
))}
</Stack>
);
})()}
</Field>
</SectionCard>
</Grid>
<Grid item xs={12} md={4}>
<SectionCard icon={PlaceOutlinedIcon} title="Location & Survey" accent="primary">
<Field label="City / State">
<Typography variant="body2" sx={{ fontWeight: 500, color: 'grey.800' }}>{[row.city, row.businessState].filter(Boolean).join(', ') || '—'}</Typography>
</Field>
<ReadField label="Neighbourhood" value={row.neighbourhood} />
<Field label="Survey Address">
<Stack direction="row" spacing={1} sx={{ p: 1.25, borderRadius: 1.5, bgcolor: 'grey.50', border: 1, borderColor: 'divider' }}>
<PlaceOutlinedIcon sx={{ fontSize: 16, color: 'grey.400', mt: '2px' }} />
<Typography variant="body2" sx={{ color: 'grey.800', lineHeight: 1.5 }}>{row.surveyAddress || '—'}</Typography>
</Stack>
</Field>
<Field label="Coordinates">
{row.surveyLat && row.surveyLng ? (
<Stack direction="row" spacing={1.25} alignItems="center" flexWrap="wrap" useFlexGap>
<Tooltip title={`${row.surveyLat}, ${row.surveyLng}`} arrow placement="top">
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ cursor: 'default' }}>
<PlaceOutlinedIcon sx={{ fontSize: 16, color: 'grey.400' }} />
<Typography variant="body2" sx={{ fontWeight: 500, color: 'grey.800', borderBottom: '1px dotted', borderColor: 'grey.300' }}>
{[row.neighbourhood, row.city].filter(Boolean).join(', ') || 'Pinned location'}
</Typography>
</Stack>
</Tooltip>
<Button
component="a"
href={`https://www.google.com/maps?q=${row.surveyLat},${row.surveyLng}`}
target="_blank" rel="noopener"
size="small" variant="outlined" startIcon={<MapOutlinedIcon sx={{ fontSize: 16 }} />}
sx={{
py: 0.25, px: 1.25, minWidth: 0, fontSize: '0.75rem', fontWeight: 600, borderRadius: 5,
color: 'primary.main', borderColor: 'primary.100', bgcolor: 'primary.lighter',
'&:hover': { borderColor: 'primary.main', bgcolor: 'primary.lighter' }
}}
>
View on map
</Button>
</Stack>
) : <Typography variant="body2" sx={{ color: 'grey.500' }}></Typography>}
</Field>
</SectionCard>
</Grid>
<Grid item xs={12}>
<SectionCard icon={InfoOutlinedIcon} title="Contact & Compliance" accent="primary">
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} md={4}><ReadField label="Phone" value={row.phone} /></Grid>
<Grid item xs={12} sm={6} md={4}><Field label="Data Consent"><Pill label={row.dataConsent && titleCase(row.dataConsent)} color={consentTone(row.dataConsent)} /></Field></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Last Updated" value={row.lastUpdated} /></Grid>
{row.notes && (
<Grid item xs={12}>
<Box sx={{ p: 2, borderRadius: 1.5, bgcolor: 'warning.lighter', border: 1, borderColor: 'warning.light' }}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
<NotesOutlinedIcon sx={{ fontSize: 16, color: 'warning.dark' }} />
<Typography variant="caption" sx={{ color: 'warning.dark', textTransform: 'uppercase', letterSpacing: 0.4, fontSize: '0.68rem', fontWeight: 700 }}>Notes</Typography>
</Stack>
<Typography variant="body2" sx={{ color: 'grey.800' }}>{row.notes}</Typography>
</Box>
</Grid>
)}
</Grid>
</SectionCard>
</Grid>
</Grid>
</Box>
</Box>
);
}
// Mobile presentation of a client — summary card with an expandable detail panel.
function ClientCard({ row, onEdit, onDelete }) {
const [open, setOpen] = useState(false);
return (
<Box sx={{ borderRadius: 3, border: 1, borderColor: open ? 'primary.light' : 'divider', bgcolor: 'background.paper', overflow: 'hidden' }}>
<Box sx={{ p: 2 }}>
<Stack direction="row" spacing={1.25} alignItems="center">
<UserAvatar name={row.name} size={42} />
<Box sx={{ minWidth: 0, flexGrow: 1 }}>
<Typography variant="body2" sx={{ fontWeight: 700, color: 'grey.800' }} noWrap>{row.name}</Typography>
<Typography variant="caption" sx={{ fontFamily: 'monospace', fontWeight: 700, color: 'primary.main' }}>{row.logicalId}</Typography>
</Box>
<StatusChip status={row.status} />
</Stack>
<Stack spacing={1} sx={{ mt: 1.75 }}>
<Stack direction="row" spacing={1} alignItems="center">
<PhoneOutlinedIcon sx={{ fontSize: 16, color: 'grey.400', flexShrink: 0 }} />
<Typography variant="body2">{row.phone || '—'}</Typography>
</Stack>
<Stack direction="row" spacing={1} alignItems="center">
<PlaceOutlinedIcon sx={{ fontSize: 16, color: 'grey.400', flexShrink: 0 }} />
<Typography variant="body2" noWrap>{row.city || '—'}{row.businessState ? `, ${row.businessState}` : ''}</Typography>
</Stack>
</Stack>
<Stack direction="row" spacing={1.5} alignItems="center" divider={<Divider orientation="vertical" flexItem />} sx={{ mt: 1.75 }}>
<Metric label="Parcels" value={Number(row.parcelVolume).toLocaleString()} />
<Metric label="Contracts" value={row.activeContracts} color="primary.main" />
</Stack>
<Divider sx={{ my: 1.5 }} />
<Stack direction="row" spacing={1} justifyContent="space-between" alignItems="center">
<Button size="small" onClick={() => setOpen((o) => !o)} endIcon={open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}>
{open ? 'Hide' : 'Details'}
</Button>
<Stack direction="row" spacing={1}>
<Button size="small" variant="outlined" startIcon={<EditOutlinedIcon fontSize="small" />} onClick={() => onEdit(row)}>Edit</Button>
<Button size="small" variant="outlined" color="error" startIcon={<DeleteOutlineIcon fontSize="small" />} onClick={() => onDelete(row)}>Delete</Button>
</Stack>
</Stack>
</Box>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ borderTop: 1, borderColor: 'divider' }}>
<ClientDetail row={row} onEdit={onEdit} />
</Box>
</Collapse>
</Box>
);
}
function ClientRow({ row, index, onEdit, onDelete }) {
const [open, setOpen] = useState(false);
@@ -203,145 +400,7 @@ function ClientRow({ row, index, onEdit, onDelete }) {
<TableRow>
<TableCell colSpan={8} sx={{ py: 0, borderBottom: open ? undefined : 'none' }}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ m: 2, borderRadius: 2.5, border: 1, borderColor: 'divider', overflow: 'hidden', boxShadow: '0 4px 16px rgba(0,0,0,0.06)' }}>
<Stack
direction={{ xs: 'column', sm: 'row' }}
justifyContent="space-between"
alignItems={{ xs: 'flex-start', sm: 'center' }}
spacing={1.5}
sx={{
px: 2.5, py: 1.75, borderBottom: 1, borderColor: 'divider',
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}88 0%, ${theme.palette.background.paper} 75%)`
}}
>
<Stack direction="row" spacing={1.5} alignItems="center">
<UserAvatar name={row.name} size={40} />
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.2 }}>{row.name}</Typography>
<Stack direction="row" spacing={0.75} alignItems="center" sx={{ mt: 0.25 }}>
<BadgeOutlinedIcon sx={{ fontSize: 14, color: 'grey.400' }} />
<Typography variant="caption" color="text.secondary">{row.clientId}</Typography>
</Stack>
</Box>
<StatusChip status={row.status} sx={{ ml: 0.5 }} />
</Stack>
<Button size="small" variant="contained" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(row)}>Edit Client</Button>
</Stack>
<Box sx={{ p: 2.5, bgcolor: 'grey.50' }}>
<Grid container spacing={2.5} alignItems="stretch">
<Grid item xs={12} md={4}>
<SectionCard icon={StorefrontOutlinedIcon} title="Business" accent="primary">
<Stack direction="row" spacing={1.5}>
<StatTile label="Parcel Volume" value={Number(row.parcelVolume).toLocaleString()} icon={Inventory2OutlinedIcon} color="primary" />
<StatTile label="Active Contracts" value={row.activeContracts} icon={HandshakeOutlinedIcon} color="primary" />
</Stack>
<Field label="Client ID">
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontWeight: 600, color: 'grey.800' }}>{row.clientId}</Typography>
</Field>
<Field label="Business Type"><Pill label={row.businessType && titleCase(row.businessType)} color="primary" /></Field>
<Field label="Order Frequency"><Pill label={row.frequency && titleCase(row.frequency)} color="info" /></Field>
</SectionCard>
</Grid>
<Grid item xs={12} md={4}>
<SectionCard icon={LocalShippingOutlinedIcon} title="Logistics" accent="primary">
<Field label="Logistics Segment">
{row.logisticsSegment ? (
<Stack direction="row" spacing={0.75} flexWrap="wrap" useFlexGap>
{String(row.logisticsSegment).split(/[,/]/).map((seg) => seg.trim()).filter(Boolean).map((seg) => (
<Pill key={seg} label={titleCase(seg)} color="primary" />
))}
</Stack>
) : <Pill label={null} />}
</Field>
<ReadField label="Current Provider" value={row.provider} />
<Field label="Efficiency"><Pill label={row.efficiency && titleCase(row.efficiency)} color={efficiencyTone(row.efficiency)} /></Field>
<Field label="Transit Route">
{(() => {
const stops = [row.transitFrom, ...String(row.transitTo || '').split(',')]
.map((s) => s.trim()).filter(Boolean);
if (!stops.length) return <Pill label={null} />;
return (
<Stack direction="row" spacing={0.75} alignItems="center" flexWrap="wrap" useFlexGap>
{stops.map((stop, idx) => (
<Fragment key={`${stop}-${idx}`}>
{idx > 0 && <ArrowRightAltIcon sx={{ fontSize: 20, color: 'primary.main' }} />}
<Pill label={titleCase(stop)} color={idx === 0 ? 'default' : 'info'} />
</Fragment>
))}
</Stack>
);
})()}
</Field>
</SectionCard>
</Grid>
<Grid item xs={12} md={4}>
<SectionCard icon={PlaceOutlinedIcon} title="Location & Survey" accent="primary">
<Field label="City / State">
<Typography variant="body2" sx={{ fontWeight: 500, color: 'grey.800' }}>{[row.city, row.businessState].filter(Boolean).join(', ') || '—'}</Typography>
</Field>
<ReadField label="Neighbourhood" value={row.neighbourhood} />
<Field label="Survey Address">
<Stack direction="row" spacing={1} sx={{ p: 1.25, borderRadius: 1.5, bgcolor: 'grey.50', border: 1, borderColor: 'divider' }}>
<PlaceOutlinedIcon sx={{ fontSize: 16, color: 'grey.400', mt: '2px' }} />
<Typography variant="body2" sx={{ color: 'grey.800', lineHeight: 1.5 }}>{row.surveyAddress || '—'}</Typography>
</Stack>
</Field>
<Field label="Coordinates">
{row.surveyLat && row.surveyLng ? (
<Stack direction="row" spacing={1.25} alignItems="center" flexWrap="wrap" useFlexGap>
<Tooltip title={`${row.surveyLat}, ${row.surveyLng}`} arrow placement="top">
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ cursor: 'default' }}>
<PlaceOutlinedIcon sx={{ fontSize: 16, color: 'grey.400' }} />
<Typography variant="body2" sx={{ fontWeight: 500, color: 'grey.800', borderBottom: '1px dotted', borderColor: 'grey.300' }}>
{[row.neighbourhood, row.city].filter(Boolean).join(', ') || 'Pinned location'}
</Typography>
</Stack>
</Tooltip>
<Button
component="a"
href={`https://www.google.com/maps?q=${row.surveyLat},${row.surveyLng}`}
target="_blank" rel="noopener"
size="small" variant="outlined" startIcon={<MapOutlinedIcon sx={{ fontSize: 16 }} />}
sx={{
py: 0.25, px: 1.25, minWidth: 0, fontSize: '0.75rem', fontWeight: 600, borderRadius: 5,
color: 'primary.main', borderColor: 'primary.100', bgcolor: 'primary.lighter',
'&:hover': { borderColor: 'primary.main', bgcolor: 'primary.lighter' }
}}
>
View on map
</Button>
</Stack>
) : <Typography variant="body2" sx={{ color: 'grey.500' }}></Typography>}
</Field>
</SectionCard>
</Grid>
<Grid item xs={12}>
<SectionCard icon={InfoOutlinedIcon} title="Contact & Compliance" accent="primary">
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} md={4}><ReadField label="Phone" value={row.phone} /></Grid>
<Grid item xs={12} sm={6} md={4}><Field label="Data Consent"><Pill label={row.dataConsent && titleCase(row.dataConsent)} color={consentTone(row.dataConsent)} /></Field></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Last Updated" value={row.lastUpdated} /></Grid>
{row.notes && (
<Grid item xs={12}>
<Box sx={{ p: 2, borderRadius: 1.5, bgcolor: 'warning.lighter', border: 1, borderColor: 'warning.light' }}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
<NotesOutlinedIcon sx={{ fontSize: 16, color: 'warning.dark' }} />
<Typography variant="caption" sx={{ color: 'warning.dark', textTransform: 'uppercase', letterSpacing: 0.4, fontSize: '0.68rem', fontWeight: 700 }}>Notes</Typography>
</Stack>
<Typography variant="body2" sx={{ color: 'grey.800' }}>{row.notes}</Typography>
</Box>
</Grid>
)}
</Grid>
</SectionCard>
</Grid>
</Grid>
</Box>
</Box>
<ClientDetail row={row} onEdit={onEdit} />
</Collapse>
</TableCell>
</TableRow>
@@ -350,6 +409,8 @@ function ClientRow({ row, index, onEdit, onDelete }) {
}
export default function Tenants() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [clients, setClients] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -450,9 +511,9 @@ export default function Tenants() {
title="Clients"
breadcrumbs={[{ label: 'Clients' }]}
action={
<Stack direction="row" spacing={1.5}>
<Button variant="outlined" startIcon={<RefreshIcon />} onClick={load} disabled={loading}>Refresh</Button>
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setDialog({ open: true, mode: 'add', initial: null })}>Add Client</Button>
<Stack direction="row" spacing={1.5} sx={{ width: { xs: '100%', sm: 'auto' } }}>
<Button variant="outlined" startIcon={<RefreshIcon />} onClick={load} disabled={loading} sx={{ flex: { xs: 1, sm: 'none' } }}>Refresh</Button>
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setDialog({ open: true, mode: 'add', initial: null })} sx={{ flex: { xs: 1, sm: 'none' } }}>Add Client</Button>
</Stack>
}
/>
@@ -484,7 +545,7 @@ export default function Tenants() {
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: 2 }} alignItems={{ md: 'center' }}>
<TextField
size="small" placeholder="Search by name, phone, city, ID…" value={search} onChange={(e) => { setSearch(e.target.value); setPage(0); }}
sx={{ minWidth: 300 }}
sx={{ width: { xs: '100%', md: 300 } }}
InputProps={{ startAdornment: <InputAdornment position="start"><SearchIcon fontSize="small" /></InputAdornment> }}
/>
<Box sx={{ flexGrow: 1 }} />
@@ -508,35 +569,38 @@ export default function Tenants() {
{error && <Alert severity="error" sx={{ m: 2 }} action={<Button color="inherit" size="small" onClick={load}>Retry</Button>}>{error}</Alert>}
<TableContainer>
<Table sx={{ minWidth: 900 }}>
<TableHead>
<TableRow sx={{ '& th': { bgcolor: 'grey.50', fontWeight: 700, color: 'grey.700', textTransform: 'uppercase', fontSize: '0.72rem', letterSpacing: 0.4 } }}>
<TableCell padding="checkbox" />
<TableCell>ID</TableCell>
<TableCell>Client</TableCell>
<TableCell>Contact</TableCell>
<TableCell>Location</TableCell>
<TableCell align="center">Volume</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={8} sx={{ border: 'none' }}>
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6 }}><CircularProgress /></Box>
</TableCell>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6 }}><CircularProgress /></Box>
) : paged.length === 0 ? (
<EmptyState title="No clients found" caption="Try a different tab or search term, or add a client." />
) : isMobile ? (
<Stack spacing={1.5} sx={{ p: 2 }}>
{paged.map((row) => (
<ClientCard
key={row.id}
row={row}
onEdit={(r) => setDialog({ open: true, mode: 'edit', initial: r })}
onDelete={(r) => setToDelete(r)}
/>
))}
</Stack>
) : (
<TableContainer>
<Table sx={{ minWidth: 900 }}>
<TableHead>
<TableRow sx={{ '& th': { bgcolor: 'grey.50', fontWeight: 700, color: 'grey.700', textTransform: 'uppercase', fontSize: '0.72rem', letterSpacing: 0.4 } }}>
<TableCell padding="checkbox" />
<TableCell>ID</TableCell>
<TableCell>Client</TableCell>
<TableCell>Contact</TableCell>
<TableCell>Location</TableCell>
<TableCell align="center">Volume</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
) : paged.length === 0 ? (
<TableRow>
<TableCell colSpan={8} sx={{ border: 'none' }}>
<EmptyState title="No clients found" caption="Try a different tab or search term, or add a client." />
</TableCell>
</TableRow>
) : (
paged.map((row, i) => (
</TableHead>
<TableBody>
{paged.map((row, i) => (
<ClientRow
key={row.id}
row={row}
@@ -544,11 +608,11 @@ export default function Tenants() {
onEdit={(r) => setDialog({ open: true, mode: 'edit', initial: r })}
onDelete={(r) => setToDelete(r)}
/>
))
)}
</TableBody>
</Table>
</TableContainer>
))}
</TableBody>
</Table>
</TableContainer>
)}
<TablePagination
component="div" count={filtered.length} page={page} onPageChange={(_, p) => setPage(p)}
rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]}