@aglyn/plugins-commerce 1.0.0-beta.232 → 1.0.0-beta.233
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/package.json +13 -13
- package/src/lib/components/cart-extras.d.ts +6 -0
- package/src/lib/components/cart-extras.js +4 -1
- package/src/lib/components/cart-extras.js.map +1 -1
- package/src/lib/components/cart-fulfillment.d.ts +117 -0
- package/src/lib/components/cart-fulfillment.js +367 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -0
- package/src/lib/components/cart.js +52 -10
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +42 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +38 -10
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.js +177 -3
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +10 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
- package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
- package/src/lib/components/console/order-offline-notice.component.js +53 -0
- package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-api.d.ts +24 -1
- package/src/lib/components/console/pos/pos-api.js +13 -0
- package/src/lib/components/console/pos/pos-api.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
- package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
- package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
- package/src/lib/components/console/pos/use-pos-display.js +3 -2
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-devices-card.component.js +2 -2
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
- package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
- package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
- package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
- package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
- package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
- package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +174 -27
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +35 -10
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/stock-movements-card.component.js +48 -16
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
- package/src/lib/components/console/store-zones.d.ts +8 -1
- package/src/lib/components/console/store-zones.js +11 -1
- package/src/lib/components/console/store-zones.js.map +1 -1
- package/src/lib/components/order-status.js +66 -0
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +42 -15
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
- package/src/lib/constants/local-fulfillment-list-query.js +84 -0
- package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
- package/src/lib/constants/orders-list-query.d.ts +7 -2
- package/src/lib/constants/orders-list-query.js +89 -8
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
- package/src/lib/constants/pos-shift-list-query.js +115 -0
- package/src/lib/constants/pos-shift-list-query.js.map +1 -0
- package/src/lib/constants/product-list-query.d.ts +6 -1
- package/src/lib/constants/product-list-query.js +77 -8
- package/src/lib/constants/product-list-query.js.map +1 -1
- package/src/lib/constants/stock-movement-query.d.ts +3 -2
- package/src/lib/constants/stock-movement-query.js +52 -8
- package/src/lib/constants/stock-movement-query.js.map +1 -1
- package/src/lib/model/buyer-notifications.d.ts +5 -3
- package/src/lib/model/buyer-notifications.js +25 -4
- package/src/lib/model/buyer-notifications.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +59 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
- package/src/lib/model/commerce-pos-kiosk.js +147 -0
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +234 -0
- package/src/lib/model/commerce-pos-offline.js +301 -0
- package/src/lib/model/commerce-pos-offline.js.map +1 -0
- package/src/lib/model/commerce-pos-ops.d.ts +16 -6
- package/src/lib/model/commerce-pos-ops.js +12 -4
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +13 -1
- package/src/lib/model/commerce-pos.js +5 -4
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-stock-holds.d.ts +24 -3
- package/src/lib/model/commerce-stock-holds.js +26 -8
- package/src/lib/model/commerce-stock-holds.js.map +1 -1
- package/src/lib/model/commerce.d.ts +3 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +4 -0
- package/src/lib/model/index.js +4 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
- package/src/lib/model/local-fulfillment-settings.js +361 -0
- package/src/lib/model/local-fulfillment-settings.js.map +1 -0
- package/src/lib/model/order-list-fields.d.ts +10 -1
- package/src/lib/model/order-list-fields.js +1 -0
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +131 -0
- package/src/lib/model/order-local-fulfillment.js +125 -0
- package/src/lib/model/order-local-fulfillment.js.map +1 -0
- package/src/lib/model/order-shipping-export.js +4 -0
- package/src/lib/model/order-shipping-export.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +20 -1
- package/src/lib/model/order-status-view.js +60 -6
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/plugin-config.js +6 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +8 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/pos-kiosk-config.d.ts +26 -0
- package/src/lib/pos-kiosk-config.js +59 -0
- package/src/lib/pos-kiosk-config.js.map +1 -0
- package/src/lib/server/api-v1/openapi.js +8 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +19 -0
- package/src/lib/server/api-v1/order-view.js +35 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/billing-webhook.js +238 -87
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.d.ts +2 -0
- package/src/lib/server/cart-checkout.js +173 -13
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout-extras.js +18 -4
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +71 -14
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/local-fulfillment-status.d.ts +86 -0
- package/src/lib/server/local-fulfillment-status.js +379 -0
- package/src/lib/server/local-fulfillment-status.js.map +1 -0
- package/src/lib/server/local-fulfillment.d.ts +190 -0
- package/src/lib/server/local-fulfillment.js +580 -0
- package/src/lib/server/local-fulfillment.js.map +1 -0
- package/src/lib/server/membership-register.js +3 -1
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/order-notifications.js +61 -0
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-display.d.ts +27 -1
- package/src/lib/server/pos-display.js +50 -9
- package/src/lib/server/pos-display.js.map +1 -1
- package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
- package/src/lib/server/pos-kiosk-principal.js +42 -0
- package/src/lib/server/pos-kiosk-principal.js.map +1 -0
- package/src/lib/server/pos-kiosk.d.ts +54 -0
- package/src/lib/server/pos-kiosk.js +972 -0
- package/src/lib/server/pos-kiosk.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +85 -0
- package/src/lib/server/pos-offline-sync.js +765 -0
- package/src/lib/server/pos-offline-sync.js.map +1 -0
- package/src/lib/server/pos-order.js +22 -4
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.d.ts +6 -0
- package/src/lib/server/pos-payment.js +225 -6
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-print.js +5 -0
- package/src/lib/server/pos-print.js.map +1 -1
- package/src/lib/server/pos-return.js +27 -0
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-sale.d.ts +6 -0
- package/src/lib/server/pos-sale.js +2 -0
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-shift.js +8 -1
- package/src/lib/server/pos-shift.js.map +1 -1
- package/src/lib/server/refund.js +3 -0
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/stock-hold.d.ts +9 -1
- package/src/lib/server/stock-hold.js +15 -7
- package/src/lib/server/stock-hold.js.map +1 -1
- package/src/lib/server.js +16 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.js +161 -0
- package/src/lib/tenant-emails.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/pickup-delivery-queue-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport {\n LOCAL_FULFILLMENT_QUEUE_TABS,\n localFulfillmentQueueQuery,\n} from '../../constants/local-fulfillment-list-query'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n Button,\n MenuItem,\n Stack,\n Tab,\n Tabs,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport OrderDetailDialog from './order-detail-dialog.component'\nimport { nextLocalFulfillmentSteps, useLocalFulfillmentStep } from './order-local-fulfillment-panel.component'\n\nexport interface PickupDeliveryQueueCardProps {\n hostId: string\n}\n\n/**\n * The pickup and local delivery queue (AGL-3624): what to prepare, what is\n * waiting at the counter, and what the store's driver takes out, per\n * location, soonest first — each tab ONE Firestore query on the fields every\n * pickup and delivery writer stamps (`constants/local-fulfillment-list-query`),\n * which the native Aglyn app runs too. A row's next step is one click; the\n * order opens for anything more.\n *\n * Draws nothing for a store with no pickup location and no local delivery\n * switched on, so a store that only ships never sees it.\n */\nexport function PickupDeliveryQueueCard(props: PickupDeliveryQueueCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const [tab, setTab] = useState<CommerceModel.OrderFulfillmentKey>('pickup_preparing')\n const [locationId, setLocationId] = useState('')\n const [open, setOpen] = useState<(CommerceModel.HostOrder & { $id: string }) | null>(null)\n const { run, busy } = useLocalFulfillmentStep(hostId)\n\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { data: settings } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const pickupOn = (locationDocs ?? []).some(\n (location: any) => CommerceModel.normalizePickupSettings(location?.pickup).enabled,\n )\n const deliveryOn = Boolean(\n CommerceModel.normalizeLocalDeliverySettings(settings?.localDelivery).enabled,\n )\n const tabs = LOCAL_FULFILLMENT_QUEUE_TABS.filter((entry) =>\n entry.method === 'pickup' ? pickupOn : deliveryOn,\n )\n const active = tabs.some((entry) => entry.key === tab) ? tab : (tabs[0]?.key ?? tab)\n const plan = useMemo(() => localFulfillmentQueueQuery(active, locationId || null), [active, locationId])\n const anyOn = tabs.length > 0\n // Paged like every list of something that grows: one tab's query, a page\n // at a time (the native app pages it by `plan.limit`).\n const {\n rows,\n status,\n page,\n setPage,\n pageSize,\n setPageSize,\n hasMore,\n } = usePagedCollection<any>(\n (pageLimit) =>\n anyOn\n ? query(\n collection(firestore, 'hosts', hostId, 'orders'),\n ...plan.where.map((clause) => where(clause.field, clause.op, clause.value as never)),\n orderBy(plan.orderBy.field, plan.orderBy.direction),\n limit(pageLimit),\n )\n : null,\n [firestore, hostId, active, locationId, anyOn],\n { idField: '$id' },\n )\n\n if (!tabs.length) return null\n const orders = (rows ?? []).map((row: any) => ({\n ...CommerceModel.liftLegacyOrder(row),\n $id: String(row.$id),\n })) as Array<CommerceModel.HostOrder & { $id: string }>\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n\n return (\n <CardDisplay\n header={'Pickup & delivery'}\n help={pluginDocsHelp('commerce', { anchor: '#pickup-and-local-delivery' })}\n HeaderProps={{\n action:\n locations.length > 1 ? (\n <TextField\n select\n label=\"Location\"\n value={locationId}\n onChange={(event) => setLocationId(event.target.value)}\n size=\"small\"\n sx={{ minWidth: 160 }}\n >\n <MenuItem value=\"\">{'All locations'}</MenuItem>\n {locations.map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null,\n }}\n contentGutterX\n contentGutterY\n >\n <Tabs\n value={active}\n onChange={(_event, value) => setTab(value)}\n variant=\"scrollable\"\n allowScrollButtonsMobile\n >\n {tabs.map((entry) => (\n <Tab key={entry.key} value={entry.key} label={entry.label} />\n ))}\n </Tabs>\n <Stack spacing={1} sx={{ pt: 1.5 }}>\n {status === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : orders.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing here right now.'}\n </Typography>\n ) : (\n orders.map((order) => {\n const units = (order.lineItems ?? []).reduce((sum, line) => sum + (Number(line.quantity) || 0), 0)\n const when =\n order.fulfillmentMethod === 'local_delivery'\n ? (order.localDelivery?.windowLabel ?? '')\n : order.createdAtMs\n ? `Ordered ${new Date(order.createdAtMs).toLocaleString()}`\n : ''\n const where =\n order.fulfillmentMethod === 'pickup'\n ? (order.pickup?.locationName ?? '')\n : [order.shippingAddress?.line1, order.localDelivery?.postalCode].filter(Boolean).join(', ')\n return (\n <Stack\n key={order.$id}\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { sm: 'center' } }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${CommerceModel.formatOrderNumber(order, order.$id)} · ${\n order.customerName || order.customerEmail || 'Guest buyer'\n } · ${units} item${units === 1 ? '' : 's'}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[when, where].filter(Boolean).join(' · ')}\n </Typography>\n </Stack>\n {nextLocalFulfillmentSteps(order)\n .filter((entry) => entry.primary)\n .map((entry) => (\n <Button\n key={entry.step}\n size=\"small\"\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void run(order.$id, entry.step)}\n >\n {entry.label}\n </Button>\n ))}\n <Button size=\"small\" onClick={() => setOpen(order)}>\n {'Open'}\n </Button>\n </Stack>\n )\n })\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={orders.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {open ? <OrderDetailDialog hostId={hostId} order={open} onClose={() => setOpen(null)} /> : null}\n </CardDisplay>\n )\n}\nPickupDeliveryQueueCard.displayName = 'PickupDeliveryQueueCard'\n\nexport default PickupDeliveryQueueCard\n"],"names":["CommerceModel","LOCAL_FULFILLMENT_QUEUE_TABS","localFulfillmentQueueQuery","CardDisplay","pluginDocsHelp","Button","MenuItem","Stack","Tab","Tabs","TextField","Typography","collection","doc","limit","orderBy","query","where","useMemo","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","usePagedCollection","ListPagination","OrderDetailDialog","nextLocalFulfillmentSteps","useLocalFulfillmentStep","PickupDeliveryQueueCard","props","tabs","hostId","firestore","tab","setTab","locationId","setLocationId","open","setOpen","run","busy","data","locationDocs","idField","settings","pickupOn","some","location","normalizePickupSettings","pickup","enabled","deliveryOn","Boolean","normalizeLocalDeliverySettings","localDelivery","filter","entry","method","active","key","plan","anyOn","length","rows","status","page","setPage","pageSize","setPageSize","hasMore","pageLimit","map","clause","field","op","value","direction","orders","row","liftLegacyOrder","$id","String","locations","sort","a","b","name","localeCompare","header","help","anchor","HeaderProps","action","select","label","onChange","event","target","size","sx","minWidth","contentGutterX","contentGutterY","_event","variant","allowScrollButtonsMobile","spacing","pt","color","order","units","lineItems","reduce","sum","line","Number","quantity","when","fulfillmentMethod","windowLabel","createdAtMs","Date","toLocaleString","locationName","shippingAddress","line1","postalCode","join","xs","sm","alignItems","flex","noWrap","formatOrderNumber","customerName","customerEmail","primary","disabled","onClick","step","rowCount","onPageChange","onPageSizeChange","onClose","displayName"],"mappings":"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"}
|
|
@@ -43,6 +43,8 @@ export interface PosSaleSummary {
|
|
|
43
43
|
changeCents?: number;
|
|
44
44
|
checkoutUrl?: string;
|
|
45
45
|
readerId?: string;
|
|
46
|
+
/** What a store-credit payment's provider calls it (AGL-3640). */
|
|
47
|
+
creditLabel?: string;
|
|
46
48
|
failureMessage?: string;
|
|
47
49
|
livemode?: boolean;
|
|
48
50
|
}>;
|
|
@@ -69,6 +71,20 @@ export interface PosRegisterContext {
|
|
|
69
71
|
}>;
|
|
70
72
|
publishableKey: string;
|
|
71
73
|
smsReceipts: boolean;
|
|
74
|
+
/** Store credit other plugins keep, taken as a tender (AGL-3640). */
|
|
75
|
+
credits?: Array<{
|
|
76
|
+
providerId: string;
|
|
77
|
+
label: string;
|
|
78
|
+
lookup: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
/** A store-credit account a staff lookup found (AGL-3640). */
|
|
82
|
+
export interface PosCreditAccount {
|
|
83
|
+
reference: string;
|
|
84
|
+
label: string;
|
|
85
|
+
last4: string;
|
|
86
|
+
availableCents: number;
|
|
87
|
+
detail?: string;
|
|
72
88
|
}
|
|
73
89
|
type User = Parameters<typeof authorizedFetch>[0];
|
|
74
90
|
export declare class PosRequestError extends Error {
|
|
@@ -96,7 +112,7 @@ export declare function openPosSale(user: User, attemptKey: string, body: {
|
|
|
96
112
|
dueCents: number;
|
|
97
113
|
stockWarnings?: unknown[];
|
|
98
114
|
}>;
|
|
99
|
-
export type PosTenderAction = 'cash' | 'gift-card' | 'folio' | 'card-present' | 'card-present-sdk' | 'card-keyed' | 'card-link' | 'status' | 'cancel' | 'retry' | 'simulate' | 'void' | 'receipt';
|
|
115
|
+
export type PosTenderAction = 'cash' | 'gift-card' | 'credit' | 'folio' | 'card-present' | 'card-present-sdk' | 'card-keyed' | 'card-link' | 'status' | 'cancel' | 'retry' | 'simulate' | 'void' | 'receipt';
|
|
100
116
|
export declare function posTender(user: User, hostId: string, orderId: string, action: PosTenderAction, body?: Record<string, unknown>, attemptKey?: string): Promise<PosTenderResult>;
|
|
101
117
|
export declare function posGiftCardBalance(user: User, hostId: string, code: string): Promise<{
|
|
102
118
|
availableCents: number;
|
|
@@ -104,7 +120,14 @@ export declare function posGiftCardBalance(user: User, hostId: string, code: str
|
|
|
104
120
|
voided: boolean;
|
|
105
121
|
last4: string;
|
|
106
122
|
}>;
|
|
123
|
+
/** Finds store-credit accounts by a customer's email or code (AGL-3640). */
|
|
124
|
+
export declare function posCreditLookup(user: User, hostId: string, providerId: string, query: string): Promise<{
|
|
125
|
+
providerId: string;
|
|
126
|
+
accounts: PosCreditAccount[];
|
|
127
|
+
}>;
|
|
107
128
|
export declare function posDisplayCall<T = any>(user: User, body: Record<string, unknown>, method?: 'GET' | 'POST'): Promise<T>;
|
|
129
|
+
/** The register's side of the self-service kiosk (AGL-3623): its queue of "pay at counter" orders. */
|
|
130
|
+
export declare function posKioskCall<T = any>(user: User, body: Record<string, unknown>, method?: 'GET' | 'POST'): Promise<T>;
|
|
108
131
|
export declare function posReadersCall<T = any>(user: User, body: Record<string, unknown>): Promise<T>;
|
|
109
132
|
/** Cents as the register shows them. */
|
|
110
133
|
export declare const usd: (cents: number) => string;
|
|
@@ -80,11 +80,24 @@ export function posGiftCardBalance(user, hostId, code) {
|
|
|
80
80
|
code
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
+
/** Finds store-credit accounts by a customer's email or code (AGL-3640). */ export function posCreditLookup(user, hostId, providerId, query) {
|
|
84
|
+
return call(user, '/api/commerce/pos-payment', {
|
|
85
|
+
action: 'credit-lookup',
|
|
86
|
+
hostId,
|
|
87
|
+
providerId,
|
|
88
|
+
query
|
|
89
|
+
});
|
|
90
|
+
}
|
|
83
91
|
export function posDisplayCall(user, body, method = 'POST') {
|
|
84
92
|
return call(user, '/api/commerce/pos-display', body, {
|
|
85
93
|
method
|
|
86
94
|
});
|
|
87
95
|
}
|
|
96
|
+
/** The register's side of the self-service kiosk (AGL-3623): its queue of "pay at counter" orders. */ export function posKioskCall(user, body, method = 'POST') {
|
|
97
|
+
return call(user, '/api/commerce/pos-kiosk', body, {
|
|
98
|
+
method
|
|
99
|
+
});
|
|
100
|
+
}
|
|
88
101
|
export function posReadersCall(user, body) {
|
|
89
102
|
return call(user, '/api/commerce/pos-readers', body);
|
|
90
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type * as CommerceModel from '../../../model'\nimport type { PosRegisterSettings } from '../../../plugin-config'\n\n/**\n * The register's client half (AGL-3607): one place that knows the routes, so\n * the components ask \"take cash\" rather than building requests. Every call\n * that starts a payment carries the attempt key it is given; the server\n * derives the payment from it, so a retried press finds the same payment.\n */\n\n/** The open sale as the server reports it after every change. */\nexport interface PosSaleSummary {\n orderId: string\n status: string\n totalCents: number\n paidCents: number\n dueCents: number\n tenderableCents: number\n tipCents: number\n payments: Array<{\n id: string\n method: CommerceModel.OrderPaymentMethod\n amountCents: number\n tipCents?: number\n status: CommerceModel.OrderPaymentStatus\n cardBrand?: string\n last4?: string\n changeCents?: number\n checkoutUrl?: string\n readerId?: string\n failureMessage?: string\n livemode?: boolean\n }>\n}\n\nexport interface PosTenderResult {\n sale: PosSaleSummary\n paymentId?: string\n completed: boolean\n clientSecret?: string\n paymentIntentId?: string\n}\n\nexport interface PosRegisterContext {\n settings: PosRegisterSettings\n terminal: { available: boolean; testMode: boolean }\n readers: Array<{\n id: string\n label: string\n registerId: string | null\n status: string\n livemode: boolean\n }>\n publishableKey: string\n smsReceipts: boolean\n}\n\ntype User = Parameters<typeof authorizedFetch>[0]\n\nexport class PosRequestError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message)\n }\n}\n\nasync function call<T>(\n user: User,\n route: string,\n body: Record<string, unknown>,\n options: { attemptKey?: string; method?: 'GET' | 'POST' } = {},\n): Promise<T> {\n const method = options.method ?? 'POST'\n const url =\n method === 'GET'\n ? `${route}?${new URLSearchParams(\n Object.entries(body).map(([key, value]) => [key, String(value)]),\n ).toString()}`\n : route\n const response = await authorizedFetch(user, url, {\n method,\n headers: {\n ...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),\n ...(options.attemptKey ? { 'Idempotency-Key': options.attemptKey } : {}),\n },\n ...(method === 'POST' ? { body: JSON.stringify(body) } : {}),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n throw new PosRequestError(String(payload?.error ?? 'Something went wrong'), response.status)\n }\n return payload as T\n}\n\n/** A fresh attempt key, for one press of one tender. */\nexport function newAttemptKey(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n )\n}\n\nexport function posRegisterContext(user: User, hostId: string) {\n return call<PosRegisterContext>(user, '/api/commerce/pos-payment', { action: 'context', hostId }, { method: 'GET' })\n}\n\n/** Prices the basket on the server and opens a sale with an empty ledger. */\nexport function openPosSale(\n user: User,\n attemptKey: string,\n body: {\n hostId: string\n registerId: string\n lines: unknown[]\n discountPct: number\n customerEmail?: string\n /** The person the register's customer lookup attached (AGL-3609). */\n customer?: unknown\n /** A PIN-switched cashier's signed assertion (AGL-3609). */\n cashierAssertion?: string\n locationId?: string\n },\n) {\n return call<{ orderId: string; totals: CommerceModel.OrderTotals; dueCents: number; stockWarnings?: unknown[] }>(\n user,\n '/api/commerce/pos-order',\n { ...body, payment: 'open' },\n { attemptKey },\n )\n}\n\nexport type PosTenderAction =\n | 'cash'\n | 'gift-card'\n | 'folio'\n | 'card-present'\n | 'card-present-sdk'\n | 'card-keyed'\n | 'card-link'\n | 'status'\n | 'cancel'\n | 'retry'\n | 'simulate'\n | 'void'\n | 'receipt'\n\nexport function posTender(\n user: User,\n hostId: string,\n orderId: string,\n action: PosTenderAction,\n body: Record<string, unknown> = {},\n attemptKey?: string,\n) {\n return call<PosTenderResult>(\n user,\n '/api/commerce/pos-payment',\n { ...body, action, hostId, orderId },\n attemptKey ? { attemptKey } : {},\n )\n}\n\nexport function posGiftCardBalance(user: User, hostId: string, code: string) {\n return call<{ availableCents: number; frozen: boolean; voided: boolean; last4: string }>(\n user,\n '/api/commerce/pos-payment',\n { action: 'gift-card-balance', hostId, code },\n )\n}\n\nexport function posDisplayCall<T = any>(\n user: User,\n body: Record<string, unknown>,\n method: 'GET' | 'POST' = 'POST',\n) {\n return call<T>(user, '/api/commerce/pos-display', body, { method })\n}\n\nexport function posReadersCall<T = any>(user: User, body: Record<string, unknown>) {\n return call<T>(user, '/api/commerce/pos-readers', body)\n}\n\n/** Cents as the register shows them. */\nexport const usd = (cents: number) => `$${(Math.max(0, cents) / 100).toFixed(2)}`\n\n/** A dollar string the cashier typed, as whole cents (NaN-safe, never negative). */\nexport function centsFromInput(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/*==========================================\n * THE NATIVE BRIDGE (AGL-3618).\n *\n * The POS companion app loads this register in a WebView and injects\n * `window.AglynPosBridge`. When it is there, the register offers a third\n * card option — Tap to Pay or a Bluetooth reader — and hands the app a\n * PaymentIntent the SERVER created (`card-present-sdk`); the app collects\n * the card with the Stripe Terminal SDK and the register settles it exactly\n * like a smart-reader payment, by asking the server.\n *=========================================*/\n\nexport interface AglynPosBridge {\n collectCardPayment(input: {\n paymentIntentId: string\n clientSecret: string\n amountCents: number\n }): Promise<{ status: 'collected' | 'canceled' | 'failed'; message?: string }>\n}\n\nexport function posNativeBridge(): AglynPosBridge | null {\n const bridge = (globalThis as { AglynPosBridge?: AglynPosBridge }).AglynPosBridge\n return bridge && typeof bridge.collectCardPayment === 'function' ? bridge : null\n}\n"],"names":["authorizedFetch","PosRequestError","Error","message","status","call","user","route","body","options","method","url","URLSearchParams","Object","entries","map","key","value","String","toString","response","headers","attemptKey","JSON","stringify","payload","json","catch","ok","error","newAttemptKey","globalThis","crypto","randomUUID","Date","now","Math","random","slice","posRegisterContext","hostId","action","openPosSale","payment","posTender","orderId","posGiftCardBalance","code","posDisplayCall","posReadersCall","usd","cents","max","toFixed","centsFromInput","number","round","Number","replace","isFinite","posNativeBridge","bridge","AglynPosBridge","collectCardPayment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type * as CommerceModel from '../../../model'\nimport type { PosRegisterSettings } from '../../../plugin-config'\n\n/**\n * The register's client half (AGL-3607): one place that knows the routes, so\n * the components ask \"take cash\" rather than building requests. Every call\n * that starts a payment carries the attempt key it is given; the server\n * derives the payment from it, so a retried press finds the same payment.\n */\n\n/** The open sale as the server reports it after every change. */\nexport interface PosSaleSummary {\n orderId: string\n status: string\n totalCents: number\n paidCents: number\n dueCents: number\n tenderableCents: number\n tipCents: number\n payments: Array<{\n id: string\n method: CommerceModel.OrderPaymentMethod\n amountCents: number\n tipCents?: number\n status: CommerceModel.OrderPaymentStatus\n cardBrand?: string\n last4?: string\n changeCents?: number\n checkoutUrl?: string\n readerId?: string\n /** What a store-credit payment's provider calls it (AGL-3640). */\n creditLabel?: string\n failureMessage?: string\n livemode?: boolean\n }>\n}\n\nexport interface PosTenderResult {\n sale: PosSaleSummary\n paymentId?: string\n completed: boolean\n clientSecret?: string\n paymentIntentId?: string\n}\n\nexport interface PosRegisterContext {\n settings: PosRegisterSettings\n terminal: { available: boolean; testMode: boolean }\n readers: Array<{\n id: string\n label: string\n registerId: string | null\n status: string\n livemode: boolean\n }>\n publishableKey: string\n smsReceipts: boolean\n /** Store credit other plugins keep, taken as a tender (AGL-3640). */\n credits?: Array<{ providerId: string; label: string; lookup: boolean }>\n}\n\n/** A store-credit account a staff lookup found (AGL-3640). */\nexport interface PosCreditAccount {\n reference: string\n label: string\n last4: string\n availableCents: number\n detail?: string\n}\n\ntype User = Parameters<typeof authorizedFetch>[0]\n\nexport class PosRequestError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message)\n }\n}\n\nasync function call<T>(\n user: User,\n route: string,\n body: Record<string, unknown>,\n options: { attemptKey?: string; method?: 'GET' | 'POST' } = {},\n): Promise<T> {\n const method = options.method ?? 'POST'\n const url =\n method === 'GET'\n ? `${route}?${new URLSearchParams(\n Object.entries(body).map(([key, value]) => [key, String(value)]),\n ).toString()}`\n : route\n const response = await authorizedFetch(user, url, {\n method,\n headers: {\n ...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),\n ...(options.attemptKey ? { 'Idempotency-Key': options.attemptKey } : {}),\n },\n ...(method === 'POST' ? { body: JSON.stringify(body) } : {}),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n throw new PosRequestError(String(payload?.error ?? 'Something went wrong'), response.status)\n }\n return payload as T\n}\n\n/** A fresh attempt key, for one press of one tender. */\nexport function newAttemptKey(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n )\n}\n\nexport function posRegisterContext(user: User, hostId: string) {\n return call<PosRegisterContext>(user, '/api/commerce/pos-payment', { action: 'context', hostId }, { method: 'GET' })\n}\n\n/** Prices the basket on the server and opens a sale with an empty ledger. */\nexport function openPosSale(\n user: User,\n attemptKey: string,\n body: {\n hostId: string\n registerId: string\n lines: unknown[]\n discountPct: number\n customerEmail?: string\n /** The person the register's customer lookup attached (AGL-3609). */\n customer?: unknown\n /** A PIN-switched cashier's signed assertion (AGL-3609). */\n cashierAssertion?: string\n locationId?: string\n },\n) {\n return call<{ orderId: string; totals: CommerceModel.OrderTotals; dueCents: number; stockWarnings?: unknown[] }>(\n user,\n '/api/commerce/pos-order',\n { ...body, payment: 'open' },\n { attemptKey },\n )\n}\n\nexport type PosTenderAction =\n | 'cash'\n | 'gift-card'\n | 'credit'\n | 'folio'\n | 'card-present'\n | 'card-present-sdk'\n | 'card-keyed'\n | 'card-link'\n | 'status'\n | 'cancel'\n | 'retry'\n | 'simulate'\n | 'void'\n | 'receipt'\n\nexport function posTender(\n user: User,\n hostId: string,\n orderId: string,\n action: PosTenderAction,\n body: Record<string, unknown> = {},\n attemptKey?: string,\n) {\n return call<PosTenderResult>(\n user,\n '/api/commerce/pos-payment',\n { ...body, action, hostId, orderId },\n attemptKey ? { attemptKey } : {},\n )\n}\n\nexport function posGiftCardBalance(user: User, hostId: string, code: string) {\n return call<{ availableCents: number; frozen: boolean; voided: boolean; last4: string }>(\n user,\n '/api/commerce/pos-payment',\n { action: 'gift-card-balance', hostId, code },\n )\n}\n\n/** Finds store-credit accounts by a customer's email or code (AGL-3640). */\nexport function posCreditLookup(user: User, hostId: string, providerId: string, query: string) {\n return call<{ providerId: string; accounts: PosCreditAccount[] }>(user, '/api/commerce/pos-payment', {\n action: 'credit-lookup',\n hostId,\n providerId,\n query,\n })\n}\n\nexport function posDisplayCall<T = any>(\n user: User,\n body: Record<string, unknown>,\n method: 'GET' | 'POST' = 'POST',\n) {\n return call<T>(user, '/api/commerce/pos-display', body, { method })\n}\n\n/** The register's side of the self-service kiosk (AGL-3623): its queue of \"pay at counter\" orders. */\nexport function posKioskCall<T = any>(\n user: User,\n body: Record<string, unknown>,\n method: 'GET' | 'POST' = 'POST',\n) {\n return call<T>(user, '/api/commerce/pos-kiosk', body, { method })\n}\n\nexport function posReadersCall<T = any>(user: User, body: Record<string, unknown>) {\n return call<T>(user, '/api/commerce/pos-readers', body)\n}\n\n/** Cents as the register shows them. */\nexport const usd = (cents: number) => `$${(Math.max(0, cents) / 100).toFixed(2)}`\n\n/** A dollar string the cashier typed, as whole cents (NaN-safe, never negative). */\nexport function centsFromInput(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/*==========================================\n * THE NATIVE BRIDGE (AGL-3618).\n *\n * The POS companion app loads this register in a WebView and injects\n * `window.AglynPosBridge`. When it is there, the register offers a third\n * card option — Tap to Pay or a Bluetooth reader — and hands the app a\n * PaymentIntent the SERVER created (`card-present-sdk`); the app collects\n * the card with the Stripe Terminal SDK and the register settles it exactly\n * like a smart-reader payment, by asking the server.\n *=========================================*/\n\nexport interface AglynPosBridge {\n collectCardPayment(input: {\n paymentIntentId: string\n clientSecret: string\n amountCents: number\n }): Promise<{ status: 'collected' | 'canceled' | 'failed'; message?: string }>\n}\n\nexport function posNativeBridge(): AglynPosBridge | null {\n const bridge = (globalThis as { AglynPosBridge?: AglynPosBridge }).AglynPosBridge\n return bridge && typeof bridge.collectCardPayment === 'function' ? bridge : null\n}\n"],"names":["authorizedFetch","PosRequestError","Error","message","status","call","user","route","body","options","method","url","URLSearchParams","Object","entries","map","key","value","String","toString","response","headers","attemptKey","JSON","stringify","payload","json","catch","ok","error","newAttemptKey","globalThis","crypto","randomUUID","Date","now","Math","random","slice","posRegisterContext","hostId","action","openPosSale","payment","posTender","orderId","posGiftCardBalance","code","posCreditLookup","providerId","query","posDisplayCall","posKioskCall","posReadersCall","usd","cents","max","toFixed","centsFromInput","number","round","Number","replace","isFinite","posNativeBridge","bridge","AglynPosBridge","collectCardPayment"],"mappings":"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"}
|
|
@@ -20,6 +20,18 @@ export interface PosItemDialogProps {
|
|
|
20
20
|
onConfirm: (choice: PosItemChoice) => void;
|
|
21
21
|
/** Editing only: takes the line out of the basket. */
|
|
22
22
|
onRemove?: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* How a price reads; the register's dollars by default. The self-service
|
|
25
|
+
* kiosk (AGL-3623) passes the store's currency.
|
|
26
|
+
*/
|
|
27
|
+
formatMoney?: (cents: number) => string;
|
|
28
|
+
/**
|
|
29
|
+
* A sold-out variant cannot be added, rather than warned about: a kiosk's
|
|
30
|
+
* customer is not holding the goods the way a cashier is (AGL-3623).
|
|
31
|
+
*/
|
|
32
|
+
blockSoldOut?: boolean;
|
|
33
|
+
/** The most of one line that can be added; 99 at the register. */
|
|
34
|
+
maxQuantity?: number;
|
|
23
35
|
}
|
|
24
36
|
/** Whether the register must ask before adding: a choice of variant or any modifier. */
|
|
25
37
|
export declare function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean;
|
|
@@ -52,8 +52,10 @@ function soldOut(variant) {
|
|
|
52
52
|
* The price shown is the register's estimate. The server prices the line
|
|
53
53
|
* again from the product when the sale is charged.
|
|
54
54
|
*/ export function PosItemDialog(props) {
|
|
55
|
-
var _ref;
|
|
55
|
+
var _props_formatMoney, _props_maxQuantity, _ref;
|
|
56
56
|
const { product, initial } = props;
|
|
57
|
+
const money = (_props_formatMoney = props.formatMoney) != null ? _props_formatMoney : usd;
|
|
58
|
+
const maxQuantity = (_props_maxQuantity = props.maxQuantity) != null ? _props_maxQuantity : 99;
|
|
57
59
|
const theme = useTheme();
|
|
58
60
|
const phone = useMediaQuery(theme.breakpoints.down('sm'));
|
|
59
61
|
const groups = useMemo(()=>product ? CommerceModel.productModifierGroups(product) : [], [
|
|
@@ -81,7 +83,7 @@ function soldOut(variant) {
|
|
|
81
83
|
const chosen = CommerceModel.resolveLineModifiers(product, modifiers);
|
|
82
84
|
const unitCents = variant ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0) : 0;
|
|
83
85
|
const missing = groups.find((group)=>modifiers.filter((pick)=>pick.groupId === group.id).length < group.min);
|
|
84
|
-
const canAdd = Boolean(variant) && chosen.ok && quantity >= 1;
|
|
86
|
+
const canAdd = Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant));
|
|
85
87
|
const toggle = (group, optionId)=>{
|
|
86
88
|
setModifiers((current)=>{
|
|
87
89
|
const inGroup = current.filter((pick)=>pick.groupId === group.id);
|
|
@@ -176,7 +178,7 @@ function soldOut(variant) {
|
|
|
176
178
|
minHeight: POS_TOUCH_PX,
|
|
177
179
|
minWidth: 88
|
|
178
180
|
},
|
|
179
|
-
children: `${variantLabel(item)} · ${
|
|
181
|
+
children: `${variantLabel(item)} · ${money(Math.round(Number(item.priceUsd) * 100))}`
|
|
180
182
|
}, item.id))
|
|
181
183
|
})
|
|
182
184
|
}) : null,
|
|
@@ -188,7 +190,7 @@ function soldOut(variant) {
|
|
|
188
190
|
variant && soldOut(variant) ? /*#__PURE__*/ _jsx(Typography, {
|
|
189
191
|
variant: "body2",
|
|
190
192
|
color: "warning.main",
|
|
191
|
-
children: 'Out of stock: selling it takes the count below zero.'
|
|
193
|
+
children: props.blockSoldOut ? 'Sold out' : 'Out of stock: selling it takes the count below zero.'
|
|
192
194
|
}) : null,
|
|
193
195
|
groups.map((group)=>{
|
|
194
196
|
const count = modifiers.filter((pick)=>pick.groupId === group.id).length;
|
|
@@ -226,7 +228,7 @@ function soldOut(variant) {
|
|
|
226
228
|
children: group.options.map((option)=>{
|
|
227
229
|
const selected = modifiers.some((pick)=>pick.groupId === group.id && pick.optionId === option.id);
|
|
228
230
|
return /*#__PURE__*/ _jsx(Chip, {
|
|
229
|
-
label: option.priceCents ? `${option.name} +${
|
|
231
|
+
label: option.priceCents ? `${option.name} +${money(option.priceCents)}` : option.name,
|
|
230
232
|
color: selected ? 'primary' : 'default',
|
|
231
233
|
variant: selected ? 'filled' : 'outlined',
|
|
232
234
|
"aria-pressed": selected,
|
|
@@ -272,7 +274,7 @@ function soldOut(variant) {
|
|
|
272
274
|
value: quantity,
|
|
273
275
|
onChange: (event)=>{
|
|
274
276
|
const next = Math.round(Number(event.target.value.replace(/\D/g, '')));
|
|
275
|
-
setQuantity(Math.max(1, Math.min(
|
|
277
|
+
setQuantity(Math.max(1, Math.min(maxQuantity, next || 1)));
|
|
276
278
|
},
|
|
277
279
|
sx: {
|
|
278
280
|
width: 72
|
|
@@ -289,7 +291,7 @@ function soldOut(variant) {
|
|
|
289
291
|
}),
|
|
290
292
|
/*#__PURE__*/ _jsx(IconButton, {
|
|
291
293
|
"aria-label": "One more",
|
|
292
|
-
onClick: ()=>setQuantity((value)=>Math.min(
|
|
294
|
+
onClick: ()=>setQuantity((value)=>Math.min(maxQuantity, value + 1)),
|
|
293
295
|
sx: {
|
|
294
296
|
width: POS_TOUCH_PX,
|
|
295
297
|
height: POS_TOUCH_PX,
|
|
@@ -340,7 +342,7 @@ function soldOut(variant) {
|
|
|
340
342
|
minHeight: POS_TOUCH_PX,
|
|
341
343
|
minWidth: 160
|
|
342
344
|
},
|
|
343
|
-
children: missing ? `Choose ${missing.name.toLowerCase()}` : `${props.onRemove ? 'Update' : 'Add'} · ${
|
|
345
|
+
children: missing ? `Choose ${missing.name.toLowerCase()}` : `${props.onRemove ? 'Update' : 'Add'} · ${money(unitCents * quantity)}`
|
|
344
346
|
})
|
|
345
347
|
]
|
|
346
348
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-item-dialog.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport * as CommerceModel from '../../../model'\nimport { usd } from './pos-api'\nimport { POS_TOUCH_PX } from './pos-product-grid.component'\n\n/** What the dialog hands back: one basket line's worth of choices. */\nexport interface PosItemChoice {\n variant: CommerceModel.ProductVariant\n modifiers: CommerceModel.ModifierSelection[]\n quantity: number\n}\n\nexport interface PosItemDialogProps {\n /** The product being added or edited; `null` closes the dialog. */\n product: (CommerceModel.HostProduct & { $id: string }) | null\n /** An existing line's choices, when the cashier tapped a basket line. */\n initial?: { variantId?: string; modifiers?: CommerceModel.ModifierSelection[]; quantity: number }\n onClose: () => void\n onConfirm: (choice: PosItemChoice) => void\n /** Editing only: takes the line out of the basket. */\n onRemove?: () => void\n}\n\n/** Whether the register must ask before adding: a choice of variant or any modifier. */\nexport function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean {\n return (\n (product.variants?.length ?? 0) > 1 ||\n CommerceModel.productModifierGroups(product).length > 0\n )\n}\n\n/** The variant whose options match every picked value, or undefined. */\nfunction variantFor(\n product: CommerceModel.HostProduct,\n picked: Record<string, string>,\n): CommerceModel.ProductVariant | undefined {\n return product.variants.find((variant) =>\n (product.options ?? []).every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n}\n\nfunction variantLabel(variant: CommerceModel.ProductVariant): string {\n return Object.values(variant.options ?? {}).join(' / ') || 'Default'\n}\n\nfunction soldOut(variant: CommerceModel.ProductVariant | undefined): boolean {\n return variant?.inventory != null && variant.inventory <= 0\n}\n\n/**\n * The register's item sheet (AGL-3607): the size, color or other variant,\n * then each modifier group — required ones marked, \"pick one\" groups as a\n * single toggle row and \"pick up to N\" groups as chips — and the quantity,\n * with the line's price on the Add button as it changes. Full screen on a\n * phone, a dialog on a tablet; every target is at least 44px.\n *\n * The price shown is the register's estimate. The server prices the line\n * again from the product when the sale is charged.\n */\nexport function PosItemDialog(props: PosItemDialogProps) {\n const { product, initial } = props\n const theme = useTheme()\n const phone = useMediaQuery(theme.breakpoints.down('sm'))\n const groups = useMemo(\n () => (product ? CommerceModel.productModifierGroups(product) : []),\n [product],\n )\n const options = product?.options ?? []\n const [picked, setPicked] = useState<Record<string, string>>({})\n const [variantId, setVariantId] = useState('')\n const [modifiers, setModifiers] = useState<CommerceModel.ModifierSelection[]>([])\n const [quantity, setQuantity] = useState(1)\n\n useEffect(() => {\n if (!product) return\n const start =\n product.variants.find((variant) => variant.id === initial?.variantId) ??\n product.variants.find((variant) => !soldOut(variant)) ??\n product.variants[0]\n setVariantId(start?.id ?? '')\n setPicked({ ...(start?.options ?? {}) })\n setModifiers(initial?.modifiers ?? [])\n setQuantity(initial?.quantity ?? 1)\n }, [product, initial])\n\n if (!product) return null\n\n const variant =\n options.length > 0\n ? variantFor(product, picked)\n : product.variants.find((item) => item.id === variantId)\n const chosen = CommerceModel.resolveLineModifiers(product, modifiers)\n const unitCents = variant\n ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0)\n : 0\n const missing = groups.find(\n (group) => modifiers.filter((pick) => pick.groupId === group.id).length < group.min,\n )\n const canAdd = Boolean(variant) && chosen.ok && quantity >= 1\n\n const toggle = (group: CommerceModel.ProductModifierGroup, optionId: string) => {\n setModifiers((current) => {\n const inGroup = current.filter((pick) => pick.groupId === group.id)\n const has = inGroup.some((pick) => pick.optionId === optionId)\n if (has) return current.filter((pick) => !(pick.groupId === group.id && pick.optionId === optionId))\n // A pick-one group swaps its choice; a pick-many group stops at its max.\n if (group.max === 1) {\n return [...current.filter((pick) => pick.groupId !== group.id), { groupId: group.id, optionId }]\n }\n if (inGroup.length >= group.max) return current\n return [...current, { groupId: group.id, optionId }]\n })\n }\n\n return (\n <Dialog open onClose={props.onClose} fullScreen={phone} fullWidth maxWidth=\"sm\">\n <DialogTitle>{product.name}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={3}>\n {options.length > 0\n ? options.map((option) => (\n <Box key={option.name} role=\"group\" aria-label={option.name}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ mb: 1 }}>\n {option.name}\n </Typography>\n <ToggleButtonGroup\n exclusive\n value={picked[option.name] ?? null}\n onChange={(_event, value: string | null) => {\n if (value) setPicked((current) => ({ ...current, [option.name]: value }))\n }}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {option.values.map((value) => (\n <ToggleButton key={value} value={value} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {value}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ))\n : product.variants.length > 1 ? (\n <Box role=\"group\" aria-label=\"Variant\">\n <ToggleButtonGroup\n exclusive\n value={variantId || null}\n onChange={(_event, value: string | null) => value && setVariantId(value)}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {product.variants.map((item) => (\n <ToggleButton key={item.id} value={item.id} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {`${variantLabel(item)} · ${usd(Math.round(Number(item.priceUsd) * 100))}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ) : null}\n {options.length > 0 && !variant ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'That combination is not sold. Pick another.'}\n </Typography>\n ) : null}\n {variant && soldOut(variant) ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'Out of stock: selling it takes the count below zero.'}\n </Typography>\n ) : null}\n {groups.map((group) => {\n const count = modifiers.filter((pick) => pick.groupId === group.id).length\n const rule = CommerceModel.modifierGroupRequired(group)\n ? group.max === 1\n ? 'Required · pick 1'\n : `Required · pick ${group.min} to ${group.max}`\n : group.max === 1\n ? 'Optional · pick 1'\n : `Optional · up to ${group.max}`\n return (\n <Box key={group.id} role=\"group\" aria-label={group.name}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {group.name}\n </Typography>\n <Typography\n variant=\"caption\"\n color={count < group.min ? 'warning.main' : 'text.secondary'}\n >\n {rule}\n </Typography>\n </Stack>\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {group.options.map((option) => {\n const selected = modifiers.some(\n (pick) => pick.groupId === group.id && pick.optionId === option.id,\n )\n return (\n <Chip\n key={option.id}\n label={option.priceCents ? `${option.name} +${usd(option.priceCents)}` : option.name}\n color={selected ? 'primary' : 'default'}\n variant={selected ? 'filled' : 'outlined'}\n aria-pressed={selected}\n onClick={() => toggle(group, option.id)}\n disabled={!selected && group.max > 1 && count >= group.max}\n sx={{ minHeight: POS_TOUCH_PX, px: 1 }}\n />\n )\n })}\n </Box>\n </Box>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ flex: 1 }}>\n {'Quantity'}\n </Typography>\n <IconButton\n aria-label=\"One fewer\"\n onClick={() => setQuantity((value) => Math.max(1, value - 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'−'}\n </IconButton>\n <TextField\n value={quantity}\n onChange={(event) => {\n const next = Math.round(Number(event.target.value.replace(/\\D/g, '')))\n setQuantity(Math.max(1, Math.min(99, next || 1)))\n }}\n sx={{ width: 72 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Quantity', style: { textAlign: 'center' } },\n }}\n />\n <IconButton\n aria-label=\"One more\"\n onClick={() => setQuantity((value) => Math.min(99, value + 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'+'}\n </IconButton>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ p: 2, gap: 1 }}>\n {props.onRemove ? (\n <Button color=\"error\" onClick={props.onRemove} sx={{ minHeight: POS_TOUCH_PX, mr: 'auto' }}>\n {'Remove'}\n </Button>\n ) : null}\n <Button onClick={props.onClose} sx={{ minHeight: POS_TOUCH_PX }}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!canAdd}\n onClick={() => {\n if (!variant || !chosen.ok) return\n props.onConfirm({ variant, modifiers, quantity })\n }}\n sx={{ minHeight: POS_TOUCH_PX, minWidth: 160 }}\n >\n {missing\n ? `Choose ${missing.name.toLowerCase()}`\n : `${props.onRemove ? 'Update' : 'Add'} · ${usd(unitCents * quantity)}`}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPosItemDialog.displayName = 'PosItemDialog'\n"],"names":["Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","CommerceModel","usd","POS_TOUCH_PX","posItemNeedsChoice","product","variants","length","productModifierGroups","variantFor","picked","find","variant","options","every","option","name","variantLabel","Object","values","join","soldOut","inventory","PosItemDialog","props","initial","theme","phone","breakpoints","down","groups","setPicked","variantId","setVariantId","modifiers","setModifiers","quantity","setQuantity","start","id","item","chosen","resolveLineModifiers","unitCents","Math","round","Number","priceUsd","ok","extraCents","missing","group","filter","pick","groupId","min","canAdd","Boolean","toggle","optionId","current","inGroup","has","some","max","open","onClose","fullScreen","fullWidth","maxWidth","dividers","spacing","map","role","aria-label","component","sx","mb","exclusive","value","onChange","_event","flexWrap","gap","minHeight","minWidth","color","count","rule","modifierGroupRequired","direction","alignItems","display","selected","label","priceCents","aria-pressed","onClick","disabled","px","flex","width","height","border","borderColor","event","next","target","replace","slotProps","htmlInput","inputMode","style","textAlign","p","onRemove","mr","onConfirm","toLowerCase","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-item-dialog.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport * as CommerceModel from '../../../model'\nimport { usd } from './pos-api'\nimport { POS_TOUCH_PX } from './pos-product-grid.component'\n\n/** What the dialog hands back: one basket line's worth of choices. */\nexport interface PosItemChoice {\n variant: CommerceModel.ProductVariant\n modifiers: CommerceModel.ModifierSelection[]\n quantity: number\n}\n\nexport interface PosItemDialogProps {\n /** The product being added or edited; `null` closes the dialog. */\n product: (CommerceModel.HostProduct & { $id: string }) | null\n /** An existing line's choices, when the cashier tapped a basket line. */\n initial?: { variantId?: string; modifiers?: CommerceModel.ModifierSelection[]; quantity: number }\n onClose: () => void\n onConfirm: (choice: PosItemChoice) => void\n /** Editing only: takes the line out of the basket. */\n onRemove?: () => void\n /**\n * How a price reads; the register's dollars by default. The self-service\n * kiosk (AGL-3623) passes the store's currency.\n */\n formatMoney?: (cents: number) => string\n /**\n * A sold-out variant cannot be added, rather than warned about: a kiosk's\n * customer is not holding the goods the way a cashier is (AGL-3623).\n */\n blockSoldOut?: boolean\n /** The most of one line that can be added; 99 at the register. */\n maxQuantity?: number\n}\n\n/** Whether the register must ask before adding: a choice of variant or any modifier. */\nexport function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean {\n return (\n (product.variants?.length ?? 0) > 1 ||\n CommerceModel.productModifierGroups(product).length > 0\n )\n}\n\n/** The variant whose options match every picked value, or undefined. */\nfunction variantFor(\n product: CommerceModel.HostProduct,\n picked: Record<string, string>,\n): CommerceModel.ProductVariant | undefined {\n return product.variants.find((variant) =>\n (product.options ?? []).every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n}\n\nfunction variantLabel(variant: CommerceModel.ProductVariant): string {\n return Object.values(variant.options ?? {}).join(' / ') || 'Default'\n}\n\nfunction soldOut(variant: CommerceModel.ProductVariant | undefined): boolean {\n return variant?.inventory != null && variant.inventory <= 0\n}\n\n/**\n * The register's item sheet (AGL-3607): the size, color or other variant,\n * then each modifier group — required ones marked, \"pick one\" groups as a\n * single toggle row and \"pick up to N\" groups as chips — and the quantity,\n * with the line's price on the Add button as it changes. Full screen on a\n * phone, a dialog on a tablet; every target is at least 44px.\n *\n * The price shown is the register's estimate. The server prices the line\n * again from the product when the sale is charged.\n */\nexport function PosItemDialog(props: PosItemDialogProps) {\n const { product, initial } = props\n const money = props.formatMoney ?? usd\n const maxQuantity = props.maxQuantity ?? 99\n const theme = useTheme()\n const phone = useMediaQuery(theme.breakpoints.down('sm'))\n const groups = useMemo(\n () => (product ? CommerceModel.productModifierGroups(product) : []),\n [product],\n )\n const options = product?.options ?? []\n const [picked, setPicked] = useState<Record<string, string>>({})\n const [variantId, setVariantId] = useState('')\n const [modifiers, setModifiers] = useState<CommerceModel.ModifierSelection[]>([])\n const [quantity, setQuantity] = useState(1)\n\n useEffect(() => {\n if (!product) return\n const start =\n product.variants.find((variant) => variant.id === initial?.variantId) ??\n product.variants.find((variant) => !soldOut(variant)) ??\n product.variants[0]\n setVariantId(start?.id ?? '')\n setPicked({ ...(start?.options ?? {}) })\n setModifiers(initial?.modifiers ?? [])\n setQuantity(initial?.quantity ?? 1)\n }, [product, initial])\n\n if (!product) return null\n\n const variant =\n options.length > 0\n ? variantFor(product, picked)\n : product.variants.find((item) => item.id === variantId)\n const chosen = CommerceModel.resolveLineModifiers(product, modifiers)\n const unitCents = variant\n ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0)\n : 0\n const missing = groups.find(\n (group) => modifiers.filter((pick) => pick.groupId === group.id).length < group.min,\n )\n const canAdd =\n Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant))\n\n const toggle = (group: CommerceModel.ProductModifierGroup, optionId: string) => {\n setModifiers((current) => {\n const inGroup = current.filter((pick) => pick.groupId === group.id)\n const has = inGroup.some((pick) => pick.optionId === optionId)\n if (has) return current.filter((pick) => !(pick.groupId === group.id && pick.optionId === optionId))\n // A pick-one group swaps its choice; a pick-many group stops at its max.\n if (group.max === 1) {\n return [...current.filter((pick) => pick.groupId !== group.id), { groupId: group.id, optionId }]\n }\n if (inGroup.length >= group.max) return current\n return [...current, { groupId: group.id, optionId }]\n })\n }\n\n return (\n <Dialog open onClose={props.onClose} fullScreen={phone} fullWidth maxWidth=\"sm\">\n <DialogTitle>{product.name}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={3}>\n {options.length > 0\n ? options.map((option) => (\n <Box key={option.name} role=\"group\" aria-label={option.name}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ mb: 1 }}>\n {option.name}\n </Typography>\n <ToggleButtonGroup\n exclusive\n value={picked[option.name] ?? null}\n onChange={(_event, value: string | null) => {\n if (value) setPicked((current) => ({ ...current, [option.name]: value }))\n }}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {option.values.map((value) => (\n <ToggleButton key={value} value={value} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {value}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ))\n : product.variants.length > 1 ? (\n <Box role=\"group\" aria-label=\"Variant\">\n <ToggleButtonGroup\n exclusive\n value={variantId || null}\n onChange={(_event, value: string | null) => value && setVariantId(value)}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {product.variants.map((item) => (\n <ToggleButton key={item.id} value={item.id} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {`${variantLabel(item)} · ${money(Math.round(Number(item.priceUsd) * 100))}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ) : null}\n {options.length > 0 && !variant ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'That combination is not sold. Pick another.'}\n </Typography>\n ) : null}\n {variant && soldOut(variant) ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {props.blockSoldOut ? 'Sold out' : 'Out of stock: selling it takes the count below zero.'}\n </Typography>\n ) : null}\n {groups.map((group) => {\n const count = modifiers.filter((pick) => pick.groupId === group.id).length\n const rule = CommerceModel.modifierGroupRequired(group)\n ? group.max === 1\n ? 'Required · pick 1'\n : `Required · pick ${group.min} to ${group.max}`\n : group.max === 1\n ? 'Optional · pick 1'\n : `Optional · up to ${group.max}`\n return (\n <Box key={group.id} role=\"group\" aria-label={group.name}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {group.name}\n </Typography>\n <Typography\n variant=\"caption\"\n color={count < group.min ? 'warning.main' : 'text.secondary'}\n >\n {rule}\n </Typography>\n </Stack>\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {group.options.map((option) => {\n const selected = modifiers.some(\n (pick) => pick.groupId === group.id && pick.optionId === option.id,\n )\n return (\n <Chip\n key={option.id}\n label={option.priceCents ? `${option.name} +${money(option.priceCents)}` : option.name}\n color={selected ? 'primary' : 'default'}\n variant={selected ? 'filled' : 'outlined'}\n aria-pressed={selected}\n onClick={() => toggle(group, option.id)}\n disabled={!selected && group.max > 1 && count >= group.max}\n sx={{ minHeight: POS_TOUCH_PX, px: 1 }}\n />\n )\n })}\n </Box>\n </Box>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ flex: 1 }}>\n {'Quantity'}\n </Typography>\n <IconButton\n aria-label=\"One fewer\"\n onClick={() => setQuantity((value) => Math.max(1, value - 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'−'}\n </IconButton>\n <TextField\n value={quantity}\n onChange={(event) => {\n const next = Math.round(Number(event.target.value.replace(/\\D/g, '')))\n setQuantity(Math.max(1, Math.min(maxQuantity, next || 1)))\n }}\n sx={{ width: 72 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Quantity', style: { textAlign: 'center' } },\n }}\n />\n <IconButton\n aria-label=\"One more\"\n onClick={() => setQuantity((value) => Math.min(maxQuantity, value + 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'+'}\n </IconButton>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ p: 2, gap: 1 }}>\n {props.onRemove ? (\n <Button color=\"error\" onClick={props.onRemove} sx={{ minHeight: POS_TOUCH_PX, mr: 'auto' }}>\n {'Remove'}\n </Button>\n ) : null}\n <Button onClick={props.onClose} sx={{ minHeight: POS_TOUCH_PX }}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!canAdd}\n onClick={() => {\n if (!variant || !chosen.ok) return\n props.onConfirm({ variant, modifiers, quantity })\n }}\n sx={{ minHeight: POS_TOUCH_PX, minWidth: 160 }}\n >\n {missing\n ? `Choose ${missing.name.toLowerCase()}`\n : `${props.onRemove ? 'Update' : 'Add'} · ${money(unitCents * quantity)}`}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPosItemDialog.displayName = 'PosItemDialog'\n"],"names":["Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","CommerceModel","usd","POS_TOUCH_PX","posItemNeedsChoice","product","variants","length","productModifierGroups","variantFor","picked","find","variant","options","every","option","name","variantLabel","Object","values","join","soldOut","inventory","PosItemDialog","props","initial","money","formatMoney","maxQuantity","theme","phone","breakpoints","down","groups","setPicked","variantId","setVariantId","modifiers","setModifiers","quantity","setQuantity","start","id","item","chosen","resolveLineModifiers","unitCents","Math","round","Number","priceUsd","ok","extraCents","missing","group","filter","pick","groupId","min","canAdd","Boolean","blockSoldOut","toggle","optionId","current","inGroup","has","some","max","open","onClose","fullScreen","fullWidth","maxWidth","dividers","spacing","map","role","aria-label","component","sx","mb","exclusive","value","onChange","_event","flexWrap","gap","minHeight","minWidth","color","count","rule","modifierGroupRequired","direction","alignItems","display","selected","label","priceCents","aria-pressed","onClick","disabled","px","flex","width","height","border","borderColor","event","next","target","replace","slotProps","htmlInput","inputMode","style","textAlign","p","onRemove","mr","onConfirm","toLowerCase","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RegisterLine } from './pos-cart-panel.component';
|
|
2
|
+
import { posKioskCall, type PosSaleSummary } from './pos-api';
|
|
3
|
+
type User = Parameters<typeof posKioskCall>[0];
|
|
4
|
+
/** How often the register looks for orders a kiosk sent to the counter. */
|
|
5
|
+
export declare const POS_KIOSK_QUEUE_POLL_MS = 10000;
|
|
6
|
+
/**
|
|
7
|
+
* "Pay at counter" orders from this register's self-service kiosks
|
|
8
|
+
* (AGL-3623), oldest first. Each is an open sale the kiosk already priced;
|
|
9
|
+
* Take payment loads it into the register's tender panel, where the cashier
|
|
10
|
+
* takes payment exactly as on a sale they rang themselves. Draws nothing
|
|
11
|
+
* when the queue is empty.
|
|
12
|
+
*/
|
|
13
|
+
export declare function PosKioskQueue({ user, hostId, registerId, disabled, onTake, notify, }: {
|
|
14
|
+
user: User | null | undefined;
|
|
15
|
+
hostId: string;
|
|
16
|
+
registerId: string;
|
|
17
|
+
/** A sale is already open on the register. */
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
onTake: (sale: PosSaleSummary, lines: RegisterLine[]) => void;
|
|
20
|
+
notify: (message: string, variant: 'error' | 'info') => void;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import Button from "@mui/material/Button";
|
|
19
|
+
import Card from "@mui/material/Card";
|
|
20
|
+
import CardContent from "@mui/material/CardContent";
|
|
21
|
+
import CardHeader from "@mui/material/CardHeader";
|
|
22
|
+
import Divider from "@mui/material/Divider";
|
|
23
|
+
import Stack from "@mui/material/Stack";
|
|
24
|
+
import Typography from "@mui/material/Typography";
|
|
25
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
26
|
+
import { displayMoney } from "../pos-display/pos-display-api.js";
|
|
27
|
+
import { posKioskCall, PosRequestError } from "./pos-api.js";
|
|
28
|
+
/** How often the register looks for orders a kiosk sent to the counter. */ export const POS_KIOSK_QUEUE_POLL_MS = 10000;
|
|
29
|
+
/**
|
|
30
|
+
* "Pay at counter" orders from this register's self-service kiosks
|
|
31
|
+
* (AGL-3623), oldest first. Each is an open sale the kiosk already priced;
|
|
32
|
+
* Take payment loads it into the register's tender panel, where the cashier
|
|
33
|
+
* takes payment exactly as on a sale they rang themselves. Draws nothing
|
|
34
|
+
* when the queue is empty.
|
|
35
|
+
*/ export function PosKioskQueue({ user, hostId, registerId, disabled, onTake, notify }) {
|
|
36
|
+
var _ref;
|
|
37
|
+
const [entries, setEntries] = useState([]);
|
|
38
|
+
const [currency, setCurrency] = useState('usd');
|
|
39
|
+
const [taking, setTaking] = useState(null);
|
|
40
|
+
const userRef = useRef(user);
|
|
41
|
+
userRef.current = user;
|
|
42
|
+
const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
|
|
43
|
+
const load = useCallback(async ()=>{
|
|
44
|
+
const signedIn = userRef.current;
|
|
45
|
+
if (!signedIn || !registerId) return;
|
|
46
|
+
try {
|
|
47
|
+
var _result_entries;
|
|
48
|
+
const result = await posKioskCall(signedIn, {
|
|
49
|
+
action: 'queue',
|
|
50
|
+
hostId,
|
|
51
|
+
registerId
|
|
52
|
+
}, 'GET');
|
|
53
|
+
setEntries((_result_entries = result.entries) != null ? _result_entries : []);
|
|
54
|
+
if (result.currency) setCurrency(result.currency);
|
|
55
|
+
} catch (unused) {
|
|
56
|
+
// A missed poll is retried on the next tick; the queue keeps what it showed.
|
|
57
|
+
}
|
|
58
|
+
}, [
|
|
59
|
+
hostId,
|
|
60
|
+
registerId
|
|
61
|
+
]);
|
|
62
|
+
useEffect(()=>{
|
|
63
|
+
if (!uid || !registerId) {
|
|
64
|
+
setEntries([]);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
void load();
|
|
68
|
+
const timer = setInterval(()=>{
|
|
69
|
+
if (typeof document !== 'undefined' && document.hidden) return;
|
|
70
|
+
void load();
|
|
71
|
+
}, POS_KIOSK_QUEUE_POLL_MS);
|
|
72
|
+
return ()=>clearInterval(timer);
|
|
73
|
+
}, [
|
|
74
|
+
uid,
|
|
75
|
+
registerId,
|
|
76
|
+
load
|
|
77
|
+
]);
|
|
78
|
+
// A sale closing on the register is the moment a queued one may have gone.
|
|
79
|
+
useEffect(()=>{
|
|
80
|
+
if (!disabled) void load();
|
|
81
|
+
}, [
|
|
82
|
+
disabled,
|
|
83
|
+
load
|
|
84
|
+
]);
|
|
85
|
+
if (!registerId || entries.length === 0) return null;
|
|
86
|
+
const take = async (entry)=>{
|
|
87
|
+
if (!user) return;
|
|
88
|
+
setTaking(entry.orderId);
|
|
89
|
+
try {
|
|
90
|
+
var _result_lines;
|
|
91
|
+
const result = await posKioskCall(user, {
|
|
92
|
+
action: 'take',
|
|
93
|
+
hostId,
|
|
94
|
+
registerId,
|
|
95
|
+
orderId: entry.orderId
|
|
96
|
+
});
|
|
97
|
+
onTake(result.sale, (_result_lines = result.lines) != null ? _result_lines : []);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
notify(error instanceof PosRequestError ? error.message : 'That order could not be opened', 'error');
|
|
100
|
+
void load();
|
|
101
|
+
} finally{
|
|
102
|
+
setTaking(null);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return /*#__PURE__*/ _jsxs(Card, {
|
|
106
|
+
variant: "outlined",
|
|
107
|
+
sx: {
|
|
108
|
+
mb: 2
|
|
109
|
+
},
|
|
110
|
+
children: [
|
|
111
|
+
/*#__PURE__*/ _jsx(CardHeader, {
|
|
112
|
+
title: "Kiosk orders",
|
|
113
|
+
subheader: "Sent to the counter to pay",
|
|
114
|
+
slotProps: {
|
|
115
|
+
title: {
|
|
116
|
+
variant: 'subtitle1',
|
|
117
|
+
component: 'h2'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
/*#__PURE__*/ _jsx(CardContent, {
|
|
122
|
+
sx: {
|
|
123
|
+
pt: 0
|
|
124
|
+
},
|
|
125
|
+
children: /*#__PURE__*/ _jsx(Stack, {
|
|
126
|
+
divider: /*#__PURE__*/ _jsx(Divider, {
|
|
127
|
+
flexItem: true
|
|
128
|
+
}),
|
|
129
|
+
spacing: 1,
|
|
130
|
+
children: entries.map((entry)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
131
|
+
direction: "row",
|
|
132
|
+
spacing: 2,
|
|
133
|
+
sx: {
|
|
134
|
+
alignItems: 'center'
|
|
135
|
+
},
|
|
136
|
+
children: [
|
|
137
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
138
|
+
variant: "h6",
|
|
139
|
+
component: "span",
|
|
140
|
+
sx: {
|
|
141
|
+
minWidth: (theme)=>theme.spacing(8)
|
|
142
|
+
},
|
|
143
|
+
children: `#${entry.number}`
|
|
144
|
+
}),
|
|
145
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
146
|
+
variant: "body2",
|
|
147
|
+
color: "text.secondary",
|
|
148
|
+
sx: {
|
|
149
|
+
flex: 1
|
|
150
|
+
},
|
|
151
|
+
children: `${entry.itemCount} ${entry.itemCount === 1 ? 'item' : 'items'} · ${displayMoney(entry.totalCents, currency)}`
|
|
152
|
+
}),
|
|
153
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
154
|
+
variant: "contained",
|
|
155
|
+
size: "small",
|
|
156
|
+
disabled: disabled || Boolean(taking),
|
|
157
|
+
onClick: ()=>void take(entry),
|
|
158
|
+
children: taking === entry.orderId ? 'Opening…' : 'Take payment'
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
}, entry.orderId))
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=pos-kiosk-queue.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-kiosk-queue.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport CardContent from '@mui/material/CardContent'\nimport CardHeader from '@mui/material/CardHeader'\nimport Divider from '@mui/material/Divider'\nimport Stack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { displayMoney } from '../pos-display/pos-display-api'\nimport type { RegisterLine } from './pos-cart-panel.component'\nimport { posKioskCall, PosRequestError, type PosSaleSummary } from './pos-api'\n\ntype User = Parameters<typeof posKioskCall>[0]\n\ninterface QueueEntry {\n orderId: string\n number: number\n queuedAtMs: number\n totalCents: number\n itemCount: number\n}\n\n/** How often the register looks for orders a kiosk sent to the counter. */\nexport const POS_KIOSK_QUEUE_POLL_MS = 10_000\n\n/**\n * \"Pay at counter\" orders from this register's self-service kiosks\n * (AGL-3623), oldest first. Each is an open sale the kiosk already priced;\n * Take payment loads it into the register's tender panel, where the cashier\n * takes payment exactly as on a sale they rang themselves. Draws nothing\n * when the queue is empty.\n */\nexport function PosKioskQueue({\n user,\n hostId,\n registerId,\n disabled,\n onTake,\n notify,\n}: {\n user: User | null | undefined\n hostId: string\n registerId: string\n /** A sale is already open on the register. */\n disabled: boolean\n onTake: (sale: PosSaleSummary, lines: RegisterLine[]) => void\n notify: (message: string, variant: 'error' | 'info') => void\n}) {\n const [entries, setEntries] = useState<QueueEntry[]>([])\n const [currency, setCurrency] = useState('usd')\n const [taking, setTaking] = useState<string | null>(null)\n const userRef = useRef(user)\n userRef.current = user\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? ''\n\n const load = useCallback(async () => {\n const signedIn = userRef.current\n if (!signedIn || !registerId) return\n try {\n const result = await posKioskCall<{ entries: QueueEntry[]; currency: string }>(\n signedIn,\n { action: 'queue', hostId, registerId },\n 'GET',\n )\n setEntries(result.entries ?? [])\n if (result.currency) setCurrency(result.currency)\n } catch {\n // A missed poll is retried on the next tick; the queue keeps what it showed.\n }\n }, [hostId, registerId])\n\n useEffect(() => {\n if (!uid || !registerId) {\n setEntries([])\n return undefined\n }\n void load()\n const timer = setInterval(() => {\n if (typeof document !== 'undefined' && document.hidden) return\n void load()\n }, POS_KIOSK_QUEUE_POLL_MS)\n return () => clearInterval(timer)\n }, [uid, registerId, load])\n\n // A sale closing on the register is the moment a queued one may have gone.\n useEffect(() => {\n if (!disabled) void load()\n }, [disabled, load])\n\n if (!registerId || entries.length === 0) return null\n\n const take = async (entry: QueueEntry) => {\n if (!user) return\n setTaking(entry.orderId)\n try {\n const result = await posKioskCall<{ sale: PosSaleSummary; lines: RegisterLine[] }>(user, {\n action: 'take',\n hostId,\n registerId,\n orderId: entry.orderId,\n })\n onTake(result.sale, result.lines ?? [])\n } catch (error) {\n notify(error instanceof PosRequestError ? error.message : 'That order could not be opened', 'error')\n void load()\n } finally {\n setTaking(null)\n }\n }\n\n return (\n <Card variant=\"outlined\" sx={{ mb: 2 }}>\n <CardHeader\n title=\"Kiosk orders\"\n subheader=\"Sent to the counter to pay\"\n slotProps={{ title: { variant: 'subtitle1', component: 'h2' } }}\n />\n <CardContent sx={{ pt: 0 }}>\n <Stack divider={<Divider flexItem />} spacing={1}>\n {entries.map((entry) => (\n <Stack key={entry.orderId} direction=\"row\" spacing={2} sx={{ alignItems: 'center' }}>\n <Typography variant=\"h6\" component=\"span\" sx={{ minWidth: (theme) => theme.spacing(8) }}>\n {`#${entry.number}`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flex: 1 }}>\n {`${entry.itemCount} ${entry.itemCount === 1 ? 'item' : 'items'} · ${displayMoney(entry.totalCents, currency)}`}\n </Typography>\n <Button\n variant=\"contained\"\n size=\"small\"\n disabled={disabled || Boolean(taking)}\n onClick={() => void take(entry)}\n >\n {taking === entry.orderId ? 'Opening…' : 'Take payment'}\n </Button>\n </Stack>\n ))}\n </Stack>\n </CardContent>\n </Card>\n )\n}\n"],"names":["Button","Card","CardContent","CardHeader","Divider","Stack","Typography","useCallback","useEffect","useRef","useState","displayMoney","posKioskCall","PosRequestError","POS_KIOSK_QUEUE_POLL_MS","PosKioskQueue","user","hostId","registerId","disabled","onTake","notify","entries","setEntries","currency","setCurrency","taking","setTaking","userRef","current","uid","load","signedIn","result","action","undefined","timer","setInterval","document","hidden","clearInterval","length","take","entry","orderId","sale","lines","error","message","variant","sx","mb","title","subheader","slotProps","component","pt","divider","flexItem","spacing","map","direction","alignItems","minWidth","theme","number","color","flex","itemCount","totalCents","size","Boolean","onClick"],"mappings":"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"}
|