@teincfood/core 0.9.19 → 0.9.21
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/customer-display/driver.d.ts +26 -0
- package/dist/customer-display/driver.js +1 -0
- package/dist/customer-display/model.d.ts +85 -0
- package/dist/customer-display/model.js +48 -0
- package/dist/db/operations.d.ts +1 -0
- package/dist/db/operations.js +6 -0
- package/dist/domain/orders/service.d.ts +1 -0
- package/dist/hooks/delivery.hooks.d.ts +4 -0
- package/dist/hooks/delivery.hooks.js +16 -1
- package/dist/hooks/orders.hooks.d.ts +7 -2
- package/dist/hooks/orders.hooks.js +8 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/test-helpers/memory-driver.js +51 -14
- package/dist/utils/date-range.d.ts +17 -0
- package/dist/utils/date-range.js +45 -0
- package/dist/utils/formatDate.d.ts +13 -0
- package/dist/utils/formatDate.js +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CustomerDisplayModel } from "./model";
|
|
2
|
+
export interface CustomerDisplayCapabilities {
|
|
3
|
+
/** True when the platform exposes a usable secondary display mechanism. */
|
|
4
|
+
supported: boolean;
|
|
5
|
+
/** Number of monitors/displays the platform reports (1 = single screen). */
|
|
6
|
+
displays: number;
|
|
7
|
+
/** Human-readable reason when unsupported (for the settings UI). */
|
|
8
|
+
reason?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Platform-specific presentation of the customer display. The shared POS/model
|
|
12
|
+
* layer never knows whether a driver renders to a Tauri window, an Android
|
|
13
|
+
* `Presentation`, or a future serial customer pole. Drivers are responsible for
|
|
14
|
+
* their own lifecycle and hardware quirks.
|
|
15
|
+
*/
|
|
16
|
+
export interface CustomerDisplayDriver {
|
|
17
|
+
capabilities(): Promise<CustomerDisplayCapabilities>;
|
|
18
|
+
/** Open/show the secondary presentation. Idempotent. */
|
|
19
|
+
open(): Promise<void>;
|
|
20
|
+
/** Close/hide the secondary presentation. Idempotent. */
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
/** Push the latest model to the presentation (fire-and-forget). */
|
|
23
|
+
push(model: CustomerDisplayModel): void;
|
|
24
|
+
/** Subscribe to display topology changes (monitor add/remove). */
|
|
25
|
+
onCapabilitiesChanged(cb: (caps: CustomerDisplayCapabilities) => void): () => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer display model — a platform-agnostic snapshot of what the
|
|
3
|
+
* customer-facing screen should show. The merchant POS owns order/payment
|
|
4
|
+
* state; this module only *describes* that state so any presentation layer
|
|
5
|
+
* (Tauri second window, Android `Presentation`, future hardware) renders it
|
|
6
|
+
* identically. No hardware or framework details live here.
|
|
7
|
+
*/
|
|
8
|
+
export type CustomerDisplayPhase = "idle" | "ordering" | "payment" | "confirmation";
|
|
9
|
+
export type CustomerDisplayPaymentMethod = "cash" | "mobile_money" | "card" | "split" | "other";
|
|
10
|
+
export type CustomerDisplayPaymentStatus = "pending" | "processing" | "paid" | "failed";
|
|
11
|
+
export interface CustomerDisplayLineItem {
|
|
12
|
+
/** Stable key for list rendering. */
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
variant?: string;
|
|
16
|
+
addons: string[];
|
|
17
|
+
quantity: number;
|
|
18
|
+
unitPriceMinor: number;
|
|
19
|
+
lineTotalMinor: number;
|
|
20
|
+
}
|
|
21
|
+
export interface CustomerDisplayTaxLine {
|
|
22
|
+
id?: string;
|
|
23
|
+
name: string;
|
|
24
|
+
amountMinor: number;
|
|
25
|
+
inclusive?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface CustomerDisplayPayment {
|
|
28
|
+
method: CustomerDisplayPaymentMethod;
|
|
29
|
+
status: CustomerDisplayPaymentStatus;
|
|
30
|
+
/** Optional hosted-checkout / QR URL the customer can scan. */
|
|
31
|
+
qrUrl?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface CustomerDisplayConfirmation {
|
|
34
|
+
orderNumber: string;
|
|
35
|
+
totalMinor: number;
|
|
36
|
+
currency: string;
|
|
37
|
+
/** Epoch ms; used to auto-dismiss back to idle. */
|
|
38
|
+
at: number;
|
|
39
|
+
}
|
|
40
|
+
export interface CustomerDisplayBranding {
|
|
41
|
+
name: string;
|
|
42
|
+
logoUrl?: string | null;
|
|
43
|
+
/** Optional promo / tagline shown on the idle screen. */
|
|
44
|
+
tagline?: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface CustomerDisplayModel {
|
|
47
|
+
phase: CustomerDisplayPhase;
|
|
48
|
+
branding: CustomerDisplayBranding | null;
|
|
49
|
+
currency: string;
|
|
50
|
+
items: CustomerDisplayLineItem[];
|
|
51
|
+
itemCount: number;
|
|
52
|
+
subtotalMinor: number;
|
|
53
|
+
taxLines: CustomerDisplayTaxLine[];
|
|
54
|
+
discountMinor: number;
|
|
55
|
+
totalMinor: number;
|
|
56
|
+
customerName?: string;
|
|
57
|
+
orderType?: string;
|
|
58
|
+
payment?: CustomerDisplayPayment;
|
|
59
|
+
confirmation?: CustomerDisplayConfirmation;
|
|
60
|
+
updatedAt: number;
|
|
61
|
+
}
|
|
62
|
+
export interface CustomerDisplayInput {
|
|
63
|
+
branding: CustomerDisplayBranding | null;
|
|
64
|
+
currency: string;
|
|
65
|
+
items: CustomerDisplayLineItem[];
|
|
66
|
+
subtotalMinor: number;
|
|
67
|
+
taxLines: CustomerDisplayTaxLine[];
|
|
68
|
+
discountMinor: number;
|
|
69
|
+
totalMinor: number;
|
|
70
|
+
customerName?: string;
|
|
71
|
+
orderType?: string;
|
|
72
|
+
/** Runtime overrides from the POS payment flow. */
|
|
73
|
+
phase?: CustomerDisplayPhase;
|
|
74
|
+
payment?: CustomerDisplayPayment;
|
|
75
|
+
confirmation?: CustomerDisplayConfirmation;
|
|
76
|
+
now?: number;
|
|
77
|
+
}
|
|
78
|
+
export declare const EMPTY_CUSTOMER_DISPLAY_MODEL: CustomerDisplayModel;
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the display phase from order + runtime state. Explicit runtime phase
|
|
81
|
+
* (set by the POS flow) wins; otherwise the presence of items implies ordering.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolvePhase(input: CustomerDisplayInput): CustomerDisplayPhase;
|
|
84
|
+
/** Pure builder — deterministic and safe to unit-test without any hardware. */
|
|
85
|
+
export declare function buildCustomerDisplayModel(input: CustomerDisplayInput): CustomerDisplayModel;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer display model — a platform-agnostic snapshot of what the
|
|
3
|
+
* customer-facing screen should show. The merchant POS owns order/payment
|
|
4
|
+
* state; this module only *describes* that state so any presentation layer
|
|
5
|
+
* (Tauri second window, Android `Presentation`, future hardware) renders it
|
|
6
|
+
* identically. No hardware or framework details live here.
|
|
7
|
+
*/
|
|
8
|
+
export const EMPTY_CUSTOMER_DISPLAY_MODEL = {
|
|
9
|
+
phase: "idle",
|
|
10
|
+
branding: null,
|
|
11
|
+
currency: "GHS",
|
|
12
|
+
items: [],
|
|
13
|
+
itemCount: 0,
|
|
14
|
+
subtotalMinor: 0,
|
|
15
|
+
taxLines: [],
|
|
16
|
+
discountMinor: 0,
|
|
17
|
+
totalMinor: 0,
|
|
18
|
+
updatedAt: 0,
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the display phase from order + runtime state. Explicit runtime phase
|
|
22
|
+
* (set by the POS flow) wins; otherwise the presence of items implies ordering.
|
|
23
|
+
*/
|
|
24
|
+
export function resolvePhase(input) {
|
|
25
|
+
if (input.phase)
|
|
26
|
+
return input.phase;
|
|
27
|
+
return input.items.length > 0 ? "ordering" : "idle";
|
|
28
|
+
}
|
|
29
|
+
/** Pure builder — deterministic and safe to unit-test without any hardware. */
|
|
30
|
+
export function buildCustomerDisplayModel(input) {
|
|
31
|
+
const itemCount = input.items.reduce((sum, item) => sum + item.quantity, 0);
|
|
32
|
+
return {
|
|
33
|
+
phase: resolvePhase(input),
|
|
34
|
+
branding: input.branding,
|
|
35
|
+
currency: input.currency,
|
|
36
|
+
items: input.items,
|
|
37
|
+
itemCount,
|
|
38
|
+
subtotalMinor: input.subtotalMinor,
|
|
39
|
+
taxLines: input.taxLines,
|
|
40
|
+
discountMinor: input.discountMinor,
|
|
41
|
+
totalMinor: input.totalMinor,
|
|
42
|
+
customerName: input.customerName,
|
|
43
|
+
orderType: input.orderType,
|
|
44
|
+
payment: input.payment,
|
|
45
|
+
confirmation: input.confirmation,
|
|
46
|
+
updatedAt: input.now ?? Date.now(),
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/db/operations.d.ts
CHANGED
|
@@ -81,6 +81,7 @@ export declare function listOrders(db: SQLiteDatabase, businessId: string, filte
|
|
|
81
81
|
order_type?: string;
|
|
82
82
|
date_from?: string;
|
|
83
83
|
date_to?: string;
|
|
84
|
+
is_paid?: string;
|
|
84
85
|
}, page?: number, pageSize?: number): Promise<{
|
|
85
86
|
data: import("../types/api/seller.api").SellerOrder[];
|
|
86
87
|
total: number;
|
package/dist/db/operations.js
CHANGED
|
@@ -263,6 +263,12 @@ export async function listOrders(db, businessId, filters, page = 1, pageSize = 2
|
|
|
263
263
|
where.push("order_type = ?");
|
|
264
264
|
args.push(filters.order_type);
|
|
265
265
|
}
|
|
266
|
+
if (filters?.is_paid === "paid") {
|
|
267
|
+
where.push("json_extract(payment, '$.is_paid') = 1");
|
|
268
|
+
}
|
|
269
|
+
else if (filters?.is_paid === "unpaid") {
|
|
270
|
+
where.push("(json_extract(payment, '$.is_paid') IS NULL OR json_extract(payment, '$.is_paid') = 0)");
|
|
271
|
+
}
|
|
266
272
|
if (filters?.q) {
|
|
267
273
|
where.push("(order_number LIKE ? OR customer_name LIKE ? OR customer_phone LIKE ?)");
|
|
268
274
|
const q = `%${filters.q}%`;
|
|
@@ -17,6 +17,7 @@ export declare function listLocalOrders(businessId: string, filters?: {
|
|
|
17
17
|
order_type?: string;
|
|
18
18
|
date_from?: string;
|
|
19
19
|
date_to?: string;
|
|
20
|
+
is_paid?: string;
|
|
20
21
|
}, page?: number, pageSize?: number): Promise<{
|
|
21
22
|
data: import("../../types/api/seller.api").SellerOrder[];
|
|
22
23
|
total: number;
|
|
@@ -17,6 +17,10 @@ export declare function useDeliveryDetailQuery(deliveryId: string, enabled?: boo
|
|
|
17
17
|
export interface DeliveryFilters {
|
|
18
18
|
status?: string;
|
|
19
19
|
search?: string;
|
|
20
|
+
order_type?: string;
|
|
21
|
+
is_paid?: string;
|
|
22
|
+
date_from?: string;
|
|
23
|
+
date_to?: string;
|
|
20
24
|
}
|
|
21
25
|
export declare function useBusinessDeliveriesQuery(enabled?: boolean, filters?: DeliveryFilters): {
|
|
22
26
|
dataUpdatedAt: number;
|
|
@@ -34,7 +34,14 @@ function useBusinessId() {
|
|
|
34
34
|
return id ?? undefined;
|
|
35
35
|
}
|
|
36
36
|
async function fetchOfflineDeliveries(businessId, filters, page, pageSize) {
|
|
37
|
-
const { data } = await listLocalOrders(businessId, {
|
|
37
|
+
const { data } = await listLocalOrders(businessId, {
|
|
38
|
+
status: undefined,
|
|
39
|
+
q: filters?.search,
|
|
40
|
+
order_type: filters?.order_type,
|
|
41
|
+
is_paid: filters?.is_paid,
|
|
42
|
+
date_from: filters?.date_from,
|
|
43
|
+
date_to: filters?.date_to,
|
|
44
|
+
}, page, pageSize);
|
|
38
45
|
const deliveryOrders = data.filter((o) => o.delivery_id);
|
|
39
46
|
const statusSet = new Set((filters?.status ?? "").split(",").map((s) => s.trim()).filter(Boolean));
|
|
40
47
|
const filtered = statusSet.size > 0
|
|
@@ -73,6 +80,14 @@ export function useBusinessDeliveriesQuery(enabled = true, filters) {
|
|
|
73
80
|
params.status = filters.status;
|
|
74
81
|
if (filters?.search)
|
|
75
82
|
params.search = filters.search;
|
|
83
|
+
if (filters?.order_type)
|
|
84
|
+
params.order_type = filters.order_type;
|
|
85
|
+
if (filters?.is_paid)
|
|
86
|
+
params.payment_status = filters.is_paid;
|
|
87
|
+
if (filters?.date_from)
|
|
88
|
+
params.date_from = filters.date_from;
|
|
89
|
+
if (filters?.date_to)
|
|
90
|
+
params.date_to = filters.date_to;
|
|
76
91
|
const res = (await deliveryService.fetchBusinessDeliveries(businessId, params));
|
|
77
92
|
return res;
|
|
78
93
|
},
|
|
@@ -12,11 +12,16 @@ export declare const SELLER_KEYS: {
|
|
|
12
12
|
order: (id: string, bid?: string) => readonly ["seller", "order", string, string | undefined];
|
|
13
13
|
};
|
|
14
14
|
export declare function usePOSOrdersQuery(filters?: Record<string, string>, enabled?: boolean): import("@tanstack/react-query").UseInfiniteQueryResult<SellerOrder[], Error>;
|
|
15
|
-
export
|
|
15
|
+
export interface SellerOrderFilters {
|
|
16
16
|
status?: string;
|
|
17
17
|
q?: string;
|
|
18
18
|
search?: string;
|
|
19
|
-
|
|
19
|
+
order_type?: string;
|
|
20
|
+
is_paid?: string;
|
|
21
|
+
date_from?: string;
|
|
22
|
+
date_to?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function useSellerOrdersQuery(enabled?: boolean, filters?: SellerOrderFilters): import("@tanstack/react-query").UseInfiniteQueryResult<SellerOrder[], Error>;
|
|
20
25
|
export declare function useSellerOrderQuery(orderId: string, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<{
|
|
21
26
|
data: SellerOrder;
|
|
22
27
|
}, Error>;
|
|
@@ -58,7 +58,14 @@ export function useSellerOrdersQuery(enabled = true, filters) {
|
|
|
58
58
|
if (!bid)
|
|
59
59
|
return { data: [], pagination: { page: 1, page_size: 20, total_pages: 1, total_entries: 0 } };
|
|
60
60
|
const q = filters?.q ?? filters?.search;
|
|
61
|
-
const { data, total } = await listLocalOrders(bid, {
|
|
61
|
+
const { data, total } = await listLocalOrders(bid, {
|
|
62
|
+
status: filters?.status,
|
|
63
|
+
q,
|
|
64
|
+
order_type: filters?.order_type,
|
|
65
|
+
is_paid: filters?.is_paid,
|
|
66
|
+
date_from: filters?.date_from,
|
|
67
|
+
date_to: filters?.date_to,
|
|
68
|
+
}, pageParam, 20);
|
|
62
69
|
const merged = await mergePendingIntoList(data, bid, pageParam);
|
|
63
70
|
const totalPages = Math.max(1, Math.ceil(total / 20));
|
|
64
71
|
return { data: merged, pagination: { page: pageParam, page_size: 20, total_pages: totalPages, total_entries: total + (merged.length - data.length) } };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export * from "./types/pos.types";
|
|
|
9
9
|
export * from "./types/rider";
|
|
10
10
|
export * from "./types/places";
|
|
11
11
|
export * from "./adapters/notify";
|
|
12
|
+
export * from "./customer-display/model";
|
|
13
|
+
export type { CustomerDisplayDriver, CustomerDisplayCapabilities } from "./customer-display/driver";
|
|
12
14
|
export { calculatePricing, calculateTaxLines, calculateExclusiveTax, calculateInclusiveTaxShare, } from "./pricing/engine";
|
|
13
15
|
export type { PricingInput, PricingResult } from "./pricing/engine";
|
|
14
16
|
export * from "./utils/constants";
|
|
@@ -21,7 +23,9 @@ export * from "./utils/attempt-stats";
|
|
|
21
23
|
export { PHONE_COUNTRIES, getDialCodeFromCountry, getCountryOption, normalizePhoneForCountry, isLikelyE164 } from "./utils/phone";
|
|
22
24
|
export type { PhoneCountryOption } from "./utils/phone";
|
|
23
25
|
export * from "./utils/formatCurrency";
|
|
24
|
-
export { formatDate, formatRelativeTime } from "./utils/formatDate";
|
|
26
|
+
export { formatDate, formatDateTime, formatTime, formatDateCompact, formatRelativeTime } from "./utils/formatDate";
|
|
27
|
+
export { resolveDateRange, DATE_PRESET_LABELS } from "./utils/date-range";
|
|
28
|
+
export type { DatePreset, DateRange } from "./utils/date-range";
|
|
25
29
|
export * from "./utils/validation";
|
|
26
30
|
export * from "./validation/offline";
|
|
27
31
|
export * from "./utils/copyToClipBoard";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@ export * from "./types/pos.types";
|
|
|
10
10
|
export * from "./types/rider";
|
|
11
11
|
export * from "./types/places";
|
|
12
12
|
export * from "./adapters/notify";
|
|
13
|
+
// ── Customer display (shared model + platform driver contract) ──
|
|
14
|
+
export * from "./customer-display/model";
|
|
13
15
|
// ── Pricing (server-parity) ──
|
|
14
16
|
export { calculatePricing, calculateTaxLines, calculateExclusiveTax, calculateInclusiveTaxShare, } from "./pricing/engine";
|
|
15
17
|
// ── Utils ──
|
|
@@ -22,7 +24,8 @@ export * from "./utils/debug-log";
|
|
|
22
24
|
export * from "./utils/attempt-stats";
|
|
23
25
|
export { PHONE_COUNTRIES, getDialCodeFromCountry, getCountryOption, normalizePhoneForCountry, isLikelyE164 } from "./utils/phone";
|
|
24
26
|
export * from "./utils/formatCurrency";
|
|
25
|
-
export { formatDate, formatRelativeTime } from "./utils/formatDate";
|
|
27
|
+
export { formatDate, formatDateTime, formatTime, formatDateCompact, formatRelativeTime } from "./utils/formatDate";
|
|
28
|
+
export { resolveDateRange, DATE_PRESET_LABELS } from "./utils/date-range";
|
|
26
29
|
export * from "./utils/validation";
|
|
27
30
|
export * from "./validation/offline";
|
|
28
31
|
export * from "./utils/copyToClipBoard";
|
|
@@ -410,24 +410,61 @@ export function createMemoryDriver() {
|
|
|
410
410
|
rows = rows.slice(0, limit);
|
|
411
411
|
return rows;
|
|
412
412
|
}
|
|
413
|
-
// orders list with filters (
|
|
413
|
+
// orders list with filters (business_id + status/order_type/is_paid/q/date)
|
|
414
414
|
if (s.includes("SELECT * FROM orders WHERE")) {
|
|
415
415
|
const business_id = args[0];
|
|
416
|
-
// args includes where-clause params plus LIMIT and OFFSET at end
|
|
417
|
-
// For test, just filter by business_id and apply basic status/q if present in sql string via args slicing?
|
|
418
|
-
// Simpler: return all orders for that business, sorted by inserted_at desc, handle LIMIT/OFFSET manually
|
|
419
416
|
let rows = tables.orders.filter((r) => r.business_id === business_id);
|
|
420
|
-
//
|
|
417
|
+
// Consume filter args in the exact clause order produced by listOrders.
|
|
418
|
+
let i = 1;
|
|
421
419
|
if (s.includes("status IN")) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
420
|
+
const n = (s.match(/status IN \([^)]*\)/)[0].match(/\?/g) || []).length;
|
|
421
|
+
const statuses = args.slice(i, i + n);
|
|
422
|
+
i += n;
|
|
423
|
+
if (statuses.length)
|
|
424
|
+
rows = rows.filter((r) => statuses.includes(r.status));
|
|
425
|
+
}
|
|
426
|
+
if (s.includes("is_pos = ?")) {
|
|
427
|
+
const v = Number(args[i++]) === 1;
|
|
428
|
+
rows = rows.filter((r) => !!r.is_pos === v);
|
|
429
|
+
}
|
|
430
|
+
if (s.includes("order_type = ?")) {
|
|
431
|
+
const t = args[i++];
|
|
432
|
+
rows = rows.filter((r) => r.order_type === t);
|
|
433
|
+
}
|
|
434
|
+
if (s.includes("json_extract(payment, '$.is_paid') = 1")) {
|
|
435
|
+
rows = rows.filter((r) => {
|
|
436
|
+
try {
|
|
437
|
+
return JSON.parse(r.payment ?? "{}").is_paid === true;
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
else if (s.includes("json_extract(payment, '$.is_paid') IS NULL")) {
|
|
445
|
+
rows = rows.filter((r) => {
|
|
446
|
+
try {
|
|
447
|
+
return JSON.parse(r.payment ?? "{}").is_paid !== true;
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (s.includes("order_number LIKE ?")) {
|
|
455
|
+
const term = String(args[i]).replace(/%/g, "").toLowerCase();
|
|
456
|
+
i += 3;
|
|
457
|
+
rows = rows.filter((r) => String(r.order_number ?? "").toLowerCase().includes(term) ||
|
|
458
|
+
String(r.customer_name ?? "").toLowerCase().includes(term) ||
|
|
459
|
+
String(r.customer_phone ?? "").toLowerCase().includes(term));
|
|
460
|
+
}
|
|
461
|
+
if (s.includes("inserted_at >= ?")) {
|
|
462
|
+
const from = String(args[i++]);
|
|
463
|
+
rows = rows.filter((r) => String(r.inserted_at) >= from);
|
|
464
|
+
}
|
|
465
|
+
if (s.includes("inserted_at < ?")) {
|
|
466
|
+
const to = String(args[i++]);
|
|
467
|
+
rows = rows.filter((r) => String(r.inserted_at) < to);
|
|
431
468
|
}
|
|
432
469
|
rows = rows.sort((a, b) => (a.inserted_at < b.inserted_at ? 1 : -1));
|
|
433
470
|
const limit = args[args.length - 2];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date-range presets for order/delivery filters.
|
|
3
|
+
*
|
|
4
|
+
* Returns YYYY-MM-DD strings (local day boundaries) that both the backend
|
|
5
|
+
* (normalizes to UTC day bounds) and the local SQLite read-model
|
|
6
|
+
* (`inserted_at >= from` / `inserted_at < `${to}T23:59:59``) accept.
|
|
7
|
+
*/
|
|
8
|
+
export type DatePreset = "all" | "today" | "yesterday" | "last7" | "custom";
|
|
9
|
+
export interface DateRange {
|
|
10
|
+
date_from?: string;
|
|
11
|
+
date_to?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveDateRange(preset: DatePreset | string | undefined, customFrom?: string, customTo?: string): DateRange;
|
|
14
|
+
export declare const DATE_PRESET_LABELS: Array<{
|
|
15
|
+
key: DatePreset;
|
|
16
|
+
label: string;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date-range presets for order/delivery filters.
|
|
3
|
+
*
|
|
4
|
+
* Returns YYYY-MM-DD strings (local day boundaries) that both the backend
|
|
5
|
+
* (normalizes to UTC day bounds) and the local SQLite read-model
|
|
6
|
+
* (`inserted_at >= from` / `inserted_at < `${to}T23:59:59``) accept.
|
|
7
|
+
*/
|
|
8
|
+
function toISODate(d) {
|
|
9
|
+
const y = d.getFullYear();
|
|
10
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
11
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
12
|
+
return `${y}-${m}-${day}`;
|
|
13
|
+
}
|
|
14
|
+
function addDays(base, days) {
|
|
15
|
+
const d = new Date(base);
|
|
16
|
+
d.setDate(d.getDate() + days);
|
|
17
|
+
return d;
|
|
18
|
+
}
|
|
19
|
+
export function resolveDateRange(preset, customFrom, customTo) {
|
|
20
|
+
const today = new Date();
|
|
21
|
+
switch (preset) {
|
|
22
|
+
case "today":
|
|
23
|
+
return { date_from: toISODate(today), date_to: toISODate(today) };
|
|
24
|
+
case "yesterday": {
|
|
25
|
+
const y = toISODate(addDays(today, -1));
|
|
26
|
+
return { date_from: y, date_to: y };
|
|
27
|
+
}
|
|
28
|
+
case "last7":
|
|
29
|
+
return { date_from: toISODate(addDays(today, -6)), date_to: toISODate(today) };
|
|
30
|
+
case "custom":
|
|
31
|
+
return {
|
|
32
|
+
...(customFrom ? { date_from: customFrom } : {}),
|
|
33
|
+
...(customTo ? { date_to: customTo } : {}),
|
|
34
|
+
};
|
|
35
|
+
default:
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export const DATE_PRESET_LABELS = [
|
|
40
|
+
{ key: "all", label: "All time" },
|
|
41
|
+
{ key: "today", label: "Today" },
|
|
42
|
+
{ key: "yesterday", label: "Yesterday" },
|
|
43
|
+
{ key: "last7", label: "Last 7 days" },
|
|
44
|
+
{ key: "custom", label: "Custom" },
|
|
45
|
+
];
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format the time-of-day of an ISO date string (e.g. "2:30 PM").
|
|
3
|
+
*/
|
|
4
|
+
export declare function formatTime(isoString: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Compact date (e.g. "26 Sep 2026") for list cards where the year may be
|
|
7
|
+
* implied but is still useful for older orders.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatDateCompact(isoString: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Date + time for order/delivery cards: "26 Sep 2026, 2:30 PM".
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatDateTime(isoString: string): string;
|
|
1
14
|
/**
|
|
2
15
|
* Format an ISO date string into a human-readable format.
|
|
3
16
|
*/
|
package/dist/utils/formatDate.js
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format the time-of-day of an ISO date string (e.g. "2:30 PM").
|
|
3
|
+
*/
|
|
4
|
+
export function formatTime(isoString) {
|
|
5
|
+
const date = new Date(isoString);
|
|
6
|
+
return date.toLocaleTimeString('en-US', {
|
|
7
|
+
hour: 'numeric',
|
|
8
|
+
minute: '2-digit',
|
|
9
|
+
hour12: true,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compact date (e.g. "26 Sep 2026") for list cards where the year may be
|
|
14
|
+
* implied but is still useful for older orders.
|
|
15
|
+
*/
|
|
16
|
+
export function formatDateCompact(isoString) {
|
|
17
|
+
const date = new Date(isoString);
|
|
18
|
+
return date.toLocaleDateString('en-US', {
|
|
19
|
+
day: '2-digit',
|
|
20
|
+
month: 'short',
|
|
21
|
+
year: 'numeric',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Date + time for order/delivery cards: "26 Sep 2026, 2:30 PM".
|
|
26
|
+
*/
|
|
27
|
+
export function formatDateTime(isoString) {
|
|
28
|
+
return `${formatDateCompact(isoString)}, ${formatTime(isoString)}`;
|
|
29
|
+
}
|
|
1
30
|
/**
|
|
2
31
|
* Format an ISO date string into a human-readable format.
|
|
3
32
|
*/
|
package/package.json
CHANGED