@gelabs/ovr 0.2.2 → 0.4.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 (90) 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-DJMUW5T2.js +298 -0
  10. package/dist/{chunk-BIQ2J75Y.js → chunk-GLIK5BHP.js} +2 -2
  11. package/dist/{chunk-JEYT63LE.js → chunk-IBZVIUNI.js} +1 -1
  12. package/dist/{chunk-4SZXBT56.js → chunk-NT72CQAI.js} +2 -2
  13. package/dist/{chunk-E2D7QT6N.js → chunk-TJSNVTVB.js} +1 -1
  14. package/dist/{chunk-5Z2IAD5I.js → chunk-TLG4C2XI.js} +2 -2
  15. package/dist/chunk-V7VQVDWS.js +237 -0
  16. package/dist/chunk-WUNTHINH.js +98 -0
  17. package/dist/{chunk-IF5UAVIE.js → chunk-YC7G2IOZ.js} +1 -1
  18. package/dist/{chunk-IB4JVGKJ.js → chunk-YGYA7KEG.js} +47 -3
  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 +330 -12
  25. package/dist/data-prisma-store.js +319 -9
  26. package/dist/data-seed-runner.js +18 -15
  27. package/dist/data.d.ts +64 -3
  28. package/dist/generated/client/edge.js +31 -10
  29. package/dist/generated/client/index-browser.js +28 -7
  30. package/dist/generated/client/index.d.ts +3583 -577
  31. package/dist/generated/client/index.js +31 -10
  32. package/dist/generated/client/package.json +1 -1
  33. package/dist/generated/client/schema.prisma +48 -9
  34. package/dist/generated/client/wasm.js +31 -10
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +1 -1
  37. package/dist/offline.d.ts +34 -1
  38. package/dist/offline.js +2 -2
  39. package/dist/types-B8MopM4b.d.ts +281 -0
  40. package/dist/types.d.ts +104 -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 +388 -60
  46. package/dist/ui-components-admin/issuance-form.js +72 -13
  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-admin/violations-manager.d.ts +32 -0
  58. package/dist/ui-components-admin/violations-manager.js +385 -0
  59. package/dist/ui-components-citizen/citizen-nav.js +2 -2
  60. package/dist/ui-components-citizen/payment-form.js +5 -5
  61. package/dist/ui-components-citizen/payment-qr-dialog.js +4 -4
  62. package/dist/ui-components-citizen/ticket-not-found.js +2 -2
  63. package/dist/ui-components-citizen/violation-history-table.js +3 -3
  64. package/dist/ui-components-shared/amount-summary.js +4 -4
  65. package/dist/ui-components-shared/money.js +3 -3
  66. package/dist/ui-components-shared/municipal-seal.js +3 -3
  67. package/dist/ui-components-shared/official-header.js +4 -4
  68. package/dist/ui-components-shared/site-header.js +4 -4
  69. package/dist/ui-components-shared/sonner.js +2 -2
  70. package/dist/ui-components-shared/theme-toggle.js +3 -3
  71. package/dist/ui-components-shared/ticket-receipt.js +13 -6
  72. package/dist/ui-components-shared/violations-table.js +4 -4
  73. package/dist/ui-components-ui/badge.d.ts +1 -1
  74. package/dist/ui-components-ui/button.d.ts +1 -1
  75. package/dist/ui-components-ui/dropdown-menu.js +2 -237
  76. package/dist/ui-components-ui/sheet.js +3 -126
  77. package/dist/ui-config.d.ts +1 -1
  78. package/dist/ui-config.js +2 -2
  79. package/dist/ui-server.d.ts +1 -1
  80. package/dist/ui-server.js +2 -2
  81. package/package.json +6 -6
  82. package/prisma/migrations/20260622010000_add_super_admin_role/migration.sql +3 -0
  83. package/prisma/migrations/20260622020000_add_apprehending_enforcer/migration.sql +4 -0
  84. package/prisma/migrations/20260622030000_custom_roles/migration.sql +30 -0
  85. package/prisma/migrations/20260622040000_add_activity_log/migration.sql +18 -0
  86. package/prisma/migrations/20260622050000_violation_catalog_management/migration.sql +5 -0
  87. package/prisma/schema.prisma +48 -9
  88. package/dist/chunk-5YYR37CF.js +0 -146
  89. package/dist/chunk-B634JHKZ.js +0 -181
  90. package/dist/types-CtBC5-TW.d.ts +0 -129
@@ -1,146 +0,0 @@
1
- // ../ovr-core/src/i18n/base.ts
2
- var baseCopy = {
3
- app: {
4
- name: "e-OVR",
5
- tagline: "Online Ordinance Violation Receipt",
6
- description: "Issue, look up, and settle traffic and ordinance violation tickets online."
7
- },
8
- common: {
9
- search: "Search",
10
- clear: "Clear",
11
- cancel: "Cancel",
12
- confirm: "Confirm",
13
- back: "Back",
14
- print: "Print",
15
- payNow: "Pay now",
16
- payFine: "Pay fine",
17
- loading: "Loading\u2026",
18
- notFound: "Not found",
19
- amountDue: "Amount due",
20
- totalDue: "Total amount due",
21
- dueDate: "Due date",
22
- status: "Status"
23
- },
24
- landing: {
25
- heroTitle: "Settle violations online \u2014 fast, clear, and official.",
26
- heroSubtitle: "The {municipality}'s modern portal for traffic and ordinance violation receipts.",
27
- citizenCardTitle: "I have a ticket",
28
- citizenCardBody: "Look up your Ordinance Violation Receipt, review the details, and pay your fine online.",
29
- citizenCardCta: "Find my ticket",
30
- adminCardTitle: "I'm an enforcer",
31
- adminCardBody: "Issue a violation ticket on the spot and manage issued receipts.",
32
- adminCardCta: "Open enforcer portal"
33
- },
34
- citizen: {
35
- searchTitle: "OVR Ticket Search",
36
- searchHelp: "Enter your OVR ticket number and last name to retrieve your violation record.",
37
- ticketNoLabel: "OVR Ticket No.",
38
- lastNameLabel: "Last name",
39
- euaLabel: "I accept the End-User Agreement",
40
- searchCta: "Search ticket",
41
- notFoundTitle: "No matching ticket",
42
- notFoundBody: "We couldn't find a ticket with that number and last name. Check your details and try again.",
43
- home: {
44
- title: "Find and settle your violation receipt",
45
- subtitle: "Enter your OVR ticket number and last name to view your Order of Payment and pay online.",
46
- searchCta: "Find my ticket",
47
- step1Title: "Find your ticket",
48
- step1Body: "Search using your OVR ticket number and last name.",
49
- step2Title: "Review the details",
50
- step2Body: "Check the violation, the basic fines, and any surcharge.",
51
- step3Title: "Pay online",
52
- step3Body: "Settle via GCash, Maya, or Landbank and keep your receipt."
53
- },
54
- ticket: {
55
- orderOfPayment: "Order of Payment",
56
- violationDetails: "Violation details",
57
- reminders: "Reminders & end-user notice",
58
- payFine: "Pay fine",
59
- printReceipt: "Print receipt",
60
- viewReceipt: "View / print receipt",
61
- backToSearch: "Back to search",
62
- alreadyPaid: "This ticket has already been paid.",
63
- history: {
64
- title: "Violation history",
65
- subtitle: "All OVR tickets on record under this name.",
66
- current: "Current",
67
- empty: "No other violation records found under this name.",
68
- colTicketNo: "OVR Ticket No.",
69
- colDate: "Apprehended",
70
- colViolations: "Violation(s)",
71
- colStatus: "Status",
72
- colAmount: "Amount"
73
- }
74
- },
75
- pay: {
76
- title: "Pay your fine",
77
- subtitle: "Choose a payment method to settle this violation.",
78
- choose: "Choose payment method",
79
- payNow: "Pay",
80
- processing: "Processing payment\u2026",
81
- secure: "Simulated checkout \u2014 no real payment is processed.",
82
- success: "Payment successful \u2014 your receipt is ready.",
83
- qr: {
84
- title: "Scan to pay",
85
- instruction: "Open your e-wallet app, scan this QR, then tap below once your payment is complete.",
86
- placeholderNote: "Demo placeholder \u2014 the official merchant QR will appear here once the payment gateway is live.",
87
- amountToPay: "Amount to pay",
88
- confirm: "I've completed payment",
89
- cancel: "Cancel"
90
- }
91
- }
92
- },
93
- admin: {
94
- portal: "Enforcer Portal",
95
- dashboard: "Dashboard",
96
- tickets: "Tickets",
97
- issueTicket: "Issue ticket",
98
- newTicketTitle: "Issue Violation Ticket",
99
- sync: {
100
- offline: "Offline",
101
- syncing: "Syncing\u2026",
102
- synced: "Synced",
103
- pending: "pending sync",
104
- // e.g. "3 pending sync"
105
- notSynced: "Not synced",
106
- willSync: "Saved on this device \u2014 will sync when back online."
107
- }
108
- }
109
- };
110
-
111
- // ../ovr-core/src/i18n/merge.ts
112
- function isObject(v) {
113
- return typeof v === "object" && v !== null && !Array.isArray(v);
114
- }
115
- function deepMerge(base, override) {
116
- if (!isObject(base) || !isObject(override)) {
117
- return override === void 0 ? base : override;
118
- }
119
- const out = { ...base };
120
- for (const k of Object.keys(override)) {
121
- out[k] = deepMerge(
122
- base[k],
123
- override[k]
124
- );
125
- }
126
- return out;
127
- }
128
- function interpolate(node, vars) {
129
- if (typeof node === "string") {
130
- return node.replace(/\{(\w+)\}/g, (_, k) => vars[k] ?? `{${k}}`);
131
- }
132
- if (isObject(node)) {
133
- const out = {};
134
- for (const k of Object.keys(node)) {
135
- out[k] = interpolate(node[k], vars);
136
- }
137
- return out;
138
- }
139
- return node;
140
- }
141
- function mergeCopy(overrides = {}, municipality) {
142
- const merged = deepMerge(baseCopy, overrides);
143
- return interpolate(merged, { municipality: municipality?.name ?? "" });
144
- }
145
-
146
- export { baseCopy, mergeCopy };
@@ -1,181 +0,0 @@
1
- // ../ovr-core/src/penalty.ts
2
- var MS_PER_DAY = 864e5;
3
- function startedMonthsSince(from, to) {
4
- const days = Math.max(0, (to.getTime() - from.getTime()) / MS_PER_DAY);
5
- return Math.ceil(days / 30);
6
- }
7
- function computeCharges({
8
- basicFines,
9
- apprehendedAt,
10
- asOf,
11
- dueDate,
12
- ratePerMonth
13
- }) {
14
- if (asOf.getTime() <= dueDate.getTime()) {
15
- return { basicFines, penalty: 0, total: basicFines, monthsOverdue: 0 };
16
- }
17
- const months = Math.max(1, startedMonthsSince(apprehendedAt, asOf));
18
- const penalty = Math.round(basicFines * ratePerMonth * months * 100) / 100;
19
- return {
20
- basicFines,
21
- penalty,
22
- total: Math.round((basicFines + penalty) * 100) / 100,
23
- monthsOverdue: months
24
- };
25
- }
26
-
27
- // ../ovr-core/src/ids.ts
28
- var pad = (n, len) => String(n).padStart(len, "0");
29
- var two = (n) => pad(n, 2);
30
- function makeOvrTicketNo(idPrefix, year, seq) {
31
- return `${idPrefix}-${year}-${pad(seq, 6)}`;
32
- }
33
- function makeOrderOfPaymentNo(ovrTicketNo) {
34
- return `${ovrTicketNo}-01`;
35
- }
36
- function makeBillNo(date, officeAbbr, officerCode, seq) {
37
- const ymd = `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`;
38
- return `M-${ymd}-${officeAbbr}-${officerCode}-${pad(seq, 6)}`;
39
- }
40
- function makePaymentRef(method, at) {
41
- const prefix = method.slice(0, 3);
42
- const ymd = `${at.getFullYear()}${two(at.getMonth() + 1)}${two(at.getDate())}`;
43
- const hms = `${two(at.getHours())}${two(at.getMinutes())}${two(at.getSeconds())}`;
44
- return `${prefix}-${ymd}-${hms}`;
45
- }
46
-
47
- // ../ovr-core/src/format.ts
48
- function createFormatters(rules) {
49
- const pesoFmt = new Intl.NumberFormat(rules.locale, {
50
- style: "currency",
51
- currency: rules.currency
52
- });
53
- const dateFmt = new Intl.DateTimeFormat(rules.locale, {
54
- year: "numeric",
55
- month: "short",
56
- day: "2-digit",
57
- timeZone: rules.timeZone
58
- });
59
- const dateTimeFmt = new Intl.DateTimeFormat(rules.locale, {
60
- year: "numeric",
61
- month: "short",
62
- day: "2-digit",
63
- hour: "numeric",
64
- minute: "2-digit",
65
- timeZone: rules.timeZone
66
- });
67
- return {
68
- formatPeso: (amount) => pesoFmt.format(amount),
69
- formatDate: (iso) => dateFmt.format(new Date(iso)),
70
- formatDateTime: (iso) => dateTimeFmt.format(new Date(iso)),
71
- nowManilaLocalInput: () => {
72
- const parts = new Intl.DateTimeFormat("en-CA", {
73
- timeZone: rules.timeZone,
74
- year: "numeric",
75
- month: "2-digit",
76
- day: "2-digit",
77
- hour: "2-digit",
78
- minute: "2-digit",
79
- hour12: false
80
- }).formatToParts(/* @__PURE__ */ new Date());
81
- const get = (t) => parts.find((p) => p.type === t)?.value ?? "00";
82
- return `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}`;
83
- }
84
- };
85
- }
86
- function fullName(v) {
87
- return [v.firstName, v.middleName, v.lastName].filter(Boolean).join(" ");
88
- }
89
- function formalName(v) {
90
- const given = [v.firstName, v.middleName].filter(Boolean).join(" ");
91
- return `${v.lastName}, ${given}`;
92
- }
93
- function formatAddress(v) {
94
- return [v.street, v.barangay, v.cityMunicipality, v.province].map((p) => p?.trim()).filter(Boolean).join(", ");
95
- }
96
- function addDays(date, days) {
97
- const d = new Date(date);
98
- d.setDate(d.getDate() + days);
99
- return d;
100
- }
101
- function localToManilaISO(local) {
102
- if (!local) return (/* @__PURE__ */ new Date()).toISOString();
103
- const withSeconds = local.length === 16 ? `${local}:00` : local;
104
- return (/* @__PURE__ */ new Date(`${withSeconds}+08:00`)).toISOString();
105
- }
106
-
107
- // ../ovr-core/src/enrich.ts
108
- var round2 = (n) => Math.round(n * 100) / 100;
109
- function enrich(rec, asOf, ratePerMonth) {
110
- if (rec.paymentStatus === "PAID") {
111
- const paid = rec.payment?.amount ?? rec.basicFinesTotal;
112
- return {
113
- ...rec,
114
- status: "PAID",
115
- penaltyAmount: Math.max(0, round2(paid - rec.basicFinesTotal)),
116
- totalAmountDue: 0
117
- };
118
- }
119
- const { penalty, total } = computeCharges({
120
- basicFines: rec.basicFinesTotal,
121
- apprehendedAt: new Date(rec.apprehendedAt),
122
- asOf,
123
- dueDate: new Date(rec.dueDate),
124
- ratePerMonth
125
- });
126
- if (rec.paymentStatus === "CONTESTED") {
127
- return { ...rec, status: "CONTESTED", penaltyAmount: penalty, totalAmountDue: total };
128
- }
129
- const overdue = asOf.getTime() > new Date(rec.dueDate).getTime();
130
- return {
131
- ...rec,
132
- status: overdue ? "OVERDUE" : "OUTSTANDING",
133
- penaltyAmount: penalty,
134
- totalAmountDue: total
135
- };
136
- }
137
-
138
- // ../ovr-core/src/preview.ts
139
- var PREVIEW_PLACEHOLDER = "\u2014 assigned on confirm \u2014";
140
- function toPreviewTicket(draft, now, rules) {
141
- const basicFinesTotal = draft.violations.reduce(
142
- (s, v) => s + v.item.basicFine,
143
- 0
144
- );
145
- const apprehendedAt = draft.apprehendedAtISO || now.toISOString();
146
- const dueDate = addDays(now, rules.dueWindowDays);
147
- const { penalty, total } = computeCharges({
148
- basicFines: basicFinesTotal,
149
- apprehendedAt: new Date(apprehendedAt),
150
- asOf: now,
151
- dueDate,
152
- ratePerMonth: rules.surchargeRatePerMonth
153
- });
154
- const officer = draft.officer ?? { id: "", name: PREVIEW_PLACEHOLDER, office: "" };
155
- return {
156
- ovrTicketNo: PREVIEW_PLACEHOLDER,
157
- orderOfPaymentNo: PREVIEW_PLACEHOLDER,
158
- billNo: PREVIEW_PLACEHOLDER,
159
- violator: draft.violator,
160
- apprehendedAt,
161
- placeOfViolation: draft.placeOfViolation,
162
- officer,
163
- violations: draft.violations.map((v) => ({
164
- catalogCode: v.item.code,
165
- title: v.item.title,
166
- basicFine: v.item.basicFine,
167
- details: v.details?.trim() || void 0
168
- })),
169
- remarks: draft.remarks,
170
- assessedAt: now.toISOString(),
171
- dueDate: dueDate.toISOString(),
172
- basicFinesTotal,
173
- paymentStatus: "UNPAID",
174
- status: "OUTSTANDING",
175
- penaltyAmount: penalty,
176
- totalAmountDue: total,
177
- createdAt: now.toISOString()
178
- };
179
- }
180
-
181
- export { PREVIEW_PLACEHOLDER, addDays, computeCharges, createFormatters, enrich, formalName, formatAddress, fullName, localToManilaISO, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket };
@@ -1,129 +0,0 @@
1
- /**
2
- * Base (neutral) copy dictionary for OVR — the single source of user-facing
3
- * strings, config-agnostic. The `{municipality}` token is resolved at read time
4
- * by `mergeCopy()` against the active LGU's config (so this stays free of any app
5
- * import). A per-LGU app supplies overrides; a Tagalog `baseCopyTl` can drop in
6
- * later behind the same token/merge structure.
7
- */
8
- declare const baseCopy: {
9
- readonly app: {
10
- readonly name: "e-OVR";
11
- readonly tagline: "Online Ordinance Violation Receipt";
12
- readonly description: "Issue, look up, and settle traffic and ordinance violation tickets online.";
13
- };
14
- readonly common: {
15
- readonly search: "Search";
16
- readonly clear: "Clear";
17
- readonly cancel: "Cancel";
18
- readonly confirm: "Confirm";
19
- readonly back: "Back";
20
- readonly print: "Print";
21
- readonly payNow: "Pay now";
22
- readonly payFine: "Pay fine";
23
- readonly loading: "Loading…";
24
- readonly notFound: "Not found";
25
- readonly amountDue: "Amount due";
26
- readonly totalDue: "Total amount due";
27
- readonly dueDate: "Due date";
28
- readonly status: "Status";
29
- };
30
- readonly landing: {
31
- readonly heroTitle: "Settle violations online — fast, clear, and official.";
32
- readonly heroSubtitle: "The {municipality}'s modern portal for traffic and ordinance violation receipts.";
33
- readonly citizenCardTitle: "I have a ticket";
34
- readonly citizenCardBody: "Look up your Ordinance Violation Receipt, review the details, and pay your fine online.";
35
- readonly citizenCardCta: "Find my ticket";
36
- readonly adminCardTitle: "I'm an enforcer";
37
- readonly adminCardBody: "Issue a violation ticket on the spot and manage issued receipts.";
38
- readonly adminCardCta: "Open enforcer portal";
39
- };
40
- readonly citizen: {
41
- readonly searchTitle: "OVR Ticket Search";
42
- readonly searchHelp: "Enter your OVR ticket number and last name to retrieve your violation record.";
43
- readonly ticketNoLabel: "OVR Ticket No.";
44
- readonly lastNameLabel: "Last name";
45
- readonly euaLabel: "I accept the End-User Agreement";
46
- readonly searchCta: "Search ticket";
47
- readonly notFoundTitle: "No matching ticket";
48
- readonly notFoundBody: "We couldn't find a ticket with that number and last name. Check your details and try again.";
49
- readonly home: {
50
- readonly title: "Find and settle your violation receipt";
51
- readonly subtitle: "Enter your OVR ticket number and last name to view your Order of Payment and pay online.";
52
- readonly searchCta: "Find my ticket";
53
- readonly step1Title: "Find your ticket";
54
- readonly step1Body: "Search using your OVR ticket number and last name.";
55
- readonly step2Title: "Review the details";
56
- readonly step2Body: "Check the violation, the basic fines, and any surcharge.";
57
- readonly step3Title: "Pay online";
58
- readonly step3Body: "Settle via GCash, Maya, or Landbank and keep your receipt.";
59
- };
60
- readonly ticket: {
61
- readonly orderOfPayment: "Order of Payment";
62
- readonly violationDetails: "Violation details";
63
- readonly reminders: "Reminders & end-user notice";
64
- readonly payFine: "Pay fine";
65
- readonly printReceipt: "Print receipt";
66
- readonly viewReceipt: "View / print receipt";
67
- readonly backToSearch: "Back to search";
68
- readonly alreadyPaid: "This ticket has already been paid.";
69
- readonly history: {
70
- readonly title: "Violation history";
71
- readonly subtitle: "All OVR tickets on record under this name.";
72
- readonly current: "Current";
73
- readonly empty: "No other violation records found under this name.";
74
- readonly colTicketNo: "OVR Ticket No.";
75
- readonly colDate: "Apprehended";
76
- readonly colViolations: "Violation(s)";
77
- readonly colStatus: "Status";
78
- readonly colAmount: "Amount";
79
- };
80
- };
81
- readonly pay: {
82
- readonly title: "Pay your fine";
83
- readonly subtitle: "Choose a payment method to settle this violation.";
84
- readonly choose: "Choose payment method";
85
- readonly payNow: "Pay";
86
- readonly processing: "Processing payment…";
87
- readonly secure: "Simulated checkout — no real payment is processed.";
88
- readonly success: "Payment successful — your receipt is ready.";
89
- readonly qr: {
90
- readonly title: "Scan to pay";
91
- readonly instruction: "Open your e-wallet app, scan this QR, then tap below once your payment is complete.";
92
- readonly placeholderNote: "Demo placeholder — the official merchant QR will appear here once the payment gateway is live.";
93
- readonly amountToPay: "Amount to pay";
94
- readonly confirm: "I've completed payment";
95
- readonly cancel: "Cancel";
96
- };
97
- };
98
- };
99
- readonly admin: {
100
- readonly portal: "Enforcer Portal";
101
- readonly dashboard: "Dashboard";
102
- readonly tickets: "Tickets";
103
- readonly issueTicket: "Issue ticket";
104
- readonly newTicketTitle: "Issue Violation Ticket";
105
- readonly sync: {
106
- readonly offline: "Offline";
107
- readonly syncing: "Syncing…";
108
- readonly synced: "Synced";
109
- readonly pending: "pending sync";
110
- readonly notSynced: "Not synced";
111
- readonly willSync: "Saved on this device — will sync when back online.";
112
- };
113
- };
114
- };
115
-
116
- type DeepMutable<T> = T extends string ? string : {
117
- -readonly [K in keyof T]: DeepMutable<T[K]>;
118
- };
119
- /** Deep-mutable mirror of the base shape (leaves widen to string). */
120
- type Dictionary = {
121
- [K in keyof typeof baseCopy]: DeepMutable<(typeof baseCopy)[K]>;
122
- };
123
- type DeepPartial<T> = T extends string ? T : {
124
- [K in keyof T]?: DeepPartial<T[K]>;
125
- };
126
- /** Per-LGU partial override (any subtree, any depth). NOT shallow Partial. */
127
- type CopyOverrides = DeepPartial<Dictionary>;
128
-
129
- export { type CopyOverrides as C, type Dictionary as D, baseCopy as b };