Files
daily_console_web/src/features/store-admin/CounterDrawer.tsx
2026-09-03 16:47:04 +05:30

111 lines
3.9 KiB
TypeScript

import { useState } from 'react';
import { Button } from '@astryxdesign/core/Button';
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 { EyeOff } from 'lucide-react';
import { Drawer } from './Drawer';
import type { CounterLabels } from './counterLabels';
import type { CounterRow } from './counterRows';
import { money } from './format';
import { shortAge } from './posStatus';
/**
* One counter, in full.
*
* The list answers "which counters need me"; this answers "what is this one
* doing" — the readings a person wants before walking over to it, plus the two
* things they can change about it.
*
* Rename and hide live here rather than on the row. They are rare actions, and
* a control on every row of a fifteen-row table is fifteen chances to hide a
* counter by mistake.
*/
export interface CounterDrawerProps {
row: CounterRow;
labels: CounterLabels;
onClose: () => void;
}
export function CounterDrawer({ row, labels, onClose }: CounterDrawerProps) {
const [draft, setDraft] = useState(() =>
labels.isNamed(row.terminalId) ? labels.nameFor(row.terminalId) : '',
);
const status = row.status;
const readings: [string, string][] = [
['Counter code', row.terminalId],
['Branch', row.branchName],
['Sold in this period', `${row.periodBills} bills · ${money(row.periodAmount)}`],
];
if (status) {
readings.push(['Today on the till', `${status.todayBills} bills · ${money(status.todayAmount)}`]);
readings.push(['Last sale', status.lastBillAt ? `${shortAge(Date.now() - status.lastBillAt.getTime())} ago` : 'never']);
readings.push(['Last heard from', status.silentForMs == null ? 'never' : `${shortAge(status.silentForMs)} ago`]);
// The number that explains a revenue figure reading low, so it is stated
// even when it is zero rather than hidden as "nothing to report".
readings.push(['Waiting to send', String(status.pendingBills)]);
if (status.reason) readings.push(['Reported', status.reason]);
} else {
readings.push(['Status', 'This counter has never reported in.']);
}
return (
<Drawer
title={labels.isNamed(row.terminalId) ? labels.nameFor(row.terminalId) : row.terminalId}
subtitle={labels.isNamed(row.terminalId) ? row.terminalId : row.branchName}
width={460}
onClose={onClose}
>
<VStack gap={3}>
<div className="reading-list">
{readings.map(([label, value]) => (
<div key={label} className="reading">
<span>{label}</span>
<strong>{value}</strong>
</div>
))}
</div>
<VStack gap={1.5}>
<TextInput
label="Name this counter"
value={draft}
onChange={setDraft}
placeholder={row.terminalId}
description="Shown instead of the code. The code stays on the receipts."
/>
<HStack justify="between" align="center" gap={2} wrap="wrap">
<Button
label="Hide this counter"
variant="ghost"
size="sm"
icon={<EyeOff size={14} />}
onClick={() => {
labels.hide(row.terminalId);
onClose();
}}
/>
<Button
label="Save name"
variant="primary"
size="sm"
isDisabled={draft.trim() === '' && !labels.isNamed(row.terminalId)}
onClick={() => {
labels.rename(row.terminalId, draft.trim());
onClose();
}}
/>
</HStack>
<Text type="body" size="xsm" color="secondary">
Hiding removes a counter from this page. It keeps recording sales — use it for a till
that has been retired.
</Text>
</VStack>
</VStack>
</Drawer>
);
}