updatess on the ui changs
This commit is contained in:
@@ -2,13 +2,29 @@ import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import dayjs from 'dayjs';
|
||||
import { MdDirectionsBike, MdArrowBack } from 'react-icons/md';
|
||||
import { MdDirectionsBike, MdArrowBack, MdArrowUpward, MdArrowDownward, MdUnfoldMore } from 'react-icons/md';
|
||||
import { FaMapLocationDot } from 'react-icons/fa6';
|
||||
import { HiOutlineArrowLeft } from 'react-icons/hi';
|
||||
|
||||
// TanStack Table v9 — same engine orders.js/riders.js/Tenants.js use, so
|
||||
// sorting/resizing behave identically across the console's list pages.
|
||||
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 { Center } from '@astryxdesign/core/Center';
|
||||
import { Text } from '@astryxdesign/core/Text';
|
||||
import { Button } from '@astryxdesign/core/Button';
|
||||
import { IconButton } from '@astryxdesign/core/IconButton';
|
||||
@@ -28,7 +44,7 @@ import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider }
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import Loader from 'components/Loader';
|
||||
import { useIsNarrowViewport } from 'hooks/useIsNarrowViewport';
|
||||
import { STATUS } from 'themes/dt/tokens';
|
||||
import { DT, STATUS } from 'themes/dt/tokens';
|
||||
import { TableScroll } from 'themes/dt/primitives';
|
||||
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
@@ -36,125 +52,154 @@ dayjs.extend(utc);
|
||||
|
||||
const orderTypeBadge = (type) => (type === 'Economy' ? 'green' : type === 'Risky' ? 'error' : 'blue');
|
||||
|
||||
// TanStack Table v9 registers features explicitly — sorting/resizing ship
|
||||
// here like every other converted page. rowPaginationFeature is registered
|
||||
// too (table.getRowModel() always resolves through getPaginatedRowModel()
|
||||
// internally), but this page deliberately never surfaces pagination UI —
|
||||
// see the `pagination` state below for why.
|
||||
const ORDERS_PREVIEW_TABLE_FEATURES = tableFeatures({
|
||||
rowSortingFeature,
|
||||
sortedRowModel: createSortedRowModel(),
|
||||
sortFns,
|
||||
columnSizingFeature,
|
||||
columnResizingFeature,
|
||||
rowPaginationFeature,
|
||||
paginatedRowModel: createPaginatedRowModel()
|
||||
});
|
||||
|
||||
// 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>
|
||||
);
|
||||
|
||||
// Mobile-only rendering of the optimised-orders preview. Mirrors the exact same
|
||||
// fields, chips and tooltips as the desktop table rows — no behaviour added,
|
||||
// purely a card layout for phones. Renders for both aiMode 1 and normal mode;
|
||||
// the Zone / Rider fields are gated on aiMode just like the table columns.
|
||||
const MobileOrdersList = ({ list, aiMode }) => {
|
||||
if (!list || list.length === 0) {
|
||||
// Reads the same sorted `table` row model the desktop table does, so a
|
||||
// sort applied before switching to a narrow viewport isn't lost.
|
||||
const MobileOrdersList = ({ table, aiMode }) => {
|
||||
const rows = table.getRowModel().rows;
|
||||
if (!rows || rows.length === 0) {
|
||||
return <EmptyState title="No orders to preview" icon={<MdDirectionsBike size={28} />} />;
|
||||
}
|
||||
return (
|
||||
<MobileCardList scroll>
|
||||
{list.map((val, index) => (
|
||||
<MobileCard
|
||||
key={index}
|
||||
header={
|
||||
<HStack justify="between" vAlign="center" gap={1}>
|
||||
<HStack gap={1} vAlign="center" minWidth={0}>
|
||||
<Text weight="bold" color="secondary">
|
||||
#{index + 1}
|
||||
</Text>
|
||||
{aiMode == 1 && <Badge variant="blue" label={val.zone_name} />}
|
||||
{rows.map((tableRow, index) => {
|
||||
const val = tableRow.original;
|
||||
return (
|
||||
<MobileCard
|
||||
key={tableRow.id}
|
||||
header={
|
||||
<HStack justify="between" vAlign="center" gap={1}>
|
||||
<HStack gap={1} vAlign="center" minWidth={0}>
|
||||
<Text weight="bold" color="secondary">
|
||||
#{index + 1}
|
||||
</Text>
|
||||
{aiMode == 1 && <Badge variant="blue" label={val.zone_name} />}
|
||||
</HStack>
|
||||
<Badge variant={orderTypeBadge(val.ordertype)} label={val.ordertype} />
|
||||
</HStack>
|
||||
<Badge variant={orderTypeBadge(val.ordertype)} label={val.ordertype} />
|
||||
</HStack>
|
||||
}
|
||||
>
|
||||
<MobileFieldGrid>
|
||||
<MobileField label="Tenant" full>
|
||||
<Tooltip content={val.tenantaddress}>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text maxLines={1}>{val.tenantname}</Text>
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.tenantsuburb}
|
||||
</Text>
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.applocation}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label="Order Location" full>
|
||||
<Tooltip content={val.locationaddress}>
|
||||
<Text maxLines={1}>{`${val.locationname}-(${val.locationsuburb})`}</Text>
|
||||
</Tooltip>
|
||||
<Tooltip content="Order Id">
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.orderid}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<HStack gap={3}>
|
||||
<Tooltip content="Ordered date">
|
||||
}
|
||||
>
|
||||
<MobileFieldGrid>
|
||||
<MobileField label="Tenant" full>
|
||||
<Tooltip content={val.tenantaddress}>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}</Text>
|
||||
<Text maxLines={1}>{val.tenantname}</Text>
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.tenantsuburb}
|
||||
</Text>
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.applocation}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
-
|
||||
<Tooltip content="Delivery date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label="Pickup">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{val.pickupcustomer}</Text>
|
||||
<Text type="supporting">{val.pickupcontactno}</Text>
|
||||
<Tooltip content={val.pickupaddress}>
|
||||
<Text type="supporting">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label="Delivery">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{val.deliverycustomer}</Text>
|
||||
<Text type="supporting">{val.deliverycontactno}</Text>
|
||||
<Tooltip content={val.deliveryaddress}>
|
||||
<Text type="supporting">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
|
||||
{val.ordernotes ? <MobileField label="Notes" value={val.ordernotes} full /> : null}
|
||||
|
||||
{aiMode == 1 && (
|
||||
<MobileField label="Rider" full>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>{val.username}</Text>
|
||||
<Text>ID : {val.userid}</Text>
|
||||
</MobileField>
|
||||
)}
|
||||
|
||||
<MobileField label="Profit">
|
||||
<VStack gap={1} padding={0}>
|
||||
<Tooltip content="Charges">
|
||||
<Badge variant="error" label={`₹ ${val.deliverycharge.toFixed(2)} `} />
|
||||
<MobileField label="Order Location" full>
|
||||
<Tooltip content={val.locationaddress}>
|
||||
<Text maxLines={1}>{`${val.locationname}-(${val.locationsuburb})`}</Text>
|
||||
</Tooltip>
|
||||
<Tooltip content="Amount">
|
||||
<Badge variant="green" label={`₹ ${val.deliveryamt.toFixed(2)} `} />
|
||||
<Tooltip content="Order Id">
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.orderid}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
<HStack gap={3}>
|
||||
<Tooltip content="Ordered date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
-
|
||||
<Tooltip content="Delivery date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label="KMS">
|
||||
<VStack gap={1} padding={0}>
|
||||
<Tooltip content="KMS">
|
||||
<Badge variant="error" label={`${Number(val.kms || 0).toFixed(2)} km`} />
|
||||
</Tooltip>
|
||||
<Tooltip content="Cumulative Kms">
|
||||
<Badge variant="green" label={`${val.cumulativekms} km`} />
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
</MobileFieldGrid>
|
||||
</MobileCard>
|
||||
))}
|
||||
<MobileField label="Pickup">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{val.pickupcustomer}</Text>
|
||||
<Text type="supporting">{val.pickupcontactno}</Text>
|
||||
<Tooltip content={val.pickupaddress}>
|
||||
<Text type="supporting">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label="Delivery">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{val.deliverycustomer}</Text>
|
||||
<Text type="supporting">{val.deliverycontactno}</Text>
|
||||
<Tooltip content={val.deliveryaddress}>
|
||||
<Text type="supporting">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
|
||||
{val.ordernotes ? <MobileField label="Notes" value={val.ordernotes} full /> : null}
|
||||
|
||||
{aiMode == 1 && (
|
||||
<MobileField label="Rider" full>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>{val.username}</Text>
|
||||
<Text>ID : {val.userid}</Text>
|
||||
</MobileField>
|
||||
)}
|
||||
|
||||
<MobileField label="Profit">
|
||||
<VStack gap={1} padding={0}>
|
||||
<Tooltip content="Charges">
|
||||
<Badge variant="error" label={`₹ ${val.deliverycharge.toFixed(2)} `} />
|
||||
</Tooltip>
|
||||
<Tooltip content="Amount">
|
||||
<Badge variant="green" label={`₹ ${val.deliveryamt.toFixed(2)} `} />
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
|
||||
<MobileField label="KMS">
|
||||
<VStack gap={1} padding={0}>
|
||||
<Tooltip content="KMS">
|
||||
<Badge variant="error" label={`${Number(val.kms || 0).toFixed(2)} km`} />
|
||||
</Tooltip>
|
||||
<Tooltip content="Cumulative Kms">
|
||||
<Badge variant="green" label={`${val.cumulativekms} km`} />
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</MobileField>
|
||||
</MobileFieldGrid>
|
||||
</MobileCard>
|
||||
);
|
||||
})}
|
||||
</MobileCardList>
|
||||
);
|
||||
};
|
||||
@@ -167,6 +212,14 @@ const OrdersPreview = () => {
|
||||
const [payment, setPayment] = useState(null);
|
||||
const [finaldeliveryList, setFinalDeliveryList] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// Sorting/resizing state — mirrors orders.js's/riders.js's slices.
|
||||
// pageSize: Infinity keeps every row on one "page" (no Pagination UI):
|
||||
// this is a pre-commit REVIEW step for a single solver run's batch, and
|
||||
// the operator needs to see every affected order before clicking
|
||||
// Finalise, not just whatever page happens to be showing.
|
||||
const [sorting, setSorting] = useState([]);
|
||||
const [pagination] = useState({ pageIndex: 0, pageSize: Infinity });
|
||||
const [columnSizing, setColumnSizing] = useState({});
|
||||
const deliverylist = location.state?.deliverylist;
|
||||
const zoneData = location.state?.zoneData;
|
||||
const metaData = location.state?.metaData;
|
||||
@@ -266,106 +319,225 @@ const OrdersPreview = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const renderRow = (val, index) => (
|
||||
<Fragment key={index}>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Text>{index + 1}</Text>
|
||||
</TableCell>
|
||||
{aiMode == 1 && (
|
||||
<TableCell>
|
||||
<Badge variant="blue" label={val.zone_name} />
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<Tooltip content={val.tenantaddress}>
|
||||
// The column model — everything a column IS declared once here instead of
|
||||
// a hand-written body row, same shape as orders.js's/riders.js's/
|
||||
// Tenants.js's `columns`. Zone/Rider are pushed in conditionally, matching
|
||||
// the exact `aiMode == 1` gate the header row used before.
|
||||
const columns = useMemo(() => {
|
||||
const base = [
|
||||
{
|
||||
id: 'serial',
|
||||
size: 44,
|
||||
minSize: 36,
|
||||
maxSize: 52,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
meta: { label: '#' },
|
||||
header: () => <SectionLabel>#</SectionLabel>,
|
||||
cell: ({ table: t, row }) => <Text>{t.getRowModel().rows.indexOf(row) + 1}</Text>
|
||||
}
|
||||
];
|
||||
|
||||
if (aiMode == 1) {
|
||||
base.push({
|
||||
id: 'zone',
|
||||
accessorKey: 'zone_name',
|
||||
size: 110,
|
||||
minSize: 90,
|
||||
meta: { label: 'Zone' },
|
||||
header: () => <SectionLabel>Zone</SectionLabel>,
|
||||
cell: ({ getValue }) => <Badge variant="blue" label={getValue()} />
|
||||
});
|
||||
}
|
||||
|
||||
base.push(
|
||||
{
|
||||
id: 'tenant',
|
||||
accessorKey: 'tenantname',
|
||||
size: 220,
|
||||
minSize: 170,
|
||||
meta: { label: 'Tenant' },
|
||||
header: () => <SectionLabel>Tenant</SectionLabel>,
|
||||
cell: ({ getValue, row }) => (
|
||||
<Tooltip content={row.original.tenantaddress}>
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text maxLines={1}>{val.tenantname}</Text>
|
||||
<Text maxLines={1}>{getValue()}</Text>
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.tenantsuburb}
|
||||
{row.original.tenantsuburb}
|
||||
</Text>
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.applocation}
|
||||
{row.original.applocation}
|
||||
</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip content={val.locationaddress}>
|
||||
<Text maxLines={1}>{`${val.locationname}-(${val.locationsuburb})`}</Text>
|
||||
</Tooltip>
|
||||
<Tooltip content="Order Id">
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.orderid}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<HStack gap={3}>
|
||||
<Tooltip content="Ordered date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
-
|
||||
<Tooltip content="Delivery date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'orderLocation',
|
||||
accessorKey: 'locationname',
|
||||
size: 240,
|
||||
minSize: 190,
|
||||
meta: { label: 'Order Location' },
|
||||
header: () => <SectionLabel>Order Location</SectionLabel>,
|
||||
cell: ({ row }) => {
|
||||
const val = row.original;
|
||||
return (
|
||||
<>
|
||||
<Tooltip content={val.locationaddress}>
|
||||
<Text maxLines={1}>{`${val.locationname}-(${val.locationsuburb})`}</Text>
|
||||
</Tooltip>
|
||||
<Tooltip content="Order Id">
|
||||
<Text type="supporting" maxLines={1}>
|
||||
{val.orderid}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<HStack gap={3}>
|
||||
<Tooltip content="Ordered date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.orderdate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
-
|
||||
<Tooltip content="Delivery date">
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('DD/MM/YYYY')}</Text>
|
||||
<Text type="supporting">{parseDoormileTimestamp(val.deliverydate).format('hh:mm A')}</Text>
|
||||
</VStack>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'pickup',
|
||||
accessorKey: 'pickupcustomer',
|
||||
size: 200,
|
||||
minSize: 160,
|
||||
meta: { label: 'Pickup' },
|
||||
header: () => <SectionLabel>Pickup</SectionLabel>,
|
||||
cell: ({ getValue, row }) => (
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{val.pickupcustomer}</Text>
|
||||
<Text type="supporting">{val.pickupcontactno}</Text>
|
||||
<Tooltip content={val.pickupaddress}>
|
||||
<Text type="supporting">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Text>
|
||||
<Text type="supporting">{getValue()}</Text>
|
||||
<Text type="supporting">{row.original.pickupcontactno}</Text>
|
||||
<Tooltip content={row.original.pickupaddress}>
|
||||
<Text type="supporting">{row.original.pickupsuburb || row.original.pickupaddress.slice(0, 20)}</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'delivery',
|
||||
accessorKey: 'deliverycustomer',
|
||||
size: 200,
|
||||
minSize: 160,
|
||||
meta: { label: 'Delivery' },
|
||||
header: () => <SectionLabel>Delivery</SectionLabel>,
|
||||
cell: ({ getValue, row }) => (
|
||||
<VStack gap={0} padding={0}>
|
||||
<Text type="supporting">{val.deliverycustomer}</Text>
|
||||
<Text type="supporting">{val.deliverycontactno}</Text>
|
||||
<Tooltip content={val.deliveryaddress}>
|
||||
<Text type="supporting">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Text>
|
||||
<Text type="supporting">{getValue()}</Text>
|
||||
<Text type="supporting">{row.original.deliverycontactno}</Text>
|
||||
<Tooltip content={row.original.deliveryaddress}>
|
||||
<Text type="supporting">{row.original.deliverysuburb || row.original.deliveryaddress.slice(0, 20)}</Text>
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</TableCell>
|
||||
<TableCell>{val.ordernotes}</TableCell>
|
||||
{aiMode == 1 && (
|
||||
<TableCell>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>{val.username}</Text>
|
||||
<Text>ID : {val.userid}</Text>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell style={{ textAlign: 'center' }}>
|
||||
<Badge variant={orderTypeBadge(val.ordertype)} label={val.ordertype} />
|
||||
</TableCell>
|
||||
<TableCell style={{ textAlign: 'center' }}>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'notes',
|
||||
accessorKey: 'ordernotes',
|
||||
size: 160,
|
||||
minSize: 120,
|
||||
meta: { label: 'Notes' },
|
||||
header: () => <SectionLabel>Notes</SectionLabel>,
|
||||
cell: ({ getValue }) => <Text>{getValue()}</Text>
|
||||
}
|
||||
);
|
||||
|
||||
if (aiMode == 1) {
|
||||
base.push({
|
||||
id: 'rider',
|
||||
accessorKey: 'username',
|
||||
size: 150,
|
||||
minSize: 120,
|
||||
meta: { label: 'Rider' },
|
||||
header: () => <SectionLabel>Rider</SectionLabel>,
|
||||
cell: ({ getValue, row }) => (
|
||||
<>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>{getValue()}</Text>
|
||||
<Text>ID : {row.original.userid}</Text>
|
||||
</>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
base.push(
|
||||
{
|
||||
id: 'type',
|
||||
accessorKey: 'ordertype',
|
||||
size: 110,
|
||||
minSize: 90,
|
||||
meta: { label: 'Type', align: 'center' },
|
||||
header: () => <SectionLabel>Type</SectionLabel>,
|
||||
cell: ({ getValue }) => <Badge variant={orderTypeBadge(getValue())} label={getValue()} />
|
||||
},
|
||||
{
|
||||
id: 'profit',
|
||||
accessorKey: 'deliveryamt',
|
||||
size: 150,
|
||||
minSize: 120,
|
||||
meta: { label: 'Profit', align: 'center' },
|
||||
header: () => <SectionLabel>Profit</SectionLabel>,
|
||||
cell: ({ row }) => (
|
||||
<VStack gap={1} padding={0} vAlign="center">
|
||||
<Tooltip content="Charges">
|
||||
<Badge variant="error" label={`₹ ${val.deliverycharge.toFixed(2)} `} />
|
||||
<Badge variant="error" label={`₹ ${row.original.deliverycharge.toFixed(2)} `} />
|
||||
</Tooltip>
|
||||
<Tooltip content="Amount">
|
||||
<Badge variant="green" label={`₹ ${val.deliveryamt.toFixed(2)} `} />
|
||||
<Badge variant="green" label={`₹ ${row.original.deliveryamt.toFixed(2)} `} />
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</TableCell>
|
||||
<TableCell style={{ textAlign: 'center' }}>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'kms',
|
||||
accessorKey: 'kms',
|
||||
size: 150,
|
||||
minSize: 120,
|
||||
meta: { label: 'Kms', align: 'center' },
|
||||
header: () => <SectionLabel>Kms</SectionLabel>,
|
||||
cell: ({ getValue, row }) => (
|
||||
<VStack gap={1} padding={0} vAlign="center">
|
||||
<Tooltip content="KMS">
|
||||
<Badge variant="error" label={`${Number(val.kms || 0).toFixed(2)} km`} />
|
||||
<Badge variant="error" label={`${Number(getValue() || 0).toFixed(2)} km`} />
|
||||
</Tooltip>
|
||||
<Tooltip content="Cumulative Kms">
|
||||
<Badge variant="green" label={`${val.cumulativekms} km`} />
|
||||
<Badge variant="green" label={`${row.original.cumulativekms} km`} />
|
||||
</Tooltip>
|
||||
</VStack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Fragment>
|
||||
);
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
return base;
|
||||
}, [aiMode]);
|
||||
|
||||
const table = useTable({
|
||||
features: ORDERS_PREVIEW_TABLE_FEATURES,
|
||||
// finaldeliveryList is already component state (stable reference between
|
||||
// renders unless setFinalDeliveryList is called) — required for
|
||||
// TanStack's row-model memoization to hold correctly.
|
||||
data: finaldeliveryList,
|
||||
columns,
|
||||
getRowId: (row, index) => String(row.orderid ?? `row-${index}`),
|
||||
columnResizeMode: 'onChange',
|
||||
state: { sorting, pagination, columnSizing },
|
||||
onSortingChange: setSorting,
|
||||
onColumnSizingChange: setColumnSizing
|
||||
});
|
||||
|
||||
const colCount = columns.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -405,34 +577,129 @@ const OrdersPreview = () => {
|
||||
|
||||
<DataCard>
|
||||
{isMobile ? (
|
||||
<MobileOrdersList list={finaldeliveryList} aiMode={aiMode} />
|
||||
<MobileOrdersList table={table} aiMode={aiMode} />
|
||||
) : (
|
||||
<TableScroll minWidth={aiMode == 1 ? 1400 : 1200}>
|
||||
<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="Orders preview"
|
||||
style={{ tableLayout: 'fixed', width: '100%', minWidth: table.getTotalSize() }}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell style={{ minWidth: 40, width: 40, maxWidth: 40 }}>#</TableHeaderCell>
|
||||
{aiMode == 1 && <TableHeaderCell>Zone</TableHeaderCell>}
|
||||
<TableHeaderCell>Tenant</TableHeaderCell>
|
||||
<TableHeaderCell>Order Location</TableHeaderCell>
|
||||
<TableHeaderCell>Pickup</TableHeaderCell>
|
||||
<TableHeaderCell>Delivery</TableHeaderCell>
|
||||
<TableHeaderCell>Notes</TableHeaderCell>
|
||||
{aiMode == 1 && <TableHeaderCell>Rider</TableHeaderCell>}
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>Type</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>Profit</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>KMS</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 === '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>
|
||||
{finaldeliveryList?.length == 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={10}>
|
||||
<TableCell colSpan={colCount}>
|
||||
<EmptyState title="No orders to preview" icon={<MdDirectionsBike size={28} />} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{finaldeliveryList?.map((val, index) => renderRow(val, index))}
|
||||
{table.getRowModel().rows.map((tableRow) => (
|
||||
<Fragment key={tableRow.id}>
|
||||
<TableRow>
|
||||
{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>
|
||||
</Fragment>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableScroll>
|
||||
|
||||
@@ -13,6 +13,7 @@ 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';
|
||||
@@ -27,13 +28,28 @@ import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
|
||||
import { EmptyState } from '@astryxdesign/core/EmptyState';
|
||||
import { Table, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '@astryxdesign/core/Table';
|
||||
|
||||
import { MdOutlineCloudUpload, MdDeleteOutline } from 'react-icons/md';
|
||||
// TanStack Table v9 — same engine orders.js/riders.js/Tenants.js use, so
|
||||
// sorting/resizing behave identically across the console. No pagination
|
||||
// feature registered here deliberately — see the comment above dropColumns.
|
||||
import {
|
||||
useTable,
|
||||
flexRender,
|
||||
tableFeatures,
|
||||
rowSortingFeature,
|
||||
createSortedRowModel,
|
||||
sortFns,
|
||||
columnSizingFeature,
|
||||
columnResizingFeature
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
import { MdOutlineCloudUpload, MdDeleteOutline, MdArrowUpward, MdArrowDownward, MdUnfoldMore } from 'react-icons/md';
|
||||
import { FaUsers, FaPaperPlane, FaRoute, FaMoneyBillWave, FaBoxes, FaReceipt, FaTruck } from 'react-icons/fa';
|
||||
import { FaLocationDot } from 'react-icons/fa6';
|
||||
|
||||
import Loader from 'components/Loader';
|
||||
import PageHeader from 'components/nearle_components/PageHeader';
|
||||
import { PageShell } from 'components/nearle_components/PageLayout';
|
||||
import { DT } from 'themes/dt/tokens';
|
||||
import { TableScroll } from 'themes/dt/primitives';
|
||||
import './OrdersRedesign.css';
|
||||
|
||||
@@ -110,6 +126,100 @@ const MetricTile = ({ icon: Icon, label, value, accent, active }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
// TanStack Table v9 registers features explicitly — sorting/resizing only,
|
||||
// no pagination (see the comment above dropColumns for why) and no
|
||||
// row-selection/column-visibility (neither table needs them).
|
||||
const MULTI_ORDERS_TABLE_FEATURES = tableFeatures({
|
||||
rowSortingFeature,
|
||||
sortedRowModel: createSortedRowModel(),
|
||||
sortFns,
|
||||
columnSizingFeature,
|
||||
columnResizingFeature
|
||||
});
|
||||
|
||||
// Uppercase micro-label for the tables' 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>
|
||||
);
|
||||
|
||||
// File Preview table's columns — purely read-only display, no closures over
|
||||
// component state, so this is safe to declare once at module scope rather
|
||||
// than inside the component.
|
||||
const previewColumns = [
|
||||
{
|
||||
id: 'serial',
|
||||
size: 44,
|
||||
minSize: 36,
|
||||
maxSize: 52,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
meta: { label: '#' },
|
||||
header: () => <SectionLabel>#</SectionLabel>,
|
||||
cell: ({ table: t, row }) => t.getRowModel().rows.indexOf(row) + 1
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
accessorKey: 'firstname',
|
||||
size: 170,
|
||||
minSize: 130,
|
||||
meta: { label: 'Name' },
|
||||
header: () => <SectionLabel>Name</SectionLabel>,
|
||||
cell: ({ getValue }) => (
|
||||
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
|
||||
{getValue() || '—'}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'contact',
|
||||
accessorKey: 'contactno',
|
||||
size: 140,
|
||||
minSize: 110,
|
||||
meta: { label: 'Contact' },
|
||||
header: () => <SectionLabel>Contact</SectionLabel>,
|
||||
cell: ({ getValue }) => getValue() || '—'
|
||||
},
|
||||
{
|
||||
id: 'address',
|
||||
accessorKey: 'address',
|
||||
size: 260,
|
||||
minSize: 180,
|
||||
meta: { label: 'Address' },
|
||||
header: () => <SectionLabel>Address</SectionLabel>,
|
||||
cell: ({ getValue }) => (
|
||||
<Tooltip content={getValue() || ''}>
|
||||
<Text maxLines={1} style={{ maxWidth: 240 }}>
|
||||
{getValue() || '—'}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'qty',
|
||||
accessorKey: 'quantity',
|
||||
size: 90,
|
||||
minSize: 70,
|
||||
meta: { label: 'Qty', align: 'center' },
|
||||
header: () => <SectionLabel>Qty</SectionLabel>,
|
||||
cell: ({ getValue }) => getValue() ?? '—'
|
||||
},
|
||||
{
|
||||
id: 'cash',
|
||||
accessorKey: 'collectionamt',
|
||||
size: 120,
|
||||
minSize: 100,
|
||||
meta: { label: 'Cash', align: 'center' },
|
||||
header: () => <SectionLabel>Cash</SectionLabel>,
|
||||
cell: ({ getValue }) => {
|
||||
const v = getValue();
|
||||
return v != null ? `₹${Number(v).toFixed(2)}` : '—';
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const SourceTile = ({ icon: Icon, title, sub, accent, disabled, onClick }) => (
|
||||
<div
|
||||
role="button"
|
||||
@@ -213,6 +323,14 @@ const MultipleOrders = () => {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [fileName, setFileName] = useState('');
|
||||
|
||||
// Table sorting/resizing state — mirrors orders.js's/riders.js's slices.
|
||||
// Two independent table instances (Drop List, File Preview), each with its
|
||||
// own state.
|
||||
const [dropSorting, setDropSorting] = useState([]);
|
||||
const [dropColumnSizing, setDropColumnSizing] = useState({});
|
||||
const [previewSorting, setPreviewSorting] = useState([]);
|
||||
const [previewColumnSizing, setPreviewColumnSizing] = useState({});
|
||||
|
||||
const toastCacheRef = useRef({});
|
||||
|
||||
// ============================== toast ==============================
|
||||
@@ -749,6 +867,179 @@ const MultipleOrders = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Drop List's column model — deliberately NOT wrapped in useMemo: its
|
||||
// Address/Qty/Cash cells close over uploadType, pickCust and several
|
||||
// setDropCust-based handlers that change often, and every one of them is
|
||||
// already recreated fresh each render in this file (none are themselves
|
||||
// memoized) — memoizing here would need an unmanageable dependency list
|
||||
// for no benefit, matching the same call made on Tenants.js's Actions
|
||||
// column. Sorting/resizing are display-only — this table never paginates,
|
||||
// since the operator has to review and complete EVERY row's address
|
||||
// before Dispatch is enabled (an incomplete row blocks submit with a
|
||||
// toast), and hiding some of those rows behind a page-2 would work
|
||||
// against that. Submission (`createorders`) always reads the full
|
||||
// `dropCust` array regardless of what's sorted/visible.
|
||||
const dropColumns = [
|
||||
{
|
||||
id: 'serial',
|
||||
size: 40,
|
||||
minSize: 34,
|
||||
maxSize: 48,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
meta: { label: '#' },
|
||||
header: () => <SectionLabel>#</SectionLabel>,
|
||||
cell: ({ table: t, row }) => t.getRowModel().rows.indexOf(row) + 1
|
||||
},
|
||||
{
|
||||
id: 'customer',
|
||||
accessorKey: 'firstname',
|
||||
size: 160,
|
||||
minSize: 120,
|
||||
meta: { label: 'Customer' },
|
||||
header: () => <SectionLabel>Customer</SectionLabel>,
|
||||
cell: ({ getValue }) => (
|
||||
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
|
||||
{getValue()}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'address',
|
||||
size: 260,
|
||||
minSize: 200,
|
||||
enableSorting: false,
|
||||
meta: { label: 'Address' },
|
||||
header: () => <SectionLabel>Address</SectionLabel>,
|
||||
cell: ({ row }) => {
|
||||
const customer = row.original;
|
||||
return customer.latitude == null || customer.longitude == null ? (
|
||||
<AddressAutocomplete
|
||||
id={`drop-address-${customer._rowkey}`}
|
||||
placeholder="Search address…"
|
||||
value={customer.address || ''}
|
||||
onChange={(text) => setDropCust((prev) => prev.map((c) => (c._rowkey === customer._rowkey ? { ...c, address: text } : c)))}
|
||||
onPlaceSelected={(place) => handleRowPlaceSelected(customer._rowkey, place)}
|
||||
bias={pickCust?.latitude ? { lat: pickCust.latitude, lng: pickCust.longitude } : undefined}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip content={customer.address || ''}>
|
||||
<Text maxLines={1} style={{ maxWidth: 260 }}>
|
||||
{customer.address}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'qty',
|
||||
size: 110,
|
||||
minSize: 90,
|
||||
enableSorting: false,
|
||||
meta: { label: 'Qty', align: 'center' },
|
||||
header: () => <SectionLabel>Qty</SectionLabel>,
|
||||
cell: ({ row }) => {
|
||||
const customer = row.original;
|
||||
return uploadType === 0 ? (
|
||||
customer.quantity
|
||||
) : (
|
||||
<NumberInput
|
||||
label="Quantity"
|
||||
isLabelHidden
|
||||
value={customer.quantity || undefined}
|
||||
onChange={(v) => handleQuantityChange(customer._rowkey, v ?? 0)}
|
||||
size="sm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'cash',
|
||||
size: 130,
|
||||
minSize: 100,
|
||||
enableSorting: false,
|
||||
meta: { label: 'Cash', align: 'center' },
|
||||
header: () => <SectionLabel>Cash</SectionLabel>,
|
||||
cell: ({ row }) => {
|
||||
const customer = row.original;
|
||||
return uploadType === 0 ? (
|
||||
`₹${Number(customer.collectionamt || 0).toFixed(2)}`
|
||||
) : (
|
||||
<NumberInput
|
||||
label="Collection amount"
|
||||
isLabelHidden
|
||||
value={customer.collectionamt || undefined}
|
||||
onChange={(v) => handleCollectionAmtChange(customer._rowkey, v && v > 0 ? v : 0)}
|
||||
size="sm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'km',
|
||||
accessorKey: 'distance',
|
||||
size: 90,
|
||||
minSize: 70,
|
||||
meta: { label: 'Km' },
|
||||
header: () => <SectionLabel>Km</SectionLabel>,
|
||||
cell: ({ getValue }) => getValue() || '—'
|
||||
},
|
||||
{
|
||||
id: 'charge',
|
||||
accessorFn: (row) => Number(row.totalcharge) || 0,
|
||||
size: 120,
|
||||
minSize: 100,
|
||||
meta: { label: 'Charge', align: 'right' },
|
||||
header: () => <SectionLabel>Charge</SectionLabel>,
|
||||
cell: ({ getValue }) => <Text weight="bold">₹{getValue().toFixed(2)}</Text>
|
||||
},
|
||||
{
|
||||
id: 'action',
|
||||
size: 56,
|
||||
minSize: 48,
|
||||
maxSize: 64,
|
||||
enableSorting: false,
|
||||
enableResizing: false,
|
||||
meta: { label: '' },
|
||||
header: () => null,
|
||||
cell: ({ row }) => {
|
||||
const customer = row.original;
|
||||
return (
|
||||
<IconButton
|
||||
label="Remove"
|
||||
tooltip="Remove"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
icon={<MdDeleteOutline size={14} />}
|
||||
onClick={() => handleCheckboxChange1(customer)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const dropTable = useTable({
|
||||
features: MULTI_ORDERS_TABLE_FEATURES,
|
||||
data: dropCust,
|
||||
columns: dropColumns,
|
||||
getRowId: (row, index) => row._rowkey ?? `row-${index}`,
|
||||
columnResizeMode: 'onChange',
|
||||
state: { sorting: dropSorting, columnSizing: dropColumnSizing },
|
||||
onSortingChange: setDropSorting,
|
||||
onColumnSizingChange: setDropColumnSizing
|
||||
});
|
||||
|
||||
const previewTable = useTable({
|
||||
features: MULTI_ORDERS_TABLE_FEATURES,
|
||||
data: users,
|
||||
columns: previewColumns,
|
||||
getRowId: (row, index) => row._rowkey ?? `row-${index}`,
|
||||
columnResizeMode: 'onChange',
|
||||
state: { sorting: previewSorting, columnSizing: previewColumnSizing },
|
||||
onSortingChange: setPreviewSorting,
|
||||
onColumnSizingChange: setPreviewColumnSizing
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{(loading || btnLoading) && <Loader />}
|
||||
@@ -1043,91 +1334,116 @@ const MultipleOrders = () => {
|
||||
|
||||
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
|
||||
{previewMode === 'drops' && (
|
||||
<TableScroll minWidth={720}>
|
||||
<Table density="compact" dividers="rows" hasHover>
|
||||
<TableScroll minWidth={dropTable.getTotalSize()}>
|
||||
<Table
|
||||
density="compact"
|
||||
dividers="rows"
|
||||
hasHover
|
||||
style={{ tableLayout: 'fixed', width: '100%', minWidth: dropTable.getTotalSize() }}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell style={{ minWidth: 40, width: 40, maxWidth: 40 }}>#</TableHeaderCell>
|
||||
<TableHeaderCell>Customer</TableHeaderCell>
|
||||
<TableHeaderCell>Address</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>Qty</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>Cash</TableHeaderCell>
|
||||
<TableHeaderCell>Km</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'right' }}>Charge</TableHeaderCell>
|
||||
<TableHeaderCell></TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{dropCust.map((customer, index) => (
|
||||
<TableRow key={customer._rowkey || index}>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
<TableCell>
|
||||
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
|
||||
{customer.firstname}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell style={{ maxWidth: 260 }}>
|
||||
{customer.latitude == null || customer.longitude == null ? (
|
||||
<AddressAutocomplete
|
||||
id={`drop-address-${customer._rowkey}`}
|
||||
placeholder="Search address…"
|
||||
value={customer.address || ''}
|
||||
onChange={(text) =>
|
||||
setDropCust((prev) => prev.map((c) => (c._rowkey === customer._rowkey ? { ...c, address: text } : c)))
|
||||
}
|
||||
onPlaceSelected={(place) => handleRowPlaceSelected(customer._rowkey, place)}
|
||||
bias={pickCust?.latitude ? { lat: pickCust.latitude, lng: pickCust.longitude } : undefined}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip content={customer.address || ''}>
|
||||
<Text maxLines={1} style={{ maxWidth: 260 }}>
|
||||
{customer.address}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell style={{ textAlign: 'center' }}>
|
||||
{uploadType === 0 ? (
|
||||
customer.quantity
|
||||
) : (
|
||||
<NumberInput
|
||||
label="Quantity"
|
||||
isLabelHidden
|
||||
value={customer.quantity || undefined}
|
||||
onChange={(v) => handleQuantityChange(customer._rowkey, v ?? 0)}
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell style={{ textAlign: 'center' }}>
|
||||
{uploadType === 0 ? (
|
||||
`₹${Number(customer.collectionamt || 0).toFixed(2)}`
|
||||
) : (
|
||||
<NumberInput
|
||||
label="Collection amount"
|
||||
isLabelHidden
|
||||
value={customer.collectionamt || undefined}
|
||||
onChange={(v) => handleCollectionAmtChange(customer._rowkey, v && v > 0 ? v : 0)}
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{customer.distance || '—'}</TableCell>
|
||||
<TableCell style={{ textAlign: 'right' }}>
|
||||
<Text weight="bold">₹{Number(customer?.totalcharge || 0).toFixed(2)}</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
label="Remove"
|
||||
tooltip="Remove"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
icon={<MdDeleteOutline size={14} />}
|
||||
onClick={() => handleCheckboxChange1(customer)}
|
||||
/>
|
||||
</TableCell>
|
||||
{dropTable.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>
|
||||
{dropTable.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>
|
||||
))}
|
||||
{/* Summary row, not a data row — deliberately hand-coded
|
||||
rather than run through the column model, and
|
||||
always sums the FULL dropCust array (not affected
|
||||
by sorting). */}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Text weight="bold">Total</Text>
|
||||
@@ -1155,39 +1471,110 @@ const MultipleOrders = () => {
|
||||
)}
|
||||
|
||||
{previewMode === 'preview' && (
|
||||
<TableScroll minWidth={640}>
|
||||
<Table density="compact" dividers="rows" hasHover>
|
||||
<TableScroll minWidth={previewTable.getTotalSize()}>
|
||||
<Table
|
||||
density="compact"
|
||||
dividers="rows"
|
||||
hasHover
|
||||
style={{ tableLayout: 'fixed', width: '100%', minWidth: previewTable.getTotalSize() }}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell style={{ minWidth: 40, width: 40, maxWidth: 40 }}>#</TableHeaderCell>
|
||||
<TableHeaderCell>Name</TableHeaderCell>
|
||||
<TableHeaderCell>Contact</TableHeaderCell>
|
||||
<TableHeaderCell>Address</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>Qty</TableHeaderCell>
|
||||
<TableHeaderCell style={{ textAlign: 'center' }}>Cash</TableHeaderCell>
|
||||
</TableRow>
|
||||
{previewTable.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>
|
||||
{users.map((u, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell>{i + 1}</TableCell>
|
||||
<TableCell>
|
||||
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
|
||||
{u.firstname || '—'}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>{u.contactno || '—'}</TableCell>
|
||||
<TableCell style={{ maxWidth: 240 }}>
|
||||
<Tooltip content={u.address || ''}>
|
||||
<Text maxLines={1} style={{ maxWidth: 240 }}>
|
||||
{u.address || '—'}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell style={{ textAlign: 'center' }}>{u.quantity ?? '—'}</TableCell>
|
||||
<TableCell style={{ textAlign: 'center' }}>
|
||||
{u.collectionamt != null ? `₹${Number(u.collectionamt).toFixed(2)}` : '—'}
|
||||
</TableCell>
|
||||
{previewTable.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>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -63,8 +63,9 @@ import StatusBadge from 'components/nearle_components/StatusBadge';
|
||||
import StatusTabs from 'components/nearle_components/StatusTabs';
|
||||
import TableLoader from 'components/nearle_components/TableLoader';
|
||||
import PageHeader from 'components/nearle_components/PageHeader';
|
||||
import StatCard from 'components/nearle_components/StatCard';
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
import { PageShell, DataCard } from 'components/nearle_components/PageLayout';
|
||||
import { PageShell, KpiGrid, DataCard } from 'components/nearle_components/PageLayout';
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { fetchPercentageData, createAutomationDeliveries, getallriders, buildMilerLookup, notifyRider } from '../../api/api';
|
||||
@@ -909,6 +910,25 @@ const Orders = () => {
|
||||
<PageShell fill>
|
||||
<PageHeader title="Orders" subtitle={`Live · ${locoName} · ${datestatus}`} live />
|
||||
|
||||
{/* Derived from the SAME statusCounts map the tab badges below read,
|
||||
by mapping over ORDERS_STATUS_TABS rather than a second parallel
|
||||
grouping — so this row can never disagree with the tabs. */}
|
||||
<KpiGrid>
|
||||
{ORDERS_STATUS_TABS.map((t) => {
|
||||
const Icon = t.icon;
|
||||
return (
|
||||
<StatCard
|
||||
key={t.status}
|
||||
title={t.label}
|
||||
value={t.statuses.reduce((sum, s) => sum + (statusCounts[s] ?? 0), 0)}
|
||||
icon={<Icon size={20} />}
|
||||
color={t.color}
|
||||
loading={isLoadingGetOrders}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</KpiGrid>
|
||||
|
||||
<DataCard
|
||||
fill
|
||||
tabs={
|
||||
|
||||
Reference in New Issue
Block a user