Files
doormile_crm/src/pages/reports/OrdersDetails.jsx
2026-06-05 17:28:05 +05:30

191 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}