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,258 @@
import { useState, useMemo, Fragment } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton,
Tooltip, TablePagination, Typography, Collapse, Grid
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import AddIcon from '@mui/icons-material/Add';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import PageHeader from '@/components/PageHeader';
import StatusChip from '@/components/StatusChip';
import EmptyState from '@/components/EmptyState';
import UserAvatar from '@/components/UserAvatar';
import TabLabelCount from '@/components/TabLabelCount';
import { tenants, tenantPricing } from '@/data/mock';
import { inr } from '@/utils/format';
const TABS = [
{ key: 'active', label: 'Active' },
{ key: 'pending', label: 'Pending' },
{ key: 'inactive', label: 'Inactive' }
];
function ReadField({ label, value }) {
return (
<Box>
<Typography variant="caption" color="text.secondary">{label}</Typography>
<Typography variant="body2" sx={{ fontWeight: 500, color: 'grey.800' }}>{value || '—'}</Typography>
</Box>
);
}
function PricingTab() {
return (
<Box>
<Stack direction="row" justifyContent="flex-end" sx={{ mb: 1.5 }}>
<Button variant="contained" startIcon={<AddIcon />}>Add Pricing</Button>
</Stack>
<Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, overflow: 'hidden' }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Date</TableCell>
<TableCell>Slab</TableCell>
<TableCell align="right">Base Price</TableCell>
<TableCell align="right">Min Kms</TableCell>
<TableCell align="right">Price/Km</TableCell>
<TableCell align="right">Other Charges</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tenantPricing.map((p, i) => (
<TableRow key={i}>
<TableCell>{p.date}</TableCell>
<TableCell>{p.slab}</TableCell>
<TableCell align="right">{inr(p.basePrice)}</TableCell>
<TableCell align="right">{p.minKms}</TableCell>
<TableCell align="right">{inr(p.pricePerKm)}</TableCell>
<TableCell align="right">{inr(p.otherCharges)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Box>
);
}
function EditTab({ tenant }) {
const [form, setForm] = useState({
name: tenant.name, contact: tenant.contact, phone: tenant.phone, email: tenant.email,
address: tenant.address, city: tenant.city, postcode: tenant.postcode, lat: tenant.lat, lng: tenant.lng
});
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
return (
<Box>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Tenant Name" value={form.name} onChange={set('name')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Contact Person" value={form.contact} onChange={set('contact')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Contact Number" value={form.phone} onChange={set('phone')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Email" value={form.email} onChange={set('email')} /></Grid>
<Grid item xs={12}><TextField fullWidth size="small" label="Address" value={form.address} onChange={set('address')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="City" value={form.city} onChange={set('city')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="PostCode" value={form.postcode} onChange={set('postcode')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Latitude" value={form.lat} onChange={set('lat')} /></Grid>
<Grid item xs={12} sm={6}><TextField fullWidth size="small" label="Longitude" value={form.lng} onChange={set('lng')} /></Grid>
</Grid>
<Stack direction="row" justifyContent="flex-end" sx={{ mt: 2.5 }}>
<Button variant="contained">Update</Button>
</Stack>
</Box>
);
}
function TenantRow({ row, index }) {
const [open, setOpen] = useState(false);
const [inner, setInner] = useState(0);
return (
<Fragment>
<TableRow hover sx={{ '& > *': { borderBottom: open ? 'unset' : undefined } }}>
<TableCell padding="checkbox">
<IconButton size="small" onClick={() => setOpen((o) => !o)}>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
<TableCell>{index + 1}</TableCell>
<TableCell>
<Stack direction="row" spacing={1.25} alignItems="center">
<UserAvatar name={row.name} size={34} />
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, color: 'grey.800' }}>{row.name}</Typography>
<Typography variant="caption" color="text.secondary">{row.volume} orders / mo</Typography>
</Box>
</Stack>
</TableCell>
<TableCell>
<Typography variant="body2">{row.contact}</Typography>
<Typography variant="caption" color="text.secondary">{row.phone}</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">{row.address}</Typography>
<Typography variant="caption" color="text.secondary">{row.city} · {row.postcode}</Typography>
</TableCell>
<TableCell><StatusChip status={row.status} /></TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={6} sx={{ py: 0, borderBottom: open ? undefined : 'none' }}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ m: 2, borderRadius: 1, border: 1, borderColor: 'divider', overflow: 'hidden' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider', bgcolor: 'grey.50' }}>
<Tabs value={inner} onChange={(_, v) => setInner(v)} sx={{ px: 2 }}>
<Tab label="Details" />
<Tab label="Pricing" />
<Tab label="Edit" />
</Tabs>
</Box>
<Box sx={{ p: 2.5 }}>
{inner === 0 && (
<Grid container spacing={2.5}>
<Grid item xs={12} sm={6} md={4}><ReadField label="Name" value={row.name} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Contact Person" value={row.contact} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Phone" value={row.phone} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="E-Mail" value={row.email} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Address" value={row.address} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="City" value={row.city} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="PostCode" value={row.postcode} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Latitude" value={row.lat} /></Grid>
<Grid item xs={12} sm={6} md={4}><ReadField label="Longitude" value={row.lng} /></Grid>
</Grid>
)}
{inner === 1 && <PricingTab />}
{inner === 2 && <EditTab tenant={row} />}
</Box>
</Box>
</Collapse>
</TableCell>
</TableRow>
</Fragment>
);
}
export default function Tenants() {
const navigate = useNavigate();
const [tab, setTab] = useState(0);
const [search, setSearch] = useState('');
const [page, setPage] = useState(0);
const [rpp, setRpp] = useState(10);
const tabKey = TABS[tab].key;
const counts = useMemo(() => {
const c = {};
TABS.forEach((t) => { c[t.key] = tenants.filter((d) => d.status === t.key).length; });
return c;
}, []);
const filtered = useMemo(
() =>
tenants.filter((t) => {
const matchTab = t.status === tabKey;
const matchSearch =
!search ||
[t.name, t.contact, t.email, t.phone, t.city].join(' ').toLowerCase().includes(search.toLowerCase());
return matchTab && matchSearch;
}),
[tabKey, search]
);
const paged = filtered.slice(page * rpp, page * rpp + rpp);
return (
<>
<PageHeader
title="Tenants"
breadcrumbs={[{ label: 'Tenants' }]}
action={
<Button variant="contained" startIcon={<AddIcon />} onClick={() => navigate('/tenants/create')}>
Create Client
</Button>
}
/>
<Card>
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: 2 }} alignItems={{ md: 'center' }}>
<TextField
size="small" placeholder="Search clients…" value={search} onChange={(e) => { setSearch(e.target.value); setPage(0); }}
sx={{ minWidth: 260 }}
InputProps={{ startAdornment: <InputAdornment position="start"><SearchIcon fontSize="small" /></InputAdornment> }}
/>
<Box sx={{ flexGrow: 1 }} />
</Stack>
<Box sx={{ px: 2, borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={tab} onChange={(_, v) => { setTab(v); setPage(0); }}>
{TABS.map((t, i) => (
<Tab key={t.key} label={<TabLabelCount label={t.label} count={counts[t.key]} active={tab === i} />} />
))}
</Tabs>
</Box>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox" />
<TableCell>S.No</TableCell>
<TableCell>Client</TableCell>
<TableCell>Contact</TableCell>
<TableCell>Address</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{paged.length === 0 ? (
<TableRow>
<TableCell colSpan={6} sx={{ border: 'none' }}>
<EmptyState title="No tenants found" caption="Try a different tab or search term." />
</TableCell>
</TableRow>
) : (
paged.map((row, i) => <TenantRow key={row.id} row={row} index={page * rpp + i} />)
)}
</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]}
/>
</Card>
</>
);
}