react-shopwave-connect 0.1.1

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.cjs ADDED
@@ -0,0 +1,824 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let react = require("react");
25
+ react = __toESM(react);
26
+
27
+ //#region src/core/request.ts
28
+ function resolveFetch(options) {
29
+ const f = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
30
+ if (!f) throw new Error("No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.");
31
+ return f;
32
+ }
33
+ function buildExtras(extras, options) {
34
+ const merged = {
35
+ "Content-Type": "application/json",
36
+ ...extras
37
+ };
38
+ if (options.token != null && merged.token == null) merged.token = options.token;
39
+ return merged;
40
+ }
41
+ /**
42
+ * Returns a JSON string of API errors if the response carries any, otherwise
43
+ * `null`. Mirrors the original `!responseJson.api.message.errors` checks.
44
+ */
45
+ function getApiErrors(api) {
46
+ const errors = api?.message?.errors;
47
+ if (errors && Object.keys(errors).length > 0) return JSON.stringify(errors);
48
+ return null;
49
+ }
50
+ /**
51
+ * GET helper. Sends request metadata via the `extras` header exactly like the
52
+ * original hooks did, parses JSON, and returns the typed payload.
53
+ */
54
+ async function apiGet(path, extras, options = {}) {
55
+ const doFetch = resolveFetch(options);
56
+ const url = (options.baseUrl ?? "") + path;
57
+ const headers = { extras: JSON.stringify(buildExtras(extras, options)) };
58
+ console.log("[API GET]", {
59
+ url,
60
+ headers
61
+ });
62
+ const response = await doFetch(url, {
63
+ method: "GET",
64
+ headers,
65
+ signal: options.signal
66
+ });
67
+ if (!response.ok) throw new Error(`Network response was not ok (${response.status})`);
68
+ return await response.json();
69
+ }
70
+ /**
71
+ * JSON body helper for POST/PUT mutations.
72
+ */
73
+ async function apiSend(path, method, payload, options = {}) {
74
+ const doFetch = resolveFetch(options);
75
+ const url = (options.baseUrl ?? "") + path;
76
+ const headers = { "Content-Type": "application/json" };
77
+ if (options.token != null) headers.token = options.token;
78
+ console.log(`[API ${method}]`, {
79
+ url,
80
+ headers,
81
+ payload
82
+ });
83
+ const response = await doFetch(url, {
84
+ method,
85
+ headers,
86
+ body: JSON.stringify(payload),
87
+ signal: options.signal
88
+ });
89
+ if (!response.ok) throw new Error(`Failed to ${method.toLowerCase()}: ${response.statusText}`);
90
+ return await response.json();
91
+ }
92
+ /**
93
+ * DELETE helper.
94
+ */
95
+ async function apiDelete(path, options = {}) {
96
+ const doFetch = resolveFetch(options);
97
+ const url = (options.baseUrl ?? "") + path;
98
+ const headers = {};
99
+ if (options.token != null) headers.token = options.token;
100
+ console.log("[API DELETE]", {
101
+ url,
102
+ headers
103
+ });
104
+ const response = await doFetch(url, {
105
+ method: "DELETE",
106
+ headers,
107
+ signal: options.signal
108
+ });
109
+ if (!response.ok) throw new Error(`Failed to delete: ${response.statusText}`);
110
+ }
111
+
112
+ //#endregion
113
+ //#region src/core/category.ts
114
+ /**
115
+ * Fetches categories. Framework-agnostic: returns the flattened `Category[]`
116
+ * or throws on API errors.
117
+ */
118
+ async function fetchCategories(params = {}, options = {}) {
119
+ const extras = { deleted: params.deleted ?? false };
120
+ if (params.categoryIds) extras.categoryIds = params.categoryIds;
121
+ if (params.token) extras.token = params.token;
122
+ const json = await apiGet("/api/categories", extras, options);
123
+ const error = getApiErrors(json.api);
124
+ if (error) throw new Error(error);
125
+ return Object.values(json.categories ?? {});
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/core/consumer.ts
130
+ /**
131
+ * Fetches consumers by id. Returns an empty array when no ids are supplied
132
+ * (the original hook short-circuited in that case).
133
+ */
134
+ async function fetchConsumers(consumerIds, options = {}) {
135
+ if (!consumerIds || consumerIds.length === 0) return [];
136
+ const json = await apiGet("/api/consumer", { ids: consumerIds.join(",") }, options);
137
+ const error = getApiErrors(json.api);
138
+ if (error) throw new Error(error);
139
+ return Object.values(json.consumers ?? {});
140
+ }
141
+
142
+ //#endregion
143
+ //#region src/core/employee.ts
144
+ const rolesById = {
145
+ "1": "Owner",
146
+ "2": "Manager",
147
+ "3": "Assistant",
148
+ "4": "Guest",
149
+ "5": "Assistant Manager"
150
+ };
151
+ /**
152
+ * Fetches employees. Returns the flattened `Employee[]`.
153
+ */
154
+ async function fetchEmployees(params = {}, options = {}) {
155
+ const extras = { deleted: params.deleted ?? false };
156
+ if (params.employeeIds) extras.employeeIds = params.employeeIds;
157
+ const json = await apiGet("/api/employees", extras, options);
158
+ const error = getApiErrors(json.api);
159
+ if (error) throw new Error(error);
160
+ return Object.values(json.employees ?? {});
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/core/product.ts
165
+ /**
166
+ * Fetches products. Returns the flattened `Product[]`.
167
+ */
168
+ async function fetchProducts(params = {}, options = {}) {
169
+ const extras = { deleted: params.deleted ?? false };
170
+ if (params.productIds) extras.productIds = params.productIds;
171
+ if (params.storeId) extras.storeId = params.storeId;
172
+ if (params.token) extras.token = params.token;
173
+ const json = await apiGet("/api/products", extras, options);
174
+ const error = getApiErrors(json.api);
175
+ if (error) throw new Error(error);
176
+ return Object.values(json.products ?? {});
177
+ }
178
+ /**
179
+ * Fetches products by id and returns them keyed by id, transparently batching
180
+ * the request (default 200 ids per call) to stay within API limits. Extracted
181
+ * from the original `useBasketReport` inline batching loop so it can be reused
182
+ * outside React.
183
+ */
184
+ async function fetchProductsMap(productIds, options = {}, batchSize = 200) {
185
+ const result = {};
186
+ if (!productIds || productIds.length === 0) return result;
187
+ for (let i = 0; i < productIds.length; i += batchSize) {
188
+ const products = await fetchProducts({ productIds: productIds.slice(i, i + batchSize) }, options);
189
+ for (const product of products) result[product.id] = product;
190
+ }
191
+ return result;
192
+ }
193
+
194
+ //#endregion
195
+ //#region src/core/promotion.ts
196
+ /** Promotion rule identifiers as stored by the Shopwave API. */
197
+ let PromotionRuleSet = /* @__PURE__ */ function(PromotionRuleSet$1) {
198
+ PromotionRuleSet$1["Percentage"] = "|%^/";
199
+ PromotionRuleSet$1["Combo"] = "&%</";
200
+ PromotionRuleSet$1["MealDeal"] = "&=^/";
201
+ return PromotionRuleSet$1;
202
+ }({});
203
+ /**
204
+ * Fetches promotions. Returns the flattened `Promotion[]`.
205
+ */
206
+ async function fetchPromotions(params = {}, options = {}) {
207
+ const extras = { deleted: params.deleted ?? false };
208
+ if (params.promotionIds) extras.promotionIds = params.promotionIds;
209
+ if (params.token) extras.token = params.token;
210
+ const json = await apiGet("/api/promotions", extras, options);
211
+ const error = getApiErrors(json.api);
212
+ if (error) throw new Error(error);
213
+ return Object.values(json.promotions ?? {});
214
+ }
215
+
216
+ //#endregion
217
+ //#region src/core/store.ts
218
+ /**
219
+ * Fetches stores. Returns the flattened `Store[]`.
220
+ */
221
+ async function fetchStores(params = {}, options = {}) {
222
+ const json = await apiGet("/api/stores", { deleted: params.deleted ?? false }, options);
223
+ const error = getApiErrors(json.api);
224
+ if (error) throw new Error(error);
225
+ return Object.values(json.stores ?? {});
226
+ }
227
+
228
+ //#endregion
229
+ //#region src/core/report.ts
230
+ /**
231
+ * Runs one or more report queries and returns the `reports` map keyed by query
232
+ * name. The query is JSON-encoded into the `extras.query` header, matching the
233
+ * original endpoint contract.
234
+ */
235
+ async function fetchReport(query, options = {}) {
236
+ const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
237
+ const error = getApiErrors(json.api);
238
+ if (error) throw new Error(error);
239
+ return json.reports ?? {};
240
+ }
241
+
242
+ //#endregion
243
+ //#region src/core/session.ts
244
+ /**
245
+ * Ends the current server session.
246
+ *
247
+ * This is the framework-agnostic half of the original `useLogout` hook. The
248
+ * Next.js-specific redirect logic (router.push to the auth server logout URL,
249
+ * built from server actions) is intentionally NOT included here because it is
250
+ * tied to `next/navigation` and `'use server'` actions — see the README.
251
+ */
252
+ async function logout(options = {}) {
253
+ await apiDelete("/api/session?action=logout", options);
254
+ }
255
+
256
+ //#endregion
257
+ //#region src/core/entity.ts
258
+ /**
259
+ * Generic CRUD-style mutations extracted from `useHandleDelete` /
260
+ * `useHandleSubmit`. The original hooks also surfaced notistack snackbars; that
261
+ * UI concern is deliberately left to the hooks/host layer so core stays
262
+ * dependency-free.
263
+ */
264
+ /**
265
+ * Deletes `/api/{resourcePath}/{entityId}`.
266
+ */
267
+ async function deleteEntity(resourcePath, entityId, options = {}) {
268
+ if (entityId == null || entityId === "") throw new Error("entityId is missing");
269
+ await apiDelete(`/api/${resourcePath}/${entityId}`, options);
270
+ }
271
+ /**
272
+ * Sends a create/update mutation and returns the parsed `result` field
273
+ * (matching the original `result.result` access).
274
+ */
275
+ async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
276
+ return (await apiSend(endpoint, method, payload, options)).result;
277
+ }
278
+
279
+ //#endregion
280
+ //#region src/core/basket.ts
281
+ const DEFAULT_BASKET_FILTERS = {
282
+ search: "",
283
+ dateRange: {
284
+ start: null,
285
+ end: null
286
+ },
287
+ consumerId: null,
288
+ status: "all"
289
+ };
290
+ const DEFAULT_PAGINATION = {
291
+ page: 0,
292
+ pageSize: 25,
293
+ total: 0
294
+ };
295
+ /** Formats a date as `YYYY-MM-DD HH:mm:ss` for the report query. */
296
+ function formatDateForQuery(date, isEndOfDay = false) {
297
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${isEndOfDay ? "23:59:59" : "00:00:00"}`;
298
+ }
299
+ /** Builds the `Basket` report query from a set of filters. */
300
+ function buildBasketReportQuery(filters) {
301
+ const whereConditions = [];
302
+ const today = /* @__PURE__ */ new Date();
303
+ const startDate = filters.dateRange.start ?? today;
304
+ const endDate = filters.dateRange.end ?? today;
305
+ whereConditions.push(`Basket.completeDate > '${formatDateForQuery(startDate, false)}'`);
306
+ whereConditions.push(`Basket.completeDate < '${formatDateForQuery(endDate, true)}'`);
307
+ if (filters.consumerId) whereConditions.push(`Basket.consumerId = ${filters.consumerId}`);
308
+ if (filters.status !== "all") whereConditions.push(`Basket.status = '${filters.status}'`);
309
+ return { baskets: {
310
+ FROM: "Basket",
311
+ WHERE: whereConditions.length > 0 ? { AND: whereConditions } : void 0
312
+ } };
313
+ }
314
+ /** Flattens the raw report rows/keys into structured baskets. */
315
+ function parseBasketReportData(reports) {
316
+ if (!reports?.baskets?.data || !reports?.baskets?.keys) return [];
317
+ const { keys, data } = reports.baskets;
318
+ const keyIndices = {};
319
+ keys.forEach((key, index) => {
320
+ keyIndices[key.name] = index;
321
+ });
322
+ const productKey = keys.find((k) => k.name === "Basket.Product");
323
+ const productSubKeys = {};
324
+ if (productKey?.item) productKey.item.forEach((subKey, index) => {
325
+ productSubKeys[subKey.name] = index;
326
+ });
327
+ return data.map((row) => {
328
+ const products = (row[keyIndices["Basket.Product"]] || []).map((productRow) => ({
329
+ productId: productRow[productSubKeys["Basket.Product.productId"]],
330
+ productName: `Product #${productRow[productSubKeys["Basket.Product.productId"]]}`,
331
+ quantity: productRow[productSubKeys["Basket.Product.quantity"]] || 1,
332
+ price: productRow[productSubKeys["Basket.Product.productPrice"]] || 0,
333
+ tax: productRow[productSubKeys["Basket.Product.productTaxPercentage"]] || 0
334
+ }));
335
+ const total = products.reduce((sum, p) => sum + p.price * p.quantity, 0);
336
+ const taxTotal = products.reduce((sum, p) => sum + p.price * p.quantity * p.tax, 0);
337
+ const completeDateTimestamp = row[keyIndices["Basket.completeDate"]];
338
+ const completeDate = completeDateTimestamp ? new Date(completeDateTimestamp).toISOString() : "";
339
+ return {
340
+ id: row[keyIndices["Basket.id"]],
341
+ name: row[keyIndices["Basket.name"]],
342
+ consumerId: row[keyIndices["Basket.consumerId"]],
343
+ products,
344
+ total,
345
+ taxTotal,
346
+ status: "completed",
347
+ completeDate
348
+ };
349
+ });
350
+ }
351
+ /** Unique non-null consumer ids referenced by a set of baskets. */
352
+ function collectConsumerIds(baskets) {
353
+ if (!baskets.length) return null;
354
+ const ids = baskets.map((b) => b.consumerId).filter((id) => id != null);
355
+ return ids.length > 0 ? [...new Set(ids)] : null;
356
+ }
357
+ /** Unique product ids referenced by a set of baskets. */
358
+ function collectProductIds(baskets) {
359
+ if (!baskets.length) return null;
360
+ const ids = /* @__PURE__ */ new Set();
361
+ baskets.forEach((basket) => {
362
+ basket.products.forEach((product) => {
363
+ if (product.productId) ids.add(product.productId);
364
+ });
365
+ });
366
+ return ids.size > 0 ? [...ids] : null;
367
+ }
368
+ /** Joins baskets with consumer + product lookups, applies search, sorts. */
369
+ function combineBasketRows({ baskets, consumerMap, productMap, search }) {
370
+ if (!baskets.length) return [];
371
+ let combinedRows = baskets.map((basket) => {
372
+ const consumer = basket.consumerId ? consumerMap[basket.consumerId] : null;
373
+ const productsWithNames = basket.products.map((p) => {
374
+ const product = productMap[p.productId];
375
+ return {
376
+ ...p,
377
+ productName: product?.name || `Product #${p.productId}`
378
+ };
379
+ });
380
+ return {
381
+ id: basket.id,
382
+ basketId: basket.id,
383
+ basketName: basket.name || `Basket #${basket.id}`,
384
+ consumerId: basket.consumerId,
385
+ consumerName: consumer ? `${consumer.firstName} ${consumer.lastName}` : "Unknown",
386
+ consumerEmail: consumer?.email || "-",
387
+ products: productsWithNames,
388
+ basketTotal: (basket.total || 0) / 100,
389
+ tax: basket.taxTotal || 0,
390
+ dateTime: basket.completeDate,
391
+ status: basket.status || "pending"
392
+ };
393
+ });
394
+ if (search) {
395
+ const searchLower = search.toLowerCase();
396
+ combinedRows = combinedRows.filter((row) => row.basketId.toString().includes(searchLower) || row.basketName.toLowerCase().includes(searchLower) || row.consumerName.toLowerCase().includes(searchLower) || row.consumerEmail.toLowerCase().includes(searchLower));
397
+ }
398
+ combinedRows.sort((a, b) => {
399
+ const dateA = new Date(a.dateTime).getTime();
400
+ return new Date(b.dateTime).getTime() - dateA;
401
+ });
402
+ return combinedRows;
403
+ }
404
+ /** Aggregate totals for a set of report rows. */
405
+ function computeBasketSummary(rows) {
406
+ const uniqueConsumerIds = new Set(rows.map((row) => row.consumerId));
407
+ return {
408
+ totalBaskets: rows.length,
409
+ totalRevenue: rows.reduce((sum, row) => sum + row.basketTotal, 0),
410
+ totalTax: rows.reduce((sum, row) => sum + row.tax, 0),
411
+ uniqueConsumers: uniqueConsumerIds.size
412
+ };
413
+ }
414
+ /** Builds a consumer id → consumer lookup. */
415
+ function buildConsumerMap(consumers) {
416
+ const map = {};
417
+ if (consumers) consumers.forEach((consumer) => {
418
+ map[consumer.id] = consumer;
419
+ });
420
+ return map;
421
+ }
422
+
423
+ //#endregion
424
+ //#region src/hooks/shared.ts
425
+ function toMessage(e) {
426
+ if (e instanceof Error) return e.message;
427
+ return String(e);
428
+ }
429
+ /**
430
+ * Internal primitive powering the auto-fetch hooks. Runs `fetcher` on mount and
431
+ * whenever `deps` change, tracks loading/error/data, cancels in-flight requests
432
+ * on unmount or dep change, and exposes `refetch`.
433
+ *
434
+ * When `enabled` is false the fetch is skipped (used for hooks that need
435
+ * arguments before they can run, e.g. consumers-by-id).
436
+ */
437
+ function useQuery(fetcher, deps, enabled = true) {
438
+ const [data, setData] = (0, react.useState)(null);
439
+ const [loading, setLoading] = (0, react.useState)(enabled);
440
+ const [error, setError] = (0, react.useState)(null);
441
+ const [tick, setTick] = (0, react.useState)(0);
442
+ const refetch = (0, react.useCallback)(() => setTick((t) => t + 1), []);
443
+ (0, react.useEffect)(() => {
444
+ if (!enabled) {
445
+ setLoading(false);
446
+ return;
447
+ }
448
+ const controller = new AbortController();
449
+ let active = true;
450
+ setData(null);
451
+ setError(null);
452
+ setLoading(true);
453
+ fetcher(controller.signal).then((result) => {
454
+ if (active) setData(result);
455
+ }).catch((e) => {
456
+ if (active && e?.name !== "AbortError") setError(toMessage(e));
457
+ }).finally(() => {
458
+ if (active) setLoading(false);
459
+ });
460
+ return () => {
461
+ active = false;
462
+ controller.abort();
463
+ };
464
+ }, [
465
+ ...deps,
466
+ tick,
467
+ enabled
468
+ ]);
469
+ return {
470
+ data,
471
+ loading,
472
+ error,
473
+ refetch
474
+ };
475
+ }
476
+ /**
477
+ * Internal primitive powering the manually-triggered hooks.
478
+ */
479
+ function useMutation(action, deps) {
480
+ const [data, setData] = (0, react.useState)(null);
481
+ const [loading, setLoading] = (0, react.useState)(false);
482
+ const [error, setError] = (0, react.useState)(null);
483
+ return {
484
+ mutate: (0, react.useCallback)(async (...args) => {
485
+ setLoading(true);
486
+ setError(null);
487
+ try {
488
+ const result = await action(...args);
489
+ setData(result);
490
+ return result;
491
+ } catch (e) {
492
+ setError(toMessage(e));
493
+ return null;
494
+ } finally {
495
+ setLoading(false);
496
+ }
497
+ }, deps),
498
+ data,
499
+ loading,
500
+ error
501
+ };
502
+ }
503
+
504
+ //#endregion
505
+ //#region src/hooks/useCategory.ts
506
+ /**
507
+ * Auto-fetches categories on mount and whenever the params change.
508
+ * Manual reloads are available via `refetch` (replaces the old `reloadFlag`).
509
+ */
510
+ function useCategory(params = {}, options) {
511
+ return useQuery((signal) => fetchCategories(params, {
512
+ ...options,
513
+ signal
514
+ }), [
515
+ JSON.stringify(params),
516
+ options?.baseUrl,
517
+ options?.token
518
+ ]);
519
+ }
520
+
521
+ //#endregion
522
+ //#region src/hooks/useConsumer.ts
523
+ /**
524
+ * Auto-fetches consumers by id. Disabled (no request) until at least one id is
525
+ * supplied, mirroring the original early-return behaviour.
526
+ */
527
+ function useConsumer(consumerIds, options) {
528
+ const enabled = !!consumerIds && consumerIds.length > 0;
529
+ return useQuery((signal) => fetchConsumers(consumerIds, {
530
+ ...options,
531
+ signal
532
+ }), [
533
+ JSON.stringify(consumerIds),
534
+ options?.baseUrl,
535
+ options?.token
536
+ ], enabled);
537
+ }
538
+ var useConsumer_default = useConsumer;
539
+
540
+ //#endregion
541
+ //#region src/hooks/useEmployee.ts
542
+ /**
543
+ * Auto-fetches employees on mount and whenever the params change.
544
+ */
545
+ function useEmployee(params = {}, options) {
546
+ return useQuery((signal) => fetchEmployees(params, {
547
+ ...options,
548
+ signal
549
+ }), [
550
+ JSON.stringify(params),
551
+ options?.baseUrl,
552
+ options?.token
553
+ ]);
554
+ }
555
+
556
+ //#endregion
557
+ //#region src/hooks/useProduct.ts
558
+ /**
559
+ * Auto-fetches products on mount and whenever the params change.
560
+ */
561
+ function useProduct(params = {}, options) {
562
+ return useQuery((signal) => fetchProducts(params, {
563
+ ...options,
564
+ signal
565
+ }), [
566
+ JSON.stringify(params),
567
+ options?.baseUrl,
568
+ options?.token
569
+ ]);
570
+ }
571
+
572
+ //#endregion
573
+ //#region src/hooks/usePromotion.ts
574
+ /**
575
+ * Auto-fetches promotions on mount and whenever the params change.
576
+ * Manual reloads are available via `refetch`.
577
+ */
578
+ function usePromotion(params = {}, options) {
579
+ return useQuery((signal) => fetchPromotions(params, {
580
+ ...options,
581
+ signal
582
+ }), [
583
+ JSON.stringify(params),
584
+ options?.baseUrl,
585
+ options?.token
586
+ ]);
587
+ }
588
+
589
+ //#endregion
590
+ //#region src/hooks/useStore.ts
591
+ /**
592
+ * Auto-fetches stores on mount. Manual reloads via `refetch`
593
+ * (replaces the old `reloadFlag` argument).
594
+ */
595
+ function useStore(params = {}, options) {
596
+ return useQuery((signal) => fetchStores(params, {
597
+ ...options,
598
+ signal
599
+ }), [
600
+ JSON.stringify(params),
601
+ options?.baseUrl,
602
+ options?.token
603
+ ]);
604
+ }
605
+
606
+ //#endregion
607
+ //#region src/hooks/useReport.ts
608
+ /**
609
+ * Auto-fetches one or more reports. Disabled (no request) until a non-null
610
+ * query is supplied, mirroring the original early-return behaviour.
611
+ */
612
+ function useReport(query, options) {
613
+ const enabled = !!query;
614
+ return useQuery((signal) => fetchReport(query, {
615
+ ...options,
616
+ signal
617
+ }), [
618
+ JSON.stringify(query),
619
+ options?.baseUrl,
620
+ options?.token
621
+ ], enabled);
622
+ }
623
+ var useReport_default = useReport;
624
+
625
+ //#endregion
626
+ //#region src/hooks/useDelete.ts
627
+ /**
628
+ * Manually-triggered delete. Returns `{ mutate, loading, error, data }`.
629
+ *
630
+ * Note: the original `useHandleDelete` surfaced notistack toasts. That UI
631
+ * concern is left to the host app — handle success/error from the returned
632
+ * state or the promise resolved by `mutate`.
633
+ *
634
+ * @example
635
+ * const { mutate, loading } = useDelete();
636
+ * await mutate("products", 42);
637
+ */
638
+ function useDelete(options) {
639
+ return useMutation((resourcePath, entityId) => deleteEntity(resourcePath, entityId, options), [options?.baseUrl, options?.token]);
640
+ }
641
+
642
+ //#endregion
643
+ //#region src/hooks/useSubmit.ts
644
+ /**
645
+ * Manually-triggered create/update. Returns `{ mutate, loading, error, data }`,
646
+ * where `data` is the API `result` payload.
647
+ *
648
+ * The original `useHandleSubmit` bundled validation, payload transformation and
649
+ * notistack toasts. Those are app concerns: resolve the endpoint/method/payload
650
+ * in your component and pass them in, then react to the returned state.
651
+ *
652
+ * @example
653
+ * const { mutate, loading } = useSubmit<Product>();
654
+ * const saved = await mutate({
655
+ * endpoint: id ? `/api/products/${id}` : "/api/products",
656
+ * method: id ? "PUT" : "POST",
657
+ * payload,
658
+ * });
659
+ */
660
+ function useSubmit(options) {
661
+ return useMutation((args) => submitEntity(args, options), [options?.baseUrl, options?.token]);
662
+ }
663
+
664
+ //#endregion
665
+ //#region src/hooks/useLogout.ts
666
+ /**
667
+ * Manually-triggered logout (ends the server session).
668
+ *
669
+ * This is framework-agnostic and does NOT redirect. The original `useLogout`
670
+ * also pushed the browser to the auth-server logout URL via `next/navigation`
671
+ * and `'use server'` actions; that part is Next-specific and stays in your app.
672
+ *
673
+ * @example
674
+ * const { mutate: doLogout } = useLogout();
675
+ * await doLogout();
676
+ * router.push(logoutUrl); // app-specific redirect
677
+ */
678
+ function useLogout(options) {
679
+ return useMutation(() => logout(options), [options?.baseUrl, options?.token]);
680
+ }
681
+
682
+ //#endregion
683
+ //#region src/hooks/useBasketReport.ts
684
+ /**
685
+ * Composite hook that orchestrates the report → consumers → products pipeline
686
+ * for the basket report screen. All the data-shaping logic now lives in the
687
+ * framework-agnostic `core/basket` helpers; this hook is the thin React glue
688
+ * (state, memoisation, effects) on top of them.
689
+ */
690
+ function useBasketReport(options) {
691
+ const [filters, setFiltersState] = (0, react.useState)(DEFAULT_BASKET_FILTERS);
692
+ const [pagination, setPaginationState] = (0, react.useState)(DEFAULT_PAGINATION);
693
+ const [refreshFlag, setRefreshFlag] = (0, react.useState)(0);
694
+ const { data: reports, loading: basketLoading, error: basketError } = useReport_default((0, react.useMemo)(() => buildBasketReportQuery(filters), [filters, refreshFlag]), options);
695
+ const basketData = (0, react.useMemo)(() => parseBasketReportData(reports), [reports]);
696
+ const consumerIds = (0, react.useMemo)(() => collectConsumerIds(basketData), [basketData]);
697
+ const productIds = (0, react.useMemo)(() => collectProductIds(basketData), [basketData]);
698
+ const { data: consumers, loading: consumerLoading, error: consumerError } = useConsumer_default(consumerIds, options);
699
+ const [productMap, setProductMap] = (0, react.useState)({});
700
+ const [productLoading, setProductLoading] = (0, react.useState)(false);
701
+ const [productError, setProductError] = (0, react.useState)(null);
702
+ (0, react.useEffect)(() => {
703
+ if (!productIds || productIds.length === 0) {
704
+ setProductMap({});
705
+ return;
706
+ }
707
+ let active = true;
708
+ setProductLoading(true);
709
+ setProductError(null);
710
+ fetchProductsMap(productIds, options).then((map) => {
711
+ if (active) setProductMap(map);
712
+ }).catch((e) => {
713
+ if (active) setProductError(e instanceof Error ? e.message : "Failed to fetch products");
714
+ }).finally(() => {
715
+ if (active) setProductLoading(false);
716
+ });
717
+ return () => {
718
+ active = false;
719
+ };
720
+ }, [
721
+ JSON.stringify(productIds),
722
+ options?.baseUrl,
723
+ options?.token
724
+ ]);
725
+ const consumerMap = (0, react.useMemo)(() => buildConsumerMap(consumers), [consumers]);
726
+ const rows = (0, react.useMemo)(() => combineBasketRows({
727
+ baskets: basketData,
728
+ consumerMap,
729
+ productMap,
730
+ search: filters.search
731
+ }), [
732
+ basketData,
733
+ consumerMap,
734
+ productMap,
735
+ filters.search
736
+ ]);
737
+ const summary = (0, react.useMemo)(() => computeBasketSummary(rows), [rows]);
738
+ (0, react.useEffect)(() => {
739
+ if (rows.length > 0 && rows.length === pagination.pageSize) setPaginationState((prev) => ({
740
+ ...prev,
741
+ total: (pagination.page + 2) * pagination.pageSize
742
+ }));
743
+ else if (rows.length > 0) setPaginationState((prev) => ({
744
+ ...prev,
745
+ total: pagination.page * pagination.pageSize + rows.length
746
+ }));
747
+ }, [
748
+ rows.length,
749
+ pagination.page,
750
+ pagination.pageSize
751
+ ]);
752
+ const setFilters = (0, react.useCallback)((newFilters) => {
753
+ setFiltersState((prev) => ({
754
+ ...prev,
755
+ ...newFilters
756
+ }));
757
+ setPaginationState((prev) => ({
758
+ ...prev,
759
+ page: 0
760
+ }));
761
+ }, []);
762
+ const setPagination = (0, react.useCallback)((page, pageSize) => {
763
+ setPaginationState((prev) => ({
764
+ ...prev,
765
+ page,
766
+ pageSize
767
+ }));
768
+ }, []);
769
+ const refresh = (0, react.useCallback)(() => {
770
+ setRefreshFlag((prev) => prev + 1);
771
+ }, []);
772
+ return {
773
+ rows,
774
+ summary,
775
+ loading: basketLoading || consumerLoading || productLoading,
776
+ error: basketError || consumerError || productError,
777
+ filters,
778
+ setFilters,
779
+ pagination,
780
+ setPagination,
781
+ refresh
782
+ };
783
+ }
784
+
785
+ //#endregion
786
+ exports.DEFAULT_BASKET_FILTERS = DEFAULT_BASKET_FILTERS;
787
+ exports.DEFAULT_PAGINATION = DEFAULT_PAGINATION;
788
+ exports.PromotionRuleSet = PromotionRuleSet;
789
+ exports.apiDelete = apiDelete;
790
+ exports.apiGet = apiGet;
791
+ exports.apiSend = apiSend;
792
+ exports.buildBasketReportQuery = buildBasketReportQuery;
793
+ exports.buildConsumerMap = buildConsumerMap;
794
+ exports.collectConsumerIds = collectConsumerIds;
795
+ exports.collectProductIds = collectProductIds;
796
+ exports.combineBasketRows = combineBasketRows;
797
+ exports.computeBasketSummary = computeBasketSummary;
798
+ exports.deleteEntity = deleteEntity;
799
+ exports.fetchCategories = fetchCategories;
800
+ exports.fetchConsumers = fetchConsumers;
801
+ exports.fetchEmployees = fetchEmployees;
802
+ exports.fetchProducts = fetchProducts;
803
+ exports.fetchProductsMap = fetchProductsMap;
804
+ exports.fetchPromotions = fetchPromotions;
805
+ exports.fetchReport = fetchReport;
806
+ exports.fetchStores = fetchStores;
807
+ exports.formatDateForQuery = formatDateForQuery;
808
+ exports.getApiErrors = getApiErrors;
809
+ exports.logout = logout;
810
+ exports.parseBasketReportData = parseBasketReportData;
811
+ exports.rolesById = rolesById;
812
+ exports.submitEntity = submitEntity;
813
+ exports.useBasketReport = useBasketReport;
814
+ exports.useCategory = useCategory;
815
+ exports.useConsumer = useConsumer;
816
+ exports.useDelete = useDelete;
817
+ exports.useEmployee = useEmployee;
818
+ exports.useLogout = useLogout;
819
+ exports.useProduct = useProduct;
820
+ exports.usePromotion = usePromotion;
821
+ exports.useReport = useReport;
822
+ exports.useStore = useStore;
823
+ exports.useSubmit = useSubmit;
824
+ //# sourceMappingURL=index.cjs.map