@tallyui/components 3.2.0 → 3.3.0

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 CHANGED
@@ -1240,6 +1240,10 @@ declare function Tender({ sale }: {
1240
1240
  sale: ReturnType<typeof useSale>;
1241
1241
  }): react_jsx_runtime.JSX.Element | null;
1242
1242
 
1243
+ declare function SplitTender({ sale }: {
1244
+ sale: ReturnType<typeof useSale>;
1245
+ }): JSX.Element | null;
1246
+
1243
1247
  /** The cashier's entry as a TallyUI Discount (a percent, or integer minor units), or the inline error to show. */
1244
1248
  declare function parseDiscount(type: Discount['type'], text: string, currency: string): Discount | string;
1245
1249
  /** "10%", or a fixed discount's amount as it actually comes off (amountMinor), never the amount requested. */
@@ -1450,4 +1454,4 @@ interface StoreSettingsChoiceScreenProps extends Omit<ViewProps, 'children'> {
1450
1454
  */
1451
1455
  declare function StoreSettingsChoiceScreen({ choices, initial, onSubmit, countryName, locale, title, body, regionLabel, countryLabel, channelLabel, submitLabel, className, ...viewProps }: StoreSettingsChoiceScreenProps): react_jsx_runtime.JSX.Element;
1452
1456
 
1453
- 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, ParkedSales, type ParkedSalesProps, type PaymentMethod, PaymentMethodCard, type PaymentMethodCardProps, PaymentSelector, type PaymentSelectorProps, Popover, PopoverContent, PopoverTrigger, PriceForm, 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, parsePrice, textVariants, toastVariants, toggleTextVariants, toggleVariants, useInputContext, vstackVariants };
1457
+ 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, ParkedSales, type ParkedSalesProps, type PaymentMethod, PaymentMethodCard, type PaymentMethodCardProps, PaymentSelector, type PaymentSelectorProps, Popover, PopoverContent, PopoverTrigger, PriceForm, 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, SplitTender, 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, parsePrice, textVariants, toastVariants, toggleTextVariants, toggleVariants, useInputContext, vstackVariants };
package/dist/index.js CHANGED
@@ -4334,12 +4334,144 @@ function Tender({ sale }) {
4334
4334
  ] });
4335
4335
  }
4336
4336
 
4337
+ // src/sale/split-tender.tsx
4338
+ import { useState as useState19 } from "react";
4339
+ import { Pressable as Pressable28, Text as Text37, TextInput as TextInput12, View as View58 } from "react-native";
4340
+ import { formatMoney as formatMoney14, minorUnitDigits as minorUnitDigits9 } from "@tallyui/core";
4341
+ import { Fragment as Fragment7, jsx as jsx93, jsxs as jsxs52 } from "react/jsx-runtime";
4342
+ function SplitTender({ sale }) {
4343
+ const { order, stage } = sale;
4344
+ const [method, setMethod] = useState19("cash");
4345
+ const [amount, setAmount] = useState19(null);
4346
+ const [reference, setReference] = useState19("");
4347
+ const [error, setError] = useState19(null);
4348
+ if (stage.kind !== "tender") return null;
4349
+ const money = (value) => formatMoney14({ amount: value, currency: order.currency });
4350
+ const digits = minorUnitDigits9(order.currency);
4351
+ const amountText = amount ?? (order.balanceDueMinor / 10 ** digits).toFixed(digits);
4352
+ const incomplete = order.balanceDueMinor > 0 || order.payments.length === 0;
4353
+ function add() {
4354
+ const parsed = parsePrice(amountText, order.currency);
4355
+ if (typeof parsed === "string") {
4356
+ setError(parsed);
4357
+ return;
4358
+ }
4359
+ const id = sale.addTender({
4360
+ method,
4361
+ amountMinor: parsed,
4362
+ ...method === "external" && reference ? { reference } : {}
4363
+ });
4364
+ if (id === null) {
4365
+ setError("That tender wasn't added: nothing is due, or the amount isn't valid.");
4366
+ return;
4367
+ }
4368
+ setError(null);
4369
+ setReference("");
4370
+ setAmount(null);
4371
+ }
4372
+ return /* @__PURE__ */ jsxs52(View58, { dataSet: { print: "hide" }, className: "gap-4 p-4", children: [
4373
+ /* @__PURE__ */ jsxs52(View58, { testID: "split-tender-summary", className: "gap-2", children: [
4374
+ /* @__PURE__ */ jsxs52(Text37, { className: "text-foreground", children: [
4375
+ "Total: ",
4376
+ money(order.totalMinor)
4377
+ ] }),
4378
+ /* @__PURE__ */ jsxs52(Text37, { className: "text-foreground", children: [
4379
+ "Paid: ",
4380
+ money(order.paidMinor)
4381
+ ] }),
4382
+ /* @__PURE__ */ jsxs52(Text37, { className: "text-foreground", children: [
4383
+ "Remaining: ",
4384
+ money(order.balanceDueMinor)
4385
+ ] }),
4386
+ order.changeDueMinor > 0 ? /* @__PURE__ */ jsxs52(Text37, { className: "text-foreground", children: [
4387
+ "Change: ",
4388
+ money(order.changeDueMinor)
4389
+ ] }) : null
4390
+ ] }),
4391
+ /* @__PURE__ */ jsx93(View58, { testID: "split-tender-list", className: "gap-2", children: order.payments.map((payment) => /* @__PURE__ */ jsxs52(View58, { testID: `split-tender-row-${payment.id}`, className: "gap-2", children: [
4392
+ /* @__PURE__ */ jsxs52(Text37, { className: "text-foreground", children: [
4393
+ tenderLabel(payment.method),
4394
+ " ",
4395
+ money(payment.amountMinor)
4396
+ ] }),
4397
+ payment.reference ? /* @__PURE__ */ jsx93(Text37, { className: "text-foreground", children: payment.reference }) : null,
4398
+ /* @__PURE__ */ jsx93(
4399
+ Pressable28,
4400
+ {
4401
+ testID: `split-tender-remove-${payment.id}`,
4402
+ accessibilityRole: "button",
4403
+ accessibilityLabel: "Remove tender",
4404
+ onPress: () => sale.removeTender(payment.id),
4405
+ className: "rounded-md border border-border bg-card px-4 py-3",
4406
+ children: /* @__PURE__ */ jsx93(Text37, { className: "text-center text-foreground", children: "Remove" })
4407
+ }
4408
+ )
4409
+ ] }, payment.id)) }),
4410
+ /* @__PURE__ */ jsxs52(View58, { testID: "split-tender-add", className: "gap-2", children: [
4411
+ /* @__PURE__ */ jsx93(View58, { className: "flex-row gap-2", children: ["cash", "external"].map((value) => /* @__PURE__ */ jsx93(
4412
+ Pressable28,
4413
+ {
4414
+ testID: `split-tender-method-${value === "cash" ? "cash" : "card"}`,
4415
+ accessibilityRole: "button",
4416
+ accessibilityState: { selected: method === value },
4417
+ onPress: () => setMethod(value),
4418
+ className: `rounded-md border border-border px-4 py-3 ${method === value ? "bg-primary" : "bg-card"}`,
4419
+ children: /* @__PURE__ */ jsx93(Text37, { className: `text-center ${method === value ? "text-primary-foreground" : "text-foreground"}`, children: tenderLabel(value) })
4420
+ },
4421
+ value
4422
+ )) }),
4423
+ /* @__PURE__ */ jsx93(
4424
+ TextInput12,
4425
+ {
4426
+ testID: "split-tender-amount",
4427
+ accessibilityLabel: "Tender amount",
4428
+ inputMode: "decimal",
4429
+ value: amountText,
4430
+ onChangeText: setAmount,
4431
+ className: "rounded-md border border-border px-3 py-2 text-foreground"
4432
+ }
4433
+ ),
4434
+ method === "external" ? /* @__PURE__ */ jsx93(
4435
+ TextInput12,
4436
+ {
4437
+ testID: "split-tender-reference",
4438
+ accessibilityLabel: "Terminal reference",
4439
+ placeholder: "Terminal reference",
4440
+ maxLength: 255,
4441
+ value: reference,
4442
+ onChangeText: setReference,
4443
+ className: "rounded-md border border-border px-3 py-2 text-foreground"
4444
+ }
4445
+ ) : null,
4446
+ error ? /* @__PURE__ */ jsx93(Text37, { testID: "split-tender-error", accessibilityRole: "alert", className: "text-destructive", children: error }) : null,
4447
+ /* @__PURE__ */ jsx93(Pressable28, { testID: "split-tender-add-button", accessibilityRole: "button", onPress: add, className: "rounded-md bg-primary px-4 py-3", children: /* @__PURE__ */ jsx93(Text37, { className: "text-center font-semibold text-primary-foreground", children: "Add" }) })
4448
+ ] }),
4449
+ /* @__PURE__ */ jsx93(
4450
+ Pressable28,
4451
+ {
4452
+ testID: "split-tender-complete",
4453
+ accessibilityRole: "button",
4454
+ disabled: incomplete,
4455
+ onPress: sale.complete,
4456
+ className: `rounded-md bg-primary px-4 py-3 ${incomplete ? "opacity-50" : ""}`,
4457
+ children: /* @__PURE__ */ jsx93(Text37, { className: "text-center font-semibold text-primary-foreground", children: "Complete sale" })
4458
+ }
4459
+ ),
4460
+ sale.error ? /* @__PURE__ */ jsx93(Text37, { accessibilityRole: "alert", className: "text-destructive", children: sale.error }) : null,
4461
+ sale.canContinue ? /* @__PURE__ */ jsxs52(Fragment7, { children: [
4462
+ /* @__PURE__ */ jsx93(Text37, { className: "text-foreground", children: "This sale is stored and will be sent. Continue to the next sale." }),
4463
+ /* @__PURE__ */ jsx93(Pressable28, { accessibilityRole: "button", onPress: () => sale.continueSale(), className: "rounded-md bg-primary px-4 py-3", children: /* @__PURE__ */ jsx93(Text37, { className: "text-center font-semibold text-primary-foreground", children: "Continue" }) })
4464
+ ] }) : null,
4465
+ /* @__PURE__ */ jsx93(Pressable28, { accessibilityRole: "button", onPress: sale.cancelTender, className: "rounded-md border border-border bg-card px-4 py-3", children: /* @__PURE__ */ jsx93(Text37, { className: "text-center text-foreground", children: "Back" }) })
4466
+ ] });
4467
+ }
4468
+
4337
4469
  // src/sale/catalogue.tsx
4338
- import { useEffect as useEffect5, useMemo as useMemo2, useState as useState19 } from "react";
4339
- import { Pressable as Pressable28, Text as Text37, View as View58 } from "react-native";
4470
+ import { useEffect as useEffect5, useMemo as useMemo2, useState as useState20 } from "react";
4471
+ import { Pressable as Pressable29, Text as Text38, View as View59 } from "react-native";
4340
4472
  import { useStockOverlaid, useStockOverlayAsOf } from "@tallyui/core";
4341
4473
  import { catalogueEntries, findEntryByCode, searchProducts, variantPriceLabel } from "@tallyui/pos";
4342
- import { jsx as jsx93, jsxs as jsxs52 } from "react/jsx-runtime";
4474
+ import { jsx as jsx94, jsxs as jsxs53 } from "react/jsx-runtime";
4343
4475
  var MIN_TILE_WIDTH = 160;
4344
4476
  var STOCK_LABEL = {
4345
4477
  in_stock: "In Stock",
@@ -4365,9 +4497,9 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
4365
4497
  const parsedOverlayAsOf = overlayAsOf ? new Date(overlayAsOf) : void 0;
4366
4498
  const validOverlayAsOf = parsedOverlayAsOf && !isNaN(parsedOverlayAsOf.getTime()) ? parsedOverlayAsOf : void 0;
4367
4499
  const stockAsOf = laterOf(laterOf(lastStockCheckAt, validOverlayAsOf), lastSyncedAt);
4368
- const [query, setQuery] = useState19("");
4369
- const [chooserId, setChooserId] = useState19(null);
4370
- const [width, setWidth] = useState19(0);
4500
+ const [query, setQuery] = useState20("");
4501
+ const [chooserId, setChooserId] = useState20(null);
4502
+ const [width, setWidth] = useState20(0);
4371
4503
  const columns = Math.max(2, Math.min(6, Math.floor((width - 8) / (MIN_TILE_WIDTH + 8))));
4372
4504
  const entries = useMemo2(() => catalogueEntries(shown, traits, { currency }), [shown, traits, currency]);
4373
4505
  const results = useMemo2(() => searchProducts(shown, query, traits), [shown, query, traits]);
@@ -4379,9 +4511,9 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
4379
4511
  onSelect(entry);
4380
4512
  setChooserId(null);
4381
4513
  }
4382
- return /* @__PURE__ */ jsxs52(View58, { className: "flex-1", onLayout: (event) => setWidth(event.nativeEvent.layout.width), children: [
4383
- /* @__PURE__ */ jsxs52(View58, { className: "gap-2 border-b border-border bg-card px-4 pb-3 pt-3", children: [
4384
- /* @__PURE__ */ jsx93(
4514
+ return /* @__PURE__ */ jsxs53(View59, { className: "flex-1", onLayout: (event) => setWidth(event.nativeEvent.layout.width), children: [
4515
+ /* @__PURE__ */ jsxs53(View59, { className: "gap-2 border-b border-border bg-card px-4 pb-3 pt-3", children: [
4516
+ /* @__PURE__ */ jsx94(
4385
4517
  SearchInput,
4386
4518
  {
4387
4519
  value: query,
@@ -4398,25 +4530,25 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
4398
4530
  }
4399
4531
  }
4400
4532
  ),
4401
- statusText || statusAccessory ? /* @__PURE__ */ jsxs52(View58, { testID: "catalogue-status-row", className: "flex-row items-center gap-2", children: [
4402
- statusText ? /* @__PURE__ */ jsxs52(Text37, { className: "flex-1 text-xs text-muted-foreground", numberOfLines: statusAccessory ? 1 : void 0, children: [
4533
+ statusText || statusAccessory ? /* @__PURE__ */ jsxs53(View59, { testID: "catalogue-status-row", className: "flex-row items-center gap-2", children: [
4534
+ statusText ? /* @__PURE__ */ jsxs53(Text38, { className: "flex-1 text-xs text-muted-foreground", numberOfLines: statusAccessory ? 1 : void 0, children: [
4403
4535
  statusText,
4404
4536
  query.trim() ? ` \xB7 ${results.length.toLocaleString()} matching` : ""
4405
4537
  ] }) : null,
4406
4538
  statusAccessory
4407
4539
  ] }) : null,
4408
- choices.length > 0 ? /* @__PURE__ */ jsxs52(View58, { accessibilityLabel: "Choose variant", className: "gap-2", children: [
4409
- choices.map((entry) => /* @__PURE__ */ jsxs52(
4410
- Pressable28,
4540
+ choices.length > 0 ? /* @__PURE__ */ jsxs53(View59, { accessibilityLabel: "Choose variant", className: "gap-2", children: [
4541
+ choices.map((entry) => /* @__PURE__ */ jsxs53(
4542
+ Pressable29,
4411
4543
  {
4412
4544
  accessibilityRole: "button",
4413
4545
  onPress: () => select(entry),
4414
4546
  className: "gap-1 rounded-md border border-border p-3",
4415
4547
  children: [
4416
- /* @__PURE__ */ jsx93(Text37, { className: "font-semibold text-foreground", children: entry.variant.title }),
4417
- /* @__PURE__ */ jsx93(Text37, { className: "text-muted-foreground", children: entry.variant.sku }),
4418
- /* @__PURE__ */ jsx93(Text37, { className: "text-foreground", children: variantPriceLabel(entry.variant, currency) }),
4419
- /* @__PURE__ */ jsxs52(Text37, { className: "text-muted-foreground", children: [
4548
+ /* @__PURE__ */ jsx94(Text38, { className: "font-semibold text-foreground", children: entry.variant.title }),
4549
+ /* @__PURE__ */ jsx94(Text38, { className: "text-muted-foreground", children: entry.variant.sku }),
4550
+ /* @__PURE__ */ jsx94(Text38, { className: "text-foreground", children: variantPriceLabel(entry.variant, currency) }),
4551
+ /* @__PURE__ */ jsxs53(Text38, { className: "text-muted-foreground", children: [
4420
4552
  STOCK_LABEL[entry.variant.stock.status],
4421
4553
  " \xB7 ",
4422
4554
  stockAsOf ? `as of ${formatStockSyncTime(stockAsOf, void 0, hour12)}` : "not yet synced"
@@ -4425,10 +4557,10 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
4425
4557
  },
4426
4558
  entry.variant.id
4427
4559
  )),
4428
- /* @__PURE__ */ jsx93(Pressable28, { accessibilityRole: "button", onPress: () => setChooserId(null), className: "rounded-md border border-border bg-card px-4 py-3", children: /* @__PURE__ */ jsx93(Text37, { className: "text-center text-foreground", children: "Cancel" }) })
4560
+ /* @__PURE__ */ jsx94(Pressable29, { accessibilityRole: "button", onPress: () => setChooserId(null), className: "rounded-md border border-border bg-card px-4 py-3", children: /* @__PURE__ */ jsx94(Text38, { className: "text-center text-foreground", children: "Cancel" }) })
4429
4561
  ] }) : null
4430
4562
  ] }),
4431
- /* @__PURE__ */ jsx93(
4563
+ /* @__PURE__ */ jsx94(
4432
4564
  ProductGrid,
4433
4565
  {
4434
4566
  items: results,
@@ -4437,18 +4569,18 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
4437
4569
  // TallyUI's ProductCard has no children slot, so the tile is composed directly here
4438
4570
  // (same structure/classes as ProductCard) to add the stock badge as a fourth child,
4439
4571
  // inside the card and centred with the rest, rather than adding a slot to ProductCard (out of scope for TV6b).
4440
- /* @__PURE__ */ jsx93(Pressable28, { accessibilityRole: "button", testID: `product-tile-${traits.getName(product)}`, onPress: () => {
4572
+ /* @__PURE__ */ jsx94(Pressable29, { accessibilityRole: "button", testID: `product-tile-${traits.getName(product)}`, onPress: () => {
4441
4573
  const variants = entries.filter((entry) => entry.product === product);
4442
4574
  if (variants.length === 1) select(variants[0]);
4443
4575
  else setChooserId(traits.getId(product));
4444
- }, children: /* @__PURE__ */ jsxs52(VStack, { space: "sm", className: "items-center rounded-lg border border-border bg-card p-3", children: [
4445
- /* @__PURE__ */ jsx93(ProductImage, { doc: product, size: 80, className: "rounded-md" }),
4446
- /* @__PURE__ */ jsx93(ProductTitle, { doc: product, className: "text-sm", numberOfLines: 2 }),
4447
- /* @__PURE__ */ jsx93(ProductPrice, { doc: product }),
4448
- /* @__PURE__ */ jsx93(ProductStockBadge, { doc: product, showAsOf: false, className: "self-center" })
4576
+ }, children: /* @__PURE__ */ jsxs53(VStack, { space: "sm", className: "items-center rounded-lg border border-border bg-card p-3", children: [
4577
+ /* @__PURE__ */ jsx94(ProductImage, { doc: product, size: 80, className: "rounded-md" }),
4578
+ /* @__PURE__ */ jsx94(ProductTitle, { doc: product, className: "text-sm", numberOfLines: 2 }),
4579
+ /* @__PURE__ */ jsx94(ProductPrice, { doc: product }),
4580
+ /* @__PURE__ */ jsx94(ProductStockBadge, { doc: product, showAsOf: false, className: "self-center" })
4449
4581
  ] }) })
4450
4582
  ),
4451
- emptyState: /* @__PURE__ */ jsx93(Text37, { className: "mt-10 text-center text-sm text-muted-foreground", children: query.trim() ? `No products match "${query.trim()}".` : isLoading ? "Loading products\u2026" : "No products yet." })
4583
+ emptyState: /* @__PURE__ */ jsx94(Text38, { className: "mt-10 text-center text-sm text-muted-foreground", children: query.trim() ? `No products match "${query.trim()}".` : isLoading ? "Loading products\u2026" : "No products yet." })
4452
4584
  }
4453
4585
  )
4454
4586
  ] });
@@ -4456,8 +4588,8 @@ function Catalogue({ products, traits, currency, onSelect, statusText, statusAcc
4456
4588
 
4457
4589
  // src/sale/receipt.tsx
4458
4590
  import { useEffect as useEffect6 } from "react";
4459
- import { Pressable as Pressable29, Text as Text38, View as View59 } from "react-native";
4460
- import { formatMoney as formatMoney14 } from "@tallyui/core";
4591
+ import { Pressable as Pressable30, Text as Text39, View as View60 } from "react-native";
4592
+ import { formatMoney as formatMoney15 } from "@tallyui/core";
4461
4593
  import { buildReceiptData as buildReceiptData3 } from "@tallyui/pos";
4462
4594
 
4463
4595
  // src/sale/print-style.ts
@@ -4477,7 +4609,7 @@ function orderReference(order) {
4477
4609
  }
4478
4610
 
4479
4611
  // src/sale/receipt.tsx
4480
- import { Fragment as Fragment7, jsx as jsx94, jsxs as jsxs53 } from "react/jsx-runtime";
4612
+ import { Fragment as Fragment8, jsx as jsx95, jsxs as jsxs54 } from "react/jsx-runtime";
4481
4613
  var defaultFormatDate = (iso) => new Intl.DateTimeFormat(void 0, { dateStyle: "medium", timeStyle: "short" }).format(new Date(iso));
4482
4614
  function Receipt({
4483
4615
  order,
@@ -4497,33 +4629,33 @@ function Receipt({
4497
4629
  cashier,
4498
4630
  register: registerId
4499
4631
  });
4500
- const money = (amount) => formatMoney14({ amount, currency: receipt.currency }) ?? "";
4501
- const row = (label, amount, bold = false, key) => /* @__PURE__ */ jsxs53(View59, { accessibilityLabel: `${label}: ${amount}`, className: "flex-row justify-between gap-4", children: [
4502
- /* @__PURE__ */ jsx94(Text38, { className: `text-foreground ${bold ? "font-semibold" : ""}`, children: label }),
4503
- /* @__PURE__ */ jsx94(Text38, { className: `text-right text-foreground ${bold ? "font-semibold" : ""}`, children: amount })
4632
+ const money = (amount) => formatMoney15({ amount, currency: receipt.currency }) ?? "";
4633
+ const row = (label, amount, bold = false, key) => /* @__PURE__ */ jsxs54(View60, { accessibilityLabel: `${label}: ${amount}`, className: "flex-row justify-between gap-4", children: [
4634
+ /* @__PURE__ */ jsx95(Text39, { className: `text-foreground ${bold ? "font-semibold" : ""}`, children: label }),
4635
+ /* @__PURE__ */ jsx95(Text39, { className: `text-right text-foreground ${bold ? "font-semibold" : ""}`, children: amount })
4504
4636
  ] }, key);
4505
4637
  const { totals } = receipt;
4506
4638
  const lineDiscountMinor = totals.discountMinor - receipt.orderDiscountMinor;
4507
4639
  const taxRows = totals.taxLines.map((line, index) => row(`${totals.taxInclusive ? "incl. " : ""}${taxLabel(line.ratePpm)}`, money(line.amountMinor), false, index));
4508
- return /* @__PURE__ */ jsxs53(Fragment7, { children: [
4509
- topInset > 0 ? /* @__PURE__ */ jsx94(View59, { dataSet: { print: "hide" }, style: { height: topInset, flexShrink: 0 } }) : null,
4510
- /* @__PURE__ */ jsxs53(View59, { className: "w-full max-w-md self-center gap-3 p-4 bg-card", children: [
4511
- /* @__PURE__ */ jsx94(Text38, { className: "text-lg font-semibold text-foreground", children: receipt.header.storeName }),
4512
- receipt.header.storeAddress ? /* @__PURE__ */ jsx94(Text38, { className: "text-muted-foreground", children: receipt.header.storeAddress }) : null,
4513
- /* @__PURE__ */ jsx94(Text38, { testID: "receipt-order", className: "text-foreground", children: posOrder ? `Order ${orderReference(posOrder)}` : `Order ${receipt.header.orderNumber.slice(-8)} (draft)` }),
4514
- /* @__PURE__ */ jsx94(Text38, { className: "text-muted-foreground", children: formatDate(posOrder?.createdAt ?? receipt.header.date) }),
4515
- /* @__PURE__ */ jsxs53(Text38, { className: "text-muted-foreground", children: [
4640
+ return /* @__PURE__ */ jsxs54(Fragment8, { children: [
4641
+ topInset > 0 ? /* @__PURE__ */ jsx95(View60, { dataSet: { print: "hide" }, style: { height: topInset, flexShrink: 0 } }) : null,
4642
+ /* @__PURE__ */ jsxs54(View60, { className: "w-full max-w-md self-center gap-3 p-4 bg-card", children: [
4643
+ /* @__PURE__ */ jsx95(Text39, { className: "text-lg font-semibold text-foreground", children: receipt.header.storeName }),
4644
+ receipt.header.storeAddress ? /* @__PURE__ */ jsx95(Text39, { className: "text-muted-foreground", children: receipt.header.storeAddress }) : null,
4645
+ /* @__PURE__ */ jsx95(Text39, { testID: "receipt-order", className: "text-foreground", children: posOrder ? `Order ${orderReference(posOrder)}` : `Order ${receipt.header.orderNumber.slice(-8)} (draft)` }),
4646
+ /* @__PURE__ */ jsx95(Text39, { className: "text-muted-foreground", children: formatDate(posOrder?.createdAt ?? receipt.header.date) }),
4647
+ /* @__PURE__ */ jsxs54(Text39, { className: "text-muted-foreground", children: [
4516
4648
  "Cashier: ",
4517
4649
  receipt.header.cashier
4518
4650
  ] }),
4519
- receipt.header.customer && /* @__PURE__ */ jsxs53(Text38, { className: "text-muted-foreground", children: [
4651
+ receipt.header.customer && /* @__PURE__ */ jsxs54(Text39, { className: "text-muted-foreground", children: [
4520
4652
  "Customer: ",
4521
4653
  receipt.header.customer
4522
4654
  ] }),
4523
- receipt.lineItems.map((line, index) => /* @__PURE__ */ jsxs53(View59, { children: [
4524
- /* @__PURE__ */ jsx94(Text38, { className: "text-foreground", children: line.name }),
4655
+ receipt.lineItems.map((line, index) => /* @__PURE__ */ jsxs54(View60, { children: [
4656
+ /* @__PURE__ */ jsx95(Text39, { className: "text-foreground", children: line.name }),
4525
4657
  row(`${line.quantity} \xD7 ${money(line.unitPriceMinor)}`, money(line.displayAmountMinor)),
4526
- line.displayDiscounts.map((discount, i) => /* @__PURE__ */ jsx94(View59, { className: "pl-4", children: row(`${discountLabel(
4658
+ line.displayDiscounts.map((discount, i) => /* @__PURE__ */ jsx95(View60, { className: "pl-4", children: row(`${discountLabel(
4527
4659
  order.lineItems[index].discounts[i],
4528
4660
  receipt.currency
4529
4661
  )} off`, `\u2212${money(discount.amountMinor)}`) }, i))
@@ -4540,19 +4672,19 @@ function Receipt({
4540
4672
  index
4541
4673
  )),
4542
4674
  row("Change", money(receipt.changeDueMinor)),
4543
- /* @__PURE__ */ jsxs53(View59, { dataSet: { print: "hide" }, className: "flex-row gap-4", children: [
4544
- /* @__PURE__ */ jsx94(Pressable29, { accessibilityRole: "button", onPress: () => {
4675
+ /* @__PURE__ */ jsxs54(View60, { dataSet: { print: "hide" }, className: "flex-row gap-4", children: [
4676
+ /* @__PURE__ */ jsx95(Pressable30, { accessibilityRole: "button", onPress: () => {
4545
4677
  if (typeof window !== "undefined" && typeof window.print === "function") window.print();
4546
- }, className: "rounded-md border border-border bg-card px-4 py-3", children: /* @__PURE__ */ jsx94(Text38, { className: "text-center text-foreground", children: "Print receipt" }) }),
4547
- /* @__PURE__ */ jsx94(Pressable29, { accessibilityRole: "button", onPress: newSale, className: "rounded-md bg-primary px-4 py-3", children: /* @__PURE__ */ jsx94(Text38, { className: "text-center font-semibold text-primary-foreground", children: "New sale" }) })
4678
+ }, className: "rounded-md border border-border bg-card px-4 py-3", children: /* @__PURE__ */ jsx95(Text39, { className: "text-center text-foreground", children: "Print receipt" }) }),
4679
+ /* @__PURE__ */ jsx95(Pressable30, { accessibilityRole: "button", onPress: newSale, className: "rounded-md bg-primary px-4 py-3", children: /* @__PURE__ */ jsx95(Text39, { className: "text-center font-semibold text-primary-foreground", children: "New sale" }) })
4548
4680
  ] })
4549
4681
  ] })
4550
4682
  ] });
4551
4683
  }
4552
4684
 
4553
4685
  // src/sale/sync-status.tsx
4554
- import { useEffect as useEffect7, useRef as useRef6, useState as useState20 } from "react";
4555
- import { AccessibilityInfo as AccessibilityInfo2, Platform as Platform3, Text as Text39, View as View60 } from "react-native";
4686
+ import { useEffect as useEffect7, useRef as useRef6, useState as useState21 } from "react";
4687
+ import { AccessibilityInfo as AccessibilityInfo2, Platform as Platform3, Text as Text40, View as View61 } from "react-native";
4556
4688
 
4557
4689
  // src/sale/since-text.ts
4558
4690
  function sinceText(clock) {
@@ -4561,7 +4693,7 @@ function sinceText(clock) {
4561
4693
  }
4562
4694
 
4563
4695
  // src/sale/sync-status.tsx
4564
- import { Fragment as Fragment8, jsx as jsx95, jsxs as jsxs54 } from "react/jsx-runtime";
4696
+ import { Fragment as Fragment9, jsx as jsx96, jsxs as jsxs55 } from "react/jsx-runtime";
4565
4697
  var PULL_NOTICE_TEXT = {
4566
4698
  unauthorized: {
4567
4699
  line: () => "Products aren't updating: this till needs to sign in to the online store again.",
@@ -4613,7 +4745,7 @@ function useAnnounceOnIos(...texts) {
4613
4745
  function SyncStatus({ state, registerState, pluginName = "the POS plugin", pullNotice }) {
4614
4746
  const updates = registerState?.pending ?? 0;
4615
4747
  const outbox = state.pending === 0 && registerState?.pending ? registerState : state;
4616
- const [now, setNow] = useState20(Date.now);
4748
+ const [now, setNow] = useState21(Date.now);
4617
4749
  useEffect7(() => {
4618
4750
  setNow(Date.now());
4619
4751
  if (!outbox.nextAttemptAt) return;
@@ -4637,24 +4769,24 @@ function SyncStatus({ state, registerState, pluginName = "the POS plugin", pullN
4637
4769
  const notice = pullNotice && text && { line: text.line(pluginName), detail: text.detail(pullNotice, pluginName) };
4638
4770
  useAnnounceOnIos(notice?.line, spoken);
4639
4771
  const oneDetail = pullNotice?.code === "missing_plugin" && !!backendMissing;
4640
- return /* @__PURE__ */ jsxs54(View60, { children: [
4641
- notice ? /* @__PURE__ */ jsxs54(Fragment8, { children: [
4642
- /* @__PURE__ */ jsx95(Text39, { accessibilityLiveRegion: "polite", className: "px-4 py-2 text-xs text-muted-foreground", children: notice.line }),
4643
- oneDetail ? null : /* @__PURE__ */ jsx95(Text39, { className: "px-4 pb-2 text-xs text-muted-foreground", children: notice.detail })
4772
+ return /* @__PURE__ */ jsxs55(View61, { children: [
4773
+ notice ? /* @__PURE__ */ jsxs55(Fragment9, { children: [
4774
+ /* @__PURE__ */ jsx96(Text40, { accessibilityLiveRegion: "polite", className: "px-4 py-2 text-xs text-muted-foreground", children: notice.line }),
4775
+ oneDetail ? null : /* @__PURE__ */ jsx96(Text40, { className: "px-4 pb-2 text-xs text-muted-foreground", children: notice.detail })
4644
4776
  ] }) : null,
4645
- /* @__PURE__ */ jsx95(Text39, { accessibilityLabel: spoken, accessibilityLiveRegion: "polite", className: "px-4 py-2 text-xs text-muted-foreground", children: spoken }),
4646
- doing ? /* @__PURE__ */ jsx95(Text39, { className: "px-4 pb-2 text-xs text-muted-foreground", children: doing }) : null,
4647
- refused ? /* @__PURE__ */ jsx95(Text39, { className: "px-4 pb-2 text-xs text-muted-foreground", children: REFUSED_DETAIL }) : null,
4648
- oneDetail && notice ? /* @__PURE__ */ jsx95(Text39, { className: "px-4 pb-2 text-xs text-muted-foreground", children: notice.detail }) : backendMissing ? /* @__PURE__ */ jsx95(Text39, { className: "px-4 pb-2 text-xs text-muted-foreground", children: BACKEND_MISSING_DETAIL.replace("{pluginName}", () => pluginName).replace("{lastTime}", () => upToDate ? " the last time it checked" : "") }) : null
4777
+ /* @__PURE__ */ jsx96(Text40, { accessibilityLabel: spoken, accessibilityLiveRegion: "polite", className: "px-4 py-2 text-xs text-muted-foreground", children: spoken }),
4778
+ doing ? /* @__PURE__ */ jsx96(Text40, { className: "px-4 pb-2 text-xs text-muted-foreground", children: doing }) : null,
4779
+ refused ? /* @__PURE__ */ jsx96(Text40, { className: "px-4 pb-2 text-xs text-muted-foreground", children: REFUSED_DETAIL }) : null,
4780
+ oneDetail && notice ? /* @__PURE__ */ jsx96(Text40, { className: "px-4 pb-2 text-xs text-muted-foreground", children: notice.detail }) : backendMissing ? /* @__PURE__ */ jsx96(Text40, { className: "px-4 pb-2 text-xs text-muted-foreground", children: BACKEND_MISSING_DETAIL.replace("{pluginName}", () => pluginName).replace("{lastTime}", () => upToDate ? " the last time it checked" : "") }) : null
4649
4781
  ] });
4650
4782
  }
4651
4783
 
4652
4784
  // src/sale/orders-list.tsx
4653
- import { useEffect as useEffect8, useRef as useRef7, useState as useState21 } from "react";
4654
- import { Pressable as Pressable30, ScrollView as ScrollView13, Text as Text40, View as View61 } from "react-native";
4655
- import { formatMoney as formatMoney15, knownWarnings } from "@tallyui/core";
4785
+ import { useEffect as useEffect8, useRef as useRef7, useState as useState22 } from "react";
4786
+ import { Pressable as Pressable31, ScrollView as ScrollView13, Text as Text41, View as View62 } from "react-native";
4787
+ import { formatMoney as formatMoney16, knownWarnings } from "@tallyui/core";
4656
4788
  import { needsAttention } from "@tallyui/pos";
4657
- import { jsx as jsx96, jsxs as jsxs55 } from "react/jsx-runtime";
4789
+ import { jsx as jsx97, jsxs as jsxs56 } from "react/jsx-runtime";
4658
4790
  var STATUS_LABEL = { pending: "Waiting to sync", applied: "Synced", rejected: "Not accepted" };
4659
4791
  var FIGURE_LABELS = /* @__PURE__ */ new Map([["subtotalMinor", "Subtotal"], ["taxMinor", "Tax"], ["discountMinor", "Discount"]]);
4660
4792
  var REFUSAL_SENTENCES = /* @__PURE__ */ new Map([
@@ -4678,26 +4810,26 @@ function defaultFormatDate2(iso) {
4678
4810
  }
4679
4811
  function OrdersList({ orders, onRetry, formatDate = defaultFormatDate2, footer, stuck }) {
4680
4812
  const retrying = useRef7(/* @__PURE__ */ new Set());
4681
- const [retryingIds, setRetryingIds] = useState21(/* @__PURE__ */ new Set());
4682
- const [expanded, setExpanded] = useState21(/* @__PURE__ */ new Set());
4813
+ const [retryingIds, setRetryingIds] = useState22(/* @__PURE__ */ new Set());
4814
+ const [expanded, setExpanded] = useState22(/* @__PURE__ */ new Set());
4683
4815
  useEffect8(() => {
4684
4816
  for (const id of retrying.current) {
4685
4817
  if (!orders.some((order) => order.id === id && order.syncStatus === "rejected")) retrying.current.delete(id);
4686
4818
  }
4687
4819
  setRetryingIds(new Set(retrying.current));
4688
4820
  }, [orders]);
4689
- return /* @__PURE__ */ jsxs55(ScrollView13, { className: "flex-1 bg-background p-4", children: [
4690
- [{ title: "Needs attention", orders: needsAttention(orders, { stuckCommandIds: stuck?.commandIds }) }, { title: "Recent", orders }].filter((section) => section.title === "Recent" || section.orders.length > 0).map((section) => /* @__PURE__ */ jsxs55(View61, { className: "mb-6 gap-3", children: [
4691
- /* @__PURE__ */ jsx96(Text40, { accessibilityRole: "header", className: "text-xl font-semibold text-foreground", children: section.title }),
4692
- section.title === "Recent" && orders.length === 0 ? /* @__PURE__ */ jsx96(Text40, { testID: "orders-empty", className: "text-muted-foreground", children: "No sales yet. Completed sales appear here." }) : null,
4821
+ return /* @__PURE__ */ jsxs56(ScrollView13, { className: "flex-1 bg-background p-4", children: [
4822
+ [{ title: "Needs attention", orders: needsAttention(orders, { stuckCommandIds: stuck?.commandIds }) }, { title: "Recent", orders }].filter((section) => section.title === "Recent" || section.orders.length > 0).map((section) => /* @__PURE__ */ jsxs56(View62, { className: "mb-6 gap-3", children: [
4823
+ /* @__PURE__ */ jsx97(Text41, { accessibilityRole: "header", className: "text-xl font-semibold text-foreground", children: section.title }),
4824
+ section.title === "Recent" && orders.length === 0 ? /* @__PURE__ */ jsx97(Text41, { testID: "orders-empty", className: "text-muted-foreground", children: "No sales yet. Completed sales appear here." }) : null,
4693
4825
  section.orders.map((order) => {
4694
4826
  const count2 = order.lines.reduce((sum, line) => sum + line.quantity, 0);
4695
4827
  const key = `${section.title}:${order.id}`;
4696
4828
  const isExpanded = expanded.has(key);
4697
4829
  const stuckEntry = order.syncStatus === "pending" ? stuck?.orders.find((entry) => entry.commandId === order.commandId) : void 0;
4698
- return /* @__PURE__ */ jsxs55(View61, { className: "gap-1 rounded-md border border-border bg-card p-3", children: [
4699
- /* @__PURE__ */ jsxs55(
4700
- Pressable30,
4830
+ return /* @__PURE__ */ jsxs56(View62, { className: "gap-1 rounded-md border border-border bg-card p-3", children: [
4831
+ /* @__PURE__ */ jsxs56(
4832
+ Pressable31,
4701
4833
  {
4702
4834
  accessibilityRole: "button",
4703
4835
  "aria-expanded": isExpanded,
@@ -4711,26 +4843,26 @@ function OrdersList({ orders, onRetry, formatDate = defaultFormatDate2, footer,
4711
4843
  return next;
4712
4844
  }),
4713
4845
  children: [
4714
- /* @__PURE__ */ jsx96(Text40, { className: "text-foreground", children: `Order ${orderReference(order)} \xB7 ${count2} ${count2 === 1 ? "item" : "items"}` }),
4715
- /* @__PURE__ */ jsxs55(Text40, { className: "text-muted-foreground", children: [
4846
+ /* @__PURE__ */ jsx97(Text41, { className: "text-foreground", children: `Order ${orderReference(order)} \xB7 ${count2} ${count2 === 1 ? "item" : "items"}` }),
4847
+ /* @__PURE__ */ jsxs56(Text41, { className: "text-muted-foreground", children: [
4716
4848
  formatDate(order.createdAt),
4717
4849
  " \xB7 ",
4718
- formatMoney15({ amount: order.totalMinor, currency: order.currency }),
4850
+ formatMoney16({ amount: order.totalMinor, currency: order.currency }),
4719
4851
  " \xB7 ",
4720
4852
  STATUS_LABEL[order.syncStatus]
4721
4853
  ] })
4722
4854
  ]
4723
4855
  }
4724
4856
  ),
4725
- isExpanded ? /* @__PURE__ */ jsxs55(View61, { testID: `order-detail-${order.id}`, className: "mt-1 gap-1 border-t border-border pt-2", children: [
4726
- order.lines.map((line) => /* @__PURE__ */ jsx96(Text40, { className: "text-foreground", children: `${line.quantity} \xD7 ${line.name}` }, line.id)),
4727
- order.payments.map((payment) => /* @__PURE__ */ jsx96(Text40, { className: "text-muted-foreground", children: `${payment.method === "cash" ? "Cash" : "Card terminal"} ${formatMoney15({ amount: payment.amountMinor, currency: order.currency })}${payment.reference ? ` \xB7 ${payment.reference}` : ""}` }, payment.id))
4857
+ isExpanded ? /* @__PURE__ */ jsxs56(View62, { testID: `order-detail-${order.id}`, className: "mt-1 gap-1 border-t border-border pt-2", children: [
4858
+ order.lines.map((line) => /* @__PURE__ */ jsx97(Text41, { className: "text-foreground", children: `${line.quantity} \xD7 ${line.name}` }, line.id)),
4859
+ order.payments.map((payment) => /* @__PURE__ */ jsx97(Text41, { className: "text-muted-foreground", children: `${payment.method === "cash" ? "Cash" : "Card terminal"} ${formatMoney16({ amount: payment.amountMinor, currency: order.currency })}${payment.reference ? ` \xB7 ${payment.reference}` : ""}` }, payment.id))
4728
4860
  ] }) : null,
4729
- stuckEntry ? /* @__PURE__ */ jsx96(Text40, { className: "text-destructive", children: `Hasn't reached the online store since ${sinceText(stuckEntry)}.` }) : null,
4730
- order.syncStatus === "rejected" ? /* @__PURE__ */ jsx96(Text40, { className: "text-destructive", children: refusalSentence(order.error?.code) }) : null,
4731
- order.lateSessionId !== void 0 ? /* @__PURE__ */ jsx96(Text40, { className: "text-foreground", children: "Taken after the register closed. It is not in that register's closure." }) : null,
4732
- order.localWarnings?.map((warning, index) => /* @__PURE__ */ jsx96(Text40, { className: "text-foreground", children: warning.code === "customer_omitted" ? `The customer's ${warning.field} couldn't be sent to the store, so the order isn't linked to them.` : "The terminal's payment reference couldn't be kept; the payment is recorded without it." }, index)),
4733
- section.title === "Needs attention" && order.syncStatus === "rejected" && order.error?.code !== "idempotency_mismatch" ? /* @__PURE__ */ jsx96(Pressable30, { accessibilityRole: "button", disabled: retryingIds.has(order.id), onPress: async () => {
4861
+ stuckEntry ? /* @__PURE__ */ jsx97(Text41, { className: "text-destructive", children: `Hasn't reached the online store since ${sinceText(stuckEntry)}.` }) : null,
4862
+ order.syncStatus === "rejected" ? /* @__PURE__ */ jsx97(Text41, { className: "text-destructive", children: refusalSentence(order.error?.code) }) : null,
4863
+ order.lateSessionId !== void 0 ? /* @__PURE__ */ jsx97(Text41, { className: "text-foreground", children: "Taken after the register closed. It is not in that register's closure." }) : null,
4864
+ order.localWarnings?.map((warning, index) => /* @__PURE__ */ jsx97(Text41, { className: "text-foreground", children: warning.code === "customer_omitted" ? `The customer's ${warning.field} couldn't be sent to the store, so the order isn't linked to them.` : "The terminal's payment reference couldn't be kept; the payment is recorded without it." }, index)),
4865
+ section.title === "Needs attention" && order.syncStatus === "rejected" && order.error?.code !== "idempotency_mismatch" ? /* @__PURE__ */ jsx97(Pressable31, { accessibilityRole: "button", disabled: retryingIds.has(order.id), onPress: async () => {
4734
4866
  if (retrying.current.has(order.id)) return;
4735
4867
  retrying.current.add(order.id);
4736
4868
  setRetryingIds(new Set(retrying.current));
@@ -4738,8 +4870,8 @@ function OrdersList({ orders, onRetry, formatDate = defaultFormatDate2, footer,
4738
4870
  retrying.current.delete(order.id);
4739
4871
  setRetryingIds(new Set(retrying.current));
4740
4872
  }
4741
- }, className: "rounded-md bg-primary px-4 py-2", children: /* @__PURE__ */ jsx96(Text40, { className: "text-center font-semibold text-primary-foreground", children: "Retry" }) }) : null,
4742
- knownWarnings(order.warnings).map((warning, index) => /* @__PURE__ */ jsx96(View61, { className: "border-l-4 border-warning pl-2", children: /* @__PURE__ */ jsx96(Text40, { className: "text-foreground", children: warning.code === "insufficient_stock" ? `Stock short by ${warning.quantity} for ${order.lines.find((line) => line.variantId === warning.variantId)?.name}` : warning.code === "tax_rate_mismatch" ? `Tax at ${(warning.ratePpm / 1e4).toLocaleString(void 0, { maximumFractionDigits: 4 })}%: store ${formatMoney15({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney15({ amount: warning.expectedMinor, currency: order.currency })}` : warning.code === "customer_ignored" ? `The online store didn't recognise the customer on this sale, so it was saved as a guest sale. Customer id: ${warning.customerId}.` : warning.code === "figures_mismatch" ? `The online store worked out different figures for this sale. ${warning.fields.map(({ field, tillMinor, serverMinor }) => `${FIGURE_LABELS.get(field) ?? field}: till ${formatMoney15({ amount: tillMinor, currency: order.currency })}, store ${formatMoney15({ amount: serverMinor, currency: order.currency })}.`).join(" ")}` : warning.bridgeMinor !== void 0 ? `Store calculated ${formatMoney15({ amount: warning.serverMinor, currency: order.currency })}; a rounding line of ${warning.bridgeMinor > 0 ? "+" : ""}${formatMoney15({ amount: warning.bridgeMinor, currency: order.currency })} brought it to ${formatMoney15({ amount: warning.expectedMinor, currency: order.currency })}` : `Store total ${formatMoney15({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney15({ amount: warning.expectedMinor, currency: order.currency })}` }) }, index))
4873
+ }, className: "rounded-md bg-primary px-4 py-2", children: /* @__PURE__ */ jsx97(Text41, { className: "text-center font-semibold text-primary-foreground", children: "Retry" }) }) : null,
4874
+ knownWarnings(order.warnings).map((warning, index) => /* @__PURE__ */ jsx97(View62, { className: "border-l-4 border-warning pl-2", children: /* @__PURE__ */ jsx97(Text41, { className: "text-foreground", children: warning.code === "insufficient_stock" ? `Stock short by ${warning.quantity} for ${order.lines.find((line) => line.variantId === warning.variantId)?.name}` : warning.code === "tax_rate_mismatch" ? `Tax at ${(warning.ratePpm / 1e4).toLocaleString(void 0, { maximumFractionDigits: 4 })}%: store ${formatMoney16({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney16({ amount: warning.expectedMinor, currency: order.currency })}` : warning.code === "customer_ignored" ? `The online store didn't recognise the customer on this sale, so it was saved as a guest sale. Customer id: ${warning.customerId}.` : warning.code === "figures_mismatch" ? `The online store worked out different figures for this sale. ${warning.fields.map(({ field, tillMinor, serverMinor }) => `${FIGURE_LABELS.get(field) ?? field}: till ${formatMoney16({ amount: tillMinor, currency: order.currency })}, store ${formatMoney16({ amount: serverMinor, currency: order.currency })}.`).join(" ")}` : warning.bridgeMinor !== void 0 ? `Store calculated ${formatMoney16({ amount: warning.serverMinor, currency: order.currency })}; a rounding line of ${warning.bridgeMinor > 0 ? "+" : ""}${formatMoney16({ amount: warning.bridgeMinor, currency: order.currency })} brought it to ${formatMoney16({ amount: warning.expectedMinor, currency: order.currency })}` : `Store total ${formatMoney16({ amount: warning.serverMinor, currency: order.currency })} vs POS ${formatMoney16({ amount: warning.expectedMinor, currency: order.currency })}` }) }, index))
4743
4875
  ] }, order.id);
4744
4876
  })
4745
4877
  ] }, section.title)),
@@ -4748,15 +4880,15 @@ function OrdersList({ orders, onRetry, formatDate = defaultFormatDate2, footer,
4748
4880
  }
4749
4881
 
4750
4882
  // src/sale/parked-sales.tsx
4751
- import { useState as useState22 } from "react";
4752
- import { ScrollView as ScrollView14, useWindowDimensions as useWindowDimensions3, View as View62 } from "react-native";
4753
- import { formatMoney as formatMoney16 } from "@tallyui/core";
4754
- import { Fragment as Fragment9, jsx as jsx97, jsxs as jsxs56 } from "react/jsx-runtime";
4883
+ import { useState as useState23 } from "react";
4884
+ import { ScrollView as ScrollView14, useWindowDimensions as useWindowDimensions3, View as View63 } from "react-native";
4885
+ import { formatMoney as formatMoney17 } from "@tallyui/core";
4886
+ import { Fragment as Fragment10, jsx as jsx98, jsxs as jsxs57 } from "react/jsx-runtime";
4755
4887
  function ParkedSales({ sale, parked, onDiscard, onPark, onResume, currency, open, onOpenChange, hour12 }) {
4756
4888
  const { height: windowHeight } = useWindowDimensions3();
4757
4889
  const maxHeight = Math.max(280, windowHeight - 64);
4758
- const [confirming, setConfirming] = useState22(null);
4759
- const [error, setError] = useState22("");
4890
+ const [confirming, setConfirming] = useState23(null);
4891
+ const [error, setError] = useState23("");
4760
4892
  const attempt = async (action) => {
4761
4893
  setError("");
4762
4894
  try {
@@ -4765,28 +4897,28 @@ function ParkedSales({ sale, parked, onDiscard, onPark, onResume, currency, open
4765
4897
  setError(String(e));
4766
4898
  }
4767
4899
  };
4768
- return /* @__PURE__ */ jsx97(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs56(DialogContent, { testID: "parked-sales", className: "flex-col overflow-hidden", style: { maxHeight }, children: [
4769
- /* @__PURE__ */ jsxs56(View62, { className: "flex-row items-center justify-between gap-2", children: [
4770
- /* @__PURE__ */ jsx97(DialogTitle, { children: "Parked sales" }),
4771
- /* @__PURE__ */ jsx97(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx97(IconButton, { variant: "ghost", size: "sm", testID: "parked-sales-dismiss", accessibilityLabel: "Close panel", children: /* @__PURE__ */ jsx97(XIcon, { size: 16 }) }) })
4900
+ return /* @__PURE__ */ jsx98(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs57(DialogContent, { testID: "parked-sales", className: "flex-col overflow-hidden", style: { maxHeight }, children: [
4901
+ /* @__PURE__ */ jsxs57(View63, { className: "flex-row items-center justify-between gap-2", children: [
4902
+ /* @__PURE__ */ jsx98(DialogTitle, { children: "Parked sales" }),
4903
+ /* @__PURE__ */ jsx98(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx98(IconButton, { variant: "ghost", size: "sm", testID: "parked-sales-dismiss", accessibilityLabel: "Close panel", children: /* @__PURE__ */ jsx98(XIcon, { size: 16 }) }) })
4772
4904
  ] }),
4773
- /* @__PURE__ */ jsxs56(ScrollView14, { className: "flex-1", contentContainerClassName: "gap-3", children: [
4774
- /* @__PURE__ */ jsx97(
4905
+ /* @__PURE__ */ jsxs57(ScrollView14, { className: "flex-1", contentContainerClassName: "gap-3", children: [
4906
+ /* @__PURE__ */ jsx98(
4775
4907
  Button,
4776
4908
  {
4777
4909
  testID: "parked-sales-park",
4778
4910
  accessibilityLabel: "Park this sale",
4779
4911
  disabled: sale.order.lineItems.length === 0,
4780
4912
  onPress: () => attempt(async () => setError(await (onPark ?? sale.park)() ?? "")),
4781
- children: /* @__PURE__ */ jsx97(Text, { children: "Park this sale" })
4913
+ children: /* @__PURE__ */ jsx98(Text, { children: "Park this sale" })
4782
4914
  }
4783
4915
  ),
4784
- !!error && /* @__PURE__ */ jsx97(Text, { testID: "parked-sales-error", className: "text-destructive", children: error }),
4785
- parked.length === 0 && /* @__PURE__ */ jsx97(Text, { testID: "parked-sales-empty", children: "No parked sales." }),
4786
- parked.map((row) => /* @__PURE__ */ jsx97(View62, { testID: `parked-row-${row.id}`, className: "gap-2 rounded-md border border-border p-3", children: confirming === row.id ? /* @__PURE__ */ jsxs56(Fragment9, { children: [
4787
- /* @__PURE__ */ jsx97(Text, { children: "Discard this parked sale?" }),
4788
- /* @__PURE__ */ jsxs56(View62, { className: "flex-row gap-2", children: [
4789
- /* @__PURE__ */ jsx97(
4916
+ !!error && /* @__PURE__ */ jsx98(Text, { testID: "parked-sales-error", className: "text-destructive", children: error }),
4917
+ parked.length === 0 && /* @__PURE__ */ jsx98(Text, { testID: "parked-sales-empty", children: "No parked sales." }),
4918
+ parked.map((row) => /* @__PURE__ */ jsx98(View63, { testID: `parked-row-${row.id}`, className: "gap-2 rounded-md border border-border p-3", children: confirming === row.id ? /* @__PURE__ */ jsxs57(Fragment10, { children: [
4919
+ /* @__PURE__ */ jsx98(Text, { children: "Discard this parked sale?" }),
4920
+ /* @__PURE__ */ jsxs57(View63, { className: "flex-row gap-2", children: [
4921
+ /* @__PURE__ */ jsx98(
4790
4922
  Button,
4791
4923
  {
4792
4924
  testID: `parked-discard-confirm-${row.id}`,
@@ -4795,22 +4927,22 @@ function ParkedSales({ sale, parked, onDiscard, onPark, onResume, currency, open
4795
4927
  await onDiscard(row.id);
4796
4928
  setConfirming(null);
4797
4929
  }),
4798
- children: /* @__PURE__ */ jsx97(Text, { children: "Discard" })
4930
+ children: /* @__PURE__ */ jsx98(Text, { children: "Discard" })
4799
4931
  }
4800
4932
  ),
4801
- /* @__PURE__ */ jsx97(Button, { testID: `parked-discard-cancel-${row.id}`, variant: "outline", onPress: () => setConfirming(null), children: /* @__PURE__ */ jsx97(Text, { children: "Cancel" }) })
4933
+ /* @__PURE__ */ jsx98(Button, { testID: `parked-discard-cancel-${row.id}`, variant: "outline", onPress: () => setConfirming(null), children: /* @__PURE__ */ jsx98(Text, { children: "Cancel" }) })
4802
4934
  ] })
4803
- ] }) : /* @__PURE__ */ jsxs56(Fragment9, { children: [
4804
- /* @__PURE__ */ jsx97(Text, { children: formatStockSyncTime(new Date(row.parkedAt), void 0, hour12) }),
4805
- /* @__PURE__ */ jsxs56(Text, { children: [
4935
+ ] }) : /* @__PURE__ */ jsxs57(Fragment10, { children: [
4936
+ /* @__PURE__ */ jsx98(Text, { children: formatStockSyncTime(new Date(row.parkedAt), void 0, hour12) }),
4937
+ /* @__PURE__ */ jsxs57(Text, { children: [
4806
4938
  row.itemCount,
4807
4939
  " ",
4808
4940
  row.itemCount === 1 ? "item" : "items"
4809
4941
  ] }),
4810
- /* @__PURE__ */ jsx97(Text, { children: formatMoney16({ amount: row.totalMinor, currency }) }),
4811
- !!row.customerName && /* @__PURE__ */ jsx97(Text, { children: row.customerName }),
4812
- /* @__PURE__ */ jsxs56(View62, { className: "flex-row gap-2", children: [
4813
- /* @__PURE__ */ jsx97(
4942
+ /* @__PURE__ */ jsx98(Text, { children: formatMoney17({ amount: row.totalMinor, currency }) }),
4943
+ !!row.customerName && /* @__PURE__ */ jsx98(Text, { children: row.customerName }),
4944
+ /* @__PURE__ */ jsxs57(View63, { className: "flex-row gap-2", children: [
4945
+ /* @__PURE__ */ jsx98(
4814
4946
  Button,
4815
4947
  {
4816
4948
  testID: `parked-resume-${row.id}`,
@@ -4820,17 +4952,17 @@ function ParkedSales({ sale, parked, onDiscard, onPark, onResume, currency, open
4820
4952
  if (refusal === null) onOpenChange(false);
4821
4953
  else setError(refusal);
4822
4954
  }),
4823
- children: /* @__PURE__ */ jsx97(Text, { children: "Resume" })
4955
+ children: /* @__PURE__ */ jsx98(Text, { children: "Resume" })
4824
4956
  }
4825
4957
  ),
4826
- /* @__PURE__ */ jsx97(
4958
+ /* @__PURE__ */ jsx98(
4827
4959
  Button,
4828
4960
  {
4829
4961
  testID: `parked-discard-${row.id}`,
4830
4962
  accessibilityLabel: "Discard parked sale",
4831
4963
  variant: "outline",
4832
4964
  onPress: () => setConfirming(row.id),
4833
- children: /* @__PURE__ */ jsx97(Text, { children: "Discard" })
4965
+ children: /* @__PURE__ */ jsx98(Text, { children: "Discard" })
4834
4966
  }
4835
4967
  )
4836
4968
  ] })
@@ -4840,10 +4972,10 @@ function ParkedSales({ sale, parked, onDiscard, onPark, onResume, currency, open
4840
4972
  }
4841
4973
 
4842
4974
  // src/layout/pos-layout.tsx
4843
- import { useState as useState23 } from "react";
4844
- import { Pressable as Pressable31, Text as Text41, View as View63, useWindowDimensions as useWindowDimensions4 } from "react-native";
4975
+ import { useState as useState24 } from "react";
4976
+ import { Pressable as Pressable32, Text as Text42, View as View64, useWindowDimensions as useWindowDimensions4 } from "react-native";
4845
4977
  import { cn as cn80 } from "@tallyui/theme";
4846
- import { Fragment as Fragment10, jsx as jsx98, jsxs as jsxs57 } from "react/jsx-runtime";
4978
+ import { Fragment as Fragment11, jsx as jsx99, jsxs as jsxs58 } from "react/jsx-runtime";
4847
4979
  function POSLayout({
4848
4980
  browseSlot,
4849
4981
  cartSlot,
@@ -4855,40 +4987,40 @@ function POSLayout({
4855
4987
  }) {
4856
4988
  const { width } = useWindowDimensions4();
4857
4989
  const layout = layoutProp ?? (width >= breakpoint ? "split" : "stacked");
4858
- const [activeTab, setActiveTab] = useState23("browse");
4859
- return /* @__PURE__ */ jsxs57(View63, { className: cn80("flex-1 bg-bg", className), ...viewProps, children: [
4860
- headerSlot && /* @__PURE__ */ jsx98(View63, { className: "border-b border-border", children: headerSlot }),
4861
- layout === "split" ? /* @__PURE__ */ jsxs57(View63, { className: "flex-1 flex-row", children: [
4862
- /* @__PURE__ */ jsx98(View63, { className: "flex-[3]", children: browseSlot }),
4863
- /* @__PURE__ */ jsx98(View63, { className: "flex-[2] border-l border-border", children: cartSlot })
4864
- ] }) : /* @__PURE__ */ jsxs57(Fragment10, { children: [
4865
- /* @__PURE__ */ jsxs57(View63, { className: "flex-row border-b border-border", children: [
4866
- /* @__PURE__ */ jsx98(
4867
- Pressable31,
4990
+ const [activeTab, setActiveTab] = useState24("browse");
4991
+ return /* @__PURE__ */ jsxs58(View64, { className: cn80("flex-1 bg-bg", className), ...viewProps, children: [
4992
+ headerSlot && /* @__PURE__ */ jsx99(View64, { className: "border-b border-border", children: headerSlot }),
4993
+ layout === "split" ? /* @__PURE__ */ jsxs58(View64, { className: "flex-1 flex-row", children: [
4994
+ /* @__PURE__ */ jsx99(View64, { className: "flex-[3]", children: browseSlot }),
4995
+ /* @__PURE__ */ jsx99(View64, { className: "flex-[2] border-l border-border", children: cartSlot })
4996
+ ] }) : /* @__PURE__ */ jsxs58(Fragment11, { children: [
4997
+ /* @__PURE__ */ jsxs58(View64, { className: "flex-row border-b border-border", children: [
4998
+ /* @__PURE__ */ jsx99(
4999
+ Pressable32,
4868
5000
  {
4869
5001
  onPress: () => setActiveTab("browse"),
4870
5002
  className: cn80("flex-1 py-3", activeTab === "browse" && "border-b-2 border-primary"),
4871
- children: /* @__PURE__ */ jsx98(Text41, { className: cn80("text-center text-sm font-semibold", activeTab === "browse" ? "text-primary" : "text-muted-foreground"), children: "Browse" })
5003
+ children: /* @__PURE__ */ jsx99(Text42, { className: cn80("text-center text-sm font-semibold", activeTab === "browse" ? "text-primary" : "text-muted-foreground"), children: "Browse" })
4872
5004
  }
4873
5005
  ),
4874
- /* @__PURE__ */ jsx98(
4875
- Pressable31,
5006
+ /* @__PURE__ */ jsx99(
5007
+ Pressable32,
4876
5008
  {
4877
5009
  onPress: () => setActiveTab("cart"),
4878
5010
  className: cn80("flex-1 py-3", activeTab === "cart" && "border-b-2 border-primary"),
4879
- children: /* @__PURE__ */ jsx98(Text41, { className: cn80("text-center text-sm font-semibold", activeTab === "cart" ? "text-primary" : "text-muted-foreground"), children: "Cart" })
5011
+ children: /* @__PURE__ */ jsx99(Text42, { className: cn80("text-center text-sm font-semibold", activeTab === "cart" ? "text-primary" : "text-muted-foreground"), children: "Cart" })
4880
5012
  }
4881
5013
  )
4882
5014
  ] }),
4883
- /* @__PURE__ */ jsx98(View63, { className: "flex-1", children: activeTab === "browse" ? browseSlot : cartSlot })
5015
+ /* @__PURE__ */ jsx99(View64, { className: "flex-1", children: activeTab === "browse" ? browseSlot : cartSlot })
4884
5016
  ] })
4885
5017
  ] });
4886
5018
  }
4887
5019
 
4888
5020
  // src/layout/checkout-layout.tsx
4889
- import { ScrollView as ScrollView15, View as View64, useWindowDimensions as useWindowDimensions5 } from "react-native";
5021
+ import { ScrollView as ScrollView15, View as View65, useWindowDimensions as useWindowDimensions5 } from "react-native";
4890
5022
  import { cn as cn81 } from "@tallyui/theme";
4891
- import { jsx as jsx99, jsxs as jsxs58 } from "react/jsx-runtime";
5023
+ import { jsx as jsx100, jsxs as jsxs59 } from "react/jsx-runtime";
4892
5024
  function CheckoutLayout({
4893
5025
  summarySlot,
4894
5026
  paymentSlot,
@@ -4900,12 +5032,12 @@ function CheckoutLayout({
4900
5032
  }) {
4901
5033
  const { width } = useWindowDimensions5();
4902
5034
  const layout = layoutProp ?? (width >= breakpoint ? "split" : "stacked");
4903
- return /* @__PURE__ */ jsxs58(View64, { className: cn81("flex-1 bg-bg", className), ...viewProps, children: [
4904
- headerSlot && /* @__PURE__ */ jsx99(View64, { className: "border-b border-border", children: headerSlot }),
4905
- layout === "split" ? /* @__PURE__ */ jsxs58(View64, { className: "flex-1 flex-row", children: [
4906
- /* @__PURE__ */ jsx99(View64, { className: "flex-1 border-r border-border", children: summarySlot }),
4907
- /* @__PURE__ */ jsx99(View64, { className: "flex-1", children: paymentSlot })
4908
- ] }) : /* @__PURE__ */ jsx99(ScrollView15, { className: "flex-1", children: /* @__PURE__ */ jsxs58(View64, { className: "gap-4 p-4", children: [
5035
+ return /* @__PURE__ */ jsxs59(View65, { className: cn81("flex-1 bg-bg", className), ...viewProps, children: [
5036
+ headerSlot && /* @__PURE__ */ jsx100(View65, { className: "border-b border-border", children: headerSlot }),
5037
+ layout === "split" ? /* @__PURE__ */ jsxs59(View65, { className: "flex-1 flex-row", children: [
5038
+ /* @__PURE__ */ jsx100(View65, { className: "flex-1 border-r border-border", children: summarySlot }),
5039
+ /* @__PURE__ */ jsx100(View65, { className: "flex-1", children: paymentSlot })
5040
+ ] }) : /* @__PURE__ */ jsx100(ScrollView15, { className: "flex-1", children: /* @__PURE__ */ jsxs59(View65, { className: "gap-4 p-4", children: [
4909
5041
  summarySlot,
4910
5042
  paymentSlot
4911
5043
  ] }) })
@@ -4913,10 +5045,10 @@ function CheckoutLayout({
4913
5045
  }
4914
5046
 
4915
5047
  // src/layout/settings-layout.tsx
4916
- import { useState as useState24 } from "react";
4917
- import { Pressable as Pressable32, Text as Text42, View as View65, useWindowDimensions as useWindowDimensions6 } from "react-native";
5048
+ import { useState as useState25 } from "react";
5049
+ import { Pressable as Pressable33, Text as Text43, View as View66, useWindowDimensions as useWindowDimensions6 } from "react-native";
4918
5050
  import { cn as cn82 } from "@tallyui/theme";
4919
- import { jsx as jsx100, jsxs as jsxs59 } from "react/jsx-runtime";
5051
+ import { jsx as jsx101, jsxs as jsxs60 } from "react/jsx-runtime";
4920
5052
  function SettingsLayout({
4921
5053
  navSlot,
4922
5054
  contentSlot,
@@ -4928,26 +5060,26 @@ function SettingsLayout({
4928
5060
  }) {
4929
5061
  const { width } = useWindowDimensions6();
4930
5062
  const layout = layoutProp ?? (width >= breakpoint ? "split" : "stacked");
4931
- const [showNav, setShowNav] = useState24(false);
4932
- return /* @__PURE__ */ jsxs59(View65, { className: cn82("flex-1 bg-bg", className), ...viewProps, children: [
4933
- headerSlot && /* @__PURE__ */ jsx100(View65, { className: "border-b border-border", children: headerSlot }),
4934
- layout === "split" ? /* @__PURE__ */ jsxs59(View65, { className: "flex-1 flex-row", children: [
4935
- /* @__PURE__ */ jsx100(View65, { className: "w-64 border-r border-border", children: navSlot }),
4936
- /* @__PURE__ */ jsx100(View65, { className: "flex-1", children: contentSlot })
4937
- ] }) : /* @__PURE__ */ jsx100(View65, { className: "flex-1", children: showNav ? /* @__PURE__ */ jsxs59(View65, { className: "flex-1", children: [
5063
+ const [showNav, setShowNav] = useState25(false);
5064
+ return /* @__PURE__ */ jsxs60(View66, { className: cn82("flex-1 bg-bg", className), ...viewProps, children: [
5065
+ headerSlot && /* @__PURE__ */ jsx101(View66, { className: "border-b border-border", children: headerSlot }),
5066
+ layout === "split" ? /* @__PURE__ */ jsxs60(View66, { className: "flex-1 flex-row", children: [
5067
+ /* @__PURE__ */ jsx101(View66, { className: "w-64 border-r border-border", children: navSlot }),
5068
+ /* @__PURE__ */ jsx101(View66, { className: "flex-1", children: contentSlot })
5069
+ ] }) : /* @__PURE__ */ jsx101(View66, { className: "flex-1", children: showNav ? /* @__PURE__ */ jsxs60(View66, { className: "flex-1", children: [
4938
5070
  navSlot,
4939
- /* @__PURE__ */ jsx100(Pressable32, { onPress: () => setShowNav(false), className: "border-t border-border p-3", children: /* @__PURE__ */ jsx100(Text42, { className: "text-center text-sm font-medium text-primary", children: "Done" }) })
4940
- ] }) : /* @__PURE__ */ jsxs59(View65, { className: "flex-1", children: [
4941
- /* @__PURE__ */ jsx100(Pressable32, { onPress: () => setShowNav(true), className: "border-b border-border px-4 py-2", children: /* @__PURE__ */ jsx100(Text42, { className: "text-sm font-medium text-primary", children: "Menu" }) }),
5071
+ /* @__PURE__ */ jsx101(Pressable33, { onPress: () => setShowNav(false), className: "border-t border-border p-3", children: /* @__PURE__ */ jsx101(Text43, { className: "text-center text-sm font-medium text-primary", children: "Done" }) })
5072
+ ] }) : /* @__PURE__ */ jsxs60(View66, { className: "flex-1", children: [
5073
+ /* @__PURE__ */ jsx101(Pressable33, { onPress: () => setShowNav(true), className: "border-b border-border px-4 py-2", children: /* @__PURE__ */ jsx101(Text43, { className: "text-sm font-medium text-primary", children: "Menu" }) }),
4942
5074
  contentSlot
4943
5075
  ] }) })
4944
5076
  ] });
4945
5077
  }
4946
5078
 
4947
5079
  // src/layout/live-tab-screen.tsx
4948
- import { Pressable as Pressable33, Text as Text43, View as View66 } from "react-native";
5080
+ import { Pressable as Pressable34, Text as Text44, View as View67 } from "react-native";
4949
5081
  import { cn as cn83 } from "@tallyui/theme";
4950
- import { jsx as jsx101, jsxs as jsxs60 } from "react/jsx-runtime";
5082
+ import { jsx as jsx102, jsxs as jsxs61 } from "react/jsx-runtime";
4951
5083
  function LiveTabScreen({
4952
5084
  state,
4953
5085
  onUseHere,
@@ -4964,21 +5096,21 @@ function LiveTabScreen({
4964
5096
  const body = state === "parked" ? parkedBody : blockedBody;
4965
5097
  const buttonLabel = state === "parked" ? useHereLabel : reloadLabel;
4966
5098
  const onPress = state === "parked" ? onUseHere : onReload;
4967
- return /* @__PURE__ */ jsxs60(View66, { className: cn83("flex-1 items-center justify-center gap-4 bg-bg p-6", className), ...viewProps, children: [
4968
- /* @__PURE__ */ jsx101(Text43, { className: "text-center text-lg font-bold text-foreground", children: parkedTitle }),
4969
- /* @__PURE__ */ jsx101(Text43, { className: "text-center text-sm text-muted-foreground", children: body }),
4970
- /* @__PURE__ */ jsx101(Pressable33, { onPress, className: "items-center rounded-lg bg-primary px-4 py-3", children: /* @__PURE__ */ jsx101(Text43, { className: "text-sm font-semibold text-primary-foreground", children: buttonLabel }) })
5099
+ return /* @__PURE__ */ jsxs61(View67, { className: cn83("flex-1 items-center justify-center gap-4 bg-bg p-6", className), ...viewProps, children: [
5100
+ /* @__PURE__ */ jsx102(Text44, { className: "text-center text-lg font-bold text-foreground", children: parkedTitle }),
5101
+ /* @__PURE__ */ jsx102(Text44, { className: "text-center text-sm text-muted-foreground", children: body }),
5102
+ /* @__PURE__ */ jsx102(Pressable34, { onPress, className: "items-center rounded-lg bg-primary px-4 py-3", children: /* @__PURE__ */ jsx102(Text44, { className: "text-sm font-semibold text-primary-foreground", children: buttonLabel }) })
4971
5103
  ] });
4972
5104
  }
4973
5105
 
4974
5106
  // src/layout/store-settings-choice-screen.tsx
4975
- import { useState as useState25 } from "react";
4976
- import { Pressable as Pressable34, Text as Text44, View as View67 } from "react-native";
5107
+ import { useState as useState26 } from "react";
5108
+ import { Pressable as Pressable35, Text as Text45, View as View68 } from "react-native";
4977
5109
  import { cn as cn84 } from "@tallyui/theme";
4978
- import { jsx as jsx102, jsxs as jsxs61 } from "react/jsx-runtime";
5110
+ import { jsx as jsx103, jsxs as jsxs62 } from "react/jsx-runtime";
4979
5111
  function useSectionSelection(options, initialValue) {
4980
5112
  const preset = options.length === 1 ? options[0].value : options.find((o) => o.value === initialValue)?.value;
4981
- return useState25(preset);
5113
+ return useState26(preset);
4982
5114
  }
4983
5115
  function defaultCountryName(code, locale) {
4984
5116
  try {
@@ -5024,32 +5156,32 @@ function StoreSettingsChoiceScreen({
5024
5156
  else if (initial?.channel !== void 0) choice.channel = initial.channel;
5025
5157
  onSubmit(choice);
5026
5158
  };
5027
- return /* @__PURE__ */ jsxs61(View67, { className: cn84("w-full max-w-[560px] flex-1 self-center gap-6 bg-bg p-6", className), ...viewProps, children: [
5028
- /* @__PURE__ */ jsxs61(View67, { className: "gap-2", children: [
5029
- /* @__PURE__ */ jsx102(Text44, { className: "text-lg font-bold text-foreground", children: title }),
5030
- /* @__PURE__ */ jsx102(Text44, { className: "text-sm text-muted-foreground", children: body })
5159
+ return /* @__PURE__ */ jsxs62(View68, { className: cn84("w-full max-w-[560px] flex-1 self-center gap-6 bg-bg p-6", className), ...viewProps, children: [
5160
+ /* @__PURE__ */ jsxs62(View68, { className: "gap-2", children: [
5161
+ /* @__PURE__ */ jsx103(Text45, { className: "text-lg font-bold text-foreground", children: title }),
5162
+ /* @__PURE__ */ jsx103(Text45, { className: "text-sm text-muted-foreground", children: body })
5031
5163
  ] }),
5032
- showRegions && /* @__PURE__ */ jsx102(ChoiceSection, { label: regionLabel, options: regionOptions, selected: region, onSelect: setRegion }),
5033
- showCountries && /* @__PURE__ */ jsx102(ChoiceSection, { label: countryLabel, options: countryOptions, selected: country, onSelect: setCountry }),
5034
- showChannels && /* @__PURE__ */ jsx102(ChoiceSection, { label: channelLabel, options: channelOptions, selected: channel, onSelect: setChannel }),
5035
- /* @__PURE__ */ jsx102(
5036
- Pressable34,
5164
+ showRegions && /* @__PURE__ */ jsx103(ChoiceSection, { label: regionLabel, options: regionOptions, selected: region, onSelect: setRegion }),
5165
+ showCountries && /* @__PURE__ */ jsx103(ChoiceSection, { label: countryLabel, options: countryOptions, selected: country, onSelect: setCountry }),
5166
+ showChannels && /* @__PURE__ */ jsx103(ChoiceSection, { label: channelLabel, options: channelOptions, selected: channel, onSelect: setChannel }),
5167
+ /* @__PURE__ */ jsx103(
5168
+ Pressable35,
5037
5169
  {
5038
5170
  onPress: handleSubmit,
5039
5171
  disabled: !complete,
5040
5172
  className: cn84("items-center rounded-lg bg-primary px-4 py-3", !complete && "opacity-50"),
5041
- children: /* @__PURE__ */ jsx102(Text44, { className: "text-sm font-semibold text-primary-foreground", children: submitLabel })
5173
+ children: /* @__PURE__ */ jsx103(Text45, { className: "text-sm font-semibold text-primary-foreground", children: submitLabel })
5042
5174
  }
5043
5175
  )
5044
5176
  ] });
5045
5177
  }
5046
5178
  function ChoiceSection({ label, options, selected, onSelect }) {
5047
- return /* @__PURE__ */ jsxs61(View67, { className: "gap-1.5", children: [
5048
- /* @__PURE__ */ jsx102(Text44, { className: "text-xs font-semibold text-muted-foreground", children: label }),
5049
- /* @__PURE__ */ jsx102(View67, { className: "rounded-lg border border-border", children: options.map((option, index) => {
5179
+ return /* @__PURE__ */ jsxs62(View68, { className: "gap-1.5", children: [
5180
+ /* @__PURE__ */ jsx103(Text45, { className: "text-xs font-semibold text-muted-foreground", children: label }),
5181
+ /* @__PURE__ */ jsx103(View68, { className: "rounded-lg border border-border", children: options.map((option, index) => {
5050
5182
  const checked = option.value === selected;
5051
- return /* @__PURE__ */ jsxs61(
5052
- Pressable34,
5183
+ return /* @__PURE__ */ jsxs62(
5184
+ Pressable35,
5053
5185
  {
5054
5186
  onPress: () => onSelect(option.value),
5055
5187
  accessibilityRole: "radio",
@@ -5057,8 +5189,8 @@ function ChoiceSection({ label, options, selected, onSelect }) {
5057
5189
  testID: `choice-option-${option.value}`,
5058
5190
  className: cn84("flex-row items-center gap-3 px-4 py-3", index > 0 && "border-t border-border", checked && "bg-primary/10"),
5059
5191
  children: [
5060
- /* @__PURE__ */ jsx102(View67, { className: "h-4 w-4 items-center justify-center rounded-full border border-primary", children: checked && /* @__PURE__ */ jsx102(View67, { testID: `choice-option-${option.value}-dot`, className: "h-2.5 w-2.5 rounded-full bg-primary" }) }),
5061
- /* @__PURE__ */ jsx102(Text44, { className: cn84("text-sm", checked ? "font-semibold text-primary" : "text-foreground"), children: option.label })
5192
+ /* @__PURE__ */ jsx103(View68, { className: "h-4 w-4 items-center justify-center rounded-full border border-primary", children: checked && /* @__PURE__ */ jsx103(View68, { testID: `choice-option-${option.value}-dot`, className: "h-2.5 w-2.5 rounded-full bg-primary" }) }),
5193
+ /* @__PURE__ */ jsx103(Text45, { className: cn84("text-sm", checked ? "font-semibold text-primary" : "text-foreground"), children: option.label })
5062
5194
  ]
5063
5195
  },
5064
5196
  option.value
@@ -5232,6 +5364,7 @@ export {
5232
5364
  SettingsLayout,
5233
5365
  SettingsRow,
5234
5366
  Slider,
5367
+ SplitTender,
5235
5368
  StoreSettingsChoiceScreen,
5236
5369
  Switch,
5237
5370
  SyncStatus,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyui/components",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
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.2.0",
31
- "@tallyui/primitives": "^3.2.0",
32
- "@tallyui/theme": "^3.2.0",
30
+ "@tallyui/core": "^3.3.0",
31
+ "@tallyui/primitives": "^3.3.0",
32
+ "@tallyui/theme": "^3.3.0",
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.2.0"
39
+ "@tallyui/pos": "3.3.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@tallyui/connector-medusa": "3.2.0",
43
- "@tallyui/connector-vendure": "3.2.0",
44
- "@tallyui/connector-woocommerce": "3.2.0",
45
- "@tallyui/mock-api": "3.2.0",
42
+ "@tallyui/connector-medusa": "3.3.0",
43
+ "@tallyui/connector-vendure": "3.3.0",
44
+ "@tallyui/connector-woocommerce": "3.3.0",
45
+ "@tallyui/mock-api": "3.3.0",
46
46
  "@types/react": ">=18",
47
47
  "react": "^19.2.4",
48
48
  "react-native-svg": "15.12.1",
package/src/index.ts CHANGED
@@ -154,7 +154,7 @@ export {
154
154
 
155
155
  // Sale components
156
156
  export {
157
- Cart, CartBar, Tender, DiscountForm, DiscountChips, parseDiscount, discountLabel,
157
+ Cart, CartBar, Tender, SplitTender, DiscountForm, DiscountChips, parseDiscount, discountLabel,
158
158
  PriceForm, parsePrice,
159
159
  Catalogue, formatStockSyncTime, Receipt, injectPrintStyle, SyncStatus, OrdersList, orderReference,
160
160
  ParkedSales, type ParkedSalesProps,
package/src/sale/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Cart } from './cart';
2
2
  export { CartBar } from './cart-bar';
3
3
  export { Tender } from './tender';
4
+ export { SplitTender } from './split-tender';
4
5
  export { DiscountForm, DiscountChips, parseDiscount, discountLabel } from './discount-form';
5
6
  export { PriceForm, parsePrice } from './price-form';
6
7
  export { Catalogue, formatStockSyncTime } from './catalogue';
@@ -0,0 +1,81 @@
1
+ import { useState, type JSX } from 'react';
2
+ import { Pressable, Text, TextInput, View } from 'react-native';
3
+ import { formatMoney, minorUnitDigits } from '@tallyui/core';
4
+ import type { useSale } from '@tallyui/pos';
5
+ import { tenderLabel } from '../register/register-count';
6
+ import { parsePrice } from './price-form';
7
+
8
+ export function SplitTender({ sale }: { sale: ReturnType<typeof useSale> }): JSX.Element | null {
9
+ const { order, stage } = sale;
10
+ const [method, setMethod] = useState<'cash' | 'external'>('cash');
11
+ const [amount, setAmount] = useState<string | null>(null);
12
+ const [reference, setReference] = useState('');
13
+ const [error, setError] = useState<string | null>(null);
14
+ if (stage.kind !== 'tender') return null;
15
+ const money = (value: number) => formatMoney({ amount: value, currency: order.currency });
16
+ const digits = minorUnitDigits(order.currency);
17
+ const amountText = amount ?? (order.balanceDueMinor / 10 ** digits).toFixed(digits);
18
+ const incomplete = order.balanceDueMinor > 0 || order.payments.length === 0;
19
+ function add() {
20
+ const parsed = parsePrice(amountText, order.currency);
21
+ if (typeof parsed === 'string') { setError(parsed); return; }
22
+ const id = sale.addTender({ method, amountMinor: parsed,
23
+ ...(method === 'external' && reference ? { reference } : {}) });
24
+ if (id === null) {
25
+ setError("That tender wasn't added: nothing is due, or the amount isn't valid.");
26
+ return;
27
+ }
28
+ setError(null);
29
+ setReference('');
30
+ setAmount(null);
31
+ }
32
+ return <View dataSet={{ print: 'hide' }} className="gap-4 p-4">
33
+ <View testID="split-tender-summary" className="gap-2">
34
+ <Text className="text-foreground">Total: {money(order.totalMinor)}</Text>
35
+ <Text className="text-foreground">Paid: {money(order.paidMinor)}</Text>
36
+ <Text className="text-foreground">Remaining: {money(order.balanceDueMinor)}</Text>
37
+ {order.changeDueMinor > 0 ? <Text className="text-foreground">Change: {money(order.changeDueMinor)}</Text> : null}
38
+ </View>
39
+ <View testID="split-tender-list" className="gap-2">
40
+ {order.payments.map((payment) => <View key={payment.id} testID={`split-tender-row-${payment.id}`} className="gap-2">
41
+ <Text className="text-foreground">{tenderLabel(payment.method)} {money(payment.amountMinor)}</Text>
42
+ {payment.reference ? <Text className="text-foreground">{payment.reference}</Text> : null}
43
+ <Pressable testID={`split-tender-remove-${payment.id}`} accessibilityRole="button" accessibilityLabel="Remove tender"
44
+ onPress={() => sale.removeTender(payment.id)} className="rounded-md border border-border bg-card px-4 py-3">
45
+ <Text className="text-center text-foreground">Remove</Text>
46
+ </Pressable>
47
+ </View>)}
48
+ </View>
49
+ <View testID="split-tender-add" className="gap-2">
50
+ <View className="flex-row gap-2">
51
+ {(['cash', 'external'] as const).map((value) => <Pressable key={value}
52
+ testID={`split-tender-method-${value === 'cash' ? 'cash' : 'card'}`} accessibilityRole="button"
53
+ accessibilityState={{ selected: method === value }} onPress={() => setMethod(value)}
54
+ className={`rounded-md border border-border px-4 py-3 ${method === value ? 'bg-primary' : 'bg-card'}`}>
55
+ <Text className={`text-center ${method === value ? 'text-primary-foreground' : 'text-foreground'}`}>{tenderLabel(value)}</Text>
56
+ </Pressable>)}
57
+ </View>
58
+ <TextInput testID="split-tender-amount" accessibilityLabel="Tender amount" inputMode="decimal"
59
+ value={amountText} onChangeText={setAmount} className="rounded-md border border-border px-3 py-2 text-foreground" />
60
+ {method === 'external' ? <TextInput testID="split-tender-reference" accessibilityLabel="Terminal reference"
61
+ placeholder="Terminal reference" maxLength={255} value={reference} onChangeText={setReference}
62
+ className="rounded-md border border-border px-3 py-2 text-foreground" /> : null}
63
+ {error ? <Text testID="split-tender-error" accessibilityRole="alert" className="text-destructive">{error}</Text> : null}
64
+ <Pressable testID="split-tender-add-button" accessibilityRole="button" onPress={add} className="rounded-md bg-primary px-4 py-3">
65
+ <Text className="text-center font-semibold text-primary-foreground">Add</Text>
66
+ </Pressable>
67
+ </View>
68
+ <Pressable testID="split-tender-complete" accessibilityRole="button" disabled={incomplete} onPress={sale.complete}
69
+ className={`rounded-md bg-primary px-4 py-3 ${incomplete ? 'opacity-50' : ''}`}>
70
+ <Text className="text-center font-semibold text-primary-foreground">Complete sale</Text>
71
+ </Pressable>
72
+ {sale.error ? <Text accessibilityRole="alert" className="text-destructive">{sale.error}</Text> : null}
73
+ {sale.canContinue ? <>
74
+ <Text className="text-foreground">This sale is stored and will be sent. Continue to the next sale.</Text>
75
+ <Pressable accessibilityRole="button" onPress={() => sale.continueSale()} className="rounded-md bg-primary px-4 py-3">
76
+ <Text className="text-center font-semibold text-primary-foreground">Continue</Text>
77
+ </Pressable>
78
+ </> : null}
79
+ <Pressable accessibilityRole="button" onPress={sale.cancelTender} className="rounded-md border border-border bg-card px-4 py-3"><Text className="text-center text-foreground">Back</Text></Pressable>
80
+ </View>;
81
+ }