first commit
This commit is contained in:
258
src/pages/tenants/Tenants.jsx
Normal file
258
src/pages/tenants/Tenants.jsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user