import React, { useMemo, useState } from 'react'; import { MdOutlineWarehouse, MdEdit, MdDeleteOutline, MdAdd, MdPlace, MdOutlineCheckCircle } 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 { 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 { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout'; import { Divider } from '@astryxdesign/core/Divider'; 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 StatusBadge from 'components/nearle_components/StatusBadge'; import { PageShell, KpiGrid, FilterToolbar, DataCard } from 'components/nearle_components/PageLayout'; import { getHubs, createHub, updateHub, deleteHub } from 'pages/api/doormileApi'; import { DT, STATUS } from 'themes/dt/tokens'; import { TableScroll, AccentAvatar } from 'themes/dt/primitives'; 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, hubname: '', hubtype: 'delivery_hub', applocationid: '', contactno: '', address: '', latitude: '', longitude: '', pincode: '', // Confirmed live: POST /admin/hubs rejects lowercase status ("active" // fails, "Active" works) — Inactive was never confirmed to work even on // create, so this only offers the confirmed-working value here. status: 'Active' }; const Hubs = () => { const queryClient = useQueryClient(); const [searchword, setSearchword] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [dialogOpen, setDialogOpen] = useState(false); const [form, setForm] = useState(emptyForm); const { data: hubs = [], isLoading } = useQuery({ queryKey: ['admin-hubs'], queryFn: getHubs }); 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)) ); }, [hubs, debouncedSearch]); const stats = useMemo(() => { const total = hubs.length; const sorting = hubs.filter((h) => h.hubtype === 'sorting_center').length; const delivery = hubs.filter((h) => h.hubtype === 'delivery_hub').length; const active = hubs.filter((h) => String(h.status || '').toLowerCase() === 'active').length; return { total, sorting, delivery, active }; }, [hubs]); const opentoast = (message, variant = 'error') => enqueueSnackbar(message, { variant, anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 }); const saveMutation = useMutation({ mutationFn: (payload) => (payload.hubid ? updateHub(payload.hubid, payload.data) : createHub(payload.data)), onSuccess: (res) => { if (res.success) { opentoast(form.hubid ? 'Hub updated' : 'Hub created', 'success'); queryClient.invalidateQueries({ queryKey: ['admin-hubs'] }); setDialogOpen(false); } else { opentoast(res.message || 'Failed to save hub'); } }, onError: (err) => opentoast(err.response?.data?.message || err.message || 'Failed to save hub') }); const deleteMutation = useMutation({ mutationFn: (id) => deleteHub(id), onSuccess: (res) => { if (res.success) { opentoast('Hub deleted', 'success'); queryClient.invalidateQueries({ queryKey: ['admin-hubs'] }); } else { opentoast(res.message || 'Failed to delete hub'); } }, onError: (err) => opentoast(err.response?.data?.message || err.message || 'Failed to delete hub') }); const openCreate = () => { setForm(emptyForm); setDialogOpen(true); }; const openEdit = (row) => { setForm({ hubid: row.hubid, hubname: row.hubname || '', hubtype: row.hubtype || 'delivery_hub', applocationid: row.applocationid ?? '', contactno: row.contactno || '', address: row.address || '', latitude: row.latitude ?? '', longitude: row.longitude ?? '', pincode: row.pincode || '', status: row.status || 'Active' }); setDialogOpen(true); }; const handleDelete = (row) => { if (window.confirm(`Delete hub "${row.hubname}"?`)) deleteMutation.mutate(row.hubid); }; const handleSave = () => { if (!form.hubname || !form.applocationid) { opentoast('Fill Hub Name and City (applocationid)'); return; } const { hubid, ...data } = form; saveMutation.mutate({ hubid, data: { ...data, applocationid: Number(data.applocationid), latitude: Number(data.latitude) || 0, longitude: Number(data.longitude) || 0 } }); }; const set = (key) => (value) => setForm((f) => ({ ...f, [key]: value })); return ( <> {(isLoading || deleteMutation.isPending) && } } onClick={openCreate} />} /> } color={BRAND} /> } color={STATUS.info} /> } color={STATUS.delivered} /> } color={STATUS.active} /> {hubs.length} total · {rows.length} shown } > # Hub Name Type City Address Pincode Status Actions {isLoading && } {rows.length === 0 && !isLoading ? ( } /> ) : ( rows.map((row, index) => ( {index + 1} {row.hubname} {row.hubtype === 'sorting_center' ? 'Sorting Center' : 'Delivery Hub'} {row.applocationid ?? '—'} {row.address || '—'} {row.pincode || '—'} {row.status ? : '—'} } onClick={() => openEdit(row)} /> } onClick={() => handleDelete(row)} /> )) )}
!next && setDialogOpen(false)} width={680} purpose="form"> !next && setDialogOpen(false)} startContent={ } /> } content={ Hub Details Location set('applocationid')(v ?? '')} description="Read the correct id from GET /admin/hubs rather than guessing" isRequired /> set('latitude')(v ?? '')} isOptional /> set('longitude')(v ?? '')} isOptional /> } footer={ ); }; export default Hubs;