@gelabs/ovr 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/auth-auth.js +1 -1
  2. package/dist/auth.js +1 -1
  3. package/dist/{chunk-MDTRBOPQ.js → chunk-2C3VCTYJ.js} +1 -1
  4. package/dist/chunk-3YKVH4Y7.js +126 -0
  5. package/dist/chunk-6YFZLXFP.js +84 -0
  6. package/dist/{chunk-3NZ2XUBO.js → chunk-AJ2RZTVX.js} +9 -2
  7. package/dist/chunk-BI4EGLPG.js +298 -0
  8. package/dist/{chunk-3KIDW4LT.js → chunk-BVI5XDDA.js} +1 -1
  9. package/dist/{chunk-BIQ2J75Y.js → chunk-GLIK5BHP.js} +2 -2
  10. package/dist/{chunk-5YYR37CF.js → chunk-HGWPA7FU.js} +119 -0
  11. package/dist/{chunk-JEYT63LE.js → chunk-IBZVIUNI.js} +1 -1
  12. package/dist/chunk-IS3THKTE.js +89 -0
  13. package/dist/{chunk-4SZXBT56.js → chunk-NT72CQAI.js} +2 -2
  14. package/dist/{chunk-E2D7QT6N.js → chunk-TJSNVTVB.js} +1 -1
  15. package/dist/{chunk-5Z2IAD5I.js → chunk-TLG4C2XI.js} +2 -2
  16. package/dist/chunk-V7VQVDWS.js +237 -0
  17. package/dist/{chunk-IF5UAVIE.js → chunk-YC7G2IOZ.js} +1 -1
  18. package/dist/chunk-YGYA7KEG.js +423 -0
  19. package/dist/{chunk-GDOCD7LT.js → chunk-ZUMEOZ22.js} +5 -5
  20. package/dist/core-i18n.d.ts +2 -2
  21. package/dist/core-i18n.js +1 -1
  22. package/dist/core.d.ts +61 -1
  23. package/dist/core.js +1 -1
  24. package/dist/data-mock-store.js +265 -9
  25. package/dist/data-prisma-store.js +254 -1
  26. package/dist/data-seed-runner.js +18 -15
  27. package/dist/data.d.ts +54 -3
  28. package/dist/generated/client/edge.js +29 -9
  29. package/dist/generated/client/index-browser.js +26 -6
  30. package/dist/generated/client/index.d.ts +3544 -552
  31. package/dist/generated/client/index.js +29 -9
  32. package/dist/generated/client/package.json +1 -1
  33. package/dist/generated/client/schema.prisma +47 -9
  34. package/dist/generated/client/wasm.js +29 -9
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +1 -1
  37. package/dist/offline.d.ts +55 -19
  38. package/dist/offline.js +2 -375
  39. package/dist/{types-CtBC5-TW.d.ts → types-BOgdk0Jw.d.ts} +119 -0
  40. package/dist/types.d.ts +94 -1
  41. package/dist/types.js +1 -1
  42. package/dist/ui-components-admin/accounts-manager.d.ts +52 -0
  43. package/dist/ui-components-admin/accounts-manager.js +471 -0
  44. package/dist/ui-components-admin/admin-nav.d.ts +15 -1
  45. package/dist/ui-components-admin/admin-nav.js +387 -58
  46. package/dist/ui-components-admin/issuance-form.js +96 -23
  47. package/dist/ui-components-admin/logs-viewer.d.ts +13 -0
  48. package/dist/ui-components-admin/logs-viewer.js +102 -0
  49. package/dist/ui-components-admin/notifications-list.d.ts +5 -0
  50. package/dist/ui-components-admin/notifications-list.js +70 -0
  51. package/dist/ui-components-admin/officers-manager.d.ts +27 -0
  52. package/dist/ui-components-admin/officers-manager.js +271 -0
  53. package/dist/ui-components-admin/roles-manager.d.ts +37 -0
  54. package/dist/ui-components-admin/roles-manager.js +406 -0
  55. package/dist/ui-components-admin/ticket-preview.js +7 -7
  56. package/dist/ui-components-admin/tickets-table.js +56 -33
  57. package/dist/ui-components-citizen/citizen-nav.js +2 -2
  58. package/dist/ui-components-citizen/payment-form.js +5 -5
  59. package/dist/ui-components-citizen/payment-qr-dialog.js +4 -4
  60. package/dist/ui-components-citizen/ticket-not-found.js +2 -2
  61. package/dist/ui-components-citizen/violation-history-table.js +3 -3
  62. package/dist/ui-components-shared/amount-summary.js +4 -4
  63. package/dist/ui-components-shared/money.js +3 -3
  64. package/dist/ui-components-shared/municipal-seal.js +3 -3
  65. package/dist/ui-components-shared/official-header.js +4 -4
  66. package/dist/ui-components-shared/site-header.js +4 -4
  67. package/dist/ui-components-shared/sonner.js +2 -2
  68. package/dist/ui-components-shared/theme-toggle.js +3 -3
  69. package/dist/ui-components-shared/ticket-receipt.js +15 -7
  70. package/dist/ui-components-shared/violations-table.js +4 -4
  71. package/dist/ui-components-ui/badge.d.ts +1 -1
  72. package/dist/ui-components-ui/button.d.ts +1 -1
  73. package/dist/ui-components-ui/dropdown-menu.js +2 -237
  74. package/dist/ui-components-ui/sheet.js +3 -126
  75. package/dist/ui-config.d.ts +1 -1
  76. package/dist/ui-config.js +2 -2
  77. package/dist/ui-server.d.ts +1 -1
  78. package/dist/ui-server.js +2 -2
  79. package/package.json +3 -3
  80. package/prisma/migrations/20260622000000_add_issued_by/migration.sql +2 -0
  81. package/prisma/migrations/20260622010000_add_super_admin_role/migration.sql +3 -0
  82. package/prisma/migrations/20260622020000_add_apprehending_enforcer/migration.sql +4 -0
  83. package/prisma/migrations/20260622030000_custom_roles/migration.sql +30 -0
  84. package/prisma/migrations/20260622040000_add_activity_log/migration.sql +18 -0
  85. package/prisma/schema.prisma +47 -9
  86. package/dist/chunk-B634JHKZ.js +0 -181
@@ -0,0 +1,423 @@
1
+ import { makeOvrTicketNo, addDays, makeBillNo, makeOrderOfPaymentNo, enrich, fullName } from './chunk-BI4EGLPG.js';
2
+ import Dexie from 'dexie';
3
+ import { useState, useEffect } from 'react';
4
+ import { useLiveQuery } from 'dexie-react-hooks';
5
+
6
+ var db = new Dexie("eovr-offline");
7
+ db.version(1).stores({
8
+ tickets: "ovrTicketNo, paymentStatus, createdAt",
9
+ catalog: "code, category",
10
+ officers: "id",
11
+ meta: "key"
12
+ });
13
+ db.version(2).stores({
14
+ outbox: "ovrTicketNo, status, createdAt"
15
+ });
16
+ async function getMeta(key) {
17
+ const row = await db.meta.get(key);
18
+ return row?.value;
19
+ }
20
+ async function setMeta(key, value) {
21
+ await db.meta.put({ key, value });
22
+ }
23
+
24
+ // ../ovr-offline/src/sync.ts
25
+ var API = "/api";
26
+ var LEASE_MIN = 10;
27
+ var LEASE_BATCH = 50;
28
+ function setOfflineApiBase(base) {
29
+ API = base.replace(/\/$/, "");
30
+ }
31
+ function offlineApiBase() {
32
+ return API;
33
+ }
34
+ var SessionExpired = class extends Error {
35
+ constructor() {
36
+ super("session_expired");
37
+ this.name = "SessionExpired";
38
+ }
39
+ };
40
+ function isOnline() {
41
+ return typeof navigator !== "undefined" ? navigator.onLine : true;
42
+ }
43
+ async function call(path, init) {
44
+ let res;
45
+ try {
46
+ res = await fetch(`${API}${path}`, { credentials: "include", ...init });
47
+ } catch {
48
+ return null;
49
+ }
50
+ if (res.status === 401) throw new SessionExpired();
51
+ return res;
52
+ }
53
+ var postInit = (body) => ({
54
+ method: "POST",
55
+ headers: { "content-type": "application/json" },
56
+ body: JSON.stringify(body)
57
+ });
58
+ async function ensureLease(min = LEASE_MIN) {
59
+ if (!isOnline()) return;
60
+ const seqs = await getMeta("leaseSeqs") ?? [];
61
+ if (seqs.length >= min) return;
62
+ const res = await call("/sync/lease", postInit({ count: LEASE_BATCH }));
63
+ if (!res || !res.ok) return;
64
+ const { seqs: fresh } = await res.json();
65
+ await setMeta("leaseSeqs", [...seqs, ...fresh]);
66
+ }
67
+ async function pushOutbox() {
68
+ const pending = await db.outbox.toArray();
69
+ if (!pending.length) return;
70
+ const res = await call(
71
+ "/sync/push",
72
+ postInit({ tickets: pending.map((p) => p.payload) })
73
+ );
74
+ if (!res || !res.ok) return;
75
+ const { results } = await res.json();
76
+ await db.transaction("rw", db.outbox, db.tickets, async () => {
77
+ for (const r of results) {
78
+ if (r.ok) {
79
+ await db.outbox.delete(r.ovrTicketNo);
80
+ if (r.ticket) await db.tickets.put(r.ticket);
81
+ } else {
82
+ await db.outbox.update(r.ovrTicketNo, { status: "error", error: r.error });
83
+ }
84
+ }
85
+ });
86
+ }
87
+ async function pullAll() {
88
+ const [t, c, o, s] = await Promise.all([
89
+ call("/tickets"),
90
+ call("/violations"),
91
+ call("/officers"),
92
+ call("/tickets/stats")
93
+ ]);
94
+ if (!t || !c || !o || !s) return;
95
+ if (!t.ok || !c.ok || !o.ok || !s.ok) {
96
+ throw new Error(
97
+ `sync.pullAll failed: tickets=${t.status} catalog=${c.status} officers=${o.status} stats=${s.status}`
98
+ );
99
+ }
100
+ const tickets = await t.json();
101
+ const catalog = await c.json();
102
+ const officers = await o.json();
103
+ const stats = await s.json();
104
+ await db.transaction(
105
+ "rw",
106
+ db.tickets,
107
+ db.catalog,
108
+ db.officers,
109
+ db.meta,
110
+ async () => {
111
+ await db.tickets.bulkPut(tickets);
112
+ await db.catalog.clear();
113
+ await db.catalog.bulkPut(catalog);
114
+ await db.officers.clear();
115
+ await db.officers.bulkPut(officers);
116
+ await db.meta.put({ key: "stats", value: stats });
117
+ }
118
+ );
119
+ await setMeta("lastSyncedAt", (/* @__PURE__ */ new Date()).toISOString());
120
+ }
121
+ async function sync() {
122
+ if (!isOnline()) return;
123
+ await pushOutbox();
124
+ await ensureLease();
125
+ await pullAll();
126
+ }
127
+
128
+ // ../ovr-offline/src/issue.ts
129
+ async function popSeq() {
130
+ return db.transaction("rw", db.meta, async () => {
131
+ const row = await db.meta.get("leaseSeqs");
132
+ const seqs = row?.value ?? [];
133
+ if (seqs.length === 0) return null;
134
+ await db.meta.put({ key: "leaseSeqs", value: seqs.slice(1) });
135
+ return seqs[0];
136
+ });
137
+ }
138
+ async function issueTicketOffline(input, rules) {
139
+ let seq = await popSeq();
140
+ if (seq === null && isOnline()) {
141
+ await ensureLease(50);
142
+ seq = await popSeq();
143
+ }
144
+ if (seq === null) {
145
+ throw new Error(
146
+ isOnline() ? "Couldn't reserve ticket numbers \u2014 please try again." : "No offline ticket numbers left. Reconnect to the internet to reserve more."
147
+ );
148
+ }
149
+ const now = /* @__PURE__ */ new Date();
150
+ const ovrTicketNo = makeOvrTicketNo(rules.idPrefix, now.getFullYear(), seq);
151
+ const officer = await db.officers.get(input.officerId);
152
+ if (!officer) {
153
+ const cur = await getMeta("leaseSeqs") ?? [];
154
+ await setMeta("leaseSeqs", [seq, ...cur]);
155
+ throw new Error("That officer isn't available offline. Sync and try again.");
156
+ }
157
+ const catalog = await db.catalog.toArray();
158
+ const byCode = new Map(catalog.map((c) => [c.code, c]));
159
+ const violations = input.violations.map((x) => {
160
+ const c = byCode.get(x.catalogCode);
161
+ if (!c) throw new Error(`Unknown violation: ${x.catalogCode}`);
162
+ const v = {
163
+ catalogCode: c.code,
164
+ title: c.title,
165
+ basicFine: c.basicFine
166
+ };
167
+ if (x.details?.trim()) v.details = x.details.trim();
168
+ return v;
169
+ });
170
+ const basicFinesTotal = violations.reduce((s, v) => s + v.basicFine, 0);
171
+ const record = {
172
+ ovrTicketNo,
173
+ orderOfPaymentNo: makeOrderOfPaymentNo(ovrTicketNo),
174
+ billNo: makeBillNo(now, officer.office, officer.badgeNo ?? "X000", seq),
175
+ violator: input.violator,
176
+ apprehendedAt: input.apprehendedAt,
177
+ officer,
178
+ violations,
179
+ assessedAt: now.toISOString(),
180
+ dueDate: addDays(now, rules.dueWindowDays).toISOString(),
181
+ basicFinesTotal,
182
+ paymentStatus: "UNPAID",
183
+ createdAt: now.toISOString()
184
+ };
185
+ if (input.placeOfViolation?.trim())
186
+ record.placeOfViolation = input.placeOfViolation.trim();
187
+ if (input.remarks?.trim()) record.remarks = input.remarks.trim();
188
+ if (input.issuedBy?.trim()) record.issuedBy = input.issuedBy.trim();
189
+ if (input.apprehendingEnforcerId)
190
+ record.apprehendingEnforcerId = input.apprehendingEnforcerId;
191
+ if (input.apprehendingEnforcerName?.trim())
192
+ record.apprehendingEnforcerName = input.apprehendingEnforcerName.trim();
193
+ const ticket = enrich(record, now, rules.surchargeRatePerMonth);
194
+ await db.transaction("rw", db.tickets, db.outbox, async () => {
195
+ await db.tickets.put(ticket);
196
+ await db.outbox.put({
197
+ ovrTicketNo,
198
+ createdAt: record.createdAt,
199
+ status: "pending",
200
+ payload: {
201
+ ovrTicketNo,
202
+ createdAt: record.createdAt,
203
+ violator: input.violator,
204
+ apprehendedAt: input.apprehendedAt,
205
+ ...record.placeOfViolation ? { placeOfViolation: record.placeOfViolation } : {},
206
+ officerId: input.officerId,
207
+ violations: input.violations,
208
+ ...record.remarks ? { remarks: record.remarks } : {},
209
+ ...record.apprehendingEnforcerId ? { apprehendingEnforcerId: record.apprehendingEnforcerId } : {},
210
+ ...record.apprehendingEnforcerName ? { apprehendingEnforcerName: record.apprehendingEnforcerName } : {}
211
+ }
212
+ });
213
+ });
214
+ if (isOnline()) void pushOutbox().catch(() => {
215
+ });
216
+ return ticket;
217
+ }
218
+ async function getIdentity() {
219
+ return await getMeta("identity") ?? void 0;
220
+ }
221
+ async function clearIdentity() {
222
+ await setMeta("identity", null);
223
+ }
224
+ function useAdminAuth() {
225
+ const [state, setState] = useState({ status: "loading" });
226
+ useEffect(() => {
227
+ let cancelled = false;
228
+ (async () => {
229
+ const cached = await getMeta("identity");
230
+ if (isOnline()) {
231
+ try {
232
+ const res = await fetch(`${offlineApiBase()}/auth/me`, {
233
+ credentials: "include"
234
+ });
235
+ if (res.ok) {
236
+ const { user } = await res.json();
237
+ await setMeta("identity", user);
238
+ if (!cancelled) setState({ status: "authed", user });
239
+ return;
240
+ }
241
+ await setMeta("identity", null);
242
+ if (!cancelled) setState({ status: "unauthed" });
243
+ return;
244
+ } catch {
245
+ }
246
+ }
247
+ if (!cancelled) {
248
+ setState(
249
+ cached ? { status: "authed", user: cached } : { status: "unauthed" }
250
+ );
251
+ }
252
+ })();
253
+ return () => {
254
+ cancelled = true;
255
+ };
256
+ }, []);
257
+ return state;
258
+ }
259
+ var PBKDF2_ITERATIONS = 21e4;
260
+ function subtle() {
261
+ return typeof crypto !== "undefined" && crypto.subtle ? crypto.subtle : null;
262
+ }
263
+ var toHex = (buf) => Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, "0")).join(
264
+ ""
265
+ );
266
+ function fromHex(hex) {
267
+ const out = new Uint8Array(hex.length / 2);
268
+ for (let i = 0; i < out.length; i++)
269
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
270
+ return out;
271
+ }
272
+ async function deriveHashHex(password, salt, iterations) {
273
+ const s = subtle();
274
+ if (!s) return null;
275
+ const keyMaterial = await s.importKey(
276
+ "raw",
277
+ new TextEncoder().encode(password),
278
+ "PBKDF2",
279
+ false,
280
+ ["deriveBits"]
281
+ );
282
+ const bits = await s.deriveBits(
283
+ { name: "PBKDF2", salt, iterations, hash: "SHA-256" },
284
+ keyMaterial,
285
+ 256
286
+ );
287
+ return toHex(bits);
288
+ }
289
+ async function cacheCredential(username, password, identity) {
290
+ if (!subtle()) return;
291
+ const salt = crypto.getRandomValues(new Uint8Array(16));
292
+ const hashHex = await deriveHashHex(password, salt, PBKDF2_ITERATIONS);
293
+ if (!hashHex) return;
294
+ const cred = {
295
+ username: username.trim(),
296
+ saltHex: toHex(salt.buffer),
297
+ hashHex,
298
+ iterations: PBKDF2_ITERATIONS,
299
+ identity
300
+ };
301
+ await setMeta("credential", cred);
302
+ }
303
+ async function verifyOffline(username, password) {
304
+ const cred = await getMeta("credential");
305
+ if (!cred || cred.username !== username.trim()) return null;
306
+ const hashHex = await deriveHashHex(
307
+ password,
308
+ fromHex(cred.saltHex),
309
+ cred.iterations
310
+ );
311
+ if (!hashHex || hashHex !== cred.hashHex) return null;
312
+ await setMeta("identity", cred.identity);
313
+ return cred.identity;
314
+ }
315
+ function useTickets() {
316
+ return useLiveQuery(() => db.tickets.orderBy("createdAt").reverse().toArray());
317
+ }
318
+ function useCatalog() {
319
+ return useLiveQuery(() => db.catalog.orderBy("category").toArray());
320
+ }
321
+ function useOfficers() {
322
+ return useLiveQuery(() => db.officers.toArray());
323
+ }
324
+ function useIdentity() {
325
+ return useLiveQuery(
326
+ async () => (await db.meta.get("identity"))?.value
327
+ );
328
+ }
329
+ function useNotifications() {
330
+ const tickets = useTickets();
331
+ if (!tickets) return [];
332
+ const items = [];
333
+ for (const t of tickets) {
334
+ const type = t.status === "OVERDUE" ? "overdue" : t.status === "OUTSTANDING" ? "outstanding" : null;
335
+ if (!type) continue;
336
+ items.push({
337
+ id: `${t.ovrTicketNo}:${type}`,
338
+ type,
339
+ ovrTicketNo: t.ovrTicketNo,
340
+ name: fullName(t.violator),
341
+ at: type === "overdue" ? t.dueDate : t.createdAt,
342
+ href: `/admin/tickets/${encodeURIComponent(t.ovrTicketNo)}`
343
+ });
344
+ }
345
+ const rank = (n) => n.type === "overdue" ? 0 : 1;
346
+ items.sort((a, b) => rank(a) - rank(b) || (a.at < b.at ? 1 : a.at > b.at ? -1 : 0));
347
+ return items;
348
+ }
349
+ function notifSignature(items) {
350
+ return items.map((n) => n.id).join("|");
351
+ }
352
+ function useNotificationsState() {
353
+ const items = useNotifications();
354
+ const seen = useLiveQuery(
355
+ async () => (await db.meta.get("notif_seen"))?.value
356
+ );
357
+ const signature = notifSignature(items);
358
+ return {
359
+ items,
360
+ total: items.length,
361
+ unseen: items.length > 0 && signature !== seen,
362
+ markSeen: () => {
363
+ void setMeta("notif_seen", signature);
364
+ }
365
+ };
366
+ }
367
+ function useStats() {
368
+ return useLiveQuery(
369
+ async () => (await db.meta.get("stats"))?.value
370
+ );
371
+ }
372
+ function useTicket(ovrTicketNo) {
373
+ return useLiveQuery(
374
+ async () => await db.tickets.get(ovrTicketNo) ?? null,
375
+ [ovrTicketNo]
376
+ );
377
+ }
378
+ function usePendingSync() {
379
+ const rows = useLiveQuery(() => db.outbox.toArray());
380
+ return new Set((rows ?? []).map((r) => r.ovrTicketNo));
381
+ }
382
+ function useSync() {
383
+ const [syncing, setSyncing] = useState(false);
384
+ const [online, setOnline] = useState(true);
385
+ const [error, setError] = useState(null);
386
+ useEffect(() => {
387
+ let cancelled = false;
388
+ setOnline(isOnline());
389
+ async function runSyncCycle() {
390
+ if (!isOnline()) return;
391
+ setSyncing(true);
392
+ setError(null);
393
+ try {
394
+ await sync();
395
+ } catch (e) {
396
+ if (e instanceof SessionExpired) {
397
+ await clearIdentity();
398
+ window.location.assign("/admin/login");
399
+ return;
400
+ }
401
+ if (!cancelled) setError(e instanceof Error ? e.message : "sync failed");
402
+ } finally {
403
+ if (!cancelled) setSyncing(false);
404
+ }
405
+ }
406
+ const onOnline = () => {
407
+ setOnline(true);
408
+ void runSyncCycle();
409
+ };
410
+ const onOffline = () => setOnline(false);
411
+ void runSyncCycle();
412
+ window.addEventListener("online", onOnline);
413
+ window.addEventListener("offline", onOffline);
414
+ return () => {
415
+ cancelled = true;
416
+ window.removeEventListener("online", onOnline);
417
+ window.removeEventListener("offline", onOffline);
418
+ };
419
+ }, []);
420
+ return { syncing, online, error };
421
+ }
422
+
423
+ export { SessionExpired, cacheCredential, clearIdentity, db, ensureLease, getIdentity, getMeta, isOnline, issueTicketOffline, offlineApiBase, pullAll, pushOutbox, setMeta, setOfflineApiBase, sync, useAdminAuth, useCatalog, useIdentity, useNotifications, useNotificationsState, useOfficers, usePendingSync, useStats, useSync, useTicket, useTickets, verifyOffline };
@@ -1,9 +1,9 @@
1
- import { ViolationsTable } from './chunk-JEYT63LE.js';
2
- import { AmountSummary } from './chunk-BIQ2J75Y.js';
3
- import { Card, CardHeader, CardTitle, CardContent } from './chunk-SETIN6XP.js';
1
+ import { ViolationsTable } from './chunk-IBZVIUNI.js';
2
+ import { AmountSummary } from './chunk-GLIK5BHP.js';
4
3
  import { StatusBadge } from './chunk-OE525ZER.js';
5
- import { useFormatters } from './chunk-E2D7QT6N.js';
6
- import { formalName, formatAddress } from './chunk-B634JHKZ.js';
4
+ import { Card, CardHeader, CardTitle, CardContent } from './chunk-SETIN6XP.js';
5
+ import { useFormatters } from './chunk-TJSNVTVB.js';
6
+ import { formalName, formatAddress } from './chunk-BI4EGLPG.js';
7
7
  import { jsxs, jsx } from 'react/jsx-runtime';
8
8
 
9
9
  function Field({ label, value }) {
@@ -1,5 +1,5 @@
1
- import { C as CopyOverrides, D as Dictionary } from './types-CtBC5-TW.js';
2
- export { b as baseCopy } from './types-CtBC5-TW.js';
1
+ import { C as CopyOverrides, D as Dictionary } from './types-BOgdk0Jw.js';
2
+ export { b as baseCopy } from './types-BOgdk0Jw.js';
3
3
  import { M as MunicipalityConfig } from './schema-CdsFQxIg.js';
4
4
  import 'zod';
5
5
 
package/dist/core-i18n.js CHANGED
@@ -1 +1 @@
1
- export { baseCopy, mergeCopy } from './chunk-5YYR37CF.js';
1
+ export { baseCopy, mergeCopy } from './chunk-HGWPA7FU.js';
package/dist/core.d.ts CHANGED
@@ -73,4 +73,64 @@ interface IssuanceDraft {
73
73
  type PreviewRules = Pick<RulesConfig, "dueWindowDays" | "surchargeRatePerMonth">;
74
74
  declare function toPreviewTicket(draft: IssuanceDraft, now: Date, rules: PreviewRules): Ticket;
75
75
 
76
- export { type ChargeInput, type ChargeResult, type IssuanceDraft, PREVIEW_PLACEHOLDER, type PreviewRules, computeCharges, enrich, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket };
76
+ /**
77
+ * Dashboard time-range presets (GE-010): 1D / 7D / MTD / prev-MTD (+ All).
78
+ *
79
+ * Ranges are computed in the LGU's timezone (config `timeZone`) so "today" / "this
80
+ * month" mean the local calendar, not the server's. Stats are derived from the
81
+ * on-device ticket list (offline-first) — no server round-trip — by ISSUE date
82
+ * (`createdAt`); each card reflects the CURRENT status of tickets issued in range.
83
+ */
84
+
85
+ type DashboardRangePreset = "1d" | "7d" | "mtd" | "prev-mtd" | "all";
86
+ interface DashboardPreset {
87
+ key: DashboardRangePreset;
88
+ label: string;
89
+ }
90
+ /** The presets, in display order. */
91
+ declare const DASHBOARD_PRESETS: DashboardPreset[];
92
+ interface DashboardRange {
93
+ /** Inclusive lower bound (ISO), or null for "all time" (no lower bound). */
94
+ fromISO: string | null;
95
+ /** Inclusive upper bound (ISO). */
96
+ toISO: string;
97
+ }
98
+ interface DashboardStats {
99
+ issued: number;
100
+ outstanding: number;
101
+ overdue: number;
102
+ paid: number;
103
+ collected: number;
104
+ }
105
+ /** Resolve a preset to a concrete [from, to] range, anchored to `now` in `timeZone`. */
106
+ declare function dashboardDateRange(preset: DashboardRangePreset, now: Date, timeZone: string): DashboardRange;
107
+ /** A custom date range from two `YYYY-MM-DD` local dates (either may be empty:
108
+ * empty `from` → no lower bound; empty `to` → up to `now`). Interpreted in
109
+ * `timeZone` as [from 00:00:00, to 23:59:59]. If from > to, they're swapped. */
110
+ declare function customDateRange(fromYMD: string | null | undefined, toYMD: string | null | undefined, now: Date, timeZone: string): DashboardRange;
111
+ /** Compute the dashboard cards from the local ticket list, scoped to `range`
112
+ * by issue date. Each card reflects each ticket's CURRENT status. */
113
+ declare function computeDashboardStats(tickets: Ticket[], range: DashboardRange): DashboardStats;
114
+
115
+ /**
116
+ * Philippine locations (PSGC) for the Issue-Ticket address dropdowns (GE-015).
117
+ *
118
+ * Provinces + their cities/municipalities ONLY (barangays omitted to keep the
119
+ * bundle small — barangay stays free-text). Bundled (no runtime API) so it works
120
+ * offline. NCR districts are flattened into a single "Metro Manila" province.
121
+ *
122
+ * Source (open data, CC0/public): flores-jacob/philippine-regions-provinces-
123
+ * cities-municipalities-barangays (2019v2, derived from PSA PSGC). Names are
124
+ * title-cased from the source's all-caps.
125
+ */
126
+ interface PhProvince {
127
+ province: string;
128
+ cities: string[];
129
+ }
130
+ declare const PH_PROVINCES: PhProvince[];
131
+ /** All province names (display order = alphabetical). */
132
+ declare function listProvinces(): string[];
133
+ /** Cities/municipalities of a province (case-insensitive match; [] if unknown). */
134
+ declare function citiesOfProvince(province: string | null | undefined): string[];
135
+
136
+ export { type ChargeInput, type ChargeResult, DASHBOARD_PRESETS, type DashboardPreset, type DashboardRange, type DashboardRangePreset, type DashboardStats, type IssuanceDraft, PH_PROVINCES, PREVIEW_PLACEHOLDER, type PhProvince, type PreviewRules, citiesOfProvince, computeCharges, computeDashboardStats, customDateRange, dashboardDateRange, enrich, listProvinces, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket };
package/dist/core.js CHANGED
@@ -1 +1 @@
1
- export { PREVIEW_PLACEHOLDER, addDays, computeCharges, createFormatters, enrich, formalName, formatAddress, fullName, localToManilaISO, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket } from './chunk-B634JHKZ.js';
1
+ export { DASHBOARD_PRESETS, PH_PROVINCES, PREVIEW_PLACEHOLDER, addDays, citiesOfProvince, computeCharges, computeDashboardStats, createFormatters, customDateRange, dashboardDateRange, enrich, formalName, formatAddress, fullName, listProvinces, localToManilaISO, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket } from './chunk-BI4EGLPG.js';