111 lines
3.9 KiB
TypeScript
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>
|
|
);
|
|
}
|