191 lines
9.2 KiB
JavaScript
191 lines
9.2 KiB
JavaScript
import { useState, useMemo } from 'react';
|
||
import {
|
||
Grid, Card, Stack, Button, TextField, MenuItem, InputAdornment, Box, IconButton, Tooltip,
|
||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination, Typography,
|
||
Dialog, DialogTitle, DialogContent, DialogActions
|
||
} from '@mui/material';
|
||
import SearchIcon from '@mui/icons-material/Search';
|
||
import CalendarTodayOutlinedIcon from '@mui/icons-material/CalendarTodayOutlined';
|
||
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
|
||
import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
|
||
import CloseIcon from '@mui/icons-material/Close';
|
||
|
||
import PageHeader from '@/components/PageHeader';
|
||
import StatusChip from '@/components/StatusChip';
|
||
import MapPlaceholder from '@/components/MapPlaceholder';
|
||
import { ordersDetailReport, locations, tenantsList } from '@/data/mock';
|
||
import { inr } from '@/utils/format';
|
||
|
||
const STATUSES = ['all', 'created', 'pending', 'picked', 'active', 'delivered', 'cancelled'];
|
||
|
||
export default function OrdersDetails() {
|
||
const [location, setLocation] = useState('all');
|
||
const [tenant, setTenant] = useState('all');
|
||
const [loc2, setLoc2] = useState('all');
|
||
const [status, setStatus] = useState('all');
|
||
const [search, setSearch] = useState('');
|
||
const [page, setPage] = useState(0);
|
||
const [rpp, setRpp] = useState(10);
|
||
const [mapRow, setMapRow] = useState(null);
|
||
const [exportOpen, setExportOpen] = useState(false);
|
||
|
||
const filtered = useMemo(
|
||
() =>
|
||
ordersDetailReport.filter((o) => {
|
||
const matchStatus = status === 'all' || o.status === status;
|
||
const matchTenant = tenant === 'all' || o.client === tenant;
|
||
const matchSearch =
|
||
!search ||
|
||
[o.id, o.client, o.pickup, o.drop].join(' ').toLowerCase().includes(search.toLowerCase());
|
||
return matchStatus && matchTenant && matchSearch;
|
||
}),
|
||
[status, tenant, search]
|
||
);
|
||
|
||
const paged = filtered.slice(page * rpp, page * rpp + rpp);
|
||
|
||
return (
|
||
<>
|
||
<PageHeader
|
||
title="Orders Details"
|
||
breadcrumbs={[{ label: 'Reports', to: '/reports' }, { label: 'Orders Details' }]}
|
||
action={
|
||
<TextField select size="small" value={location} onChange={(e) => setLocation(e.target.value)} sx={{ minWidth: 160 }} label="Location">
|
||
<MenuItem value="all">All Locations</MenuItem>
|
||
{locations.map((l) => <MenuItem key={l} value={l}>{l}</MenuItem>)}
|
||
</TextField>
|
||
}
|
||
/>
|
||
|
||
<Card>
|
||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: 2 }} alignItems={{ md: 'center' }} flexWrap="wrap" useFlexGap>
|
||
<TextField select size="small" value={tenant} onChange={(e) => setTenant(e.target.value)} sx={{ minWidth: 170 }} label="Tenant">
|
||
<MenuItem value="all">All Tenants</MenuItem>
|
||
{tenantsList.map((t) => <MenuItem key={t} value={t}>{t}</MenuItem>)}
|
||
</TextField>
|
||
<TextField select size="small" value={loc2} onChange={(e) => setLoc2(e.target.value)} sx={{ minWidth: 160 }} label="Location">
|
||
<MenuItem value="all">All Locations</MenuItem>
|
||
{locations.map((l) => <MenuItem key={l} value={l}>{l}</MenuItem>)}
|
||
</TextField>
|
||
<Button variant="outlined" startIcon={<CalendarTodayOutlinedIcon />} sx={{ color: 'text.secondary', borderColor: 'grey.300' }}>
|
||
Jun 01 – Jun 05
|
||
</Button>
|
||
<TextField select size="small" value={status} onChange={(e) => { setStatus(e.target.value); setPage(0); }} sx={{ minWidth: 150 }} label="Status">
|
||
{STATUSES.map((s) => <MenuItem key={s} value={s}>{s === 'all' ? 'All Status' : s[0].toUpperCase() + s.slice(1)}</MenuItem>)}
|
||
</TextField>
|
||
<TextField
|
||
size="small" placeholder="Search orders…" value={search} onChange={(e) => { setSearch(e.target.value); setPage(0); }} sx={{ minWidth: 220 }}
|
||
InputProps={{ startAdornment: <InputAdornment position="start"><SearchIcon fontSize="small" /></InputAdornment> }}
|
||
/>
|
||
<Box sx={{ flexGrow: 1 }} />
|
||
<Button variant="contained" startIcon={<FileDownloadOutlinedIcon />} onClick={() => setExportOpen(true)}>
|
||
Export Report
|
||
</Button>
|
||
</Stack>
|
||
|
||
<TableContainer>
|
||
<Table size="small">
|
||
<TableHead>
|
||
<TableRow>
|
||
<TableCell>#</TableCell>
|
||
<TableCell />
|
||
<TableCell>Client</TableCell>
|
||
<TableCell>Pickup</TableCell>
|
||
<TableCell>Drop</TableCell>
|
||
<TableCell>Status</TableCell>
|
||
<TableCell align="center">Assigned</TableCell>
|
||
<TableCell align="center">Accepted</TableCell>
|
||
<TableCell align="center">Arrived</TableCell>
|
||
<TableCell align="center">Picked</TableCell>
|
||
<TableCell align="center">Active</TableCell>
|
||
<TableCell align="center">Delivered</TableCell>
|
||
<TableCell align="center">Cancelled</TableCell>
|
||
<TableCell>Notes</TableCell>
|
||
<TableCell align="right">KMS</TableCell>
|
||
<TableCell align="right">Charges</TableCell>
|
||
</TableRow>
|
||
</TableHead>
|
||
<TableBody>
|
||
{paged.map((o, i) => (
|
||
<TableRow key={o.id} hover>
|
||
<TableCell>{page * rpp + i + 1}</TableCell>
|
||
<TableCell padding="checkbox">
|
||
<Tooltip title="View route">
|
||
<IconButton size="small" color="primary" onClick={() => setMapRow(o)}><MapOutlinedIcon fontSize="small" /></IconButton>
|
||
</Tooltip>
|
||
</TableCell>
|
||
<TableCell sx={{ fontWeight: 600 }}>{o.client}</TableCell>
|
||
<TableCell>{o.pickup}</TableCell>
|
||
<TableCell>{o.drop}</TableCell>
|
||
<TableCell><StatusChip status={o.status} /></TableCell>
|
||
<TableCell align="center">{o.assigned}</TableCell>
|
||
<TableCell align="center">{o.accepted}</TableCell>
|
||
<TableCell align="center">{o.arrived}</TableCell>
|
||
<TableCell align="center">{o.picked}</TableCell>
|
||
<TableCell align="center">{o.active}</TableCell>
|
||
<TableCell align="center">{o.delivered}</TableCell>
|
||
<TableCell align="center">{o.cancelled}</TableCell>
|
||
<TableCell><Typography variant="caption" color="text.secondary" noWrap>{o.notes || '—'}</Typography></TableCell>
|
||
<TableCell align="right">{o.kms}</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>{inr(o.charges)}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</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={[10, 25, 50]}
|
||
/>
|
||
</Card>
|
||
|
||
{/* Map dialog */}
|
||
<Dialog open={!!mapRow} onClose={() => setMapRow(null)} fullScreen>
|
||
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||
<Box>
|
||
Route — {mapRow?.id}
|
||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
||
{mapRow?.pickup} → {mapRow?.drop}
|
||
</Typography>
|
||
</Box>
|
||
<IconButton onClick={() => setMapRow(null)}><CloseIcon /></IconButton>
|
||
</DialogTitle>
|
||
<DialogContent dividers>
|
||
<MapPlaceholder height={520} label="Route" />
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* Export dialog */}
|
||
<Dialog open={exportOpen} onClose={() => setExportOpen(false)} maxWidth="xs" fullWidth>
|
||
<DialogTitle>Export Report</DialogTitle>
|
||
<DialogContent dividers>
|
||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||
The export will include {filtered.length} record(s) matching the current filters:
|
||
</Typography>
|
||
<Grid container spacing={1.5}>
|
||
<Filter label="Location" value={location === 'all' ? 'All Locations' : location} />
|
||
<Filter label="Tenant" value={tenant === 'all' ? 'All Tenants' : tenant} />
|
||
<Filter label="Location (2)" value={loc2 === 'all' ? 'All Locations' : loc2} />
|
||
<Filter label="Status" value={status === 'all' ? 'All Status' : status} />
|
||
<Filter label="Date Range" value="Jun 01 – Jun 05" />
|
||
<Filter label="Search" value={search || '—'} />
|
||
</Grid>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button onClick={() => setExportOpen(false)}>Cancel</Button>
|
||
<Button variant="contained" startIcon={<FileDownloadOutlinedIcon />} onClick={() => setExportOpen(false)}>Download CSV</Button>
|
||
</DialogActions>
|
||
</Dialog>
|
||
</>
|
||
);
|
||
}
|
||
|
||
function Filter({ label, value }) {
|
||
return (
|
||
<Grid item xs={6}>
|
||
<Typography variant="caption" color="text.secondary">{label}</Typography>
|
||
<Typography variant="subtitle2" sx={{ textTransform: 'capitalize' }}>{value}</Typography>
|
||
</Grid>
|
||
);
|
||
}
|