@tallyui/components 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -3
- package/dist/index.js +235 -224
- package/package.json +9 -9
- package/src/index.ts +1 -1
- package/src/register/register-count.tsx +5 -2
- package/src/register/register-panel.tsx +15 -2
- package/src/sale/catalogue.tsx +7 -4
package/dist/index.d.ts
CHANGED
|
@@ -1255,7 +1255,7 @@ declare function DiscountChips({ discounts, currency, onRemove, prefix, amounts
|
|
|
1255
1255
|
}): react_jsx_runtime.JSX.Element | null;
|
|
1256
1256
|
|
|
1257
1257
|
declare function formatStockSyncTime(time: Date, locale?: string, hour12?: boolean, now?: Date): string;
|
|
1258
|
-
declare function Catalogue<Doc>({ products, traits, currency, onSelect, statusText, statusAccessory, lastSyncedAt, loading, lastStockCheckAt, hour12, minCodeLength }: {
|
|
1258
|
+
declare function Catalogue<Doc>({ products, traits, currency, onSelect, statusText, statusAccessory, lastSyncedAt, loading, pullError, lastStockCheckAt, hour12, minCodeLength }: {
|
|
1259
1259
|
products: Doc[];
|
|
1260
1260
|
traits: ProductTraits<Doc>;
|
|
1261
1261
|
currency: string;
|
|
@@ -1265,9 +1265,12 @@ declare function Catalogue<Doc>({ products, traits, currency, onSelect, statusTe
|
|
|
1265
1265
|
/** Rendered at the end of the status line, e.g. a register control at phone width; the row renders even without `statusText`. */
|
|
1266
1266
|
statusAccessory?: ReactNode;
|
|
1267
1267
|
lastSyncedAt: Date | null;
|
|
1268
|
-
/** The catalogue's first pull is still running, so an empty grid says "Loading products…" rather than "No products yet.". Defaults to lastSyncedAt === null; pass false
|
|
1268
|
+
/** The catalogue's first pull is still running, so an empty grid says "Loading products…" rather than "No products yet.". Defaults to lastSyncedAt === null with no pullError; pass false
|
|
1269
1269
|
* when a null lastSyncedAt doesn't mean a pull is running (e.g. offline on first start). */
|
|
1270
1270
|
loading?: boolean;
|
|
1271
|
+
/** The product pull's last error (an Error, a SyncNotice, anything the app holds), or null/undefined when none. While it is
|
|
1272
|
+
* set, a catalogue that has never synced isn't treated as loading, so its empty grid says "No products yet." beside the app's error. */
|
|
1273
|
+
pullError?: unknown;
|
|
1271
1274
|
/** The last completed stock reconcile pass, persisted across restarts. The "stock as of" time shown is the
|
|
1272
1275
|
* latest of this, the provider's stockOverlayAsOf (when valid) and lastSyncedAt; null and omitted alike. */
|
|
1273
1276
|
lastStockCheckAt?: Date | null;
|
|
@@ -1299,6 +1302,15 @@ declare function Receipt({ order, posOrder, store, cashier, registerId, newSale,
|
|
|
1299
1302
|
formatDate?: (iso: string) => string;
|
|
1300
1303
|
}): react_jsx_runtime.JSX.Element;
|
|
1301
1304
|
|
|
1305
|
+
/** The order reference a visitor sees on the receipt and in Orders (front desk ruling, 2026-10-06): the finalized
|
|
1306
|
+
* PosOrder id's last 8 characters, then the backend's display id once the store has one. */
|
|
1307
|
+
declare function orderReference(order: {
|
|
1308
|
+
id: string;
|
|
1309
|
+
serverRefs?: {
|
|
1310
|
+
displayId?: string;
|
|
1311
|
+
};
|
|
1312
|
+
}): string;
|
|
1313
|
+
|
|
1302
1314
|
declare function injectPrintStyle(): void;
|
|
1303
1315
|
|
|
1304
1316
|
/**
|
|
@@ -1402,4 +1414,4 @@ interface StoreSettingsChoiceScreenProps extends Omit<ViewProps, 'children'> {
|
|
|
1402
1414
|
*/
|
|
1403
1415
|
declare function StoreSettingsChoiceScreen({ choices, initial, onSubmit, countryName, locale, title, body, regionLabel, countryLabel, channelLabel, submitLabel, className, ...viewProps }: StoreSettingsChoiceScreenProps): react_jsx_runtime.JSX.Element;
|
|
1404
1416
|
|
|
1405
|
-
export { APPROVAL_REQUIRED_TEXT, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Cart, type CartAction, CartBar, CartLine, CartLineActions, type CartLineActionsProps, type CartLineProps, CartNoteInput, type CartNoteInputProps, CartPanel, type CartPanelProps, CartTotal, type CartTotalProps, CashCountInput, type CashCountInputProps, CashTendered, type CashTenderedProps, Catalogue, type CategoryItem, CategoryNav, type CategoryNavProps, ChangeDisplay, type ChangeDisplayProps, Checkbox, CheckoutLayout, type CheckoutLayoutProps, type ChipItem, ClosureSheet, type ClosureSheetProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxTrigger, type ConnectionStatus, ConnectorStatus, type ConnectorStatusProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CustomerCard, type CustomerCardProps, CustomerForm, type CustomerFormProps, type CustomerFormValues, CustomerOrderHistory, type CustomerOrderHistoryProps, CustomerPicker, type CustomerPickerProps, CustomerSelect, type CustomerSelectProps, type Denomination, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiscountBadge, type DiscountBadgeProps, DiscountChips, DiscountForm, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FilterChip, FilterChipGroup, type FilterChipGroupProps, type FilterChipProps, HStack, type HStackProps, HoverCard, HoverCardContent, HoverCardTrigger, Icon, IconButton, type IconButtonProps, type IconProps, Input, InputField, InputRoot, type InputRootProps, InputSlot, Label, type LabelProps, LiveTabScreen, type LiveTabScreenProps, type LiveTabScreenState, Loader, type LoaderProps, MovementSheet, type MovementSheetProps, OpenRegisterCard, type OpenRegisterCardProps, OrderCard, type OrderCardProps, OrderDetail, type OrderDetailLineItem, type OrderDetailProps, OrderList, type OrderListProps, type OrderStatus, OrderStatusBadge, type OrderStatusBadgeProps, OrderSummary, OrderSummaryLine, type OrderSummaryLineProps, type OrderSummaryPayment, type OrderSummaryProps, OrdersList, POSLayout, type POSLayoutProps, type PaymentMethod, PaymentMethodCard, type PaymentMethodCardProps, PaymentSelector, type PaymentSelectorProps, Popover, PopoverContent, PopoverTrigger, ProductCard, type ProductCardProps, ProductGrid, type ProductGridProps, ProductImage, type ProductImageProps, ProductList, type ProductListProps, ProductPrice, type ProductPriceProps, ProductSku, type ProductSkuProps, ProductStockBadge, type ProductStockBadgeProps, ProductTitle, type ProductTitleProps, ProductVariantPicker, type ProductVariantPickerProps, Progress, type ProgressProps, QuantityStepper, type QuantityStepperProps, RadioGroup, RadioGroupItem, Receipt, type ReceiptItem, ReceiptPreview, type ReceiptPreviewProps, RegisterBar, type RegisterBarPill, type RegisterBarProps, RegisterColumn, type RegisterColumnProps, RegisterCount, type RegisterCountProps, RegisterOpenClose, type RegisterOpenCloseProps, RegisterPanel, type RegisterPanelProps, RegisterPicker, type RegisterPickerProps, type RegisterPickerRegister, RegisterSummary, type RegisterSummaryProps, SearchIcon, SearchInput, type SearchInputProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, SettingsGroup, type SettingsGroupProps, SettingsLayout, type SettingsLayoutProps, SettingsRow, type SettingsRowProps, Slider, type SliderProps, StoreSettingsChoiceScreen, type StoreSettingsChoiceScreenProps, type SvgIconProps, Switch, SyncStatus, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tender, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastClose, ToastDescription, type ToastProps, ToastTitle, Toggle, ToggleGroup, ToggleGroupItem, type ToggleGroupItemProps, type ToggleGroupProps, type ToggleProps, Tooltip, TooltipContent, TooltipTrigger, type TransactionSummary, VStack, type VStackProps, type VariantOption, XIcon, avatarVariants, badgeTextVariants, badgeVariants, buttonTextVariants, buttonVariants, createSvgIcon, describeRegisterBarPill, discountLabel, formatStockSyncTime, hstackVariants, iconButtonVariants, iconVariants, injectPrintStyle, loaderVariants, parseDiscount, textVariants, toastVariants, toggleTextVariants, toggleVariants, useInputContext, vstackVariants };
|
|
1417
|
+
export { APPROVAL_REQUIRED_TEXT, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, type AvatarProps, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Cart, type CartAction, CartBar, CartLine, CartLineActions, type CartLineActionsProps, type CartLineProps, CartNoteInput, type CartNoteInputProps, CartPanel, type CartPanelProps, CartTotal, type CartTotalProps, CashCountInput, type CashCountInputProps, CashTendered, type CashTenderedProps, Catalogue, type CategoryItem, CategoryNav, type CategoryNavProps, ChangeDisplay, type ChangeDisplayProps, Checkbox, CheckoutLayout, type CheckoutLayoutProps, type ChipItem, ClosureSheet, type ClosureSheetProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxTrigger, type ConnectionStatus, ConnectorStatus, type ConnectorStatusProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CustomerCard, type CustomerCardProps, CustomerForm, type CustomerFormProps, type CustomerFormValues, CustomerOrderHistory, type CustomerOrderHistoryProps, CustomerPicker, type CustomerPickerProps, CustomerSelect, type CustomerSelectProps, type Denomination, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiscountBadge, type DiscountBadgeProps, DiscountChips, DiscountForm, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FilterChip, FilterChipGroup, type FilterChipGroupProps, type FilterChipProps, HStack, type HStackProps, HoverCard, HoverCardContent, HoverCardTrigger, Icon, IconButton, type IconButtonProps, type IconProps, Input, InputField, InputRoot, type InputRootProps, InputSlot, Label, type LabelProps, LiveTabScreen, type LiveTabScreenProps, type LiveTabScreenState, Loader, type LoaderProps, MovementSheet, type MovementSheetProps, OpenRegisterCard, type OpenRegisterCardProps, OrderCard, type OrderCardProps, OrderDetail, type OrderDetailLineItem, type OrderDetailProps, OrderList, type OrderListProps, type OrderStatus, OrderStatusBadge, type OrderStatusBadgeProps, OrderSummary, OrderSummaryLine, type OrderSummaryLineProps, type OrderSummaryPayment, type OrderSummaryProps, OrdersList, POSLayout, type POSLayoutProps, type PaymentMethod, PaymentMethodCard, type PaymentMethodCardProps, PaymentSelector, type PaymentSelectorProps, Popover, PopoverContent, PopoverTrigger, ProductCard, type ProductCardProps, ProductGrid, type ProductGridProps, ProductImage, type ProductImageProps, ProductList, type ProductListProps, ProductPrice, type ProductPriceProps, ProductSku, type ProductSkuProps, ProductStockBadge, type ProductStockBadgeProps, ProductTitle, type ProductTitleProps, ProductVariantPicker, type ProductVariantPickerProps, Progress, type ProgressProps, QuantityStepper, type QuantityStepperProps, RadioGroup, RadioGroupItem, Receipt, type ReceiptItem, ReceiptPreview, type ReceiptPreviewProps, RegisterBar, type RegisterBarPill, type RegisterBarProps, RegisterColumn, type RegisterColumnProps, RegisterCount, type RegisterCountProps, RegisterOpenClose, type RegisterOpenCloseProps, RegisterPanel, type RegisterPanelProps, RegisterPicker, type RegisterPickerProps, type RegisterPickerRegister, RegisterSummary, type RegisterSummaryProps, SearchIcon, SearchInput, type SearchInputProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, SettingsGroup, type SettingsGroupProps, SettingsLayout, type SettingsLayoutProps, SettingsRow, type SettingsRowProps, Slider, type SliderProps, StoreSettingsChoiceScreen, type StoreSettingsChoiceScreenProps, type SvgIconProps, Switch, SyncStatus, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tender, Text, type TextProps, Textarea, type TextareaProps, Toast, ToastClose, ToastDescription, type ToastProps, ToastTitle, Toggle, ToggleGroup, ToggleGroupItem, type ToggleGroupItemProps, type ToggleGroupProps, type ToggleProps, Tooltip, TooltipContent, TooltipTrigger, type TransactionSummary, VStack, type VStackProps, type VariantOption, XIcon, avatarVariants, badgeTextVariants, badgeVariants, buttonTextVariants, buttonVariants, createSvgIcon, describeRegisterBarPill, discountLabel, formatStockSyncTime, hstackVariants, iconButtonVariants, iconVariants, injectPrintStyle, loaderVariants, orderReference, parseDiscount, textVariants, toastVariants, toggleTextVariants, toggleVariants, useInputContext, vstackVariants };
|
package/dist/index.js
CHANGED
|
@@ -3341,194 +3341,13 @@ function MovementSheet({ register, type, currency, onDone, onOpenChange, onOpenD
|
|
|
3341
3341
|
}
|
|
3342
3342
|
|
|
3343
3343
|
// src/register/register-panel.tsx
|
|
3344
|
-
import { useState as useState11 } from "react";
|
|
3345
|
-
import { ScrollView as ScrollView11, useWindowDimensions, View as View47 } from "react-native";
|
|
3346
|
-
import { formatMoney as formatMoney7 } from "@tallyui/core";
|
|
3347
|
-
import { jsx as jsx81, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
3348
|
-
var DIALOG_VERTICAL_MARGIN = 64;
|
|
3349
|
-
function RegisterPanel({ register, currency, registerName, open, onOpenChange, className }) {
|
|
3350
|
-
const { session, expected, salesCount, blind, movements, lastClosure, actions } = register;
|
|
3351
|
-
const { height: windowHeight } = useWindowDimensions();
|
|
3352
|
-
const maxHeight = Math.max(280, windowHeight - DIALOG_VERTICAL_MARGIN);
|
|
3353
|
-
const [movementType, setMovementType] = useState11(null);
|
|
3354
|
-
const [expanded, setExpanded] = useState11(false);
|
|
3355
|
-
const [error, setError] = useState11("");
|
|
3356
|
-
const attempt = async (action) => {
|
|
3357
|
-
try {
|
|
3358
|
-
await action();
|
|
3359
|
-
} catch (e) {
|
|
3360
|
-
setError(String(e));
|
|
3361
|
-
}
|
|
3362
|
-
};
|
|
3363
|
-
if (!session && !lastClosure) return null;
|
|
3364
|
-
const activeMovements = movements.filter(
|
|
3365
|
-
(row) => row.type !== "void" && !row.voided_by && !movements.some((entry) => entry.voids === row.id)
|
|
3366
|
-
);
|
|
3367
|
-
return /* @__PURE__ */ jsx81(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs40(
|
|
3368
|
-
DialogContent,
|
|
3369
|
-
{
|
|
3370
|
-
testID: "register-panel",
|
|
3371
|
-
className: `flex-col overflow-hidden ${className ?? ""}`,
|
|
3372
|
-
style: { maxHeight },
|
|
3373
|
-
children: [
|
|
3374
|
-
/* @__PURE__ */ jsxs40(View47, { className: "flex-row items-center justify-between gap-2", children: [
|
|
3375
|
-
/* @__PURE__ */ jsx81(DialogTitle, { testID: "register-panel-amount", children: registerName ?? "Register" }),
|
|
3376
|
-
/* @__PURE__ */ jsx81(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx81(IconButton, { variant: "ghost", size: "sm", testID: "register-panel-dismiss", accessibilityLabel: "Close panel", children: /* @__PURE__ */ jsx81(XIcon, { size: 16 }) }) })
|
|
3377
|
-
] }),
|
|
3378
|
-
/* @__PURE__ */ jsxs40(Text, { testID: "register-panel-sales-count", children: [
|
|
3379
|
-
salesCount,
|
|
3380
|
-
" sale",
|
|
3381
|
-
salesCount === 1 ? "" : "s",
|
|
3382
|
-
" this session"
|
|
3383
|
-
] }),
|
|
3384
|
-
/* @__PURE__ */ jsxs40(ScrollView11, { testID: "register-panel-body", className: "flex-1", contentContainerClassName: "gap-3", children: [
|
|
3385
|
-
/* @__PURE__ */ jsx81(View47, { className: "flex-row gap-2", children: ["paid_in", "paid_out", "no_sale"].map((type) => /* @__PURE__ */ jsx81(
|
|
3386
|
-
Button,
|
|
3387
|
-
{
|
|
3388
|
-
testID: `register-panel-${type.replace("_", "-")}`,
|
|
3389
|
-
className: "min-h-14 flex-1",
|
|
3390
|
-
variant: "outline",
|
|
3391
|
-
disabled: session?.status !== "open",
|
|
3392
|
-
onPress: () => setMovementType(type),
|
|
3393
|
-
children: /* @__PURE__ */ jsx81(Text, { children: type === "paid_in" ? "Paid in" : type === "paid_out" ? "Paid out" : "No sale" })
|
|
3394
|
-
},
|
|
3395
|
-
type
|
|
3396
|
-
)) }),
|
|
3397
|
-
!blind && /* @__PURE__ */ jsxs40(View47, { testID: "register-panel-expected", className: "gap-1", children: [
|
|
3398
|
-
/* @__PURE__ */ jsx81(Text, { className: "text-muted-foreground text-sm font-semibold", children: "Expected in the drawer" }),
|
|
3399
|
-
Object.entries(expected).map(([method, amount]) => /* @__PURE__ */ jsxs40(View47, { className: "min-h-11 flex-row items-center justify-between", children: [
|
|
3400
|
-
/* @__PURE__ */ jsx81(Text, { className: "capitalize", children: method === "cash" ? "Cash" : method }),
|
|
3401
|
-
/* @__PURE__ */ jsx81(Text, { className: "tabular-nums", children: formatMoney7({ amount, currency }) })
|
|
3402
|
-
] }, method))
|
|
3403
|
-
] }),
|
|
3404
|
-
/* @__PURE__ */ jsx81(
|
|
3405
|
-
Button,
|
|
3406
|
-
{
|
|
3407
|
-
testID: "register-panel-movements",
|
|
3408
|
-
variant: "ghost",
|
|
3409
|
-
className: "min-h-11",
|
|
3410
|
-
onPress: () => setExpanded(!expanded),
|
|
3411
|
-
children: /* @__PURE__ */ jsx81(Text, { children: "Paid in / out" })
|
|
3412
|
-
}
|
|
3413
|
-
),
|
|
3414
|
-
expanded && activeMovements.map((row) => /* @__PURE__ */ jsxs40(View47, { className: "min-h-11 flex-row items-center gap-2", children: [
|
|
3415
|
-
/* @__PURE__ */ jsxs40(Text, { testID: `movement-row-${row.id}`, className: "flex-1", children: [
|
|
3416
|
-
row.type === "paid_in" ? "Paid in" : row.type === "paid_out" ? "Paid out" : "No sale",
|
|
3417
|
-
!blind ? ` \xB7 ${formatMoney7({ amount: row.amountMinor, currency })} \xB7 ${row.reason}` : ""
|
|
3418
|
-
] }),
|
|
3419
|
-
/* @__PURE__ */ jsx81(
|
|
3420
|
-
Button,
|
|
3421
|
-
{
|
|
3422
|
-
variant: "ghost",
|
|
3423
|
-
className: "min-h-11",
|
|
3424
|
-
testID: `movement-void-${row.id}`,
|
|
3425
|
-
disabled: session?.status !== "open",
|
|
3426
|
-
onPress: () => attempt(() => actions.voidMovement(row.id)),
|
|
3427
|
-
children: /* @__PURE__ */ jsx81(Text, { children: "Undo" })
|
|
3428
|
-
}
|
|
3429
|
-
)
|
|
3430
|
-
] }, row.id)),
|
|
3431
|
-
!!error && /* @__PURE__ */ jsx81(Text, { className: "text-destructive", children: error }),
|
|
3432
|
-
/* @__PURE__ */ jsx81(
|
|
3433
|
-
Button,
|
|
3434
|
-
{
|
|
3435
|
-
testID: "register-panel-close",
|
|
3436
|
-
disabled: !session,
|
|
3437
|
-
variant: "outline",
|
|
3438
|
-
className: "min-h-14",
|
|
3439
|
-
onPress: () => attempt(async () => {
|
|
3440
|
-
await actions.startCounting();
|
|
3441
|
-
onOpenChange(false);
|
|
3442
|
-
}),
|
|
3443
|
-
children: /* @__PURE__ */ jsx81(Text, { children: "Close register" })
|
|
3444
|
-
}
|
|
3445
|
-
)
|
|
3446
|
-
] }),
|
|
3447
|
-
movementType && /* @__PURE__ */ jsx81(
|
|
3448
|
-
MovementSheet,
|
|
3449
|
-
{
|
|
3450
|
-
register,
|
|
3451
|
-
type: movementType,
|
|
3452
|
-
currency,
|
|
3453
|
-
onOpenChange: (value) => {
|
|
3454
|
-
if (!value) setMovementType(null);
|
|
3455
|
-
},
|
|
3456
|
-
onDone: () => setMovementType(null)
|
|
3457
|
-
}
|
|
3458
|
-
)
|
|
3459
|
-
]
|
|
3460
|
-
}
|
|
3461
|
-
) });
|
|
3462
|
-
}
|
|
3463
|
-
|
|
3464
|
-
// src/register/register-column.tsx
|
|
3465
3344
|
import { useState as useState12 } from "react";
|
|
3466
|
-
import { View as View48 } from "react-native";
|
|
3467
|
-
import {
|
|
3468
|
-
function RegisterColumn({
|
|
3469
|
-
register,
|
|
3470
|
-
registerId,
|
|
3471
|
-
registers,
|
|
3472
|
-
onPick,
|
|
3473
|
-
currency,
|
|
3474
|
-
configuredFloatMinor,
|
|
3475
|
-
countSlot,
|
|
3476
|
-
cartEmpty,
|
|
3477
|
-
children
|
|
3478
|
-
}) {
|
|
3479
|
-
const [finishing, setFinishing] = useState12(false);
|
|
3480
|
-
if (registerId === null) {
|
|
3481
|
-
return /* @__PURE__ */ jsx82(RegisterPicker, { registers, onPick, className: "flex-1" });
|
|
3482
|
-
}
|
|
3483
|
-
if (register.session?.status === "closed") {
|
|
3484
|
-
return register.closing && !finishing ? /* @__PURE__ */ jsx82(Fragment4, { children: countSlot }) : /* @__PURE__ */ jsx82(FinishClose, { register, busy: finishing, setBusy: setFinishing });
|
|
3485
|
-
}
|
|
3486
|
-
if (!register.session) {
|
|
3487
|
-
return /* @__PURE__ */ jsx82(OpenRegisterCard, { register, currency, configuredFloatMinor, className: "flex-1" });
|
|
3488
|
-
}
|
|
3489
|
-
if (register.session.status === "counting") {
|
|
3490
|
-
return /* @__PURE__ */ jsx82(Fragment4, { children: countSlot });
|
|
3491
|
-
}
|
|
3492
|
-
return /* @__PURE__ */ jsxs41(View48, { testID: "register-column", className: "flex-1", children: [
|
|
3493
|
-
register.overdue && cartEmpty && /* @__PURE__ */ jsxs41(View48, { testID: "register-column-overdue", className: "bg-warning/10 flex-row items-center justify-between gap-2 px-3 py-2", children: [
|
|
3494
|
-
/* @__PURE__ */ jsx82(Text, { className: "flex-1", children: "This register is overdue for closing." }),
|
|
3495
|
-
/* @__PURE__ */ jsx82(
|
|
3496
|
-
Button,
|
|
3497
|
-
{
|
|
3498
|
-
testID: "register-column-close-overdue",
|
|
3499
|
-
variant: "outline",
|
|
3500
|
-
className: "min-h-11",
|
|
3501
|
-
onPress: () => register.actions.startCounting(),
|
|
3502
|
-
children: /* @__PURE__ */ jsx82(Text, { children: "Close register" })
|
|
3503
|
-
}
|
|
3504
|
-
)
|
|
3505
|
-
] }),
|
|
3506
|
-
children
|
|
3507
|
-
] });
|
|
3508
|
-
}
|
|
3509
|
-
function FinishClose({ register, busy, setBusy }) {
|
|
3510
|
-
const [error, setError] = useState12("");
|
|
3511
|
-
const finish = async () => {
|
|
3512
|
-
setBusy(true);
|
|
3513
|
-
setError("");
|
|
3514
|
-
try {
|
|
3515
|
-
await register.actions.closeSession({ counted: register.session?.counted ?? {} });
|
|
3516
|
-
} catch (e) {
|
|
3517
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
3518
|
-
} finally {
|
|
3519
|
-
setBusy(false);
|
|
3520
|
-
}
|
|
3521
|
-
};
|
|
3522
|
-
return /* @__PURE__ */ jsxs41(View48, { testID: "register-column-finish-close", className: "flex-1 gap-3 p-4", children: [
|
|
3523
|
-
/* @__PURE__ */ jsx82(Text, { children: "The last close didn't finish." }),
|
|
3524
|
-
/* @__PURE__ */ jsx82(Button, { testID: "register-column-finish-close-button", nativeID: "register-column-finish-close-button", disabled: busy, onPress: finish, children: /* @__PURE__ */ jsx82(Text, { children: "Finish closing" }) }),
|
|
3525
|
-
!!error && /* @__PURE__ */ jsx82(Text, { testID: "register-column-finish-close-error", className: "text-destructive", children: error })
|
|
3526
|
-
] });
|
|
3527
|
-
}
|
|
3345
|
+
import { ScrollView as ScrollView12, useWindowDimensions as useWindowDimensions2, View as View48 } from "react-native";
|
|
3346
|
+
import { formatMoney as formatMoney8 } from "@tallyui/core";
|
|
3528
3347
|
|
|
3529
3348
|
// src/register/register-count.tsx
|
|
3530
|
-
import { useEffect as useEffect3, useRef as useRef4, useState as
|
|
3531
|
-
import { Platform, Pressable as Pressable22, ScrollView as
|
|
3349
|
+
import { useEffect as useEffect3, useRef as useRef4, useState as useState11 } from "react";
|
|
3350
|
+
import { Platform, Pressable as Pressable22, ScrollView as ScrollView11, useWindowDimensions, View as View47 } from "react-native";
|
|
3532
3351
|
import {
|
|
3533
3352
|
closeNeedsApproval,
|
|
3534
3353
|
countVariance,
|
|
@@ -3539,9 +3358,9 @@ import {
|
|
|
3539
3358
|
validAmount as validAmount2,
|
|
3540
3359
|
varianceText
|
|
3541
3360
|
} from "@tallyui/pos";
|
|
3542
|
-
import { currencySymbol as currencySymbol3, formatMoney as
|
|
3361
|
+
import { currencySymbol as currencySymbol3, formatMoney as formatMoney7, minorUnitDigits as minorUnitDigits5 } from "@tallyui/core";
|
|
3543
3362
|
import { cn as cn76 } from "@tallyui/theme";
|
|
3544
|
-
import { jsx as
|
|
3363
|
+
import { jsx as jsx81, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
3545
3364
|
var AMOUNT_LABEL_ID3 = "count-amount-label";
|
|
3546
3365
|
var APPROVAL_REQUIRED_TEXT = "Manager approval needed. Ask a manager to approve, or count again.";
|
|
3547
3366
|
function varianceWord(key) {
|
|
@@ -3562,7 +3381,7 @@ function tileLabel(value, currency) {
|
|
|
3562
3381
|
}
|
|
3563
3382
|
return formatter.format(value / 10 ** minorUnitDigits5(currency));
|
|
3564
3383
|
}
|
|
3565
|
-
var tenderLabel = (method) => method.charAt(0).toUpperCase() + method.slice(1);
|
|
3384
|
+
var tenderLabel = (method) => (/* @__PURE__ */ new Map([["cash", "Cash"], ["external", "Card"]])).get(method) ?? method.charAt(0).toUpperCase() + method.slice(1);
|
|
3566
3385
|
function DenominationTile({ value, count: count2, currency, onAdd }) {
|
|
3567
3386
|
const label = tileLabel(value, currency);
|
|
3568
3387
|
const held = useRef4(false);
|
|
@@ -3572,7 +3391,7 @@ function DenominationTile({ value, count: count2, currency, onAdd }) {
|
|
|
3572
3391
|
timer.current = void 0;
|
|
3573
3392
|
};
|
|
3574
3393
|
useEffect3(() => stop, []);
|
|
3575
|
-
return /* @__PURE__ */
|
|
3394
|
+
return /* @__PURE__ */ jsxs40(
|
|
3576
3395
|
Pressable22,
|
|
3577
3396
|
{
|
|
3578
3397
|
testID: `den-tile-${value}`,
|
|
@@ -3594,8 +3413,8 @@ function DenominationTile({ value, count: count2, currency, onAdd }) {
|
|
|
3594
3413
|
if (!held.current) onAdd(1);
|
|
3595
3414
|
},
|
|
3596
3415
|
children: [
|
|
3597
|
-
/* @__PURE__ */
|
|
3598
|
-
/* @__PURE__ */
|
|
3416
|
+
/* @__PURE__ */ jsx81(Text, { className: "text-sm tabular-nums", numberOfLines: 1, children: label }),
|
|
3417
|
+
/* @__PURE__ */ jsx81(Text, { testID: `den-count-${value}`, className: "bg-background min-w-6 rounded-full px-1.5 text-center text-xs tabular-nums", children: count2 })
|
|
3599
3418
|
]
|
|
3600
3419
|
}
|
|
3601
3420
|
);
|
|
@@ -3604,20 +3423,20 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3604
3423
|
const { session, expected, blind, varianceThreshold, actions } = register;
|
|
3605
3424
|
const digits = minorUnitDigits5(currency);
|
|
3606
3425
|
const faces = denominations[currency] ?? denominations.default;
|
|
3607
|
-
const { width } =
|
|
3426
|
+
const { width } = useWindowDimensions();
|
|
3608
3427
|
const columns = width < 480 ? 3 : 4;
|
|
3609
|
-
const [pieces, setPieces] =
|
|
3610
|
-
const [cashText, setCashText] =
|
|
3428
|
+
const [pieces, setPieces] = useState11({});
|
|
3429
|
+
const [cashText, setCashText] = useState11(
|
|
3611
3430
|
() => session?.counted?.cash != null ? minorToDecimal(session.counted.cash, digits) : ""
|
|
3612
3431
|
);
|
|
3613
|
-
const [others, setOthers] =
|
|
3432
|
+
const [others, setOthers] = useState11(
|
|
3614
3433
|
() => Object.fromEntries(
|
|
3615
3434
|
Object.entries(session?.counted ?? {}).filter(([method]) => method !== "cash").map(([method, minor]) => [method, minorToDecimal(minor, digits)])
|
|
3616
3435
|
)
|
|
3617
3436
|
);
|
|
3618
|
-
const [busy, setBusy] =
|
|
3619
|
-
const [error, setError] =
|
|
3620
|
-
const [refusal, setRefusal] =
|
|
3437
|
+
const [busy, setBusy] = useState11(false);
|
|
3438
|
+
const [error, setError] = useState11("");
|
|
3439
|
+
const [refusal, setRefusal] = useState11("");
|
|
3621
3440
|
const usingTiles = Object.keys(pieces).length > 0;
|
|
3622
3441
|
const tilesTotal = denominationTotal(pieces);
|
|
3623
3442
|
const displayAmount = usingTiles ? minorToDecimal(tilesTotal, digits) : cashText;
|
|
@@ -3665,11 +3484,11 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3665
3484
|
void attempt(() => actions.closeSession({ counted }));
|
|
3666
3485
|
};
|
|
3667
3486
|
if (!session) return null;
|
|
3668
|
-
return /* @__PURE__ */
|
|
3669
|
-
/* @__PURE__ */
|
|
3670
|
-
/* @__PURE__ */
|
|
3671
|
-
/* @__PURE__ */
|
|
3672
|
-
/* @__PURE__ */
|
|
3487
|
+
return /* @__PURE__ */ jsxs40(View47, { testID: "register-count", className: cn76("bg-card flex-1 rounded-md", className), children: [
|
|
3488
|
+
/* @__PURE__ */ jsxs40(ScrollView11, { className: "flex-1", contentContainerClassName: "gap-3 p-4", children: [
|
|
3489
|
+
/* @__PURE__ */ jsx81(Text, { className: "min-h-11 text-lg font-semibold", children: "Count the drawer" }),
|
|
3490
|
+
/* @__PURE__ */ jsx81(Label, { testID: "count-amount-label", nativeID: AMOUNT_LABEL_ID3, children: `Cash counted (${currencySymbol3(currency)})` }),
|
|
3491
|
+
/* @__PURE__ */ jsx81(Input, { children: /* @__PURE__ */ jsx81(
|
|
3673
3492
|
Input.Field,
|
|
3674
3493
|
{
|
|
3675
3494
|
testID: "count-amount",
|
|
@@ -3683,16 +3502,16 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3683
3502
|
accessibilityLabelledBy: AMOUNT_LABEL_ID3
|
|
3684
3503
|
}
|
|
3685
3504
|
) }),
|
|
3686
|
-
!blind && valid && /* @__PURE__ */
|
|
3505
|
+
!blind && valid && /* @__PURE__ */ jsx81(Text, { testID: "count-variance", className: "min-h-11 tabular-nums", children: `Expected ${formatMoney7({ amount: expected.cash ?? 0, currency })} \xB7 ${varianceText(
|
|
3687
3506
|
variance,
|
|
3688
3507
|
digits,
|
|
3689
|
-
(major) =>
|
|
3508
|
+
(major) => formatMoney7({ amount: Math.round(major * 10 ** digits), currency }) ?? "",
|
|
3690
3509
|
varianceWord
|
|
3691
3510
|
)}` }),
|
|
3692
|
-
/* @__PURE__ */
|
|
3693
|
-
Array.from({ length: Math.ceil(faces.length / columns) }, (_, row) => /* @__PURE__ */
|
|
3511
|
+
/* @__PURE__ */ jsxs40(View47, { testID: "count-denominations", className: "gap-2", children: [
|
|
3512
|
+
Array.from({ length: Math.ceil(faces.length / columns) }, (_, row) => /* @__PURE__ */ jsx81(View47, { className: "flex-row gap-2", children: Array.from({ length: columns }, (_2, column) => {
|
|
3694
3513
|
const value = faces[row * columns + column];
|
|
3695
|
-
return value ? /* @__PURE__ */
|
|
3514
|
+
return value ? /* @__PURE__ */ jsx81(
|
|
3696
3515
|
DenominationTile,
|
|
3697
3516
|
{
|
|
3698
3517
|
value,
|
|
@@ -3701,9 +3520,9 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3701
3520
|
onAdd: (n) => addPieces(value, n)
|
|
3702
3521
|
},
|
|
3703
3522
|
value
|
|
3704
|
-
) : /* @__PURE__ */
|
|
3523
|
+
) : /* @__PURE__ */ jsx81(View47, { className: "flex-1" }, column);
|
|
3705
3524
|
}) }, row)),
|
|
3706
|
-
/* @__PURE__ */
|
|
3525
|
+
/* @__PURE__ */ jsx81(
|
|
3707
3526
|
Button,
|
|
3708
3527
|
{
|
|
3709
3528
|
testID: "count-clear",
|
|
@@ -3713,16 +3532,16 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3713
3532
|
setPieces({});
|
|
3714
3533
|
setCashText(minorToDecimal(0, digits));
|
|
3715
3534
|
},
|
|
3716
|
-
children: /* @__PURE__ */
|
|
3535
|
+
children: /* @__PURE__ */ jsx81(Text, { children: "Clear" })
|
|
3717
3536
|
}
|
|
3718
3537
|
)
|
|
3719
3538
|
] }),
|
|
3720
3539
|
otherMethods.map((method) => {
|
|
3721
3540
|
const labelId = `count-tender-${method}-label`;
|
|
3722
3541
|
const label = `${tenderLabel(method)} counted (${currencySymbol3(currency)})`;
|
|
3723
|
-
return /* @__PURE__ */
|
|
3724
|
-
/* @__PURE__ */
|
|
3725
|
-
/* @__PURE__ */
|
|
3542
|
+
return /* @__PURE__ */ jsxs40(View47, { className: "gap-2", children: [
|
|
3543
|
+
/* @__PURE__ */ jsx81(Label, { testID: labelId, nativeID: labelId, children: label }),
|
|
3544
|
+
/* @__PURE__ */ jsx81(Input, { children: /* @__PURE__ */ jsx81(
|
|
3726
3545
|
Input.Field,
|
|
3727
3546
|
{
|
|
3728
3547
|
testID: `count-tender-${method}`,
|
|
@@ -3735,11 +3554,11 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3735
3554
|
) })
|
|
3736
3555
|
] }, method);
|
|
3737
3556
|
}),
|
|
3738
|
-
!!refusal && /* @__PURE__ */
|
|
3739
|
-
!!error && /* @__PURE__ */
|
|
3557
|
+
!!refusal && /* @__PURE__ */ jsx81(Text, { testID: "count-manager-line", className: "text-warning min-h-11", children: refusal }),
|
|
3558
|
+
!!error && /* @__PURE__ */ jsx81(Text, { testID: "count-error", className: "text-destructive min-h-11", children: error })
|
|
3740
3559
|
] }),
|
|
3741
|
-
/* @__PURE__ */
|
|
3742
|
-
/* @__PURE__ */
|
|
3560
|
+
/* @__PURE__ */ jsxs40(View47, { className: "gap-2 border-t border-border p-4", children: [
|
|
3561
|
+
/* @__PURE__ */ jsx81(
|
|
3743
3562
|
Button,
|
|
3744
3563
|
{
|
|
3745
3564
|
testID: "count-back",
|
|
@@ -3747,23 +3566,214 @@ function RegisterCount({ register, currency, approve, className }) {
|
|
|
3747
3566
|
className: "min-h-11",
|
|
3748
3567
|
disabled: busy,
|
|
3749
3568
|
onPress: () => attempt(actions.backToSelling),
|
|
3750
|
-
children: /* @__PURE__ */
|
|
3569
|
+
children: /* @__PURE__ */ jsx81(Text, { children: "Back to selling" })
|
|
3751
3570
|
}
|
|
3752
3571
|
),
|
|
3753
|
-
/* @__PURE__ */
|
|
3572
|
+
/* @__PURE__ */ jsx81(
|
|
3754
3573
|
Button,
|
|
3755
3574
|
{
|
|
3756
3575
|
testID: "count-close",
|
|
3757
3576
|
className: "min-h-14",
|
|
3758
3577
|
disabled: !valid || otherTendersInvalid || busy,
|
|
3759
3578
|
onPress: onClose,
|
|
3760
|
-
children: /* @__PURE__ */
|
|
3579
|
+
children: /* @__PURE__ */ jsx81(Text, { children: busy ? "Closing\u2026" : "Close register" })
|
|
3761
3580
|
}
|
|
3762
3581
|
)
|
|
3763
3582
|
] })
|
|
3764
3583
|
] });
|
|
3765
3584
|
}
|
|
3766
3585
|
|
|
3586
|
+
// src/register/register-panel.tsx
|
|
3587
|
+
import { jsx as jsx82, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
3588
|
+
var DIALOG_VERTICAL_MARGIN = 64;
|
|
3589
|
+
function RegisterPanel({ register, currency, registerName, open, onOpenChange, className }) {
|
|
3590
|
+
const { session, expected, salesCount, blind, movements, lastClosure, actions } = register;
|
|
3591
|
+
const otherTenders = Object.entries(expected).filter(([method, amount]) => method !== "cash" && amount !== 0);
|
|
3592
|
+
const { height: windowHeight } = useWindowDimensions2();
|
|
3593
|
+
const maxHeight = Math.max(280, windowHeight - DIALOG_VERTICAL_MARGIN);
|
|
3594
|
+
const [movementType, setMovementType] = useState12(null);
|
|
3595
|
+
const [expanded, setExpanded] = useState12(false);
|
|
3596
|
+
const [error, setError] = useState12("");
|
|
3597
|
+
const attempt = async (action) => {
|
|
3598
|
+
try {
|
|
3599
|
+
await action();
|
|
3600
|
+
} catch (e) {
|
|
3601
|
+
setError(String(e));
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
if (!session && !lastClosure) return null;
|
|
3605
|
+
const activeMovements = movements.filter(
|
|
3606
|
+
(row) => row.type !== "void" && !row.voided_by && !movements.some((entry) => entry.voids === row.id)
|
|
3607
|
+
);
|
|
3608
|
+
return /* @__PURE__ */ jsx82(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs41(
|
|
3609
|
+
DialogContent,
|
|
3610
|
+
{
|
|
3611
|
+
testID: "register-panel",
|
|
3612
|
+
className: `flex-col overflow-hidden ${className ?? ""}`,
|
|
3613
|
+
style: { maxHeight },
|
|
3614
|
+
children: [
|
|
3615
|
+
/* @__PURE__ */ jsxs41(View48, { className: "flex-row items-center justify-between gap-2", children: [
|
|
3616
|
+
/* @__PURE__ */ jsx82(DialogTitle, { testID: "register-panel-amount", children: registerName ?? "Register" }),
|
|
3617
|
+
/* @__PURE__ */ jsx82(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx82(IconButton, { variant: "ghost", size: "sm", testID: "register-panel-dismiss", accessibilityLabel: "Close panel", children: /* @__PURE__ */ jsx82(XIcon, { size: 16 }) }) })
|
|
3618
|
+
] }),
|
|
3619
|
+
/* @__PURE__ */ jsxs41(Text, { testID: "register-panel-sales-count", children: [
|
|
3620
|
+
salesCount,
|
|
3621
|
+
" sale",
|
|
3622
|
+
salesCount === 1 ? "" : "s",
|
|
3623
|
+
" this session"
|
|
3624
|
+
] }),
|
|
3625
|
+
/* @__PURE__ */ jsxs41(ScrollView12, { testID: "register-panel-body", className: "flex-1", contentContainerClassName: "gap-3", children: [
|
|
3626
|
+
/* @__PURE__ */ jsx82(View48, { className: "flex-row gap-2", children: ["paid_in", "paid_out", "no_sale"].map((type) => /* @__PURE__ */ jsx82(
|
|
3627
|
+
Button,
|
|
3628
|
+
{
|
|
3629
|
+
testID: `register-panel-${type.replace("_", "-")}`,
|
|
3630
|
+
className: "min-h-14 flex-1",
|
|
3631
|
+
variant: "outline",
|
|
3632
|
+
disabled: session?.status !== "open",
|
|
3633
|
+
onPress: () => setMovementType(type),
|
|
3634
|
+
children: /* @__PURE__ */ jsx82(Text, { children: type === "paid_in" ? "Paid in" : type === "paid_out" ? "Paid out" : "No sale" })
|
|
3635
|
+
},
|
|
3636
|
+
type
|
|
3637
|
+
)) }),
|
|
3638
|
+
!blind && /* @__PURE__ */ jsxs41(View48, { testID: "register-panel-expected", className: "gap-1", children: [
|
|
3639
|
+
/* @__PURE__ */ jsx82(Text, { className: "text-muted-foreground text-sm font-semibold", children: "Expected in the drawer" }),
|
|
3640
|
+
Object.entries(expected).filter(([method]) => method === "cash").map(([method, amount]) => /* @__PURE__ */ jsxs41(View48, { className: "min-h-11 flex-row items-center justify-between", children: [
|
|
3641
|
+
/* @__PURE__ */ jsx82(Text, { children: tenderLabel("cash") }),
|
|
3642
|
+
/* @__PURE__ */ jsx82(Text, { className: "tabular-nums", children: formatMoney8({ amount, currency }) })
|
|
3643
|
+
] }, method))
|
|
3644
|
+
] }),
|
|
3645
|
+
!blind && otherTenders.length > 0 && /* @__PURE__ */ jsxs41(View48, { testID: "register-panel-other-tenders", className: "gap-1", children: [
|
|
3646
|
+
/* @__PURE__ */ jsx82(Text, { className: "text-muted-foreground text-sm font-semibold", children: "Other tenders" }),
|
|
3647
|
+
otherTenders.map(([method, amount]) => /* @__PURE__ */ jsxs41(View48, { className: "min-h-11 flex-row items-center justify-between", children: [
|
|
3648
|
+
/* @__PURE__ */ jsx82(Text, { children: tenderLabel(method) }),
|
|
3649
|
+
/* @__PURE__ */ jsx82(Text, { className: "tabular-nums", children: formatMoney8({ amount, currency }) })
|
|
3650
|
+
] }, method))
|
|
3651
|
+
] }),
|
|
3652
|
+
/* @__PURE__ */ jsx82(
|
|
3653
|
+
Button,
|
|
3654
|
+
{
|
|
3655
|
+
testID: "register-panel-movements",
|
|
3656
|
+
variant: "ghost",
|
|
3657
|
+
className: "min-h-11",
|
|
3658
|
+
onPress: () => setExpanded(!expanded),
|
|
3659
|
+
children: /* @__PURE__ */ jsx82(Text, { children: "Paid in / out" })
|
|
3660
|
+
}
|
|
3661
|
+
),
|
|
3662
|
+
expanded && activeMovements.map((row) => /* @__PURE__ */ jsxs41(View48, { className: "min-h-11 flex-row items-center gap-2", children: [
|
|
3663
|
+
/* @__PURE__ */ jsxs41(Text, { testID: `movement-row-${row.id}`, className: "flex-1", children: [
|
|
3664
|
+
row.type === "paid_in" ? "Paid in" : row.type === "paid_out" ? "Paid out" : "No sale",
|
|
3665
|
+
!blind ? ` \xB7 ${formatMoney8({ amount: row.amountMinor, currency })} \xB7 ${row.reason}` : ""
|
|
3666
|
+
] }),
|
|
3667
|
+
/* @__PURE__ */ jsx82(
|
|
3668
|
+
Button,
|
|
3669
|
+
{
|
|
3670
|
+
variant: "ghost",
|
|
3671
|
+
className: "min-h-11",
|
|
3672
|
+
testID: `movement-void-${row.id}`,
|
|
3673
|
+
disabled: session?.status !== "open",
|
|
3674
|
+
onPress: () => attempt(() => actions.voidMovement(row.id)),
|
|
3675
|
+
children: /* @__PURE__ */ jsx82(Text, { children: "Undo" })
|
|
3676
|
+
}
|
|
3677
|
+
)
|
|
3678
|
+
] }, row.id)),
|
|
3679
|
+
!!error && /* @__PURE__ */ jsx82(Text, { className: "text-destructive", children: error }),
|
|
3680
|
+
/* @__PURE__ */ jsx82(
|
|
3681
|
+
Button,
|
|
3682
|
+
{
|
|
3683
|
+
testID: "register-panel-close",
|
|
3684
|
+
disabled: !session,
|
|
3685
|
+
variant: "outline",
|
|
3686
|
+
className: "min-h-14",
|
|
3687
|
+
onPress: () => attempt(async () => {
|
|
3688
|
+
await actions.startCounting();
|
|
3689
|
+
onOpenChange(false);
|
|
3690
|
+
}),
|
|
3691
|
+
children: /* @__PURE__ */ jsx82(Text, { children: "Close register" })
|
|
3692
|
+
}
|
|
3693
|
+
)
|
|
3694
|
+
] }),
|
|
3695
|
+
movementType && /* @__PURE__ */ jsx82(
|
|
3696
|
+
MovementSheet,
|
|
3697
|
+
{
|
|
3698
|
+
register,
|
|
3699
|
+
type: movementType,
|
|
3700
|
+
currency,
|
|
3701
|
+
onOpenChange: (value) => {
|
|
3702
|
+
if (!value) setMovementType(null);
|
|
3703
|
+
},
|
|
3704
|
+
onDone: () => setMovementType(null)
|
|
3705
|
+
}
|
|
3706
|
+
)
|
|
3707
|
+
]
|
|
3708
|
+
}
|
|
3709
|
+
) });
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
// src/register/register-column.tsx
|
|
3713
|
+
import { useState as useState13 } from "react";
|
|
3714
|
+
import { View as View49 } from "react-native";
|
|
3715
|
+
import { Fragment as Fragment4, jsx as jsx83, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
3716
|
+
function RegisterColumn({
|
|
3717
|
+
register,
|
|
3718
|
+
registerId,
|
|
3719
|
+
registers,
|
|
3720
|
+
onPick,
|
|
3721
|
+
currency,
|
|
3722
|
+
configuredFloatMinor,
|
|
3723
|
+
countSlot,
|
|
3724
|
+
cartEmpty,
|
|
3725
|
+
children
|
|
3726
|
+
}) {
|
|
3727
|
+
const [finishing, setFinishing] = useState13(false);
|
|
3728
|
+
if (registerId === null) {
|
|
3729
|
+
return /* @__PURE__ */ jsx83(RegisterPicker, { registers, onPick, className: "flex-1" });
|
|
3730
|
+
}
|
|
3731
|
+
if (register.session?.status === "closed") {
|
|
3732
|
+
return register.closing && !finishing ? /* @__PURE__ */ jsx83(Fragment4, { children: countSlot }) : /* @__PURE__ */ jsx83(FinishClose, { register, busy: finishing, setBusy: setFinishing });
|
|
3733
|
+
}
|
|
3734
|
+
if (!register.session) {
|
|
3735
|
+
return /* @__PURE__ */ jsx83(OpenRegisterCard, { register, currency, configuredFloatMinor, className: "flex-1" });
|
|
3736
|
+
}
|
|
3737
|
+
if (register.session.status === "counting") {
|
|
3738
|
+
return /* @__PURE__ */ jsx83(Fragment4, { children: countSlot });
|
|
3739
|
+
}
|
|
3740
|
+
return /* @__PURE__ */ jsxs42(View49, { testID: "register-column", className: "flex-1", children: [
|
|
3741
|
+
register.overdue && cartEmpty && /* @__PURE__ */ jsxs42(View49, { testID: "register-column-overdue", className: "bg-warning/10 flex-row items-center justify-between gap-2 px-3 py-2", children: [
|
|
3742
|
+
/* @__PURE__ */ jsx83(Text, { className: "flex-1", children: "This register is overdue for closing." }),
|
|
3743
|
+
/* @__PURE__ */ jsx83(
|
|
3744
|
+
Button,
|
|
3745
|
+
{
|
|
3746
|
+
testID: "register-column-close-overdue",
|
|
3747
|
+
variant: "outline",
|
|
3748
|
+
className: "min-h-11",
|
|
3749
|
+
onPress: () => register.actions.startCounting(),
|
|
3750
|
+
children: /* @__PURE__ */ jsx83(Text, { children: "Close register" })
|
|
3751
|
+
}
|
|
3752
|
+
)
|
|
3753
|
+
] }),
|
|
3754
|
+
children
|
|
3755
|
+
] });
|
|
3756
|
+
}
|
|
3757
|
+
function FinishClose({ register, busy, setBusy }) {
|
|
3758
|
+
const [error, setError] = useState13("");
|
|
3759
|
+
const finish = async () => {
|
|
3760
|
+
setBusy(true);
|
|
3761
|
+
setError("");
|
|
3762
|
+
try {
|
|
3763
|
+
await register.actions.closeSession({ counted: register.session?.counted ?? {} });
|
|
3764
|
+
} catch (e) {
|
|
3765
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
3766
|
+
} finally {
|
|
3767
|
+
setBusy(false);
|
|
3768
|
+
}
|
|
3769
|
+
};
|
|
3770
|
+
return /* @__PURE__ */ jsxs42(View49, { testID: "register-column-finish-close", className: "flex-1 gap-3 p-4", children: [
|
|
3771
|
+
/* @__PURE__ */ jsx83(Text, { children: "The last close didn't finish." }),
|
|
3772
|
+
/* @__PURE__ */ jsx83(Button, { testID: "register-column-finish-close-button", nativeID: "register-column-finish-close-button", disabled: busy, onPress: finish, children: /* @__PURE__ */ jsx83(Text, { children: "Finish closing" }) }),
|
|
3773
|
+
!!error && /* @__PURE__ */ jsx83(Text, { testID: "register-column-finish-close-error", className: "text-destructive", children: error })
|
|
3774
|
+
] });
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3767
3777
|
// src/register/closure-sheet.tsx
|
|
3768
3778
|
import { useEffect as useEffect4, useRef as useRef5, useState as useState14 } from "react";
|
|
3769
3779
|
import { AccessibilityInfo, Animated, View as View50 } from "react-native";
|
|
@@ -4250,8 +4260,8 @@ function laterOf(a, b) {
|
|
|
4250
4260
|
if (!b) return a;
|
|
4251
4261
|
return a.getTime() >= b.getTime() ? a : b;
|
|
4252
4262
|
}
|
|
4253
|
-
function Catalogue({ products, traits, currency, onSelect, statusText, statusAccessory, lastSyncedAt, loading, lastStockCheckAt, hour12, minCodeLength }) {
|
|
4254
|
-
const isLoading = loading ?? lastSyncedAt === null;
|
|
4263
|
+
function Catalogue({ products, traits, currency, onSelect, statusText, statusAccessory, lastSyncedAt, loading, pullError, lastStockCheckAt, hour12, minCodeLength }) {
|
|
4264
|
+
const isLoading = loading ?? (lastSyncedAt === null && pullError == null);
|
|
4255
4265
|
const shown = useStockOverlaid(products);
|
|
4256
4266
|
const overlayAsOf = useStockOverlayAsOf();
|
|
4257
4267
|
const parsedOverlayAsOf = overlayAsOf ? new Date(overlayAsOf) : void 0;
|
|
@@ -4261,7 +4271,7 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
|
|
|
4261
4271
|
const [chooserId, setChooserId] = useState17(null);
|
|
4262
4272
|
const [width, setWidth] = useState17(0);
|
|
4263
4273
|
const columns = Math.max(2, Math.min(6, Math.floor((width - 8) / (MIN_TILE_WIDTH + 8))));
|
|
4264
|
-
const entries = useMemo2(() => catalogueEntries(shown, traits), [shown, traits]);
|
|
4274
|
+
const entries = useMemo2(() => catalogueEntries(shown, traits, { currency }), [shown, traits, currency]);
|
|
4265
4275
|
const results = useMemo2(() => searchProducts(shown, query, traits), [shown, query, traits]);
|
|
4266
4276
|
const choices = useMemo2(() => chooserId === null ? [] : entries.filter((entry) => traits.getId(entry.product) === chooserId), [entries, chooserId, traits]);
|
|
4267
4277
|
useEffect5(() => {
|
|
@@ -5074,6 +5084,7 @@ export {
|
|
|
5074
5084
|
iconVariants,
|
|
5075
5085
|
injectPrintStyle,
|
|
5076
5086
|
loaderVariants,
|
|
5087
|
+
orderReference,
|
|
5077
5088
|
parseDiscount,
|
|
5078
5089
|
textVariants,
|
|
5079
5090
|
toastVariants,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/components",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Universal POS UI components for Tally UI",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,22 +27,22 @@
|
|
|
27
27
|
"directory": "packages/components"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@tallyui/core": "^3.0.
|
|
31
|
-
"@tallyui/primitives": "^3.0.
|
|
32
|
-
"@tallyui/theme": "^3.0.
|
|
30
|
+
"@tallyui/core": "^3.0.4",
|
|
31
|
+
"@tallyui/primitives": "^3.0.4",
|
|
32
|
+
"@tallyui/theme": "^3.0.4",
|
|
33
33
|
"react": ">=18",
|
|
34
34
|
"react-native": ">=0.74",
|
|
35
35
|
"react-native-svg": ">=15"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"class-variance-authority": "^0.7.1",
|
|
39
|
-
"@tallyui/pos": "3.0.
|
|
39
|
+
"@tallyui/pos": "3.0.4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@tallyui/connector-medusa": "3.0.
|
|
43
|
-
"@tallyui/connector-vendure": "3.0.
|
|
44
|
-
"@tallyui/connector-woocommerce": "3.0.
|
|
45
|
-
"@tallyui/mock-api": "3.0.
|
|
42
|
+
"@tallyui/connector-medusa": "3.0.4",
|
|
43
|
+
"@tallyui/connector-vendure": "3.0.4",
|
|
44
|
+
"@tallyui/connector-woocommerce": "3.0.4",
|
|
45
|
+
"@tallyui/mock-api": "3.0.4",
|
|
46
46
|
"@types/react": ">=18",
|
|
47
47
|
"react": "^19.2.4",
|
|
48
48
|
"react-native-svg": "15.12.1",
|
package/src/index.ts
CHANGED
|
@@ -155,7 +155,7 @@ export {
|
|
|
155
155
|
// Sale components
|
|
156
156
|
export {
|
|
157
157
|
Cart, CartBar, Tender, DiscountForm, DiscountChips, parseDiscount, discountLabel,
|
|
158
|
-
Catalogue, formatStockSyncTime, Receipt, injectPrintStyle, SyncStatus, OrdersList,
|
|
158
|
+
Catalogue, formatStockSyncTime, Receipt, injectPrintStyle, SyncStatus, OrdersList, orderReference,
|
|
159
159
|
} from './sale';
|
|
160
160
|
|
|
161
161
|
// Layout components
|
|
@@ -52,8 +52,11 @@ function tileLabel(value: number, currency: string): string {
|
|
|
52
52
|
return formatter.format(value / 10 ** minorUnitDigits(currency));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/** Plain-English label for a tender method key: `external` is the till's card tender ('Card');
|
|
56
|
+
* other keys are title-cased. Shared with `ClosureSheet` and `RegisterPanel`. */
|
|
57
|
+
export const tenderLabel = (method: string) =>
|
|
58
|
+
new Map([['cash', 'Cash'], ['external', 'Card']]).get(method) ??
|
|
59
|
+
method.charAt(0).toUpperCase() + method.slice(1);
|
|
57
60
|
|
|
58
61
|
interface DenominationTileProps {
|
|
59
62
|
value: number;
|
|
@@ -9,6 +9,7 @@ import { IconButton } from '../ui/icon-button';
|
|
|
9
9
|
import { XIcon } from '../ui/icon/x-icon';
|
|
10
10
|
import { Text } from '../ui/text';
|
|
11
11
|
import { MovementSheet } from './movement-sheet';
|
|
12
|
+
import { tenderLabel } from './register-count';
|
|
12
13
|
|
|
13
14
|
/** Margin kept clear above and below the dialog (LEDGER-free layout fix, 2026-09-28 review):
|
|
14
15
|
* a phone-height viewport must never let the panel's top go under the page header. */
|
|
@@ -37,6 +38,7 @@ export interface RegisterPanelProps {
|
|
|
37
38
|
*/
|
|
38
39
|
export function RegisterPanel({ register, currency, registerName, open, onOpenChange, className }: RegisterPanelProps) {
|
|
39
40
|
const { session, expected, salesCount, blind, movements, lastClosure, actions } = register;
|
|
41
|
+
const otherTenders = Object.entries(expected).filter(([method, amount]) => method !== 'cash' && amount !== 0);
|
|
40
42
|
const { height: windowHeight } = useWindowDimensions();
|
|
41
43
|
const maxHeight = Math.max(280, windowHeight - DIALOG_VERTICAL_MARGIN);
|
|
42
44
|
const [movementType, setMovementType] = useState<MovementType | null>(null);
|
|
@@ -93,9 +95,20 @@ export function RegisterPanel({ register, currency, registerName, open, onOpenCh
|
|
|
93
95
|
{!blind && (
|
|
94
96
|
<View testID="register-panel-expected" className="gap-1">
|
|
95
97
|
<Text className="text-muted-foreground text-sm font-semibold">Expected in the drawer</Text>
|
|
96
|
-
{Object.entries(expected).map(([method, amount]) => (
|
|
98
|
+
{Object.entries(expected).filter(([method]) => method === 'cash').map(([method, amount]) => (
|
|
97
99
|
<View key={method} className="min-h-11 flex-row items-center justify-between">
|
|
98
|
-
<Text
|
|
100
|
+
<Text>{tenderLabel('cash')}</Text>
|
|
101
|
+
<Text className="tabular-nums">{formatMoney({ amount, currency })}</Text>
|
|
102
|
+
</View>
|
|
103
|
+
))}
|
|
104
|
+
</View>
|
|
105
|
+
)}
|
|
106
|
+
{!blind && otherTenders.length > 0 && (
|
|
107
|
+
<View testID="register-panel-other-tenders" className="gap-1">
|
|
108
|
+
<Text className="text-muted-foreground text-sm font-semibold">Other tenders</Text>
|
|
109
|
+
{otherTenders.map(([method, amount]) => (
|
|
110
|
+
<View key={method} className="min-h-11 flex-row items-center justify-between">
|
|
111
|
+
<Text>{tenderLabel(method)}</Text>
|
|
99
112
|
<Text className="tabular-nums">{formatMoney({ amount, currency })}</Text>
|
|
100
113
|
</View>
|
|
101
114
|
))}
|
package/src/sale/catalogue.tsx
CHANGED
|
@@ -32,7 +32,7 @@ function laterOf(a: Date | null | undefined, b: Date | null | undefined): Date |
|
|
|
32
32
|
return a.getTime() >= b.getTime() ? a : b;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function Catalogue<Doc>({ products, traits, currency, onSelect, statusText, statusAccessory, lastSyncedAt, loading, lastStockCheckAt, hour12, minCodeLength }: {
|
|
35
|
+
export function Catalogue<Doc>({ products, traits, currency, onSelect, statusText, statusAccessory, lastSyncedAt, loading, pullError, lastStockCheckAt, hour12, minCodeLength }: {
|
|
36
36
|
products: Doc[];
|
|
37
37
|
traits: ProductTraits<Doc>;
|
|
38
38
|
currency: string;
|
|
@@ -42,9 +42,12 @@ export function Catalogue<Doc>({ products, traits, currency, onSelect, statusTex
|
|
|
42
42
|
/** Rendered at the end of the status line, e.g. a register control at phone width; the row renders even without `statusText`. */
|
|
43
43
|
statusAccessory?: ReactNode;
|
|
44
44
|
lastSyncedAt: Date | null;
|
|
45
|
-
/** The catalogue's first pull is still running, so an empty grid says "Loading products…" rather than "No products yet.". Defaults to lastSyncedAt === null; pass false
|
|
45
|
+
/** The catalogue's first pull is still running, so an empty grid says "Loading products…" rather than "No products yet.". Defaults to lastSyncedAt === null with no pullError; pass false
|
|
46
46
|
* when a null lastSyncedAt doesn't mean a pull is running (e.g. offline on first start). */
|
|
47
47
|
loading?: boolean;
|
|
48
|
+
/** The product pull's last error (an Error, a SyncNotice, anything the app holds), or null/undefined when none. While it is
|
|
49
|
+
* set, a catalogue that has never synced isn't treated as loading, so its empty grid says "No products yet." beside the app's error. */
|
|
50
|
+
pullError?: unknown;
|
|
48
51
|
/** The last completed stock reconcile pass, persisted across restarts. The "stock as of" time shown is the
|
|
49
52
|
* latest of this, the provider's stockOverlayAsOf (when valid) and lastSyncedAt; null and omitted alike. */
|
|
50
53
|
lastStockCheckAt?: Date | null;
|
|
@@ -56,7 +59,7 @@ export function Catalogue<Doc>({ products, traits, currency, onSelect, statusTex
|
|
|
56
59
|
* in its unfocused wedge listener — not this prop's job. */
|
|
57
60
|
minCodeLength?: number;
|
|
58
61
|
}) {
|
|
59
|
-
const isLoading = loading ?? lastSyncedAt === null;
|
|
62
|
+
const isLoading = loading ?? (lastSyncedAt === null && pullError == null);
|
|
60
63
|
// Idempotent (the adapter's overlay returns just the fields the stock map sets), so an app that already
|
|
61
64
|
// merges the overlay itself keeps working, and can drop its own merge.
|
|
62
65
|
const shown = useStockOverlaid(products) as Doc[];
|
|
@@ -71,7 +74,7 @@ export function Catalogue<Doc>({ products, traits, currency, onSelect, statusTex
|
|
|
71
74
|
const [width, setWidth] = useState(0);
|
|
72
75
|
// ProductGrid has 4 px padding on each side of the content and each cell.
|
|
73
76
|
const columns = Math.max(2, Math.min(6, Math.floor((width - 8) / (MIN_TILE_WIDTH + 8))));
|
|
74
|
-
const entries = useMemo(() => catalogueEntries(shown, traits), [shown, traits]);
|
|
77
|
+
const entries = useMemo(() => catalogueEntries(shown, traits, { currency }), [shown, traits, currency]);
|
|
75
78
|
const results = useMemo(() => searchProducts(shown, query, traits), [shown, query, traits]);
|
|
76
79
|
const choices = useMemo(() => (chooserId === null ? []
|
|
77
80
|
: entries.filter((entry) => traits.getId(entry.product) === chooserId)), [entries, chooserId, traits]);
|