react-shopwave-connect 0.1.2 → 0.3.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["merged: Extras","headers: Record<string, string>","extras: Record<string, unknown>","extras: Record<string, unknown>","extras: Record<string, unknown>","result: { [id: number]: Product }","extras: Record<string, unknown>","DEFAULT_BASKET_FILTERS: BasketFilters","DEFAULT_PAGINATION: BasketPagination","whereConditions: string[]","keyIndices: { [key: string]: number }","productSubKeys: { [key: string]: number }","products: BasketItem[]","combinedRows: BasketReportRow[]","productsWithNames: BasketItem[]","map: { [id: number]: Consumer }","useReport","useConsumer","rows: BasketReportRow[]","summary: BasketSummary"],"sources":["../../src/core/request.ts","../../src/core/category.ts","../../src/core/consumer.ts","../../src/core/employee.ts","../../src/core/product.ts","../../src/core/promotion.ts","../../src/core/store.ts","../../src/core/report.ts","../../src/core/session.ts","../../src/core/entity.ts","../../src/core/basket.ts","../../src/hooks/shared.ts","../../src/hooks/useCategory.ts","../../src/hooks/useConsumer.ts","../../src/hooks/useEmployee.ts","../../src/hooks/useProduct.ts","../../src/hooks/usePromotion.ts","../../src/hooks/useStore.ts","../../src/hooks/useReport.ts","../../src/hooks/useDelete.ts","../../src/hooks/useSubmit.ts","../../src/hooks/useLogout.ts","../../src/hooks/useBasketReport.ts"],"sourcesContent":["import type { apiResponse } from \"./types\";\n\n/**\n * Per-call configuration shared by every core API function.\n *\n * Nothing here is React-specific. By accepting these options per call, the\n * core layer works in the browser/Next (relative `/api/...` URLs, the default)\n * AND in Node / CLI / Vue / Angular by passing an absolute `baseUrl`, an auth\n * `token`, and/or a custom `fetch` implementation.\n */\nexport interface RequestOptions {\n /**\n * Prefixed in front of every request path. Leave undefined in the browser to\n * use relative URLs (e.g. `/api/products`). In Node/CLI pass something like\n * `https://api.shopwave.com`.\n */\n baseUrl?: string;\n\n /** OAuth/session token forwarded in the `extras.token` header. */\n token?: string;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`. Useful for\n * Node < 18, testing, or injecting interceptors.\n */\n fetch?: typeof fetch;\n\n /** Optional abort signal for cancellation (used by the hooks layer). */\n signal?: AbortSignal;\n}\n\n/** The Shopwave API ships request metadata inside a single `extras` header. */\nexport type Extras = Record<string, unknown>;\n\nfunction resolveFetch(options: RequestOptions): typeof fetch {\n const f = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!f) {\n throw new Error(\n \"No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.\"\n );\n }\n return f;\n}\n\nfunction buildExtras(extras: Extras, options: RequestOptions): Extras {\n const merged: Extras = { \"Content-Type\": \"application/json\", ...extras };\n if (options.token != null && merged.token == null) {\n merged.token = options.token;\n }\n return merged;\n}\n\n/**\n * Returns a JSON string of API errors if the response carries any, otherwise\n * `null`. Mirrors the original `!responseJson.api.message.errors` checks.\n */\nexport function getApiErrors(api?: apiResponse): string | null {\n const errors = api?.message?.errors;\n if (errors && Object.keys(errors).length > 0) {\n return JSON.stringify(errors);\n }\n return null;\n}\n\n/**\n * GET helper. Sends request metadata via the `extras` header exactly like the\n * original hooks did, parses JSON, and returns the typed payload.\n */\nexport async function apiGet<T>(\n path: string,\n extras: Extras,\n options: RequestOptions = {}\n): Promise<T> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n const headers = { extras: JSON.stringify(buildExtras(extras, options)) };\n console.log('[API GET]', { url, headers });\n\n const response = await doFetch(url, {\n method: \"GET\",\n headers,\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Network response was not ok (${response.status})`);\n }\n\n return (await response.json()) as T;\n}\n\n/**\n * JSON body helper for POST/PUT mutations.\n */\nexport async function apiSend<T>(\n path: string,\n method: \"POST\" | \"PUT\",\n payload: unknown,\n options: RequestOptions = {}\n): Promise<T> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (options.token != null) {\n headers.token = options.token;\n }\n\n console.log(`[API ${method}]`, { url, headers, payload });\n\n const response = await doFetch(url, {\n method,\n headers,\n body: JSON.stringify(payload),\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to ${method.toLowerCase()}: ${response.statusText}`);\n }\n\n return (await response.json()) as T;\n}\n\n/**\n * DELETE helper.\n */\nexport async function apiDelete(\n path: string,\n options: RequestOptions = {}\n): Promise<void> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n const headers: Record<string, string> = {};\n if (options.token != null) {\n headers.token = options.token;\n }\n\n console.log('[API DELETE]', { url, headers });\n\n const response = await doFetch(url, {\n method: \"DELETE\",\n headers,\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete: ${response.statusText}`);\n }\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Category {\n id: number;\n title: string;\n parentId: number | null;\n deleteDate: string;\n activeDate: string;\n type: number;\n}\n\nexport interface CategoryResponse {\n categories: {\n [key: number]: Category;\n };\n api: apiResponse;\n}\n\nexport interface FetchCategoriesParams {\n categoryIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Token override (also accepted via `options.token`). */\n token?: string;\n}\n\n/**\n * Fetches categories. Framework-agnostic: returns the flattened `Category[]`\n * or throws on API errors.\n */\nexport async function fetchCategories(\n params: FetchCategoriesParams = {},\n options: RequestOptions = {}\n): Promise<Category[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.categoryIds) {\n extras.categoryIds = params.categoryIds;\n }\n if (params.token) {\n extras.token = params.token;\n }\n\n const json = await apiGet<CategoryResponse>(\"/api/categories\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.categories ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Consumer {\n id: number;\n activeCount: number;\n firstName: string;\n lastName: string;\n email: string;\n}\n\nexport interface ConsumerResponse {\n consumers: {\n [key: number]: Consumer;\n };\n api: apiResponse;\n}\n\n/**\n * Fetches consumers by id. Returns an empty array when no ids are supplied\n * (the original hook short-circuited in that case).\n */\nexport async function fetchConsumers(\n consumerIds: number[] | null | undefined,\n options: RequestOptions = {}\n): Promise<Consumer[]> {\n if (!consumerIds || consumerIds.length === 0) {\n return [];\n }\n\n const extras: Record<string, unknown> = { ids: consumerIds.join(\",\") };\n\n const json = await apiGet<ConsumerResponse>(\"/api/consumer\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.consumers ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport const rolesById = {\n \"1\": \"Owner\",\n \"2\": \"Manager\",\n \"3\": \"Assistant\",\n \"4\": \"Guest\",\n \"5\": \"Assistant Manager\",\n} as const;\n\nexport interface Employee {\n id: number;\n firstName: string;\n lastName: string;\n email: string;\n roleId: number;\n joinedDate: string;\n exitDate: string;\n stores: {\n [key: number]: {\n id: number;\n roleId: number;\n };\n };\n}\n\nexport interface EmployeeResponse {\n employees: {\n [key: number]: Employee;\n };\n api: apiResponse;\n}\n\nexport interface FetchEmployeesParams {\n /** Optional id filter. Forwarded when provided. */\n employeeIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n}\n\n/**\n * Fetches employees. Returns the flattened `Employee[]`.\n */\nexport async function fetchEmployees(\n params: FetchEmployeesParams = {},\n options: RequestOptions = {}\n): Promise<Employee[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.employeeIds) {\n extras.employeeIds = params.employeeIds;\n }\n\n const json = await apiGet<EmployeeResponse>(\"/api/employees\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.employees ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ProductInstance {\n id: number;\n price: string;\n taxPercentage: string;\n timestamp: string;\n size: string;\n name: string;\n activeDate: string;\n tags: string;\n}\n\nexport interface Product {\n id: number;\n barcode: string;\n name: string;\n details: string;\n tags: string;\n unit: number;\n timestamp: string;\n activeDate: string;\n deleteDate: string;\n images: Array<string>;\n categories: Array<number>;\n instances: {\n [key: number]: ProductInstance;\n };\n}\n\nexport interface ProductResponse {\n products: {\n [key: number]: Product;\n };\n api: apiResponse;\n}\n\nexport interface FetchProductsParams {\n productIds?: Array<number>;\n storeId?: number;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Token override (also accepted via `options.token`). */\n token?: string;\n}\n\n/**\n * Fetches products. Returns the flattened `Product[]`.\n */\nexport async function fetchProducts(\n params: FetchProductsParams = {},\n options: RequestOptions = {}\n): Promise<Product[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.productIds) {\n extras.productIds = params.productIds;\n }\n if (params.storeId) {\n extras.storeId = params.storeId;\n }\n if (params.token) {\n extras.token = params.token;\n }\n\n const json = await apiGet<ProductResponse>(\"/api/products\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.products ?? {});\n}\n\n/**\n * Fetches products by id and returns them keyed by id, transparently batching\n * the request (default 200 ids per call) to stay within API limits. Extracted\n * from the original `useBasketReport` inline batching loop so it can be reused\n * outside React.\n */\nexport async function fetchProductsMap(\n productIds: number[] | null | undefined,\n options: RequestOptions = {},\n batchSize = 200\n): Promise<{ [id: number]: Product }> {\n const result: { [id: number]: Product } = {};\n if (!productIds || productIds.length === 0) {\n return result;\n }\n\n for (let i = 0; i < productIds.length; i += batchSize) {\n const batch = productIds.slice(i, i + batchSize);\n const products = await fetchProducts({ productIds: batch }, options);\n for (const product of products) {\n result[product.id] = product;\n }\n }\n\n return result;\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\n/** Promotion rule identifiers as stored by the Shopwave API. */\nexport enum PromotionRuleSet {\n Percentage = \"|%^/\",\n Combo = \"&%</\",\n MealDeal = \"&=^/\",\n}\n\nexport interface Promotion {\n id: number;\n merchantId: number;\n storeIds: number[];\n categoryIds: { [key: string]: number };\n productIds: { [key: string]: any };\n title: string;\n details: string | null;\n terms: string | null;\n startDate: string;\n endDate: string;\n daysAvailable: number[];\n automatic: number;\n rule: PromotionRuleSet;\n x: number;\n y: number | null;\n}\n\nexport interface PromotionResponse {\n promotions: {\n [key: number]: Promotion;\n };\n api: apiResponse;\n}\n\nexport interface FetchPromotionsParams {\n /** Optional id filter. Forwarded when provided. */\n promotionIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Filter for imminent promotions. */\n imminent?: boolean;\n /** Filter for active promotions. */\n active?: boolean;\n /** Filter for expired promotions. */\n expired?: boolean;\n /** Token override (also accepted via `options.token`). */\n token?: string;\n}\n\n/**\n * Fetches promotions. Returns the flattened `Promotion[]`.\n */\nexport async function fetchPromotions(\n params: FetchPromotionsParams = {},\n options: RequestOptions = {}\n): Promise<Promotion[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.promotionIds) {\n extras.promotionIds = params.promotionIds;\n }\n if (params.imminent !== undefined) {\n extras.imminent = params.imminent;\n }\n if (params.active !== undefined) {\n extras.active = params.active;\n }\n if (params.expired !== undefined) {\n extras.expired = params.expired;\n }\n if (params.token) {\n extras.token = params.token;\n }\n\n const json = await apiGet<PromotionResponse>(\"/api/promotions\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.promotions ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Store {\n id: number;\n lat: number;\n lng: number;\n addressLine1: string;\n addressLine2: string | null;\n addressLine3: string;\n phoneNumber: string;\n city: string;\n postcode: string;\n countryId: number | null;\n timezoneId: number | null;\n storeDeleteDate: string | null;\n email: string | null;\n sandbox: boolean | null;\n}\n\nexport interface StoreResponse {\n stores: {\n [key: number]: Store;\n };\n api: apiResponse;\n}\n\nexport interface FetchStoresParams {\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n}\n\n/**\n * Fetches stores. Returns the flattened `Store[]`.\n */\nexport async function fetchStores(\n params: FetchStoresParams = {},\n options: RequestOptions = {}\n): Promise<Store[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n const json = await apiGet<StoreResponse>(\"/api/stores\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return Object.values(json.stores ?? {});\n}\n","import { apiGet, getApiErrors, type RequestOptions } from \"./request\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ReportKey {\n name: string;\n type: string;\n item?: ReportKey[];\n}\n\nexport interface ReportData {\n keys: ReportKey[];\n data: any[][];\n}\n\nexport interface ReportResponse {\n reports: {\n [key: string]: ReportData;\n };\n api: apiResponse;\n}\n\nexport interface ReportQuery {\n FROM: string;\n WHERE?: {\n AND?: string[];\n OR?: string[];\n };\n SELECT?: string[];\n LIMIT?: number;\n OFFSET?: number;\n}\n\nexport interface ReportQueryMap {\n [key: string]: ReportQuery;\n}\n\n/**\n * Runs one or more report queries and returns the `reports` map keyed by query\n * name. The query is JSON-encoded into the `extras.query` header, matching the\n * original endpoint contract.\n */\nexport async function fetchReport(\n query: ReportQueryMap,\n options: RequestOptions = {}\n): Promise<ReportResponse[\"reports\"]> {\n const extras: Record<string, unknown> = { query: JSON.stringify(query) };\n\n const json = await apiGet<ReportResponse>(\"/api/report\", extras, options);\n\n const error = getApiErrors(json.api);\n if (error) {\n throw new Error(error);\n }\n\n return json.reports ?? {};\n}\n","import { apiDelete, type RequestOptions } from \"./request\";\n\n/**\n * Ends the current server session.\n *\n * This is the framework-agnostic half of the original `useLogout` hook. The\n * Next.js-specific redirect logic (router.push to the auth server logout URL,\n * built from server actions) is intentionally NOT included here because it is\n * tied to `next/navigation` and `'use server'` actions — see the README.\n */\nexport async function logout(options: RequestOptions = {}): Promise<void> {\n await apiDelete(\"/api/session?action=logout\", options);\n}\n","import { apiDelete, apiSend, type RequestOptions } from \"./request\";\n\n/**\n * Generic CRUD-style mutations extracted from `useHandleDelete` /\n * `useHandleSubmit`. The original hooks also surfaced notistack snackbars; that\n * UI concern is deliberately left to the hooks/host layer so core stays\n * dependency-free.\n */\n\n/**\n * Deletes `/api/{resourcePath}/{entityId}`.\n */\nexport async function deleteEntity(\n resourcePath: string,\n entityId: number | string,\n options: RequestOptions = {}\n): Promise<void> {\n if (entityId == null || entityId === \"\") {\n throw new Error(\"entityId is missing\");\n }\n await apiDelete(`/api/${resourcePath}/${entityId}`, options);\n}\n\nexport interface SubmitEntityArgs {\n /** Fully-resolved endpoint, e.g. `/api/products` or `/api/products/42`. */\n endpoint: string;\n /** Defaults to PUT when an `entityId` exists, otherwise POST. */\n method?: \"POST\" | \"PUT\";\n /** The request payload (already transformed by the caller). */\n payload: unknown;\n}\n\nexport interface SubmitEntityResponse<R = any> {\n result?: R;\n [key: string]: unknown;\n}\n\n/**\n * Sends a create/update mutation and returns the parsed `result` field\n * (matching the original `result.result` access).\n */\nexport async function submitEntity<R = any>(\n { endpoint, method = \"POST\", payload }: SubmitEntityArgs,\n options: RequestOptions = {}\n): Promise<R | undefined> {\n const json = await apiSend<SubmitEntityResponse<R>>(\n endpoint,\n method,\n payload,\n options\n );\n return json.result;\n}\n","import type { Consumer } from \"./consumer\";\nimport type { Product } from \"./product\";\nimport type { ReportQueryMap, ReportResponse } from \"./report\";\n\n// ── Basket Report Types (preserved from the original `types/basket.ts`) ──────\n\nexport interface BasketItem {\n productId: number;\n productName: string;\n quantity: number;\n price: number;\n tax: number;\n}\n\nexport interface Basket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"pending\" | \"completed\" | \"cancelled\";\n completeDate: string;\n}\n\nexport interface BasketReportRow {\n id: number;\n basketId: number;\n basketName: string;\n consumerId: number;\n consumerName: string;\n consumerEmail: string;\n products: BasketItem[];\n basketTotal: number;\n tax: number;\n dateTime: string;\n status: string;\n}\n\nexport interface BasketSummary {\n totalBaskets: number;\n totalRevenue: number;\n totalTax: number;\n uniqueConsumers: number;\n}\n\nexport interface BasketFilters {\n search: string;\n dateRange: {\n start: Date | null;\n end: Date | null;\n };\n consumerId: number | null;\n status: \"all\" | \"pending\" | \"completed\" | \"cancelled\";\n}\n\nexport interface BasketPagination {\n page: number;\n pageSize: number;\n total: number;\n}\n\nexport const DEFAULT_BASKET_FILTERS: BasketFilters = {\n search: \"\",\n dateRange: {\n start: null,\n end: null,\n },\n consumerId: null,\n status: \"all\",\n};\n\nexport const DEFAULT_PAGINATION: BasketPagination = {\n page: 0,\n pageSize: 25,\n total: 0,\n};\n\n/** Intermediate shape produced by {@link parseBasketReportData}. */\nexport interface ParsedBasket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"completed\";\n completeDate: string;\n}\n\n// ── Pure transform helpers (extracted from the original `useBasketReport`) ────\n\n/** Formats a date as `YYYY-MM-DD HH:mm:ss` for the report query. */\nexport function formatDateForQuery(date: Date, isEndOfDay = false): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const time = isEndOfDay ? \"23:59:59\" : \"00:00:00\";\n return `${year}-${month}-${day} ${time}`;\n}\n\n/** Builds the `Basket` report query from a set of filters. */\nexport function buildBasketReportQuery(filters: BasketFilters): ReportQueryMap {\n const whereConditions: string[] = [];\n\n const today = new Date();\n const startDate = filters.dateRange.start ?? today;\n const endDate = filters.dateRange.end ?? today;\n\n whereConditions.push(\n `Basket.completeDate > '${formatDateForQuery(startDate, false)}'`\n );\n whereConditions.push(\n `Basket.completeDate < '${formatDateForQuery(endDate, true)}'`\n );\n\n if (filters.consumerId) {\n whereConditions.push(`Basket.consumerId = ${filters.consumerId}`);\n }\n\n if (filters.status !== \"all\") {\n whereConditions.push(`Basket.status = '${filters.status}'`);\n }\n\n return {\n baskets: {\n FROM: \"Basket\",\n WHERE: whereConditions.length > 0 ? { AND: whereConditions } : undefined,\n },\n };\n}\n\n/** Flattens the raw report rows/keys into structured baskets. */\nexport function parseBasketReportData(\n reports: ReportResponse[\"reports\"] | null | undefined\n): ParsedBasket[] {\n if (!reports?.baskets?.data || !reports?.baskets?.keys) {\n return [];\n }\n\n const { keys, data } = reports.baskets;\n\n const keyIndices: { [key: string]: number } = {};\n keys.forEach((key, index) => {\n keyIndices[key.name] = index;\n });\n\n const productKey = keys.find((k) => k.name === \"Basket.Product\");\n const productSubKeys: { [key: string]: number } = {};\n if (productKey?.item) {\n productKey.item.forEach((subKey, index) => {\n productSubKeys[subKey.name] = index;\n });\n }\n\n return data.map((row) => {\n const productsArray = (row[keyIndices[\"Basket.Product\"]] as any[][]) || [];\n const products: BasketItem[] = productsArray.map((productRow) => ({\n productId: productRow[productSubKeys[\"Basket.Product.productId\"]] as number,\n productName: `Product #${productRow[productSubKeys[\"Basket.Product.productId\"]]}`,\n quantity:\n (productRow[productSubKeys[\"Basket.Product.quantity\"]] as number) || 1,\n price:\n (productRow[productSubKeys[\"Basket.Product.productPrice\"]] as number) ||\n 0,\n tax:\n (productRow[\n productSubKeys[\"Basket.Product.productTaxPercentage\"]\n ] as number) || 0,\n }));\n\n const total = products.reduce((sum, p) => sum + p.price * p.quantity, 0);\n const taxTotal = products.reduce(\n (sum, p) => sum + p.price * p.quantity * p.tax,\n 0\n );\n\n const completeDateTimestamp = row[keyIndices[\"Basket.completeDate\"]] as number;\n const completeDate = completeDateTimestamp\n ? new Date(completeDateTimestamp).toISOString()\n : \"\";\n\n return {\n id: row[keyIndices[\"Basket.id\"]] as number,\n name: row[keyIndices[\"Basket.name\"]] as string,\n consumerId: row[keyIndices[\"Basket.consumerId\"]] as number,\n products,\n total,\n taxTotal,\n status: \"completed\" as const,\n completeDate,\n };\n });\n}\n\n/** Unique non-null consumer ids referenced by a set of baskets. */\nexport function collectConsumerIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = baskets\n .map((b) => b.consumerId)\n .filter((id): id is number => id != null);\n return ids.length > 0 ? [...new Set(ids)] : null;\n}\n\n/** Unique product ids referenced by a set of baskets. */\nexport function collectProductIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = new Set<number>();\n baskets.forEach((basket) => {\n basket.products.forEach((product) => {\n if (product.productId) {\n ids.add(product.productId);\n }\n });\n });\n return ids.size > 0 ? [...ids] : null;\n}\n\nexport interface CombineBasketRowsParams {\n baskets: ParsedBasket[];\n consumerMap: { [id: number]: Consumer };\n productMap: { [id: number]: Product };\n search?: string;\n}\n\n/** Joins baskets with consumer + product lookups, applies search, sorts. */\nexport function combineBasketRows({\n baskets,\n consumerMap,\n productMap,\n search,\n}: CombineBasketRowsParams): BasketReportRow[] {\n if (!baskets.length) return [];\n\n let combinedRows: BasketReportRow[] = baskets.map((basket) => {\n const consumer = basket.consumerId ? consumerMap[basket.consumerId] : null;\n\n const productsWithNames: BasketItem[] = basket.products.map((p) => {\n const product = productMap[p.productId];\n return {\n ...p,\n productName: product?.name || `Product #${p.productId}`,\n };\n });\n\n return {\n id: basket.id,\n basketId: basket.id,\n basketName: basket.name || `Basket #${basket.id}`,\n consumerId: basket.consumerId,\n consumerName: consumer\n ? `${consumer.firstName} ${consumer.lastName}`\n : \"Unknown\",\n consumerEmail: consumer?.email || \"-\",\n products: productsWithNames,\n basketTotal: (basket.total || 0) / 100,\n tax: basket.taxTotal || 0,\n dateTime: basket.completeDate,\n status: basket.status || \"pending\",\n };\n });\n\n if (search) {\n const searchLower = search.toLowerCase();\n combinedRows = combinedRows.filter(\n (row) =>\n row.basketId.toString().includes(searchLower) ||\n row.basketName.toLowerCase().includes(searchLower) ||\n row.consumerName.toLowerCase().includes(searchLower) ||\n row.consumerEmail.toLowerCase().includes(searchLower)\n );\n }\n\n combinedRows.sort((a, b) => {\n const dateA = new Date(a.dateTime).getTime();\n const dateB = new Date(b.dateTime).getTime();\n return dateB - dateA;\n });\n\n return combinedRows;\n}\n\n/** Aggregate totals for a set of report rows. */\nexport function computeBasketSummary(rows: BasketReportRow[]): BasketSummary {\n const uniqueConsumerIds = new Set(rows.map((row) => row.consumerId));\n return {\n totalBaskets: rows.length,\n totalRevenue: rows.reduce((sum, row) => sum + row.basketTotal, 0),\n totalTax: rows.reduce((sum, row) => sum + row.tax, 0),\n uniqueConsumers: uniqueConsumerIds.size,\n };\n}\n\n/** Builds a consumer id → consumer lookup. */\nexport function buildConsumerMap(consumers: Consumer[] | null | undefined): {\n [id: number]: Consumer;\n} {\n const map: { [id: number]: Consumer } = {};\n if (consumers) {\n consumers.forEach((consumer) => {\n map[consumer.id] = consumer;\n });\n }\n return map;\n}\n","import { useCallback, useEffect, useState } from \"react\";\n\n/**\n * Consistent return shape for every auto-fetching (\"query\") hook.\n */\nexport interface QueryState<T> {\n data: T | null;\n loading: boolean;\n error: string | null;\n /** Re-runs the underlying fetch. */\n refetch: () => void;\n}\n\n/**\n * Consistent return shape for manually-triggered (\"mutation\") hooks.\n */\nexport interface MutationState<TData, TArgs extends any[]> {\n /** Triggers the request. Resolves to the result, or `null` on error. */\n mutate: (...args: TArgs) => Promise<TData | null>;\n data: TData | null;\n loading: boolean;\n error: string | null;\n}\n\nfunction toMessage(e: unknown): string {\n if (e instanceof Error) return e.message;\n return String(e);\n}\n\n/**\n * Internal primitive powering the auto-fetch hooks. Runs `fetcher` on mount and\n * whenever `deps` change, tracks loading/error/data, cancels in-flight requests\n * on unmount or dep change, and exposes `refetch`.\n *\n * When `enabled` is false the fetch is skipped (used for hooks that need\n * arguments before they can run, e.g. consumers-by-id).\n */\nexport function useQuery<T>(\n fetcher: (signal: AbortSignal) => Promise<T>,\n deps: ReadonlyArray<unknown>,\n enabled = true\n): QueryState<T> {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState<boolean>(enabled);\n const [error, setError] = useState<string | null>(null);\n const [tick, setTick] = useState(0);\n\n const refetch = useCallback(() => setTick((t) => t + 1), []);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n return;\n }\n\n const controller = new AbortController();\n let active = true;\n\n setData(null);\n setError(null);\n setLoading(true);\n\n fetcher(controller.signal)\n .then((result) => {\n if (active) setData(result);\n })\n .catch((e) => {\n if (active && (e as { name?: string })?.name !== \"AbortError\") {\n setError(toMessage(e));\n }\n })\n .finally(() => {\n if (active) setLoading(false);\n });\n\n return () => {\n active = false;\n controller.abort();\n };\n // `fetcher` is intentionally excluded; callers pass stable `deps`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [...deps, tick, enabled]);\n\n return { data, loading, error, refetch };\n}\n\n/**\n * Internal primitive powering the manually-triggered hooks.\n */\nexport function useMutation<TData, TArgs extends any[]>(\n action: (...args: TArgs) => Promise<TData>,\n deps: ReadonlyArray<unknown>\n): MutationState<TData, TArgs> {\n const [data, setData] = useState<TData | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const mutate = useCallback(\n async (...args: TArgs): Promise<TData | null> => {\n setLoading(true);\n setError(null);\n try {\n const result = await action(...args);\n setData(result);\n return result;\n } catch (e) {\n setError(toMessage(e));\n return null;\n } finally {\n setLoading(false);\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n deps\n );\n\n return { mutate, data, loading, error };\n}\n","import {\n fetchCategories,\n type Category,\n type FetchCategoriesParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches categories on mount and whenever the params change.\n * Manual reloads are available via `refetch` (replaces the old `reloadFlag`).\n */\nexport function useCategory(\n params: FetchCategoriesParams = {},\n options?: RequestOptions\n): QueryState<Category[]> {\n return useQuery<Category[]>(\n (signal) => fetchCategories(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useCategory;\n","import {\n fetchConsumers,\n type Consumer,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches consumers by id. Disabled (no request) until at least one id is\n * supplied, mirroring the original early-return behaviour.\n */\nexport function useConsumer(\n consumerIds: number[] | null,\n options?: RequestOptions\n): QueryState<Consumer[]> {\n const enabled = !!consumerIds && consumerIds.length > 0;\n return useQuery<Consumer[]>(\n (signal) => fetchConsumers(consumerIds, { ...options, signal }),\n [JSON.stringify(consumerIds), options?.baseUrl, options?.token],\n enabled\n );\n}\n\nexport default useConsumer;\n","import {\n fetchEmployees,\n type Employee,\n type FetchEmployeesParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches employees on mount and whenever the params change.\n */\nexport function useEmployee(\n params: FetchEmployeesParams = {},\n options?: RequestOptions\n): QueryState<Employee[]> {\n return useQuery<Employee[]>(\n (signal) => fetchEmployees(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useEmployee;\n","import {\n fetchProducts,\n type Product,\n type FetchProductsParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches products on mount and whenever the params change.\n */\nexport function useProduct(\n params: FetchProductsParams = {},\n options?: RequestOptions\n): QueryState<Product[]> {\n return useQuery<Product[]>(\n (signal) => fetchProducts(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useProduct;\n","import {\n fetchPromotions,\n type Promotion,\n type FetchPromotionsParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches promotions on mount and whenever the params change.\n * Manual reloads are available via `refetch`.\n */\nexport function usePromotion(\n params: FetchPromotionsParams = {},\n options?: RequestOptions\n): QueryState<Promotion[]> {\n return useQuery<Promotion[]>(\n (signal) => fetchPromotions(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default usePromotion;\n","import {\n fetchStores,\n type Store,\n type FetchStoresParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches stores on mount. Manual reloads via `refetch`\n * (replaces the old `reloadFlag` argument).\n */\nexport function useStore(\n params: FetchStoresParams = {},\n options?: RequestOptions\n): QueryState<Store[]> {\n return useQuery<Store[]>(\n (signal) => fetchStores(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useStore;\n","import {\n fetchReport,\n type ReportQueryMap,\n type ReportResponse,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches one or more reports. Disabled (no request) until a non-null\n * query is supplied, mirroring the original early-return behaviour.\n */\nexport function useReport(\n query: ReportQueryMap | null,\n options?: RequestOptions\n): QueryState<ReportResponse[\"reports\"]> {\n const enabled = !!query;\n return useQuery<ReportResponse[\"reports\"]>(\n (signal) => fetchReport(query as ReportQueryMap, { ...options, signal }),\n [JSON.stringify(query), options?.baseUrl, options?.token],\n enabled\n );\n}\n\nexport default useReport;\n","import { deleteEntity, type RequestOptions } from \"../core\";\nimport { useMutation, type MutationState } from \"./shared\";\n\n/**\n * Manually-triggered delete. Returns `{ mutate, loading, error, data }`.\n *\n * Note: the original `useHandleDelete` surfaced notistack toasts. That UI\n * concern is left to the host app — handle success/error from the returned\n * state or the promise resolved by `mutate`.\n *\n * @example\n * const { mutate, loading } = useDelete();\n * await mutate(\"products\", 42);\n */\nexport function useDelete(\n options?: RequestOptions\n): MutationState<void, [resourcePath: string, entityId: number | string]> {\n return useMutation<void, [string, number | string]>(\n (resourcePath, entityId) => deleteEntity(resourcePath, entityId, options),\n [options?.baseUrl, options?.token]\n );\n}\n\nexport default useDelete;\n","import {\n submitEntity,\n type SubmitEntityArgs,\n type RequestOptions,\n} from \"../core\";\nimport { useMutation, type MutationState } from \"./shared\";\n\n/**\n * Manually-triggered create/update. Returns `{ mutate, loading, error, data }`,\n * where `data` is the API `result` payload.\n *\n * The original `useHandleSubmit` bundled validation, payload transformation and\n * notistack toasts. Those are app concerns: resolve the endpoint/method/payload\n * in your component and pass them in, then react to the returned state.\n *\n * @example\n * const { mutate, loading } = useSubmit<Product>();\n * const saved = await mutate({\n * endpoint: id ? `/api/products/${id}` : \"/api/products\",\n * method: id ? \"PUT\" : \"POST\",\n * payload,\n * });\n */\nexport function useSubmit<R = any>(\n options?: RequestOptions\n): MutationState<R | undefined, [args: SubmitEntityArgs]> {\n return useMutation<R | undefined, [SubmitEntityArgs]>(\n (args) => submitEntity<R>(args, options),\n [options?.baseUrl, options?.token]\n );\n}\n\nexport default useSubmit;\n","import { logout, type RequestOptions } from \"../core\";\nimport { useMutation, type MutationState } from \"./shared\";\n\n/**\n * Manually-triggered logout (ends the server session).\n *\n * This is framework-agnostic and does NOT redirect. The original `useLogout`\n * also pushed the browser to the auth-server logout URL via `next/navigation`\n * and `'use server'` actions; that part is Next-specific and stays in your app.\n *\n * @example\n * const { mutate: doLogout } = useLogout();\n * await doLogout();\n * router.push(logoutUrl); // app-specific redirect\n */\nexport function useLogout(\n options?: RequestOptions\n): MutationState<void, []> {\n return useMutation<void, []>(\n () => logout(options),\n [options?.baseUrl, options?.token]\n );\n}\n\nexport default useLogout;\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport {\n buildBasketReportQuery,\n buildConsumerMap,\n collectConsumerIds,\n collectProductIds,\n combineBasketRows,\n computeBasketSummary,\n fetchProductsMap,\n parseBasketReportData,\n DEFAULT_BASKET_FILTERS,\n DEFAULT_PAGINATION,\n type BasketFilters,\n type BasketPagination,\n type BasketReportRow,\n type BasketSummary,\n type Product,\n type RequestOptions,\n} from \"../core\";\nimport useReport from \"./useReport\";\nimport useConsumer from \"./useConsumer\";\n\nexport interface UseBasketReportReturn {\n rows: BasketReportRow[];\n summary: BasketSummary;\n loading: boolean;\n error: string | null;\n filters: BasketFilters;\n setFilters: (filters: Partial<BasketFilters>) => void;\n pagination: BasketPagination;\n setPagination: (page: number, pageSize: number) => void;\n refresh: () => void;\n}\n\n/**\n * Composite hook that orchestrates the report → consumers → products pipeline\n * for the basket report screen. All the data-shaping logic now lives in the\n * framework-agnostic `core/basket` helpers; this hook is the thin React glue\n * (state, memoisation, effects) on top of them.\n */\nexport function useBasketReport(\n options?: RequestOptions\n): UseBasketReportReturn {\n const [filters, setFiltersState] = useState<BasketFilters>(\n DEFAULT_BASKET_FILTERS\n );\n const [pagination, setPaginationState] = useState<BasketPagination>(\n DEFAULT_PAGINATION\n );\n const [refreshFlag, setRefreshFlag] = useState(0);\n\n // Build the report query from filters (core helper).\n const reportQuery = useMemo(\n () => buildBasketReportQuery(filters),\n // refreshFlag forces a new object identity to re-trigger the fetch.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [filters, refreshFlag]\n );\n\n const {\n data: reports,\n loading: basketLoading,\n error: basketError,\n } = useReport(reportQuery, options);\n\n // Flatten raw report rows into structured baskets (core helper).\n const basketData = useMemo(() => parseBasketReportData(reports), [reports]);\n\n // Derive id sets for the dependent fetches (core helpers).\n const consumerIds = useMemo(\n () => collectConsumerIds(basketData),\n [basketData]\n );\n const productIds = useMemo(\n () => collectProductIds(basketData),\n [basketData]\n );\n\n const {\n data: consumers,\n loading: consumerLoading,\n error: consumerError,\n } = useConsumer(consumerIds, options);\n\n // Batched product fetch (core helper handles the 200-per-request batching).\n const [productMap, setProductMap] = useState<{ [id: number]: Product }>({});\n const [productLoading, setProductLoading] = useState(false);\n const [productError, setProductError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!productIds || productIds.length === 0) {\n setProductMap({});\n return;\n }\n\n let active = true;\n setProductLoading(true);\n setProductError(null);\n\n fetchProductsMap(productIds, options)\n .then((map) => {\n if (active) setProductMap(map);\n })\n .catch((e) => {\n if (active) {\n setProductError(\n e instanceof Error ? e.message : \"Failed to fetch products\"\n );\n }\n })\n .finally(() => {\n if (active) setProductLoading(false);\n });\n\n return () => {\n active = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(productIds), options?.baseUrl, options?.token]);\n\n // Consumer lookup (core helper).\n const consumerMap = useMemo(() => buildConsumerMap(consumers), [consumers]);\n\n // Combine into final rows (core helper: join + search + sort).\n const rows: BasketReportRow[] = useMemo(\n () =>\n combineBasketRows({\n baskets: basketData,\n consumerMap,\n productMap,\n search: filters.search,\n }),\n [basketData, consumerMap, productMap, filters.search]\n );\n\n // Summary (core helper).\n const summary: BasketSummary = useMemo(\n () => computeBasketSummary(rows),\n [rows]\n );\n\n // Keep pagination total in sync with fetched data.\n useEffect(() => {\n if (rows.length > 0 && rows.length === pagination.pageSize) {\n setPaginationState((prev) => ({\n ...prev,\n total: (pagination.page + 2) * pagination.pageSize,\n }));\n } else if (rows.length > 0) {\n setPaginationState((prev) => ({\n ...prev,\n total: pagination.page * pagination.pageSize + rows.length,\n }));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [rows.length, pagination.page, pagination.pageSize]);\n\n const setFilters = useCallback((newFilters: Partial<BasketFilters>) => {\n setFiltersState((prev) => ({ ...prev, ...newFilters }));\n setPaginationState((prev) => ({ ...prev, page: 0 }));\n }, []);\n\n const setPagination = useCallback((page: number, pageSize: number) => {\n setPaginationState((prev) => ({ ...prev, page, pageSize }));\n }, []);\n\n const refresh = useCallback(() => {\n setRefreshFlag((prev) => prev + 1);\n }, []);\n\n const loading = basketLoading || consumerLoading || productLoading;\n const error = basketError || consumerError || productError;\n\n return {\n rows,\n summary,\n loading,\n error,\n filters,\n setFilters,\n pagination,\n setPagination,\n refresh,\n };\n}\n\nexport default useBasketReport;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["headers: Record<string, string>","merged: Extras","response: Response","extras: Record<string, unknown>","extras: Record<string, unknown>","extras: Record<string, unknown>","result: { [id: number]: Product }","extras: Record<string, unknown>","extras: Record<string, unknown>","DEFAULT_BASKET_FILTERS: BasketFilters","DEFAULT_PAGINATION: BasketPagination","whereConditions: string[]","keyIndices: { [key: string]: number }","productSubKeys: { [key: string]: number }","products: BasketItem[]","combinedRows: BasketReportRow[]","productsWithNames: BasketItem[]","map: { [id: number]: Consumer }","useReport","useConsumer","rows: BasketReportRow[]","summary: BasketSummary"],"sources":["../../src/core/errors.ts","../../src/core/request.ts","../../src/core/category.ts","../../src/core/consumer.ts","../../src/core/employee.ts","../../src/core/product.ts","../../src/core/promotion.ts","../../src/core/store.ts","../../src/core/report.ts","../../src/core/session.ts","../../src/core/entity.ts","../../src/core/basket.ts","../../src/hooks/shared.ts","../../src/hooks/useCategory.ts","../../src/hooks/useConsumer.ts","../../src/hooks/useEmployee.ts","../../src/hooks/useProduct.ts","../../src/hooks/usePromotion.ts","../../src/hooks/useStore.ts","../../src/hooks/useReport.ts","../../src/hooks/useDelete.ts","../../src/hooks/useSubmit.ts","../../src/hooks/useLogout.ts","../../src/hooks/useSession.ts","../../src/hooks/useBasketReport.ts"],"sourcesContent":["import type { ErrorMessage } from \"./types\";\n\n/** Map of Shopwave API errors as found in `api.message.errors`, keyed by error code. */\nexport type ShopwaveErrorMap = Record<string, Partial<ErrorMessage>>;\n\n/**\n * Thrown by every `core` function when a request fails, either at the HTTP\n * level (`status` is the HTTP status) or because the Shopwave response carried\n * `api.message.errors` (then `status` is the HTTP status of that response,\n * usually 200/201, and `errors` holds the API errors).\n *\n * Branch on `status` (e.g. `isUnauthorized` → send the user to log in) rather\n * than parsing `message`.\n */\nexport class ShopwaveApiError extends Error {\n /** HTTP status of the response (0 when the request never got a response). */\n readonly status: number;\n /** Shopwave API errors, when the response carried any. */\n readonly errors: ShopwaveErrorMap | null;\n /** Parsed response body, when there was one. */\n readonly body?: unknown;\n\n constructor(\n status: number,\n message: string,\n details: { errors?: ShopwaveErrorMap | null; body?: unknown; cause?: unknown } = {}\n ) {\n super(message, details.cause !== undefined ? { cause: details.cause } : undefined);\n this.name = \"ShopwaveApiError\";\n this.status = status;\n this.errors = details.errors ?? null;\n this.body = details.body;\n }\n\n /** 401, or the Shopwave \"token expired / invalid\" error 908. */\n get isUnauthorized(): boolean {\n if (this.status === 401) return true;\n return !!this.errors && Object.entries(this.errors).some(([k, v]) => k === \"908\" || Number(v?.id) === 908);\n }\n\n get isNotFound(): boolean {\n return this.status === 404;\n }\n}\n\n/** The `api.message.errors` (or `api.message.error`) map of a Shopwave response, or `null` when there are none. */\nexport function getApiErrorMap(body: unknown): ShopwaveErrorMap | null {\n // The API reference names the map `error`; responses seen so far use `errors`.\n const message = (body as { api?: { message?: { errors?: unknown; error?: unknown } } } | null)?.api?.message;\n const errors = message?.errors ?? message?.error;\n if (errors && typeof errors === \"object\" && Object.keys(errors).length > 0) {\n return errors as ShopwaveErrorMap;\n }\n return null;\n}\n\nfunction describeErrors(errors: ShopwaveErrorMap): string {\n return Object.entries(errors)\n .map(([code, e]) => {\n const text = [e?.title, e?.details].filter(Boolean).join(\": \");\n return text ? `${code} ${text}` : code;\n })\n .join(\"; \");\n}\n\n/**\n * Builds a {@link ShopwaveApiError} for a response. Understands the Shopwave\n * envelope (`api.message.errors`) and the `{ error, message }` bodies returned\n * by the `react-shopwave-connect/next` routes.\n */\nexport function toShopwaveApiError(status: number, body: unknown, fallback?: string): ShopwaveApiError {\n const errors = getApiErrorMap(body);\n let detail = errors ? describeErrors(errors) : \"\";\n if (!detail && body && typeof body === \"object\") {\n const b = body as { message?: unknown; error?: unknown };\n if (typeof b.message === \"string\") detail = b.message;\n else if (typeof b.error === \"string\") detail = b.error;\n }\n if (!detail) detail = fallback ?? (status >= 400 ? \"request failed\" : \"API returned errors\");\n return new ShopwaveApiError(status, `Shopwave API error (${status}): ${detail}`, { errors, body });\n}\n\n/** Throws a {@link ShopwaveApiError} when the response body carries API errors. */\nexport function assertNoApiErrors(body: unknown, status: number): void {\n if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);\n}\n","import type { apiResponse } from \"./types\";\nimport { ShopwaveApiError, getApiErrorMap, toShopwaveApiError } from \"./errors\";\n\n/**\n * Per-call configuration shared by every core API function.\n *\n * Nothing here is React-specific. In the browser/Next leave `baseUrl` empty to\n * call the app's own `/api/...` routes (which hold the session). From Node, a\n * CLI or tests, pass an absolute `baseUrl` pointing at such an app plus a\n * `token`.\n */\nexport interface RequestOptions {\n /**\n * Prefixed in front of every request path. Leave undefined in the browser to\n * use relative URLs (e.g. `/api/products`). From Node pass the app origin,\n * e.g. `http://localhost:3000`.\n */\n baseUrl?: string;\n\n /**\n * Shopwave OAuth access token. Sent as `Authorization: OAuth <token>` on every\n * request (GET, POST, PUT and DELETE alike). A value that already starts with\n * a scheme (`OAuth …` / `Bearer …`) is sent as-is. Leave unset in the browser:\n * the app's routes use the session cookie.\n */\n token?: string;\n\n /**\n * Custom fetch implementation. Defaults to the global `fetch`. Useful for\n * Node < 18, testing, or injecting interceptors.\n */\n fetch?: typeof fetch;\n\n /** Optional abort signal for cancellation (used by the hooks layer). */\n signal?: AbortSignal;\n}\n\n/** The Shopwave API ships request metadata inside a single `extras` header. */\nexport type Extras = Record<string, unknown>;\n\nfunction resolveFetch(options: RequestOptions): typeof fetch {\n const f = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!f) {\n throw new Error(\n \"No fetch implementation available. Pass `options.fetch` (e.g. node-fetch / undici) when running outside the browser.\"\n );\n }\n return f;\n}\n\n/** Options with `token` filled in from a legacy per-call `params.token`, unless `options.token` is set. */\nexport function withToken(options: RequestOptions, token: string | undefined): RequestOptions {\n return token && !options.token ? { ...options, token } : options;\n}\n\n/** `Authorization` header value for a token (`OAuth <token>` unless it already has a scheme). */\nexport function authorizationFor(token: string): string {\n const t = token.trim();\n return /^(oauth|bearer)\\s+\\S/i.test(t) ? t : `OAuth ${t}`;\n}\n\n/** Headers shared by every request: Accept + the token, when one is given. */\nfunction baseHeaders(options: RequestOptions): Record<string, string> {\n const headers: Record<string, string> = { Accept: \"application/json\" };\n if (options.token) headers.Authorization = authorizationFor(options.token);\n return headers;\n}\n\nfunction buildExtras(extras: Extras): Extras {\n const merged: Extras = { \"Content-Type\": \"application/json\", ...extras };\n // Tokens never travel inside extras any more (see RequestOptions.token).\n delete merged.token;\n return merged;\n}\n\n/** Parses a response body as JSON; `null` for an empty body, `{ raw }` for non-JSON. */\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return { raw: text };\n }\n}\n\n/**\n * Low-level request: resolves `{ status, body }` for 2xx responses and throws\n * {@link ShopwaveApiError} otherwise. `init.headers` are merged over the\n * standard ones (Accept + Authorization).\n */\nexport async function apiRequest(\n path: string,\n init: RequestInit,\n options: RequestOptions = {}\n): Promise<{ status: number; body: unknown }> {\n return send(path, { ...init, headers: { ...baseHeaders(options), ...(init.headers as Record<string, string>) } }, options);\n}\n\nasync function send(path: string, init: RequestInit, options: RequestOptions): Promise<{ status: number; body: unknown }> {\n const doFetch = resolveFetch(options);\n const url = (options.baseUrl ?? \"\") + path;\n\n let response: Response;\n try {\n response = await doFetch(url, { ...init, signal: options.signal });\n } catch (error) {\n if ((error as { name?: string })?.name === \"AbortError\") throw error;\n throw new ShopwaveApiError(0, `Shopwave API request failed: ${(error as Error)?.message ?? error}`, { cause: error });\n }\n\n const body = await readBody(response);\n if (!response.ok) {\n throw toShopwaveApiError(response.status, body, response.statusText || undefined);\n }\n return { status: response.status, body };\n}\n\n/**\n * Returns a JSON string of API errors if the response carries any, otherwise\n * `null`. Kept for backwards compatibility; prefer `ShopwaveApiError`.\n */\nexport function getApiErrors(api?: apiResponse): string | null {\n const errors = getApiErrorMap({ api });\n return errors ? JSON.stringify(errors) : null;\n}\n\n/**\n * GET helper. Sends request metadata via the `extras` header, parses JSON and\n * returns the payload. Throws {@link ShopwaveApiError} for non-2xx responses.\n */\nexport async function apiGet<T>(path: string, extras: Extras, options: RequestOptions = {}): Promise<T> {\n const { body } = await send(\n path,\n {\n method: \"GET\",\n headers: { ...baseHeaders(options), extras: JSON.stringify(buildExtras(extras)) },\n },\n options\n );\n return body as T;\n}\n\n/**\n * JSON body helper for POST/PUT mutations. Returns the parsed body (`null` for\n * an empty one). Throws {@link ShopwaveApiError} for non-2xx responses.\n */\nexport async function apiSend<T>(\n path: string,\n method: \"POST\" | \"PUT\",\n payload: unknown,\n options: RequestOptions = {}\n): Promise<T> {\n const { body } = await send(\n path,\n {\n method,\n headers: { ...baseHeaders(options), \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload),\n },\n options\n );\n return body as T;\n}\n\n/**\n * DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body\n * does come back and carries API errors, they are thrown. Returns the parsed\n * body (`null` when empty).\n */\nexport async function apiDelete(path: string, options: RequestOptions = {}): Promise<unknown> {\n const { status, body } = await send(path, { method: \"DELETE\", headers: baseHeaders(options) }, options);\n if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);\n return body;\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Category {\n id: number;\n title: string;\n parentId: number | null;\n deleteDate: string;\n activeDate: string;\n type: number;\n}\n\nexport interface CategoryResponse {\n categories: {\n [key: number]: Category;\n };\n api: apiResponse;\n}\n\nexport interface FetchCategoriesParams {\n categoryIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches categories. Framework-agnostic: returns the flattened `Category[]`\n * or throws on API errors.\n */\nexport async function fetchCategories(\n params: FetchCategoriesParams = {},\n options: RequestOptions = {}\n): Promise<Category[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.categoryIds) {\n extras.categoryIds = params.categoryIds;\n }\n\n const json = await apiGet<CategoryResponse | null>(\"/api/categories\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.categories ?? {});\n}\n","import { apiGet, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Consumer {\n id: number;\n activeCount: number;\n firstName: string;\n lastName: string;\n email: string;\n}\n\nexport interface ConsumerResponse {\n consumers: {\n [key: number]: Consumer;\n };\n api: apiResponse;\n}\n\n/**\n * Fetches consumers by id. Returns an empty array when no ids are supplied\n * (the original hook short-circuited in that case).\n */\nexport async function fetchConsumers(\n consumerIds: number[] | null | undefined,\n options: RequestOptions = {}\n): Promise<Consumer[]> {\n if (!consumerIds || consumerIds.length === 0) {\n return [];\n }\n\n const extras: Record<string, unknown> = { ids: consumerIds.join(\",\") };\n\n const json = await apiGet<ConsumerResponse | null>(\"/api/consumer\", extras, options);\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.consumers ?? {});\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport const rolesById = {\n \"1\": \"Owner\",\n \"2\": \"Manager\",\n \"3\": \"Assistant\",\n \"4\": \"Guest\",\n \"5\": \"Assistant Manager\",\n} as const;\n\nexport interface Employee {\n id: number;\n firstName: string;\n lastName: string;\n email: string;\n roleId: number;\n joinedDate: string;\n exitDate: string;\n stores: {\n [key: number]: {\n id: number;\n roleId: number;\n };\n };\n}\n\nexport interface EmployeeResponse {\n employees: {\n [key: number]: Employee;\n };\n api: apiResponse;\n}\n\nexport interface FetchEmployeesParams {\n /** Optional id filter. Forwarded when provided. */\n employeeIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches employees. Returns the flattened `Employee[]`.\n */\nexport async function fetchEmployees(\n params: FetchEmployeesParams = {},\n options: RequestOptions = {}\n): Promise<Employee[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.employeeIds) {\n extras.employeeIds = params.employeeIds;\n }\n\n const json = await apiGet<EmployeeResponse | null>(\"/api/employees\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.employees ?? {});\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ProductInstance {\n id: number;\n price: string;\n taxPercentage: string;\n timestamp: string;\n size: string;\n name: string;\n activeDate: string;\n tags: string;\n}\n\nexport interface Product {\n id: number;\n barcode: string;\n name: string;\n details: string;\n tags: string;\n unit: number;\n timestamp: string;\n activeDate: string;\n deleteDate: string;\n images: Array<string>;\n categories: Array<number>;\n instances: {\n [key: number]: ProductInstance;\n };\n}\n\nexport interface ProductResponse {\n products: {\n [key: number]: Product;\n };\n api: apiResponse;\n}\n\nexport interface FetchProductsParams {\n productIds?: Array<number>;\n storeId?: number;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches products. Returns the flattened `Product[]`.\n */\nexport async function fetchProducts(\n params: FetchProductsParams = {},\n options: RequestOptions = {}\n): Promise<Product[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.productIds) {\n extras.productIds = params.productIds;\n }\n if (params.storeId) {\n extras.storeId = params.storeId;\n }\n\n const json = await apiGet<ProductResponse | null>(\"/api/products\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.products ?? {});\n}\n\n/**\n * Fetches products by id and returns them keyed by id, transparently batching\n * the request (default 200 ids per call) to stay within API limits. Extracted\n * from the original `useBasketReport` inline batching loop so it can be reused\n * outside React.\n */\nexport async function fetchProductsMap(\n productIds: number[] | null | undefined,\n options: RequestOptions = {},\n batchSize = 200\n): Promise<{ [id: number]: Product }> {\n const result: { [id: number]: Product } = {};\n if (!productIds || productIds.length === 0) {\n return result;\n }\n\n for (let i = 0; i < productIds.length; i += batchSize) {\n const batch = productIds.slice(i, i + batchSize);\n const products = await fetchProducts({ productIds: batch }, options);\n for (const product of products) {\n result[product.id] = product;\n }\n }\n\n return result;\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\n/** Promotion rule identifiers as stored by the Shopwave API. */\nexport enum PromotionRuleSet {\n Percentage = \"|%^/\",\n Combo = \"&%</\",\n MealDeal = \"&=^/\",\n}\n\nexport interface Promotion {\n id: number;\n merchantId: number;\n storeIds: number[];\n categoryIds: { [key: string]: number };\n productIds: { [key: string]: any };\n title: string;\n details: string | null;\n terms: string | null;\n startDate: string;\n endDate: string;\n daysAvailable: number[];\n automatic: number;\n rule: PromotionRuleSet;\n x: number;\n y: number | null;\n}\n\nexport interface PromotionResponse {\n promotions: {\n [key: number]: Promotion;\n };\n api: apiResponse;\n}\n\nexport interface FetchPromotionsParams {\n /** Optional id filter. Forwarded when provided. */\n promotionIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** Filter for imminent promotions. */\n imminent?: boolean;\n /** Filter for active promotions. */\n active?: boolean;\n /** Filter for expired promotions. */\n expired?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches promotions. Returns the flattened `Promotion[]`.\n */\nexport async function fetchPromotions(\n params: FetchPromotionsParams = {},\n options: RequestOptions = {}\n): Promise<Promotion[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n\n if (params.promotionIds) {\n extras.promotionIds = params.promotionIds;\n }\n if (params.imminent !== undefined) {\n extras.imminent = params.imminent;\n }\n if (params.active !== undefined) {\n extras.active = params.active;\n }\n if (params.expired !== undefined) {\n extras.expired = params.expired;\n }\n\n const json = await apiGet<PromotionResponse | null>(\"/api/promotions\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.promotions ?? {});\n}\n","import { apiGet, withToken, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface Store {\n id: number;\n lat: number;\n lng: number;\n addressLine1: string;\n addressLine2: string | null;\n addressLine3: string;\n phoneNumber: string;\n city: string;\n postcode: string;\n countryId: number | null;\n timezoneId: number | null;\n storeDeleteDate: string | null;\n email: string | null;\n sandbox: boolean | null;\n}\n\nexport interface StoreResponse {\n stores: {\n [key: number]: Store;\n };\n api: apiResponse;\n}\n\nexport interface FetchStoresParams {\n /** Optional id filter. */\n storeIds?: Array<number>;\n /** Whether to include soft-deleted records. Defaults to `false`. */\n deleted?: boolean;\n /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */\n token?: string;\n}\n\n/**\n * Fetches stores. Returns the flattened `Store[]`.\n */\nexport async function fetchStores(\n params: FetchStoresParams = {},\n options: RequestOptions = {}\n): Promise<Store[]> {\n const extras: Record<string, unknown> = { deleted: params.deleted ?? false };\n if (params.storeIds) {\n extras.storeIds = params.storeIds;\n }\n\n const json = await apiGet<StoreResponse | null>(\"/api/stores\", extras, withToken(options, params.token));\n\n assertNoApiErrors(json, 200);\n\n // Shopwave answers an empty body when nothing matches.\n return Object.values(json?.stores ?? {});\n}\n","import { apiGet, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\nimport type { apiResponse } from \"./types\";\n\nexport interface ReportKey {\n name: string;\n type: string;\n item?: ReportKey[];\n}\n\nexport interface ReportData {\n keys: ReportKey[];\n data: any[][];\n}\n\nexport interface ReportResponse {\n reports: {\n [key: string]: ReportData;\n };\n api: apiResponse;\n}\n\nexport interface ReportQuery {\n FROM: string;\n WHERE?: {\n AND?: string[];\n OR?: string[];\n };\n SELECT?: string[];\n LIMIT?: number;\n OFFSET?: number;\n}\n\nexport interface ReportQueryMap {\n [key: string]: ReportQuery;\n}\n\n/**\n * Runs one or more report queries and returns the `reports` map keyed by query\n * name. The query is JSON-encoded into the `extras.query` header, matching the\n * original endpoint contract.\n */\nexport async function fetchReport(\n query: ReportQueryMap,\n options: RequestOptions = {}\n): Promise<ReportResponse[\"reports\"]> {\n const extras: Record<string, unknown> = { query: JSON.stringify(query) };\n\n const json = await apiGet<ReportResponse | null>(\"/api/report\", extras, options);\n\n assertNoApiErrors(json, 200);\n\n return json?.reports ?? {};\n}\n","import { apiDelete, type RequestOptions } from \"./request\";\n\n/**\n * Login status returned by the app's `/api/session` route. It deliberately\n * never contains tokens — those stay in the encrypted httpOnly cookie.\n */\nexport interface SessionStatus {\n loggedIn: boolean;\n /** Access-token expiry (epoch ms), when known. */\n expiresAt?: number;\n}\n\n/**\n * Reads the current login status from `GET /api/session`.\n *\n * Works with the new `react-shopwave-connect/next` session route\n * (`{ loggedIn }`) and with older routes that returned the raw session\n * (`{ token: {...} }`) — in that case only a boolean is surfaced.\n */\nexport async function fetchSession(options: RequestOptions = {}): Promise<SessionStatus> {\n const doFetch = options.fetch ?? (typeof fetch !== \"undefined\" ? fetch : undefined);\n if (!doFetch) {\n throw new Error(\"No fetch implementation available. Pass `options.fetch`.\");\n }\n\n const response = await doFetch(`${options.baseUrl ?? \"\"}/api/session`, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n cache: \"no-store\",\n credentials: \"same-origin\",\n signal: options.signal,\n } as RequestInit);\n\n if (response.status === 401) return { loggedIn: false };\n if (!response.ok) {\n throw new Error(`Session request failed (${response.status})`);\n }\n\n const json = (await response.json()) as Record<string, unknown> | null;\n if (json && typeof json.loggedIn === \"boolean\") {\n return {\n loggedIn: json.loggedIn,\n expiresAt: typeof json.expiresAt === \"number\" ? json.expiresAt : undefined,\n };\n }\n // Legacy route that returns the whole session: never pass tokens through.\n return { loggedIn: Boolean(json && (json as { token?: unknown }).token) };\n}\n\n/**\n * Path that starts a login and returns to `returnTo` afterwards.\n * Navigate the browser to it (`window.location.href = loginPath(...)`).\n */\nexport function loginPath(returnTo?: string, authPath = \"/auth\"): string {\n return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;\n}\n\n/** Path that logs out of the app and the Shopwave auth server. */\nexport function logoutPath(authPath = \"/auth\"): string {\n return `${authPath}/logout`;\n}\n\n/**\n * Ends the current server session (no redirect).\n *\n * Prefer navigating to {@link logoutPath} when using\n * `react-shopwave-connect/next`: it also signs the user out of the auth server.\n */\nexport async function logout(options: RequestOptions = {}): Promise<void> {\n await apiDelete(\"/api/session?action=logout\", options);\n}\n","import { apiDelete, apiSend, type RequestOptions } from \"./request\";\nimport { assertNoApiErrors } from \"./errors\";\n\n/**\n * Low-level, untyped mutations: the caller supplies the route and payload.\n *\n * Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,\n * …): they know each route, build the request envelope, check the response and\n * return the saved entity with its id. Keep these for routes that have no typed\n * function yet (e.g. `/api/merchant`).\n */\n\n/**\n * Deletes `/api/{resourcePath}/{entityId}`.\n */\nexport async function deleteEntity(\n resourcePath: string,\n entityId: number | string,\n options: RequestOptions = {}\n): Promise<void> {\n if (entityId == null || entityId === \"\") {\n throw new Error(\"entityId is missing\");\n }\n await apiDelete(`/api/${resourcePath}/${entityId}`, options);\n}\n\nexport interface SubmitEntityArgs {\n /** Fully-resolved endpoint, e.g. `/api/products` or `/api/products/42`. */\n endpoint: string;\n /** Defaults to PUT when an `entityId` exists, otherwise POST. */\n method?: \"POST\" | \"PUT\";\n /** The request payload (already transformed by the caller). */\n payload: unknown;\n}\n\nexport interface SubmitEntityResponse<R = any> {\n result?: R;\n [key: string]: unknown;\n}\n\n/**\n * Sends a create/update mutation. Returns the response's `result` field when it\n * has one (older app routes wrapped their answer that way), otherwise the whole\n * parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.\n */\nexport async function submitEntity<R = any>(\n { endpoint, method = \"POST\", payload }: SubmitEntityArgs,\n options: RequestOptions = {}\n): Promise<R | undefined> {\n const json = await apiSend<SubmitEntityResponse<R> | null>(\n endpoint,\n method,\n payload,\n options\n );\n assertNoApiErrors(json, 200);\n if (json && typeof json === \"object\" && \"result\" in json) return json.result;\n return (json ?? undefined) as R | undefined;\n}\n","import type { Consumer } from \"./consumer\";\nimport type { Product } from \"./product\";\nimport type { ReportQueryMap, ReportResponse } from \"./report\";\n\n// ── Basket Report Types (preserved from the original `types/basket.ts`) ──────\n\nexport interface BasketItem {\n productId: number;\n productName: string;\n quantity: number;\n price: number;\n tax: number;\n}\n\nexport interface Basket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"pending\" | \"completed\" | \"cancelled\";\n completeDate: string;\n}\n\nexport interface BasketReportRow {\n id: number;\n basketId: number;\n basketName: string;\n consumerId: number;\n consumerName: string;\n consumerEmail: string;\n products: BasketItem[];\n basketTotal: number;\n tax: number;\n dateTime: string;\n status: string;\n}\n\nexport interface BasketSummary {\n totalBaskets: number;\n totalRevenue: number;\n totalTax: number;\n uniqueConsumers: number;\n}\n\nexport interface BasketFilters {\n search: string;\n dateRange: {\n start: Date | null;\n end: Date | null;\n };\n consumerId: number | null;\n status: \"all\" | \"pending\" | \"completed\" | \"cancelled\";\n}\n\nexport interface BasketPagination {\n page: number;\n pageSize: number;\n total: number;\n}\n\nexport const DEFAULT_BASKET_FILTERS: BasketFilters = {\n search: \"\",\n dateRange: {\n start: null,\n end: null,\n },\n consumerId: null,\n status: \"all\",\n};\n\nexport const DEFAULT_PAGINATION: BasketPagination = {\n page: 0,\n pageSize: 25,\n total: 0,\n};\n\n/** Intermediate shape produced by {@link parseBasketReportData}. */\nexport interface ParsedBasket {\n id: number;\n name: string;\n consumerId: number;\n products: BasketItem[];\n total: number;\n taxTotal: number;\n status: \"completed\";\n completeDate: string;\n}\n\n// ── Pure transform helpers (extracted from the original `useBasketReport`) ────\n\n/** Formats a date as `YYYY-MM-DD HH:mm:ss` for the report query. */\nexport function formatDateForQuery(date: Date, isEndOfDay = false): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n const time = isEndOfDay ? \"23:59:59\" : \"00:00:00\";\n return `${year}-${month}-${day} ${time}`;\n}\n\n/** Builds the `Basket` report query from a set of filters. */\nexport function buildBasketReportQuery(filters: BasketFilters): ReportQueryMap {\n const whereConditions: string[] = [];\n\n const today = new Date();\n const startDate = filters.dateRange.start ?? today;\n const endDate = filters.dateRange.end ?? today;\n\n whereConditions.push(\n `Basket.completeDate > '${formatDateForQuery(startDate, false)}'`\n );\n whereConditions.push(\n `Basket.completeDate < '${formatDateForQuery(endDate, true)}'`\n );\n\n if (filters.consumerId) {\n whereConditions.push(`Basket.consumerId = ${filters.consumerId}`);\n }\n\n if (filters.status !== \"all\") {\n whereConditions.push(`Basket.status = '${filters.status}'`);\n }\n\n return {\n baskets: {\n FROM: \"Basket\",\n WHERE: whereConditions.length > 0 ? { AND: whereConditions } : undefined,\n },\n };\n}\n\n/** Flattens the raw report rows/keys into structured baskets. */\nexport function parseBasketReportData(\n reports: ReportResponse[\"reports\"] | null | undefined\n): ParsedBasket[] {\n if (!reports?.baskets?.data || !reports?.baskets?.keys) {\n return [];\n }\n\n const { keys, data } = reports.baskets;\n\n const keyIndices: { [key: string]: number } = {};\n keys.forEach((key, index) => {\n keyIndices[key.name] = index;\n });\n\n const productKey = keys.find((k) => k.name === \"Basket.Product\");\n const productSubKeys: { [key: string]: number } = {};\n if (productKey?.item) {\n productKey.item.forEach((subKey, index) => {\n productSubKeys[subKey.name] = index;\n });\n }\n\n return data.map((row) => {\n const productsArray = (row[keyIndices[\"Basket.Product\"]] as any[][]) || [];\n const products: BasketItem[] = productsArray.map((productRow) => ({\n productId: productRow[productSubKeys[\"Basket.Product.productId\"]] as number,\n productName: `Product #${productRow[productSubKeys[\"Basket.Product.productId\"]]}`,\n quantity:\n (productRow[productSubKeys[\"Basket.Product.quantity\"]] as number) || 1,\n price:\n (productRow[productSubKeys[\"Basket.Product.productPrice\"]] as number) ||\n 0,\n tax:\n (productRow[\n productSubKeys[\"Basket.Product.productTaxPercentage\"]\n ] as number) || 0,\n }));\n\n const total = products.reduce((sum, p) => sum + p.price * p.quantity, 0);\n const taxTotal = products.reduce(\n (sum, p) => sum + p.price * p.quantity * p.tax,\n 0\n );\n\n const completeDateTimestamp = row[keyIndices[\"Basket.completeDate\"]] as number;\n const completeDate = completeDateTimestamp\n ? new Date(completeDateTimestamp).toISOString()\n : \"\";\n\n return {\n id: row[keyIndices[\"Basket.id\"]] as number,\n name: row[keyIndices[\"Basket.name\"]] as string,\n consumerId: row[keyIndices[\"Basket.consumerId\"]] as number,\n products,\n total,\n taxTotal,\n status: \"completed\" as const,\n completeDate,\n };\n });\n}\n\n/** Unique non-null consumer ids referenced by a set of baskets. */\nexport function collectConsumerIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = baskets\n .map((b) => b.consumerId)\n .filter((id): id is number => id != null);\n return ids.length > 0 ? [...new Set(ids)] : null;\n}\n\n/** Unique product ids referenced by a set of baskets. */\nexport function collectProductIds(baskets: ParsedBasket[]): number[] | null {\n if (!baskets.length) return null;\n const ids = new Set<number>();\n baskets.forEach((basket) => {\n basket.products.forEach((product) => {\n if (product.productId) {\n ids.add(product.productId);\n }\n });\n });\n return ids.size > 0 ? [...ids] : null;\n}\n\nexport interface CombineBasketRowsParams {\n baskets: ParsedBasket[];\n consumerMap: { [id: number]: Consumer };\n productMap: { [id: number]: Product };\n search?: string;\n}\n\n/** Joins baskets with consumer + product lookups, applies search, sorts. */\nexport function combineBasketRows({\n baskets,\n consumerMap,\n productMap,\n search,\n}: CombineBasketRowsParams): BasketReportRow[] {\n if (!baskets.length) return [];\n\n let combinedRows: BasketReportRow[] = baskets.map((basket) => {\n const consumer = basket.consumerId ? consumerMap[basket.consumerId] : null;\n\n const productsWithNames: BasketItem[] = basket.products.map((p) => {\n const product = productMap[p.productId];\n return {\n ...p,\n productName: product?.name || `Product #${p.productId}`,\n };\n });\n\n return {\n id: basket.id,\n basketId: basket.id,\n basketName: basket.name || `Basket #${basket.id}`,\n consumerId: basket.consumerId,\n consumerName: consumer\n ? `${consumer.firstName} ${consumer.lastName}`\n : \"Unknown\",\n consumerEmail: consumer?.email || \"-\",\n products: productsWithNames,\n basketTotal: (basket.total || 0) / 100,\n tax: basket.taxTotal || 0,\n dateTime: basket.completeDate,\n status: basket.status || \"pending\",\n };\n });\n\n if (search) {\n const searchLower = search.toLowerCase();\n combinedRows = combinedRows.filter(\n (row) =>\n row.basketId.toString().includes(searchLower) ||\n row.basketName.toLowerCase().includes(searchLower) ||\n row.consumerName.toLowerCase().includes(searchLower) ||\n row.consumerEmail.toLowerCase().includes(searchLower)\n );\n }\n\n combinedRows.sort((a, b) => {\n const dateA = new Date(a.dateTime).getTime();\n const dateB = new Date(b.dateTime).getTime();\n return dateB - dateA;\n });\n\n return combinedRows;\n}\n\n/** Aggregate totals for a set of report rows. */\nexport function computeBasketSummary(rows: BasketReportRow[]): BasketSummary {\n const uniqueConsumerIds = new Set(rows.map((row) => row.consumerId));\n return {\n totalBaskets: rows.length,\n totalRevenue: rows.reduce((sum, row) => sum + row.basketTotal, 0),\n totalTax: rows.reduce((sum, row) => sum + row.tax, 0),\n uniqueConsumers: uniqueConsumerIds.size,\n };\n}\n\n/** Builds a consumer id → consumer lookup. */\nexport function buildConsumerMap(consumers: Consumer[] | null | undefined): {\n [id: number]: Consumer;\n} {\n const map: { [id: number]: Consumer } = {};\n if (consumers) {\n consumers.forEach((consumer) => {\n map[consumer.id] = consumer;\n });\n }\n return map;\n}\n","import { useCallback, useEffect, useReducer, useState } from \"react\";\nimport { ShopwaveApiError } from \"../core/errors\";\n\n/**\n * Consistent return shape for every auto-fetching (\"query\") hook.\n */\nexport interface QueryState<T> {\n /**\n * The latest data for the current params. Kept while a `refetch()` is in\n * flight (and after a failed refetch), so lists don't blank out and flash a\n * skeleton after every save. Reset to `null` when the params change.\n */\n data: T | null;\n /** True while there's no data to show yet (first load, or params changed). */\n loading: boolean;\n /** True whenever a request is in flight, including background refetches. */\n fetching: boolean;\n error: string | null;\n /** HTTP status of the last error (0 = no response), or `null`. */\n errorStatus: number | null;\n /** Re-runs the underlying fetch, keeping the current data visible meanwhile. */\n refetch: () => void;\n}\n\n/**\n * Consistent return shape for manually-triggered (\"mutation\") hooks.\n */\nexport interface MutationState<TData, TArgs extends any[]> {\n /** Triggers the request. Resolves to the result, or `null` on error. */\n mutate: (...args: TArgs) => Promise<TData | null>;\n data: TData | null;\n loading: boolean;\n error: string | null;\n /** HTTP status of the last error (0 = no response), or `null`. */\n errorStatus: number | null;\n}\n\nfunction toMessage(e: unknown): string {\n if (e instanceof Error) return e.message;\n return String(e);\n}\n\nfunction toStatus(e: unknown): number | null {\n return e instanceof ShopwaveApiError ? e.status : null;\n}\n\n// ---------------------------------------------------------------------------\n// Query state machine (pure, unit-tested)\n// ---------------------------------------------------------------------------\n\nexport interface QueryInternalState<T> {\n data: T | null;\n /** Params key the `data` belongs to. */\n key: string | null;\n fetching: boolean;\n error: string | null;\n errorStatus: number | null;\n}\n\nexport type QueryAction<T> =\n | { type: \"start\"; key: string }\n | { type: \"success\"; key: string; data: T }\n | { type: \"failure\"; key: string; error: string; errorStatus: number | null }\n | { type: \"idle\" };\n\nexport function queryReducer<T>(state: QueryInternalState<T>, action: QueryAction<T>): QueryInternalState<T> {\n switch (action.type) {\n case \"start\":\n // New params: the old data describes something else, so drop it.\n if (action.key !== state.key) {\n return { data: null, key: action.key, fetching: true, error: null, errorStatus: null };\n }\n // Same params (refetch): keep showing what we have.\n return { ...state, fetching: true, error: null, errorStatus: null };\n case \"success\":\n if (action.key !== state.key) return state;\n return { data: action.data, key: action.key, fetching: false, error: null, errorStatus: null };\n case \"failure\":\n if (action.key !== state.key) return state;\n return { ...state, fetching: false, error: action.error, errorStatus: action.errorStatus };\n case \"idle\":\n return state.fetching ? { ...state, fetching: false } : state;\n default:\n return state;\n }\n}\n\n/** Public view of the internal state for the params currently being rendered. */\nexport function selectQueryView<T>(\n state: QueryInternalState<T>,\n key: string,\n enabled: boolean\n): Pick<QueryState<T>, \"data\" | \"loading\" | \"fetching\" | \"error\" | \"errorStatus\"> {\n // Params changed but the effect hasn't run yet: don't show the old data.\n if (state.key !== key) {\n return { data: null, loading: enabled, fetching: enabled, error: null, errorStatus: null };\n }\n return {\n data: state.data,\n loading: state.fetching && state.data === null,\n fetching: state.fetching,\n error: state.error,\n errorStatus: state.errorStatus,\n };\n}\n\nfunction depsKey(deps: ReadonlyArray<unknown>): string {\n try {\n return JSON.stringify(deps);\n } catch {\n return String(deps);\n }\n}\n\n/**\n * Internal primitive powering the auto-fetch hooks. Runs `fetcher` on mount and\n * whenever `deps` change, tracks loading/error/data, cancels in-flight requests\n * on unmount or dep change, and exposes `refetch` (which keeps the current data\n * visible until the new data arrives).\n *\n * When `enabled` is false the fetch is skipped (used for hooks that need\n * arguments before they can run, e.g. consumers-by-id).\n */\nexport function useQuery<T>(\n fetcher: (signal: AbortSignal) => Promise<T>,\n deps: ReadonlyArray<unknown>,\n enabled = true\n): QueryState<T> {\n const key = depsKey(deps);\n const [state, dispatch] = useReducer(queryReducer<T>, {\n data: null,\n key: enabled ? key : null,\n fetching: enabled,\n error: null,\n errorStatus: null,\n } as QueryInternalState<T>);\n const [tick, setTick] = useState(0);\n\n const refetch = useCallback(() => setTick((t) => t + 1), []);\n\n useEffect(() => {\n if (!enabled) {\n dispatch({ type: \"idle\" });\n return;\n }\n\n const controller = new AbortController();\n dispatch({ type: \"start\", key });\n\n fetcher(controller.signal)\n .then((result) => {\n if (!controller.signal.aborted) dispatch({ type: \"success\", key, data: result });\n })\n .catch((e) => {\n if (!controller.signal.aborted && (e as { name?: string })?.name !== \"AbortError\") {\n dispatch({ type: \"failure\", key, error: toMessage(e), errorStatus: toStatus(e) });\n }\n });\n\n return () => {\n controller.abort();\n };\n // `fetcher` is intentionally excluded; callers pass stable `deps`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key, tick, enabled]);\n\n return { ...selectQueryView(state, key, enabled), refetch };\n}\n\n/**\n * Internal primitive powering the manually-triggered hooks.\n */\nexport function useMutation<TData, TArgs extends any[]>(\n action: (...args: TArgs) => Promise<TData>,\n deps: ReadonlyArray<unknown>\n): MutationState<TData, TArgs> {\n const [data, setData] = useState<TData | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [errorStatus, setErrorStatus] = useState<number | null>(null);\n\n const mutate = useCallback(\n async (...args: TArgs): Promise<TData | null> => {\n setLoading(true);\n setError(null);\n setErrorStatus(null);\n try {\n const result = await action(...args);\n setData(result);\n return result;\n } catch (e) {\n setError(toMessage(e));\n setErrorStatus(toStatus(e));\n return null;\n } finally {\n setLoading(false);\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n deps\n );\n\n return { mutate, data, loading, error, errorStatus };\n}\n","import {\n fetchCategories,\n type Category,\n type FetchCategoriesParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches categories on mount and whenever the params change.\n * Manual reloads are available via `refetch` (replaces the old `reloadFlag`).\n */\nexport function useCategory(\n params: FetchCategoriesParams = {},\n options?: RequestOptions\n): QueryState<Category[]> {\n return useQuery<Category[]>(\n (signal) => fetchCategories(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useCategory;\n","import {\n fetchConsumers,\n type Consumer,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches consumers by id. Disabled (no request) until at least one id is\n * supplied, mirroring the original early-return behaviour.\n */\nexport function useConsumer(\n consumerIds: number[] | null,\n options?: RequestOptions\n): QueryState<Consumer[]> {\n const enabled = !!consumerIds && consumerIds.length > 0;\n return useQuery<Consumer[]>(\n (signal) => fetchConsumers(consumerIds, { ...options, signal }),\n [JSON.stringify(consumerIds), options?.baseUrl, options?.token],\n enabled\n );\n}\n\nexport default useConsumer;\n","import {\n fetchEmployees,\n type Employee,\n type FetchEmployeesParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches employees on mount and whenever the params change.\n */\nexport function useEmployee(\n params: FetchEmployeesParams = {},\n options?: RequestOptions\n): QueryState<Employee[]> {\n return useQuery<Employee[]>(\n (signal) => fetchEmployees(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useEmployee;\n","import {\n fetchProducts,\n type Product,\n type FetchProductsParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches products on mount and whenever the params change.\n */\nexport function useProduct(\n params: FetchProductsParams = {},\n options?: RequestOptions\n): QueryState<Product[]> {\n return useQuery<Product[]>(\n (signal) => fetchProducts(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useProduct;\n","import {\n fetchPromotions,\n type Promotion,\n type FetchPromotionsParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches promotions on mount and whenever the params change.\n * Manual reloads are available via `refetch`.\n */\nexport function usePromotion(\n params: FetchPromotionsParams = {},\n options?: RequestOptions\n): QueryState<Promotion[]> {\n return useQuery<Promotion[]>(\n (signal) => fetchPromotions(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default usePromotion;\n","import {\n fetchStores,\n type Store,\n type FetchStoresParams,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches stores on mount. Manual reloads via `refetch`\n * (replaces the old `reloadFlag` argument).\n */\nexport function useStore(\n params: FetchStoresParams = {},\n options?: RequestOptions\n): QueryState<Store[]> {\n return useQuery<Store[]>(\n (signal) => fetchStores(params, { ...options, signal }),\n [JSON.stringify(params), options?.baseUrl, options?.token]\n );\n}\n\nexport default useStore;\n","import {\n fetchReport,\n type ReportQueryMap,\n type ReportResponse,\n type RequestOptions,\n} from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Auto-fetches one or more reports. Disabled (no request) until a non-null\n * query is supplied, mirroring the original early-return behaviour.\n */\nexport function useReport(\n query: ReportQueryMap | null,\n options?: RequestOptions\n): QueryState<ReportResponse[\"reports\"]> {\n const enabled = !!query;\n return useQuery<ReportResponse[\"reports\"]>(\n (signal) => fetchReport(query as ReportQueryMap, { ...options, signal }),\n [JSON.stringify(query), options?.baseUrl, options?.token],\n enabled\n );\n}\n\nexport default useReport;\n","import { deleteEntity, type RequestOptions } from \"../core\";\nimport { useMutation, type MutationState } from \"./shared\";\n\n/**\n * Manually-triggered delete. Returns `{ mutate, loading, error, data }`.\n *\n * Note: the original `useHandleDelete` surfaced notistack toasts. That UI\n * concern is left to the host app — handle success/error from the returned\n * state or the promise resolved by `mutate`.\n *\n * @example\n * const { mutate, loading } = useDelete();\n * await mutate(\"products\", 42);\n */\nexport function useDelete(\n options?: RequestOptions\n): MutationState<void, [resourcePath: string, entityId: number | string]> {\n return useMutation<void, [string, number | string]>(\n (resourcePath, entityId) => deleteEntity(resourcePath, entityId, options),\n [options?.baseUrl, options?.token]\n );\n}\n\nexport default useDelete;\n","import {\n submitEntity,\n type SubmitEntityArgs,\n type RequestOptions,\n} from \"../core\";\nimport { useMutation, type MutationState } from \"./shared\";\n\n/**\n * Manually-triggered create/update. Returns `{ mutate, loading, error, data }`,\n * where `data` is the API `result` payload.\n *\n * The original `useHandleSubmit` bundled validation, payload transformation and\n * notistack toasts. Those are app concerns: resolve the endpoint/method/payload\n * in your component and pass them in, then react to the returned state.\n *\n * @example\n * const { mutate, loading } = useSubmit<Product>();\n * const saved = await mutate({\n * endpoint: id ? `/api/products/${id}` : \"/api/products\",\n * method: id ? \"PUT\" : \"POST\",\n * payload,\n * });\n */\nexport function useSubmit<R = any>(\n options?: RequestOptions\n): MutationState<R | undefined, [args: SubmitEntityArgs]> {\n return useMutation<R | undefined, [SubmitEntityArgs]>(\n (args) => submitEntity<R>(args, options),\n [options?.baseUrl, options?.token]\n );\n}\n\nexport default useSubmit;\n","import { logout, type RequestOptions } from \"../core\";\nimport { useMutation, type MutationState } from \"./shared\";\n\n/**\n * Manually-triggered logout (ends the server session).\n *\n * This is framework-agnostic and does NOT redirect. The original `useLogout`\n * also pushed the browser to the auth-server logout URL via `next/navigation`\n * and `'use server'` actions; that part is Next-specific and stays in your app.\n *\n * @example\n * const { mutate: doLogout } = useLogout();\n * await doLogout();\n * router.push(logoutUrl); // app-specific redirect\n */\nexport function useLogout(\n options?: RequestOptions\n): MutationState<void, []> {\n return useMutation<void, []>(\n () => logout(options),\n [options?.baseUrl, options?.token]\n );\n}\n\nexport default useLogout;\n","import { fetchSession, type RequestOptions, type SessionStatus } from \"../core\";\nimport { useQuery, type QueryState } from \"./shared\";\n\n/**\n * Current login status (`{ loggedIn, expiresAt }`) from `GET /api/session`.\n * Tokens are never exposed to the browser.\n *\n * @example\n * const { data: session, loading } = useSession();\n * if (!loading && !session?.loggedIn) window.location.href = loginPath(location.pathname);\n */\nexport function useSession(options?: RequestOptions): QueryState<SessionStatus> {\n return useQuery<SessionStatus>(\n (signal) => fetchSession({ ...options, signal }),\n [options?.baseUrl]\n );\n}\n\nexport default useSession;\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport {\n buildBasketReportQuery,\n buildConsumerMap,\n collectConsumerIds,\n collectProductIds,\n combineBasketRows,\n computeBasketSummary,\n fetchProductsMap,\n parseBasketReportData,\n DEFAULT_BASKET_FILTERS,\n DEFAULT_PAGINATION,\n type BasketFilters,\n type BasketPagination,\n type BasketReportRow,\n type BasketSummary,\n type Product,\n type RequestOptions,\n} from \"../core\";\nimport useReport from \"./useReport\";\nimport useConsumer from \"./useConsumer\";\n\nexport interface UseBasketReportReturn {\n rows: BasketReportRow[];\n summary: BasketSummary;\n loading: boolean;\n error: string | null;\n filters: BasketFilters;\n setFilters: (filters: Partial<BasketFilters>) => void;\n pagination: BasketPagination;\n setPagination: (page: number, pageSize: number) => void;\n refresh: () => void;\n}\n\n/**\n * Composite hook that orchestrates the report → consumers → products pipeline\n * for the basket report screen. All the data-shaping logic now lives in the\n * framework-agnostic `core/basket` helpers; this hook is the thin React glue\n * (state, memoisation, effects) on top of them.\n */\nexport function useBasketReport(\n options?: RequestOptions\n): UseBasketReportReturn {\n const [filters, setFiltersState] = useState<BasketFilters>(\n DEFAULT_BASKET_FILTERS\n );\n const [pagination, setPaginationState] = useState<BasketPagination>(\n DEFAULT_PAGINATION\n );\n const [refreshFlag, setRefreshFlag] = useState(0);\n\n // Build the report query from filters (core helper).\n const reportQuery = useMemo(\n () => buildBasketReportQuery(filters),\n // refreshFlag forces a new object identity to re-trigger the fetch.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [filters, refreshFlag]\n );\n\n const {\n data: reports,\n loading: basketLoading,\n error: basketError,\n } = useReport(reportQuery, options);\n\n // Flatten raw report rows into structured baskets (core helper).\n const basketData = useMemo(() => parseBasketReportData(reports), [reports]);\n\n // Derive id sets for the dependent fetches (core helpers).\n const consumerIds = useMemo(\n () => collectConsumerIds(basketData),\n [basketData]\n );\n const productIds = useMemo(\n () => collectProductIds(basketData),\n [basketData]\n );\n\n const {\n data: consumers,\n loading: consumerLoading,\n error: consumerError,\n } = useConsumer(consumerIds, options);\n\n // Batched product fetch (core helper handles the 200-per-request batching).\n const [productMap, setProductMap] = useState<{ [id: number]: Product }>({});\n const [productLoading, setProductLoading] = useState(false);\n const [productError, setProductError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!productIds || productIds.length === 0) {\n setProductMap({});\n return;\n }\n\n let active = true;\n setProductLoading(true);\n setProductError(null);\n\n fetchProductsMap(productIds, options)\n .then((map) => {\n if (active) setProductMap(map);\n })\n .catch((e) => {\n if (active) {\n setProductError(\n e instanceof Error ? e.message : \"Failed to fetch products\"\n );\n }\n })\n .finally(() => {\n if (active) setProductLoading(false);\n });\n\n return () => {\n active = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(productIds), options?.baseUrl, options?.token]);\n\n // Consumer lookup (core helper).\n const consumerMap = useMemo(() => buildConsumerMap(consumers), [consumers]);\n\n // Combine into final rows (core helper: join + search + sort).\n const rows: BasketReportRow[] = useMemo(\n () =>\n combineBasketRows({\n baskets: basketData,\n consumerMap,\n productMap,\n search: filters.search,\n }),\n [basketData, consumerMap, productMap, filters.search]\n );\n\n // Summary (core helper).\n const summary: BasketSummary = useMemo(\n () => computeBasketSummary(rows),\n [rows]\n );\n\n // Keep pagination total in sync with fetched data.\n useEffect(() => {\n if (rows.length > 0 && rows.length === pagination.pageSize) {\n setPaginationState((prev) => ({\n ...prev,\n total: (pagination.page + 2) * pagination.pageSize,\n }));\n } else if (rows.length > 0) {\n setPaginationState((prev) => ({\n ...prev,\n total: pagination.page * pagination.pageSize + rows.length,\n }));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [rows.length, pagination.page, pagination.pageSize]);\n\n const setFilters = useCallback((newFilters: Partial<BasketFilters>) => {\n setFiltersState((prev) => ({ ...prev, ...newFilters }));\n setPaginationState((prev) => ({ ...prev, page: 0 }));\n }, []);\n\n const setPagination = useCallback((page: number, pageSize: number) => {\n setPaginationState((prev) => ({ ...prev, page, pageSize }));\n }, []);\n\n const refresh = useCallback(() => {\n setRefreshFlag((prev) => prev + 1);\n }, []);\n\n const loading = basketLoading || consumerLoading || productLoading;\n const error = basketError || consumerError || productError;\n\n return {\n rows,\n summary,\n loading,\n error,\n filters,\n setFilters,\n pagination,\n setPagination,\n refresh,\n };\n}\n\nexport default useBasketReport;\n"],"mappings":"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"}
@@ -3,10 +3,20 @@
3
3
  * Consistent return shape for every auto-fetching ("query") hook.
4
4
  */
5
5
  interface QueryState<T> {
6
+ /**
7
+ * The latest data for the current params. Kept while a `refetch()` is in
8
+ * flight (and after a failed refetch), so lists don't blank out and flash a
9
+ * skeleton after every save. Reset to `null` when the params change.
10
+ */
6
11
  data: T | null;
12
+ /** True while there's no data to show yet (first load, or params changed). */
7
13
  loading: boolean;
14
+ /** True whenever a request is in flight, including background refetches. */
15
+ fetching: boolean;
8
16
  error: string | null;
9
- /** Re-runs the underlying fetch. */
17
+ /** HTTP status of the last error (0 = no response), or `null`. */
18
+ errorStatus: number | null;
19
+ /** Re-runs the underlying fetch, keeping the current data visible meanwhile. */
10
20
  refetch: () => void;
11
21
  }
12
22
  /**
@@ -18,6 +28,8 @@ interface MutationState<TData, TArgs extends any[]> {
18
28
  data: TData | null;
19
29
  loading: boolean;
20
30
  error: string | null;
31
+ /** HTTP status of the last error (0 = no response), or `null`. */
32
+ errorStatus: number | null;
21
33
  }
22
34
  //#endregion
23
35
  //#region src/core/types.d.ts
@@ -56,19 +68,24 @@ interface apiResponse {
56
68
  /**
57
69
  * Per-call configuration shared by every core API function.
58
70
  *
59
- * Nothing here is React-specific. By accepting these options per call, the
60
- * core layer works in the browser/Next (relative `/api/...` URLs, the default)
61
- * AND in Node / CLI / Vue / Angular by passing an absolute `baseUrl`, an auth
62
- * `token`, and/or a custom `fetch` implementation.
71
+ * Nothing here is React-specific. In the browser/Next leave `baseUrl` empty to
72
+ * call the app's own `/api/...` routes (which hold the session). From Node, a
73
+ * CLI or tests, pass an absolute `baseUrl` pointing at such an app plus a
74
+ * `token`.
63
75
  */
64
76
  interface RequestOptions {
65
77
  /**
66
78
  * Prefixed in front of every request path. Leave undefined in the browser to
67
- * use relative URLs (e.g. `/api/products`). In Node/CLI pass something like
68
- * `https://api.shopwave.com`.
79
+ * use relative URLs (e.g. `/api/products`). From Node pass the app origin,
80
+ * e.g. `http://localhost:3000`.
69
81
  */
70
82
  baseUrl?: string;
71
- /** OAuth/session token forwarded in the `extras.token` header. */
83
+ /**
84
+ * Shopwave OAuth access token. Sent as `Authorization: OAuth <token>` on every
85
+ * request (GET, POST, PUT and DELETE alike). A value that already starts with
86
+ * a scheme (`OAuth …` / `Bearer …`) is sent as-is. Leave unset in the browser:
87
+ * the app's routes use the session cookie.
88
+ */
72
89
  token?: string;
73
90
  /**
74
91
  * Custom fetch implementation. Defaults to the global `fetch`. Useful for
@@ -92,7 +109,7 @@ interface FetchCategoriesParams {
92
109
  categoryIds?: Array<number>;
93
110
  /** Whether to include soft-deleted records. Defaults to `false`. */
94
111
  deleted?: boolean;
95
- /** Token override (also accepted via `options.token`). */
112
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
96
113
  token?: string;
97
114
  }
98
115
  //#endregion
@@ -126,6 +143,8 @@ interface FetchEmployeesParams {
126
143
  employeeIds?: Array<number>;
127
144
  /** Whether to include soft-deleted records. Defaults to `false`. */
128
145
  deleted?: boolean;
146
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
147
+ token?: string;
129
148
  }
130
149
  //#endregion
131
150
  //#region src/core/product.d.ts
@@ -160,7 +179,7 @@ interface FetchProductsParams {
160
179
  storeId?: number;
161
180
  /** Whether to include soft-deleted records. Defaults to `false`. */
162
181
  deleted?: boolean;
163
- /** Token override (also accepted via `options.token`). */
182
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
164
183
  token?: string;
165
184
  }
166
185
  //#endregion
@@ -203,7 +222,7 @@ interface FetchPromotionsParams {
203
222
  active?: boolean;
204
223
  /** Filter for expired promotions. */
205
224
  expired?: boolean;
206
- /** Token override (also accepted via `options.token`). */
225
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
207
226
  token?: string;
208
227
  }
209
228
  //#endregion
@@ -225,8 +244,12 @@ interface Store {
225
244
  sandbox: boolean | null;
226
245
  }
227
246
  interface FetchStoresParams {
247
+ /** Optional id filter. */
248
+ storeIds?: Array<number>;
228
249
  /** Whether to include soft-deleted records. Defaults to `false`. */
229
250
  deleted?: boolean;
251
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
252
+ token?: string;
230
253
  }
231
254
  //#endregion
232
255
  //#region src/core/report.d.ts
@@ -259,6 +282,17 @@ interface ReportQueryMap {
259
282
  [key: string]: ReportQuery;
260
283
  }
261
284
  //#endregion
285
+ //#region src/core/session.d.ts
286
+ /**
287
+ * Login status returned by the app's `/api/session` route. It deliberately
288
+ * never contains tokens — those stay in the encrypted httpOnly cookie.
289
+ */
290
+ interface SessionStatus {
291
+ loggedIn: boolean;
292
+ /** Access-token expiry (epoch ms), when known. */
293
+ expiresAt?: number;
294
+ }
295
+ //#endregion
262
296
  //#region src/core/entity.d.ts
263
297
  interface SubmitEntityArgs {
264
298
  /** Fully-resolved endpoint, e.g. `/api/products` or `/api/products/42`. */
@@ -406,6 +440,17 @@ declare function useSubmit<R = any>(options?: RequestOptions): MutationState<R |
406
440
  */
407
441
  declare function useLogout(options?: RequestOptions): MutationState<void, []>;
408
442
  //#endregion
443
+ //#region src/hooks/useSession.d.ts
444
+ /**
445
+ * Current login status (`{ loggedIn, expiresAt }`) from `GET /api/session`.
446
+ * Tokens are never exposed to the browser.
447
+ *
448
+ * @example
449
+ * const { data: session, loading } = useSession();
450
+ * if (!loading && !session?.loggedIn) window.location.href = loginPath(location.pathname);
451
+ */
452
+ declare function useSession(options?: RequestOptions): QueryState<SessionStatus>;
453
+ //#endregion
409
454
  //#region src/hooks/useBasketReport.d.ts
410
455
  interface UseBasketReportReturn {
411
456
  rows: BasketReportRow[];
@@ -426,5 +471,5 @@ interface UseBasketReportReturn {
426
471
  */
427
472
  declare function useBasketReport(options?: RequestOptions): UseBasketReportReturn;
428
473
  //#endregion
429
- export { type MutationState, type QueryState, type UseBasketReportReturn, useBasketReport, useCategory, useConsumer, useDelete, useEmployee, useLogout, useProduct, usePromotion, useReport, useStore, useSubmit };
474
+ export { type MutationState, type QueryState, type UseBasketReportReturn, useBasketReport, useCategory, useConsumer, useDelete, useEmployee, useLogout, useProduct, usePromotion, useReport, useSession, useStore, useSubmit };
430
475
  //# sourceMappingURL=index.d.cts.map
@@ -3,10 +3,20 @@
3
3
  * Consistent return shape for every auto-fetching ("query") hook.
4
4
  */
5
5
  interface QueryState<T> {
6
+ /**
7
+ * The latest data for the current params. Kept while a `refetch()` is in
8
+ * flight (and after a failed refetch), so lists don't blank out and flash a
9
+ * skeleton after every save. Reset to `null` when the params change.
10
+ */
6
11
  data: T | null;
12
+ /** True while there's no data to show yet (first load, or params changed). */
7
13
  loading: boolean;
14
+ /** True whenever a request is in flight, including background refetches. */
15
+ fetching: boolean;
8
16
  error: string | null;
9
- /** Re-runs the underlying fetch. */
17
+ /** HTTP status of the last error (0 = no response), or `null`. */
18
+ errorStatus: number | null;
19
+ /** Re-runs the underlying fetch, keeping the current data visible meanwhile. */
10
20
  refetch: () => void;
11
21
  }
12
22
  /**
@@ -18,6 +28,8 @@ interface MutationState<TData, TArgs extends any[]> {
18
28
  data: TData | null;
19
29
  loading: boolean;
20
30
  error: string | null;
31
+ /** HTTP status of the last error (0 = no response), or `null`. */
32
+ errorStatus: number | null;
21
33
  }
22
34
  //#endregion
23
35
  //#region src/core/types.d.ts
@@ -56,19 +68,24 @@ interface apiResponse {
56
68
  /**
57
69
  * Per-call configuration shared by every core API function.
58
70
  *
59
- * Nothing here is React-specific. By accepting these options per call, the
60
- * core layer works in the browser/Next (relative `/api/...` URLs, the default)
61
- * AND in Node / CLI / Vue / Angular by passing an absolute `baseUrl`, an auth
62
- * `token`, and/or a custom `fetch` implementation.
71
+ * Nothing here is React-specific. In the browser/Next leave `baseUrl` empty to
72
+ * call the app's own `/api/...` routes (which hold the session). From Node, a
73
+ * CLI or tests, pass an absolute `baseUrl` pointing at such an app plus a
74
+ * `token`.
63
75
  */
64
76
  interface RequestOptions {
65
77
  /**
66
78
  * Prefixed in front of every request path. Leave undefined in the browser to
67
- * use relative URLs (e.g. `/api/products`). In Node/CLI pass something like
68
- * `https://api.shopwave.com`.
79
+ * use relative URLs (e.g. `/api/products`). From Node pass the app origin,
80
+ * e.g. `http://localhost:3000`.
69
81
  */
70
82
  baseUrl?: string;
71
- /** OAuth/session token forwarded in the `extras.token` header. */
83
+ /**
84
+ * Shopwave OAuth access token. Sent as `Authorization: OAuth <token>` on every
85
+ * request (GET, POST, PUT and DELETE alike). A value that already starts with
86
+ * a scheme (`OAuth …` / `Bearer …`) is sent as-is. Leave unset in the browser:
87
+ * the app's routes use the session cookie.
88
+ */
72
89
  token?: string;
73
90
  /**
74
91
  * Custom fetch implementation. Defaults to the global `fetch`. Useful for
@@ -92,7 +109,7 @@ interface FetchCategoriesParams {
92
109
  categoryIds?: Array<number>;
93
110
  /** Whether to include soft-deleted records. Defaults to `false`. */
94
111
  deleted?: boolean;
95
- /** Token override (also accepted via `options.token`). */
112
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
96
113
  token?: string;
97
114
  }
98
115
  //#endregion
@@ -126,6 +143,8 @@ interface FetchEmployeesParams {
126
143
  employeeIds?: Array<number>;
127
144
  /** Whether to include soft-deleted records. Defaults to `false`. */
128
145
  deleted?: boolean;
146
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
147
+ token?: string;
129
148
  }
130
149
  //#endregion
131
150
  //#region src/core/product.d.ts
@@ -160,7 +179,7 @@ interface FetchProductsParams {
160
179
  storeId?: number;
161
180
  /** Whether to include soft-deleted records. Defaults to `false`. */
162
181
  deleted?: boolean;
163
- /** Token override (also accepted via `options.token`). */
182
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
164
183
  token?: string;
165
184
  }
166
185
  //#endregion
@@ -203,7 +222,7 @@ interface FetchPromotionsParams {
203
222
  active?: boolean;
204
223
  /** Filter for expired promotions. */
205
224
  expired?: boolean;
206
- /** Token override (also accepted via `options.token`). */
225
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
207
226
  token?: string;
208
227
  }
209
228
  //#endregion
@@ -225,8 +244,12 @@ interface Store {
225
244
  sandbox: boolean | null;
226
245
  }
227
246
  interface FetchStoresParams {
247
+ /** Optional id filter. */
248
+ storeIds?: Array<number>;
228
249
  /** Whether to include soft-deleted records. Defaults to `false`. */
229
250
  deleted?: boolean;
251
+ /** @deprecated Pass `options.token` instead. Sent the same way (Authorization header). */
252
+ token?: string;
230
253
  }
231
254
  //#endregion
232
255
  //#region src/core/report.d.ts
@@ -259,6 +282,17 @@ interface ReportQueryMap {
259
282
  [key: string]: ReportQuery;
260
283
  }
261
284
  //#endregion
285
+ //#region src/core/session.d.ts
286
+ /**
287
+ * Login status returned by the app's `/api/session` route. It deliberately
288
+ * never contains tokens — those stay in the encrypted httpOnly cookie.
289
+ */
290
+ interface SessionStatus {
291
+ loggedIn: boolean;
292
+ /** Access-token expiry (epoch ms), when known. */
293
+ expiresAt?: number;
294
+ }
295
+ //#endregion
262
296
  //#region src/core/entity.d.ts
263
297
  interface SubmitEntityArgs {
264
298
  /** Fully-resolved endpoint, e.g. `/api/products` or `/api/products/42`. */
@@ -406,6 +440,17 @@ declare function useSubmit<R = any>(options?: RequestOptions): MutationState<R |
406
440
  */
407
441
  declare function useLogout(options?: RequestOptions): MutationState<void, []>;
408
442
  //#endregion
443
+ //#region src/hooks/useSession.d.ts
444
+ /**
445
+ * Current login status (`{ loggedIn, expiresAt }`) from `GET /api/session`.
446
+ * Tokens are never exposed to the browser.
447
+ *
448
+ * @example
449
+ * const { data: session, loading } = useSession();
450
+ * if (!loading && !session?.loggedIn) window.location.href = loginPath(location.pathname);
451
+ */
452
+ declare function useSession(options?: RequestOptions): QueryState<SessionStatus>;
453
+ //#endregion
409
454
  //#region src/hooks/useBasketReport.d.ts
410
455
  interface UseBasketReportReturn {
411
456
  rows: BasketReportRow[];
@@ -426,5 +471,5 @@ interface UseBasketReportReturn {
426
471
  */
427
472
  declare function useBasketReport(options?: RequestOptions): UseBasketReportReturn;
428
473
  //#endregion
429
- export { type MutationState, type QueryState, type UseBasketReportReturn, useBasketReport, useCategory, useConsumer, useDelete, useEmployee, useLogout, useProduct, usePromotion, useReport, useStore, useSubmit };
474
+ export { type MutationState, type QueryState, type UseBasketReportReturn, useBasketReport, useCategory, useConsumer, useDelete, useEmployee, useLogout, useProduct, usePromotion, useReport, useSession, useStore, useSubmit };
430
475
  //# sourceMappingURL=index.d.ts.map