react-shopwave-connect 0.1.1 → 0.2.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.
- package/README.md +140 -21
- package/dist/core/index.cjs +46 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +34 -6
- package/dist/core/index.d.ts +34 -6
- package/dist/core/index.js +44 -6
- package/dist/core/index.js.map +1 -1
- package/dist/hooks/index.cjs +50 -5
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +29 -1
- package/dist/hooks/index.d.ts +29 -1
- package/dist/hooks/index.js +50 -6
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +64 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -6
- package/dist/index.d.ts +45 -6
- package/dist/index.js +61 -6
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +585 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +255 -0
- package/dist/next/index.d.ts +255 -0
- package/dist/next/index.js +556 -0
- package/dist/next/index.js.map +1 -0
- package/dist/server/index.cjs +260 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +177 -0
- package/dist/server/index.d.ts +177 -0
- package/dist/server/index.js +248 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +41 -8
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ npm install react react-dom
|
|
|
28
28
|
| `react-shopwave-connect/core` | Framework-agnostic functions + types only |
|
|
29
29
|
| `react-shopwave-connect/hooks` | React hooks only |
|
|
30
30
|
| `react-shopwave-connect` | Both (re-exports `core` + `hooks`) |
|
|
31
|
+
| `react-shopwave-connect/server` | **Server-only.** Framework-agnostic Shopwave OAuth client + token helpers |
|
|
32
|
+
| `react-shopwave-connect/next` | **Server-only.** Next.js login/callback/logout/session route handlers, token refresh, proxy guard |
|
|
31
33
|
|
|
32
34
|
Prefer the subpath imports when you want a hard boundary — e.g. a Node service should import from `/core` so React never enters the dependency graph.
|
|
33
35
|
|
|
@@ -107,7 +109,7 @@ Functions **throw** on network failure or API errors (the error message contains
|
|
|
107
109
|
| product | `fetchProducts`, `fetchProductsMap` (batched, keyed) |
|
|
108
110
|
| store | `fetchStores` |
|
|
109
111
|
| report | `fetchReport` |
|
|
110
|
-
| session | `logout`
|
|
112
|
+
| session | `fetchSession`, `loginPath`, `logoutPath`, `logout` |
|
|
111
113
|
| entity | `deleteEntity`, `submitEntity` |
|
|
112
114
|
| basket | `buildBasketReportQuery`, `parseBasketReportData`, `combineBasketRows`, `computeBasketSummary`, … (pure transforms) |
|
|
113
115
|
|
|
@@ -150,7 +152,7 @@ const { data } = useProduct({ storeId }, { baseUrl: "https://api.merchantstack.c
|
|
|
150
152
|
|
|
151
153
|
### Auto-fetch hooks (`useEffect`-based)
|
|
152
154
|
|
|
153
|
-
`useCategory`, `useConsumer`, `useEmployee`, `useProduct`, `useStore`, `useReport`.
|
|
155
|
+
`useCategory`, `useConsumer`, `useEmployee`, `useProduct`, `usePromotion`, `useStore`, `useReport`, `useSession`.
|
|
154
156
|
They fetch on mount and re-run when their arguments change. `useConsumer` and `useReport` stay idle until you pass ids / a query (they return `loading: false`, `data: null` until then). `refetch()` replaces the old `reloadFlag` argument.
|
|
155
157
|
|
|
156
158
|
### Manually-triggered hooks (`useCallback`-based)
|
|
@@ -179,37 +181,144 @@ const saved = await save({
|
|
|
179
181
|
|
|
180
182
|
---
|
|
181
183
|
|
|
182
|
-
##
|
|
184
|
+
## 3. Authentication (Shopwave OAuth)
|
|
183
185
|
|
|
184
|
-
|
|
186
|
+
Login is the same for every Shopwave app; only the **config** differs (client id/secret, redirect URL, session secret). The Shopwave auth server uses the authorization-code flow **with a client secret** (no PKCE), so the exchange must happen on a server — the browser only follows redirects and never sees a token.
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
```
|
|
189
|
+
browser ──/auth?returnTo=/products──▶ your app ──302──▶ {authServer}/login?…&state=…
|
|
190
|
+
◀──────────── user signs in on Shopwave ─────────────┘
|
|
191
|
+
browser ──/auth?code=…&state=…──▶ your app ──POST /oauth/token (secret)──▶ auth server
|
|
192
|
+
◀──302 /products + encrypted httpOnly cookie (tokens stay server-side)
|
|
193
|
+
```
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
What the SDK handles for you:
|
|
196
|
+
|
|
197
|
+
- `state` check (login-CSRF protection) and a safe `returnTo` (same-origin paths only).
|
|
198
|
+
- One callback URL per app: `returnTo` rides along in the session, so tools/sub-sections don't need their own redirect URIs.
|
|
199
|
+
- Tokens stored as `{ accessToken, refreshToken, tokenType, expiresAt }` in an **iron-session** cookie (httpOnly, SameSite=Lax, Secure in production).
|
|
200
|
+
- Refresh when the access token has expired (Shopwave issues a new one only after expiry; the refresh token is not rotated), de-duplicated across concurrent requests. A rejected refresh token logs the user out.
|
|
201
|
+
- `GET /api/session` returns `{ loggedIn, expiresAt }` — **never tokens**.
|
|
202
|
+
- Sessions written by older apps (raw `{ access_token, refresh_token, … }` in `session.token`) keep working.
|
|
203
|
+
|
|
204
|
+
### Next.js (App Router) setup
|
|
205
|
+
|
|
206
|
+
Install the peer dependencies once: `npm install react-shopwave-connect iron-session`.
|
|
207
|
+
|
|
208
|
+
**1. One config file per app**
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
// lib/auth.ts
|
|
212
|
+
import { createShopwaveAuth } from "react-shopwave-connect/next";
|
|
213
|
+
|
|
214
|
+
export const auth = createShopwaveAuth({
|
|
215
|
+
authServerUrl: process.env.SHOPWAVE_AUTH_SERVER_URL!, // e.g. https://secure.merchantstack.com
|
|
216
|
+
clientId: process.env.SHOPWAVE_CLIENT_ID!,
|
|
217
|
+
clientSecret: process.env.SHOPWAVE_CLIENT_SECRET!,
|
|
218
|
+
redirectUri: process.env.SHOPWAVE_REDIRECT_URL!, // e.g. https://admin.example.com/auth (registered on the auth server)
|
|
219
|
+
session: {
|
|
220
|
+
password: process.env.SESSION_SECRET!, // ≥ 32 random chars
|
|
221
|
+
cookieName: "shopwave_session_cookie", // keep your existing name to keep users logged in
|
|
222
|
+
},
|
|
223
|
+
// authPath: "/auth", logoutPath: "/auth/logout", sessionPath: "/api/session",
|
|
224
|
+
// defaultReturnTo: "/", requireState: true, refreshSkewSeconds: 0,
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Config is validated on first use, so a missing env var doesn't break `next build`.
|
|
229
|
+
|
|
230
|
+
**2. Three route files**
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
232
|
+
```ts
|
|
233
|
+
// app/auth/route.ts — the redirect-URI path: starts login AND receives the callback
|
|
234
|
+
import { auth } from "@/lib/auth";
|
|
235
|
+
export const GET = auth.handlers.auth;
|
|
236
|
+
|
|
237
|
+
// app/auth/logout/route.ts — clears the session, then logs out of the auth server
|
|
238
|
+
import { auth } from "@/lib/auth";
|
|
239
|
+
export const GET = auth.handlers.logout;
|
|
198
240
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
241
|
+
// app/api/session/route.ts — { loggedIn, expiresAt } for the browser; DELETE ends the session
|
|
242
|
+
import { auth } from "@/lib/auth";
|
|
243
|
+
export const { GET, DELETE } = auth.handlers.session;
|
|
244
|
+
```
|
|
203
245
|
|
|
204
|
-
|
|
246
|
+
**3. Protect pages and APIs** (`proxy.ts` in Next 16, `middleware.ts` before that)
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
250
|
+
import { auth } from "@/lib/auth";
|
|
251
|
+
|
|
252
|
+
export async function proxy(request: NextRequest) {
|
|
253
|
+
return (await auth.protect(request, { publicPaths: ["/tools/tag-joiner"] })) ?? NextResponse.next();
|
|
205
254
|
}
|
|
255
|
+
|
|
256
|
+
export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };
|
|
206
257
|
```
|
|
207
258
|
|
|
259
|
+
Logged-out page requests are redirected to `/auth?returnTo=<page>`; paths under `/api` get a `401` JSON instead. The auth, logout and session paths are always public. `protect` only reads the cookie — it never calls the auth server.
|
|
260
|
+
|
|
261
|
+
**4. Use the token in route handlers**
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { auth } from "@/lib/auth";
|
|
265
|
+
import { isExpiredTokenResponse } from "react-shopwave-connect/next";
|
|
266
|
+
|
|
267
|
+
export const GET = auth.withAuth(async (request, context, { authorization }) => {
|
|
268
|
+
const call = (header: string) =>
|
|
269
|
+
fetch(`${process.env.SHOPWAVE_API_SERVER_URL}/product`, {
|
|
270
|
+
headers: { Authorization: header, "x-accept-version": "2.0" },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
let res = await call(authorization);
|
|
274
|
+
let body = await res.json();
|
|
275
|
+
|
|
276
|
+
// Clock said valid but the API says expired (HTTP 401 / API error 908): refresh once and retry.
|
|
277
|
+
if (isExpiredTokenResponse(res.status, body)) {
|
|
278
|
+
const fresh = await auth.getAuthorizationHeader({ forceRefresh: true });
|
|
279
|
+
if (!fresh) return Response.json({ error: "unauthorized" }, { status: 401 });
|
|
280
|
+
res = await call(fresh);
|
|
281
|
+
body = await res.json();
|
|
282
|
+
}
|
|
283
|
+
return Response.json(body, { status: res.status });
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Also available: `auth.getAccessToken()`, `auth.getAuthorizationHeader()`, `auth.getToken()`, `auth.getStatus()`, `auth.loginPath(returnTo)`, `auth.logoutPath`, `auth.isAuthenticated(request)`. Refreshed tokens are saved automatically from route handlers, server actions and proxy (Server Components can read tokens but can't write cookies).
|
|
288
|
+
|
|
289
|
+
**5. In the browser**
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
"use client";
|
|
293
|
+
import { useSession, loginPath, logoutPath } from "react-shopwave-connect";
|
|
294
|
+
|
|
295
|
+
export function AccountButton() {
|
|
296
|
+
const { data: session, loading } = useSession();
|
|
297
|
+
if (loading) return null;
|
|
298
|
+
return session?.loggedIn
|
|
299
|
+
? <a href={logoutPath()}>Log out</a>
|
|
300
|
+
: <a href={loginPath(window.location.pathname)}>Log in</a>;
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Use plain links / `window.location` for login and logout — they are full-page redirects to the auth server, not client-side navigations.
|
|
305
|
+
|
|
306
|
+
### Other frameworks
|
|
307
|
+
|
|
308
|
+
`react-shopwave-connect/server` has the same building blocks without Next.js: `createShopwaveOAuth(config)` → `buildLoginUrl({ state })`, `exchangeCode(code)`, `refreshToken(token)`, `buildLogoutUrl()`, plus `createState`, `sanitizeReturnTo`, `isTokenExpired`, `isExpiredTokenResponse` and `authorizationHeader`. Store the token in your framework's session (Express `express-session`, iron-session's `getIronSession(req, res)`, …).
|
|
309
|
+
|
|
310
|
+
### Moving an existing app over
|
|
311
|
+
|
|
312
|
+
- Keep `redirectUri` equal to the URL already registered on the auth server (AdminUI: `…/auth`) and mount `handlers.auth` at that path.
|
|
313
|
+
- Keep `cookieName` the same and use the same `SESSION_SECRET` value as the old iron-session password — current users stay logged in; their old token shape is read and upgraded on the next refresh.
|
|
314
|
+
- Per-tool auth routes can go: link to `auth.loginPath("/tools/where-to-next")` instead.
|
|
315
|
+
- `core.logout()` / `useLogout()` still work (`DELETE /api/session`), but prefer linking to `logoutPath()` so the auth-server session ends too.
|
|
316
|
+
|
|
208
317
|
---
|
|
209
318
|
|
|
210
319
|
## Build
|
|
211
320
|
|
|
212
|
-
Built with [`tsdown`](https://tsdown.dev) into dual ESM + CJS with `.d.ts` types, via
|
|
321
|
+
Built with [`tsdown`](https://tsdown.dev) into dual ESM + CJS with `.d.ts` types, via separate entries: `core` (no externals), `hooks` (`react`/`react-dom` external), `server` (no externals) and `next` (`next`/`iron-session` external — optional peer dependencies). `server` and `next` are never re-exported from the root entry, so they can't end up in client bundles.
|
|
213
322
|
|
|
214
323
|
```bash
|
|
215
324
|
npm run build # emit dist/
|
|
@@ -220,6 +329,12 @@ npm run typecheck # tsc --noEmit
|
|
|
220
329
|
|
|
221
330
|
## Testing
|
|
222
331
|
|
|
332
|
+
Unit tests (offline, no credentials) cover the OAuth client, token handling, the Next.js handlers (with an in-memory cookie store) and the session client:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npm test # = npm run test:unit
|
|
336
|
+
```
|
|
337
|
+
|
|
223
338
|
Integration tests run against a live API using [Vitest](https://vitest.dev). Tests are located in `tests/integration/` and cover full CRUD lifecycles for Products, Categories, Consumers, and Employees.
|
|
224
339
|
|
|
225
340
|
### Environment Variables
|
|
@@ -239,7 +354,7 @@ export API_TOKEN=your-staging-token
|
|
|
239
354
|
# Run all integration tests
|
|
240
355
|
npm run test:integration
|
|
241
356
|
|
|
242
|
-
# Run tests
|
|
357
|
+
# Run unit tests
|
|
243
358
|
npm test
|
|
244
359
|
```
|
|
245
360
|
|
|
@@ -247,6 +362,10 @@ npm test
|
|
|
247
362
|
|
|
248
363
|
```
|
|
249
364
|
tests/
|
|
365
|
+
unit/
|
|
366
|
+
server.test.ts # OAuth client, tokens, returnTo/state
|
|
367
|
+
next.test.ts # Next.js handlers, refresh, proxy guard
|
|
368
|
+
session-client.test.ts # fetchSession / loginPath / logoutPath
|
|
250
369
|
integration/
|
|
251
370
|
setup.ts # Shared config and helpers
|
|
252
371
|
products.integration.test.ts # Products CRUD lifecycle
|
package/dist/core/index.cjs
CHANGED
|
@@ -181,6 +181,9 @@ let PromotionRuleSet = /* @__PURE__ */ function(PromotionRuleSet$1) {
|
|
|
181
181
|
async function fetchPromotions(params = {}, options = {}) {
|
|
182
182
|
const extras = { deleted: params.deleted ?? false };
|
|
183
183
|
if (params.promotionIds) extras.promotionIds = params.promotionIds;
|
|
184
|
+
if (params.imminent !== void 0) extras.imminent = params.imminent;
|
|
185
|
+
if (params.active !== void 0) extras.active = params.active;
|
|
186
|
+
if (params.expired !== void 0) extras.expired = params.expired;
|
|
184
187
|
if (params.token) extras.token = params.token;
|
|
185
188
|
const json = await apiGet("/api/promotions", extras, options);
|
|
186
189
|
const error = getApiErrors(json.api);
|
|
@@ -217,12 +220,47 @@ async function fetchReport(query, options = {}) {
|
|
|
217
220
|
//#endregion
|
|
218
221
|
//#region src/core/session.ts
|
|
219
222
|
/**
|
|
220
|
-
*
|
|
223
|
+
* Reads the current login status from `GET /api/session`.
|
|
221
224
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
|
|
225
|
+
* Works with the new `react-shopwave-connect/next` session route
|
|
226
|
+
* (`{ loggedIn }`) and with older routes that returned the raw session
|
|
227
|
+
* (`{ token: {...} }`) — in that case only a boolean is surfaced.
|
|
228
|
+
*/
|
|
229
|
+
async function fetchSession(options = {}) {
|
|
230
|
+
const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
231
|
+
if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
|
|
232
|
+
const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
|
|
233
|
+
method: "GET",
|
|
234
|
+
headers: { Accept: "application/json" },
|
|
235
|
+
cache: "no-store",
|
|
236
|
+
credentials: "same-origin",
|
|
237
|
+
signal: options.signal
|
|
238
|
+
});
|
|
239
|
+
if (response.status === 401) return { loggedIn: false };
|
|
240
|
+
if (!response.ok) throw new Error(`Session request failed (${response.status})`);
|
|
241
|
+
const json = await response.json();
|
|
242
|
+
if (json && typeof json.loggedIn === "boolean") return {
|
|
243
|
+
loggedIn: json.loggedIn,
|
|
244
|
+
expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
|
|
245
|
+
};
|
|
246
|
+
return { loggedIn: Boolean(json && json.token) };
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Path that starts a login and returns to `returnTo` afterwards.
|
|
250
|
+
* Navigate the browser to it (`window.location.href = loginPath(...)`).
|
|
251
|
+
*/
|
|
252
|
+
function loginPath(returnTo, authPath = "/auth") {
|
|
253
|
+
return returnTo ? `${authPath}?returnTo=${encodeURIComponent(returnTo)}` : authPath;
|
|
254
|
+
}
|
|
255
|
+
/** Path that logs out of the app and the Shopwave auth server. */
|
|
256
|
+
function logoutPath(authPath = "/auth") {
|
|
257
|
+
return `${authPath}/logout`;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Ends the current server session (no redirect).
|
|
261
|
+
*
|
|
262
|
+
* Prefer navigating to {@link logoutPath} when using
|
|
263
|
+
* `react-shopwave-connect/next`: it also signs the user out of the auth server.
|
|
226
264
|
*/
|
|
227
265
|
async function logout(options = {}) {
|
|
228
266
|
await apiDelete("/api/session?action=logout", options);
|
|
@@ -416,10 +454,13 @@ exports.fetchProducts = fetchProducts;
|
|
|
416
454
|
exports.fetchProductsMap = fetchProductsMap;
|
|
417
455
|
exports.fetchPromotions = fetchPromotions;
|
|
418
456
|
exports.fetchReport = fetchReport;
|
|
457
|
+
exports.fetchSession = fetchSession;
|
|
419
458
|
exports.fetchStores = fetchStores;
|
|
420
459
|
exports.formatDateForQuery = formatDateForQuery;
|
|
421
460
|
exports.getApiErrors = getApiErrors;
|
|
461
|
+
exports.loginPath = loginPath;
|
|
422
462
|
exports.logout = logout;
|
|
463
|
+
exports.logoutPath = logoutPath;
|
|
423
464
|
exports.parseBasketReportData = parseBasketReportData;
|
|
424
465
|
exports.rolesById = rolesById;
|
|
425
466
|
exports.submitEntity = submitEntity;
|
package/dist/core/index.cjs.map
CHANGED
|
@@ -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 }"],"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"],"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 /** 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.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"],"mappings":"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"}
|
|
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 }"],"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"],"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 * 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\";\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"],"mappings":"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"}
|
package/dist/core/index.d.cts
CHANGED
|
@@ -259,6 +259,12 @@ interface FetchPromotionsParams {
|
|
|
259
259
|
promotionIds?: Array<number>;
|
|
260
260
|
/** Whether to include soft-deleted records. Defaults to `false`. */
|
|
261
261
|
deleted?: boolean;
|
|
262
|
+
/** Filter for imminent promotions. */
|
|
263
|
+
imminent?: boolean;
|
|
264
|
+
/** Filter for active promotions. */
|
|
265
|
+
active?: boolean;
|
|
266
|
+
/** Filter for expired promotions. */
|
|
267
|
+
expired?: boolean;
|
|
262
268
|
/** Token override (also accepted via `options.token`). */
|
|
263
269
|
token?: string;
|
|
264
270
|
}
|
|
@@ -337,12 +343,34 @@ declare function fetchReport(query: ReportQueryMap, options?: RequestOptions): P
|
|
|
337
343
|
//#endregion
|
|
338
344
|
//#region src/core/session.d.ts
|
|
339
345
|
/**
|
|
340
|
-
*
|
|
346
|
+
* Login status returned by the app's `/api/session` route. It deliberately
|
|
347
|
+
* never contains tokens — those stay in the encrypted httpOnly cookie.
|
|
348
|
+
*/
|
|
349
|
+
interface SessionStatus {
|
|
350
|
+
loggedIn: boolean;
|
|
351
|
+
/** Access-token expiry (epoch ms), when known. */
|
|
352
|
+
expiresAt?: number;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Reads the current login status from `GET /api/session`.
|
|
356
|
+
*
|
|
357
|
+
* Works with the new `react-shopwave-connect/next` session route
|
|
358
|
+
* (`{ loggedIn }`) and with older routes that returned the raw session
|
|
359
|
+
* (`{ token: {...} }`) — in that case only a boolean is surfaced.
|
|
360
|
+
*/
|
|
361
|
+
declare function fetchSession(options?: RequestOptions): Promise<SessionStatus>;
|
|
362
|
+
/**
|
|
363
|
+
* Path that starts a login and returns to `returnTo` afterwards.
|
|
364
|
+
* Navigate the browser to it (`window.location.href = loginPath(...)`).
|
|
365
|
+
*/
|
|
366
|
+
declare function loginPath(returnTo?: string, authPath?: string): string;
|
|
367
|
+
/** Path that logs out of the app and the Shopwave auth server. */
|
|
368
|
+
declare function logoutPath(authPath?: string): string;
|
|
369
|
+
/**
|
|
370
|
+
* Ends the current server session (no redirect).
|
|
341
371
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* built from server actions) is intentionally NOT included here because it is
|
|
345
|
-
* tied to `next/navigation` and `'use server'` actions — see the README.
|
|
372
|
+
* Prefer navigating to {@link logoutPath} when using
|
|
373
|
+
* `react-shopwave-connect/next`: it also signs the user out of the auth server.
|
|
346
374
|
*/
|
|
347
375
|
declare function logout(options?: RequestOptions): Promise<void>;
|
|
348
376
|
//#endregion
|
|
@@ -477,5 +505,5 @@ declare function buildConsumerMap(consumers: Consumer[] | null | undefined): {
|
|
|
477
505
|
[id: number]: Consumer;
|
|
478
506
|
};
|
|
479
507
|
//#endregion
|
|
480
|
-
export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, Employee, EmployeeResponse, ErrorMessage, Extras, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, apiDelete, apiGet, apiResponse, apiSend, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, deleteEntity, fetchCategories, fetchConsumers, fetchEmployees, fetchProducts, fetchProductsMap, fetchPromotions, fetchReport, fetchStores, formatDateForQuery, getApiErrors, logout, parseBasketReportData, rolesById, submitEntity };
|
|
508
|
+
export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, Employee, EmployeeResponse, ErrorMessage, Extras, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, SessionStatus, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, apiDelete, apiGet, apiResponse, apiSend, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, deleteEntity, fetchCategories, fetchConsumers, fetchEmployees, fetchProducts, fetchProductsMap, fetchPromotions, fetchReport, fetchSession, fetchStores, formatDateForQuery, getApiErrors, loginPath, logout, logoutPath, parseBasketReportData, rolesById, submitEntity };
|
|
481
509
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/core/index.d.ts
CHANGED
|
@@ -259,6 +259,12 @@ interface FetchPromotionsParams {
|
|
|
259
259
|
promotionIds?: Array<number>;
|
|
260
260
|
/** Whether to include soft-deleted records. Defaults to `false`. */
|
|
261
261
|
deleted?: boolean;
|
|
262
|
+
/** Filter for imminent promotions. */
|
|
263
|
+
imminent?: boolean;
|
|
264
|
+
/** Filter for active promotions. */
|
|
265
|
+
active?: boolean;
|
|
266
|
+
/** Filter for expired promotions. */
|
|
267
|
+
expired?: boolean;
|
|
262
268
|
/** Token override (also accepted via `options.token`). */
|
|
263
269
|
token?: string;
|
|
264
270
|
}
|
|
@@ -337,12 +343,34 @@ declare function fetchReport(query: ReportQueryMap, options?: RequestOptions): P
|
|
|
337
343
|
//#endregion
|
|
338
344
|
//#region src/core/session.d.ts
|
|
339
345
|
/**
|
|
340
|
-
*
|
|
346
|
+
* Login status returned by the app's `/api/session` route. It deliberately
|
|
347
|
+
* never contains tokens — those stay in the encrypted httpOnly cookie.
|
|
348
|
+
*/
|
|
349
|
+
interface SessionStatus {
|
|
350
|
+
loggedIn: boolean;
|
|
351
|
+
/** Access-token expiry (epoch ms), when known. */
|
|
352
|
+
expiresAt?: number;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Reads the current login status from `GET /api/session`.
|
|
356
|
+
*
|
|
357
|
+
* Works with the new `react-shopwave-connect/next` session route
|
|
358
|
+
* (`{ loggedIn }`) and with older routes that returned the raw session
|
|
359
|
+
* (`{ token: {...} }`) — in that case only a boolean is surfaced.
|
|
360
|
+
*/
|
|
361
|
+
declare function fetchSession(options?: RequestOptions): Promise<SessionStatus>;
|
|
362
|
+
/**
|
|
363
|
+
* Path that starts a login and returns to `returnTo` afterwards.
|
|
364
|
+
* Navigate the browser to it (`window.location.href = loginPath(...)`).
|
|
365
|
+
*/
|
|
366
|
+
declare function loginPath(returnTo?: string, authPath?: string): string;
|
|
367
|
+
/** Path that logs out of the app and the Shopwave auth server. */
|
|
368
|
+
declare function logoutPath(authPath?: string): string;
|
|
369
|
+
/**
|
|
370
|
+
* Ends the current server session (no redirect).
|
|
341
371
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* built from server actions) is intentionally NOT included here because it is
|
|
345
|
-
* tied to `next/navigation` and `'use server'` actions — see the README.
|
|
372
|
+
* Prefer navigating to {@link logoutPath} when using
|
|
373
|
+
* `react-shopwave-connect/next`: it also signs the user out of the auth server.
|
|
346
374
|
*/
|
|
347
375
|
declare function logout(options?: RequestOptions): Promise<void>;
|
|
348
376
|
//#endregion
|
|
@@ -477,5 +505,5 @@ declare function buildConsumerMap(consumers: Consumer[] | null | undefined): {
|
|
|
477
505
|
[id: number]: Consumer;
|
|
478
506
|
};
|
|
479
507
|
//#endregion
|
|
480
|
-
export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, Employee, EmployeeResponse, ErrorMessage, Extras, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, apiDelete, apiGet, apiResponse, apiSend, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, deleteEntity, fetchCategories, fetchConsumers, fetchEmployees, fetchProducts, fetchProductsMap, fetchPromotions, fetchReport, fetchStores, formatDateForQuery, getApiErrors, logout, parseBasketReportData, rolesById, submitEntity };
|
|
508
|
+
export { Basket, BasketFilters, BasketItem, BasketPagination, BasketReportRow, BasketSummary, Category, CategoryResponse, CombineBasketRowsParams, Consumer, ConsumerResponse, DEFAULT_BASKET_FILTERS, DEFAULT_PAGINATION, Employee, EmployeeResponse, ErrorMessage, Extras, FetchCategoriesParams, FetchEmployeesParams, FetchProductsParams, FetchPromotionsParams, FetchStoresParams, MessageInfo, ParsedBasket, Product, ProductInstance, ProductResponse, Promotion, PromotionResponse, PromotionRuleSet, ReportData, ReportKey, ReportQuery, ReportQueryMap, ReportResponse, RequestOptions, SessionStatus, Store, StoreResponse, SubmitEntityArgs, SubmitEntityResponse, SuccessMessage, apiDelete, apiGet, apiResponse, apiSend, buildBasketReportQuery, buildConsumerMap, collectConsumerIds, collectProductIds, combineBasketRows, computeBasketSummary, deleteEntity, fetchCategories, fetchConsumers, fetchEmployees, fetchProducts, fetchProductsMap, fetchPromotions, fetchReport, fetchSession, fetchStores, formatDateForQuery, getApiErrors, loginPath, logout, logoutPath, parseBasketReportData, rolesById, submitEntity };
|
|
481
509
|
//# sourceMappingURL=index.d.ts.map
|