@tallyui/components 3.0.0-next.1 → 3.0.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -6
- package/package.json +9 -9
- package/src/customer/customer-picker.tsx +1 -1
- package/src/product/product-stock-badge.tsx +1 -1
- package/src/sale/catalogue.tsx +1 -1
- package/src/sale/orders-list.tsx +2 -1
- package/src/sale/since-text.ts +7 -0
- package/src/sale/sync-status.tsx +7 -8
package/dist/index.d.ts
CHANGED
|
@@ -737,7 +737,7 @@ interface CustomerPickerProps {
|
|
|
737
737
|
create?: (input: CustomerInput) => Promise<Customer>;
|
|
738
738
|
selected: Customer | null;
|
|
739
739
|
onSelect: (customer: Customer | null) => void;
|
|
740
|
-
/** Called for errors the picker can't explain to the cashier, including ConnectorUnauthorizedError
|
|
740
|
+
/** Called for errors the picker can't explain to the cashier, including `ConnectorUnauthorizedError`: sign in again only when its `code` is `'unauthorized'` (401). `'forbidden'` (403) means signed in but not allowed; never sign out for it. */
|
|
741
741
|
onError?: (error: unknown) => void;
|
|
742
742
|
/** false disables search and create (online only in v1). */
|
|
743
743
|
online: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1734,7 +1734,7 @@ var STATUS_STYLES = {
|
|
|
1734
1734
|
};
|
|
1735
1735
|
function formatAsOf(iso) {
|
|
1736
1736
|
const date = new Date(iso);
|
|
1737
|
-
return date.toDateString() === (/* @__PURE__ */ new Date()).toDateString() ? date.toLocaleTimeString(void 0, { hour: "
|
|
1737
|
+
return date.toDateString() === (/* @__PURE__ */ new Date()).toDateString() ? date.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" }) : date.toLocaleDateString(void 0, { day: "numeric", month: "short" });
|
|
1738
1738
|
}
|
|
1739
1739
|
function ProductStockBadge({ doc, showQuantity = false, showAsOf = true, className, ...props }) {
|
|
1740
1740
|
const { status, quantity, asOf } = useProductStock(doc);
|
|
@@ -4229,7 +4229,7 @@ var STOCK_LABEL = {
|
|
|
4229
4229
|
unknown: "Unknown"
|
|
4230
4230
|
};
|
|
4231
4231
|
function formatStockSyncTime(time, locale, hour12, now = /* @__PURE__ */ new Date()) {
|
|
4232
|
-
const timeLabel = time.toLocaleTimeString(locale, { hour: "
|
|
4232
|
+
const timeLabel = time.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit", hour12 });
|
|
4233
4233
|
if (time.toDateString() === now.toDateString()) return timeLabel;
|
|
4234
4234
|
const dateLabel = time.toLocaleDateString(locale, { day: "numeric", month: "short" });
|
|
4235
4235
|
return `${dateLabel}, ${timeLabel}`;
|
|
@@ -4428,6 +4428,14 @@ function Receipt({
|
|
|
4428
4428
|
// src/sale/sync-status.tsx
|
|
4429
4429
|
import { useEffect as useEffect7, useRef as useRef6, useState as useState18 } from "react";
|
|
4430
4430
|
import { AccessibilityInfo as AccessibilityInfo2, Platform as Platform3, Text as Text38, View as View59 } from "react-native";
|
|
4431
|
+
|
|
4432
|
+
// src/sale/since-text.ts
|
|
4433
|
+
function sinceText(clock) {
|
|
4434
|
+
const time = (t) => new Date(t).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
|
|
4435
|
+
return clock.firstFailedAt === void 0 ? "about " + time(clock.since) : time(clock.firstFailedAt);
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
// src/sale/sync-status.tsx
|
|
4431
4439
|
import { Fragment as Fragment7, jsx as jsx93, jsxs as jsxs52 } from "react/jsx-runtime";
|
|
4432
4440
|
var PULL_NOTICE_TEXT = {
|
|
4433
4441
|
unauthorized: {
|
|
@@ -4467,7 +4475,7 @@ var BACKEND_MISSING_DETAIL = "This till couldn't find {pluginName} on the online
|
|
|
4467
4475
|
var REFUSED = (n, waiting) => n === 1 ? `1 sale needs attention${waiting} \xB7 The online store refused it. Ask the store owner to look at the till's sync log.` : `${n} sales need attention${waiting} \xB7 The online store refused them. Ask the store owner to look at the till's sync log.`;
|
|
4468
4476
|
var REFUSED_DETAIL = "Refused sales stay on this till under Needs attention, each with what to do next.";
|
|
4469
4477
|
var SALES_REFUSED = "The online store refused the last send. This till will try again with the next sale, or when the app is reopened.";
|
|
4470
|
-
var TILL_UPDATES_REFUSED = "The online store refused the last send. This till will try again with the next till update.";
|
|
4478
|
+
var TILL_UPDATES_REFUSED = "The online store refused the last send. This till will try again with the next till update, or when the app is reopened.";
|
|
4471
4479
|
var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
4472
4480
|
function useAnnounceOnIos(...texts) {
|
|
4473
4481
|
const announced = useRef6(texts);
|
|
@@ -4494,8 +4502,7 @@ function SyncStatus({ state, registerState, pluginName = "the POS plugin", pullN
|
|
|
4494
4502
|
].filter(Boolean).join(" and ") + " waiting to sync";
|
|
4495
4503
|
const { stuck } = state;
|
|
4496
4504
|
const backendMissing = state.backendMissing ?? registerState?.backendMissing;
|
|
4497
|
-
const
|
|
4498
|
-
const backendMissingText = state.pending === 0 && updates > 0 ? registerState?.stuck ? TILL_UPDATES_MISSING_SINCE.replace("{time}", () => at(registerState.stuck.since)) : TILL_UPDATES_MISSING : stuck ? BACKEND_MISSING_SINCE.replace("{time}", () => at(stuck.since)) : BACKEND_MISSING;
|
|
4505
|
+
const backendMissingText = state.pending === 0 && updates > 0 ? registerState?.stuck ? TILL_UPDATES_MISSING_SINCE.replace("{time}", () => sinceText(registerState.stuck)) : TILL_UPDATES_MISSING : stuck ? BACKEND_MISSING_SINCE.replace("{time}", () => sinceText(stuck)) : BACKEND_MISSING;
|
|
4499
4506
|
const upToDate = state.pending === 0 && updates === 0;
|
|
4500
4507
|
const sentence = outbox.refused ? ` \xB7 ${outbox === state ? SALES_REFUSED : TILL_UPDATES_REFUSED}` : backendMissing || (state.pending === 0 && updates > 0 ? registerState?.stuck : stuck) ? ` \xB7 ${backendMissingText}` : "";
|
|
4501
4508
|
const refused = state.rejected ? REFUSED(state.rejected, upToDate ? "" : `, ${updates ? label : `${state.pending} waiting to sync`}`) : "";
|
|
@@ -4573,7 +4580,7 @@ function OrdersList({ orders, onRetry, formatDate = defaultFormatDate2, footer,
|
|
|
4573
4580
|
" \xB7 ",
|
|
4574
4581
|
STATUS_LABEL[order.syncStatus]
|
|
4575
4582
|
] }),
|
|
4576
|
-
stuckEntry ? /* @__PURE__ */ jsx94(Text39, { className: "text-destructive", children: `Hasn't reached the online store since ${
|
|
4583
|
+
stuckEntry ? /* @__PURE__ */ jsx94(Text39, { className: "text-destructive", children: `Hasn't reached the online store since ${sinceText(stuckEntry)}.` }) : null,
|
|
4577
4584
|
order.syncStatus === "rejected" ? /* @__PURE__ */ jsx94(Text39, { className: "text-destructive", children: refusalSentence(order.error?.code) }) : null,
|
|
4578
4585
|
order.lateSessionId !== void 0 ? /* @__PURE__ */ jsx94(Text39, { className: "text-foreground", children: "Taken after the register closed. It is not in that register's closure." }) : null,
|
|
4579
4586
|
order.localWarnings?.map((warning, index) => /* @__PURE__ */ jsx94(Text39, { className: "text-foreground", children: warning.code === "customer_omitted" ? `The customer's ${warning.field} couldn't be sent to the store, so the order isn't linked to them.` : "The terminal's payment reference couldn't be kept; the payment is recorded without it." }, index)),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/components",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Universal POS UI components for Tally UI",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,22 +27,22 @@
|
|
|
27
27
|
"directory": "packages/components"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@tallyui/core": "^3.0.0-next.
|
|
31
|
-
"@tallyui/primitives": "^3.0.0-next.
|
|
32
|
-
"@tallyui/theme": "^3.0.0-next.
|
|
30
|
+
"@tallyui/core": "^3.0.0-next.2",
|
|
31
|
+
"@tallyui/primitives": "^3.0.0-next.2",
|
|
32
|
+
"@tallyui/theme": "^3.0.0-next.2",
|
|
33
33
|
"react": ">=18",
|
|
34
34
|
"react-native": ">=0.74",
|
|
35
35
|
"react-native-svg": ">=15"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"class-variance-authority": "^0.7.1",
|
|
39
|
-
"@tallyui/pos": "3.0.0-next.
|
|
39
|
+
"@tallyui/pos": "3.0.0-next.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@tallyui/connector-medusa": "3.0.0-next.
|
|
43
|
-
"@tallyui/connector-vendure": "3.0.0-next.
|
|
44
|
-
"@tallyui/connector-woocommerce": "3.0.0-next.
|
|
45
|
-
"@tallyui/mock-api": "3.0.0-next.
|
|
42
|
+
"@tallyui/connector-medusa": "3.0.0-next.2",
|
|
43
|
+
"@tallyui/connector-vendure": "3.0.0-next.2",
|
|
44
|
+
"@tallyui/connector-woocommerce": "3.0.0-next.2",
|
|
45
|
+
"@tallyui/mock-api": "3.0.0-next.2",
|
|
46
46
|
"@types/react": ">=18",
|
|
47
47
|
"react": "^19.2.4",
|
|
48
48
|
"react-native-svg": "15.12.1",
|
|
@@ -13,7 +13,7 @@ export interface CustomerPickerProps {
|
|
|
13
13
|
create?: (input: CustomerInput) => Promise<Customer>;
|
|
14
14
|
selected: Customer | null;
|
|
15
15
|
onSelect: (customer: Customer | null) => void;
|
|
16
|
-
/** Called for errors the picker can't explain to the cashier, including ConnectorUnauthorizedError
|
|
16
|
+
/** Called for errors the picker can't explain to the cashier, including `ConnectorUnauthorizedError`: sign in again only when its `code` is `'unauthorized'` (401). `'forbidden'` (403) means signed in but not allowed; never sign out for it. */
|
|
17
17
|
onError?: (error: unknown) => void;
|
|
18
18
|
/** false disables search and create (online only in v1). */
|
|
19
19
|
online: boolean;
|
|
@@ -49,7 +49,7 @@ const STATUS_STYLES: Record<StockStatus, { badge: string; dot: string; text: str
|
|
|
49
49
|
function formatAsOf(iso: string): string {
|
|
50
50
|
const date = new Date(iso);
|
|
51
51
|
return date.toDateString() === new Date().toDateString()
|
|
52
|
-
? date.toLocaleTimeString(undefined, { hour: '
|
|
52
|
+
? date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })
|
|
53
53
|
: date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' });
|
|
54
54
|
}
|
|
55
55
|
|
package/src/sale/catalogue.tsx
CHANGED
|
@@ -16,7 +16,7 @@ const STOCK_LABEL = {
|
|
|
16
16
|
// function) so the formatting is testable without the device; callers default them to the
|
|
17
17
|
// device's own settings and the current moment.
|
|
18
18
|
export function formatStockSyncTime(time: Date, locale?: string, hour12?: boolean, now: Date = new Date()): string {
|
|
19
|
-
const timeLabel = time.toLocaleTimeString(locale, { hour: '
|
|
19
|
+
const timeLabel = time.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit', hour12 });
|
|
20
20
|
// "Today" is decided in the device's own timezone: toDateString() reads the Date in local time,
|
|
21
21
|
// the same timezone the device clock and toLocaleTimeString above already use.
|
|
22
22
|
if (time.toDateString() === now.toDateString()) return timeLabel;
|
package/src/sale/orders-list.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState, type ReactNode } from 'react';
|
|
|
2
2
|
import { Pressable, ScrollView, Text, View } from 'react-native';
|
|
3
3
|
import { formatMoney, knownWarnings } from '@tallyui/core';
|
|
4
4
|
import { needsAttention, type OutboxState, type PosOrder } from '@tallyui/pos';
|
|
5
|
+
import { sinceText } from './since-text';
|
|
5
6
|
|
|
6
7
|
const STATUS_LABEL = { pending: 'Waiting to sync', applied: 'Synced', rejected: 'Not accepted' };
|
|
7
8
|
/** `figures_mismatch`'s labels; a field a newer store sends that isn't here is shown by its raw name. */
|
|
@@ -61,7 +62,7 @@ export function OrdersList({ orders, onRetry, formatDate = defaultFormatDate, fo
|
|
|
61
62
|
<Text className="text-muted-foreground">{formatDate(order.createdAt)} · {formatMoney({ amount: order.totalMinor, currency: order.currency })} · {STATUS_LABEL[order.syncStatus]}</Text>
|
|
62
63
|
{stuckEntry ? <Text className="text-destructive">
|
|
63
64
|
{/* The same words for any reason, with no reason code; the hour as the status line has it (numeric, #245). */}
|
|
64
|
-
{`Hasn't reached the online store since ${
|
|
65
|
+
{`Hasn't reached the online store since ${sinceText(stuckEntry)}.`}
|
|
65
66
|
</Text> : null}
|
|
66
67
|
{/* The code's sentence alone: the cashier sees no error code and never the store's message. */}
|
|
67
68
|
{order.syncStatus === 'rejected' ? <Text className="text-destructive">{refusalSentence(order.error?.code)}</Text> : null}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The time a stuck line names: the real first failure when this run has one, else "about" the virtual since (after a restart
|
|
2
|
+
// firstFailedAt is gone; see OutboxState.stuck).
|
|
3
|
+
export function sinceText(clock: { since: number; firstFailedAt?: number }): string {
|
|
4
|
+
// The device's own 12/24-hour format, with no forced leading zero on the hour.
|
|
5
|
+
const time = (t: number) => new Date(t).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
|
6
|
+
return clock.firstFailedAt === undefined ? 'about ' + time(clock.since) : time(clock.firstFailedAt);
|
|
7
|
+
}
|
package/src/sale/sync-status.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { AccessibilityInfo, Platform, Text, View } from 'react-native';
|
|
3
3
|
import type { SyncNotice } from '@tallyui/core';
|
|
4
4
|
import type { OutboxState } from '@tallyui/pos';
|
|
5
|
+
import { sinceText } from './since-text';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* What the cashier reads when the product pull stops, keyed by the notice's code; the code itself is never shown.
|
|
@@ -47,7 +48,7 @@ const PULL_NOTICE_FALLBACK: PullNoticeText = {
|
|
|
47
48
|
|
|
48
49
|
// The line the cashier reads while the store keeps answering 404 (OutboxState.backendMissing) and no order is stuck.
|
|
49
50
|
const BACKEND_MISSING = "Sales aren't reaching the online store. Keep selling: they're saved on this till and will send by themselves.";
|
|
50
|
-
// The same line once an order is stuck, with the store missing or not; {time}
|
|
51
|
+
// The same line once an order is stuck, with the store missing or not; {time} is the real first failure or about the virtual since.
|
|
51
52
|
const BACKEND_MISSING_SINCE = "Sales haven't reached the online store since {time}. Keep selling: they're saved on this till and will send by themselves.";
|
|
52
53
|
// The line instead when no sale waits but till updates do, and its "since" variant once registerState.stuck is set.
|
|
53
54
|
const TILL_UPDATES_MISSING = "Till updates aren't reaching the online store. Keep selling: they're saved on this till and will send by themselves.";
|
|
@@ -66,10 +67,10 @@ const REFUSED = (n: number, waiting: string) => n === 1
|
|
|
66
67
|
const REFUSED_DETAIL = 'Refused sales stay on this till under Needs attention, each with what to do next.';
|
|
67
68
|
// The sentence after the waiting count once the store refused a whole batch (OutboxState.refused) from the outbox shown, when
|
|
68
69
|
// no command carries a code. Neither outbox sets a timer then: a new pending command flushes its outbox. For sales,
|
|
69
|
-
// useOrderOutbox
|
|
70
|
-
//
|
|
70
|
+
// useOrderOutbox calls start() when its store opens and useRegisterOutbox when its collection opens, so both flush
|
|
71
|
+
// on reopen and both sentences name a reopen.
|
|
71
72
|
const SALES_REFUSED = 'The online store refused the last send. This till will try again with the next sale, or when the app is reopened.';
|
|
72
|
-
const TILL_UPDATES_REFUSED = 'The online store refused the last send. This till will try again with the next till update.';
|
|
73
|
+
const TILL_UPDATES_REFUSED = 'The online store refused the last send. This till will try again with the next till update, or when the app is reopened.';
|
|
73
74
|
|
|
74
75
|
const count = (n: number, noun: string) => `${n} ${noun}${n === 1 ? '' : 's'}`;
|
|
75
76
|
// iOS has no live region (accessibilityLiveRegion is Android-only), so there each text is announced when it changes: never on
|
|
@@ -103,11 +104,9 @@ export function SyncStatus({ state, registerState, pluginName = 'the POS plugin'
|
|
|
103
104
|
updates && count(updates, 'till update')].filter(Boolean).join(' and ') + ' waiting to sync';
|
|
104
105
|
const { stuck } = state;
|
|
105
106
|
const backendMissing = state.backendMissing ?? registerState?.backendMissing;
|
|
106
|
-
// The device's own 12/24-hour format, with no forced leading zero on the hour.
|
|
107
|
-
const at = (time: number) => new Date(time).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
|
108
107
|
const backendMissingText = state.pending === 0 && updates > 0 ? (registerState?.stuck
|
|
109
|
-
? TILL_UPDATES_MISSING_SINCE.replace('{time}', () =>
|
|
110
|
-
: stuck ? BACKEND_MISSING_SINCE.replace('{time}', () =>
|
|
108
|
+
? TILL_UPDATES_MISSING_SINCE.replace('{time}', () => sinceText(registerState.stuck!)) : TILL_UPDATES_MISSING)
|
|
109
|
+
: stuck ? BACKEND_MISSING_SINCE.replace('{time}', () => sinceText(stuck)) : BACKEND_MISSING;
|
|
111
110
|
const upToDate = state.pending === 0 && updates === 0;
|
|
112
111
|
// The sentence shows with the store missing, or once the waiting sales (with none waiting, the till updates) are stuck, then as
|
|
113
112
|
// its "since" variant: the same words for any reason, and no reason code.
|