updates on the ui changes and frully removed the mui and converted into astryx and color change

This commit is contained in:
2026-08-13 18:24:04 +05:30
parent 498bb7cbc0
commit 8cd8e34b11
122 changed files with 15111 additions and 22411 deletions

View File

@@ -1,59 +1,42 @@
import React, { useMemo, useState } from 'react';
import {
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
IconButton,
InputLabel,
MenuItem,
Paper,
Select,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Tooltip,
Typography,
useMediaQuery,
useTheme
} from '@mui/material';
import { MdOutlineWarehouse, MdEdit, MdDeleteOutline, MdAdd, MdPlace } from 'react-icons/md';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { enqueueSnackbar } from 'notistack';
import { Grid } from '@astryxdesign/core/Grid';
import { HStack } from '@astryxdesign/core/HStack';
import { VStack } from '@astryxdesign/core/VStack';
import { Card } from '@astryxdesign/core/Card';
import { Text } from '@astryxdesign/core/Text';
import { Button } from '@astryxdesign/core/Button';
import { IconButton } from '@astryxdesign/core/IconButton';
import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog';
import { TextInput } from '@astryxdesign/core/TextInput';
import { NumberInput } from '@astryxdesign/core/NumberInput';
import { Selector } from '@astryxdesign/core/Selector';
import { EmptyState } from '@astryxdesign/core/EmptyState';
import { Table, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '@astryxdesign/core/Table';
import Loader from 'components/Loader';
import TableLoader from 'components/nearle_components/TableLoader';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import { getHubs, createHub, updateHub, deleteHub } from 'pages/api/doormileApi';
import { enqueueSnackbar } from 'notistack';
import { DT, STATUS } from 'themes/dt/tokens';
import { TableScroll } from 'themes/dt/primitives';
const DT = {
radiusCard: 14,
radiusField: 10,
textPrimary: '#0f172a',
textSecondary: '#64748b',
textMuted: '#94a3b8',
borderSubtle: '#e2e8f0',
borderHover: '#cbd5e1',
divider: '#f1f5f9',
surface: '#ffffff',
surfaceAlt: '#f8fafc'
};
const a = (c, suffix) => `${c}${suffix}`;
const soft = (c) => a(c, '18');
const ring = (c) => a(c, '26');
const BRAND = '#C01227';
const HUB_TYPES = ['sorting_center', 'delivery_hub'];
const BRAND = DT.brand;
const HUB_TYPE_OPTIONS = [
{ value: 'sorting_center', label: 'Sorting Center' },
{ value: 'delivery_hub', label: 'Delivery Hub' }
];
const STATUS_OPTIONS = [
{ value: 'Active', label: 'Active' },
{ value: 'Inactive', label: 'Inactive' }
];
const emptyForm = {
hubid: null,
@@ -72,8 +55,6 @@ const emptyForm = {
};
const Hubs = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const queryClient = useQueryClient();
const [searchword, setSearchword] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
@@ -85,7 +66,9 @@ const Hubs = () => {
const rows = useMemo(() => {
if (!debouncedSearch) return hubs;
const q = debouncedSearch.toLowerCase().trim();
return hubs.filter((row) => [row.hubname, row.hubtype, row.address, row.pincode].filter(Boolean).some((f) => String(f).toLowerCase().includes(q)));
return hubs.filter((row) =>
[row.hubname, row.hubtype, row.address, row.pincode].filter(Boolean).some((f) => String(f).toLowerCase().includes(q))
);
}, [hubs, debouncedSearch]);
const stats = useMemo(() => {
@@ -167,6 +150,8 @@ const Hubs = () => {
});
};
const set = (key) => (value) => setForm((f) => ({ ...f, [key]: value }));
return (
<>
{(isLoading || saveMutation.isPending || deleteMutation.isPending) && <Loader />}
@@ -175,200 +160,143 @@ const Hubs = () => {
title="Hubs"
subtitle="Live · Sorting Centers & Delivery Hubs"
live
action={
<Button variant="contained" startIcon={<MdAdd size={16} />} onClick={openCreate} sx={{ bgcolor: BRAND, whiteSpace: 'nowrap' }}>
New Hub
</Button>
}
action={<Button label="New Hub" variant="primary" icon={<MdAdd size={16} />} onClick={openCreate} />}
/>
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
<Grid item xs={4}>
<StatCard title="Total Hubs" value={stats.total} icon={<MdOutlineWarehouse size={20} />} color={BRAND} />
</Grid>
<Grid item xs={4}>
<StatCard title="Sorting Centers" value={stats.sorting} icon={<MdOutlineWarehouse size={20} />} color="#0ea5e9" />
</Grid>
<Grid item xs={4}>
<StatCard title="Delivery Hubs" value={stats.delivery} icon={<MdPlace size={20} />} color="#10b981" />
</Grid>
<Grid columns={{ minWidth: 220 }} gap={3}>
<StatCard title="Total Hubs" value={stats.total} icon={<MdOutlineWarehouse size={20} />} color={BRAND} />
<StatCard title="Sorting Centers" value={stats.sorting} icon={<MdOutlineWarehouse size={20} />} color={STATUS.info} />
<StatCard title="Delivery Hubs" value={stats.delivery} icon={<MdPlace size={20} />} color={STATUS.delivered} />
</Grid>
<Paper
elevation={0}
sx={{ mt: { xs: 1.5, md: 2 }, p: { xs: 1, md: 1.5 }, borderTopLeftRadius: DT.radiusCard / 8, borderTopRightRadius: DT.radiusCard / 8, border: '1px solid', borderColor: DT.borderSubtle, borderBottom: 0, background: '#fff' }}
>
<Stack direction={{ xs: 'column', sm: 'row' }} alignItems={{ xs: 'stretch', sm: 'center' }} justifyContent="space-between" spacing={1.25}>
<Typography variant="body2" sx={{ color: DT.textPrimary, fontWeight: 700 }}>
{hubs.length} total · {rows.length} shown
</Typography>
<Box sx={{ width: { xs: '100%', sm: 280 } }}>
<DebounceSearchBar
value={searchword}
onChange={setSearchword}
onDebouncedChange={setDebouncedSearch}
placeholder="Search hubs (ctrl+k)"
sx={{
m: 0,
width: '100%',
borderRadius: DT.radiusField + 'px',
bgcolor: DT.surface,
'& fieldset': { borderColor: DT.borderSubtle, borderWidth: 1 },
'&:hover fieldset': { borderColor: DT.borderHover },
'&.Mui-focused fieldset': { borderColor: BRAND, borderWidth: 1.5 },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
/>
</Box>
</Stack>
</Paper>
<VStack gap={0} padding={0} style={{ marginTop: 16 }}>
<Card padding={3} elevation="none" style={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0, borderBottom: 'none' }}>
<HStack justify="between" vAlign="center" gap={3} wrap="wrap">
<Text weight="bold">
{hubs.length} total · {rows.length} shown
</Text>
<div style={{ width: 280, maxWidth: '100%' }}>
<DebounceSearchBar
value={searchword}
onChange={setSearchword}
onDebouncedChange={setDebouncedSearch}
placeholder="Search hubs (ctrl+k)"
/>
</div>
</HStack>
</Card>
<Paper elevation={0} sx={{ borderBottomLeftRadius: DT.radiusCard / 8, borderBottomRightRadius: DT.radiusCard / 8, border: '1px solid', borderColor: DT.borderSubtle, overflow: 'hidden', background: '#fff' }}>
<TableContainer sx={{ maxHeight: { xs: 'calc(100vh - 260px)', md: 'calc(100vh - 190px)' } }}>
<Table stickyHeader sx={{ minWidth: isMobile ? 900 : 960 }}>
<TableHead>
<TableRow sx={{ '& th': { backgroundColor: DT.surfaceAlt, color: DT.textSecondary, fontSize: 11, fontWeight: 800, letterSpacing: 0.6, textTransform: 'uppercase', whiteSpace: 'nowrap', borderBottom: `1px solid ${DT.borderSubtle}` } }}>
<TableCell>#</TableCell>
<TableCell>Hub Name</TableCell>
<TableCell>Type</TableCell>
<TableCell>City</TableCell>
<TableCell>Address</TableCell>
<TableCell>Pincode</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoading && <OrdersTableSkeleton col={6} />}
{rows.length === 0 && !isLoading ? (
<Card padding={0} elevation="none" style={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, overflow: 'hidden' }}>
<TableScroll minWidth={960}>
<Table density="balanced" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableCell colSpan={8} sx={{ py: 6 }}>
<Stack alignItems="center" spacing={1.5}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdOutlineWarehouse size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
No hubs to show
</Typography>
</Stack>
</TableCell>
<TableHeaderCell>#</TableHeaderCell>
<TableHeaderCell>Hub Name</TableHeaderCell>
<TableHeaderCell>Type</TableHeaderCell>
<TableHeaderCell>City</TableHeaderCell>
<TableHeaderCell>Address</TableHeaderCell>
<TableHeaderCell>Pincode</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
<TableHeaderCell>Actions</TableHeaderCell>
</TableRow>
) : (
rows.map((row, index) => (
<TableRow key={row.hubid || index} sx={{ '& td': { borderBottom: `1px solid ${DT.divider}` }, '&:hover': { backgroundColor: DT.surfaceAlt } }}>
<TableCell>{index + 1}</TableCell>
<TableCell>
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{row.hubname}
</Typography>
</TableCell>
<TableCell>{row.hubtype === 'sorting_center' ? 'Sorting Center' : 'Delivery Hub'}</TableCell>
<TableCell>{row.applocationid ?? '—'}</TableCell>
<TableCell>{row.address || '—'}</TableCell>
<TableCell>{row.pincode || '—'}</TableCell>
<TableCell>{row.status || '—'}</TableCell>
<TableCell align="right">
<Stack direction="row" spacing={0.5} justifyContent="flex-end">
<Tooltip title="Edit hub">
<IconButton size="small" onClick={() => openEdit(row)} sx={{ color: BRAND }}>
<MdEdit size={16} />
</IconButton>
</Tooltip>
<Tooltip title="Delete hub">
<IconButton size="small" onClick={() => handleDelete(row)} sx={{ color: '#ef4444' }}>
<MdDeleteOutline size={16} />
</IconButton>
</Tooltip>
</Stack>
</TableHeader>
<TableBody>
{isLoading && <TableLoader rows={5} columns={8} />}
{rows.length === 0 && !isLoading ? (
<TableRow>
<TableCell colSpan={8}>
<EmptyState
title="No hubs to show"
description="Create a hub to start routing consignments through it."
icon={<MdOutlineWarehouse size={28} />}
/>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
) : (
rows.map((row, index) => (
<TableRow key={row.hubid || index}>
<TableCell>{index + 1}</TableCell>
<TableCell>
<Text weight="bold">{row.hubname}</Text>
</TableCell>
<TableCell>{row.hubtype === 'sorting_center' ? 'Sorting Center' : 'Delivery Hub'}</TableCell>
<TableCell>{row.applocationid ?? '—'}</TableCell>
<TableCell>{row.address || '—'}</TableCell>
<TableCell>{row.pincode || '—'}</TableCell>
<TableCell>{row.status || '—'}</TableCell>
<TableCell>
<HStack gap={1} justify="end">
<IconButton
label="Edit hub"
tooltip="Edit hub"
size="sm"
variant="ghost"
icon={<MdEdit size={16} />}
onClick={() => openEdit(row)}
/>
<IconButton
label="Delete hub"
tooltip="Delete hub"
size="sm"
variant="destructive"
icon={<MdDeleteOutline size={16} />}
onClick={() => handleDelete(row)}
/>
</HStack>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableScroll>
</Card>
</VStack>
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle sx={{ fontWeight: 700 }}>{form.hubid ? 'Edit Hub' : 'New Hub'}</DialogTitle>
<DialogContent>
<Grid container spacing={2} sx={{ mt: 0.5 }}>
<Grid item xs={12} sm={6}>
<Stack spacing={1}>
<InputLabel>Hub Name</InputLabel>
<TextField value={form.hubname} onChange={(e) => setForm((f) => ({ ...f, hubname: e.target.value }))} />
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={1}>
<InputLabel>Type</InputLabel>
<Select fullWidth value={form.hubtype} onChange={(e) => setForm((f) => ({ ...f, hubtype: e.target.value }))}>
{HUB_TYPES.map((t) => (
<MenuItem key={t} value={t}>
{t === 'sorting_center' ? 'Sorting Center' : 'Delivery Hub'}
</MenuItem>
))}
</Select>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={1}>
<InputLabel>City (applocationid)</InputLabel>
<TextField
type="number"
value={form.applocationid}
onChange={(e) => setForm((f) => ({ ...f, applocationid: e.target.value }))}
helperText="Read the correct id from GET /admin/hubs rather than guessing"
/>
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={1}>
<InputLabel>Contact No</InputLabel>
<TextField value={form.contactno} onChange={(e) => setForm((f) => ({ ...f, contactno: e.target.value }))} />
</Stack>
</Grid>
<Grid item xs={12}>
<Stack spacing={1}>
<InputLabel>Address</InputLabel>
<TextField value={form.address} onChange={(e) => setForm((f) => ({ ...f, address: e.target.value }))} />
</Stack>
</Grid>
<Grid item xs={12} sm={4}>
<Stack spacing={1}>
<InputLabel>Pincode</InputLabel>
<TextField value={form.pincode} onChange={(e) => setForm((f) => ({ ...f, pincode: e.target.value }))} />
</Stack>
</Grid>
<Grid item xs={12} sm={4}>
<Stack spacing={1}>
<InputLabel>Latitude</InputLabel>
<TextField type="number" value={form.latitude} onChange={(e) => setForm((f) => ({ ...f, latitude: e.target.value }))} />
</Stack>
</Grid>
<Grid item xs={12} sm={4}>
<Stack spacing={1}>
<InputLabel>Longitude</InputLabel>
<TextField type="number" value={form.longitude} onChange={(e) => setForm((f) => ({ ...f, longitude: e.target.value }))} />
</Stack>
</Grid>
<Grid item xs={12} sm={6}>
<Stack spacing={1}>
<InputLabel>Status</InputLabel>
<Select fullWidth value={form.status} onChange={(e) => setForm((f) => ({ ...f, status: e.target.value }))}>
<MenuItem value="Active">Active</MenuItem>
<MenuItem value="Inactive">Inactive</MenuItem>
</Select>
</Stack>
</Grid>
<Dialog isOpen={dialogOpen} onOpenChange={(next) => !next && setDialogOpen(false)} width={640} purpose="form">
<DialogHeader title={form.hubid ? 'Edit Hub' : 'New Hub'} />
<VStack gap={4} padding={4}>
<Grid columns={{ minWidth: 240 }} gap={3}>
<TextInput label="Hub Name" value={form.hubname} onChange={set('hubname')} isRequired />
<Selector label="Type" options={HUB_TYPE_OPTIONS} value={form.hubtype} onChange={set('hubtype')} />
<NumberInput
label="City (applocationid)"
value={form.applocationid === '' ? undefined : Number(form.applocationid)}
onChange={(v) => set('applocationid')(v ?? '')}
description="Read the correct id from GET /admin/hubs rather than guessing"
isRequired
/>
<TextInput label="Contact No" value={form.contactno} onChange={set('contactno')} isOptional />
</Grid>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={() => setDialogOpen(false)}>Cancel</Button>
<Button variant="contained" onClick={handleSave} sx={{ bgcolor: BRAND }}>
{form.hubid ? 'Save Changes' : 'Create'}
</Button>
</DialogActions>
<TextInput label="Address" value={form.address} onChange={set('address')} isOptional />
<Grid columns={{ minWidth: 220 }} gap={3}>
<TextInput label="Pincode" value={form.pincode} onChange={set('pincode')} isOptional />
<NumberInput
label="Latitude"
value={form.latitude === '' ? undefined : Number(form.latitude)}
onChange={(v) => set('latitude')(v ?? '')}
isOptional
/>
<NumberInput
label="Longitude"
value={form.longitude === '' ? undefined : Number(form.longitude)}
onChange={(v) => set('longitude')(v ?? '')}
isOptional
/>
</Grid>
<Grid columns={{ minWidth: 240 }} gap={3}>
<Selector label="Status" options={STATUS_OPTIONS} value={form.status} onChange={set('status')} />
</Grid>
<HStack gap={2} justify="end">
<Button label="Cancel" variant="secondary" onClick={() => setDialogOpen(false)} />
<Button label={form.hubid ? 'Save Changes' : 'Create'} variant="primary" onClick={handleSave} />
</HStack>
</VStack>
</Dialog>
</>
);