updates on the ui changes and table

This commit is contained in:
2026-08-17 15:39:46 +05:30
parent a46212f291
commit 809d046323
38 changed files with 6403 additions and 7019 deletions

View File

@@ -40,6 +40,7 @@ import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import { PageShell, KpiGrid, FilterToolbar, DataCard } from 'components/nearle_components/PageLayout';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import { getallpricing, getTenants, fetchAppLocations } from 'pages/api/api';
import { createAdminPricing, updateAdminPricing, deleteAdminPricing, simulatePricing } from 'pages/api/doormileApi';
@@ -115,7 +116,10 @@ const ClientsPricing = () => {
[locations]
);
const locationOptions = useMemo(() => (locations || []).filter((l) => l.applocationid), [locations]);
const tenantSelectOptions = useMemo(() => (tenants || []).map((t) => ({ value: String(t.tenantid), label: t.tenantname || `Tenant #${t.tenantid}` })), [tenants]);
const tenantSelectOptions = useMemo(
() => (tenants || []).map((t) => ({ value: String(t.tenantid), label: t.tenantname || `Tenant #${t.tenantid}` })),
[tenants]
);
const locationSelectOptions = useMemo(
() => locationOptions.map((l) => ({ value: String(l.applocationid), label: l.locationname || `Zone #${l.applocationid}` })),
[locationOptions]
@@ -123,9 +127,7 @@ const ClientsPricing = () => {
const tenantName = (row) => row.tenantname || tenantMap.get(row.tenantid)?.tenantname || (row.tenantid ? `Tenant #${row.tenantid}` : '—');
const zoneName = (row) =>
row.applocation ||
locationMap.get(String(row.applocationid))?.locationname ||
(row.applocationid ? `Zone #${row.applocationid}` : '—');
row.applocation || locationMap.get(String(row.applocationid))?.locationname || (row.applocationid ? `Zone #${row.applocationid}` : '—');
// getallpricing() takes no zone arg (see api.js) — the LocationAutocomplete
// only ever changed the header subtitle text, not which rows were shown.
@@ -136,10 +138,7 @@ const ClientsPricing = () => {
// from) are two independently-serialized resources with no guarantee they
// agree on number-vs-string; a raw === silently zeroed out every row
// whenever they didn't match, making the table look empty for any zone.
const zonePricing = useMemo(
() => (appId ? pricing.filter((r) => String(r.applocationid) === String(appId)) : pricing),
[pricing, appId]
);
const zonePricing = useMemo(() => (appId ? pricing.filter((r) => String(r.applocationid) === String(appId)) : pricing), [pricing, appId]);
const rows = useMemo(() => {
if (!debouncedSearch) return zonePricing;
@@ -286,54 +285,56 @@ const ClientsPricing = () => {
<>
{(isLoading || savePricingMutation.isPending || deletePricingMutation.isPending) && <Loader />}
<PageHeader
title="Pricing"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<HStack gap={1.5} vAlign="center" wrap="wrap">
<LocationAutocomplete locaName={locaName} setAppId={setAppId} setLocoName={setLocoName} placeholder="Select Zone" />
<Button label="Quote Price" variant="secondary" icon={<MdCalculate size={16} />} onClick={() => setQuoteOpen(true)} />
<Button label="New Pricing Rule" variant="primary" icon={<MdAdd size={16} />} onClick={openCreateDialog} />
</HStack>
}
/>
<Grid columns={{ minWidth: 180 }} gap={3}>
{KPI_META.map((item) => {
const Icon = item.icon;
return <StatCard key={item.key} title={item.label} value={item.value} icon={<Icon size={20} />} color={item.color} />;
})}
</Grid>
<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">
<HStack gap={2} vAlign="center">
<AccentAvatar color={BRAND} size={32}>
<MdLocalOffer size={18} />
</AccentAvatar>
<VStack gap={0} padding={0}>
<Text type="label" weight="bold" color="secondary" style={{ letterSpacing: 0.6, textTransform: 'uppercase' }}>
Pricing Catalog
</Text>
<Text weight="bold">
{pricing.length} total · {rows.length} shown
</Text>
</VStack>
<PageShell>
<PageHeader
title="Pricing"
subtitle={`Live · ${locaName || 'All Zones'}`}
live
action={
<HStack gap={1.5} vAlign="center" wrap="wrap">
<LocationAutocomplete locaName={locaName} setAppId={setAppId} setLocoName={setLocoName} placeholder="Select Zone" />
<Button label="Quote Price" variant="secondary" icon={<MdCalculate size={16} />} onClick={() => setQuoteOpen(true)} />
<Button label="New Pricing Rule" variant="primary" icon={<MdAdd size={16} />} onClick={openCreateDialog} />
</HStack>
<div style={{ width: 300, maxWidth: '100%' }}>
<DebounceSearchBar
value={searchword}
onChange={setSearchword}
onDebouncedChange={setDebouncedSearch}
placeholder="Search pricing (ctrl+k)"
/>
</div>
</HStack>
</Card>
}
/>
<Card padding={0} elevation="none" style={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, overflow: 'hidden' }}>
<KpiGrid>
{KPI_META.map((item) => {
const Icon = item.icon;
return <StatCard key={item.key} title={item.label} value={item.value} icon={<Icon size={20} />} color={item.color} />;
})}
</KpiGrid>
<DataCard
toolbar={
<FilterToolbar max={2}>
<HStack gap={2} vAlign="center">
<AccentAvatar color={BRAND} size={32}>
<MdLocalOffer size={18} />
</AccentAvatar>
<VStack gap={0} padding={0}>
<Text type="label" weight="bold" color="secondary" style={{ letterSpacing: 0.6, textTransform: 'uppercase' }}>
Pricing Catalog
</Text>
<Text weight="bold">
{pricing.length} total · {rows.length} shown
</Text>
</VStack>
</HStack>
<HStack justify="end">
<VStack width={260} padding={0} gap={0}>
<DebounceSearchBar
value={searchword}
onChange={setSearchword}
onDebouncedChange={setDebouncedSearch}
placeholder="Search pricing (ctrl+k)"
/>
</VStack>
</HStack>
</FilterToolbar>
}
>
{isMobile ? (
rows.length === 0 && !isLoading ? (
<EmptyState
@@ -363,7 +364,14 @@ const ClientsPricing = () => {
</VStack>
</HStack>
<HStack gap={0.5}>
<IconButton label="Edit rule" tooltip="Edit rule" size="sm" variant="ghost" icon={<MdEdit size={14} />} onClick={() => openEditDialog(row)} />
<IconButton
label="Edit rule"
tooltip="Edit rule"
size="sm"
variant="ghost"
icon={<MdEdit size={14} />}
onClick={() => openEditDialog(row)}
/>
<IconButton
label="Delete rule"
tooltip="Delete rule"
@@ -491,7 +499,13 @@ const ClientsPricing = () => {
<TableCell>
<HStack gap={1} justify="end">
<Tooltip content="Edit rule">
<IconButton label="Edit rule" size="sm" variant="ghost" icon={<MdEdit size={16} />} onClick={() => openEditDialog(row)} />
<IconButton
label="Edit rule"
size="sm"
variant="ghost"
icon={<MdEdit size={16} />}
onClick={() => openEditDialog(row)}
/>
</Tooltip>
<Tooltip content="Delete rule">
<IconButton
@@ -511,8 +525,8 @@ const ClientsPricing = () => {
</Table>
</TableScroll>
)}
</Card>
</VStack>
</DataCard>
</PageShell>
<Dialog isOpen={dialogOpen} onOpenChange={(next) => !next && setDialogOpen(false)} width={720} purpose="form">
<Layout
@@ -529,30 +543,81 @@ const ClientsPricing = () => {
}
content={
<LayoutContent padding={4}>
<Grid columns={{ minWidth: 220 }} gap={3}>
<Selector label="Tenant" options={tenantSelectOptions} value={form.tenant} onChange={set('tenant')} placeholder="Choose tenant" hasSearch isRequired />
<Selector label="Zone" options={locationSelectOptions} value={form.location} onChange={set('location')} placeholder="Choose zone" hasSearch isOptional />
<Grid columns={{ minWidth: 220, repeat: 'fit' }} gap={3}>
<Selector
label="Tenant"
options={tenantSelectOptions}
value={form.tenant}
onChange={set('tenant')}
placeholder="Choose tenant"
hasSearch
isRequired
/>
<Selector
label="Zone"
options={locationSelectOptions}
value={form.location}
onChange={set('location')}
placeholder="Choose zone"
hasSearch
isOptional
/>
<Selector label="Vehicle Type" options={VEHICLE_TYPE_OPTIONS} value={form.vehicletype} onChange={set('vehicletype')} />
<Selector label="Status" options={STATUS_OPTIONS} value={form.status} onChange={set('status')} />
<NumberInput label="Base Price" value={form.baseprice === '' ? undefined : Number(form.baseprice)} onChange={(v) => set('baseprice')(v ?? '')} isRequired />
<NumberInput label="Base Weight (kg)" value={form.baseweight === '' ? undefined : Number(form.baseweight)} onChange={(v) => set('baseweight')(v ?? '')} isOptional />
<NumberInput label="Price / KG" value={form.priceperkg === '' ? undefined : Number(form.priceperkg)} onChange={(v) => set('priceperkg')(v ?? '')} isOptional />
<NumberInput label="Base Distance (km)" value={form.basedistance === '' ? undefined : Number(form.basedistance)} onChange={(v) => set('basedistance')(v ?? '')} isOptional />
<NumberInput label="Price / KM" value={form.priceperkm === '' ? undefined : Number(form.priceperkm)} onChange={(v) => set('priceperkm')(v ?? '')} isRequired />
<NumberInput
label="Base Price"
value={form.baseprice === '' ? undefined : Number(form.baseprice)}
onChange={(v) => set('baseprice')(v ?? '')}
isRequired
/>
<NumberInput
label="Base Weight (kg)"
value={form.baseweight === '' ? undefined : Number(form.baseweight)}
onChange={(v) => set('baseweight')(v ?? '')}
isOptional
/>
<NumberInput
label="Price / KG"
value={form.priceperkg === '' ? undefined : Number(form.priceperkg)}
onChange={(v) => set('priceperkg')(v ?? '')}
isOptional
/>
<NumberInput
label="Base Distance (km)"
value={form.basedistance === '' ? undefined : Number(form.basedistance)}
onChange={(v) => set('basedistance')(v ?? '')}
isOptional
/>
<NumberInput
label="Price / KM"
value={form.priceperkm === '' ? undefined : Number(form.priceperkm)}
onChange={(v) => set('priceperkm')(v ?? '')}
isRequired
/>
<NumberInput
label="Handling Charges"
value={form.handlingcharges === '' ? undefined : Number(form.handlingcharges)}
onChange={(v) => set('handlingcharges')(v ?? '')}
isOptional
/>
<NumberInput label="Priority" value={form.priority === '' ? undefined : Number(form.priority)} onChange={(v) => set('priority')(v ?? '')} isOptional />
<NumberInput
label="Priority"
value={form.priority === '' ? undefined : Number(form.priority)}
onChange={(v) => set('priority')(v ?? '')}
isOptional
/>
</Grid>
</LayoutContent>
}
footer={
<LayoutFooter hasDivider>
<HStack gap={2} justify="end">
<Button label="Cancel" variant="secondary" onClick={() => setDialogOpen(false)} isDisabled={savePricingMutation.isPending} />
<Button
label="Cancel"
variant="secondary"
onClick={() => setDialogOpen(false)}
isDisabled={savePricingMutation.isPending}
/>
<Button
label={form.pricingid ? 'Save Changes' : 'Create'}
variant="primary"
@@ -598,17 +663,46 @@ const ClientsPricing = () => {
content={
<LayoutContent padding={4}>
<VStack gap={3} padding={0}>
<Selector label="Tenant" options={tenantSelectOptions} value={quoteForm.tenant} onChange={setQuote('tenant')} placeholder="Choose tenant" hasSearch isRequired />
<Selector label="Zone" options={locationSelectOptions} value={quoteForm.location} onChange={setQuote('location')} placeholder="Choose zone" hasSearch isOptional />
<Grid columns={{ minWidth: 160 }} gap={3}>
<Selector label="Vehicle Type" options={VEHICLE_TYPE_OPTIONS} value={quoteForm.vehicletype} onChange={setQuote('vehicletype')} />
<NumberInput label="Weight (kg)" value={quoteForm.weight === '' ? undefined : Number(quoteForm.weight)} onChange={(v) => setQuote('weight')(v ?? '')} isOptional />
<NumberInput label="Distance (km)" value={quoteForm.distance === '' ? undefined : Number(quoteForm.distance)} onChange={(v) => setQuote('distance')(v ?? '')} isRequired />
<Selector
label="Tenant"
options={tenantSelectOptions}
value={quoteForm.tenant}
onChange={setQuote('tenant')}
placeholder="Choose tenant"
hasSearch
isRequired
/>
<Selector
label="Zone"
options={locationSelectOptions}
value={quoteForm.location}
onChange={setQuote('location')}
placeholder="Choose zone"
hasSearch
isOptional
/>
<Grid columns={{ minWidth: 160, repeat: 'fit' }} gap={3}>
<Selector
label="Vehicle Type"
options={VEHICLE_TYPE_OPTIONS}
value={quoteForm.vehicletype}
onChange={setQuote('vehicletype')}
/>
<NumberInput
label="Weight (kg)"
value={quoteForm.weight === '' ? undefined : Number(quoteForm.weight)}
onChange={(v) => setQuote('weight')(v ?? '')}
isOptional
/>
<NumberInput
label="Distance (km)"
value={quoteForm.distance === '' ? undefined : Number(quoteForm.distance)}
onChange={(v) => setQuote('distance')(v ?? '')}
isRequired
/>
</Grid>
{quoteMutation.isPending && (
<Text color="secondary">Calculating…</Text>
)}
{quoteMutation.isPending && <Text color="secondary">Calculating…</Text>}
{quoteMutation.data && (
<Card padding={3} elevation="none" style={{ backgroundColor: DT.surfaceAlt }}>