first commit

This commit is contained in:
2026-06-05 17:28:05 +05:30
commit a162fa89e5
62 changed files with 8729 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
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>
);
}