updatess on the ui changs
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo, useState, useEffect } from 'react';
|
||||
import {
|
||||
MdLocalOffer,
|
||||
MdGroups,
|
||||
@@ -13,25 +13,46 @@ import {
|
||||
MdAdd,
|
||||
MdEdit,
|
||||
MdDeleteOutline,
|
||||
MdCalculate
|
||||
MdCalculate,
|
||||
MdArrowUpward,
|
||||
MdArrowDownward,
|
||||
MdUnfoldMore
|
||||
} from 'react-icons/md';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
// TanStack Table v9 — same engine orders.js/riders.js/Tenants.js use, so
|
||||
// sorting/resizing/pagination behave identically across the console.
|
||||
import {
|
||||
useTable,
|
||||
flexRender,
|
||||
tableFeatures,
|
||||
rowSortingFeature,
|
||||
createSortedRowModel,
|
||||
sortFns,
|
||||
columnSizingFeature,
|
||||
columnResizingFeature,
|
||||
rowPaginationFeature,
|
||||
createPaginatedRowModel
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
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 { Center } from '@astryxdesign/core/Center';
|
||||
import { Text } from '@astryxdesign/core/Text';
|
||||
import { Button } from '@astryxdesign/core/Button';
|
||||
import { IconButton } from '@astryxdesign/core/IconButton';
|
||||
import { Tooltip } from '@astryxdesign/core/Tooltip';
|
||||
import { Badge } from '@astryxdesign/core/Badge';
|
||||
import { Divider } from '@astryxdesign/core/Divider';
|
||||
import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog';
|
||||
import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
|
||||
import { NumberInput } from '@astryxdesign/core/NumberInput';
|
||||
import { Selector } from '@astryxdesign/core/Selector';
|
||||
import { EmptyState } from '@astryxdesign/core/EmptyState';
|
||||
import { Pagination } from '@astryxdesign/core/Pagination';
|
||||
import { Table, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '@astryxdesign/core/Table';
|
||||
|
||||
import Loader from 'components/Loader';
|
||||
@@ -71,6 +92,29 @@ const MetricPill = ({ label }) => (
|
||||
|
||||
const CategoryChip = ({ icon, label }) => <Badge variant="neutral" icon={icon} label={label} />;
|
||||
|
||||
// TanStack Table v9 registers features explicitly — only sorting/resizing/
|
||||
// pagination ship here (no row-selection or column-visibility, since this
|
||||
// table has no bulk-select or column-hide flow).
|
||||
const PRICING_TABLE_FEATURES = tableFeatures({
|
||||
rowSortingFeature,
|
||||
sortedRowModel: createSortedRowModel(),
|
||||
sortFns,
|
||||
columnSizingFeature,
|
||||
columnResizingFeature,
|
||||
rowPaginationFeature,
|
||||
paginatedRowModel: createPaginatedRowModel()
|
||||
});
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
||||
|
||||
// Uppercase micro-label for the table's column headings — same convention
|
||||
// orders.js's/riders.js's/Tenants.js's tables use.
|
||||
const SectionLabel = ({ children }) => (
|
||||
<Text type="label" weight="bold" color="secondary" style={{ textTransform: 'uppercase', letterSpacing: 0.4 }}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const emptyForm = {
|
||||
pricingid: null,
|
||||
tenant: '',
|
||||
@@ -100,6 +144,11 @@ const ClientsPricing = () => {
|
||||
const [form, setForm] = useState(emptyForm);
|
||||
const [quoteOpen, setQuoteOpen] = useState(false);
|
||||
const [quoteForm, setQuoteForm] = useState({ tenant: '', location: '', vehicletype: 'Bike', weight: '', distance: '' });
|
||||
// Table sorting/resizing/pagination state — mirrors orders.js's/riders.js's
|
||||
// slices.
|
||||
const [sorting, setSorting] = useState([]);
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||
const [columnSizing, setColumnSizing] = useState({});
|
||||
|
||||
const { data: pricing = [], isLoading } = useQuery({
|
||||
queryKey: ['getallpricing', appId],
|
||||
@@ -151,6 +200,12 @@ const ClientsPricing = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [zonePricing, debouncedSearch, tenantMap, locationMap]);
|
||||
|
||||
// A zone/search change re-scopes the list, so page 1 is the only page that
|
||||
// still means anything — same reset orders.js/riders.js do.
|
||||
useEffect(() => {
|
||||
setPagination((p) => ({ ...p, pageIndex: 0 }));
|
||||
}, [appId, debouncedSearch]);
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const total = zonePricing.length;
|
||||
const tenantsPriced = new Set(zonePricing.map((r) => r.tenantid).filter(Boolean)).size;
|
||||
@@ -281,6 +336,166 @@ const ClientsPricing = () => {
|
||||
const set = (key) => (value) => setForm((f) => ({ ...f, [key]: value }));
|
||||
const setQuote = (key) => (value) => setQuoteForm((f) => ({ ...f, [key]: value }));
|
||||
|
||||
// The column model — everything a column IS declared once here instead of
|
||||
// a hardcoded header row + hand-written body row, same shape as
|
||||
// orders.js's/riders.js's/Tenants.js's `columns`. Tenant/Zone keep the
|
||||
// widths from this file's earlier column-squeeze fix (Tenant needed real
|
||||
// width so Zone's text didn't get crushed under `table-layout: auto`).
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'serial',
|
||||
size: 44,
|
||||
minSize: 36,
|
||||
maxSize: 52,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
meta: { label: '#' },
|
||||
header: () => <SectionLabel>#</SectionLabel>,
|
||||
cell: ({ table: t, row }) => (
|
||||
<Text type="supporting" weight="bold" color="secondary" hasTabularNumbers>
|
||||
{pagination.pageIndex * pagination.pageSize + t.getRowModel().rows.indexOf(row) + 1}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'tenant',
|
||||
accessorFn: (row) => tenantName(row),
|
||||
size: 260,
|
||||
minSize: 240,
|
||||
meta: { label: 'Tenant' },
|
||||
header: () => <SectionLabel>Tenant</SectionLabel>,
|
||||
cell: ({ getValue, row }) => (
|
||||
<HStack gap={2} vAlign="center">
|
||||
<AccentAvatar color={BRAND} size={36}>
|
||||
<MdGroups size={18} />
|
||||
</AccentAvatar>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
|
||||
{getValue()}
|
||||
</Text>
|
||||
<Text type="supporting" color="secondary">
|
||||
ID #{row.original.pricingid}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'zone',
|
||||
accessorFn: (row) => zoneName(row),
|
||||
size: 180,
|
||||
minSize: 160,
|
||||
meta: { label: 'Zone' },
|
||||
header: () => <SectionLabel>Zone</SectionLabel>,
|
||||
cell: ({ getValue }) => <CategoryChip icon={<MdPlace size={12} />} label={getValue()} />
|
||||
},
|
||||
{
|
||||
id: 'vehicle',
|
||||
accessorKey: 'vehicletype',
|
||||
size: 120,
|
||||
minSize: 100,
|
||||
meta: { label: 'Vehicle' },
|
||||
header: () => <SectionLabel>Vehicle</SectionLabel>,
|
||||
cell: ({ getValue }) => <CategoryChip icon={<MdSpeed size={12} />} label={getValue() || '—'} />
|
||||
},
|
||||
{
|
||||
id: 'baseprice',
|
||||
accessorKey: 'baseprice',
|
||||
size: 130,
|
||||
minSize: 120,
|
||||
meta: { label: 'Base Price' },
|
||||
header: () => <SectionLabel>Base Price</SectionLabel>,
|
||||
cell: ({ getValue }) => <MetricPill label={formatRupees(getValue())} />
|
||||
},
|
||||
{
|
||||
id: 'priceperkm',
|
||||
accessorKey: 'priceperkm',
|
||||
size: 130,
|
||||
minSize: 120,
|
||||
meta: { label: 'Price / KM' },
|
||||
header: () => <SectionLabel>Price / KM</SectionLabel>,
|
||||
cell: ({ getValue }) => <MetricPill label={formatRupees(getValue())} />
|
||||
},
|
||||
{
|
||||
id: 'priceperkg',
|
||||
accessorKey: 'priceperkg',
|
||||
size: 130,
|
||||
minSize: 120,
|
||||
meta: { label: 'Price / KG' },
|
||||
header: () => <SectionLabel>Price / KG</SectionLabel>,
|
||||
cell: ({ getValue }) => <MetricPill label={formatRupees(getValue())} />
|
||||
},
|
||||
{
|
||||
id: 'handling',
|
||||
accessorKey: 'handlingcharges',
|
||||
size: 120,
|
||||
minSize: 100,
|
||||
meta: { label: 'Handling' },
|
||||
header: () => <SectionLabel>Handling</SectionLabel>,
|
||||
cell: ({ getValue }) => <MetricPill label={formatRupees(getValue())} />
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
accessorKey: 'status',
|
||||
size: 120,
|
||||
minSize: 100,
|
||||
meta: { label: 'Status' },
|
||||
header: () => <SectionLabel>Status</SectionLabel>,
|
||||
cell: ({ getValue }) => <CategoryChip icon={<MdPriceCheck size={12} />} label={getValue() || '—'} />
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
size: 110,
|
||||
minSize: 100,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
meta: { label: 'Actions' },
|
||||
header: () => <SectionLabel>Actions</SectionLabel>,
|
||||
cell: ({ row }) => (
|
||||
<HStack gap={1} justify="end">
|
||||
<Tooltip content="Edit rule">
|
||||
<IconButton
|
||||
label="Edit rule"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon={<MdEdit size={16} />}
|
||||
onClick={() => openEditDialog(row.original)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content="Delete rule">
|
||||
<IconButton
|
||||
label="Delete rule"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
icon={<MdDeleteOutline size={16} />}
|
||||
onClick={() => handleDelete(row.original)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[pagination.pageIndex, pagination.pageSize, tenantMap, locationMap]
|
||||
);
|
||||
|
||||
const table = useTable({
|
||||
features: PRICING_TABLE_FEATURES,
|
||||
// `rows` is already useMemo'd above (stable reference) — required for
|
||||
// pagination clicks to stick, see riders.js's fix for the same bug.
|
||||
data: rows,
|
||||
columns,
|
||||
getRowId: (row, index) => String(row.pricingid ?? `row-${index}`),
|
||||
columnResizeMode: 'onChange',
|
||||
state: { sorting, pagination, columnSizing },
|
||||
onSortingChange: setSorting,
|
||||
onPaginationChange: setPagination,
|
||||
onColumnSizingChange: setColumnSizing
|
||||
});
|
||||
|
||||
const colCount = columns.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isLoading || savePricingMutation.isPending || deletePricingMutation.isPending) && <Loader />}
|
||||
@@ -344,98 +559,181 @@ const ClientsPricing = () => {
|
||||
/>
|
||||
) : (
|
||||
<MobileCardList scroll>
|
||||
{rows.map((row, index) => (
|
||||
<MobileCard
|
||||
key={row.pricingid || `${row.tenantid}-${index}`}
|
||||
accent={BRAND}
|
||||
header={
|
||||
<HStack justify="between" vAlign="center" gap={1}>
|
||||
<HStack gap={2} vAlign="center" minWidth={0}>
|
||||
<AccentAvatar color={BRAND} size={36}>
|
||||
<MdGroups size={18} />
|
||||
</AccentAvatar>
|
||||
<VStack gap={0} padding={0} minWidth={0}>
|
||||
<Text weight="bold" maxLines={1}>
|
||||
{tenantName(row)}
|
||||
</Text>
|
||||
<Text type="supporting" color="secondary">
|
||||
ID #{row.pricingid}
|
||||
</Text>
|
||||
</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="Delete rule"
|
||||
tooltip="Delete rule"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
icon={<MdDeleteOutline size={14} />}
|
||||
onClick={() => handleDelete(row)}
|
||||
/>
|
||||
{table.getRowModel().rows.map((tableRow) => {
|
||||
const row = tableRow.original;
|
||||
return (
|
||||
<MobileCard
|
||||
key={tableRow.id}
|
||||
accent={BRAND}
|
||||
header={
|
||||
<HStack justify="between" vAlign="center" gap={1}>
|
||||
<HStack gap={2} vAlign="center" minWidth={0}>
|
||||
<AccentAvatar color={BRAND} size={36}>
|
||||
<MdGroups size={18} />
|
||||
</AccentAvatar>
|
||||
<VStack gap={0} padding={0} minWidth={0}>
|
||||
<Text weight="bold" maxLines={1}>
|
||||
{tenantName(row)}
|
||||
</Text>
|
||||
<Text type="supporting" color="secondary">
|
||||
ID #{row.pricingid}
|
||||
</Text>
|
||||
</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="Delete rule"
|
||||
tooltip="Delete rule"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
icon={<MdDeleteOutline size={14} />}
|
||||
onClick={() => handleDelete(row)}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
}
|
||||
>
|
||||
<HStack gap={1} wrap="wrap" style={{ marginTop: 8 }}>
|
||||
<Badge variant="green" icon={<MdPlace size={12} />} label={zoneName(row)} />
|
||||
<Badge variant="blue" icon={<MdSpeed size={12} />} label={row.vehicletype || '—'} />
|
||||
</HStack>
|
||||
}
|
||||
>
|
||||
<HStack gap={1} wrap="wrap" style={{ marginTop: 8 }}>
|
||||
<Badge variant="green" icon={<MdPlace size={12} />} label={zoneName(row)} />
|
||||
<Badge variant="blue" icon={<MdSpeed size={12} />} label={row.vehicletype || '—'} />
|
||||
</HStack>
|
||||
|
||||
<MobileFieldGrid>
|
||||
<MobileField label="Base Price">
|
||||
<MetricPill label={formatRupees(row.baseprice)} />
|
||||
</MobileField>
|
||||
<MobileField label="Price / KM">
|
||||
<MetricPill label={formatRupees(row.priceperkm)} />
|
||||
</MobileField>
|
||||
<MobileField label="Price / KG">
|
||||
<MetricPill label={formatRupees(row.priceperkg)} />
|
||||
</MobileField>
|
||||
<MobileField label="Handling">
|
||||
<MetricPill label={formatRupees(row.handlingcharges)} />
|
||||
</MobileField>
|
||||
<MobileField label="Status">
|
||||
<HStack gap={1} vAlign="center">
|
||||
<MdReceiptLong size={14} color={DT.textMuted} />
|
||||
<Text weight="bold">{row.status || '—'}</Text>
|
||||
</HStack>
|
||||
</MobileField>
|
||||
</MobileFieldGrid>
|
||||
</MobileCard>
|
||||
))}
|
||||
<MobileFieldGrid>
|
||||
<MobileField label="Base Price">
|
||||
<MetricPill label={formatRupees(row.baseprice)} />
|
||||
</MobileField>
|
||||
<MobileField label="Price / KM">
|
||||
<MetricPill label={formatRupees(row.priceperkm)} />
|
||||
</MobileField>
|
||||
<MobileField label="Price / KG">
|
||||
<MetricPill label={formatRupees(row.priceperkg)} />
|
||||
</MobileField>
|
||||
<MobileField label="Handling">
|
||||
<MetricPill label={formatRupees(row.handlingcharges)} />
|
||||
</MobileField>
|
||||
<MobileField label="Status">
|
||||
<HStack gap={1} vAlign="center">
|
||||
<MdReceiptLong size={14} color={DT.textMuted} />
|
||||
<Text weight="bold">{row.status || '—'}</Text>
|
||||
</HStack>
|
||||
</MobileField>
|
||||
</MobileFieldGrid>
|
||||
</MobileCard>
|
||||
);
|
||||
})}
|
||||
</MobileCardList>
|
||||
)
|
||||
) : (
|
||||
<TableScroll minWidth={1260}>
|
||||
<Table density="balanced" dividers="rows" hasHover>
|
||||
<TableScroll minWidth={table.getTotalSize()}>
|
||||
{/* table-layout: fixed makes the declared column widths
|
||||
authoritative — same reasoning as orders.js's/riders.js's/
|
||||
Tenants.js's tables. Header and body are both rendered FROM
|
||||
the column model, so they cannot drift apart. */}
|
||||
<Table
|
||||
density="balanced"
|
||||
dividers="rows"
|
||||
hasHover
|
||||
verticalAlign="middle"
|
||||
aria-label="Pricing"
|
||||
style={{ tableLayout: 'fixed', width: '100%', minWidth: table.getTotalSize() }}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell style={{ minWidth: 40, width: 40, maxWidth: 40 }}>#</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 240 }}>Tenant</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 160 }}>Zone</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 100 }}>Vehicle</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 120 }}>Base Price</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 120 }}>Price / KM</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 120 }}>Price / KG</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 100 }}>Handling</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 100 }}>Status</TableHeaderCell>
|
||||
<TableHeaderCell style={{ minWidth: 100 }}>Actions</TableHeaderCell>
|
||||
</TableRow>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} isHeaderRow>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const align = header.column.columnDef.meta?.align;
|
||||
const canSort = header.column.getCanSort();
|
||||
const sorted = header.column.getIsSorted();
|
||||
return (
|
||||
<TableHeaderCell
|
||||
key={header.id}
|
||||
scope="col"
|
||||
aria-sort={sorted === 'asc' ? 'ascending' : sorted === 'desc' ? 'descending' : canSort ? 'none' : undefined}
|
||||
style={{
|
||||
width: header.getSize(),
|
||||
minWidth: header.column.columnDef.minSize,
|
||||
maxWidth: header.column.columnDef.maxSize || header.getSize(),
|
||||
textAlign: align,
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<HStack
|
||||
gap={1}
|
||||
vAlign="center"
|
||||
justify={align === 'right' ? 'end' : align === 'center' ? 'center' : 'start'}
|
||||
role={canSort ? 'button' : undefined}
|
||||
tabIndex={canSort ? 0 : undefined}
|
||||
onClick={canSort ? header.column.getToggleSortingHandler() : undefined}
|
||||
onKeyDown={
|
||||
canSort
|
||||
? (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
header.column.toggleSorting();
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
style={canSort ? { cursor: 'pointer', userSelect: 'none' } : undefined}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{canSort &&
|
||||
(sorted === 'asc' ? (
|
||||
<MdArrowUpward size={12} color={DT.textPrimary} style={{ flexShrink: 0 }} />
|
||||
) : sorted === 'desc' ? (
|
||||
<MdArrowDownward size={12} color={DT.textPrimary} style={{ flexShrink: 0 }} />
|
||||
) : (
|
||||
<MdUnfoldMore size={12} color={DT.textMuted} style={{ flexShrink: 0, opacity: 0.6 }} />
|
||||
))}
|
||||
</HStack>
|
||||
|
||||
{header.column.getCanResize() && (
|
||||
<Center
|
||||
aria-hidden="true"
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
height: '100%',
|
||||
width: 10,
|
||||
cursor: 'col-resize',
|
||||
userSelect: 'none',
|
||||
touchAction: 'none'
|
||||
}}
|
||||
>
|
||||
<Center
|
||||
style={{
|
||||
width: 2,
|
||||
height: '60%',
|
||||
borderRadius: 'var(--radius-element)',
|
||||
backgroundColor: header.column.getIsResizing() ? DT.brand : DT.borderSubtle,
|
||||
transition: 'background-color 120ms ease'
|
||||
}}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</TableHeaderCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader rows={5} columns={10} />}
|
||||
{isLoading && <TableLoader rows={5} columns={colCount} />}
|
||||
{rows.length === 0 && !isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={10}>
|
||||
<TableCell colSpan={colCount}>
|
||||
<EmptyState
|
||||
title="No pricing to show"
|
||||
description={searchword ? 'Try a different keyword.' : 'Create a pricing rule to get started.'}
|
||||
@@ -444,80 +742,19 @@ const ClientsPricing = () => {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
rows.map((row, index) => (
|
||||
<TableRow key={row.pricingid || `${row.tenantid}-${index}`}>
|
||||
<TableCell>
|
||||
<Text type="supporting" weight="bold" color="secondary">
|
||||
{index + 1}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<HStack gap={2} vAlign="center">
|
||||
<AccentAvatar color={BRAND} size={36}>
|
||||
<MdGroups size={18} />
|
||||
</AccentAvatar>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
|
||||
{tenantName(row)}
|
||||
</Text>
|
||||
<Text type="supporting" color="secondary">
|
||||
ID #{row.pricingid}
|
||||
</Text>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<CategoryChip icon={<MdPlace size={12} />} label={zoneName(row)} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<CategoryChip icon={<MdSpeed size={12} />} label={row.vehicletype || '—'} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<MetricPill label={formatRupees(row.baseprice)} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<MetricPill label={formatRupees(row.priceperkm)} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<MetricPill label={formatRupees(row.priceperkg)} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<MetricPill label={formatRupees(row.handlingcharges)} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<CategoryChip icon={<MdPriceCheck size={12} />} label={row.status || '—'} />
|
||||
</TableCell>
|
||||
|
||||
<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)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content="Delete rule">
|
||||
<IconButton
|
||||
label="Delete rule"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
icon={<MdDeleteOutline size={16} />}
|
||||
onClick={() => handleDelete(row)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
</TableCell>
|
||||
table.getRowModel().rows.map((tableRow) => (
|
||||
<TableRow key={tableRow.id}>
|
||||
{tableRow.getAllCells().map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
style={{
|
||||
textAlign: cell.column.columnDef.meta?.align,
|
||||
maxWidth: cell.column.columnDef.maxSize || cell.column.getSize()
|
||||
}}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
@@ -525,6 +762,30 @@ const ClientsPricing = () => {
|
||||
</Table>
|
||||
</TableScroll>
|
||||
)}
|
||||
|
||||
{rows.length > 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<HStack justify="between" vAlign="center" gap={2} wrap="wrap" paddingInline={4} paddingBlock={2}>
|
||||
<Text type="supporting" weight="bold" color="secondary" hasTabularNumbers>
|
||||
{`${pagination.pageIndex * pagination.pageSize + 1}–${Math.min(
|
||||
(pagination.pageIndex + 1) * pagination.pageSize,
|
||||
rows.length
|
||||
)} of ${rows.length} pricing rule${rows.length === 1 ? '' : 's'}`}
|
||||
</Text>
|
||||
<Pagination
|
||||
page={pagination.pageIndex + 1}
|
||||
onChange={(p) => table.setPageIndex(p - 1)}
|
||||
totalItems={rows.length}
|
||||
pageSize={pagination.pageSize}
|
||||
pageSizeOptions={PAGE_SIZE_OPTIONS}
|
||||
onPageSizeChange={(size) => table.setPageSize(size)}
|
||||
size="sm"
|
||||
label="Pricing pagination"
|
||||
/>
|
||||
</HStack>
|
||||
</>
|
||||
)}
|
||||
</DataCard>
|
||||
</PageShell>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user