enhancement

This commit is contained in:
2026-09-07 16:46:47 +05:30
parent 05ac76f5f0
commit c31696ce41
35 changed files with 926 additions and 986 deletions

View File

@@ -1,12 +1,12 @@
import { useMemo, useState } from 'react';
import { useState, useMemo } from 'react';
import { useDateScope } from '@/components/shell/DateScope';
import { CheckboxInput } from '@astryxdesign/core/CheckboxInput';
import { Card } from '@astryxdesign/core/Card';
import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text';
import { TextInput } from '@astryxdesign/core/TextInput';
import { VStack } from '@astryxdesign/core/VStack';
import { Banknote, Bike, Calculator, IndianRupee, MapPin, Package, Percent, Receipt, ReceiptText, ShoppingCart, Truck, Upload, UserX } from 'lucide-react';
import type { DateRange } from '@/api/insights';
import { Banknote, Bike, Calculator, IndianRupee, MapPin, Package, Percent, Receipt, ReceiptText, ShoppingCart, Truck, UserX } from 'lucide-react';
import type { DeliveryRow, OrderRow, PosSale, TenantLocation } from '@/api/types';
import { KpiCard } from '@/components/KpiCard';
import { PageHeader } from '@/components/PageHeader';
@@ -17,7 +17,6 @@ import {
usePosSalesByBranch,
} from '@/queries/hooks';
import { useBranchScope } from '../BranchScope';
import { DateRangePicker, presetRange, type RangePreset } from '../DateRangePicker';
import { branchLabel, count, money, moneyExact } from '../format';
import {
DELIVERY_STATUS,
@@ -31,7 +30,6 @@ import {
syncLagMs,
type StatusKey,
} from '../orderStatus';
import { CounterSalesDrawer } from '../offlineSales/CounterSalesDrawer';
import { OrderDetailDrawer, type RowKind } from '../OrderDetailDrawer';
import { shortAge } from '../posStatus';
import { AssignBar } from '../AssignBar';
@@ -60,17 +58,15 @@ type Tab = 'orders' | 'deliveries' | 'counter';
* every row would be a column of dashes.
*/
export function SalesPage() {
const { scoped, selected, current, branches, tenantid, isPinned } = useBranchScope();
const [isUploading, setUploading] = useState(false);
const { scoped, selected, branches, tenantid } = useBranchScope();
const [tab, setTab] = useState<Tab>('orders');
const [status, setStatus] = useState<StatusKey>('all');
const [preset, setPreset] = useState<RangePreset>('month');
const [range, setRange] = useState<DateRange>(() => presetRange('month'));
const dates = useDateScope();
const [keyword, setKeyword] = useState('');
/* What the tables are currently showing. Any change to it sends every pager
back to page 1 — landing on page 4 of a search you just typed reads as an
empty result. */
const queryKey = `${tab}|${status}|${keyword}|${range.fromdate}|${range.todate}|${selected ?? 'all'}`;
const queryKey = `${tab}|${status}|${keyword}|${dates.range.fromdate}|${dates.range.todate}|${selected ?? 'all'}`;
const [detail, setDetail] = useState<{ row: OrderRow | DeliveryRow; kind: RowKind } | null>(
null,
@@ -86,15 +82,15 @@ export function SalesPage() {
tenantid,
locationid: selected ?? undefined,
keyword: keyword.trim() || undefined,
...range,
...dates.range,
pagesize: 200,
}
: undefined;
const orders = useOrders(query);
const deliveries = useDeliveries(query);
const billPages = usePosBillsByBranch(branchIds, { ...range, pagesize: 200 });
const posSummary = usePosSalesByBranch(branchIds, range);
const billPages = usePosBillsByBranch(branchIds, { ...dates.range, pagesize: 200 });
const posSummary = usePosSalesByBranch(branchIds, dates.range);
const allOrders = orders.data ?? [];
const allDeliveries = deliveries.data ?? [];
@@ -218,30 +214,6 @@ export function SalesPage() {
<VStack gap={3}>
<PageHeader
title="Sales"
count={selected === null ? `${branches.length} branches` : current?.locationname}
description="Every order placed in the app, the delivery jobs riding on them, and every bill rung up at a till."
isLive
actions={
<HStack gap={0.5} align="center" wrap="wrap">
{/* Counter sales that never touched a till. Sits beside the date
range rather than under the Counter tab because it is how the
figures on every tab get there for a shop with no POS. */}
<BarAction
label="Upload counter sales"
icon={<Upload size={14} />}
isDisabled={!tenantid}
onClick={() => setUploading(true)}
/>
<DateRangePicker
preset={preset}
range={range}
onChange={(nextPreset, nextRange) => {
setPreset(nextPreset);
setRange(nextRange);
}}
/>
</HStack>
}
tabs={
<HStack gap={0.5} wrap="wrap">
<TabButton
@@ -482,15 +454,6 @@ export function SalesPage() {
</>
) : null}
{isUploading && tenantid ? (
<CounterSalesDrawer
tenantid={tenantid}
{...(isPinned && current ? { locationid: current.locationid } : {})}
branchIds={branches.map((branch) => branch.locationid)}
onClose={() => setUploading(false)}
/>
) : null}
{/* The row is re-read from the live list on every render rather than
used as captured, because the drawer can now CHANGE the row it is
@@ -1427,48 +1390,3 @@ function Td({
);
}
/**
* A header action drawn like a tab, so the two rows read as one bar. Same shape
* as Inventory's — copied rather than shared because the two pages own their
* chrome, and a shared one would have to take a theme prop to stay identical.
*/
function BarAction({
label,
icon,
onClick,
isDisabled,
}: {
label: string;
icon: React.ReactNode;
onClick: () => void;
isDisabled?: boolean;
}) {
const [isHovered, setHovered] = useState(false);
return (
<button
type="button"
onClick={onClick}
disabled={isDisabled}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 6,
height: 32,
padding: '0 12px',
borderRadius: 12,
border: 0,
background: isHovered && !isDisabled ? 'var(--color-surface-sunken)' : 'transparent',
color: isDisabled ? 'var(--color-ink-4)' : 'var(--color-ink-3)',
fontSize: 13,
fontWeight: 500,
cursor: isDisabled ? 'not-allowed' : 'pointer',
transition: 'background .2s, color .2s',
}}
>
{icon}
{label}
</button>
);
}