@teincfood/core 0.7.6 → 0.7.8
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/adapters/business.d.ts +1 -3
- package/dist/adapters/business.js +3 -1
- package/dist/adapters/connectivity.d.ts +1 -1
- package/dist/adapters/connectivity.js +6 -3
- package/dist/adapters/filesystem.d.ts +22 -0
- package/dist/adapters/filesystem.js +6 -0
- package/dist/adapters/print.d.ts +29 -0
- package/dist/adapters/print.js +12 -0
- package/dist/cart/hooks.d.ts +36 -0
- package/dist/cart/hooks.js +45 -0
- package/dist/cart/store.d.ts +33 -0
- package/dist/cart/store.js +122 -0
- package/dist/catalog/hooks.d.ts +25 -0
- package/dist/catalog/hooks.js +160 -0
- package/dist/hooks/kiosk.hooks.d.ts +5 -0
- package/dist/hooks/kiosk.hooks.js +5 -0
- package/dist/hooks/orders.hooks.d.ts +16 -0
- package/dist/hooks/orders.hooks.js +93 -0
- package/dist/image-cache/service.d.ts +29 -0
- package/dist/image-cache/service.js +125 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.js +27 -2
- package/dist/printer/hooks.d.ts +32 -0
- package/dist/printer/hooks.js +53 -0
- package/dist/printer/receipt.d.ts +12 -0
- package/dist/printer/receipt.js +315 -0
- package/dist/printer/service.d.ts +31 -0
- package/dist/printer/service.js +119 -0
- package/dist/printer/store.d.ts +24 -0
- package/dist/printer/store.js +94 -0
- package/dist/printer/types.d.ts +12 -0
- package/dist/printer/types.js +1 -0
- package/dist/sync/command-queue.service.js +17 -12
- package/package.json +1 -1
|
@@ -7,9 +7,7 @@ export declare function setActiveBusiness(b: {
|
|
|
7
7
|
export declare function getActiveBusiness(): {
|
|
8
8
|
id: string;
|
|
9
9
|
} | null;
|
|
10
|
-
export declare function subscribeBusiness(listener: (
|
|
11
|
-
id: string;
|
|
12
|
-
} | null) => void): () => void;
|
|
10
|
+
export declare function subscribeBusiness(listener: () => void): () => void;
|
|
13
11
|
export declare const useBusinessStore: {
|
|
14
12
|
getState: () => {
|
|
15
13
|
activeBusiness: {
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
let activeBusiness = null;
|
|
5
5
|
const listeners = new Set();
|
|
6
6
|
export function setActiveBusiness(b) {
|
|
7
|
+
if ((b?.id ?? null) === (activeBusiness?.id ?? null))
|
|
8
|
+
return;
|
|
7
9
|
activeBusiness = b;
|
|
8
|
-
listeners.forEach((l) => l(
|
|
10
|
+
listeners.forEach((l) => l());
|
|
9
11
|
}
|
|
10
12
|
export function getActiveBusiness() {
|
|
11
13
|
return activeBusiness;
|
|
@@ -11,7 +11,7 @@ export declare function getConnectivityState(): ConnectivityState;
|
|
|
11
11
|
export declare function setConnectivityState(patch: Partial<ConnectivityState>): void;
|
|
12
12
|
export declare function setCloudReachable(value: CloudReachable): void;
|
|
13
13
|
export declare function setOnline(value: boolean): void;
|
|
14
|
-
export declare function subscribeConnectivity(listener: (
|
|
14
|
+
export declare function subscribeConnectivity(listener: () => void): () => void;
|
|
15
15
|
export declare const connectivityStoreShim: {
|
|
16
16
|
getState: typeof getConnectivityState;
|
|
17
17
|
};
|
|
@@ -8,11 +8,14 @@ let state = {
|
|
|
8
8
|
};
|
|
9
9
|
const listeners = new Set();
|
|
10
10
|
export function getConnectivityState() {
|
|
11
|
-
return
|
|
11
|
+
return state;
|
|
12
12
|
}
|
|
13
13
|
export function setConnectivityState(patch) {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const next = { ...state, ...patch };
|
|
15
|
+
if (next.isOnline === state.isOnline && next.isCloudReachable === state.isCloudReachable)
|
|
16
|
+
return;
|
|
17
|
+
state = next;
|
|
18
|
+
listeners.forEach((l) => l());
|
|
16
19
|
}
|
|
17
20
|
export function setCloudReachable(value) {
|
|
18
21
|
setConnectivityState({ isCloudReachable: value });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem adapter — host injects expo-file-system / Tauri fs
|
|
3
|
+
*/
|
|
4
|
+
export interface FileSystemAdapter {
|
|
5
|
+
getInfoAsync(path: string): Promise<{
|
|
6
|
+
exists: boolean;
|
|
7
|
+
isDirectory?: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
makeDirectoryAsync(path: string, opts?: {
|
|
10
|
+
intermediates?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
downloadAsync(url: string, fileUri: string): Promise<{
|
|
13
|
+
uri: string;
|
|
14
|
+
}>;
|
|
15
|
+
deleteAsync(path: string, opts?: {
|
|
16
|
+
idempotent?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
readDirectoryAsync(path: string): Promise<string[]>;
|
|
19
|
+
getDocumentDirectory(): string;
|
|
20
|
+
}
|
|
21
|
+
export declare function setFileSystemAdapter(a: FileSystemAdapter | null): void;
|
|
22
|
+
export declare function getFileSystemAdapter(): FileSystemAdapter | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print adapters — host injects platform-specific implementations.
|
|
3
|
+
*/
|
|
4
|
+
export interface TcpSocketAdapter {
|
|
5
|
+
sendToTcpPrinter(ip: string, port: number, text: string): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface PrintFileAdapter {
|
|
8
|
+
printToFileAsync(html: string): Promise<{
|
|
9
|
+
uri: string;
|
|
10
|
+
}>;
|
|
11
|
+
isSharingAvailable?(): Promise<boolean>;
|
|
12
|
+
shareAsync?(uri: string, options?: {
|
|
13
|
+
dialogTitle?: string;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface MdnsAdapter {
|
|
18
|
+
discoverPrinters?(timeoutMs?: number): Promise<{
|
|
19
|
+
name: string;
|
|
20
|
+
ip: string;
|
|
21
|
+
port: number;
|
|
22
|
+
}[]>;
|
|
23
|
+
}
|
|
24
|
+
export declare function setTcpAdapter(a: TcpSocketAdapter | null): void;
|
|
25
|
+
export declare function getTcpAdapter(): TcpSocketAdapter | null;
|
|
26
|
+
export declare function setPrintFileAdapter(a: PrintFileAdapter | null): void;
|
|
27
|
+
export declare function getPrintFileAdapter(): PrintFileAdapter | null;
|
|
28
|
+
export declare function setMdnsAdapter(a: MdnsAdapter | null): void;
|
|
29
|
+
export declare function getMdnsAdapter(): MdnsAdapter | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Print adapters — host injects platform-specific implementations.
|
|
3
|
+
*/
|
|
4
|
+
let tcpAdapter = null;
|
|
5
|
+
let printFileAdapter = null;
|
|
6
|
+
let mdnsAdapter = null;
|
|
7
|
+
export function setTcpAdapter(a) { tcpAdapter = a; }
|
|
8
|
+
export function getTcpAdapter() { return tcpAdapter; }
|
|
9
|
+
export function setPrintFileAdapter(a) { printFileAdapter = a; }
|
|
10
|
+
export function getPrintFileAdapter() { return printFileAdapter; }
|
|
11
|
+
export function setMdnsAdapter(a) { mdnsAdapter = a; }
|
|
12
|
+
export function getMdnsAdapter() { return mdnsAdapter; }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cart hooks — UI bindings for the device-local cart store + pricing.
|
|
3
|
+
*/
|
|
4
|
+
import { removeItem, updateQuantity, clearCart, clearAll } from "./store";
|
|
5
|
+
import type { MenuItem, MenuItemVariantResponse, MenuItemAddonResponse } from "../types/api/menu.api";
|
|
6
|
+
import type { POSCheckoutForm } from "../types/pos.types";
|
|
7
|
+
export declare function usePOSStore(): {
|
|
8
|
+
items: import("..").POSCartItem[];
|
|
9
|
+
checkoutForm: POSCheckoutForm;
|
|
10
|
+
addItem: (menuItem: MenuItem, quantity: number, variant: MenuItemVariantResponse | null, addons: MenuItemAddonResponse[]) => void;
|
|
11
|
+
removeItem: typeof removeItem;
|
|
12
|
+
updateQuantity: typeof updateQuantity;
|
|
13
|
+
updateCheckoutForm: (u: Partial<POSCheckoutForm>) => void;
|
|
14
|
+
clearCart: typeof clearCart;
|
|
15
|
+
clearAll: typeof clearAll;
|
|
16
|
+
};
|
|
17
|
+
export declare function usePOSCart(): {
|
|
18
|
+
items: import("..").POSCartItem[];
|
|
19
|
+
checkoutForm: POSCheckoutForm;
|
|
20
|
+
addItem: (menuItem: MenuItem, quantity: number, variant: MenuItemVariantResponse | null, addons: MenuItemAddonResponse[]) => void;
|
|
21
|
+
removeItem: typeof removeItem;
|
|
22
|
+
updateQuantity: typeof updateQuantity;
|
|
23
|
+
updateCheckoutForm: (u: Partial<POSCheckoutForm>) => void;
|
|
24
|
+
clearCart: typeof clearCart;
|
|
25
|
+
clearAll: typeof clearAll;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Pricing hook — returns local calculation (always available offline) and
|
|
29
|
+
* optional cloud preview; mirrors CheckoutPanel calculatePricing logic.
|
|
30
|
+
*/
|
|
31
|
+
export declare function usePOSPricing(): {
|
|
32
|
+
cart: import("./store").CartState;
|
|
33
|
+
localPricing: import("..").PricingResult | null;
|
|
34
|
+
preview: import("@tanstack/react-query").UseQueryResult<import("..").POSOrderPreviewResponse, Error>;
|
|
35
|
+
currency: string;
|
|
36
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cart hooks — UI bindings for the device-local cart store + pricing.
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
|
5
|
+
import { getCartSnapshot, subscribeCart, hydrateCartStore, addItem, removeItem, updateQuantity, updateCheckoutForm, clearCart, clearAll } from "./store";
|
|
6
|
+
import { getActiveBusiness, subscribeBusiness } from "../adapters/business";
|
|
7
|
+
import { usePOSOrderPreviewQuery } from "../hooks/orders.hooks";
|
|
8
|
+
import { calculatePricing } from "../pricing/engine";
|
|
9
|
+
export function usePOSStore() {
|
|
10
|
+
const state = useSyncExternalStore(subscribeCart, getCartSnapshot, getCartSnapshot);
|
|
11
|
+
useEffect(() => { void hydrateCartStore(); }, []);
|
|
12
|
+
return {
|
|
13
|
+
items: state.items,
|
|
14
|
+
checkoutForm: state.checkoutForm,
|
|
15
|
+
addItem: (menuItem, quantity, variant, addons) => addItem(menuItem, quantity, variant, addons),
|
|
16
|
+
removeItem,
|
|
17
|
+
updateQuantity,
|
|
18
|
+
updateCheckoutForm: (u) => updateCheckoutForm(u),
|
|
19
|
+
clearCart,
|
|
20
|
+
clearAll,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function usePOSCart() { return usePOSStore(); }
|
|
24
|
+
/**
|
|
25
|
+
* Pricing hook — returns local calculation (always available offline) and
|
|
26
|
+
* optional cloud preview; mirrors CheckoutPanel calculatePricing logic.
|
|
27
|
+
*/
|
|
28
|
+
export function usePOSPricing() {
|
|
29
|
+
const cart = useSyncExternalStore(subscribeCart, getCartSnapshot, getCartSnapshot);
|
|
30
|
+
const business = useSyncExternalStore(subscribeBusiness, () => getActiveBusiness(), () => null);
|
|
31
|
+
const currency = business?.currency_code ?? "GHS";
|
|
32
|
+
// Need tax rules — pull from reference_data via lazy import to avoid cycle
|
|
33
|
+
// For now, calculate without taxRules; host can pass taxRules if needed.
|
|
34
|
+
const localPricing = useMemo(() => {
|
|
35
|
+
try {
|
|
36
|
+
return calculatePricing({ items: cart.items, taxRules: [], currency_code: currency });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}, [cart.items, currency]);
|
|
42
|
+
// Cloud preview still available via dedicated hook
|
|
43
|
+
const preview = usePOSOrderPreviewQuery(cart.items, cart.checkoutForm.order_type, cart.checkoutForm.customer_name);
|
|
44
|
+
return { cart, localPricing, preview, currency };
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS cart store — device-local, persisted via KV driver.
|
|
3
|
+
* Mirrors TeincFoodBusiness/src/stores/pos.store.ts
|
|
4
|
+
* Persist key: "Teinc Food-pos-cart" (for migration compatibility).
|
|
5
|
+
*/
|
|
6
|
+
import type { MenuItem, MenuItemVariantResponse, MenuItemAddonResponse } from "../types/api/menu.api";
|
|
7
|
+
import type { POSCartItem, POSCheckoutForm } from "../types/pos.types";
|
|
8
|
+
export interface CartState {
|
|
9
|
+
items: POSCartItem[];
|
|
10
|
+
checkoutForm: POSCheckoutForm;
|
|
11
|
+
}
|
|
12
|
+
export declare function hydrateCartStore(): Promise<void>;
|
|
13
|
+
export declare function getCartState(): CartState;
|
|
14
|
+
export declare function subscribeCart(listener: () => void): () => void;
|
|
15
|
+
export declare function getCartSnapshot(): CartState;
|
|
16
|
+
export declare function addItem(menuItem: MenuItem, quantity: number, variant: MenuItemVariantResponse | null, addons: MenuItemAddonResponse[]): void;
|
|
17
|
+
export declare function removeItem(cartItemId: string): void;
|
|
18
|
+
export declare function updateQuantity(cartItemId: string, quantity: number): void;
|
|
19
|
+
export declare function updateCheckoutForm(updates: Partial<POSCheckoutForm>): void;
|
|
20
|
+
export declare function clearCart(): void;
|
|
21
|
+
export declare function clearAll(): void;
|
|
22
|
+
export declare const cartStore: {
|
|
23
|
+
getState: typeof getCartState;
|
|
24
|
+
subscribe: typeof subscribeCart;
|
|
25
|
+
getSnapshot: typeof getCartSnapshot;
|
|
26
|
+
hydrate: typeof hydrateCartStore;
|
|
27
|
+
addItem: typeof addItem;
|
|
28
|
+
removeItem: typeof removeItem;
|
|
29
|
+
updateQuantity: typeof updateQuantity;
|
|
30
|
+
updateCheckoutForm: typeof updateCheckoutForm;
|
|
31
|
+
clearCart: typeof clearCart;
|
|
32
|
+
clearAll: typeof clearAll;
|
|
33
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS cart store — device-local, persisted via KV driver.
|
|
3
|
+
* Mirrors TeincFoodBusiness/src/stores/pos.store.ts
|
|
4
|
+
* Persist key: "Teinc Food-pos-cart" (for migration compatibility).
|
|
5
|
+
*/
|
|
6
|
+
import { getKVDriver } from "../adapters/kv";
|
|
7
|
+
const CART_KEY = "Teinc Food-pos-cart";
|
|
8
|
+
const defaultCheckoutForm = {
|
|
9
|
+
customer_name: "",
|
|
10
|
+
customer_phone: "",
|
|
11
|
+
order_type: "takeout",
|
|
12
|
+
pos_payment_method: "cash",
|
|
13
|
+
};
|
|
14
|
+
let state = {
|
|
15
|
+
items: [],
|
|
16
|
+
checkoutForm: { ...defaultCheckoutForm },
|
|
17
|
+
};
|
|
18
|
+
let hydrated = false;
|
|
19
|
+
const listeners = new Set();
|
|
20
|
+
function notify() { listeners.forEach((l) => l()); }
|
|
21
|
+
async function persist() {
|
|
22
|
+
try {
|
|
23
|
+
const kv = getKVDriver();
|
|
24
|
+
await kv.setItem(CART_KEY, JSON.stringify({ state: { items: state.items, checkoutForm: state.checkoutForm }, version: 0 }));
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
export async function hydrateCartStore() {
|
|
29
|
+
if (hydrated)
|
|
30
|
+
return;
|
|
31
|
+
hydrated = true;
|
|
32
|
+
try {
|
|
33
|
+
const kv = getKVDriver();
|
|
34
|
+
const raw = await kv.getItem(CART_KEY);
|
|
35
|
+
if (raw) {
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
const s = parsed.state ?? parsed;
|
|
38
|
+
state = {
|
|
39
|
+
items: Array.isArray(s.items) ? s.items : [],
|
|
40
|
+
checkoutForm: s.checkoutForm ? { ...defaultCheckoutForm, ...s.checkoutForm } : { ...defaultCheckoutForm },
|
|
41
|
+
};
|
|
42
|
+
notify();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
}
|
|
47
|
+
export function getCartState() { return state; }
|
|
48
|
+
export function subscribeCart(listener) {
|
|
49
|
+
listeners.add(listener);
|
|
50
|
+
return () => listeners.delete(listener);
|
|
51
|
+
}
|
|
52
|
+
export function getCartSnapshot() { return state; }
|
|
53
|
+
function computeUnitPrice(menuItem, variant, addons) {
|
|
54
|
+
let price = menuItem?.basePrice?.amountMinor
|
|
55
|
+
?? menuItem?.base_price?.amount_minor
|
|
56
|
+
?? 0;
|
|
57
|
+
if (variant)
|
|
58
|
+
price = variant.price?.amountMinor ?? variant.price_minor ?? price;
|
|
59
|
+
// addons: amountMinor
|
|
60
|
+
for (const a of addons) {
|
|
61
|
+
const amt = a.amountMinor ?? a.amount_minor ?? 0;
|
|
62
|
+
price += amt;
|
|
63
|
+
}
|
|
64
|
+
return price;
|
|
65
|
+
}
|
|
66
|
+
function makeCartItemId(menuItemId, variantId, addonIds) {
|
|
67
|
+
const sorted = [...addonIds].sort();
|
|
68
|
+
return `${menuItemId}-${variantId || ""}-${sorted.join(",")}`;
|
|
69
|
+
}
|
|
70
|
+
export function addItem(menuItem, quantity, variant, addons) {
|
|
71
|
+
const variantId = variant?.id;
|
|
72
|
+
const addonIds = addons.map((a) => a.id);
|
|
73
|
+
const cartItemId = makeCartItemId(menuItem.id, variantId, addonIds);
|
|
74
|
+
const unitPrice = computeUnitPrice(menuItem, variant, addons);
|
|
75
|
+
const selectedAddons = addons.map((a) => ({
|
|
76
|
+
addon_id: a.id,
|
|
77
|
+
name: a.name,
|
|
78
|
+
price_minor: a.amountMinor ?? a.amount_minor ?? 0,
|
|
79
|
+
}));
|
|
80
|
+
const existing = state.items.find((i) => i.id === cartItemId);
|
|
81
|
+
if (existing) {
|
|
82
|
+
state = { ...state, items: state.items.map((i) => i.id === cartItemId ? { ...i, quantity: i.quantity + quantity } : i) };
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const item = {
|
|
86
|
+
id: cartItemId,
|
|
87
|
+
menu_item: menuItem,
|
|
88
|
+
quantity,
|
|
89
|
+
selected_variant: variant,
|
|
90
|
+
selected_addons: selectedAddons,
|
|
91
|
+
notes: "",
|
|
92
|
+
unit_price_minor: unitPrice,
|
|
93
|
+
};
|
|
94
|
+
state = { ...state, items: [...state.items, item] };
|
|
95
|
+
}
|
|
96
|
+
notify();
|
|
97
|
+
void persist();
|
|
98
|
+
}
|
|
99
|
+
export function removeItem(cartItemId) {
|
|
100
|
+
state = { ...state, items: state.items.filter((i) => i.id !== cartItemId) };
|
|
101
|
+
notify();
|
|
102
|
+
void persist();
|
|
103
|
+
}
|
|
104
|
+
export function updateQuantity(cartItemId, quantity) {
|
|
105
|
+
state = { ...state, items: state.items.map((i) => i.id === cartItemId ? { ...i, quantity: Math.max(1, quantity) } : i) };
|
|
106
|
+
notify();
|
|
107
|
+
void persist();
|
|
108
|
+
}
|
|
109
|
+
export function updateCheckoutForm(updates) {
|
|
110
|
+
state = { ...state, checkoutForm: { ...state.checkoutForm, ...updates } };
|
|
111
|
+
notify();
|
|
112
|
+
void persist();
|
|
113
|
+
}
|
|
114
|
+
export function clearCart() { state = { ...state, items: [] }; notify(); void persist(); }
|
|
115
|
+
export function clearAll() { state = { items: [], checkoutForm: { ...defaultCheckoutForm } }; notify(); void persist(); }
|
|
116
|
+
export const cartStore = {
|
|
117
|
+
getState: getCartState,
|
|
118
|
+
subscribe: subscribeCart,
|
|
119
|
+
getSnapshot: getCartSnapshot,
|
|
120
|
+
hydrate: hydrateCartStore,
|
|
121
|
+
addItem, removeItem, updateQuantity, updateCheckoutForm, clearCart, clearAll,
|
|
122
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog hooks — offline-first menu browsing
|
|
3
|
+
* Port of TeincFoodBusiness/src/hooks/menu.hooks.ts
|
|
4
|
+
* Uses reference_data SQLite cache + network refresh when online.
|
|
5
|
+
*/
|
|
6
|
+
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
|
7
|
+
export declare const CATALOG_KEYS: {
|
|
8
|
+
readonly all: readonly ["menu"];
|
|
9
|
+
readonly items: (businessId?: string) => readonly ["menu", "items", string | undefined];
|
|
10
|
+
readonly categories: (businessId?: string) => readonly ["menu", "categories", string | undefined];
|
|
11
|
+
};
|
|
12
|
+
export declare function useMenuItemsQuery(filters?: {
|
|
13
|
+
search?: string;
|
|
14
|
+
category_ids?: string;
|
|
15
|
+
sort?: string;
|
|
16
|
+
}, enabled?: boolean): ReturnType<typeof useInfiniteQuery> & {
|
|
17
|
+
data: unknown[] | undefined;
|
|
18
|
+
};
|
|
19
|
+
export declare function useMenuCategoriesQuery(): ReturnType<typeof useQuery>;
|
|
20
|
+
export declare function useCreateMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<unknown, Error, Record<string, unknown>, unknown>;
|
|
21
|
+
export declare function useUpdateMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<unknown, Error, {
|
|
22
|
+
id: string;
|
|
23
|
+
payload: Record<string, unknown>;
|
|
24
|
+
}, unknown>;
|
|
25
|
+
export declare function useDeleteMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog hooks — offline-first menu browsing
|
|
3
|
+
* Port of TeincFoodBusiness/src/hooks/menu.hooks.ts
|
|
4
|
+
* Uses reference_data SQLite cache + network refresh when online.
|
|
5
|
+
*/
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
import { useSyncExternalStore } from "react";
|
|
9
|
+
import { getActiveBusiness, subscribeBusiness } from "../adapters/business";
|
|
10
|
+
import { getConnectivityState, subscribeConnectivity } from "../adapters/connectivity";
|
|
11
|
+
import { menuService } from "../adapters/services";
|
|
12
|
+
import { getReferenceData } from "../reference/operations";
|
|
13
|
+
import { openSyncDatabase } from "../db/connection";
|
|
14
|
+
import { referenceSyncService } from "../reference/service";
|
|
15
|
+
import { emitReferenceChanged } from "../reference/events";
|
|
16
|
+
import { runOfflineCapableMutation } from "../sync/offline-capable";
|
|
17
|
+
export const CATALOG_KEYS = {
|
|
18
|
+
all: ["menu"],
|
|
19
|
+
items: (businessId) => [...CATALOG_KEYS.all, "items", businessId],
|
|
20
|
+
categories: (businessId) => [...CATALOG_KEYS.all, "categories", businessId],
|
|
21
|
+
};
|
|
22
|
+
function useBusinessId() {
|
|
23
|
+
const id = useSyncExternalStore(subscribeBusiness, () => getActiveBusiness()?.id ?? null, () => null);
|
|
24
|
+
return id ?? undefined;
|
|
25
|
+
}
|
|
26
|
+
function useIsOnline() {
|
|
27
|
+
const s = useSyncExternalStore(subscribeConnectivity, getConnectivityState, getConnectivityState);
|
|
28
|
+
return s.isOnline;
|
|
29
|
+
}
|
|
30
|
+
export function useMenuItemsQuery(filters, enabled = true) {
|
|
31
|
+
const businessId = useBusinessId();
|
|
32
|
+
const isOnline = useIsOnline();
|
|
33
|
+
// Cached reference query — synchronous from SQLite via query
|
|
34
|
+
const cachedQuery = useQuery({
|
|
35
|
+
queryKey: [...CATALOG_KEYS.items(businessId), "cached", filters],
|
|
36
|
+
queryFn: async () => {
|
|
37
|
+
if (!businessId)
|
|
38
|
+
return null;
|
|
39
|
+
const db = await openSyncDatabase();
|
|
40
|
+
const rec = await getReferenceData(db, businessId, "menu:items");
|
|
41
|
+
return rec?.payload ?? null;
|
|
42
|
+
},
|
|
43
|
+
enabled: enabled && !!businessId,
|
|
44
|
+
staleTime: Infinity,
|
|
45
|
+
});
|
|
46
|
+
const filtered = useMemo(() => {
|
|
47
|
+
const all = cachedQuery.data?.data ?? [];
|
|
48
|
+
if (!enabled)
|
|
49
|
+
return [];
|
|
50
|
+
let out = all;
|
|
51
|
+
if (filters?.category_ids) {
|
|
52
|
+
const ids = new Set(filters.category_ids.split(",").map((s) => s.trim()).filter(Boolean));
|
|
53
|
+
if (ids.size > 0)
|
|
54
|
+
out = out.filter((i) => ids.has((i.category_id ?? i.categoryId) ?? ""));
|
|
55
|
+
}
|
|
56
|
+
if (filters?.search) {
|
|
57
|
+
const q = filters.search.trim().toLowerCase();
|
|
58
|
+
if (q)
|
|
59
|
+
out = out.filter((i) => (i.name ?? "").toLowerCase().includes(q) || (i.description ?? "").toLowerCase().includes(q));
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}, [cachedQuery.data, filters?.category_ids, filters?.search, enabled]);
|
|
63
|
+
const networkQuery = useInfiniteQuery({
|
|
64
|
+
queryKey: [...CATALOG_KEYS.items(businessId), filters, "network"],
|
|
65
|
+
queryFn: ({ pageParam = 1 }) => menuService.fetchBusinessMenuItems(businessId, pageParam, filters),
|
|
66
|
+
initialPageParam: 1,
|
|
67
|
+
getNextPageParam: (lastPage) => {
|
|
68
|
+
const p = lastPage?.pagination;
|
|
69
|
+
return p && p.page < p.total_pages ? p.page + 1 : undefined;
|
|
70
|
+
},
|
|
71
|
+
enabled: enabled && !!businessId && isOnline,
|
|
72
|
+
staleTime: 30 * 1000,
|
|
73
|
+
retry: false,
|
|
74
|
+
networkMode: "offlineFirst",
|
|
75
|
+
});
|
|
76
|
+
const networkData = networkQuery.data?.pages
|
|
77
|
+
? networkQuery.data.pages.flatMap((p) => p.data)
|
|
78
|
+
: undefined;
|
|
79
|
+
const data = isOnline && networkData !== undefined ? networkData : enabled ? filtered : undefined;
|
|
80
|
+
return {
|
|
81
|
+
data,
|
|
82
|
+
isPending: cachedQuery.isPending && !networkData,
|
|
83
|
+
isFetching: networkQuery.isFetching,
|
|
84
|
+
isError: cachedQuery.isError || networkQuery.isError,
|
|
85
|
+
error: (cachedQuery.error ?? networkQuery.error),
|
|
86
|
+
refetch: async () => {
|
|
87
|
+
if (!businessId)
|
|
88
|
+
return;
|
|
89
|
+
if (isOnline)
|
|
90
|
+
await referenceSyncService.sync({ businessId, keys: ["menu:items", "menu:categories"] });
|
|
91
|
+
return networkQuery.refetch();
|
|
92
|
+
},
|
|
93
|
+
fetchNextPage: networkQuery.fetchNextPage,
|
|
94
|
+
hasNextPage: networkQuery.hasNextPage,
|
|
95
|
+
isFetchingNextPage: networkQuery.isFetchingNextPage,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function useMenuCategoriesQuery() {
|
|
99
|
+
const businessId = useBusinessId();
|
|
100
|
+
const isOnline = useIsOnline();
|
|
101
|
+
const cachedQuery = useQuery({
|
|
102
|
+
queryKey: [...CATALOG_KEYS.categories(businessId), "cached"],
|
|
103
|
+
queryFn: async () => {
|
|
104
|
+
if (!businessId)
|
|
105
|
+
return null;
|
|
106
|
+
const db = await openSyncDatabase();
|
|
107
|
+
const rec = await getReferenceData(db, businessId, "menu:categories");
|
|
108
|
+
return rec?.payload ?? null;
|
|
109
|
+
},
|
|
110
|
+
enabled: !!businessId,
|
|
111
|
+
staleTime: Infinity,
|
|
112
|
+
});
|
|
113
|
+
const networkQuery = useQuery({
|
|
114
|
+
queryKey: CATALOG_KEYS.categories(businessId),
|
|
115
|
+
queryFn: () => menuService.fetchBusinessCategories(businessId),
|
|
116
|
+
enabled: !!businessId && isOnline,
|
|
117
|
+
staleTime: 30 * 1000,
|
|
118
|
+
retry: false,
|
|
119
|
+
networkMode: "offlineFirst",
|
|
120
|
+
});
|
|
121
|
+
const data = (isOnline && networkQuery.data ? networkQuery.data : cachedQuery.data);
|
|
122
|
+
return { ...networkQuery, data, isPending: cachedQuery.isPending && !networkQuery.data };
|
|
123
|
+
}
|
|
124
|
+
// Mutations — offline-capable
|
|
125
|
+
export function useCreateMenuItemMutation() {
|
|
126
|
+
const businessId = useBusinessId();
|
|
127
|
+
const qc = useQueryClient();
|
|
128
|
+
return useMutation({
|
|
129
|
+
mutationFn: (payload) => runOfflineCapableMutation({ run: () => menuService.createMenuItem(payload), commandType: "menu:create_item", businessId: businessId, payload }),
|
|
130
|
+
onSuccess: () => {
|
|
131
|
+
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
132
|
+
if (businessId)
|
|
133
|
+
emitReferenceChanged(businessId, ["menu:items"]);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export function useUpdateMenuItemMutation() {
|
|
138
|
+
const businessId = useBusinessId();
|
|
139
|
+
const qc = useQueryClient();
|
|
140
|
+
return useMutation({
|
|
141
|
+
mutationFn: ({ id, payload }) => runOfflineCapableMutation({ run: () => menuService.updateMenuItem(id, payload), commandType: "menu:update_item", businessId: businessId, entityId: id, payload }),
|
|
142
|
+
onSuccess: () => {
|
|
143
|
+
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
144
|
+
if (businessId)
|
|
145
|
+
emitReferenceChanged(businessId, ["menu:items"]);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
export function useDeleteMenuItemMutation() {
|
|
150
|
+
const businessId = useBusinessId();
|
|
151
|
+
const qc = useQueryClient();
|
|
152
|
+
return useMutation({
|
|
153
|
+
mutationFn: (id) => runOfflineCapableMutation({ run: () => menuService.deleteMenuItem(id), commandType: "menu:delete_item", businessId: businessId, entityId: id, payload: { id } }),
|
|
154
|
+
onSuccess: () => {
|
|
155
|
+
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
156
|
+
if (businessId)
|
|
157
|
+
emitReferenceChanged(businessId, ["menu:items"]);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiosk hooks shim — delegates to order lifecycle hooks for offline-first.
|
|
3
|
+
* Kiosk is just a filtered view of seller orders; transitions use the same outbox.
|
|
4
|
+
*/
|
|
5
|
+
export { useAcceptOrderMutation as useAcceptKioskOrderMutation, useRejectOrderMutation as useRejectKioskOrderMutation, useStartPreparingOrderMutation as useStartPreparingKioskOrderMutation, useMarkOrderReadyMutation as useMarkKioskOrderReadyMutation, } from "./orders.hooks";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiosk hooks shim — delegates to order lifecycle hooks for offline-first.
|
|
3
|
+
* Kiosk is just a filtered view of seller orders; transitions use the same outbox.
|
|
4
|
+
*/
|
|
5
|
+
export { useAcceptOrderMutation as useAcceptKioskOrderMutation, useRejectOrderMutation as useRejectKioskOrderMutation, useStartPreparingOrderMutation as useStartPreparingKioskOrderMutation, useMarkOrderReadyMutation as useMarkKioskOrderReadyMutation, } from "./orders.hooks";
|
|
@@ -45,9 +45,25 @@ export declare function useSellerOrdersQuery(enabled?: boolean, filters?: {
|
|
|
45
45
|
export declare function usePendingOrders(businessId?: string): SellerOrder[];
|
|
46
46
|
export declare function useCreatePOSOrderMutation(): import("@tanstack/react-query").UseMutationResult<unknown, Error, POSOrderCreateRequest, unknown>;
|
|
47
47
|
export declare function usePOSOrderPreviewQuery(items: POSCartItem[], orderType: POSOrderType, customerName: string): import("@tanstack/react-query").UseQueryResult<POSOrderPreviewResponse, Error>;
|
|
48
|
+
export declare function useSellerOrderQuery(orderId: string, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<{
|
|
49
|
+
data: SellerOrder;
|
|
50
|
+
}, Error>;
|
|
51
|
+
export declare function isQueuedTransition(result: unknown): boolean;
|
|
48
52
|
export declare const useAcceptOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
49
53
|
export declare const useRejectOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
50
54
|
export declare const useStartPreparingOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
51
55
|
export declare const useMarkOrderReadyMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
52
56
|
export declare const useCompletePickupMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
53
57
|
export declare const useCancelOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
|
|
58
|
+
export declare function useConfirmHandoverMutation(): import("@tanstack/react-query").UseMutationResult<{
|
|
59
|
+
id: string;
|
|
60
|
+
status: string;
|
|
61
|
+
}, Error, string, unknown>;
|
|
62
|
+
export declare function useAssignRiderMutation(): import("@tanstack/react-query").UseMutationResult<unknown, Error, {
|
|
63
|
+
orderId: string;
|
|
64
|
+
riderId: string;
|
|
65
|
+
}, unknown>;
|
|
66
|
+
export declare function useUpdateOrderStatusMutation(): {
|
|
67
|
+
mutate: (orderId: string, status: string) => Promise<unknown>;
|
|
68
|
+
isPending: boolean;
|
|
69
|
+
};
|