kaafil-react-uikit 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +187 -0
  4. package/dist/OfflineFallbackScreen-B5dfaJBN.d.ts +37 -0
  5. package/dist/OfflineFallbackScreen-Ctm7nBa-.d.cts +37 -0
  6. package/dist/SessionExpiredScreen-CytQNuak.d.cts +50 -0
  7. package/dist/SessionExpiredScreen-D8GHyZc3.d.ts +50 -0
  8. package/dist/admin/index.cjs +23779 -0
  9. package/dist/admin/index.cjs.map +1 -0
  10. package/dist/admin/index.d.cts +1157 -0
  11. package/dist/admin/index.d.ts +1157 -0
  12. package/dist/admin/index.js +23724 -0
  13. package/dist/admin/index.js.map +1 -0
  14. package/dist/chunk-6FPAGGFL.js +6028 -0
  15. package/dist/chunk-6FPAGGFL.js.map +1 -0
  16. package/dist/chunk-ACVU55PZ.cjs +294 -0
  17. package/dist/chunk-ACVU55PZ.cjs.map +1 -0
  18. package/dist/chunk-EM7JV3D7.cjs +6051 -0
  19. package/dist/chunk-EM7JV3D7.cjs.map +1 -0
  20. package/dist/chunk-IIPQ4JNC.js +287 -0
  21. package/dist/chunk-IIPQ4JNC.js.map +1 -0
  22. package/dist/chunk-LL243STJ.cjs +2195 -0
  23. package/dist/chunk-LL243STJ.cjs.map +1 -0
  24. package/dist/chunk-PJRZCOFF.cjs +958 -0
  25. package/dist/chunk-PJRZCOFF.cjs.map +1 -0
  26. package/dist/chunk-PTQ2CJKE.cjs +40 -0
  27. package/dist/chunk-PTQ2CJKE.cjs.map +1 -0
  28. package/dist/chunk-PZKXLQSU.js +10 -0
  29. package/dist/chunk-PZKXLQSU.js.map +1 -0
  30. package/dist/chunk-QHUDOVKW.cjs +111 -0
  31. package/dist/chunk-QHUDOVKW.cjs.map +1 -0
  32. package/dist/chunk-TOBZFADB.cjs +12 -0
  33. package/dist/chunk-TOBZFADB.cjs.map +1 -0
  34. package/dist/chunk-UUGMNP27.js +936 -0
  35. package/dist/chunk-UUGMNP27.js.map +1 -0
  36. package/dist/chunk-UYJ6LIWP.js +2154 -0
  37. package/dist/chunk-UYJ6LIWP.js.map +1 -0
  38. package/dist/chunk-VHTSTUCJ.cjs +346 -0
  39. package/dist/chunk-VHTSTUCJ.cjs.map +1 -0
  40. package/dist/chunk-WUAFL77X.js +340 -0
  41. package/dist/chunk-WUAFL77X.js.map +1 -0
  42. package/dist/chunk-XI7MK4Y7.js +38 -0
  43. package/dist/chunk-XI7MK4Y7.js.map +1 -0
  44. package/dist/chunk-YDGR7JSY.js +107 -0
  45. package/dist/chunk-YDGR7JSY.js.map +1 -0
  46. package/dist/core/index.cjs +1254 -0
  47. package/dist/core/index.cjs.map +1 -0
  48. package/dist/core/index.d.cts +1267 -0
  49. package/dist/core/index.d.ts +1267 -0
  50. package/dist/core/index.js +995 -0
  51. package/dist/core/index.js.map +1 -0
  52. package/dist/index.css +1 -0
  53. package/dist/manager/index.cjs +13562 -0
  54. package/dist/manager/index.cjs.map +1 -0
  55. package/dist/manager/index.d.cts +1054 -0
  56. package/dist/manager/index.d.ts +1054 -0
  57. package/dist/manager/index.js +13518 -0
  58. package/dist/manager/index.js.map +1 -0
  59. package/dist/testing/index.cjs +221 -0
  60. package/dist/testing/index.cjs.map +1 -0
  61. package/dist/testing/index.d.cts +269 -0
  62. package/dist/testing/index.d.ts +269 -0
  63. package/dist/testing/index.js +208 -0
  64. package/dist/testing/index.js.map +1 -0
  65. package/dist/tokenStyle-CToG5oOS.d.cts +3 -0
  66. package/dist/tokenStyle-CToG5oOS.d.ts +3 -0
  67. package/dist/traveller/index.cjs +1799 -0
  68. package/dist/traveller/index.cjs.map +1 -0
  69. package/dist/traveller/index.d.cts +58 -0
  70. package/dist/traveller/index.d.ts +58 -0
  71. package/dist/traveller/index.js +1789 -0
  72. package/dist/traveller/index.js.map +1 -0
  73. package/dist/useAgencyTrip-BUYSIaC2.d.cts +454 -0
  74. package/dist/useAgencyTrip-BzyJB1D9.d.ts +454 -0
  75. package/dist/useNotifications-BZW1Z_ur.d.cts +356 -0
  76. package/dist/useNotifications-D262i5BQ.d.ts +356 -0
  77. package/dist/useVendors-DXOuZWhZ.d.cts +784 -0
  78. package/dist/useVendors-DXOuZWhZ.d.ts +784 -0
  79. package/package.json +95 -0
@@ -0,0 +1,2154 @@
1
+ import { OfflineEngineContext, KaafilClientContext, useCapabilityRows, UNKNOWN_CAPABILITY_TRIPLE, deriveCapabilityTriple, darkReasonFor, useKaafilClient, useOfflineMutation, usePersona, isDevelopment, useOfflineEvent } from './chunk-6FPAGGFL.js';
2
+ import { UnsatisfiableSchemeError, isKaafilError, SyncOpMethod } from 'kaafil-js/client';
3
+ export { FALLBACK_GLYPH, SHADES_PER_FAMILY, TONE_FAMILIES, parseRoomingTone, roomingHeuristicSeverity } from 'kaafil-js/client';
4
+ import { useContext, useState, useRef, useCallback, useEffect } from 'react';
5
+
6
+ // src/core/errors/catalog.ts
7
+ var ERROR_HANDLING_PATH = {
8
+ // re-auth
9
+ UNAUTHENTICATED: "re-auth",
10
+ // retryable (TRANSIENT, `06-offline-first.md §7`)
11
+ RATE_LIMITED: "retryable",
12
+ INTERNAL_ERROR: "retryable",
13
+ TENANT_SCOPE_MISSING: "retryable",
14
+ // parked (the closed never-retried set, `07-offline-and-sync.md §10`)
15
+ PLAN_FEATURE_DISABLED: "parked",
16
+ FILE_PURGED: "parked",
17
+ LOCKED: "parked",
18
+ // surface-to-user (everything else in the 24-code catalog)
19
+ VALIDATION_ERROR: "surface-to-user",
20
+ RESOURCE_NOT_FOUND: "surface-to-user",
21
+ CONFLICT_VERSION: "surface-to-user",
22
+ STOP_HAS_PENDING: "surface-to-user",
23
+ RESPONSE_CLOSED: "surface-to-user",
24
+ BUSINESS_RULE_VIOLATION: "surface-to-user",
25
+ OUT_OF_TRIP_WINDOW: "surface-to-user",
26
+ UPLOAD_MISMATCH: "surface-to-user",
27
+ FILE_ORPHANED: "surface-to-user",
28
+ CAPABILITY_UNAVAILABLE: "surface-to-user",
29
+ READ_ONLY_ROLE: "surface-to-user",
30
+ SUBJECT_REQUIRED: "surface-to-user",
31
+ IDEMPOTENCY_KEY_REUSED: "surface-to-user",
32
+ SECRET_NOT_REPLAYABLE: "surface-to-user",
33
+ NOT_IMPLEMENTED: "surface-to-user",
34
+ SHARE_TOKEN_EXPIRED: "surface-to-user",
35
+ SHARE_TOKEN_REVOKED: "surface-to-user"
36
+ };
37
+ function pathForStatusOnly(status) {
38
+ if (status === void 0 || status === null) {
39
+ return "retryable";
40
+ }
41
+ if (status === 401) {
42
+ return "re-auth";
43
+ }
44
+ if (status === 429 || status >= 500) {
45
+ return "retryable";
46
+ }
47
+ if (status === 402 || status === 410 || status === 423) {
48
+ return "parked";
49
+ }
50
+ return "surface-to-user";
51
+ }
52
+ function classifyError(error) {
53
+ if (error instanceof UnsatisfiableSchemeError) {
54
+ return {
55
+ path: "parked",
56
+ code: void 0,
57
+ status: void 0,
58
+ unsatisfiableScheme: true,
59
+ cause: error
60
+ };
61
+ }
62
+ if (isKaafilError(error)) {
63
+ const catalogPath = error.code !== void 0 ? ERROR_HANDLING_PATH[error.code] : void 0;
64
+ const path = catalogPath ?? pathForStatusOnly(error.status);
65
+ return {
66
+ path,
67
+ code: error.code,
68
+ status: error.status,
69
+ unsatisfiableScheme: false,
70
+ cause: error
71
+ };
72
+ }
73
+ return {
74
+ path: "surface-to-user",
75
+ code: void 0,
76
+ status: void 0,
77
+ unsatisfiableScheme: false,
78
+ cause: error
79
+ };
80
+ }
81
+
82
+ // src/core/internal/ensureTripPulled.ts
83
+ var pulledTrips = /* @__PURE__ */ new WeakMap();
84
+ function ensureTripPulled(engine, tripRef) {
85
+ let seen = pulledTrips.get(engine);
86
+ if (seen === void 0) {
87
+ seen = /* @__PURE__ */ new Set();
88
+ pulledTrips.set(engine, seen);
89
+ }
90
+ if (seen.has(tripRef)) {
91
+ return;
92
+ }
93
+ seen.add(tripRef);
94
+ void engine.pull.pullTrip({ tripRef }).catch(() => {
95
+ seen?.delete(tripRef);
96
+ });
97
+ }
98
+
99
+ // src/core/hooks/useSnapshotList.ts
100
+ function useSnapshotList(tripRef, listName, options) {
101
+ const engine = useContext(OfflineEngineContext);
102
+ const client = useContext(KaafilClientContext);
103
+ const [rows, setRows] = useState(
104
+ () => engine === null ? [] : engine.snapshot.list(tripRef, listName)
105
+ );
106
+ const [liveRows, setLiveRows] = useState(void 0);
107
+ const [liveSyncedAt, setLiveSyncedAt] = useState(void 0);
108
+ const liveRef = useRef(options?.live);
109
+ liveRef.current = options?.live;
110
+ const hasLive = options?.live !== void 0;
111
+ const readLive = useCallback(async () => {
112
+ const live = liveRef.current;
113
+ if (engine !== null || live === void 0 || client === null) {
114
+ return;
115
+ }
116
+ try {
117
+ const result = await live(client, tripRef);
118
+ setLiveRows(result.rows);
119
+ setLiveSyncedAt(result.syncedAt);
120
+ } catch {
121
+ }
122
+ }, [engine, client, tripRef, hasLive]);
123
+ useEffect(() => {
124
+ void readLive();
125
+ }, [readLive]);
126
+ useEffect(() => {
127
+ if (engine === null) {
128
+ setRows([]);
129
+ return;
130
+ }
131
+ ensureTripPulled(engine, tripRef);
132
+ setRows(engine.snapshot.list(tripRef, listName));
133
+ const unsubscribe = engine.events.on("snapshot.updated", (event) => {
134
+ if (event.tripRef === tripRef && event.lists.includes(listName)) {
135
+ setRows(engine.snapshot.list(tripRef, listName));
136
+ }
137
+ });
138
+ return unsubscribe;
139
+ }, [engine, tripRef, listName]);
140
+ if (engine === null) {
141
+ return {
142
+ rows: liveRows ?? rows,
143
+ syncedAt: liveSyncedAt,
144
+ refreshLive: readLive
145
+ };
146
+ }
147
+ let syncedAt;
148
+ for (const row of rows) {
149
+ const stamp = engine.snapshot.syncedAt(tripRef, listName, row.id);
150
+ if (stamp !== void 0 && (syncedAt === void 0 || stamp > syncedAt)) {
151
+ syncedAt = stamp;
152
+ }
153
+ }
154
+ if (syncedAt === void 0 && rows.length === 0) {
155
+ syncedAt = engine.snapshot.cursor(tripRef, listName);
156
+ }
157
+ return { rows, syncedAt, refreshLive: readLive };
158
+ }
159
+
160
+ // src/core/internal/createDomainHook.ts
161
+ function createDomainHook(moduleKey, build) {
162
+ return function useDomainHook(tripRef) {
163
+ const { rows, loaded } = useCapabilityRows(tripRef);
164
+ const ready = build(tripRef);
165
+ if (!loaded) {
166
+ return { status: "loading" };
167
+ }
168
+ const row = rows.find((candidate) => candidate.capability === moduleKey);
169
+ const triple = row === void 0 ? UNKNOWN_CAPABILITY_TRIPLE : deriveCapabilityTriple(row);
170
+ if (triple.status !== "lit") {
171
+ const reason = darkReasonFor(triple);
172
+ return { status: "dark", reason };
173
+ }
174
+ return { status: "ready", ...ready };
175
+ };
176
+ }
177
+
178
+ // src/core/hooks/useChecklists.ts
179
+ var useChecklistsInternal = createDomainHook("checklists", (tripRef) => {
180
+ const client = useKaafilClient();
181
+ const engine = useContext(OfflineEngineContext);
182
+ const { rows: snapshotItems, syncedAt } = useSnapshotList(
183
+ tripRef,
184
+ "checklist"
185
+ );
186
+ const [aggregate, setAggregate] = useState({
187
+ title: void 0,
188
+ subtitle: void 0,
189
+ progress: void 0,
190
+ hasOpenMandatoryByPhase: void 0,
191
+ sections: [],
192
+ items: [],
193
+ availableTemplates: [],
194
+ loaded: false,
195
+ lastFetchFailed: false,
196
+ fetchedAt: void 0
197
+ });
198
+ const refreshAggregate = useCallback(async () => {
199
+ try {
200
+ const response = await client.checklists.read({ tripRef });
201
+ setAggregate({
202
+ title: response.title,
203
+ subtitle: response.subtitle,
204
+ progress: response.progress,
205
+ hasOpenMandatoryByPhase: response.hasOpenMandatoryByPhase,
206
+ sections: response.sections,
207
+ items: response.items,
208
+ availableTemplates: response.availableTemplates,
209
+ loaded: true,
210
+ lastFetchFailed: false,
211
+ fetchedAt: response.meta.serverTime
212
+ });
213
+ } catch {
214
+ setAggregate((previous) => ({ ...previous, lastFetchFailed: true }));
215
+ }
216
+ }, [client, tripRef]);
217
+ useEffect(() => {
218
+ void refreshAggregate();
219
+ }, [refreshAggregate]);
220
+ const refreshAggregateAfterDrain = useCallback(() => {
221
+ if (engine === null) {
222
+ return;
223
+ }
224
+ const unsubscribe = engine.events.on("sync.drained", () => {
225
+ unsubscribe();
226
+ void refreshAggregate();
227
+ });
228
+ }, [engine, refreshAggregate]);
229
+ const addItemMutation = useOfflineMutation({
230
+ tripRef,
231
+ enqueue: (engine2, input) => engine2.enqueue({
232
+ tripRef,
233
+ method: SyncOpMethod.Post,
234
+ path: `/api/v1/trips/${tripRef}/checklist/items`,
235
+ operationId: "addChecklistItem",
236
+ body: input
237
+ }),
238
+ // ── THE DESK LANE ─────────────────────────────────────────────────
239
+ // All five checklist writes take the admin triple, and a desk console
240
+ // mounts no `OfflineShell` — without this the write has no transport
241
+ // at all and every attempt fails as an unclassified error.
242
+ // `idempotencyKey` is OPTIONAL on these SDK options, so no lane here
243
+ // mints one (hard rule #4); `linkExpenseReceipt`, whose key is
244
+ // required, is the one write in this package that CANNOT have a desk
245
+ // lane for exactly that reason.
246
+ direct: (liveClient, input) => liveClient.checklists.items.add({ tripRef, ...input })
247
+ });
248
+ const patchItemMutation = useOfflineMutation({
249
+ tripRef,
250
+ enqueue: (engine2, input) => {
251
+ const { itemId, version, ...body } = input;
252
+ return engine2.enqueue({
253
+ tripRef,
254
+ method: SyncOpMethod.Patch,
255
+ path: `/api/v1/trips/${tripRef}/checklist/items/${itemId}`,
256
+ operationId: "patchChecklistItem",
257
+ body,
258
+ ifMatch: version
259
+ });
260
+ },
261
+ direct: (liveClient, input) => liveClient.checklists.items.patch({ tripRef, ...input })
262
+ });
263
+ const removeItemMutation = useOfflineMutation({
264
+ tripRef,
265
+ enqueue: (engine2, input) => {
266
+ const { itemId, version } = input;
267
+ return engine2.enqueue({
268
+ tripRef,
269
+ method: SyncOpMethod.Delete,
270
+ path: `/api/v1/trips/${tripRef}/checklist/items/${itemId}`,
271
+ operationId: "deleteChecklistItem",
272
+ ifMatch: version
273
+ });
274
+ },
275
+ direct: (liveClient, input) => liveClient.checklists.items.remove({ tripRef, ...input })
276
+ });
277
+ const toggleItemMutation = useOfflineMutation({
278
+ tripRef,
279
+ enqueue: (engine2, input) => {
280
+ const { itemId, ...body } = input;
281
+ return engine2.enqueue({
282
+ tripRef,
283
+ method: SyncOpMethod.Post,
284
+ path: `/api/v1/trips/${tripRef}/checklist/items/${itemId}/toggle`,
285
+ // `expectedStatus`, not `version`/`ifMatch` — this file's header,
286
+ // quirk 1.
287
+ operationId: "toggleChecklistItem",
288
+ body
289
+ });
290
+ },
291
+ direct: (liveClient, input) => liveClient.checklists.items.toggle({ tripRef, ...input })
292
+ });
293
+ const pullTemplateMutation = useOfflineMutation({
294
+ tripRef,
295
+ enqueue: (engine2, input) => engine2.enqueue({
296
+ tripRef,
297
+ method: SyncOpMethod.Post,
298
+ path: `/api/v1/trips/${tripRef}/checklist/templates/pull`,
299
+ operationId: "pullChecklistTemplate",
300
+ body: input
301
+ }),
302
+ direct: (liveClient, input) => liveClient.checklists.templates.pull({ tripRef, ...input })
303
+ });
304
+ const addItemMutate = addItemMutation.mutate;
305
+ const addItem = useCallback(
306
+ async (input) => {
307
+ const op = await addItemMutate(input);
308
+ void refreshAggregate();
309
+ refreshAggregateAfterDrain();
310
+ return op;
311
+ },
312
+ [addItemMutate, refreshAggregate, refreshAggregateAfterDrain]
313
+ );
314
+ const patchItemMutate = patchItemMutation.mutate;
315
+ const patchItem = useCallback(
316
+ async (input) => {
317
+ const op = await patchItemMutate(input);
318
+ void refreshAggregate();
319
+ refreshAggregateAfterDrain();
320
+ return op;
321
+ },
322
+ [patchItemMutate, refreshAggregate, refreshAggregateAfterDrain]
323
+ );
324
+ const removeItemMutate = removeItemMutation.mutate;
325
+ const removeItem = useCallback(
326
+ async (input) => {
327
+ const op = await removeItemMutate(input);
328
+ void refreshAggregate();
329
+ refreshAggregateAfterDrain();
330
+ return op;
331
+ },
332
+ [removeItemMutate, refreshAggregate, refreshAggregateAfterDrain]
333
+ );
334
+ const toggleItemMutate = toggleItemMutation.mutate;
335
+ const toggleItem = useCallback(
336
+ async (input) => {
337
+ const op = await toggleItemMutate(input);
338
+ void refreshAggregate();
339
+ refreshAggregateAfterDrain();
340
+ return op;
341
+ },
342
+ [toggleItemMutate, refreshAggregate, refreshAggregateAfterDrain]
343
+ );
344
+ const pullTemplateMutate = pullTemplateMutation.mutate;
345
+ const pullTemplate = useCallback(
346
+ async (input) => {
347
+ const op = await pullTemplateMutate(input);
348
+ void refreshAggregate();
349
+ refreshAggregateAfterDrain();
350
+ return op;
351
+ },
352
+ [pullTemplateMutate, refreshAggregate, refreshAggregateAfterDrain]
353
+ );
354
+ const listTemplates = useCallback(
355
+ (input) => client.checklists.templates.list({ tripRef, ...input }),
356
+ [client, tripRef]
357
+ );
358
+ const listAgencyTemplates = useCallback(
359
+ (input) => client.checklists.agencyTemplates.list(input),
360
+ [client]
361
+ );
362
+ const createAgencyTemplate = useCallback(
363
+ (input) => client.checklists.agencyTemplates.create(input),
364
+ [client]
365
+ );
366
+ const patchAgencyTemplate = useCallback(
367
+ (input) => client.checklists.agencyTemplates.patch(input),
368
+ [client]
369
+ );
370
+ const removeAgencyTemplate = useCallback(
371
+ (input) => client.checklists.agencyTemplates.remove(input),
372
+ [client]
373
+ );
374
+ const publishAgencyTemplate = useCallback(
375
+ (input) => client.checklists.agencyTemplates.publish(input),
376
+ [client]
377
+ );
378
+ const items = syncedAt === void 0 ? aggregate.items : snapshotItems;
379
+ return {
380
+ items,
381
+ syncedAt,
382
+ aggregate: { ...aggregate, refresh: refreshAggregate },
383
+ addItem,
384
+ patchItem,
385
+ removeItem,
386
+ toggleItem,
387
+ listTemplates,
388
+ pullTemplate,
389
+ listAgencyTemplates,
390
+ createAgencyTemplate,
391
+ patchAgencyTemplate,
392
+ removeAgencyTemplate,
393
+ publishAgencyTemplate
394
+ };
395
+ });
396
+ function useChecklists(tripRef) {
397
+ return useChecklistsInternal(tripRef);
398
+ }
399
+ var useCloseoutInternal = createDomainHook("closing-day", (tripRef) => {
400
+ const client = useKaafilClient();
401
+ const [aggregate, setAggregate] = useState(void 0);
402
+ const [loaded, setLoaded] = useState(false);
403
+ const [lastFetchFailed, setLastFetchFailed] = useState(false);
404
+ const [syncedAt, setSyncedAt] = useState(void 0);
405
+ const refresh = useCallback(async () => {
406
+ try {
407
+ const response = await client.closeout.get({ tripRef });
408
+ setAggregate(response);
409
+ setLoaded(true);
410
+ setLastFetchFailed(false);
411
+ setSyncedAt(response.meta.serverTime);
412
+ } catch {
413
+ setLastFetchFailed(true);
414
+ }
415
+ }, [client, tripRef]);
416
+ useEffect(() => {
417
+ void refresh();
418
+ }, [refresh]);
419
+ const saveHandover = useCallback(
420
+ async (input) => {
421
+ const response = await client.closeout.saveHandover({ tripRef, ...input });
422
+ setAggregate(response);
423
+ setLoaded(true);
424
+ setLastFetchFailed(false);
425
+ setSyncedAt(response.meta.serverTime);
426
+ return response;
427
+ },
428
+ [client, tripRef]
429
+ );
430
+ const lock = useCallback(
431
+ async (input = {}) => {
432
+ const response = await client.closeout.lock({ tripRef, ...input });
433
+ setAggregate(response);
434
+ setLoaded(true);
435
+ setLastFetchFailed(false);
436
+ setSyncedAt(response.meta.serverTime);
437
+ return response;
438
+ },
439
+ [client, tripRef]
440
+ );
441
+ const unlock = useCallback(
442
+ async (input) => {
443
+ const response = await client.closeout.unlock({ tripRef, ...input });
444
+ setAggregate(response);
445
+ setLoaded(true);
446
+ setLastFetchFailed(false);
447
+ setSyncedAt(response.meta.serverTime);
448
+ return response;
449
+ },
450
+ [client, tripRef]
451
+ );
452
+ const exportPdf = useCallback(
453
+ (input = {}) => client.closeout.exportPdf({ tripRef, ...input }),
454
+ [client, tripRef]
455
+ );
456
+ return {
457
+ aggregate,
458
+ loaded,
459
+ lastFetchFailed,
460
+ syncedAt,
461
+ refresh,
462
+ saveHandover,
463
+ lock,
464
+ unlock,
465
+ exportPdf
466
+ };
467
+ });
468
+ function useCloseout(tripRef) {
469
+ return useCloseoutInternal(tripRef);
470
+ }
471
+ var useCollectionsInternal = createDomainHook("collections", (tripRef) => {
472
+ const client = useKaafilClient();
473
+ const {
474
+ rows: collections,
475
+ syncedAt,
476
+ refreshLive
477
+ } = useSnapshotList(tripRef, "collections", {
478
+ live: async (liveClient, ref) => {
479
+ const response = await liveClient.collections.list({ tripRef: ref });
480
+ return { rows: response, syncedAt: response.meta.serverTime };
481
+ }
482
+ });
483
+ const [eligibleStatus, setEligibleStatus] = useState("due");
484
+ const [eligibleRows, setEligibleRows] = useState([]);
485
+ const [eligibleLoaded, setEligibleLoaded] = useState(false);
486
+ const [eligibleFetchFailed, setEligibleFetchFailed] = useState(false);
487
+ const [eligibleFetchedAt, setEligibleFetchedAt] = useState(void 0);
488
+ const refreshEligible = useCallback(
489
+ async (status) => {
490
+ const nextStatus = status ?? eligibleStatus;
491
+ if (status !== void 0 && status !== eligibleStatus) {
492
+ setEligibleStatus(status);
493
+ }
494
+ try {
495
+ const response = await client.collections.eligible({ tripRef, status: nextStatus });
496
+ setEligibleRows(response);
497
+ setEligibleLoaded(true);
498
+ setEligibleFetchFailed(false);
499
+ setEligibleFetchedAt(response.meta.serverTime);
500
+ } catch {
501
+ setEligibleFetchFailed(true);
502
+ }
503
+ },
504
+ [client, tripRef, eligibleStatus]
505
+ );
506
+ useEffect(() => {
507
+ void refreshEligible(eligibleStatus);
508
+ }, [refreshEligible, eligibleStatus]);
509
+ const recordMutation = useOfflineMutation({
510
+ tripRef,
511
+ enqueue: (engine, input) => engine.enqueue({
512
+ tripRef,
513
+ method: SyncOpMethod.Post,
514
+ path: `/api/v1/trips/${tripRef}/collections`,
515
+ // `OperationId` is not exported from `kaafil-js/client`'s public
516
+ // surface (the same gap `useRooming()`'s own comment names) —
517
+ // hardcoded literal, verified against
518
+ // `kaafil-js/src/generated/security.ts` this session.
519
+ operationId: "recordCollection",
520
+ body: input
521
+ }),
522
+ // The desk lane. `recordCollection` takes the admin triple, and a desk
523
+ // console mounts no `OfflineShell` — without this the write has no
524
+ // transport at all and every attempt fails as an unclassified error.
525
+ direct: (liveClient, input) => liveClient.collections.record({ tripRef, ...input })
526
+ });
527
+ const recordMutate = recordMutation.mutate;
528
+ const record = useCallback(
529
+ async (input) => {
530
+ const op = await recordMutate(input);
531
+ void refreshEligible();
532
+ return op;
533
+ },
534
+ [recordMutate, refreshEligible]
535
+ );
536
+ const voidMutation = useOfflineMutation({
537
+ tripRef,
538
+ enqueue: (engine, input) => {
539
+ const { collectionId, version, ...body } = input;
540
+ return engine.enqueue({
541
+ tripRef,
542
+ method: SyncOpMethod.Delete,
543
+ path: `/api/v1/trips/${tripRef}/collections/${collectionId}/void`,
544
+ operationId: "voidCollection",
545
+ body,
546
+ ifMatch: version
547
+ });
548
+ },
549
+ direct: (liveClient, input) => liveClient.collections.void({ tripRef, ...input })
550
+ });
551
+ const voidMutate = voidMutation.mutate;
552
+ const voidCollection = useCallback(
553
+ async (input) => {
554
+ const op = await voidMutate(input);
555
+ void refreshEligible();
556
+ return op;
557
+ },
558
+ [voidMutate, refreshEligible]
559
+ );
560
+ return {
561
+ collections,
562
+ syncedAt,
563
+ refreshLive,
564
+ eligible: {
565
+ rows: eligibleRows,
566
+ status: eligibleStatus,
567
+ loaded: eligibleLoaded,
568
+ lastFetchFailed: eligibleFetchFailed,
569
+ fetchedAt: eligibleFetchedAt,
570
+ refresh: refreshEligible
571
+ },
572
+ record,
573
+ void: voidCollection
574
+ };
575
+ });
576
+ function useCollections(tripRef) {
577
+ return useCollectionsInternal(tripRef);
578
+ }
579
+ var useExpensesInternal = createDomainHook("expenses", (tripRef) => {
580
+ const client = useKaafilClient();
581
+ const {
582
+ rows: expenses,
583
+ syncedAt,
584
+ refreshLive
585
+ } = useSnapshotList(tripRef, "expenses", {
586
+ live: async (liveClient, ref) => {
587
+ const response = await liveClient.expenses.list({ tripRef: ref });
588
+ return { rows: response.items, syncedAt: response.meta.serverTime };
589
+ }
590
+ });
591
+ const [spendTotalMinor, setSpendTotalMinor] = useState(void 0);
592
+ const [categoryTotals, setCategoryTotals] = useState(void 0);
593
+ const [totalsLoaded, setTotalsLoaded] = useState(false);
594
+ const [totalsFailed, setTotalsFailed] = useState(false);
595
+ const refreshTotals = useCallback(async () => {
596
+ try {
597
+ const response = await client.expenses.list({ tripRef });
598
+ setSpendTotalMinor(response.spendTotalMinor);
599
+ setCategoryTotals(response.categoryTotals);
600
+ setTotalsLoaded(true);
601
+ setTotalsFailed(false);
602
+ } catch {
603
+ setTotalsFailed(true);
604
+ }
605
+ }, [client, tripRef]);
606
+ useEffect(() => {
607
+ void refreshTotals();
608
+ }, [refreshTotals]);
609
+ const logMutation = useOfflineMutation({
610
+ tripRef,
611
+ enqueue: (engine, input) => engine.enqueue({
612
+ tripRef,
613
+ method: SyncOpMethod.Post,
614
+ path: `/api/v1/trips/${tripRef}/expenses`,
615
+ // Hardcoded literal, not imported: `OperationId` is not exported
616
+ // from `kaafil-js/client`'s public surface (verified — same gap
617
+ // `useRooming.ts` documents). Value verified against
618
+ // `kaafil-js/src/generated/security.ts` this session.
619
+ operationId: "logExpense",
620
+ body: input
621
+ }),
622
+ // The desk lane. `log`, `linkReceipt` and `void` each additionally
623
+ // accept an agency-admin session (the SDK's own header says so), and a
624
+ // desk console mounts no `OfflineShell` — without these the writes have
625
+ // no transport at all. `submitClaim`/`withdrawClaim` deliberately get
626
+ // NONE: those are `['manager', 'apiKey']`, a manager claiming their own
627
+ // reimbursement, and an admin has no business standing in for them.
628
+ direct: (liveClient, input) => liveClient.expenses.log({ tripRef, ...input })
629
+ });
630
+ const linkReceiptMutation = useOfflineMutation({
631
+ tripRef,
632
+ enqueue: (engine, input) => {
633
+ const { expenseId, ...body } = input;
634
+ return engine.enqueue({
635
+ tripRef,
636
+ method: SyncOpMethod.Patch,
637
+ path: `/api/v1/trips/${tripRef}/expenses/${expenseId}/receipt`,
638
+ operationId: "linkExpenseReceipt",
639
+ body
640
+ });
641
+ }
642
+ // NO DESK LANE, AND IT CANNOT HAVE ONE. `LinkExpenseReceiptOptions`
643
+ // declares `idempotencyKey` as REQUIRED — the route "addresses no
644
+ // client-held version", so the key is its only replay guard — and hard
645
+ // rule #4 forbids any hook here from minting one or accepting one in its
646
+ // signature. The outbox path is unaffected: `OfflineEngine.enqueue()`
647
+ // mints the key itself, which is exactly the arrangement rule #4 exists
648
+ // to preserve. So this write is FIELD-ONLY in practice, and a desk
649
+ // surface must not offer it rather than offering a control that throws.
650
+ // Widening it would mean either breaking rule #4 or making the key
651
+ // optional on the wire, neither of which belongs in a UIKit pass.
652
+ });
653
+ const voidMutation = useOfflineMutation({
654
+ tripRef,
655
+ enqueue: (engine, input) => {
656
+ const { expenseId, version, ...body } = input;
657
+ return engine.enqueue({
658
+ tripRef,
659
+ method: SyncOpMethod.Delete,
660
+ path: `/api/v1/trips/${tripRef}/expenses/${expenseId}/void`,
661
+ operationId: "voidExpense",
662
+ body,
663
+ ifMatch: version
664
+ });
665
+ },
666
+ direct: (liveClient, input) => liveClient.expenses.void({ tripRef, ...input })
667
+ });
668
+ const submitClaimMutation = useOfflineMutation({
669
+ tripRef,
670
+ enqueue: (engine, input) => {
671
+ const { expenseId } = input;
672
+ return engine.enqueue({
673
+ tripRef,
674
+ method: SyncOpMethod.Post,
675
+ path: `/api/v1/trips/${tripRef}/expenses/${expenseId}/claim`,
676
+ operationId: "submitExpenseClaim",
677
+ body: {}
678
+ });
679
+ }
680
+ });
681
+ const withdrawClaimMutation = useOfflineMutation({
682
+ tripRef,
683
+ enqueue: (engine, input) => {
684
+ const { expenseId, version } = input;
685
+ return engine.enqueue({
686
+ tripRef,
687
+ method: SyncOpMethod.Delete,
688
+ path: `/api/v1/trips/${tripRef}/expenses/${expenseId}/claim/withdraw`,
689
+ operationId: "withdrawExpenseClaim",
690
+ body: {},
691
+ ifMatch: version
692
+ });
693
+ }
694
+ });
695
+ const read = useCallback(
696
+ (expenseId) => client.expenses.read({ tripRef, expenseId }),
697
+ [client, tripRef]
698
+ );
699
+ return {
700
+ expenses,
701
+ syncedAt,
702
+ refreshLive,
703
+ totals: {
704
+ spendTotalMinor,
705
+ categoryTotals,
706
+ loaded: totalsLoaded,
707
+ lastFetchFailed: totalsFailed,
708
+ refresh: refreshTotals
709
+ },
710
+ read,
711
+ log: logMutation.mutate,
712
+ linkReceipt: linkReceiptMutation.mutate,
713
+ void: voidMutation.mutate,
714
+ submitClaim: submitClaimMutation.mutate,
715
+ withdrawClaim: withdrawClaimMutation.mutate
716
+ };
717
+ });
718
+ function useExpenses(tripRef) {
719
+ return useExpensesInternal(tripRef);
720
+ }
721
+ var DEFAULT_MAX_ATTEMPTS = 3;
722
+ function useFiles(context) {
723
+ const client = useKaafilClient();
724
+ const { tripRef } = context;
725
+ const request = useCallback(
726
+ (input) => client.files.request({ ...input, tripRef }),
727
+ [client, tripRef]
728
+ );
729
+ const confirm = useCallback((input) => client.files.confirm(input), [client]);
730
+ const meta = useCallback((fileId) => client.files.meta({ fileId }), [client]);
731
+ const url = useCallback((fileId) => client.files.url({ fileId }), [client]);
732
+ const list = useCallback(
733
+ async (query) => (await client.files.list({ ...query, tripRef })).items,
734
+ [client, tripRef]
735
+ );
736
+ const managerView = useCallback(
737
+ (fileId) => client.files.managerView({ fileId }),
738
+ [client]
739
+ );
740
+ const upload = useCallback(
741
+ async ({
742
+ file,
743
+ purpose,
744
+ title,
745
+ travellerRef,
746
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
747
+ signal
748
+ }) => {
749
+ let lastError;
750
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
751
+ const created = await client.files.request({
752
+ tripRef,
753
+ purpose,
754
+ contentType: file.type,
755
+ sizeBytes: file.size,
756
+ // Conditional spreads, not `title: title` — the engine rejects a
757
+ // present-but-undefined key on the wrong purpose the same way it
758
+ // rejects a real one, and "absent" is the only thing that means
759
+ // "no title"/"the whole trip".
760
+ ...title !== void 0 ? { title } : {},
761
+ ...travellerRef !== void 0 ? { travellerRef } : {},
762
+ ...signal !== void 0 ? { signal } : {}
763
+ });
764
+ try {
765
+ const put = await fetch(created.uploadUrl, {
766
+ method: "PUT",
767
+ headers: { "Content-Type": file.type },
768
+ body: file,
769
+ ...signal !== void 0 ? { signal } : {}
770
+ });
771
+ if (!put.ok) {
772
+ lastError = new Error(`Upload PUT failed with status ${put.status}`);
773
+ continue;
774
+ }
775
+ } catch (putError) {
776
+ lastError = putError;
777
+ continue;
778
+ }
779
+ try {
780
+ return await client.files.confirm({
781
+ fileId: created.fileId,
782
+ ...signal !== void 0 ? { signal } : {}
783
+ });
784
+ } catch (confirmError) {
785
+ lastError = confirmError;
786
+ }
787
+ }
788
+ throw lastError instanceof Error ? lastError : new Error("File upload did not confirm after retrying.");
789
+ },
790
+ [client, tripRef]
791
+ );
792
+ return { request, confirm, meta, url, list, managerView, upload };
793
+ }
794
+ var useFloatInternal = createDomainHook("float", (tripRef) => {
795
+ const client = useKaafilClient();
796
+ const { rows: movements, syncedAt } = useSnapshotList(tripRef, "float");
797
+ const [summaryRows, setSummaryRows] = useState([]);
798
+ const [summaryLoaded, setSummaryLoaded] = useState(false);
799
+ const [summaryFetchFailed, setSummaryFetchFailed] = useState(false);
800
+ const [summaryFetchedAt, setSummaryFetchedAt] = useState(void 0);
801
+ const refreshSummary = useCallback(async () => {
802
+ try {
803
+ const response = await client.float.readSummary({ tripRef });
804
+ setSummaryRows(response.data);
805
+ setSummaryLoaded(true);
806
+ setSummaryFetchFailed(false);
807
+ setSummaryFetchedAt(response.meta.serverTime);
808
+ } catch {
809
+ setSummaryFetchFailed(true);
810
+ }
811
+ }, [client, tripRef]);
812
+ useEffect(() => {
813
+ void refreshSummary();
814
+ }, [refreshSummary]);
815
+ const issueMutation = useOfflineMutation({
816
+ tripRef,
817
+ enqueue: (engine, input) => engine.enqueue({
818
+ tripRef,
819
+ method: SyncOpMethod.Post,
820
+ path: `/api/v1/trips/${tripRef}/float/issue`,
821
+ // `OperationId` is not exported from `kaafil-js/client`'s public
822
+ // surface (verified, same gap `useRooming.ts` documents for its own
823
+ // literal operationId casts).
824
+ operationId: "issueFloat",
825
+ body: input
826
+ }),
827
+ // The desk lane. All three float writes take the admin triple — `return`
828
+ // included, contrary to what this file used to claim — and a desk
829
+ // console mounts no `OfflineShell`, so without these the writes have no
830
+ // transport at all.
831
+ direct: (liveClient, input) => liveClient.float.issue({ tripRef, ...input })
832
+ });
833
+ const returnMutation = useOfflineMutation({
834
+ tripRef,
835
+ enqueue: (engine, input) => engine.enqueue({
836
+ tripRef,
837
+ method: SyncOpMethod.Post,
838
+ path: `/api/v1/trips/${tripRef}/float/return`,
839
+ operationId: "returnFloat",
840
+ body: input
841
+ }),
842
+ direct: (liveClient, input) => liveClient.float.return({ tripRef, ...input })
843
+ });
844
+ const adjustMutation = useOfflineMutation({
845
+ tripRef,
846
+ enqueue: (engine, input) => engine.enqueue({
847
+ tripRef,
848
+ method: SyncOpMethod.Post,
849
+ path: `/api/v1/trips/${tripRef}/float/adjust`,
850
+ operationId: "adjustFloat",
851
+ body: input
852
+ }),
853
+ direct: (liveClient, input) => liveClient.float.adjust({ tripRef, ...input })
854
+ });
855
+ const issueMutate = issueMutation.mutate;
856
+ const issue = useCallback(
857
+ async (input) => {
858
+ const op = await issueMutate(input);
859
+ void refreshSummary();
860
+ return op;
861
+ },
862
+ [issueMutate, refreshSummary]
863
+ );
864
+ const returnMutate = returnMutation.mutate;
865
+ const returnFloat = useCallback(
866
+ async (input) => {
867
+ const op = await returnMutate(input);
868
+ void refreshSummary();
869
+ return op;
870
+ },
871
+ [returnMutate, refreshSummary]
872
+ );
873
+ const adjustMutate = adjustMutation.mutate;
874
+ const adjust = useCallback(
875
+ async (input) => {
876
+ const op = await adjustMutate(input);
877
+ void refreshSummary();
878
+ return op;
879
+ },
880
+ [adjustMutate, refreshSummary]
881
+ );
882
+ const readLedger = useCallback(
883
+ (managerId) => client.float.readLedger({ tripRef, managerId }),
884
+ [client, tripRef]
885
+ );
886
+ return {
887
+ movements,
888
+ syncedAt,
889
+ readLedger,
890
+ summary: {
891
+ rows: summaryRows,
892
+ loaded: summaryLoaded,
893
+ lastFetchFailed: summaryFetchFailed,
894
+ fetchedAt: summaryFetchedAt,
895
+ refresh: refreshSummary
896
+ },
897
+ issue,
898
+ return: returnFloat,
899
+ adjust
900
+ };
901
+ });
902
+ function useFloat(tripRef) {
903
+ return useFloatInternal(tripRef);
904
+ }
905
+ var FORM_FIELD_KIND_SINGLE_BOOLEAN = "CHECKBOX";
906
+ var useFormsInternal = createDomainHook("forms", (tripRef) => {
907
+ const client = useKaafilClient();
908
+ const engine = useContext(OfflineEngineContext);
909
+ const [forms, setForms] = useState([]);
910
+ const [syncedAt, setSyncedAt] = useState(void 0);
911
+ const refreshForms = useCallback(async () => {
912
+ try {
913
+ const response = await client.forms.trip.list({ tripRef });
914
+ setForms(response.items);
915
+ setSyncedAt(response.meta.serverTime);
916
+ } catch {
917
+ }
918
+ }, [client, tripRef]);
919
+ useEffect(() => {
920
+ void refreshForms();
921
+ }, [refreshForms]);
922
+ const refreshFormsAfterDrain = useCallback(() => {
923
+ if (engine === null) {
924
+ return;
925
+ }
926
+ const unsubscribe = engine.events.on("sync.drained", () => {
927
+ unsubscribe();
928
+ void refreshForms();
929
+ });
930
+ }, [engine, refreshForms]);
931
+ const getDefinition = useCallback(
932
+ async (formId) => client.forms.get({ formId }),
933
+ [client]
934
+ );
935
+ const getResponse = useCallback(
936
+ async (formId, responseId) => client.forms.responses.get({ formId, responseId }),
937
+ [client]
938
+ );
939
+ const travellerResponses = useCallback(
940
+ async (travellerRef) => {
941
+ const response = await client.forms.trip.travellerResponses.list({ tripRef, travellerRef });
942
+ return response.items;
943
+ },
944
+ [client, tripRef]
945
+ );
946
+ const formResponses = useCallback(
947
+ async (formId) => {
948
+ const response = await client.forms.trip.responses.list({ tripRef, formId });
949
+ return response.items;
950
+ },
951
+ [client, tripRef]
952
+ );
953
+ const submitMutation = useOfflineMutation({
954
+ tripRef,
955
+ enqueue: (engine2, input) => {
956
+ const { formId, ...body } = input;
957
+ return engine2.enqueue({
958
+ tripRef,
959
+ method: SyncOpMethod.Post,
960
+ path: `/api/v1/trips/${tripRef}/forms/${formId}/responses`,
961
+ // Hardcoded literal, not imported: `OperationId` is not exported
962
+ // from `kaafil-js/client`'s public surface (same reasoning
963
+ // `useRooming.ts` documents). Verified against
964
+ // `kaafil-js/src/generated/security.ts` this session:
965
+ // `OperationId.ManagerCreateFormResponse === 'managerCreateFormResponse'`.
966
+ operationId: "managerCreateFormResponse",
967
+ body
968
+ });
969
+ },
970
+ // THE DESK LANE. On-behalf capture is exactly what an office does when
971
+ // a traveller phones in an answer, and a desk console mounts no
972
+ // `OfflineShell` — without this the write has no transport at all.
973
+ // `idempotencyKey` is OPTIONAL on `ManagerCreateFormResponseOptions`,
974
+ // so nothing here mints one (hard rule #4).
975
+ direct: (liveClient, input) => {
976
+ const { formId, ...body } = input;
977
+ return liveClient.forms.trip.responses.create({ tripRef, formId, ...body });
978
+ }
979
+ });
980
+ const submitMutate = submitMutation.mutate;
981
+ const submitResponse = useCallback(
982
+ async (input) => {
983
+ const op = await submitMutate(input);
984
+ void refreshForms();
985
+ refreshFormsAfterDrain();
986
+ return op;
987
+ },
988
+ [submitMutate, refreshForms, refreshFormsAfterDrain]
989
+ );
990
+ const completion = useCallback(async () => {
991
+ const response = await client.forms.trip.completion({ tripRef });
992
+ return response.items;
993
+ }, [client, tripRef]);
994
+ const dispatch = useCallback(
995
+ async (input) => {
996
+ const { formId, ...body } = input;
997
+ await client.forms.trip.dispatch({ tripRef, formId, ...body });
998
+ void refreshForms();
999
+ },
1000
+ [client, tripRef, refreshForms]
1001
+ );
1002
+ const exportResponses = useCallback(
1003
+ (input) => client.forms.responses.export(input),
1004
+ [client]
1005
+ );
1006
+ const consentReceipt = useCallback(
1007
+ (input) => client.forms.responses.consentReceipt(input),
1008
+ [client]
1009
+ );
1010
+ const listAgencyForms = useCallback(async () => {
1011
+ const response = await client.forms.list({});
1012
+ return response.items;
1013
+ }, [client]);
1014
+ const createForm = useCallback(
1015
+ async (input) => client.forms.create(input),
1016
+ [client]
1017
+ );
1018
+ const patchForm = useCallback(
1019
+ async (input) => client.forms.patch(input),
1020
+ [client]
1021
+ );
1022
+ const publishForm = useCallback(
1023
+ async (formId) => client.forms.publish({ formId }),
1024
+ [client]
1025
+ );
1026
+ const closeForm = useCallback(
1027
+ async (formId) => client.forms.close({ formId }),
1028
+ [client]
1029
+ );
1030
+ const reopenForm = useCallback(
1031
+ async (formId) => client.forms.reopen({ formId }),
1032
+ [client]
1033
+ );
1034
+ const archiveForm = useCallback(
1035
+ async (formId) => client.forms.archive({ formId }),
1036
+ [client]
1037
+ );
1038
+ const unarchiveForm = useCallback(
1039
+ async (formId) => client.forms.unarchive({ formId }),
1040
+ [client]
1041
+ );
1042
+ const cloneForm = useCallback(
1043
+ async (input) => client.forms.clone(input),
1044
+ [client]
1045
+ );
1046
+ const reorderForm = useCallback(
1047
+ async (input) => {
1048
+ await client.forms.reorder(input);
1049
+ },
1050
+ [client]
1051
+ );
1052
+ const createSection = useCallback(
1053
+ async (input) => client.forms.sections.create(input),
1054
+ [client]
1055
+ );
1056
+ const patchSection = useCallback(
1057
+ async (input) => client.forms.sections.patch(input),
1058
+ [client]
1059
+ );
1060
+ const deleteSection = useCallback(
1061
+ async (input) => {
1062
+ await client.forms.sections.delete(input);
1063
+ },
1064
+ [client]
1065
+ );
1066
+ const createField = useCallback(
1067
+ async (input) => client.forms.fields.create(input),
1068
+ [client]
1069
+ );
1070
+ const patchField = useCallback(
1071
+ async (input) => client.forms.fields.patch(input),
1072
+ [client]
1073
+ );
1074
+ const deleteField = useCallback(
1075
+ async (input) => {
1076
+ await client.forms.fields.delete(input);
1077
+ },
1078
+ [client]
1079
+ );
1080
+ const listBindings = useCallback(async () => {
1081
+ const response = await client.forms.bindings.list();
1082
+ return response.items;
1083
+ }, [client]);
1084
+ return {
1085
+ forms,
1086
+ syncedAt,
1087
+ getDefinition,
1088
+ getResponse,
1089
+ travellerResponses,
1090
+ formResponses,
1091
+ submitResponse,
1092
+ refresh: refreshForms,
1093
+ completion,
1094
+ dispatch,
1095
+ exportResponses,
1096
+ consentReceipt,
1097
+ listAgencyForms,
1098
+ createForm,
1099
+ patchForm,
1100
+ publishForm,
1101
+ closeForm,
1102
+ reopenForm,
1103
+ archiveForm,
1104
+ unarchiveForm,
1105
+ cloneForm,
1106
+ reorderForm,
1107
+ createSection,
1108
+ patchSection,
1109
+ deleteSection,
1110
+ createField,
1111
+ patchField,
1112
+ deleteField,
1113
+ listBindings
1114
+ };
1115
+ });
1116
+ function useForms(tripRef) {
1117
+ return useFormsInternal(tripRef);
1118
+ }
1119
+ function isLiveItineraryItem(row) {
1120
+ return !("_tombstone" in row);
1121
+ }
1122
+ function useItinerary(tripRef) {
1123
+ const client = useKaafilClient();
1124
+ const [trip, setTrip] = useState(void 0);
1125
+ const [initialDayIso, setInitialDayIso] = useState(void 0);
1126
+ const [canAddItems, setCanAddItems] = useState(false);
1127
+ const [canAddItemsReason, setCanAddItemsReason] = useState(null);
1128
+ const [days, setDays] = useState([]);
1129
+ const [items, setItems] = useState([]);
1130
+ const [loaded, setLoaded] = useState(false);
1131
+ const [lastFetchFailed, setLastFetchFailed] = useState(false);
1132
+ const [syncedAt, setSyncedAt] = useState(void 0);
1133
+ const refresh = useCallback(async () => {
1134
+ try {
1135
+ const response = await client.itinerary.read({ tripRef });
1136
+ setTrip(response.trip);
1137
+ setInitialDayIso(response.initialDayIso);
1138
+ setCanAddItems(response.canAddItems);
1139
+ setCanAddItemsReason(response.canAddItemsReason);
1140
+ setDays(response.days);
1141
+ setItems(response.items.filter(isLiveItineraryItem));
1142
+ setLoaded(true);
1143
+ setLastFetchFailed(false);
1144
+ setSyncedAt(response.meta.serverTime);
1145
+ } catch {
1146
+ setLastFetchFailed(true);
1147
+ }
1148
+ }, [client, tripRef]);
1149
+ useEffect(() => {
1150
+ void refresh();
1151
+ }, [refresh]);
1152
+ const [changeLog, setChangeLog] = useState([]);
1153
+ const [changeLogLoaded, setChangeLogLoaded] = useState(false);
1154
+ const [changeLogFetchFailed, setChangeLogFetchFailed] = useState(false);
1155
+ const loadChangeLog = useCallback(
1156
+ async (input) => {
1157
+ try {
1158
+ const response = await client.itinerary.changeLog.list({ tripRef, ...input });
1159
+ setChangeLog(response);
1160
+ setChangeLogLoaded(true);
1161
+ setChangeLogFetchFailed(false);
1162
+ } catch {
1163
+ setChangeLogFetchFailed(true);
1164
+ }
1165
+ },
1166
+ [client, tripRef]
1167
+ );
1168
+ const patchDayMutation = useOfflineMutation({
1169
+ tripRef,
1170
+ enqueue: (engine, input) => {
1171
+ const { dayIndex, version, ...body } = input;
1172
+ return engine.enqueue({
1173
+ tripRef,
1174
+ method: SyncOpMethod.Patch,
1175
+ path: `/api/v1/trips/${tripRef}/itinerary/days/${dayIndex}`,
1176
+ operationId: "patchItineraryDay",
1177
+ body,
1178
+ ifMatch: version
1179
+ });
1180
+ },
1181
+ direct: (client2, input) => client2.itinerary.days.patch({ tripRef, ...input })
1182
+ });
1183
+ const patchDayMutate = patchDayMutation.mutate;
1184
+ const patchDay = useCallback(
1185
+ async (input) => {
1186
+ const op = await patchDayMutate(input);
1187
+ void refresh();
1188
+ return op;
1189
+ },
1190
+ [patchDayMutate, refresh]
1191
+ );
1192
+ const addItemMutation = useOfflineMutation({
1193
+ tripRef,
1194
+ enqueue: (engine, input) => engine.enqueue({
1195
+ tripRef,
1196
+ method: SyncOpMethod.Post,
1197
+ path: `/api/v1/trips/${tripRef}/itinerary/items`,
1198
+ operationId: "addItineraryItem",
1199
+ body: input
1200
+ }),
1201
+ direct: (client2, input) => client2.itinerary.items.add({ tripRef, ...input })
1202
+ });
1203
+ const addItemMutate = addItemMutation.mutate;
1204
+ const addItem = useCallback(
1205
+ async (input) => {
1206
+ const op = await addItemMutate(input);
1207
+ void refresh();
1208
+ return op;
1209
+ },
1210
+ [addItemMutate, refresh]
1211
+ );
1212
+ const patchItemMutation = useOfflineMutation({
1213
+ tripRef,
1214
+ enqueue: (engine, input) => {
1215
+ const { itemId, version, ...body } = input;
1216
+ return engine.enqueue({
1217
+ tripRef,
1218
+ method: SyncOpMethod.Patch,
1219
+ path: `/api/v1/trips/${tripRef}/itinerary/items/${itemId}`,
1220
+ operationId: "patchItineraryItem",
1221
+ body,
1222
+ ifMatch: version
1223
+ });
1224
+ },
1225
+ direct: (client2, input) => client2.itinerary.items.patch({ tripRef, ...input })
1226
+ });
1227
+ const patchItemMutate = patchItemMutation.mutate;
1228
+ const patchItem = useCallback(
1229
+ async (input) => {
1230
+ const op = await patchItemMutate(input);
1231
+ void refresh();
1232
+ return op;
1233
+ },
1234
+ [patchItemMutate, refresh]
1235
+ );
1236
+ const removeItemMutation = useOfflineMutation({
1237
+ tripRef,
1238
+ enqueue: (engine, input) => {
1239
+ const { itemId, version } = input;
1240
+ return engine.enqueue({
1241
+ tripRef,
1242
+ method: SyncOpMethod.Delete,
1243
+ path: `/api/v1/trips/${tripRef}/itinerary/items/${itemId}`,
1244
+ operationId: "deleteItineraryItem",
1245
+ ifMatch: version
1246
+ });
1247
+ },
1248
+ direct: (client2, input) => client2.itinerary.items.remove({ tripRef, ...input })
1249
+ });
1250
+ const removeItemMutate = removeItemMutation.mutate;
1251
+ const removeItem = useCallback(
1252
+ async (input) => {
1253
+ const op = await removeItemMutate(input);
1254
+ void refresh();
1255
+ return op;
1256
+ },
1257
+ [removeItemMutate, refresh]
1258
+ );
1259
+ const reorderItemMutation = useOfflineMutation({
1260
+ tripRef,
1261
+ enqueue: (engine, input) => {
1262
+ const { itemId, ...body } = input;
1263
+ return engine.enqueue({
1264
+ tripRef,
1265
+ method: SyncOpMethod.Post,
1266
+ path: `/api/v1/trips/${tripRef}/itinerary/items/${itemId}/reorder`,
1267
+ operationId: "reorderItineraryItem",
1268
+ body
1269
+ });
1270
+ },
1271
+ direct: (client2, input) => client2.itinerary.items.reorder({ tripRef, ...input })
1272
+ });
1273
+ const reorderItemMutate = reorderItemMutation.mutate;
1274
+ const reorderItem = useCallback(
1275
+ async (input) => {
1276
+ const op = await reorderItemMutate(input);
1277
+ void refresh();
1278
+ return op;
1279
+ },
1280
+ [reorderItemMutate, refresh]
1281
+ );
1282
+ if (!loaded && !lastFetchFailed) {
1283
+ return { status: "loading" };
1284
+ }
1285
+ if (!loaded && lastFetchFailed) {
1286
+ return { status: "error" };
1287
+ }
1288
+ return {
1289
+ status: "ready",
1290
+ trip,
1291
+ initialDayIso,
1292
+ canAddItems,
1293
+ canAddItemsReason,
1294
+ days,
1295
+ items,
1296
+ loaded,
1297
+ lastFetchFailed,
1298
+ syncedAt,
1299
+ refresh,
1300
+ changeLog,
1301
+ changeLogLoaded,
1302
+ changeLogFetchFailed,
1303
+ loadChangeLog,
1304
+ patchDay,
1305
+ addItem,
1306
+ patchItem,
1307
+ removeItem,
1308
+ reorderItem
1309
+ };
1310
+ }
1311
+ function useManagers(tripRef) {
1312
+ const client = useKaafilClient();
1313
+ const persona = usePersona();
1314
+ const [result, setResult] = useState({ status: "loading" });
1315
+ useEffect(() => {
1316
+ if (persona === "share") {
1317
+ if (isDevelopment()) {
1318
+ throw new Error(
1319
+ "useManagers() was called from a share session \u2014 it is manager/agencyAdmin only."
1320
+ );
1321
+ }
1322
+ return;
1323
+ }
1324
+ let cancelled = false;
1325
+ setResult({ status: "loading" });
1326
+ void Promise.resolve().then(() => client.trips.managers.list({ tripRef })).then((managers) => {
1327
+ if (!cancelled) {
1328
+ setResult({ status: "ready", managers });
1329
+ }
1330
+ }).catch((error) => {
1331
+ if (!cancelled) {
1332
+ setResult({ status: "error", error: classifyError(error) });
1333
+ }
1334
+ });
1335
+ return () => {
1336
+ cancelled = true;
1337
+ };
1338
+ }, [client, persona, tripRef]);
1339
+ return result;
1340
+ }
1341
+ function useManagerToday(tripRef) {
1342
+ const client = useKaafilClient();
1343
+ const [result, setResult] = useState({ status: "loading" });
1344
+ useEffect(() => {
1345
+ let cancelled = false;
1346
+ setResult({ status: "loading" });
1347
+ client.managerToday.read({ tripRef }).then((response) => {
1348
+ if (cancelled) {
1349
+ return;
1350
+ }
1351
+ const { meta, ...data } = response;
1352
+ setResult({ status: "ready", ...data, syncedAt: meta.serverTime });
1353
+ }).catch(() => {
1354
+ if (!cancelled) {
1355
+ setResult({ status: "error" });
1356
+ }
1357
+ });
1358
+ return () => {
1359
+ cancelled = true;
1360
+ };
1361
+ }, [client, tripRef]);
1362
+ return result;
1363
+ }
1364
+ function isLiveParty(row) {
1365
+ return !("_tombstone" in row);
1366
+ }
1367
+ function useParties(tripRef) {
1368
+ const client = useKaafilClient();
1369
+ const [rows, setRows] = useState([]);
1370
+ const [listLoaded, setListLoaded] = useState(false);
1371
+ const [listFetchFailed, setListFetchFailed] = useState(false);
1372
+ const [listFetchedAt, setListFetchedAt] = useState(void 0);
1373
+ const [firstLoadError, setFirstLoadError] = useState(void 0);
1374
+ const refreshList = useCallback(async () => {
1375
+ try {
1376
+ const response = await client.trips.parties.list({ tripRef });
1377
+ setRows(response.filter(isLiveParty));
1378
+ setListLoaded(true);
1379
+ setListFetchFailed(false);
1380
+ setListFetchedAt(response.meta.serverTime);
1381
+ setFirstLoadError(void 0);
1382
+ } catch (error) {
1383
+ setListFetchFailed(true);
1384
+ setFirstLoadError((previous) => previous ?? classifyError(error));
1385
+ }
1386
+ }, [client, tripRef]);
1387
+ useEffect(() => {
1388
+ void refreshList();
1389
+ }, [refreshList]);
1390
+ const [rosterRows, setRosterRows] = useState([]);
1391
+ const [rosterLoaded, setRosterLoaded] = useState(false);
1392
+ const [rosterFetchFailed, setRosterFetchFailed] = useState(false);
1393
+ const [rosterFetchedAt, setRosterFetchedAt] = useState(void 0);
1394
+ const refreshRoster = useCallback(async () => {
1395
+ try {
1396
+ const response = await client.travellers.listForTrip({ tripRef });
1397
+ setRosterRows(response);
1398
+ setRosterLoaded(true);
1399
+ setRosterFetchFailed(false);
1400
+ setRosterFetchedAt(response.meta.serverTime);
1401
+ } catch {
1402
+ setRosterFetchFailed(true);
1403
+ }
1404
+ }, [client, tripRef]);
1405
+ useEffect(() => {
1406
+ void refreshRoster();
1407
+ }, [refreshRoster]);
1408
+ const addMutation = useOfflineMutation({
1409
+ tripRef,
1410
+ enqueue: (engine, input) => engine.enqueue({
1411
+ tripRef,
1412
+ method: SyncOpMethod.Post,
1413
+ path: `/api/v1/trips/${tripRef}/parties`,
1414
+ operationId: "createParty",
1415
+ body: input
1416
+ })
1417
+ });
1418
+ const patchMutation = useOfflineMutation({
1419
+ tripRef,
1420
+ enqueue: (engine, input) => {
1421
+ const { partyId, version, ...body } = input;
1422
+ return engine.enqueue({
1423
+ tripRef,
1424
+ method: SyncOpMethod.Patch,
1425
+ path: `/api/v1/trips/${tripRef}/parties/${partyId}`,
1426
+ operationId: "patchParty",
1427
+ body,
1428
+ ifMatch: version
1429
+ });
1430
+ }
1431
+ });
1432
+ const removeMutation = useOfflineMutation({
1433
+ tripRef,
1434
+ enqueue: (engine, input) => {
1435
+ const { partyId } = input;
1436
+ return engine.enqueue({
1437
+ tripRef,
1438
+ method: SyncOpMethod.Delete,
1439
+ path: `/api/v1/trips/${tripRef}/parties/${partyId}`,
1440
+ operationId: "deleteParty"
1441
+ });
1442
+ }
1443
+ });
1444
+ const addMutate = addMutation.mutate;
1445
+ const add = useCallback(
1446
+ async (input) => {
1447
+ const op = await addMutate(input);
1448
+ void refreshList();
1449
+ return op;
1450
+ },
1451
+ [addMutate, refreshList]
1452
+ );
1453
+ const patchMutate = patchMutation.mutate;
1454
+ const patch = useCallback(
1455
+ async (input) => {
1456
+ const op = await patchMutate(input);
1457
+ void refreshList();
1458
+ return op;
1459
+ },
1460
+ [patchMutate, refreshList]
1461
+ );
1462
+ const removeMutate = removeMutation.mutate;
1463
+ const remove = useCallback(
1464
+ async (input) => {
1465
+ const op = await removeMutate(input);
1466
+ void refreshList();
1467
+ return op;
1468
+ },
1469
+ [removeMutate, refreshList]
1470
+ );
1471
+ if (!listLoaded && !listFetchFailed) {
1472
+ return { status: "loading" };
1473
+ }
1474
+ if (!listLoaded && listFetchFailed) {
1475
+ return {
1476
+ status: "error",
1477
+ error: firstLoadError ?? {
1478
+ path: "surface-to-user",
1479
+ code: void 0,
1480
+ status: void 0,
1481
+ unsatisfiableScheme: false,
1482
+ cause: void 0
1483
+ },
1484
+ retry: refreshList
1485
+ };
1486
+ }
1487
+ return {
1488
+ status: "ready",
1489
+ list: {
1490
+ rows,
1491
+ loaded: listLoaded,
1492
+ lastFetchFailed: listFetchFailed,
1493
+ fetchedAt: listFetchedAt,
1494
+ refresh: refreshList
1495
+ },
1496
+ tripRoster: {
1497
+ rows: rosterRows,
1498
+ loaded: rosterLoaded,
1499
+ lastFetchFailed: rosterFetchFailed,
1500
+ fetchedAt: rosterFetchedAt,
1501
+ refresh: refreshRoster
1502
+ },
1503
+ add,
1504
+ patch,
1505
+ remove
1506
+ };
1507
+ }
1508
+ var usePickupsInternal = createDomainHook("pickup-points", (tripRef) => {
1509
+ const client = useKaafilClient();
1510
+ const {
1511
+ rows: stops,
1512
+ syncedAt,
1513
+ refreshLive
1514
+ } = useSnapshotList(tripRef, "pickups", {
1515
+ live: async (liveClient, ref) => {
1516
+ const response = await liveClient.pickups.list({ tripRef: ref });
1517
+ return { rows: response, syncedAt: response.meta.serverTime };
1518
+ }
1519
+ });
1520
+ const createMutation = useOfflineMutation({
1521
+ tripRef,
1522
+ enqueue: (engine, input) => engine.enqueue({
1523
+ tripRef,
1524
+ method: SyncOpMethod.Post,
1525
+ path: `/api/v1/trips/${tripRef}/pickups`,
1526
+ // `OperationId` is not exported from `kaafil-js/client`'s public
1527
+ // surface (verified, same gap `useRooming.ts` documents) — every
1528
+ // literal below is checked against `kaafil-js/src/generated/
1529
+ // security.ts`'s own operation-id table.
1530
+ operationId: "createPickupStop",
1531
+ body: input
1532
+ }),
1533
+ // The desk lane. `board`, `close` and `reopen` below deliberately get
1534
+ // NONE — they are `managerAuth`-only on the wire, so a direct call from
1535
+ // an agency-admin session would be refused by `CredentialResolver`
1536
+ // before it left the process. See this file's own persona note.
1537
+ direct: (liveClient, input) => liveClient.pickups.create({ tripRef, ...input })
1538
+ });
1539
+ const patchMutation = useOfflineMutation({
1540
+ tripRef,
1541
+ enqueue: (engine, input) => {
1542
+ const { pointId, version, ...body } = input;
1543
+ return engine.enqueue({
1544
+ tripRef,
1545
+ method: SyncOpMethod.Patch,
1546
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}`,
1547
+ operationId: "patchPickupStop",
1548
+ body,
1549
+ ifMatch: version
1550
+ });
1551
+ },
1552
+ direct: (liveClient, input) => liveClient.pickups.patch({ tripRef, ...input })
1553
+ });
1554
+ const removeMutation = useOfflineMutation({
1555
+ tripRef,
1556
+ enqueue: (engine, input) => {
1557
+ const { pointId, version, force } = input;
1558
+ const query = force === true ? "?force=true" : "";
1559
+ return engine.enqueue({
1560
+ tripRef,
1561
+ method: SyncOpMethod.Delete,
1562
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}${query}`,
1563
+ operationId: "deletePickupStop",
1564
+ ifMatch: version
1565
+ });
1566
+ },
1567
+ direct: (liveClient, input) => liveClient.pickups.remove({ tripRef, ...input })
1568
+ });
1569
+ const reorderMutation = useOfflineMutation({
1570
+ tripRef,
1571
+ enqueue: (engine, input) => engine.enqueue({
1572
+ tripRef,
1573
+ method: SyncOpMethod.Post,
1574
+ path: `/api/v1/trips/${tripRef}/pickups/reorder`,
1575
+ operationId: "reorderPickupStops",
1576
+ body: input
1577
+ }),
1578
+ direct: (liveClient, input) => liveClient.pickups.reorder({ tripRef, ...input })
1579
+ });
1580
+ const assignMutation = useOfflineMutation({
1581
+ tripRef,
1582
+ enqueue: (engine, input) => {
1583
+ const { pointId, ...body } = input;
1584
+ return engine.enqueue({
1585
+ tripRef,
1586
+ method: SyncOpMethod.Post,
1587
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}/assign`,
1588
+ operationId: "assignPickupTraveller",
1589
+ body
1590
+ });
1591
+ },
1592
+ direct: (liveClient, input) => liveClient.pickups.assign({ tripRef, ...input })
1593
+ });
1594
+ const boardMutation = useOfflineMutation({
1595
+ tripRef,
1596
+ enqueue: (engine, input) => {
1597
+ const { pointId, ...body } = input;
1598
+ return engine.enqueue({
1599
+ tripRef,
1600
+ method: SyncOpMethod.Post,
1601
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}/board`,
1602
+ operationId: "boardPickupTraveller",
1603
+ body
1604
+ });
1605
+ }
1606
+ });
1607
+ const correctBoardStatusMutation = useOfflineMutation({
1608
+ tripRef,
1609
+ enqueue: (engine, input) => {
1610
+ const { pointId, travellerId, ...body } = input;
1611
+ return engine.enqueue({
1612
+ tripRef,
1613
+ method: SyncOpMethod.Patch,
1614
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}/board/${travellerId}`,
1615
+ operationId: "correctPickupBoardStatus",
1616
+ body
1617
+ });
1618
+ },
1619
+ // The office's ONE door onto the board table, so it needs the desk lane
1620
+ // as much as the five stop writes do — `board`/`close`/`reopen` below
1621
+ // still get none, because they are `managerAuth`-only.
1622
+ direct: (liveClient, input) => liveClient.pickups.correctBoardStatus({ tripRef, ...input })
1623
+ });
1624
+ const closeMutation = useOfflineMutation({
1625
+ tripRef,
1626
+ enqueue: (engine, input) => {
1627
+ const { pointId, ...body } = input;
1628
+ return engine.enqueue({
1629
+ tripRef,
1630
+ method: SyncOpMethod.Post,
1631
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}/close`,
1632
+ operationId: "closePickupStop",
1633
+ body
1634
+ });
1635
+ }
1636
+ });
1637
+ const reopenMutation = useOfflineMutation({
1638
+ tripRef,
1639
+ enqueue: (engine, input) => {
1640
+ const { pointId } = input;
1641
+ return engine.enqueue({
1642
+ tripRef,
1643
+ method: SyncOpMethod.Post,
1644
+ path: `/api/v1/trips/${tripRef}/pickups/${pointId}/reopen`,
1645
+ operationId: "reopenPickupStop"
1646
+ });
1647
+ }
1648
+ });
1649
+ const reassign = useCallback(
1650
+ (input) => client.pickups.reassign({ tripRef, ...input }),
1651
+ [client, tripRef]
1652
+ );
1653
+ const manifestByPickup = useCallback(
1654
+ (input) => client.pickups.manifestByPickup({ tripRef, ...input }),
1655
+ [client, tripRef]
1656
+ );
1657
+ return {
1658
+ stops,
1659
+ syncedAt,
1660
+ refreshLive,
1661
+ create: createMutation.mutate,
1662
+ patch: patchMutation.mutate,
1663
+ remove: removeMutation.mutate,
1664
+ reorder: reorderMutation.mutate,
1665
+ assign: assignMutation.mutate,
1666
+ reassign,
1667
+ board: boardMutation.mutate,
1668
+ correctBoardStatus: correctBoardStatusMutation.mutate,
1669
+ close: closeMutation.mutate,
1670
+ reopen: reopenMutation.mutate,
1671
+ manifestByPickup
1672
+ };
1673
+ });
1674
+ function usePickups(tripRef) {
1675
+ return usePickupsInternal(tripRef);
1676
+ }
1677
+ var useRoomingInternal = createDomainHook("rooming", (tripRef) => {
1678
+ const client = useKaafilClient();
1679
+ const {
1680
+ rows: rooms,
1681
+ syncedAt: roomsSyncedAt,
1682
+ refreshLive: refreshRoomsLive
1683
+ } = useSnapshotList(tripRef, "rooming", {
1684
+ live: async (liveClient, ref) => {
1685
+ const response = await liveClient.rooming.read({ tripRef: ref });
1686
+ return { rows: response.rooms, syncedAt: response.meta.serverTime };
1687
+ }
1688
+ });
1689
+ const {
1690
+ rows: stayWindows,
1691
+ syncedAt: windowsSyncedAt,
1692
+ refreshLive: refreshWindowsLive
1693
+ } = useSnapshotList(tripRef, "stayWindows", {
1694
+ live: async (liveClient, ref) => {
1695
+ const response = await liveClient.rooming.stayWindows.list({ tripRef: ref });
1696
+ return { rows: response, syncedAt: response.meta.serverTime };
1697
+ }
1698
+ });
1699
+ const refreshLive = useCallback(async () => {
1700
+ await Promise.all([refreshRoomsLive(), refreshWindowsLive()]);
1701
+ }, [refreshRoomsLive, refreshWindowsLive]);
1702
+ const assignMutation = useOfflineMutation({
1703
+ tripRef,
1704
+ enqueue: (engine, input) => engine.enqueue({
1705
+ tripRef,
1706
+ method: SyncOpMethod.Post,
1707
+ path: `/api/v1/trips/${tripRef}/rooming/assign`,
1708
+ // Hardcoded literal, not imported: `OperationId` is not exported from
1709
+ // `kaafil-js/client`'s public surface (verified — see this worker's
1710
+ // final report). Value verified against
1711
+ // `kaafil-js/src/generated/security.ts` this session.
1712
+ operationId: "assignRoomingBed",
1713
+ body: input,
1714
+ // `RoomingAssignDto.rooms` is every room this write touched, whole —
1715
+ // up to two, on a swap. `arrayKey` is this hook's multi-row rebase
1716
+ // (`OutboxEntityRef.arrayKey`), the reason the room grid now
1717
+ // repaints the instant an assign lands rather than waiting for the
1718
+ // next full pull.
1719
+ entity: { listName: "rooming", arrayKey: "rooms" }
1720
+ }),
1721
+ // The desk lane. Every rooming write takes the admin triple, so unlike
1722
+ // `usePickups` there is no member here that deliberately lacks one.
1723
+ direct: (liveClient, input) => liveClient.rooming.assign({ tripRef, ...input })
1724
+ });
1725
+ const [poolTravellers, setPoolTravellers] = useState([]);
1726
+ const [poolLoaded, setPoolLoaded] = useState(false);
1727
+ const [poolFetchFailed, setPoolFetchFailed] = useState(false);
1728
+ const [poolFetchedAt, setPoolFetchedAt] = useState(void 0);
1729
+ const lastRequestedStayWindowIdRef = useRef(void 0);
1730
+ const refreshUnassignedPool = useCallback(
1731
+ async (stayWindowId) => {
1732
+ lastRequestedStayWindowIdRef.current = stayWindowId;
1733
+ try {
1734
+ const response = await client.rooming.read({ tripRef, stayWindowId });
1735
+ setPoolTravellers(response.unassigned);
1736
+ setPoolLoaded(true);
1737
+ setPoolFetchFailed(false);
1738
+ setPoolFetchedAt(response.meta.serverTime);
1739
+ } catch {
1740
+ setPoolFetchFailed(true);
1741
+ }
1742
+ },
1743
+ [client, tripRef]
1744
+ );
1745
+ useEffect(() => {
1746
+ void refreshUnassignedPool();
1747
+ }, [refreshUnassignedPool]);
1748
+ const assignMutate = assignMutation.mutate;
1749
+ const assign = useCallback(
1750
+ async (input) => {
1751
+ const op = await assignMutate(input);
1752
+ void refreshUnassignedPool(lastRequestedStayWindowIdRef.current);
1753
+ return op;
1754
+ },
1755
+ [assignMutate, refreshUnassignedPool]
1756
+ );
1757
+ const createWindowMutation = useOfflineMutation({
1758
+ tripRef,
1759
+ enqueue: (engine, input) => engine.enqueue({
1760
+ tripRef,
1761
+ method: SyncOpMethod.Post,
1762
+ path: `/api/v1/trips/${tripRef}/rooming/stay-windows`,
1763
+ operationId: "createRoomingStayWindow",
1764
+ body: input
1765
+ }),
1766
+ direct: (liveClient, input) => liveClient.rooming.stayWindows.create({ tripRef, ...input })
1767
+ });
1768
+ const patchWindowMutation = useOfflineMutation({
1769
+ tripRef,
1770
+ enqueue: (engine, input) => {
1771
+ const { windowId, version, ...body } = input;
1772
+ return engine.enqueue({
1773
+ tripRef,
1774
+ method: SyncOpMethod.Patch,
1775
+ path: `/api/v1/trips/${tripRef}/rooming/stay-windows/${windowId}`,
1776
+ operationId: "patchRoomingStayWindow",
1777
+ body,
1778
+ ifMatch: version
1779
+ });
1780
+ },
1781
+ direct: (liveClient, input) => liveClient.rooming.stayWindows.patch({ tripRef, ...input })
1782
+ });
1783
+ const removeWindowMutation = useOfflineMutation({
1784
+ tripRef,
1785
+ enqueue: (engine, input) => {
1786
+ const { windowId, version, force } = input;
1787
+ const query = force === true ? "?force=true" : "";
1788
+ return engine.enqueue({
1789
+ tripRef,
1790
+ method: SyncOpMethod.Delete,
1791
+ path: `/api/v1/trips/${tripRef}/rooming/stay-windows/${windowId}${query}`,
1792
+ operationId: "deleteRoomingStayWindow",
1793
+ ifMatch: version
1794
+ });
1795
+ },
1796
+ direct: (liveClient, input) => liveClient.rooming.stayWindows.remove({ tripRef, ...input })
1797
+ });
1798
+ const createRoomMutation = useOfflineMutation({
1799
+ tripRef,
1800
+ enqueue: (engine, input) => engine.enqueue({
1801
+ tripRef,
1802
+ method: SyncOpMethod.Post,
1803
+ path: `/api/v1/trips/${tripRef}/rooming/rooms`,
1804
+ operationId: "createRoomingRoom",
1805
+ body: input
1806
+ }),
1807
+ direct: (liveClient, input) => liveClient.rooming.rooms.create({ tripRef, ...input })
1808
+ });
1809
+ const patchRoomMutation = useOfflineMutation({
1810
+ tripRef,
1811
+ enqueue: (engine, input) => {
1812
+ const { roomId, version, ...body } = input;
1813
+ return engine.enqueue({
1814
+ tripRef,
1815
+ method: SyncOpMethod.Patch,
1816
+ path: `/api/v1/trips/${tripRef}/rooming/rooms/${roomId}`,
1817
+ operationId: "patchRoomingRoom",
1818
+ body,
1819
+ ifMatch: version
1820
+ });
1821
+ },
1822
+ direct: (liveClient, input) => liveClient.rooming.rooms.patch({ tripRef, ...input })
1823
+ });
1824
+ const removeRoomMutation = useOfflineMutation({
1825
+ tripRef,
1826
+ enqueue: (engine, input) => {
1827
+ const { roomId, version, force } = input;
1828
+ const query = force === true ? "?force=true" : "";
1829
+ return engine.enqueue({
1830
+ tripRef,
1831
+ method: SyncOpMethod.Delete,
1832
+ path: `/api/v1/trips/${tripRef}/rooming/rooms/${roomId}${query}`,
1833
+ operationId: "deleteRoomingRoom",
1834
+ ifMatch: version
1835
+ });
1836
+ },
1837
+ direct: (liveClient, input) => liveClient.rooming.rooms.remove({ tripRef, ...input })
1838
+ });
1839
+ const syncedAt = roomsSyncedAt === void 0 ? windowsSyncedAt : windowsSyncedAt === void 0 ? roomsSyncedAt : roomsSyncedAt > windowsSyncedAt ? roomsSyncedAt : windowsSyncedAt;
1840
+ const autoAssign = useCallback(
1841
+ (input) => client.rooming.autoAssign({ tripRef, ...input }),
1842
+ [client, tripRef]
1843
+ );
1844
+ return {
1845
+ rooms,
1846
+ stayWindows,
1847
+ syncedAt,
1848
+ refreshLive,
1849
+ unassignedPool: {
1850
+ travellers: poolTravellers,
1851
+ loaded: poolLoaded,
1852
+ lastFetchFailed: poolFetchFailed,
1853
+ fetchedAt: poolFetchedAt,
1854
+ refresh: refreshUnassignedPool
1855
+ },
1856
+ assign,
1857
+ autoAssign,
1858
+ createStayWindow: createWindowMutation.mutate,
1859
+ patchStayWindow: patchWindowMutation.mutate,
1860
+ removeStayWindow: removeWindowMutation.mutate,
1861
+ createRoom: createRoomMutation.mutate,
1862
+ patchRoom: patchRoomMutation.mutate,
1863
+ removeRoom: removeRoomMutation.mutate
1864
+ };
1865
+ });
1866
+ function useRooming(tripRef) {
1867
+ return useRoomingInternal(tripRef);
1868
+ }
1869
+ var useSeatingInternal = createDomainHook("transport-seating", (tripRef) => {
1870
+ const client = useKaafilClient();
1871
+ const {
1872
+ rows: vehicles,
1873
+ syncedAt,
1874
+ refreshLive
1875
+ } = useSnapshotList(tripRef, "seating", {
1876
+ live: async (liveClient, ref) => {
1877
+ const response = await liveClient.seating.read({ tripRef: ref });
1878
+ return { rows: response.vehicles, syncedAt: response.meta.serverTime };
1879
+ }
1880
+ });
1881
+ const assignMutation = useOfflineMutation({
1882
+ tripRef,
1883
+ enqueue: (engine, input) => engine.enqueue({
1884
+ tripRef,
1885
+ method: SyncOpMethod.Post,
1886
+ path: `/api/v1/trips/${tripRef}/seating/assign`,
1887
+ operationId: "assignSeating",
1888
+ body: input,
1889
+ // `SeatingAssignDto.vehicles` is "every vehicle touched by this
1890
+ // write, whole — a client rebases without a follow-up read" (the
1891
+ // engine DTO's own words) — up to two, on a displacement. `arrayKey`
1892
+ // is this hook's one multi-row rebase (`OutboxEntityRef.arrayKey`),
1893
+ // the reason the vehicle grid now repaints the instant an assign
1894
+ // lands rather than waiting for the next full pull.
1895
+ entity: { listName: "seating", arrayKey: "vehicles" }
1896
+ }),
1897
+ // The desk lane. Every `transport-seating` write takes the admin triple,
1898
+ // so unlike `usePickups` there is no member here that deliberately
1899
+ // lacks one.
1900
+ direct: (liveClient, input) => liveClient.seating.assign({ tripRef, ...input })
1901
+ });
1902
+ const [poolTravellers, setPoolTravellers] = useState([]);
1903
+ const [poolLoaded, setPoolLoaded] = useState(false);
1904
+ const [poolFetchFailed, setPoolFetchFailed] = useState(false);
1905
+ const [poolFetchedAt, setPoolFetchedAt] = useState(void 0);
1906
+ const refreshUnassignedPool = useCallback(async () => {
1907
+ try {
1908
+ const response = await client.seating.read({ tripRef });
1909
+ setPoolTravellers(response.unassignedPool);
1910
+ setPoolLoaded(true);
1911
+ setPoolFetchFailed(false);
1912
+ setPoolFetchedAt(response.meta.serverTime);
1913
+ } catch {
1914
+ setPoolFetchFailed(true);
1915
+ }
1916
+ }, [client, tripRef]);
1917
+ useEffect(() => {
1918
+ void refreshUnassignedPool();
1919
+ }, [refreshUnassignedPool]);
1920
+ const assignMutate = assignMutation.mutate;
1921
+ const assign = useCallback(
1922
+ async (input) => {
1923
+ const op = await assignMutate(input);
1924
+ void refreshUnassignedPool();
1925
+ return op;
1926
+ },
1927
+ [assignMutate, refreshUnassignedPool]
1928
+ );
1929
+ const createVehicleMutation = useOfflineMutation({
1930
+ tripRef,
1931
+ enqueue: (engine, input) => engine.enqueue({
1932
+ tripRef,
1933
+ method: SyncOpMethod.Post,
1934
+ path: `/api/v1/trips/${tripRef}/seating/vehicles`,
1935
+ operationId: "createSeatingVehicle",
1936
+ body: input
1937
+ }),
1938
+ direct: (liveClient, input) => liveClient.seating.vehicles.create({ tripRef, ...input })
1939
+ });
1940
+ const patchVehicleMutation = useOfflineMutation({
1941
+ tripRef,
1942
+ enqueue: (engine, input) => {
1943
+ const { vehicleId, version, ...body } = input;
1944
+ return engine.enqueue({
1945
+ tripRef,
1946
+ method: SyncOpMethod.Patch,
1947
+ path: `/api/v1/trips/${tripRef}/seating/vehicles/${vehicleId}`,
1948
+ operationId: "patchSeatingVehicle",
1949
+ body,
1950
+ ifMatch: version
1951
+ });
1952
+ },
1953
+ direct: (liveClient, input) => liveClient.seating.vehicles.patch({ tripRef, ...input })
1954
+ });
1955
+ const removeVehicleMutation = useOfflineMutation({
1956
+ tripRef,
1957
+ enqueue: (engine, input) => {
1958
+ const { vehicleId, version } = input;
1959
+ return engine.enqueue({
1960
+ tripRef,
1961
+ method: SyncOpMethod.Delete,
1962
+ path: `/api/v1/trips/${tripRef}/seating/vehicles/${vehicleId}`,
1963
+ operationId: "deleteSeatingVehicle",
1964
+ ifMatch: version
1965
+ });
1966
+ },
1967
+ direct: (liveClient, input) => liveClient.seating.vehicles.remove({ tripRef, ...input })
1968
+ });
1969
+ const removeVehicleMutate = removeVehicleMutation.mutate;
1970
+ const removeVehicle = useCallback(
1971
+ async (input) => {
1972
+ const op = await removeVehicleMutate(input);
1973
+ void refreshUnassignedPool();
1974
+ return op;
1975
+ },
1976
+ [removeVehicleMutate, refreshUnassignedPool]
1977
+ );
1978
+ const patchVehicleMutate = patchVehicleMutation.mutate;
1979
+ const patchVehicleDays = useCallback(
1980
+ async (vehicleRef, days) => {
1981
+ const current = vehicles.find(
1982
+ (row) => row.id === vehicleRef && !("_tombstone" in row)
1983
+ );
1984
+ if (current === void 0) {
1985
+ throw new Error(
1986
+ `patchVehicleDays: vehicle "${vehicleRef}" is not in the current seating cache \u2014 read the board before patching its day coverage.`
1987
+ );
1988
+ }
1989
+ return patchVehicleMutate({
1990
+ vehicleId: vehicleRef,
1991
+ version: current.version,
1992
+ days: [...days]
1993
+ });
1994
+ },
1995
+ [patchVehicleMutate, vehicles]
1996
+ );
1997
+ const linkManagerMutation = useOfflineMutation({
1998
+ tripRef,
1999
+ enqueue: (engine, input) => {
2000
+ const { vehicleId, ...body } = input;
2001
+ return engine.enqueue({
2002
+ tripRef,
2003
+ method: SyncOpMethod.Post,
2004
+ path: `/api/v1/trips/${tripRef}/seating/vehicles/${vehicleId}/manager`,
2005
+ operationId: "linkSeatingManager",
2006
+ body
2007
+ });
2008
+ },
2009
+ direct: (liveClient, input) => liveClient.seating.vehicles.manager.link({ tripRef, ...input })
2010
+ });
2011
+ const unlinkManagerMutation = useOfflineMutation({
2012
+ tripRef,
2013
+ enqueue: (engine, input) => {
2014
+ const { vehicleId, version } = input;
2015
+ return engine.enqueue({
2016
+ tripRef,
2017
+ method: SyncOpMethod.Delete,
2018
+ path: `/api/v1/trips/${tripRef}/seating/vehicles/${vehicleId}/manager`,
2019
+ operationId: "unlinkSeatingManager",
2020
+ ifMatch: version
2021
+ });
2022
+ },
2023
+ direct: (liveClient, input) => liveClient.seating.vehicles.manager.unlink({ tripRef, ...input })
2024
+ });
2025
+ const autoAssign = useCallback(
2026
+ (input) => client.seating.autoAssign({ tripRef, ...input }),
2027
+ [client, tripRef]
2028
+ );
2029
+ return {
2030
+ vehicles,
2031
+ syncedAt,
2032
+ refreshLive,
2033
+ unassignedPool: {
2034
+ travellers: poolTravellers,
2035
+ loaded: poolLoaded,
2036
+ lastFetchFailed: poolFetchFailed,
2037
+ fetchedAt: poolFetchedAt,
2038
+ refresh: refreshUnassignedPool
2039
+ },
2040
+ assign,
2041
+ autoAssign,
2042
+ createVehicle: createVehicleMutation.mutate,
2043
+ patchVehicle: patchVehicleMutation.mutate,
2044
+ removeVehicle,
2045
+ patchVehicleDays,
2046
+ linkManager: linkManagerMutation.mutate,
2047
+ unlinkManager: unlinkManagerMutation.mutate
2048
+ };
2049
+ });
2050
+ function useSeating(tripRef) {
2051
+ return useSeatingInternal(tripRef);
2052
+ }
2053
+ function useTravellerProfile(tripRef, travellerRef) {
2054
+ const client = useKaafilClient();
2055
+ const [read, setRead] = useState(void 0);
2056
+ const [readFailed, setReadFailed] = useState(false);
2057
+ const refresh = useCallback(async () => {
2058
+ try {
2059
+ const response = await client.travellers.getDetail({ tripRef, travellerRef });
2060
+ setRead(response);
2061
+ setReadFailed(false);
2062
+ } catch {
2063
+ setReadFailed(true);
2064
+ }
2065
+ }, [client, tripRef, travellerRef]);
2066
+ useEffect(() => {
2067
+ void refresh();
2068
+ }, [refresh]);
2069
+ useOfflineEvent("sync.drained", refresh);
2070
+ const noteMutation = useOfflineMutation({
2071
+ tripRef,
2072
+ enqueue: (engine, input) => engine.enqueue({
2073
+ tripRef,
2074
+ method: SyncOpMethod.Post,
2075
+ path: `/api/v1/trips/${tripRef}/travellers/${travellerRef}/notes`,
2076
+ operationId: "addTravellerNote",
2077
+ body: input
2078
+ })
2079
+ });
2080
+ const noteMutate = noteMutation.mutate;
2081
+ const addNote = useCallback(
2082
+ async (input) => {
2083
+ const op = await noteMutate(input);
2084
+ void refresh();
2085
+ return op;
2086
+ },
2087
+ [noteMutate, refresh]
2088
+ );
2089
+ return { read, readFailed, refresh, addNote };
2090
+ }
2091
+ function useVendors(tripRef, agencyRef) {
2092
+ const client = useKaafilClient();
2093
+ const { rows, loaded: capabilitiesLoaded } = useCapabilityRows(tripRef);
2094
+ const [vendors, setVendors] = useState([]);
2095
+ const [listLoaded, setListLoaded] = useState(false);
2096
+ const [lastFetchFailed, setLastFetchFailed] = useState(false);
2097
+ const [syncedAt, setSyncedAt] = useState(void 0);
2098
+ const [firstLoadError, setFirstLoadError] = useState(void 0);
2099
+ const refresh = useCallback(async () => {
2100
+ try {
2101
+ const response = await client.vendors.list({ tripRef });
2102
+ setVendors(response);
2103
+ setListLoaded(true);
2104
+ setLastFetchFailed(false);
2105
+ setSyncedAt(response.meta.serverTime);
2106
+ setFirstLoadError(void 0);
2107
+ } catch (error) {
2108
+ setLastFetchFailed(true);
2109
+ setFirstLoadError((previous) => previous ?? classifyError(error));
2110
+ }
2111
+ }, [client, tripRef]);
2112
+ useEffect(() => {
2113
+ void refresh();
2114
+ }, [refresh]);
2115
+ const create = useCallback((input) => client.vendors.create(input), [client]);
2116
+ const upsert = useCallback((input) => client.vendors.upsert(input), [client]);
2117
+ const remove = useCallback((input) => client.vendors.remove(input), [client]);
2118
+ if (!capabilitiesLoaded) {
2119
+ return { status: "loading" };
2120
+ }
2121
+ const row = rows.find((candidate) => candidate.capability === "vendors");
2122
+ const triple = row === void 0 ? UNKNOWN_CAPABILITY_TRIPLE : deriveCapabilityTriple(row);
2123
+ if (triple.status !== "lit") {
2124
+ const reason = darkReasonFor(triple);
2125
+ return { status: "dark", reason };
2126
+ }
2127
+ if (!listLoaded && lastFetchFailed) {
2128
+ return {
2129
+ status: "error",
2130
+ error: firstLoadError ?? {
2131
+ path: "surface-to-user",
2132
+ code: void 0,
2133
+ status: void 0,
2134
+ unsatisfiableScheme: false,
2135
+ cause: void 0
2136
+ }
2137
+ };
2138
+ }
2139
+ return {
2140
+ status: "ready",
2141
+ vendors,
2142
+ loaded: listLoaded,
2143
+ lastFetchFailed,
2144
+ syncedAt,
2145
+ refresh,
2146
+ create,
2147
+ upsert,
2148
+ remove
2149
+ };
2150
+ }
2151
+
2152
+ export { ERROR_HANDLING_PATH, FORM_FIELD_KIND_SINGLE_BOOLEAN, classifyError, createDomainHook, useChecklists, useCloseout, useCollections, useExpenses, useFiles, useFloat, useForms, useItinerary, useManagerToday, useManagers, useParties, usePickups, useRooming, useSeating, useSnapshotList, useTravellerProfile, useVendors };
2153
+ //# sourceMappingURL=chunk-UYJ6LIWP.js.map
2154
+ //# sourceMappingURL=chunk-UYJ6LIWP.js.map