updates on the api integration and the design changes on the whole website

This commit is contained in:
2026-08-11 11:18:40 +05:30
parent d144ff71a9
commit bf28249528
48 changed files with 2888 additions and 6361 deletions

View File

@@ -79,7 +79,7 @@ import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getalltenants, gettenantsummary } from 'pages/api/api';
import { getalltenants } from 'pages/api/api';
import {
getAdminPricing,
getDoormilePricing,
@@ -440,7 +440,11 @@ const TenantCustomersDialog = ({ tenant, open, onClose }) => {
id: tenantCustId(row),
firstname: row.firstname || '',
lastname: row.lastname || '',
phone: row.phone || '',
// GET/POST /admin/tenantcustomers has a write/read asymmetry confirmed
// live: POST accepts `phone`, but the stored/returned field is
// `contactno`. The form field itself stays named `phone` (matching
// what create/update accept) — only the read source differs.
phone: row.contactno || '',
email: row.email || ''
});
setFormOpen(true);
@@ -470,7 +474,7 @@ const TenantCustomersDialog = ({ tenant, open, onClose }) => {
{c.firstname} {c.lastname}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{[c.phone, c.email].filter(Boolean).join(' · ')}
{[c.contactno, c.email].filter(Boolean).join(' · ')}
</Typography>
</Box>
<Stack direction="row" spacing={0.5}>
@@ -580,11 +584,9 @@ const Clients1 = () => {
setPage(0);
};
const handleChange1 = (event, newValue1) => {
console.log('newValue1', newValue1);
setValue1(newValue1);
};
const handleChange2 = (event, newValue2) => {
console.log('newValue2', newValue2);
setValue2(newValue2);
};
@@ -600,9 +602,6 @@ const Clients1 = () => {
const handleCollapseToggle2 = (rowIndex) => {
setOpenRowIndex2((prevIndex) => (prevIndex === rowIndex ? null : rowIndex));
};
useEffect(() => {
console.log('selectedCustomer', selectedCustomer);
}, [selectedCustomer]);
useEffect(() => {
setOpenRowIndex1(-1);
setOpenRowIndex2(-1);
@@ -700,15 +699,38 @@ const Clients1 = () => {
queryFn: getalltenants
});
// ==============================|| gettenantsummary ||============================== //
const {
data: summaryData = [],
isLoading: summaryDataIsLoading,
refetch: summaryDataRefetch
} = useQuery({
queryKey: ['gettenantsummary', appId, status],
queryFn: gettenantsummary
});
// No tenant-tab-counts endpoint in the new API (gettenantsummary always
// returns null) — derive the Active/Pending/Inactive counts client-side
// from the full tenant list instead, which getalltenants already fetches.
const tenantStatusCounts = React.useMemo(() => {
const counts = { active: 0, pending: 0, inactive: 0 };
(tenantList || []).forEach((t) => {
const s = String(t.status || '').toLowerCase();
if (s === 'active') counts.active += 1;
else if (s === 'pending') counts.pending += 1;
else if (s === 'inactive') counts.inactive += 1;
});
return counts;
}, [tenantList]);
// getalltenants() takes no filter args (see api.js) — the status tabs and
// search box only ever changed the query-cache key, never the actual
// request, so every tab showed the exact same unfiltered list with just a
// relabeled status badge. Filter the already-fetched list client-side
// instead, matching the same real `status` field tenantStatusCounts uses.
const filteredTenantList = React.useMemo(() => {
const activeStatus = STATUS_TABS[value0]?.status;
const q = debouncedSearch.trim().toLowerCase();
return (tenantList || []).filter((t) => {
if (String(t.status || '').toLowerCase() !== activeStatus) return false;
if (!q) return true;
return (
t.tenantname?.toLowerCase().includes(q) ||
t.primaryemail?.toLowerCase().includes(q) ||
t.primarycontact?.includes(debouncedSearch)
);
});
}, [tenantList, value0, debouncedSearch]);
/* ============================================= || fetchclientpricelist || ============================================= */
@@ -718,7 +740,7 @@ const Clients1 = () => {
const pricing = await getAdminPricing();
setClientpricelist((pricing || []).filter((p) => p.tenantid === selectedTenid));
} catch (err) {
console.log(err);
// swallow
}
};
useEffect(() => {
@@ -731,7 +753,7 @@ const Clients1 = () => {
const pricing = await getAdminPricing();
setTenanatPricing((pricing || []).filter((p) => p.tenantid === id));
} catch (error) {
console.log('fetchTenanatPricing', error);
// swallow
}
};
useEffect(() => {
@@ -760,10 +782,8 @@ const Clients1 = () => {
2000
);
getalltenantsRefetch();
summaryDataRefetch();
}
} catch (err) {
console.log(err);
opentoast(err.message, 'error', 1500);
} finally {
setisloader(false);
@@ -776,7 +796,7 @@ const Clients1 = () => {
const pricing = await getDoormilePricing();
setAppPricing((pricing || []).filter((p) => !id || p.applocationid === id));
} catch (error) {
console.log('appPricingRes', error);
// swallow
}
};
/* ============================================= || createpricing || ============================================= */
@@ -793,10 +813,14 @@ const Clients1 = () => {
priceperkg: +selectedPricing.priceperkg || 0,
basedistance: +selectedPricing.basedistance || 0,
handlingcharges: +selectedPricing.handlingcharges || 0,
effectivefrom: dayjs().format('YYYY-MM-DD'),
// Confirmed live: POST /admin/pricing rejects a bare YYYY-MM-DD date
// for effectivefrom ("invalid request body") — needs a full ISO
// datetime. Status also confirmed to need title case ("Active"),
// matching every real pricing row.
effectivefrom: dayjs().toISOString(),
currency: 'INR',
priority: 1,
status: 'active'
status: 'Active'
});
if (res.success) {
enqueueSnackbar('Price Created Successfully', {
@@ -812,7 +836,6 @@ const Clients1 = () => {
opentoast(res.message || 'Failed to save pricing', 'error', 2000);
}
} catch (err) {
console.log(err);
opentoast(err.response?.data?.message || err.message || 'Failed to save pricing', 'error', 2000);
}
};
@@ -826,9 +849,7 @@ const Clients1 = () => {
opentoast(updateRes.message || 'Update Failed', 'error', 1500);
}
getalltenantsRefetch();
summaryDataRefetch();
} catch (err) {
console.log('updateClient', err);
opentoast(err.response?.data?.message || err.message || 'Update Failed', 'error', 1500);
}
};
@@ -837,7 +858,7 @@ const Clients1 = () => {
return (
<>
{(getalltenantsIsLoading || summaryDataIsLoading || isloader) && <Loader />}
{(getalltenantsIsLoading || isloader) && <Loader />}
{/* ============================================= || Header | ============================================= */}
<PageHeader
title="Tenants"
@@ -864,7 +885,7 @@ const Clients1 = () => {
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item, idx) => {
const Icon = item.icon;
const value = summaryData?.[item.countKey] ?? 0;
const value = tenantStatusCounts[item.countKey] ?? 0;
return (
<Grid item key={item.key} xs={12} sm={4}>
<Box
@@ -878,7 +899,7 @@ const Clients1 = () => {
value={value ?? 0}
icon={<Icon size={20} />}
color={idx === 0 ? '#662582' : item.color}
loading={summaryDataIsLoading}
loading={getalltenantsIsLoading}
/>
</Box>
</Grid>
@@ -925,7 +946,7 @@ const Clients1 = () => {
const meta = STATUS_META[t.status];
const Icon = meta.icon;
const active = value0 === idx;
const count = summaryData?.[t.countKey] ?? 0;
const count = tenantStatusCounts[t.countKey] ?? 0;
return (
<Box
key={t.status}
@@ -987,7 +1008,7 @@ const Clients1 = () => {
border: 'none'
}}
>
{summaryDataIsLoading ? <Skeleton variant="text" width={14} height={10} /> : count}
{getalltenantsIsLoading ? <Skeleton variant="text" width={14} height={10} /> : count}
</Box>
</Box>
);
@@ -1064,14 +1085,14 @@ const Clients1 = () => {
<TableCell>Status</TableCell>
<TableCell>Client</TableCell>
<TableCell>Contact</TableCell>
<TableCell>Address</TableCell>
<TableCell>Delivery OTP</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{getalltenantsIsLoading && <OrdersTableSkeleton />}
{tenantList?.length == 0 && !isloader ? (
{filteredTenantList.length == 0 && !isloader ? (
<TableRow>
<TableCell colSpan={6} sx={{ py: 6 }}>
<Stack alignItems="center" spacing={1.5}>
@@ -1088,9 +1109,9 @@ const Clients1 = () => {
</TableCell>
</TableRow>
) : (
tenantList?.map((row, index) => {
const rowStatusKey = (value0 === 0 ? 'active' : value0 === 1 ? 'pending' : 'inactive');
const rowStatusMeta = STATUS_META[rowStatusKey];
filteredTenantList.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row, index) => {
const rowStatusKey = String(row.status || '').toLowerCase();
const rowStatusMeta = STATUS_META[rowStatusKey] || STATUS_META.active;
const RowStatusIcon = rowStatusMeta.icon;
return (
<Fragment key={row.tenantid ?? index}>
@@ -1162,20 +1183,9 @@ const Clients1 = () => {
</Stack>
</TableCell>
<TableCell sx={{ maxWidth: 280 }}>
<Tooltip title={row.address || ''} placement="top">
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
}}
>
{row.address || '—'}
</Typography>
</Tooltip>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{row.requiredeliveryotp ? 'Yes' : 'No'}
</Typography>
</TableCell>
<TableCell align="right">
<Stack direction="row" spacing={0.75} justifyContent="flex-end">
@@ -1217,7 +1227,7 @@ const Clients1 = () => {
setDialogopen(true);
setSelectedtenid(row.tenantid);
setAppId(row.applocationid);
getAppPricing(row.applolcationid);
getAppPricing(row.applocationid);
}}
>
<IssuesCloseOutlined style={{ fontSize: 14 }} />
@@ -1407,7 +1417,7 @@ const Clients1 = () => {
<Grid item xs={6}>
<Grid container spacing={4} alignItems="center">
<Grid item>
<FiUser
<FaRegCheckCircle
style={{
fontSize: '20px',
fontWeight: 'lighter'
@@ -1415,9 +1425,9 @@ const Clients1 = () => {
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left">{row.firstname}</Typography>
<Typography align="left">{row.status || '—'}</Typography>
<Typography align="left" variant="caption" color="secondary">
Contact Person
Status
</Typography>
</Grid>
</Grid>
@@ -1461,121 +1471,26 @@ const Clients1 = () => {
<Grid item xs={12}>
<Grid container spacing={4} alignItems="center">
<Grid item>
<FaRegAddressCard
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
{row.requiredeliveryotp ? (
<MdCheckCircle
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
) : (
<MdCancel
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
)}
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left">{row.address}</Typography>
<Typography align="left">{row.requiredeliveryotp ? 'Yes' : 'No'}</Typography>
<Typography align="left" variant="caption" color="secondary">
Address
</Typography>
</Grid>
</Grid>
</Grid>
{/* <Grid item xs={12}>
<Grid container spacing={4} alignItems="center">
<Grid item>
<TbCategoryMinus
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left">{row.subcategoryname}</Typography>
<Typography align="left" variant="caption" color="secondary">
Category
</Typography>
</Grid>
<Grid item>
<MdMyLocation
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left" variant="subtitle1">
{row.suburb}
</Typography>
<Typography align="left" variant="caption" color="secondary">
Location
</Typography>
</Grid>
</Grid>
</Grid> */}
<Grid item xs={12}>
<Grid container spacing={4} alignItems="center">
<Grid item>
<GiModernCity
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left">{row.city}</Typography>
<Typography align="left" variant="caption" color="secondary">
City
</Typography>
</Grid>
<Grid item>
<MdNumbers
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left" variant="subtitle1">
{row.postcode}
</Typography>
<Typography align="left" variant="caption" color="secondary">
PostCode
</Typography>
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Grid container spacing={4} alignItems="center">
<Grid item>
<TbWorldLatitude
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left">{row.latitude}</Typography>
<Typography align="left" variant="caption" color="secondary">
Latitude
</Typography>
</Grid>
<Grid item>
<TbWorldLongitude
style={{
fontSize: '20px',
fontWeight: 'lighter'
}}
/>
</Grid>
<Grid item xs zeroMinWidth>
<Typography align="left" variant="subtitle1">
{row.longitude}
</Typography>
<Typography align="left" variant="caption" color="secondary">
Longitude
Delivery OTP Required
</Typography>
</Grid>
</Grid>
@@ -1681,7 +1596,7 @@ const Clients1 = () => {
flexDirection: 'row'
}}
onClick={() => {
getAppPricing(selectedCustomer.applolcationid);
getAppPricing(selectedCustomer.applocationid);
}}
/>
)}
@@ -2105,7 +2020,7 @@ const Clients1 = () => {
</Stack>
)}
{tenantList?.length == 0 && !isloader ? (
{filteredTenantList.length == 0 && !isloader ? (
<Stack alignItems="center" spacing={1.5} sx={{ py: 6 }}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdGroups size={28} />
@@ -2118,9 +2033,9 @@ const Clients1 = () => {
</Typography>
</Stack>
) : (
tenantList?.map((row, index) => {
const rowStatusKey = value0 === 0 ? 'active' : value0 === 1 ? 'pending' : 'inactive';
const rowStatusMeta = STATUS_META[rowStatusKey];
filteredTenantList.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row, index) => {
const rowStatusKey = String(row.status || '').toLowerCase();
const rowStatusMeta = STATUS_META[rowStatusKey] || STATUS_META.active;
const RowStatusIcon = rowStatusMeta.icon;
const expanded = openRowIndex1 === index;
return (
@@ -2199,7 +2114,7 @@ const Clients1 = () => {
setDialogopen(true);
setSelectedtenid(row.tenantid);
setAppId(row.applocationid);
getAppPricing(row.applolcationid);
getAppPricing(row.applocationid);
}}
>
<IssuesCloseOutlined style={{ fontSize: 14 }} />
@@ -2280,7 +2195,7 @@ const Clients1 = () => {
setSelectedCustomer(row);
setSelectedtenid(row.tenantid);
setAppId(row.applocationid);
getAppPricing(row.applolcationid);
getAppPricing(row.applocationid);
setDialogopen(true);
}}
>
@@ -2295,21 +2210,13 @@ const Clients1 = () => {
<MobileField label="Tenant ID" value={`#${row.tenantid}`} />
<MobileField label="Contact" value={row.primarycontact || '—'} />
<MobileField label="Email" value={row.primaryemail || '—'} full />
<MobileField label="Address" full>
<Typography sx={{ fontSize: 13, fontWeight: 600, color: DT.textPrimary }}>
{row.address || '—'}
</Typography>
</MobileField>
<MobileField label="Status" value={row.status || '—'} />
</MobileFieldGrid>
{expanded && (
<Box sx={{ mt: 1.25, pt: 1.25, borderTop: `1px solid ${DT.divider}` }}>
<MobileFieldGrid>
<MobileField label="Contact Person" value={row.firstname || '—'} />
<MobileField label="City" value={row.city || '—'} />
<MobileField label="Postcode" value={row.postcode || '—'} />
<MobileField label="Latitude" value={row.latitude || '—'} />
<MobileField label="Longitude" value={row.longitude || '—'} />
<MobileField label="Delivery OTP Required" value={row.requiredeliveryotp ? 'Yes' : 'No'} />
</MobileFieldGrid>
</Box>
)}
@@ -2321,13 +2228,13 @@ const Clients1 = () => {
)}
{/* ============================================= || Pagination| ============================================= */}
{!searchword && tenantList?.length > 0 && (
{!searchword && filteredTenantList.length > 0 && (
<>
<Divider sx={{ borderColor: DT.borderSubtle }} />
<TablePagination
rowsPerPageOptions={[5, 10, 25, 50, 100]}
component="div"
count={value0 == 0 ? summaryData?.active : value0 == 1 ? summaryData?.pending : value0 == 2 ? summaryData?.inactive : 0}
count={filteredTenantList.length ?? 0}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}