@gelabs/ovr 0.4.3 → 0.4.4

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 (75) hide show
  1. package/dist/accounts.d.ts +52 -0
  2. package/dist/accounts.js +13 -0
  3. package/dist/audit.d.ts +13 -0
  4. package/dist/audit.js +11 -0
  5. package/dist/auth-auth.d.ts +1 -1
  6. package/dist/auth.d.ts +1 -1
  7. package/dist/chunk-4EDMZRGS.js +98 -0
  8. package/dist/chunk-4JMHQKMK.js +268 -0
  9. package/dist/chunk-6662IONX.js +69 -0
  10. package/dist/chunk-6WMPBAUH.js +18 -0
  11. package/dist/chunk-7GVZZWK3.js +74 -0
  12. package/dist/chunk-ACXED4UH.js +403 -0
  13. package/dist/chunk-BBTAG3FD.js +165 -0
  14. package/dist/chunk-HX7QT2FE.js +452 -0
  15. package/dist/chunk-LC3S47FM.js +468 -0
  16. package/dist/chunk-NPTR7GFZ.js +432 -0
  17. package/dist/chunk-SLQRZBMR.js +66 -0
  18. package/dist/chunk-UBUXPJLN.js +29 -0
  19. package/dist/chunk-UN6Z4WGF.js +31 -0
  20. package/dist/chunk-YE3D2DYY.js +83 -0
  21. package/dist/citizen.d.ts +5 -0
  22. package/dist/citizen.js +12 -0
  23. package/dist/config.d.ts +2 -2
  24. package/dist/core-i18n.d.ts +1 -1
  25. package/dist/core.d.ts +2 -2
  26. package/dist/dashboard.d.ts +10 -0
  27. package/dist/dashboard.js +3 -0
  28. package/dist/data-mock-store.d.ts +1 -1
  29. package/dist/{format-C7MSwUHK.d.ts → format-VyCUfF8R.d.ts} +1 -1
  30. package/dist/generated/client/edge.js +2 -2
  31. package/dist/generated/client/index.js +2 -2
  32. package/dist/generated/client/wasm.js +2 -2
  33. package/dist/index.d.ts +2 -2
  34. package/dist/notifications.d.ts +6 -0
  35. package/dist/notifications.js +11 -0
  36. package/dist/officers.d.ts +27 -0
  37. package/dist/officers.js +11 -0
  38. package/dist/offline.d.ts +1 -1
  39. package/dist/payments.d.ts +15 -0
  40. package/dist/payments.js +11 -0
  41. package/dist/roles.d.ts +37 -0
  42. package/dist/roles.js +12 -0
  43. package/dist/runtime.d.ts +1 -1
  44. package/dist/tickets.d.ts +6 -0
  45. package/dist/tickets.js +24 -0
  46. package/dist/ui-components-admin/accounts-manager.d.ts +3 -52
  47. package/dist/ui-components-admin/accounts-manager.js +11 -468
  48. package/dist/ui-components-admin/admin-nav.js +9 -81
  49. package/dist/ui-components-admin/issuance-form.js +15 -432
  50. package/dist/ui-components-admin/logs-viewer.d.ts +3 -13
  51. package/dist/ui-components-admin/logs-viewer.js +8 -98
  52. package/dist/ui-components-admin/notifications-list.js +6 -66
  53. package/dist/ui-components-admin/officers-manager.d.ts +3 -27
  54. package/dist/ui-components-admin/officers-manager.js +9 -268
  55. package/dist/ui-components-admin/roles-manager.d.ts +3 -37
  56. package/dist/ui-components-admin/roles-manager.js +10 -403
  57. package/dist/ui-components-admin/stat-card.d.ts +2 -10
  58. package/dist/ui-components-admin/stat-card.js +3 -29
  59. package/dist/ui-components-admin/ticket-preview.js +2 -2
  60. package/dist/ui-components-admin/tickets-table.js +7 -69
  61. package/dist/ui-components-admin/violations-manager.js +13 -452
  62. package/dist/ui-components-citizen/citizen-nav.js +3 -31
  63. package/dist/ui-components-citizen/payment-form.d.ts +3 -14
  64. package/dist/ui-components-citizen/payment-form.js +9 -165
  65. package/dist/ui-components-citizen/payment-qr-dialog.js +2 -2
  66. package/dist/ui-components-citizen/ticket-not-found.js +4 -18
  67. package/dist/ui-components-citizen/violation-history-table.js +6 -74
  68. package/dist/ui-config.d.ts +2 -2
  69. package/dist/ui-server.d.ts +2 -2
  70. package/dist/violations.d.ts +3 -0
  71. package/dist/violations.js +14 -0
  72. package/package.json +42 -2
  73. package/dist/{chunk-ZUMEOZ22.js → chunk-JTSTNZAB.js} +1 -1
  74. package/dist/{chunk-TLG4C2XI.js → chunk-QCAURREW.js} +1 -1
  75. package/dist/{schema-CdsFQxIg.d.ts → schema-BUhh_mKX.d.ts} +108 -108
@@ -0,0 +1,432 @@
1
+ import { Skeleton } from './chunk-EGKFELO3.js';
2
+ import { TicketPreview } from './chunk-JTSTNZAB.js';
3
+ import { Textarea } from './chunk-QCRVT2SS.js';
4
+ import { useIdentity } from './chunk-YGYA7KEG.js';
5
+ import { Alert, AlertDescription } from './chunk-EYFZWQ4J.js';
6
+ import { Checkbox } from './chunk-BBQBKQA4.js';
7
+ import { Label } from './chunk-XQTVSNHC.js';
8
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from './chunk-M35R6JLA.js';
9
+ import { Input } from './chunk-K3KIBHJF.js';
10
+ import { Button } from './chunk-I4WDVYHX.js';
11
+ import { useFormatters, useOvrConfig } from './chunk-TJSNVTVB.js';
12
+ import { Card, CardHeader, CardTitle, CardContent, CardDescription } from './chunk-SETIN6XP.js';
13
+ import { cn } from './chunk-77QBZC7J.js';
14
+ import { localToManilaISO, toPreviewTicket, listProvinces, citiesOfProvince } from './chunk-BI4EGLPG.js';
15
+ import * as React from 'react';
16
+ import { toast } from 'sonner';
17
+ import { FilePlus2, TriangleAlert, Loader2 } from 'lucide-react';
18
+ import { jsxs, jsx } from 'react/jsx-runtime';
19
+
20
+ var fieldClass = "h-9 w-full rounded-lg border border-input bg-transparent px-2.5 text-sm shadow-xs outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:opacity-50 dark:bg-input/30";
21
+ function TextField({
22
+ id,
23
+ label,
24
+ value,
25
+ onChange,
26
+ placeholder,
27
+ required,
28
+ type = "text"
29
+ }) {
30
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
31
+ /* @__PURE__ */ jsxs(Label, { htmlFor: id, children: [
32
+ label,
33
+ required ? /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" }) : null
34
+ ] }),
35
+ /* @__PURE__ */ jsx(
36
+ Input,
37
+ {
38
+ id,
39
+ type,
40
+ value,
41
+ onChange: (e) => onChange(e.target.value),
42
+ placeholder,
43
+ autoComplete: "off"
44
+ }
45
+ )
46
+ ] });
47
+ }
48
+ function ViolationRow({
49
+ item,
50
+ checked,
51
+ details,
52
+ onToggle,
53
+ onDetails,
54
+ formatPeso
55
+ }) {
56
+ return /* @__PURE__ */ jsxs(
57
+ "div",
58
+ {
59
+ className: cn(
60
+ "rounded-lg border p-3 transition-colors",
61
+ checked ? "border-primary/40 bg-primary/5" : "hover:bg-muted/40"
62
+ ),
63
+ children: [
64
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
65
+ /* @__PURE__ */ jsx(
66
+ Checkbox,
67
+ {
68
+ id: `v-${item.code}`,
69
+ checked,
70
+ onCheckedChange: (v) => onToggle(v === true),
71
+ className: "mt-0.5"
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxs("label", { htmlFor: `v-${item.code}`, className: "flex-1 cursor-pointer", children: [
75
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-2", children: [
76
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: item.title }),
77
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-medium tabular-nums", children: formatPeso(item.basicFine) })
78
+ ] }),
79
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: item.code })
80
+ ] })
81
+ ] }),
82
+ checked ? /* @__PURE__ */ jsx(
83
+ Textarea,
84
+ {
85
+ value: details,
86
+ onChange: (e) => onDetails(e.target.value),
87
+ placeholder: "Add details \u2014 location, circumstances, plate, etc.",
88
+ rows: 2,
89
+ className: "mt-2.5"
90
+ }
91
+ ) : null
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ function IssuanceForm({
97
+ catalog,
98
+ officers,
99
+ createAction
100
+ }) {
101
+ const { formatPeso, nowManilaLocalInput } = useFormatters();
102
+ const { municipality, rules } = useOvrConfig();
103
+ const identity = useIdentity();
104
+ const [firstName, setFirstName] = React.useState("");
105
+ const [middleName, setMiddleName] = React.useState("");
106
+ const [lastName, setLastName] = React.useState("");
107
+ const [street, setStreet] = React.useState("");
108
+ const [barangay, setBarangay] = React.useState("");
109
+ const [cityMunicipality, setCityMunicipality] = React.useState(
110
+ municipality.shortName
111
+ );
112
+ const [province, setProvince] = React.useState(municipality.province);
113
+ const [licenseNumber, setLicenseNumber] = React.useState("");
114
+ const [plateNumber, setPlateNumber] = React.useState("");
115
+ const [contactNo, setContactNo] = React.useState("");
116
+ const [apprehendedLocal, setApprehendedLocal] = React.useState("");
117
+ const [placeOfViolation, setPlaceOfViolation] = React.useState("");
118
+ const [officerId, setOfficerId] = React.useState(officers[0]?.id ?? "");
119
+ const [selected, setSelected] = React.useState({});
120
+ const [remarks, setRemarks] = React.useState("");
121
+ const [issuedBy, setIssuedBy] = React.useState("");
122
+ const [mounted, setMounted] = React.useState(false);
123
+ const [open, setOpen] = React.useState(false);
124
+ const [error, setError] = React.useState(null);
125
+ const [isPending, startTransition] = React.useTransition();
126
+ React.useEffect(() => {
127
+ setMounted(true);
128
+ setApprehendedLocal(nowManilaLocalInput());
129
+ }, [nowManilaLocalInput]);
130
+ function toggle(code, checked) {
131
+ setSelected((prev) => {
132
+ const next = { ...prev };
133
+ if (checked) next[code] = next[code] ?? "";
134
+ else delete next[code];
135
+ return next;
136
+ });
137
+ }
138
+ const violator = {
139
+ firstName,
140
+ middleName: middleName || void 0,
141
+ lastName,
142
+ street,
143
+ barangay: barangay || void 0,
144
+ cityMunicipality,
145
+ province,
146
+ licenseNumber,
147
+ plateNumber: plateNumber || void 0,
148
+ contactNo: contactNo || void 0
149
+ };
150
+ const chosen = catalog.filter((c) => c.code in selected);
151
+ const total = chosen.reduce((s, c) => s + c.basicFine, 0);
152
+ const draft = {
153
+ violator,
154
+ apprehendedAtISO: apprehendedLocal ? localToManilaISO(apprehendedLocal) : "",
155
+ placeOfViolation: placeOfViolation || void 0,
156
+ officer: officers.find((o) => o.id === officerId),
157
+ violations: chosen.map((c) => ({ item: c, details: selected[c.code] })),
158
+ remarks: remarks || void 0
159
+ };
160
+ const preview = toPreviewTicket(draft, /* @__PURE__ */ new Date(), rules);
161
+ const valid = !!firstName.trim() && !!lastName.trim() && !!street.trim() && !!cityMunicipality.trim() && !!province.trim() && !!licenseNumber.trim() && !!apprehendedLocal && !!officerId && chosen.length > 0;
162
+ function issue() {
163
+ setError(null);
164
+ const input = {
165
+ violator,
166
+ apprehendedAt: localToManilaISO(apprehendedLocal),
167
+ placeOfViolation: placeOfViolation || void 0,
168
+ officerId,
169
+ violations: Object.entries(selected).map(([catalogCode, details]) => ({
170
+ catalogCode,
171
+ details: details || void 0
172
+ })),
173
+ remarks: remarks || void 0,
174
+ issuedBy: issuedBy || void 0,
175
+ apprehendingEnforcerId: identity?.userId,
176
+ apprehendingEnforcerName: identity?.username
177
+ };
178
+ startTransition(async () => {
179
+ const res = await createAction(input);
180
+ if (res?.error) {
181
+ setError(res.error);
182
+ toast.error(res.error);
183
+ }
184
+ });
185
+ }
186
+ const traffic = catalog.filter((c) => c.category === "TRAFFIC");
187
+ const ordinance = catalog.filter((c) => c.category === "ORDINANCE");
188
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-6 lg:grid-cols-5", children: [
189
+ /* @__PURE__ */ jsxs("div", { className: "space-y-6 lg:col-span-3", children: [
190
+ /* @__PURE__ */ jsxs(Card, { children: [
191
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Violator details" }) }),
192
+ /* @__PURE__ */ jsxs(CardContent, { className: "grid gap-4 sm:grid-cols-2", children: [
193
+ /* @__PURE__ */ jsx(TextField, { id: "firstName", label: "First name", value: firstName, onChange: setFirstName, required: true }),
194
+ /* @__PURE__ */ jsx(TextField, { id: "middleName", label: "Middle name", value: middleName, onChange: setMiddleName }),
195
+ /* @__PURE__ */ jsx(TextField, { id: "lastName", label: "Last name", value: lastName, onChange: setLastName, required: true }),
196
+ /* @__PURE__ */ jsx(TextField, { id: "license", label: "License number", value: licenseNumber, onChange: setLicenseNumber, required: true, placeholder: "N03-12-345678" }),
197
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-2", children: /* @__PURE__ */ jsx(TextField, { id: "street", label: "Address line", value: street, onChange: setStreet, required: true, placeholder: "House no. / Purok / Zone / Street" }) }),
198
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-2", children: /* @__PURE__ */ jsx(TextField, { id: "barangay", label: "Barangay", value: barangay, onChange: setBarangay, placeholder: "e.g. Amungan" }) }),
199
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
200
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "province", children: [
201
+ "Province",
202
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
203
+ ] }),
204
+ /* @__PURE__ */ jsxs(
205
+ "select",
206
+ {
207
+ id: "province",
208
+ value: province,
209
+ onChange: (e) => {
210
+ const p = e.target.value;
211
+ setProvince(p);
212
+ if (cityMunicipality && !citiesOfProvince(p).includes(cityMunicipality)) {
213
+ setCityMunicipality("");
214
+ }
215
+ },
216
+ className: fieldClass,
217
+ children: [
218
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select province\u2026" }),
219
+ listProvinces().map((p) => /* @__PURE__ */ jsx("option", { value: p, children: p }, p))
220
+ ]
221
+ }
222
+ )
223
+ ] }),
224
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
225
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "city", children: [
226
+ "City / Municipality",
227
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
228
+ ] }),
229
+ /* @__PURE__ */ jsxs(
230
+ "select",
231
+ {
232
+ id: "city",
233
+ value: cityMunicipality,
234
+ onChange: (e) => setCityMunicipality(e.target.value),
235
+ disabled: !province,
236
+ className: fieldClass,
237
+ children: [
238
+ /* @__PURE__ */ jsx("option", { value: "", children: province ? "Select city / municipality\u2026" : "Select a province first" }),
239
+ citiesOfProvince(province).map((c) => /* @__PURE__ */ jsx("option", { value: c, children: c }, c))
240
+ ]
241
+ }
242
+ )
243
+ ] }),
244
+ /* @__PURE__ */ jsx(TextField, { id: "plate", label: "Plate number", value: plateNumber, onChange: setPlateNumber, placeholder: "ABC 1234" }),
245
+ /* @__PURE__ */ jsx(TextField, { id: "contact", label: "Contact number", value: contactNo, onChange: setContactNo, placeholder: "0917 555 0000" })
246
+ ] })
247
+ ] }),
248
+ /* @__PURE__ */ jsxs(Card, { children: [
249
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Apprehension details" }) }),
250
+ /* @__PURE__ */ jsxs(CardContent, { className: "grid gap-4 sm:grid-cols-2", children: [
251
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
252
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "apprehendedAt", children: [
253
+ "Date & time",
254
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
255
+ ] }),
256
+ /* @__PURE__ */ jsx(
257
+ Input,
258
+ {
259
+ id: "apprehendedAt",
260
+ type: "datetime-local",
261
+ value: apprehendedLocal,
262
+ onChange: (e) => setApprehendedLocal(e.target.value)
263
+ }
264
+ )
265
+ ] }),
266
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
267
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "officer", children: [
268
+ "Apprehending officer",
269
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
270
+ ] }),
271
+ /* @__PURE__ */ jsx(
272
+ "select",
273
+ {
274
+ id: "officer",
275
+ value: officerId,
276
+ onChange: (e) => setOfficerId(e.target.value),
277
+ className: fieldClass,
278
+ children: officers.map((o) => /* @__PURE__ */ jsxs("option", { value: o.id, children: [
279
+ o.name,
280
+ o.badgeNo ? ` (${o.badgeNo})` : ""
281
+ ] }, o.id))
282
+ }
283
+ )
284
+ ] }),
285
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
286
+ /* @__PURE__ */ jsx(Label, { htmlFor: "apprehendingEnforcer", children: "Apprehending enforcer" }),
287
+ /* @__PURE__ */ jsx(
288
+ "p",
289
+ {
290
+ id: "apprehendingEnforcer",
291
+ className: "flex h-9 items-center rounded-lg border border-input bg-muted/40 px-2.5 text-sm text-muted-foreground",
292
+ children: identity?.username ?? "\u2014"
293
+ }
294
+ ),
295
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Auto-recorded from your sign-in." })
296
+ ] }),
297
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-2", children: /* @__PURE__ */ jsx(TextField, { id: "place", label: "Place of violation", value: placeOfViolation, onChange: setPlaceOfViolation, placeholder: `Street / landmark, ${municipality.shortName}` }) })
298
+ ] })
299
+ ] }),
300
+ /* @__PURE__ */ jsxs(Card, { children: [
301
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
302
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Violations" }),
303
+ /* @__PURE__ */ jsx(CardDescription, { children: "Tick each violation and add details. Fines total automatically." })
304
+ ] }),
305
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
306
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
307
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Traffic" }),
308
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: traffic.map((c) => /* @__PURE__ */ jsx(
309
+ ViolationRow,
310
+ {
311
+ item: c,
312
+ checked: c.code in selected,
313
+ details: selected[c.code] ?? "",
314
+ onToggle: (v) => toggle(c.code, v),
315
+ onDetails: (v) => setSelected((p) => ({ ...p, [c.code]: v })),
316
+ formatPeso
317
+ },
318
+ c.code
319
+ )) })
320
+ ] }),
321
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
322
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Ordinance" }),
323
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: ordinance.map((c) => /* @__PURE__ */ jsx(
324
+ ViolationRow,
325
+ {
326
+ item: c,
327
+ checked: c.code in selected,
328
+ details: selected[c.code] ?? "",
329
+ onToggle: (v) => toggle(c.code, v),
330
+ onDetails: (v) => setSelected((p) => ({ ...p, [c.code]: v })),
331
+ formatPeso
332
+ },
333
+ c.code
334
+ )) })
335
+ ] })
336
+ ] })
337
+ ] }),
338
+ /* @__PURE__ */ jsxs(Card, { children: [
339
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: "Remarks" }) }),
340
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3", children: [
341
+ /* @__PURE__ */ jsx(
342
+ Textarea,
343
+ {
344
+ value: remarks,
345
+ onChange: (e) => setRemarks(e.target.value),
346
+ placeholder: "Optional notes for this ticket.",
347
+ rows: 3
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsx(
351
+ TextField,
352
+ {
353
+ id: "issuedBy",
354
+ label: "Issued by",
355
+ value: issuedBy,
356
+ onChange: setIssuedBy,
357
+ placeholder: "Optional \u2014 name shown on the receipt"
358
+ }
359
+ )
360
+ ] })
361
+ ] })
362
+ ] }),
363
+ /* @__PURE__ */ jsx("div", { className: "lg:col-span-2", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4 lg:sticky lg:top-6", children: [
364
+ mounted ? /* @__PURE__ */ jsx(TicketPreview, { ticket: preview }) : /* @__PURE__ */ jsx(Skeleton, { className: "h-80 w-full rounded-xl" }),
365
+ /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex items-center justify-between gap-3", children: [
366
+ /* @__PURE__ */ jsxs("div", { children: [
367
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Total fines" }),
368
+ /* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tabular-nums", children: formatPeso(total) })
369
+ ] }),
370
+ /* @__PURE__ */ jsxs(
371
+ Button,
372
+ {
373
+ type: "button",
374
+ disabled: !valid,
375
+ onClick: () => {
376
+ setError(null);
377
+ setOpen(true);
378
+ },
379
+ className: "h-10 gap-2 px-4",
380
+ children: [
381
+ /* @__PURE__ */ jsx(FilePlus2, { className: "size-4" }),
382
+ "Review & issue"
383
+ ]
384
+ }
385
+ )
386
+ ] }) }),
387
+ !valid ? /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-muted-foreground", children: "Fill the required fields and select at least one violation." }) : null
388
+ ] }) }),
389
+ /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
390
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
391
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Confirm & issue ticket" }),
392
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
393
+ "This issues an official OVR for",
394
+ " ",
395
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
396
+ firstName,
397
+ " ",
398
+ lastName
399
+ ] }),
400
+ " ",
401
+ "with ",
402
+ chosen.length,
403
+ " ",
404
+ chosen.length === 1 ? "violation" : "violations",
405
+ " totaling",
406
+ " ",
407
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatPeso(total) }),
408
+ ". It becomes immediately searchable by the citizen."
409
+ ] })
410
+ ] }),
411
+ error ? /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
412
+ /* @__PURE__ */ jsx(TriangleAlert, {}),
413
+ /* @__PURE__ */ jsx(AlertDescription, { children: error })
414
+ ] }) : null,
415
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
416
+ /* @__PURE__ */ jsx(
417
+ DialogClose,
418
+ {
419
+ render: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: isPending }),
420
+ children: "Cancel"
421
+ }
422
+ ),
423
+ /* @__PURE__ */ jsxs(Button, { onClick: issue, disabled: isPending, className: "gap-2", children: [
424
+ isPending ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(FilePlus2, { className: "size-4" }),
425
+ "Confirm & issue"
426
+ ] })
427
+ ] })
428
+ ] }) })
429
+ ] });
430
+ }
431
+
432
+ export { IssuanceForm };
@@ -0,0 +1,66 @@
1
+ import { useNotifications } from './chunk-YGYA7KEG.js';
2
+ import { usePagination, Pagination } from './chunk-6YFZLXFP.js';
3
+ import { Badge } from './chunk-55FQP2DO.js';
4
+ import { useCopy, useOvrConfig } from './chunk-TJSNVTVB.js';
5
+ import { Card, CardContent } from './chunk-SETIN6XP.js';
6
+ import Link from 'next/link';
7
+ import { AlertTriangle, Clock } from 'lucide-react';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
+
10
+ function NotificationsList() {
11
+ const items = useNotifications();
12
+ const t = useCopy().admin.notifications;
13
+ const { rules } = useOvrConfig();
14
+ const { pageItems, page, setPage, totalPages, from, to, total } = usePagination(items, 20);
15
+ const fmt = (iso) => new Date(iso).toLocaleString(rules.locale, {
16
+ timeZone: rules.timeZone,
17
+ year: "numeric",
18
+ month: "short",
19
+ day: "2-digit",
20
+ hour: "numeric",
21
+ minute: "2-digit"
22
+ });
23
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6 lg:p-8", children: [
24
+ /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
25
+ /* @__PURE__ */ jsx("h1", { className: "font-heading text-2xl font-semibold tracking-tight", children: t.title }),
26
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t.subtitle })
27
+ ] }),
28
+ /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "p-0", children: [
29
+ items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-8 text-center text-sm text-muted-foreground", children: t.empty }) : /* @__PURE__ */ jsx("ul", { className: "divide-y", children: pageItems.map((n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
30
+ Link,
31
+ {
32
+ href: n.href,
33
+ className: "flex items-center gap-3 px-4 py-3 transition-colors hover:bg-muted/50",
34
+ children: [
35
+ n.type === "overdue" ? /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4 shrink-0 text-red-500" }) : /* @__PURE__ */ jsx(Clock, { className: "size-4 shrink-0 text-amber-500" }),
36
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
37
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: n.name }),
38
+ /* @__PURE__ */ jsx("span", { className: "block font-mono text-xs text-muted-foreground", children: n.ovrTicketNo })
39
+ ] }),
40
+ /* @__PURE__ */ jsx(
41
+ Badge,
42
+ {
43
+ variant: n.type === "overdue" ? "destructive" : "secondary",
44
+ children: n.type === "overdue" ? t.overdue : t.outstanding
45
+ }
46
+ ),
47
+ /* @__PURE__ */ jsx("span", { className: "hidden whitespace-nowrap text-xs text-muted-foreground sm:block", children: fmt(n.at) })
48
+ ]
49
+ }
50
+ ) }, n.id)) }),
51
+ /* @__PURE__ */ jsx(
52
+ Pagination,
53
+ {
54
+ page,
55
+ totalPages,
56
+ from,
57
+ to,
58
+ total,
59
+ onPage: setPage
60
+ }
61
+ )
62
+ ] }) })
63
+ ] });
64
+ }
65
+
66
+ export { NotificationsList };
@@ -0,0 +1,29 @@
1
+ import { Card, CardContent } from './chunk-SETIN6XP.js';
2
+ import { cn } from './chunk-77QBZC7J.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ function StatCard({
6
+ label,
7
+ value,
8
+ icon,
9
+ accent = "bg-muted text-muted-foreground"
10
+ }) {
11
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex items-center gap-3", children: [
12
+ /* @__PURE__ */ jsx(
13
+ "div",
14
+ {
15
+ className: cn(
16
+ "flex size-10 shrink-0 items-center justify-center rounded-lg",
17
+ accent
18
+ ),
19
+ children: icon
20
+ }
21
+ ),
22
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
23
+ /* @__PURE__ */ jsx("p", { className: "truncate text-2xl font-semibold tabular-nums", children: value }),
24
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: label })
25
+ ] })
26
+ ] }) });
27
+ }
28
+
29
+ export { StatCard };
@@ -0,0 +1,31 @@
1
+ import { useCopy } from './chunk-TJSNVTVB.js';
2
+ import { cn } from './chunk-77QBZC7J.js';
3
+ import Link from 'next/link';
4
+ import { usePathname } from 'next/navigation';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ function CitizenNav() {
8
+ const pathname = usePathname();
9
+ const copy = useCopy();
10
+ const items = [
11
+ { href: "/citizen", label: "Home", exact: true },
12
+ { href: "/citizen/search", label: copy.citizen.home.searchCta }
13
+ ];
14
+ return /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1", children: items.map((it) => {
15
+ const active = it.exact ? pathname === it.href : pathname.startsWith(it.href);
16
+ return /* @__PURE__ */ jsx(
17
+ Link,
18
+ {
19
+ href: it.href,
20
+ className: cn(
21
+ "rounded-md px-2.5 py-1.5 text-sm font-medium transition-colors sm:px-3",
22
+ active ? "bg-white/15 text-gov-foreground" : "text-gov-foreground/80 hover:bg-white/10 hover:text-gov-foreground"
23
+ ),
24
+ children: it.label
25
+ },
26
+ it.href
27
+ );
28
+ }) });
29
+ }
30
+
31
+ export { CitizenNav };
@@ -0,0 +1,83 @@
1
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuItem } from './chunk-V7VQVDWS.js';
2
+ import { useNotificationsState } from './chunk-YGYA7KEG.js';
3
+ import { useCopy } from './chunk-TJSNVTVB.js';
4
+ import { cn } from './chunk-77QBZC7J.js';
5
+ import Link from 'next/link';
6
+ import { Bell } from 'lucide-react';
7
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
+
9
+ var BELL_LIMIT = 6;
10
+ function NotificationBell() {
11
+ const { items, total, unseen, markSeen } = useNotificationsState();
12
+ const t = useCopy().admin.notifications;
13
+ const hasOverdue = items.some((n) => n.type === "overdue");
14
+ const top = items.slice(0, BELL_LIMIT);
15
+ return /* @__PURE__ */ jsxs(
16
+ DropdownMenu,
17
+ {
18
+ onOpenChange: (open) => {
19
+ if (open) markSeen();
20
+ },
21
+ children: [
22
+ /* @__PURE__ */ jsxs(
23
+ DropdownMenuTrigger,
24
+ {
25
+ render: /* @__PURE__ */ jsx(
26
+ "button",
27
+ {
28
+ type: "button",
29
+ "aria-label": t.title,
30
+ className: "relative flex items-center rounded-md px-2 py-1.5 text-sm font-medium text-gov-foreground/80 transition-colors hover:bg-white/10 hover:text-gov-foreground aria-expanded:bg-white/10 aria-expanded:text-gov-foreground"
31
+ }
32
+ ),
33
+ children: [
34
+ /* @__PURE__ */ jsx(Bell, { className: "size-4 shrink-0" }),
35
+ unseen ? /* @__PURE__ */ jsx(
36
+ "span",
37
+ {
38
+ className: cn(
39
+ "absolute -top-0.5 -right-0.5 flex min-w-4 items-center justify-center rounded-full px-1 text-[0.625rem] leading-4 font-semibold text-white",
40
+ hasOverdue ? "bg-red-500" : "bg-amber-500"
41
+ ),
42
+ children: total > 99 ? "99+" : total
43
+ }
44
+ ) : null
45
+ ]
46
+ }
47
+ ),
48
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "min-w-72", children: [
49
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-sm font-medium", children: t.title }),
50
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
51
+ items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-center text-sm text-muted-foreground", children: t.empty }) : /* @__PURE__ */ jsxs(Fragment, { children: [
52
+ top.map((n) => /* @__PURE__ */ jsxs(DropdownMenuItem, { render: /* @__PURE__ */ jsx(Link, { href: n.href }), children: [
53
+ /* @__PURE__ */ jsx(
54
+ "span",
55
+ {
56
+ className: cn(
57
+ "mt-1 size-2 shrink-0 rounded-full",
58
+ n.type === "overdue" ? "bg-red-500" : "bg-amber-500"
59
+ )
60
+ }
61
+ ),
62
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
63
+ /* @__PURE__ */ jsx("span", { className: "block font-mono text-xs", children: n.ovrTicketNo }),
64
+ /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
65
+ n.type === "overdue" ? t.overdue : t.outstanding,
66
+ " \xB7 ",
67
+ n.name
68
+ ] })
69
+ ] })
70
+ ] }, n.id)),
71
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
72
+ /* @__PURE__ */ jsx(DropdownMenuItem, { render: /* @__PURE__ */ jsx(Link, { href: "/admin/notifications" }), children: /* @__PURE__ */ jsxs("span", { className: "flex-1 text-center text-sm", children: [
73
+ t.viewAll,
74
+ total > top.length ? ` (${total})` : ""
75
+ ] }) })
76
+ ] })
77
+ ] })
78
+ ]
79
+ }
80
+ );
81
+ }
82
+
83
+ export { NotificationBell };
@@ -0,0 +1,5 @@
1
+ export { CitizenNav } from './ui-components-citizen/citizen-nav.js';
2
+ export { ViolationHistoryTable } from './ui-components-citizen/violation-history-table.js';
3
+ export { TicketNotFound } from './ui-components-citizen/ticket-not-found.js';
4
+ import 'react';
5
+ import './types.js';
@@ -0,0 +1,12 @@
1
+ export { CitizenNav } from './chunk-UN6Z4WGF.js';
2
+ export { TicketNotFound } from './chunk-6WMPBAUH.js';
3
+ export { ViolationHistoryTable } from './chunk-7GVZZWK3.js';
4
+ import './chunk-OE525ZER.js';
5
+ import './chunk-BVI5XDDA.js';
6
+ import './chunk-OWCGEEAZ.js';
7
+ import './chunk-55FQP2DO.js';
8
+ import './chunk-I4WDVYHX.js';
9
+ import './chunk-TJSNVTVB.js';
10
+ import './chunk-SETIN6XP.js';
11
+ import './chunk-77QBZC7J.js';
12
+ import './chunk-BI4EGLPG.js';
package/dist/config.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as OvrConfigInput } from './schema-CdsFQxIg.js';
2
- export { D as DemoAdminConfig, M as MunicipalityConfig, O as OfficeConfig, a as OfficesConfig, b as OvrConfig, P as PaymentMethodConfig, R as RulesConfig, d as demoAdminConfigSchema, m as municipalityConfigSchema, o as officeConfigSchema, e as officesConfigSchema, f as ovrConfigSchema, p as paymentMethodConfigSchema, r as rulesConfigSchema } from './schema-CdsFQxIg.js';
1
+ import { c as OvrConfigInput } from './schema-BUhh_mKX.js';
2
+ export { D as DemoAdminConfig, M as MunicipalityConfig, O as OfficeConfig, a as OfficesConfig, b as OvrConfig, P as PaymentMethodConfig, R as RulesConfig, d as demoAdminConfigSchema, m as municipalityConfigSchema, o as officeConfigSchema, e as officesConfigSchema, f as ovrConfigSchema, p as paymentMethodConfigSchema, r as rulesConfigSchema } from './schema-BUhh_mKX.js';
3
3
  import 'zod';
4
4
 
5
5
  /**