@aglyn/plugins-commerce 1.0.0-beta.278 → 1.0.0-beta.283
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 +17 -14
- package/src/lib/components/commerce-console-page.js +33 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
- package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.js +5 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
- package/src/lib/components/console/pos-hardware-card.component.js +456 -0
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +6 -3
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-zones.d.ts +37 -3
- package/src/lib/components/console/pos-zones.js +11 -1
- package/src/lib/components/console/pos-zones.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
- package/src/lib/components/console/product-check-in-fields.component.js +172 -0
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +48 -9
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
- package/src/lib/components/console/product-register-fields.component.js +39 -1
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +21 -6
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/reservations-card.component.js +239 -70
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/reservation-widget.js +16 -0
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/constants/operator-alerts.d.ts +6 -0
- package/src/lib/constants/operator-alerts.js +20 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +7 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +34 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/model/commerce-check-in-product.d.ts +37 -0
- package/src/lib/model/commerce-check-in-product.js +49 -0
- package/src/lib/model/commerce-check-in-product.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +187 -0
- package/src/lib/model/commerce-check-in.js +468 -0
- package/src/lib/model/commerce-check-in.js.map +1 -0
- package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
- package/src/lib/model/commerce-pos-day-report.js +572 -0
- package/src/lib/model/commerce-pos-day-report.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +65 -3
- package/src/lib/model/commerce-pos-offline.js +104 -7
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.d.ts +3 -0
- package/src/lib/model/commerce-pos-ops.js +1 -0
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +43 -3
- package/src/lib/model/commerce-pos.js +73 -5
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +73 -7
- package/src/lib/model/commerce-reservations.js +90 -10
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +40 -0
- package/src/lib/model/commerce.js +22 -4
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +2 -1
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-hardware.d.ts +288 -0
- package/src/lib/model/pos-hardware.js +499 -0
- package/src/lib/model/pos-hardware.js.map +1 -0
- package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
- package/src/lib/model/pos-offline-card-limits.js +28 -0
- package/src/lib/model/pos-offline-card-limits.js.map +1 -0
- package/src/lib/model/pos-reader-splash.d.ts +114 -0
- package/src/lib/model/pos-reader-splash.js +141 -0
- package/src/lib/model/pos-reader-splash.js.map +1 -0
- package/src/lib/model/product-write.d.ts +6 -0
- package/src/lib/plugin-config.d.ts +10 -0
- package/src/lib/plugin-config.js +40 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +12 -1
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.js +49 -12
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +1 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +7 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +8 -1
- package/src/lib/server/person-eraser.js +23 -1
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-check-in.d.ts +49 -0
- package/src/lib/server/pos-check-in.js +652 -0
- package/src/lib/server/pos-check-in.js.map +1 -0
- package/src/lib/server/pos-day-report.d.ts +46 -0
- package/src/lib/server/pos-day-report.js +299 -0
- package/src/lib/server/pos-day-report.js.map +1 -0
- package/src/lib/server/pos-hardware-admin.d.ts +50 -0
- package/src/lib/server/pos-hardware-admin.js +366 -0
- package/src/lib/server/pos-hardware-admin.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +27 -0
- package/src/lib/server/pos-hardware-notify.js +142 -0
- package/src/lib/server/pos-hardware-notify.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
- package/src/lib/server/pos-hardware-user-eraser.js +67 -0
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
- package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
- package/src/lib/server/pos-hardware-webhook.js +176 -0
- package/src/lib/server/pos-hardware-webhook.js.map +1 -0
- package/src/lib/server/pos-hardware.d.ts +58 -0
- package/src/lib/server/pos-hardware.js +234 -0
- package/src/lib/server/pos-hardware.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
- package/src/lib/server/pos-offline-card-reconcile.js +58 -0
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +32 -0
- package/src/lib/server/pos-offline-sync.js +191 -15
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-routes.d.ts +4 -2
- package/src/lib/server/pos-ops-routes.js +14 -2
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order.js +39 -51
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +8 -4
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
- package/src/lib/server/pos-reader-splash-admin.js +295 -0
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
- package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
- package/src/lib/server/pos-reader-splash-render.js +138 -0
- package/src/lib/server/pos-reader-splash-render.js.map +1 -0
- package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
- package/src/lib/server/pos-reader-splash-server.js +32 -0
- package/src/lib/server/pos-reader-splash-server.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +105 -0
- package/src/lib/server/pos-reader-splash.js +548 -0
- package/src/lib/server/pos-reader-splash.js.map +1 -0
- package/src/lib/server/pos-sale.d.ts +2 -1
- package/src/lib/server/pos-sale.js +3 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +67 -4
- package/src/lib/server/pos-terminal.js +239 -32
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/process-restock.js +4 -0
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-card.js +2 -2
- package/src/lib/server/product-card.js.map +1 -1
- package/src/lib/server/product-catalog.d.ts +2 -0
- package/src/lib/server/product-catalog.js +11 -4
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product.js +3 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/related.js +8 -1
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +35 -0
- package/src/lib/server/reservation-availability-query.js +34 -0
- package/src/lib/server/reservation-availability-query.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +21 -0
- package/src/lib/server/reservation-availability.js +64 -10
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-walk-in.d.ts +28 -0
- package/src/lib/server/reservation-walk-in.js +166 -0
- package/src/lib/server/reservation-walk-in.js.map +1 -0
- package/src/lib/server/reserve.js +13 -10
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/reviews.js +3 -1
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +5 -2
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +72 -0
- package/src/lib/testing/fake-firestore.js.map +1 -1
|
@@ -0,0 +1,346 @@
|
|
|
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 { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
+
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
|
+
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
22
|
+
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
23
|
+
import { useFirestore, useFirestoreCollection, useUser } from "@aglyn/tenant-feature-instance";
|
|
24
|
+
import { Alert, Button, Chip, Stack, Typography } from "@mui/material";
|
|
25
|
+
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
|
26
|
+
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
|
27
|
+
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
|
28
|
+
import { collection, documentId, limit, orderBy, query } from "firebase/firestore";
|
|
29
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
30
|
+
import { checkInItemLabel, checkInTimeLabel, describeWristband, POS_CHECK_IN_ROUTE } from "../../../model/commerce-check-in.js";
|
|
31
|
+
import { escapeHtml } from "../../../utils/escape-html.js";
|
|
32
|
+
import CommerceStatTile from "../commerce-stat-tile.component.js";
|
|
33
|
+
import { EntitlementGatedCard } from "../entitlement-gate.component.js";
|
|
34
|
+
import { POS_RECEIPT_STYLES } from "./pos-receipt.js";
|
|
35
|
+
import { printPosDocument } from "./pos-shift-report.component.js";
|
|
36
|
+
const HEADER = "Today's check-ins";
|
|
37
|
+
const HELP = pluginDocsHelp('posOperations', {
|
|
38
|
+
anchor: '#check-in-at-the-window'
|
|
39
|
+
});
|
|
40
|
+
const DAY_FORMAT = 'YYYY-MM-DD';
|
|
41
|
+
/** The day's list as a printable 80 mm document, like the day totals. */ export function posCheckInsDocument(list) {
|
|
42
|
+
const rows = list.checkIns.map((row)=>`<div class="row"><span>${escapeHtml(`${row.customerName || row.customerEmail || 'Customer'} · ${checkInItemLabel(row)}`)}</span>` + `<span>${escapeHtml(row.status === 'checked_in' ? [
|
|
43
|
+
checkInTimeLabel(row.checkedInAtMs, list.timeZone),
|
|
44
|
+
describeWristband(row.wristband)
|
|
45
|
+
].filter(Boolean).join(' ') : '—')}</span></div>`).join('');
|
|
46
|
+
return '<!doctype html><html><head><meta charset="utf-8">' + `<title>CHECK-INS</title><style>${POS_RECEIPT_STYLES}</style></head><body>` + `<div class="receipt"><div class="center"><div class="title">CHECK-INS</div>` + `<div class="muted">${escapeHtml(list.dayKey)} · ${list.totals.checkedIn} of ${list.totals.expected} checked in</div></div>` + `<hr class="rule">${rows}</div></body></html>`;
|
|
47
|
+
}
|
|
48
|
+
function PosCheckInsBody(props) {
|
|
49
|
+
const { hostId, canManagePos = false } = props;
|
|
50
|
+
const { data: user } = useUser();
|
|
51
|
+
const [day, setDay] = useState(null);
|
|
52
|
+
const [answer, setAnswer] = useState({
|
|
53
|
+
state: 'idle'
|
|
54
|
+
});
|
|
55
|
+
const [exporting, setExporting] = useState(false);
|
|
56
|
+
const url = useCallback((format)=>{
|
|
57
|
+
const params = new URLSearchParams({
|
|
58
|
+
hostId
|
|
59
|
+
});
|
|
60
|
+
if (day == null ? void 0 : day.isValid()) params.set('day', day.format(DAY_FORMAT));
|
|
61
|
+
if (format) params.set('format', format);
|
|
62
|
+
return `${POS_CHECK_IN_ROUTE}?${params.toString()}`;
|
|
63
|
+
}, [
|
|
64
|
+
hostId,
|
|
65
|
+
day
|
|
66
|
+
]);
|
|
67
|
+
const load = useCallback(async ()=>{
|
|
68
|
+
if (!user || !canManagePos) return;
|
|
69
|
+
setAnswer({
|
|
70
|
+
state: 'loading'
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const response = await authorizedFetch(user, url(), {
|
|
74
|
+
method: 'GET'
|
|
75
|
+
});
|
|
76
|
+
const body = await response.json().catch(()=>({}));
|
|
77
|
+
setAnswer(response.ok && body.checkIns ? {
|
|
78
|
+
state: 'ready',
|
|
79
|
+
list: body
|
|
80
|
+
} : {
|
|
81
|
+
state: 'error',
|
|
82
|
+
error: body.error || 'The check-ins could not be read.'
|
|
83
|
+
});
|
|
84
|
+
} catch (unused) {
|
|
85
|
+
setAnswer({
|
|
86
|
+
state: 'error',
|
|
87
|
+
error: 'The check-ins could not be read. Check the connection.'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}, [
|
|
91
|
+
user,
|
|
92
|
+
canManagePos,
|
|
93
|
+
url
|
|
94
|
+
]);
|
|
95
|
+
useEffect(()=>{
|
|
96
|
+
void load();
|
|
97
|
+
}, [
|
|
98
|
+
load
|
|
99
|
+
]);
|
|
100
|
+
const list = answer.state === 'ready' ? answer.list : null;
|
|
101
|
+
const columns = useMemo(()=>[
|
|
102
|
+
{
|
|
103
|
+
field: 'customerName',
|
|
104
|
+
headerName: 'Name',
|
|
105
|
+
flex: 1,
|
|
106
|
+
minWidth: 150,
|
|
107
|
+
valueGetter: (_value, row)=>row.customerName || row.customerEmail || ''
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
field: 'itemName',
|
|
111
|
+
headerName: 'Item',
|
|
112
|
+
flex: 1,
|
|
113
|
+
minWidth: 160,
|
|
114
|
+
valueGetter: (_value, row)=>checkInItemLabel(row)
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
field: 'orderLabel',
|
|
118
|
+
headerName: 'Order',
|
|
119
|
+
minWidth: 100,
|
|
120
|
+
valueGetter: (_value, row)=>{
|
|
121
|
+
var _row_orderLabel;
|
|
122
|
+
return (_row_orderLabel = row.orderLabel) != null ? _row_orderLabel : row.source === 'reservation' ? 'Stay' : '';
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
field: 'status',
|
|
127
|
+
headerName: 'Status',
|
|
128
|
+
minWidth: 120,
|
|
129
|
+
renderCell: ({ row })=>row.status === 'checked_in' ? /*#__PURE__*/ _jsx(Chip, {
|
|
130
|
+
size: "small",
|
|
131
|
+
color: "success",
|
|
132
|
+
label: "Checked in"
|
|
133
|
+
}) : /*#__PURE__*/ _jsx(Chip, {
|
|
134
|
+
size: "small",
|
|
135
|
+
variant: "outlined",
|
|
136
|
+
label: "Expected"
|
|
137
|
+
})
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
field: 'checkedInAtMs',
|
|
141
|
+
headerName: 'Checked in at',
|
|
142
|
+
type: 'number',
|
|
143
|
+
minWidth: 120,
|
|
144
|
+
valueFormatter: (value)=>list ? checkInTimeLabel(value, list.timeZone) : ''
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
field: 'checkedInByName',
|
|
148
|
+
headerName: 'By',
|
|
149
|
+
minWidth: 120
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
field: 'wristband',
|
|
153
|
+
headerName: 'Wristband',
|
|
154
|
+
minWidth: 120,
|
|
155
|
+
valueGetter: (_value, row)=>describeWristband(row.wristband)
|
|
156
|
+
}
|
|
157
|
+
], [
|
|
158
|
+
list
|
|
159
|
+
]);
|
|
160
|
+
const rows = useMemo(()=>list ? list.checkIns.map((row)=>_extends({}, row, {
|
|
161
|
+
$id: row.id
|
|
162
|
+
})) : [], [
|
|
163
|
+
list
|
|
164
|
+
]);
|
|
165
|
+
const exportCsv = async ()=>{
|
|
166
|
+
if (!user || !list) return;
|
|
167
|
+
setExporting(true);
|
|
168
|
+
try {
|
|
169
|
+
const response = await authorizedFetch(user, url('csv'), {
|
|
170
|
+
method: 'GET'
|
|
171
|
+
});
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
const body = await response.json().catch(()=>({}));
|
|
174
|
+
setAnswer({
|
|
175
|
+
state: 'error',
|
|
176
|
+
error: body.error || 'The spreadsheet could not be made.'
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const blob = new Blob([
|
|
181
|
+
await response.text()
|
|
182
|
+
], {
|
|
183
|
+
type: 'text/csv;charset=utf-8'
|
|
184
|
+
});
|
|
185
|
+
const href = URL.createObjectURL(blob);
|
|
186
|
+
const link = document.createElement('a');
|
|
187
|
+
link.href = href;
|
|
188
|
+
link.download = `check-ins-${list.dayKey}.csv`;
|
|
189
|
+
link.click();
|
|
190
|
+
window.setTimeout(()=>URL.revokeObjectURL(href), 1000);
|
|
191
|
+
} finally{
|
|
192
|
+
setExporting(false);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
196
|
+
header: HEADER,
|
|
197
|
+
help: HELP,
|
|
198
|
+
HeaderProps: {
|
|
199
|
+
action: /*#__PURE__*/ _jsxs(Stack, {
|
|
200
|
+
direction: "row",
|
|
201
|
+
spacing: 1,
|
|
202
|
+
children: [
|
|
203
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
204
|
+
size: "small",
|
|
205
|
+
onClick: ()=>void load(),
|
|
206
|
+
disabled: !canManagePos || answer.state === 'loading',
|
|
207
|
+
children: 'Refresh'
|
|
208
|
+
}),
|
|
209
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
210
|
+
size: "small",
|
|
211
|
+
onClick: ()=>void exportCsv(),
|
|
212
|
+
disabled: !list || exporting,
|
|
213
|
+
children: 'Export CSV'
|
|
214
|
+
}),
|
|
215
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
216
|
+
size: "small",
|
|
217
|
+
onClick: ()=>list && printPosDocument(posCheckInsDocument(list)),
|
|
218
|
+
disabled: !list,
|
|
219
|
+
children: 'Print'
|
|
220
|
+
})
|
|
221
|
+
]
|
|
222
|
+
})
|
|
223
|
+
},
|
|
224
|
+
contentGutterX: true,
|
|
225
|
+
contentGutterY: true,
|
|
226
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
227
|
+
spacing: 2.5,
|
|
228
|
+
children: [
|
|
229
|
+
!canManagePos ? /*#__PURE__*/ _jsx(Alert, {
|
|
230
|
+
severity: "info",
|
|
231
|
+
children: 'Check-ins are for members who can use the register: a site admin or editor with the "Use the point of sale" permission.'
|
|
232
|
+
}) : null,
|
|
233
|
+
/*#__PURE__*/ _jsx(LocalizationProvider, {
|
|
234
|
+
dateAdapter: AdapterDayjs,
|
|
235
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
236
|
+
direction: {
|
|
237
|
+
xs: 'column',
|
|
238
|
+
sm: 'row'
|
|
239
|
+
},
|
|
240
|
+
spacing: 1.5,
|
|
241
|
+
sx: {
|
|
242
|
+
alignItems: {
|
|
243
|
+
sm: 'center'
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
children: [
|
|
247
|
+
/*#__PURE__*/ _jsx(DatePicker, {
|
|
248
|
+
label: "Day",
|
|
249
|
+
value: day,
|
|
250
|
+
onChange: setDay,
|
|
251
|
+
disabled: !canManagePos,
|
|
252
|
+
slotProps: {
|
|
253
|
+
textField: {
|
|
254
|
+
size: 'small'
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}),
|
|
258
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
259
|
+
size: "small",
|
|
260
|
+
disabled: !canManagePos || !day,
|
|
261
|
+
onClick: ()=>setDay(null),
|
|
262
|
+
children: 'Today'
|
|
263
|
+
})
|
|
264
|
+
]
|
|
265
|
+
})
|
|
266
|
+
}),
|
|
267
|
+
answer.state === 'loading' ? /*#__PURE__*/ _jsx(Typography, {
|
|
268
|
+
variant: "body2",
|
|
269
|
+
color: "text.secondary",
|
|
270
|
+
children: 'Reading who is due…'
|
|
271
|
+
}) : null,
|
|
272
|
+
answer.state === 'error' ? /*#__PURE__*/ _jsx(Alert, {
|
|
273
|
+
severity: "warning",
|
|
274
|
+
children: answer.error
|
|
275
|
+
}) : null,
|
|
276
|
+
list ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
277
|
+
children: [
|
|
278
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
279
|
+
variant: "body2",
|
|
280
|
+
color: "text.secondary",
|
|
281
|
+
children: `${list.dayKey} · ${list.timeZone} · passes and stays that need check-in`
|
|
282
|
+
}),
|
|
283
|
+
list.truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
284
|
+
severity: "warning",
|
|
285
|
+
children: 'This day has more check-ins than one list reads; some are missing here.'
|
|
286
|
+
}) : null,
|
|
287
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
288
|
+
direction: "row",
|
|
289
|
+
spacing: 3,
|
|
290
|
+
sx: {
|
|
291
|
+
flexWrap: 'wrap',
|
|
292
|
+
rowGap: 1.5
|
|
293
|
+
},
|
|
294
|
+
children: [
|
|
295
|
+
/*#__PURE__*/ _jsx(CommerceStatTile, {
|
|
296
|
+
label: "Expected",
|
|
297
|
+
value: String(list.totals.expected)
|
|
298
|
+
}),
|
|
299
|
+
/*#__PURE__*/ _jsx(CommerceStatTile, {
|
|
300
|
+
label: "Checked in",
|
|
301
|
+
value: String(list.totals.checkedIn)
|
|
302
|
+
}),
|
|
303
|
+
/*#__PURE__*/ _jsx(CommerceStatTile, {
|
|
304
|
+
label: "Still to come",
|
|
305
|
+
value: String(list.totals.expected - list.totals.checkedIn)
|
|
306
|
+
})
|
|
307
|
+
]
|
|
308
|
+
}),
|
|
309
|
+
/*#__PURE__*/ _jsx(ListTable, {
|
|
310
|
+
"aria-label": "Check-ins on this day",
|
|
311
|
+
rows: rows,
|
|
312
|
+
columns: columns,
|
|
313
|
+
noRowsLabel: "Nobody is due to check in on this day"
|
|
314
|
+
})
|
|
315
|
+
]
|
|
316
|
+
}) : null
|
|
317
|
+
]
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Today's check-ins (AGL-4043): beside the day totals, for a store with a
|
|
323
|
+
* register, on a plan with the point of sale.
|
|
324
|
+
*/ export function PosCheckInsCard(props) {
|
|
325
|
+
const firestore = useFirestore();
|
|
326
|
+
// Whether the store has a register at all, as the day totals ask it.
|
|
327
|
+
const { data: registers, status } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', props.hostId, 'registers'), orderBy(documentId()), limit(1)), [
|
|
328
|
+
firestore,
|
|
329
|
+
props.hostId
|
|
330
|
+
], {
|
|
331
|
+
idField: '$id'
|
|
332
|
+
});
|
|
333
|
+
if (status === 'success' && !(registers == null ? void 0 : registers.length)) return null;
|
|
334
|
+
return /*#__PURE__*/ _jsx(EntitlementGatedCard, {
|
|
335
|
+
hostId: props.hostId,
|
|
336
|
+
feature: "pos",
|
|
337
|
+
header: HEADER,
|
|
338
|
+
help: HELP,
|
|
339
|
+
upsell: 'Checking in passes, camping and stays at the window, with wristbands, comes with the point of sale.',
|
|
340
|
+
children: /*#__PURE__*/ _jsx(PosCheckInsBody, _extends({}, props))
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
PosCheckInsCard.displayName = 'PosCheckInsCard';
|
|
344
|
+
export default PosCheckInsCard;
|
|
345
|
+
|
|
346
|
+
//# sourceMappingURL=pos-check-ins-card.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos-ops/pos-check-ins-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'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useFirestoreCollection, useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Chip, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'\nimport { DatePicker } from '@mui/x-date-pickers/DatePicker'\nimport { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'\nimport type { Dayjs } from 'dayjs'\nimport { collection, documentId, limit, orderBy, query } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n checkInItemLabel,\n checkInTimeLabel,\n describeWristband,\n POS_CHECK_IN_ROUTE,\n type CheckInDayTotals,\n type CheckInRow,\n} from '../../../model/commerce-check-in'\nimport { escapeHtml } from '../../../utils/escape-html'\nimport CommerceStatTile from '../commerce-stat-tile.component'\nimport { EntitlementGatedCard } from '../entitlement-gate.component'\nimport { POS_RECEIPT_STYLES } from './pos-receipt'\nimport { printPosDocument } from './pos-shift-report.component'\n\n/*==========================================\n * TODAY'S CHECK-INS (AGL-4043): who is due at the window on a day, who is\n * checked in, by whom and with which wristband, from\n * `GET /api/commerce/pos-check-in`. The day is a whole, bounded list, so\n * the grid sorts it by any column itself.\n *=========================================*/\n\nexport interface PosCheckInsCardProps {\n hostId: string\n /** The viewer may work the register: what the route's gate asks. */\n canManagePos?: boolean\n}\n\nconst HEADER = \"Today's check-ins\"\nconst HELP = pluginDocsHelp('posOperations', { anchor: '#check-in-at-the-window' })\nconst DAY_FORMAT = 'YYYY-MM-DD'\n\ninterface DayList {\n dayKey: string\n timeZone: string\n totals: CheckInDayTotals\n checkIns: CheckInRow[]\n truncated?: boolean\n}\n\ntype Answer =\n | { state: 'idle' | 'loading' }\n | { state: 'ready'; list: DayList }\n | { state: 'error'; error: string }\n\n/** The day's list as a printable 80 mm document, like the day totals. */\nexport function posCheckInsDocument(list: DayList): string {\n const rows = list.checkIns\n .map(\n (row) =>\n `<div class=\"row\"><span>${escapeHtml(`${row.customerName || row.customerEmail || 'Customer'} · ${checkInItemLabel(row)}`)}</span>` +\n `<span>${escapeHtml(\n row.status === 'checked_in'\n ? [checkInTimeLabel(row.checkedInAtMs, list.timeZone), describeWristband(row.wristband)].filter(Boolean).join(' ')\n : '—',\n )}</span></div>`,\n )\n .join('')\n return (\n '<!doctype html><html><head><meta charset=\"utf-8\">' +\n `<title>CHECK-INS</title><style>${POS_RECEIPT_STYLES}</style></head><body>` +\n `<div class=\"receipt\"><div class=\"center\"><div class=\"title\">CHECK-INS</div>` +\n `<div class=\"muted\">${escapeHtml(list.dayKey)} · ${list.totals.checkedIn} of ${list.totals.expected} checked in</div></div>` +\n `<hr class=\"rule\">${rows}</div></body></html>`\n )\n}\n\nfunction PosCheckInsBody(props: PosCheckInsCardProps) {\n const { hostId, canManagePos = false } = props\n const { data: user } = useUser()\n const [day, setDay] = useState<Dayjs | null>(null)\n const [answer, setAnswer] = useState<Answer>({ state: 'idle' })\n const [exporting, setExporting] = useState(false)\n\n const url = useCallback(\n (format?: 'csv') => {\n const params = new URLSearchParams({ hostId })\n if (day?.isValid()) params.set('day', day.format(DAY_FORMAT))\n if (format) params.set('format', format)\n return `${POS_CHECK_IN_ROUTE}?${params.toString()}`\n },\n [hostId, day],\n )\n\n const load = useCallback(async () => {\n if (!user || !canManagePos) return\n setAnswer({ state: 'loading' })\n try {\n const response = await authorizedFetch(user as never, url(), { method: 'GET' })\n const body = (await response.json().catch(() => ({}))) as Partial<DayList> & { error?: string }\n setAnswer(\n response.ok && body.checkIns\n ? { state: 'ready', list: body as DayList }\n : { state: 'error', error: body.error || 'The check-ins could not be read.' },\n )\n } catch {\n setAnswer({ state: 'error', error: 'The check-ins could not be read. Check the connection.' })\n }\n }, [user, canManagePos, url])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const list = answer.state === 'ready' ? answer.list : null\n const columns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'customerName',\n headerName: 'Name',\n flex: 1,\n minWidth: 150,\n valueGetter: (_value: unknown, row: CheckInRow) => row.customerName || row.customerEmail || '',\n },\n {\n field: 'itemName',\n headerName: 'Item',\n flex: 1,\n minWidth: 160,\n valueGetter: (_value: unknown, row: CheckInRow) => checkInItemLabel(row),\n },\n {\n field: 'orderLabel',\n headerName: 'Order',\n minWidth: 100,\n valueGetter: (_value: unknown, row: CheckInRow) => row.orderLabel ?? (row.source === 'reservation' ? 'Stay' : ''),\n },\n {\n field: 'status',\n headerName: 'Status',\n minWidth: 120,\n renderCell: ({ row }: { row: CheckInRow }) =>\n row.status === 'checked_in' ? (\n <Chip size=\"small\" color=\"success\" label=\"Checked in\" />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label=\"Expected\" />\n ),\n },\n {\n field: 'checkedInAtMs',\n headerName: 'Checked in at',\n type: 'number',\n minWidth: 120,\n valueFormatter: (value: number | null) => (list ? checkInTimeLabel(value, list.timeZone) : ''),\n },\n { field: 'checkedInByName', headerName: 'By', minWidth: 120 },\n {\n field: 'wristband',\n headerName: 'Wristband',\n minWidth: 120,\n valueGetter: (_value: unknown, row: CheckInRow) => describeWristband(row.wristband),\n },\n ],\n [list],\n )\n const rows = useMemo(() => (list ? list.checkIns.map((row) => ({ ...row, $id: row.id })) : []), [list])\n\n const exportCsv = async () => {\n if (!user || !list) return\n setExporting(true)\n try {\n const response = await authorizedFetch(user as never, url('csv'), { method: 'GET' })\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string }\n setAnswer({ state: 'error', error: body.error || 'The spreadsheet could not be made.' })\n return\n }\n const blob = new Blob([await response.text()], { type: 'text/csv;charset=utf-8' })\n const href = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = href\n link.download = `check-ins-${list.dayKey}.csv`\n link.click()\n window.setTimeout(() => URL.revokeObjectURL(href), 1000)\n } finally {\n setExporting(false)\n }\n }\n\n return (\n <CardDisplay\n header={HEADER}\n help={HELP}\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1}>\n <Button size=\"small\" onClick={() => void load()} disabled={!canManagePos || answer.state === 'loading'}>\n {'Refresh'}\n </Button>\n <Button size=\"small\" onClick={() => void exportCsv()} disabled={!list || exporting}>\n {'Export CSV'}\n </Button>\n <Button size=\"small\" onClick={() => list && printPosDocument(posCheckInsDocument(list))} disabled={!list}>\n {'Print'}\n </Button>\n </Stack>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2.5}>\n {!canManagePos ? (\n <Alert severity=\"info\">\n {'Check-ins are for members who can use the register: a site admin or editor with the \"Use the point of sale\" permission.'}\n </Alert>\n ) : null}\n <LocalizationProvider dateAdapter={AdapterDayjs}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} sx={{ alignItems: { sm: 'center' } }}>\n <DatePicker\n label=\"Day\"\n value={day}\n onChange={setDay}\n disabled={!canManagePos}\n slotProps={{ textField: { size: 'small' } }}\n />\n <Button size=\"small\" disabled={!canManagePos || !day} onClick={() => setDay(null)}>\n {'Today'}\n </Button>\n </Stack>\n </LocalizationProvider>\n {answer.state === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reading who is due…'}\n </Typography>\n ) : null}\n {answer.state === 'error' ? <Alert severity=\"warning\">{answer.error}</Alert> : null}\n {list ? (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${list.dayKey} · ${list.timeZone} · passes and stays that need check-in`}\n </Typography>\n {list.truncated ? (\n <Alert severity=\"warning\">\n {'This day has more check-ins than one list reads; some are missing here.'}\n </Alert>\n ) : null}\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap', rowGap: 1.5 }}>\n <CommerceStatTile label=\"Expected\" value={String(list.totals.expected)} />\n <CommerceStatTile label=\"Checked in\" value={String(list.totals.checkedIn)} />\n <CommerceStatTile label=\"Still to come\" value={String(list.totals.expected - list.totals.checkedIn)} />\n </Stack>\n <ListTable\n aria-label=\"Check-ins on this day\"\n rows={rows}\n columns={columns}\n noRowsLabel=\"Nobody is due to check in on this day\"\n />\n </>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\n\n/**\n * Today's check-ins (AGL-4043): beside the day totals, for a store with a\n * register, on a plan with the point of sale.\n */\nexport function PosCheckInsCard(props: PosCheckInsCardProps) {\n const firestore = useFirestore()\n // Whether the store has a register at all, as the day totals ask it.\n const { data: registers, status } = useFirestoreCollection<any>(\n () =>\n query(collection(firestore, 'hosts', props.hostId, 'registers'), orderBy(documentId()), limit(1)),\n [firestore, props.hostId],\n { idField: '$id' },\n )\n if (status === 'success' && !registers?.length) return null\n return (\n <EntitlementGatedCard\n hostId={props.hostId}\n feature=\"pos\"\n header={HEADER}\n help={HELP}\n upsell={'Checking in passes, camping and stays at the window, with wristbands, comes with the point of sale.'}\n >\n <PosCheckInsBody {...props} />\n </EntitlementGatedCard>\n )\n}\n\nPosCheckInsCard.displayName = 'PosCheckInsCard'\n\nexport default PosCheckInsCard\n"],"names":["pluginDocsHelp","CardDisplay","ListTable","authorizedFetch","useFirestore","useFirestoreCollection","useUser","Alert","Button","Chip","Stack","Typography","AdapterDayjs","DatePicker","LocalizationProvider","collection","documentId","limit","orderBy","query","useCallback","useEffect","useMemo","useState","checkInItemLabel","checkInTimeLabel","describeWristband","POS_CHECK_IN_ROUTE","escapeHtml","CommerceStatTile","EntitlementGatedCard","POS_RECEIPT_STYLES","printPosDocument","HEADER","HELP","anchor","DAY_FORMAT","posCheckInsDocument","list","rows","checkIns","map","row","customerName","customerEmail","status","checkedInAtMs","timeZone","wristband","filter","Boolean","join","dayKey","totals","checkedIn","expected","PosCheckInsBody","props","hostId","canManagePos","data","user","day","setDay","answer","setAnswer","state","exporting","setExporting","url","format","params","URLSearchParams","isValid","set","toString","load","response","method","body","json","catch","ok","error","columns","field","headerName","flex","minWidth","valueGetter","_value","orderLabel","source","renderCell","size","color","label","variant","type","valueFormatter","value","$id","id","exportCsv","blob","Blob","text","href","URL","createObjectURL","link","document","createElement","download","click","window","setTimeout","revokeObjectURL","header","help","HeaderProps","action","direction","spacing","onClick","disabled","contentGutterX","contentGutterY","severity","dateAdapter","xs","sm","sx","alignItems","onChange","slotProps","textField","truncated","flexWrap","rowGap","String","aria-label","noRowsLabel","PosCheckInsCard","firestore","registers","idField","length","feature","upsell","displayName"],"mappings":"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"}
|
|
@@ -26,11 +26,15 @@ export interface PosCustomerLookupProps {
|
|
|
26
26
|
hostId: string;
|
|
27
27
|
value: PosSelectedCustomer | null;
|
|
28
28
|
onChange: (customer: PosSelectedCustomer | null) => void;
|
|
29
|
+
/** The register and its PIN's cashier, for who checks the customer in (AGL-4043). */
|
|
30
|
+
registerId?: string;
|
|
31
|
+
cashierAssertion?: string;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* The customer at the register (AGL-3609): search the workspace's people by
|
|
32
35
|
* name, email or phone, see what they have bought here before, attach them
|
|
33
|
-
* to the sale — or add a new one in a few taps
|
|
36
|
+
* to the sale — or add a new one in a few taps — and check them in when they
|
|
37
|
+
* have a pass or a stay due today (AGL-4043). Where the workspace keeps no
|
|
34
38
|
* people, it falls back to an email typed for the receipt.
|
|
35
39
|
*/
|
|
36
40
|
export declare function PosCustomerLookup(props: PosCustomerLookupProps): import("react").JSX.Element;
|
|
@@ -18,17 +18,20 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
19
|
import { Alert, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, List, ListItemButton, ListItemText, Stack, TextField, Typography } from "@mui/material";
|
|
20
20
|
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
21
|
-
import { useEffect, useRef, useState } from "react";
|
|
21
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
22
22
|
import { posMoney } from "../../../model/commerce-pos-ops.js";
|
|
23
|
+
import { PosCheckInDialog } from "./pos-check-in-dialog.component.js";
|
|
24
|
+
import { PosCheckInZone } from "./pos-check-in-zone.component.js";
|
|
23
25
|
import { callPosOps } from "./pos-ops-api.js";
|
|
24
26
|
/** How long typing pauses before the register asks. */ const SEARCH_DEBOUNCE_MS = 250;
|
|
25
27
|
/**
|
|
26
28
|
* The customer at the register (AGL-3609): search the workspace's people by
|
|
27
29
|
* name, email or phone, see what they have bought here before, attach them
|
|
28
|
-
* to the sale — or add a new one in a few taps
|
|
30
|
+
* to the sale — or add a new one in a few taps — and check them in when they
|
|
31
|
+
* have a pass or a stay due today (AGL-4043). Where the workspace keeps no
|
|
29
32
|
* people, it falls back to an email typed for the receipt.
|
|
30
33
|
*/ export function PosCustomerLookup(props) {
|
|
31
|
-
const { hostId, value, onChange } = props;
|
|
34
|
+
const { hostId, value, onChange, registerId, cashierAssertion } = props;
|
|
32
35
|
const { data: user } = useUser();
|
|
33
36
|
const [text, setText] = useState('');
|
|
34
37
|
const [results, setResults] = useState([]);
|
|
@@ -93,6 +96,48 @@ import { callPosOps } from "./pos-ops-api.js";
|
|
|
93
96
|
user,
|
|
94
97
|
hostId
|
|
95
98
|
]);
|
|
99
|
+
// What the attached customer has to check in today (AGL-4043). The panel
|
|
100
|
+
// asks on every customer, so the server brings the day in line only on a
|
|
101
|
+
// day the window is already checking people in.
|
|
102
|
+
const [due, setDue] = useState(null);
|
|
103
|
+
const [checkingIn, setCheckingIn] = useState(false);
|
|
104
|
+
const contactId = (value == null ? void 0 : value.kind) === 'contact' ? value.id : '';
|
|
105
|
+
const refreshDue = useCallback(()=>{
|
|
106
|
+
if (!(value == null ? void 0 : value.email) && !contactId || !user) return undefined;
|
|
107
|
+
let active = true;
|
|
108
|
+
void callPosOps(user, 'pos-check-in', _extends({
|
|
109
|
+
hostId,
|
|
110
|
+
action: 'customer',
|
|
111
|
+
reconcile: 'if-used'
|
|
112
|
+
}, (value == null ? void 0 : value.email) ? {
|
|
113
|
+
email: value.email
|
|
114
|
+
} : {
|
|
115
|
+
contactId
|
|
116
|
+
})).then((answer)=>{
|
|
117
|
+
var _answer_body_checkIns;
|
|
118
|
+
if (!active || !answer.ok || !answer.body.dayKey) return;
|
|
119
|
+
const rows = (_answer_body_checkIns = answer.body.checkIns) != null ? _answer_body_checkIns : [];
|
|
120
|
+
setDue({
|
|
121
|
+
dayKey: answer.body.dayKey,
|
|
122
|
+
waiting: rows.filter((row)=>row.status === 'expected').length,
|
|
123
|
+
total: rows.length
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
return ()=>{
|
|
127
|
+
active = false;
|
|
128
|
+
};
|
|
129
|
+
}, [
|
|
130
|
+
value == null ? void 0 : value.email,
|
|
131
|
+
contactId,
|
|
132
|
+
user,
|
|
133
|
+
hostId
|
|
134
|
+
]);
|
|
135
|
+
useEffect(()=>{
|
|
136
|
+
setDue(null);
|
|
137
|
+
return refreshDue();
|
|
138
|
+
}, [
|
|
139
|
+
refreshDue
|
|
140
|
+
]);
|
|
96
141
|
const pick = (customer)=>{
|
|
97
142
|
onChange(customer);
|
|
98
143
|
setText('');
|
|
@@ -137,7 +182,41 @@ import { callPosOps } from "./pos-ops-api.js";
|
|
|
137
182
|
variant: "caption",
|
|
138
183
|
color: "text.secondary",
|
|
139
184
|
children: stats.orderCount ? `${stats.orderCount} past ${stats.orderCount === 1 ? 'order' : 'orders'} · ${posMoney(stats.lifetimeSpendCents)} spent` : 'First order here'
|
|
140
|
-
}) : null
|
|
185
|
+
}) : null,
|
|
186
|
+
(due == null ? void 0 : due.waiting) ? /*#__PURE__*/ _jsx(Button, {
|
|
187
|
+
size: "small",
|
|
188
|
+
variant: "outlined",
|
|
189
|
+
onClick: ()=>setCheckingIn(true),
|
|
190
|
+
children: `Check in (${due.waiting})`
|
|
191
|
+
}) : null,
|
|
192
|
+
due ? /*#__PURE__*/ _jsx(PosCheckInZone, {
|
|
193
|
+
hostId: hostId,
|
|
194
|
+
orgId: undefined,
|
|
195
|
+
contactId: contactId || null,
|
|
196
|
+
email: value.email ? value.email.toLowerCase() : null,
|
|
197
|
+
dayKey: due.dayKey,
|
|
198
|
+
name: value.name || null,
|
|
199
|
+
unitsDue: due.total,
|
|
200
|
+
placement: "register-customer"
|
|
201
|
+
}) : null,
|
|
202
|
+
/*#__PURE__*/ _jsx(PosCheckInDialog, _extends({
|
|
203
|
+
open: checkingIn,
|
|
204
|
+
hostId: hostId
|
|
205
|
+
}, registerId ? {
|
|
206
|
+
registerId
|
|
207
|
+
} : {}, cashierAssertion ? {
|
|
208
|
+
cashierAssertion
|
|
209
|
+
} : {}, {
|
|
210
|
+
initialCustomer: value.email ? {
|
|
211
|
+
email: value.email
|
|
212
|
+
} : {
|
|
213
|
+
contactId: contactId || null
|
|
214
|
+
},
|
|
215
|
+
onClose: ()=>{
|
|
216
|
+
setCheckingIn(false);
|
|
217
|
+
refreshDue();
|
|
218
|
+
}
|
|
219
|
+
}))
|
|
141
220
|
]
|
|
142
221
|
});
|
|
143
222
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos-ops/pos-customer-lookup.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 {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n List,\n ListItemButton,\n ListItemText,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useEffect, useRef, useState } from 'react'\nimport { posMoney } from '../../../model/commerce-pos-ops'\nimport { callPosOps } from './pos-ops-api'\n\n/** The customer a sale is rung for. `id` is empty when no record system keeps people. */\nexport interface PosSelectedCustomer {\n kind: string\n id: string\n name: string\n email: string | null\n phone: string | null\n}\n\nexport interface PosCustomerLookupProps {\n hostId: string\n value: PosSelectedCustomer | null\n onChange: (customer: PosSelectedCustomer | null) => void\n}\n\n/** How long typing pauses before the register asks. */\nconst SEARCH_DEBOUNCE_MS = 250\n\n/**\n * The customer at the register (AGL-3609): search the workspace's people by\n * name, email or phone, see what they have bought here before, attach them\n * to the sale — or add a new one in a few taps. Where the workspace keeps no\n * people, it falls back to an email typed for the receipt.\n */\nexport function PosCustomerLookup(props: PosCustomerLookupProps) {\n const { hostId, value, onChange } = props\n const { data: user } = useUser()\n const [text, setText] = useState('')\n const [results, setResults] = useState<PosSelectedCustomer[]>([])\n const [available, setAvailable] = useState(true)\n const [stats, setStats] = useState<{ orderCount: number; lifetimeSpendCents: number } | null>(null)\n const [adding, setAdding] = useState(false)\n const [draft, setDraft] = useState({ name: '', email: '', phone: '' })\n const [error, setError] = useState('')\n const [busy, setBusy] = useState(false)\n const latest = useRef(0)\n\n useEffect(() => {\n const typed = text.trim()\n if (typed.length < 2 || !user) {\n setResults([])\n return\n }\n const ticket = ++latest.current\n const timer = window.setTimeout(() => {\n void callPosOps<{ available?: boolean; customers?: PosSelectedCustomer[] }>(user, 'pos-customer', {\n hostId,\n action: 'search',\n text: typed,\n }).then((answer) => {\n if (ticket !== latest.current) return\n setAvailable(answer.body.available !== false)\n setResults(answer.ok ? (answer.body.customers ?? []) : [])\n })\n }, SEARCH_DEBOUNCE_MS)\n return () => window.clearTimeout(timer)\n }, [text, user, hostId])\n\n useEffect(() => {\n setStats(null)\n if (!value?.email || !user) return\n let active = true\n void callPosOps<{ orderCount?: number; lifetimeSpendCents?: number }>(user, 'pos-customer', {\n hostId,\n action: 'stats',\n email: value.email,\n }).then((answer) => {\n if (active && answer.ok) {\n setStats({\n orderCount: Number(answer.body.orderCount ?? 0),\n lifetimeSpendCents: Number(answer.body.lifetimeSpendCents ?? 0),\n })\n }\n })\n return () => {\n active = false\n }\n }, [value?.email, user, hostId])\n\n const pick = (customer: PosSelectedCustomer) => {\n onChange(customer)\n setText('')\n setResults([])\n }\n\n const typedEmail = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(text.trim()) ? text.trim().toLowerCase() : ''\n\n const create = async () => {\n setBusy(true)\n setError('')\n const answer = await callPosOps<{ customer?: PosSelectedCustomer }>(user, 'pos-customer', {\n hostId,\n action: 'create',\n ...draft,\n })\n setBusy(false)\n if (!answer.ok || !answer.body.customer) return setError(answer.body.error ?? 'Could not add the customer.')\n pick(answer.body.customer)\n setAdding(false)\n }\n\n if (value) {\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip\n label={value.name || value.email || 'Customer'}\n onDelete={() => onChange(null)}\n color=\"primary\"\n variant=\"outlined\"\n />\n {value.name && value.email ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {value.email}\n </Typography>\n ) : null}\n {stats ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {stats.orderCount\n ? `${stats.orderCount} past ${stats.orderCount === 1 ? 'order' : 'orders'} · ${posMoney(\n stats.lifetimeSpendCents,\n )} spent`\n : 'First order here'}\n </Typography>\n ) : null}\n </Stack>\n )\n }\n\n return (\n <Stack spacing={0.5}>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Customer\"\n placeholder={available ? 'Name, email or phone' : 'Email for the receipt'}\n value={text}\n onChange={(event) => setText(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !available && typedEmail) {\n pick({ kind: 'none', id: '', name: '', email: typedEmail, phone: null })\n }\n }}\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n onClick={() => {\n setDraft({ name: '', email: typedEmail, phone: '' })\n setError('')\n setAdding(true)\n }}\n >\n {'New customer'}\n </Button>\n </Stack>\n {results.length ? (\n <List dense disablePadding>\n {results.map((customer) => (\n <ListItemButton key={`${customer.kind}:${customer.id}`} onClick={() => pick(customer)}>\n <ListItemText\n primary={customer.name || customer.email || 'Unnamed'}\n secondary={[customer.email, customer.phone].filter(Boolean).join(' · ')}\n />\n </ListItemButton>\n ))}\n </List>\n ) : null}\n {!available && typedEmail ? (\n <Button size=\"small\" onClick={() => pick({ kind: 'none', id: '', name: '', email: typedEmail, phone: null })} sx={{ alignSelf: 'flex-start' }}>\n {`Use ${typedEmail}`}\n </Button>\n ) : null}\n <Dialog open={adding} onClose={() => setAdding(false)} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'New customer'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n autoFocus\n label=\"Name\"\n value={draft.name}\n onChange={(event) => setDraft({ ...draft, name: event.target.value })}\n />\n <TextField\n label=\"Email\"\n type=\"email\"\n value={draft.email}\n onChange={(event) => setDraft({ ...draft, email: event.target.value })}\n />\n <TextField\n label=\"Phone (optional)\"\n type=\"tel\"\n value={draft.phone}\n onChange={(event) => setDraft({ ...draft, phone: event.target.value })}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Adding a customer does not sign them up for marketing email.'}\n </Typography>\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAdding(false)}>{'Cancel'}</Button>\n <Button variant=\"contained\" disabled={busy} onClick={create}>\n {'Add customer'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\n\nPosCustomerLookup.displayName = 'PosCustomerLookup'\n\nexport default PosCustomerLookup\n"],"names":["Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","List","ListItemButton","ListItemText","Stack","TextField","Typography","useUser","useEffect","useRef","useState","posMoney","callPosOps","SEARCH_DEBOUNCE_MS","PosCustomerLookup","props","hostId","value","onChange","data","user","text","setText","results","setResults","available","setAvailable","stats","setStats","adding","setAdding","draft","setDraft","name","email","phone","error","setError","busy","setBusy","latest","typed","trim","length","ticket","current","timer","window","setTimeout","action","then","answer","body","ok","customers","clearTimeout","active","orderCount","Number","lifetimeSpendCents","pick","customer","typedEmail","test","toLowerCase","create","direction","spacing","useFlexGap","sx","alignItems","flexWrap","label","onDelete","color","variant","size","placeholder","event","target","onKeyDown","key","kind","id","flex","onClick","dense","disablePadding","map","primary","secondary","filter","Boolean","join","alignSelf","open","onClose","maxWidth","fullWidth","pt","autoFocus","type","severity","disabled","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos-ops/pos-customer-lookup.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 {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n List,\n ListItemButton,\n ListItemText,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { CheckInRow } from '../../../model/commerce-check-in'\nimport { posMoney } from '../../../model/commerce-pos-ops'\nimport { PosCheckInDialog } from './pos-check-in-dialog.component'\nimport { PosCheckInZone } from './pos-check-in-zone.component'\nimport { callPosOps } from './pos-ops-api'\n\n/** The customer a sale is rung for. `id` is empty when no record system keeps people. */\nexport interface PosSelectedCustomer {\n kind: string\n id: string\n name: string\n email: string | null\n phone: string | null\n}\n\nexport interface PosCustomerLookupProps {\n hostId: string\n value: PosSelectedCustomer | null\n onChange: (customer: PosSelectedCustomer | null) => void\n /** The register and its PIN's cashier, for who checks the customer in (AGL-4043). */\n registerId?: string\n cashierAssertion?: string\n}\n\n/** How long typing pauses before the register asks. */\nconst SEARCH_DEBOUNCE_MS = 250\n\n/**\n * The customer at the register (AGL-3609): search the workspace's people by\n * name, email or phone, see what they have bought here before, attach them\n * to the sale — or add a new one in a few taps — and check them in when they\n * have a pass or a stay due today (AGL-4043). Where the workspace keeps no\n * people, it falls back to an email typed for the receipt.\n */\nexport function PosCustomerLookup(props: PosCustomerLookupProps) {\n const { hostId, value, onChange, registerId, cashierAssertion } = props\n const { data: user } = useUser()\n const [text, setText] = useState('')\n const [results, setResults] = useState<PosSelectedCustomer[]>([])\n const [available, setAvailable] = useState(true)\n const [stats, setStats] = useState<{ orderCount: number; lifetimeSpendCents: number } | null>(null)\n const [adding, setAdding] = useState(false)\n const [draft, setDraft] = useState({ name: '', email: '', phone: '' })\n const [error, setError] = useState('')\n const [busy, setBusy] = useState(false)\n const latest = useRef(0)\n\n useEffect(() => {\n const typed = text.trim()\n if (typed.length < 2 || !user) {\n setResults([])\n return\n }\n const ticket = ++latest.current\n const timer = window.setTimeout(() => {\n void callPosOps<{ available?: boolean; customers?: PosSelectedCustomer[] }>(user, 'pos-customer', {\n hostId,\n action: 'search',\n text: typed,\n }).then((answer) => {\n if (ticket !== latest.current) return\n setAvailable(answer.body.available !== false)\n setResults(answer.ok ? (answer.body.customers ?? []) : [])\n })\n }, SEARCH_DEBOUNCE_MS)\n return () => window.clearTimeout(timer)\n }, [text, user, hostId])\n\n useEffect(() => {\n setStats(null)\n if (!value?.email || !user) return\n let active = true\n void callPosOps<{ orderCount?: number; lifetimeSpendCents?: number }>(user, 'pos-customer', {\n hostId,\n action: 'stats',\n email: value.email,\n }).then((answer) => {\n if (active && answer.ok) {\n setStats({\n orderCount: Number(answer.body.orderCount ?? 0),\n lifetimeSpendCents: Number(answer.body.lifetimeSpendCents ?? 0),\n })\n }\n })\n return () => {\n active = false\n }\n }, [value?.email, user, hostId])\n\n // What the attached customer has to check in today (AGL-4043). The panel\n // asks on every customer, so the server brings the day in line only on a\n // day the window is already checking people in.\n const [due, setDue] = useState<{ dayKey: string; waiting: number; total: number } | null>(null)\n const [checkingIn, setCheckingIn] = useState(false)\n const contactId = value?.kind === 'contact' ? value.id : ''\n const refreshDue = useCallback(() => {\n if ((!value?.email && !contactId) || !user) return undefined\n let active = true\n void callPosOps<{ dayKey?: string; checkIns?: CheckInRow[] }>(user, 'pos-check-in', {\n hostId,\n action: 'customer',\n reconcile: 'if-used',\n ...(value?.email ? { email: value.email } : { contactId }),\n }).then((answer) => {\n if (!active || !answer.ok || !answer.body.dayKey) return\n const rows = answer.body.checkIns ?? []\n setDue({\n dayKey: answer.body.dayKey,\n waiting: rows.filter((row) => row.status === 'expected').length,\n total: rows.length,\n })\n })\n return () => {\n active = false\n }\n }, [value?.email, contactId, user, hostId])\n useEffect(() => {\n setDue(null)\n return refreshDue()\n }, [refreshDue])\n\n const pick = (customer: PosSelectedCustomer) => {\n onChange(customer)\n setText('')\n setResults([])\n }\n\n const typedEmail = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(text.trim()) ? text.trim().toLowerCase() : ''\n\n const create = async () => {\n setBusy(true)\n setError('')\n const answer = await callPosOps<{ customer?: PosSelectedCustomer }>(user, 'pos-customer', {\n hostId,\n action: 'create',\n ...draft,\n })\n setBusy(false)\n if (!answer.ok || !answer.body.customer) return setError(answer.body.error ?? 'Could not add the customer.')\n pick(answer.body.customer)\n setAdding(false)\n }\n\n if (value) {\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip\n label={value.name || value.email || 'Customer'}\n onDelete={() => onChange(null)}\n color=\"primary\"\n variant=\"outlined\"\n />\n {value.name && value.email ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {value.email}\n </Typography>\n ) : null}\n {stats ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {stats.orderCount\n ? `${stats.orderCount} past ${stats.orderCount === 1 ? 'order' : 'orders'} · ${posMoney(\n stats.lifetimeSpendCents,\n )} spent`\n : 'First order here'}\n </Typography>\n ) : null}\n {due?.waiting ? (\n <Button size=\"small\" variant=\"outlined\" onClick={() => setCheckingIn(true)}>\n {`Check in (${due.waiting})`}\n </Button>\n ) : null}\n {due ? (\n <PosCheckInZone\n hostId={hostId}\n orgId={undefined}\n contactId={contactId || null}\n email={value.email ? value.email.toLowerCase() : null}\n dayKey={due.dayKey}\n name={value.name || null}\n unitsDue={due.total}\n placement=\"register-customer\"\n />\n ) : null}\n <PosCheckInDialog\n open={checkingIn}\n hostId={hostId}\n {...(registerId ? { registerId } : {})}\n {...(cashierAssertion ? { cashierAssertion } : {})}\n initialCustomer={value.email ? { email: value.email } : { contactId: contactId || null }}\n onClose={() => {\n setCheckingIn(false)\n refreshDue()\n }}\n />\n </Stack>\n )\n }\n\n return (\n <Stack spacing={0.5}>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n size=\"small\"\n label=\"Customer\"\n placeholder={available ? 'Name, email or phone' : 'Email for the receipt'}\n value={text}\n onChange={(event) => setText(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !available && typedEmail) {\n pick({ kind: 'none', id: '', name: '', email: typedEmail, phone: null })\n }\n }}\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n onClick={() => {\n setDraft({ name: '', email: typedEmail, phone: '' })\n setError('')\n setAdding(true)\n }}\n >\n {'New customer'}\n </Button>\n </Stack>\n {results.length ? (\n <List dense disablePadding>\n {results.map((customer) => (\n <ListItemButton key={`${customer.kind}:${customer.id}`} onClick={() => pick(customer)}>\n <ListItemText\n primary={customer.name || customer.email || 'Unnamed'}\n secondary={[customer.email, customer.phone].filter(Boolean).join(' · ')}\n />\n </ListItemButton>\n ))}\n </List>\n ) : null}\n {!available && typedEmail ? (\n <Button size=\"small\" onClick={() => pick({ kind: 'none', id: '', name: '', email: typedEmail, phone: null })} sx={{ alignSelf: 'flex-start' }}>\n {`Use ${typedEmail}`}\n </Button>\n ) : null}\n <Dialog open={adding} onClose={() => setAdding(false)} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'New customer'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n autoFocus\n label=\"Name\"\n value={draft.name}\n onChange={(event) => setDraft({ ...draft, name: event.target.value })}\n />\n <TextField\n label=\"Email\"\n type=\"email\"\n value={draft.email}\n onChange={(event) => setDraft({ ...draft, email: event.target.value })}\n />\n <TextField\n label=\"Phone (optional)\"\n type=\"tel\"\n value={draft.phone}\n onChange={(event) => setDraft({ ...draft, phone: event.target.value })}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Adding a customer does not sign them up for marketing email.'}\n </Typography>\n {error ? <Alert severity=\"warning\">{error}</Alert> : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAdding(false)}>{'Cancel'}</Button>\n <Button variant=\"contained\" disabled={busy} onClick={create}>\n {'Add customer'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\n\nPosCustomerLookup.displayName = 'PosCustomerLookup'\n\nexport default PosCustomerLookup\n"],"names":["Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","List","ListItemButton","ListItemText","Stack","TextField","Typography","useUser","useCallback","useEffect","useRef","useState","posMoney","PosCheckInDialog","PosCheckInZone","callPosOps","SEARCH_DEBOUNCE_MS","PosCustomerLookup","props","hostId","value","onChange","registerId","cashierAssertion","data","user","text","setText","results","setResults","available","setAvailable","stats","setStats","adding","setAdding","draft","setDraft","name","email","phone","error","setError","busy","setBusy","latest","typed","trim","length","ticket","current","timer","window","setTimeout","action","then","answer","body","ok","customers","clearTimeout","active","orderCount","Number","lifetimeSpendCents","due","setDue","checkingIn","setCheckingIn","contactId","kind","id","refreshDue","undefined","reconcile","dayKey","rows","checkIns","waiting","filter","row","status","total","pick","customer","typedEmail","test","toLowerCase","create","direction","spacing","useFlexGap","sx","alignItems","flexWrap","label","onDelete","color","variant","size","onClick","orgId","unitsDue","placement","open","initialCustomer","onClose","placeholder","event","target","onKeyDown","key","flex","dense","disablePadding","map","primary","secondary","Boolean","join","alignSelf","maxWidth","fullWidth","pt","autoFocus","type","severity","disabled","displayName"],"mappings":"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"}
|