@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
|
@@ -272,6 +272,45 @@ export function usePOSOrderPreviewQuery(items, orderType, customerName) {
|
|
|
272
272
|
staleTime: 30 * 1000,
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
|
+
// ─── Seller order detail (offline via snapshot fallback) ───────────────────
|
|
276
|
+
export function useSellerOrderQuery(orderId, enabled = true) {
|
|
277
|
+
const businessId = useBusinessId();
|
|
278
|
+
const connectivity = useConnectivity();
|
|
279
|
+
const offline = isOfflineState(connectivity);
|
|
280
|
+
return useQuery({
|
|
281
|
+
queryKey: SELLER_KEYS.order(orderId, businessId),
|
|
282
|
+
queryFn: async () => {
|
|
283
|
+
if (offline) {
|
|
284
|
+
const list = businessId ? await orderSnapshotsService.getSellerList(businessId, businessId) : null;
|
|
285
|
+
const found = list?.find((o) => o.id === orderId || o.order_number === orderId);
|
|
286
|
+
if (found)
|
|
287
|
+
return { data: found };
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const res = await sellerService.fetchSellerOrder(orderId);
|
|
291
|
+
return res;
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
const list = businessId ? await orderSnapshotsService.getSellerList(businessId, businessId) : null;
|
|
295
|
+
const found = list?.find((o) => o.id === orderId || o.order_number === orderId);
|
|
296
|
+
if (found)
|
|
297
|
+
return { data: found };
|
|
298
|
+
throw e;
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
enabled: enabled && !!orderId && !!businessId,
|
|
302
|
+
staleTime: 30 * 1000,
|
|
303
|
+
retry: false,
|
|
304
|
+
networkMode: "offlineFirst",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// ─── Helpers for queued UI ───────────────────────────────────────────────
|
|
308
|
+
export function isQueuedTransition(result) {
|
|
309
|
+
const r = result;
|
|
310
|
+
if (!r)
|
|
311
|
+
return false;
|
|
312
|
+
return r.status === "pending" && !!r.command_id && !r.id;
|
|
313
|
+
}
|
|
275
314
|
// ─── Order status mutations via SyncEngine (all go through outbox) ────────
|
|
276
315
|
function makeOrderActionMutation(build) {
|
|
277
316
|
return function useOrderActionMutation() {
|
|
@@ -298,3 +337,57 @@ export const useStartPreparingOrderMutation = makeOrderActionMutation(({ orderId
|
|
|
298
337
|
export const useMarkOrderReadyMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.markOrderReady({ orderId, businessId }));
|
|
299
338
|
export const useCompletePickupMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.completePickup({ orderId, businessId }));
|
|
300
339
|
export const useCancelOrderMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.cancelOrder({ orderId, businessId }));
|
|
340
|
+
export function useConfirmHandoverMutation() {
|
|
341
|
+
const businessId = useBusinessId();
|
|
342
|
+
const queryClient = useQueryClient();
|
|
343
|
+
return useMutation({
|
|
344
|
+
mutationFn: (deliveryId) => {
|
|
345
|
+
if (!businessId)
|
|
346
|
+
throw new Error("No active business");
|
|
347
|
+
return orderRepository.confirmHandover({ deliveryId, businessId });
|
|
348
|
+
},
|
|
349
|
+
onSettled: (_d, _e, deliveryId) => {
|
|
350
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
351
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
352
|
+
if (deliveryId)
|
|
353
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.order(deliveryId) });
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
export function useAssignRiderMutation() {
|
|
358
|
+
const businessId = useBusinessId();
|
|
359
|
+
const queryClient = useQueryClient();
|
|
360
|
+
return useMutation({
|
|
361
|
+
mutationFn: ({ orderId, riderId }) => {
|
|
362
|
+
if (!businessId)
|
|
363
|
+
throw new Error("No active business");
|
|
364
|
+
return orderRepository.assignRider({ orderId, riderId, businessId });
|
|
365
|
+
},
|
|
366
|
+
onSettled: () => {
|
|
367
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
368
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
export function useUpdateOrderStatusMutation() {
|
|
373
|
+
const accept = useAcceptOrderMutation();
|
|
374
|
+
const reject = useRejectOrderMutation();
|
|
375
|
+
const preparing = useStartPreparingOrderMutation();
|
|
376
|
+
const ready = useMarkOrderReadyMutation();
|
|
377
|
+
const complete = useCompletePickupMutation();
|
|
378
|
+
const cancel = useCancelOrderMutation();
|
|
379
|
+
return {
|
|
380
|
+
mutate: (orderId, status) => {
|
|
381
|
+
switch (status) {
|
|
382
|
+
case "accepted": return accept.mutateAsync(orderId);
|
|
383
|
+
case "preparing": return preparing.mutateAsync(orderId);
|
|
384
|
+
case "ready": return ready.mutateAsync(orderId);
|
|
385
|
+
case "delivered": return complete.mutateAsync(orderId);
|
|
386
|
+
case "cancelled": return cancel.mutateAsync(orderId);
|
|
387
|
+
case "rejected": return reject.mutateAsync(orderId);
|
|
388
|
+
default: throw new Error(`Unknown status ${status}`);
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
isPending: accept.isPending || reject.isPending || preparing.isPending || ready.isPending || complete.isPending || cancel.isPending,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image cache for offline reference — port of Business src/reference/image-cache.ts
|
|
3
|
+
* Durable documentDirectory cache (not cacheDirectory) with stable presigned URL keying.
|
|
4
|
+
* Host injects FileSystemAdapter; falls back to no-op on web.
|
|
5
|
+
*/
|
|
6
|
+
export interface ImageCacheEntry {
|
|
7
|
+
remoteUrl: string;
|
|
8
|
+
localUri: string | null;
|
|
9
|
+
etag: string | null;
|
|
10
|
+
lastModified: string | null;
|
|
11
|
+
cachedAt: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function stableCacheUrl(url: string): string;
|
|
14
|
+
export declare function getCachedImagePath(url: string): string;
|
|
15
|
+
export declare function ensureImageCacheDir(): Promise<string>;
|
|
16
|
+
export declare function getCachedImageInfo(url: string): Promise<ImageCacheEntry>;
|
|
17
|
+
export declare function cacheRemoteImage(url: string | null | undefined, opts?: {
|
|
18
|
+
skipIfCached?: boolean;
|
|
19
|
+
removeOnFailure?: boolean;
|
|
20
|
+
}): Promise<ImageCacheEntry | null>;
|
|
21
|
+
export declare function cacheImageBatch(urls: (string | null | undefined)[], opts?: {
|
|
22
|
+
skipIfCached?: boolean;
|
|
23
|
+
removeOnFailure?: boolean;
|
|
24
|
+
}): Promise<ImageCacheEntry[]>;
|
|
25
|
+
export declare function clearImageCache(): Promise<void>;
|
|
26
|
+
export declare function pruneImageCache(knownUrls: Iterable<string | null | undefined>, maxAgeDays?: number): Promise<number>;
|
|
27
|
+
export declare function resolveImageSource(url: string | null | undefined): Promise<{
|
|
28
|
+
uri: string;
|
|
29
|
+
} | null>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image cache for offline reference — port of Business src/reference/image-cache.ts
|
|
3
|
+
* Durable documentDirectory cache (not cacheDirectory) with stable presigned URL keying.
|
|
4
|
+
* Host injects FileSystemAdapter; falls back to no-op on web.
|
|
5
|
+
*/
|
|
6
|
+
import { getFileSystemAdapter } from "../adapters/filesystem";
|
|
7
|
+
import { syncLogger } from "../utils/sync-logger";
|
|
8
|
+
export function stableCacheUrl(url) {
|
|
9
|
+
try {
|
|
10
|
+
const p = new URL(url);
|
|
11
|
+
return `${p.origin}${p.pathname}`;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function cacheKeyFromUrl(url) {
|
|
18
|
+
let hash = 0;
|
|
19
|
+
const stable = stableCacheUrl(url);
|
|
20
|
+
for (let i = 0; i < stable.length; i++) {
|
|
21
|
+
hash = (hash << 5) - hash + stable.charCodeAt(i);
|
|
22
|
+
hash |= 0;
|
|
23
|
+
}
|
|
24
|
+
return `${Math.abs(hash).toString(16)}.img`;
|
|
25
|
+
}
|
|
26
|
+
function getImageCacheDir(fs) {
|
|
27
|
+
const base = fs?.getDocumentDirectory() ?? "";
|
|
28
|
+
return `${base}teincfood_images/`;
|
|
29
|
+
}
|
|
30
|
+
export function getCachedImagePath(url) {
|
|
31
|
+
const fs = getFileSystemAdapter();
|
|
32
|
+
return `${getImageCacheDir(fs)}${cacheKeyFromUrl(url)}`;
|
|
33
|
+
}
|
|
34
|
+
export async function ensureImageCacheDir() {
|
|
35
|
+
const fs = getFileSystemAdapter();
|
|
36
|
+
if (!fs)
|
|
37
|
+
return "";
|
|
38
|
+
const dir = getImageCacheDir(fs);
|
|
39
|
+
const info = await fs.getInfoAsync(dir);
|
|
40
|
+
if (!info.exists)
|
|
41
|
+
await fs.makeDirectoryAsync(dir, { intermediates: true });
|
|
42
|
+
return dir;
|
|
43
|
+
}
|
|
44
|
+
export async function getCachedImageInfo(url) {
|
|
45
|
+
const fs = getFileSystemAdapter();
|
|
46
|
+
if (!fs)
|
|
47
|
+
return { remoteUrl: url, localUri: url, etag: null, lastModified: null, cachedAt: new Date().toISOString() };
|
|
48
|
+
const localPath = getCachedImagePath(url);
|
|
49
|
+
const info = await fs.getInfoAsync(localPath);
|
|
50
|
+
return { remoteUrl: url, localUri: info.exists && !info.isDirectory ? localPath : null, etag: null, lastModified: null, cachedAt: info.exists ? new Date().toISOString() : null };
|
|
51
|
+
}
|
|
52
|
+
export async function cacheRemoteImage(url, opts = {}) {
|
|
53
|
+
if (!url)
|
|
54
|
+
return null;
|
|
55
|
+
const fs = getFileSystemAdapter();
|
|
56
|
+
if (!fs)
|
|
57
|
+
return { remoteUrl: url, localUri: url, etag: null, lastModified: null, cachedAt: new Date().toISOString() };
|
|
58
|
+
await ensureImageCacheDir();
|
|
59
|
+
const localPath = getCachedImagePath(url);
|
|
60
|
+
const existing = await fs.getInfoAsync(localPath);
|
|
61
|
+
if (opts.skipIfCached && existing.exists && !existing.isDirectory) {
|
|
62
|
+
return { remoteUrl: url, localUri: localPath, etag: null, lastModified: null, cachedAt: new Date().toISOString() };
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const res = await fs.downloadAsync(url, localPath);
|
|
66
|
+
if (res.uri)
|
|
67
|
+
return { remoteUrl: url, localUri: localPath, etag: null, lastModified: null, cachedAt: new Date().toISOString() };
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
syncLogger.warn("ImageCache", "Failed to download", { url, error: String(e) });
|
|
71
|
+
}
|
|
72
|
+
if (opts.removeOnFailure)
|
|
73
|
+
await fs.deleteAsync(localPath, { idempotent: true }).catch(() => { });
|
|
74
|
+
if (existing.exists && !existing.isDirectory)
|
|
75
|
+
return { remoteUrl: url, localUri: localPath, etag: null, lastModified: null, cachedAt: new Date().toISOString() };
|
|
76
|
+
return { remoteUrl: url, localUri: null, etag: null, lastModified: null, cachedAt: null };
|
|
77
|
+
}
|
|
78
|
+
export async function cacheImageBatch(urls, opts = {}) {
|
|
79
|
+
const results = await Promise.all(urls.map((u) => cacheRemoteImage(u, opts)));
|
|
80
|
+
return results.filter((r) => r !== null);
|
|
81
|
+
}
|
|
82
|
+
export async function clearImageCache() {
|
|
83
|
+
const fs = getFileSystemAdapter();
|
|
84
|
+
if (!fs)
|
|
85
|
+
return;
|
|
86
|
+
await fs.deleteAsync(getImageCacheDir(fs), { idempotent: true });
|
|
87
|
+
}
|
|
88
|
+
export async function pruneImageCache(knownUrls, maxAgeDays = 30) {
|
|
89
|
+
try {
|
|
90
|
+
const fs = getFileSystemAdapter();
|
|
91
|
+
if (!fs)
|
|
92
|
+
return 0;
|
|
93
|
+
const known = new Set();
|
|
94
|
+
for (const u of knownUrls)
|
|
95
|
+
if (u)
|
|
96
|
+
known.add(u);
|
|
97
|
+
const dir = getImageCacheDir(fs);
|
|
98
|
+
const info = await fs.getInfoAsync(dir);
|
|
99
|
+
if (!info.exists || info.isDirectory !== true)
|
|
100
|
+
return 0;
|
|
101
|
+
// cutoff check skipped without modificationTime — just orphan check
|
|
102
|
+
const entries = await fs.readDirectoryAsync(dir);
|
|
103
|
+
let pruned = 0;
|
|
104
|
+
for (const f of entries) {
|
|
105
|
+
if (!f.endsWith(".img"))
|
|
106
|
+
continue;
|
|
107
|
+
if ([...known].some((url) => getCachedImagePath(url) === `${dir}${f}`))
|
|
108
|
+
continue;
|
|
109
|
+
await fs.deleteAsync(`${dir}${f}`, { idempotent: true });
|
|
110
|
+
pruned += 1;
|
|
111
|
+
}
|
|
112
|
+
if (pruned > 0)
|
|
113
|
+
syncLogger.info("ImageCache", "Pruned orphan images", { pruned });
|
|
114
|
+
return pruned;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export async function resolveImageSource(url) {
|
|
121
|
+
if (!url)
|
|
122
|
+
return null;
|
|
123
|
+
const cached = await getCachedImageInfo(url);
|
|
124
|
+
return cached.localUri ? { uri: cached.localUri } : { uri: url };
|
|
125
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -63,16 +63,27 @@ export * from "./reference/service";
|
|
|
63
63
|
export { referenceSyncService } from "./reference/service";
|
|
64
64
|
export * from "./reference/capabilities";
|
|
65
65
|
export * from "./adapters/sqlite";
|
|
66
|
-
export
|
|
66
|
+
export { KVDriver, setKVDriver, getKVDriver, kvGetItem, kvSetItem, kvRemoveItem, getItem, setItem, removeItem, getItemSync, setItemSync } from "./adapters/kv";
|
|
67
67
|
export * from "./adapters/connectivity";
|
|
68
68
|
export * from "./adapters/http";
|
|
69
69
|
export * from "./adapters/local-node";
|
|
70
70
|
export * from "./adapters/services";
|
|
71
71
|
export * from "./adapters/devices";
|
|
72
|
-
export
|
|
72
|
+
export { imageCacheService } from "./adapters/image-cache";
|
|
73
73
|
export * from "./adapters/business";
|
|
74
74
|
export * from "./adapters/pending-orders";
|
|
75
|
+
export * from "./adapters/print";
|
|
76
|
+
export * from "./adapters/filesystem";
|
|
75
77
|
export { KIOSK_KEYS, SELLER_ORDERS_KEYS, BUSINESS_DELIVERIES_KEYS, MENU_KEYS, REFERENCE_KEYS } from "./adapters/query-keys";
|
|
78
|
+
export * from "./printer/types";
|
|
79
|
+
export * from "./printer/store";
|
|
80
|
+
export * from "./printer/receipt";
|
|
81
|
+
export * from "./printer/service";
|
|
82
|
+
export * from "./printer/hooks";
|
|
83
|
+
export * from "./cart/store";
|
|
84
|
+
export * from "./cart/hooks";
|
|
85
|
+
export * from "./catalog/hooks";
|
|
86
|
+
export { getCachedImagePath, ensureImageCacheDir, getCachedImageInfo, cacheRemoteImage, cacheImageBatch, clearImageCache, pruneImageCache, resolveImageSource, stableCacheUrl } from "./image-cache/service";
|
|
76
87
|
import { type SqliteDriver } from "./adapters/sqlite";
|
|
77
88
|
import { type KVDriver } from "./adapters/kv";
|
|
78
89
|
import { type HttpClient } from "./adapters/http";
|
|
@@ -88,6 +99,12 @@ export interface CoreConfig {
|
|
|
88
99
|
isOnline?: boolean;
|
|
89
100
|
isCloudReachable?: "reachable" | "unreachable" | "unknown";
|
|
90
101
|
};
|
|
102
|
+
printAdapters?: {
|
|
103
|
+
tcp?: import("./adapters/print").TcpSocketAdapter;
|
|
104
|
+
printFile?: import("./adapters/print").PrintFileAdapter;
|
|
105
|
+
mdns?: import("./adapters/print").MdnsAdapter;
|
|
106
|
+
};
|
|
107
|
+
fileSystemAdapter?: import("./adapters/filesystem").FileSystemAdapter;
|
|
91
108
|
}
|
|
92
109
|
export declare function createTeincCore(config?: CoreConfig): {
|
|
93
110
|
engine: SyncEngine;
|
package/dist/index.js
CHANGED
|
@@ -66,22 +66,39 @@ export { referenceSyncService } from "./reference/service";
|
|
|
66
66
|
export * from "./reference/capabilities";
|
|
67
67
|
// ── Adapters (for host apps to supply) ──
|
|
68
68
|
export * from "./adapters/sqlite";
|
|
69
|
-
export
|
|
69
|
+
export { setKVDriver, getKVDriver, kvGetItem, kvSetItem, kvRemoveItem, getItem, setItem, removeItem, getItemSync, setItemSync } from "./adapters/kv";
|
|
70
70
|
export * from "./adapters/connectivity";
|
|
71
71
|
export * from "./adapters/http";
|
|
72
72
|
export * from "./adapters/local-node";
|
|
73
73
|
export * from "./adapters/services";
|
|
74
74
|
export * from "./adapters/devices";
|
|
75
|
-
export
|
|
75
|
+
export { imageCacheService } from "./adapters/image-cache";
|
|
76
76
|
export * from "./adapters/business";
|
|
77
77
|
export * from "./adapters/pending-orders";
|
|
78
|
+
export * from "./adapters/print";
|
|
79
|
+
export * from "./adapters/filesystem";
|
|
78
80
|
export { KIOSK_KEYS, SELLER_ORDERS_KEYS, BUSINESS_DELIVERIES_KEYS, MENU_KEYS, REFERENCE_KEYS } from "./adapters/query-keys";
|
|
79
81
|
// POS_KEYS / SELLER_KEYS are exported from hooks/orders.hooks (offline-first source of truth)
|
|
82
|
+
// ── Printer (backend-identical template) ──
|
|
83
|
+
export * from "./printer/types";
|
|
84
|
+
export * from "./printer/store";
|
|
85
|
+
export * from "./printer/receipt";
|
|
86
|
+
export * from "./printer/service";
|
|
87
|
+
export * from "./printer/hooks";
|
|
88
|
+
// ── Cart (device-local) ──
|
|
89
|
+
export * from "./cart/store";
|
|
90
|
+
export * from "./cart/hooks";
|
|
91
|
+
// ── Catalog ──
|
|
92
|
+
export * from "./catalog/hooks";
|
|
93
|
+
// ── Image cache (durable) ──
|
|
94
|
+
export { getCachedImagePath, ensureImageCacheDir, getCachedImageInfo, cacheRemoteImage, cacheImageBatch, clearImageCache, pruneImageCache, resolveImageSource, stableCacheUrl } from "./image-cache/service";
|
|
80
95
|
// ── Init helper ──
|
|
81
96
|
import { setSqliteDriver } from "./adapters/sqlite";
|
|
82
97
|
import { setKVDriver } from "./adapters/kv";
|
|
83
98
|
import { setConnectivityState } from "./adapters/connectivity";
|
|
84
99
|
import { setHttpClient } from "./adapters/http";
|
|
100
|
+
import { setTcpAdapter, setPrintFileAdapter, setMdnsAdapter } from "./adapters/print";
|
|
101
|
+
import { setFileSystemAdapter } from "./adapters/filesystem";
|
|
85
102
|
import { API_BASE_URL, WS_BASE_URL, setApiBaseUrl, setWsBaseUrl } from "./utils/constants";
|
|
86
103
|
import { SyncEngine } from "./sync/engine";
|
|
87
104
|
import { restTransport } from "./sync/transport-rest";
|
|
@@ -105,6 +122,14 @@ export function createTeincCore(config = {}) {
|
|
|
105
122
|
setKVDriver(config.kvDriver);
|
|
106
123
|
if (config.httpClient)
|
|
107
124
|
setHttpClient(config.httpClient);
|
|
125
|
+
if (config.printAdapters?.tcp)
|
|
126
|
+
setTcpAdapter(config.printAdapters.tcp);
|
|
127
|
+
if (config.printAdapters?.printFile)
|
|
128
|
+
setPrintFileAdapter(config.printAdapters.printFile);
|
|
129
|
+
if (config.printAdapters?.mdns)
|
|
130
|
+
setMdnsAdapter(config.printAdapters.mdns);
|
|
131
|
+
if (config.fileSystemAdapter)
|
|
132
|
+
setFileSystemAdapter(config.fileSystemAdapter);
|
|
108
133
|
if (config.apiBaseUrl)
|
|
109
134
|
setApiBaseUrl(config.apiBaseUrl);
|
|
110
135
|
else {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Printer hooks for UI.
|
|
3
|
+
*/
|
|
4
|
+
import { setDefaultPrinter, setAutoPrint, addPrinter, updatePrinter, removePrinter } from "./store";
|
|
5
|
+
import type { SellerOrder } from "../types/api/seller.api";
|
|
6
|
+
export declare function usePrinter(): {
|
|
7
|
+
defaultPrinterId: string | null;
|
|
8
|
+
printers: import("./types").PrinterConfig[];
|
|
9
|
+
autoPrint: boolean;
|
|
10
|
+
setDefaultPrinter: typeof setDefaultPrinter;
|
|
11
|
+
setAutoPrint: typeof setAutoPrint;
|
|
12
|
+
addPrinter: typeof addPrinter;
|
|
13
|
+
updatePrinter: typeof updatePrinter;
|
|
14
|
+
removePrinter: typeof removePrinter;
|
|
15
|
+
};
|
|
16
|
+
export declare function usePrintOrder(): {
|
|
17
|
+
print: (order: SellerOrder) => Promise<void>;
|
|
18
|
+
printing: boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function useDiscoverPrinters(): {
|
|
21
|
+
printers: {
|
|
22
|
+
name: string;
|
|
23
|
+
ip: string;
|
|
24
|
+
port: number;
|
|
25
|
+
}[];
|
|
26
|
+
discovering: boolean;
|
|
27
|
+
discover: (timeoutMs?: number) => Promise<{
|
|
28
|
+
name: string;
|
|
29
|
+
ip: string;
|
|
30
|
+
port: number;
|
|
31
|
+
}[]>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Printer hooks for UI.
|
|
3
|
+
*/
|
|
4
|
+
import { useSyncExternalStore, useCallback } from "react";
|
|
5
|
+
import { subscribePrinter, getPrinterSnapshot, setDefaultPrinter, setAutoPrint, addPrinter, updatePrinter, removePrinter, hydratePrinterStore } from "./store";
|
|
6
|
+
import { getMdnsAdapter } from "../adapters/print";
|
|
7
|
+
import { printerService } from "./service";
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
export function usePrinter() {
|
|
10
|
+
const state = useSyncExternalStore(subscribePrinter, getPrinterSnapshot, getPrinterSnapshot);
|
|
11
|
+
useEffect(() => { void hydratePrinterStore(); }, []);
|
|
12
|
+
return {
|
|
13
|
+
...state,
|
|
14
|
+
setDefaultPrinter,
|
|
15
|
+
setAutoPrint,
|
|
16
|
+
addPrinter,
|
|
17
|
+
updatePrinter,
|
|
18
|
+
removePrinter,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function usePrintOrder() {
|
|
22
|
+
const [printing, setPrinting] = useState(false);
|
|
23
|
+
const print = useCallback(async (order) => {
|
|
24
|
+
setPrinting(true);
|
|
25
|
+
try {
|
|
26
|
+
await printerService.printOrderReceipt(order);
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
setPrinting(false);
|
|
30
|
+
}
|
|
31
|
+
}, []);
|
|
32
|
+
return { print, printing };
|
|
33
|
+
}
|
|
34
|
+
export function useDiscoverPrinters() {
|
|
35
|
+
const [discovering, setDiscovering] = useState(false);
|
|
36
|
+
const [printers, setPrinters] = useState([]);
|
|
37
|
+
const discover = useCallback(async (timeoutMs = 8000) => {
|
|
38
|
+
setDiscovering(true);
|
|
39
|
+
try {
|
|
40
|
+
const mdns = getMdnsAdapter();
|
|
41
|
+
if (mdns?.discoverPrinters) {
|
|
42
|
+
const found = await mdns.discoverPrinters(timeoutMs);
|
|
43
|
+
setPrinters(found);
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
setDiscovering(false);
|
|
50
|
+
}
|
|
51
|
+
}, []);
|
|
52
|
+
return { printers, discovering, discover };
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local receipt generation — 1:1 port of TeincFoodBackend.Orders.Receipt
|
|
3
|
+
* Generates ESC/POS (80mm, 32 chars) and HTML from a SellerOrder snapshot.
|
|
4
|
+
* Works fully offline for queued DD-RRR-CCC orders without backend fetch.
|
|
5
|
+
*
|
|
6
|
+
* Backend source: lib/teinc_food_backend/orders/receipt.ex
|
|
7
|
+
* Keep this file in sync with backend when template changes.
|
|
8
|
+
*/
|
|
9
|
+
import type { SellerOrder } from "../types/api/seller.api";
|
|
10
|
+
export declare function calloutNumber(order: SellerOrder): string;
|
|
11
|
+
export declare function generateEscpos(order: SellerOrder): string;
|
|
12
|
+
export declare function generateHtml(order: SellerOrder, logoDataUri?: string | null): string;
|