@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,406 @@
1
+ "use client";
2
+ import { Checkbox } from '../chunk-BBQBKQA4.js';
3
+ import { Card, CardContent } from '../chunk-SETIN6XP.js';
4
+ import { Badge } from '../chunk-55FQP2DO.js';
5
+ import { Label } from '../chunk-XQTVSNHC.js';
6
+ import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '../chunk-M35R6JLA.js';
7
+ import { Input } from '../chunk-K3KIBHJF.js';
8
+ import { Button } from '../chunk-I4WDVYHX.js';
9
+ import { useCopy } from '../chunk-TJSNVTVB.js';
10
+ import '../chunk-77QBZC7J.js';
11
+ import { PERMISSION_CATALOG, SUPER_ADMIN_LOCKED_PERMISSIONS } from '../chunk-IS3THKTE.js';
12
+ import '../chunk-BI4EGLPG.js';
13
+ import * as React from 'react';
14
+ import { toast } from 'sonner';
15
+ import { useRouter } from 'next/navigation';
16
+ import { ShieldCheck, SlidersHorizontal, Loader2, Trash2, Plus, Lock } from 'lucide-react';
17
+ import { jsxs, jsx } from 'react/jsx-runtime';
18
+
19
+ function RolesManager({
20
+ roles,
21
+ usage = {},
22
+ createAction,
23
+ updateAction,
24
+ deleteAction
25
+ }) {
26
+ const t = useCopy().admin.rolesPage;
27
+ const router = useRouter();
28
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6 lg:p-8", children: [
29
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between gap-3", children: [
30
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
31
+ /* @__PURE__ */ jsx("h1", { className: "font-heading text-2xl font-semibold tracking-tight", children: t.title }),
32
+ /* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm text-muted-foreground", children: t.subtitle })
33
+ ] }),
34
+ /* @__PURE__ */ jsx(
35
+ NewRoleDialog,
36
+ {
37
+ createAction,
38
+ onChanged: () => router.refresh()
39
+ }
40
+ )
41
+ ] }),
42
+ roles.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-xl border bg-card p-8 text-center text-sm text-muted-foreground", children: t.empty }) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: roles.map((role) => /* @__PURE__ */ jsx(
43
+ RoleCard,
44
+ {
45
+ role,
46
+ inUse: usage[role.name] ?? 0,
47
+ updateAction,
48
+ deleteAction,
49
+ onChanged: () => router.refresh()
50
+ },
51
+ role.name
52
+ )) })
53
+ ] });
54
+ }
55
+ function PermissionChecklist({
56
+ selected,
57
+ onToggle,
58
+ locked
59
+ }) {
60
+ return /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: PERMISSION_CATALOG.map((perm) => {
61
+ const isLocked = locked?.(perm.key) ?? false;
62
+ const checked = selected.has(perm.key) || isLocked;
63
+ return /* @__PURE__ */ jsxs(
64
+ "label",
65
+ {
66
+ className: "flex items-start gap-2.5 rounded-lg border p-2.5",
67
+ children: [
68
+ /* @__PURE__ */ jsx(
69
+ Checkbox,
70
+ {
71
+ checked,
72
+ disabled: isLocked,
73
+ onCheckedChange: (v) => onToggle(perm.key, v === true),
74
+ className: "mt-0.5"
75
+ }
76
+ ),
77
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
78
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium", children: [
79
+ perm.label,
80
+ isLocked ? /* @__PURE__ */ jsx(Lock, { className: "size-3 text-muted-foreground" }) : null
81
+ ] }),
82
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: perm.description })
83
+ ] })
84
+ ]
85
+ },
86
+ perm.key
87
+ );
88
+ }) });
89
+ }
90
+ function RoleCard({
91
+ role,
92
+ inUse,
93
+ updateAction,
94
+ deleteAction,
95
+ onChanged
96
+ }) {
97
+ const t = useCopy().admin.rolesPage;
98
+ const grantedLabels = PERMISSION_CATALOG.filter(
99
+ (p) => role.permissions.includes(p.key)
100
+ ).map((p) => p.label);
101
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex items-center justify-between gap-3 py-4", children: [
102
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
103
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
104
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "size-4 shrink-0 text-muted-foreground" }),
105
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: role.label }),
106
+ /* @__PURE__ */ jsx(Badge, { variant: role.isSystem ? "secondary" : "outline", children: role.isSystem ? t.system : t.custom }),
107
+ inUse > 0 ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
108
+ inUse,
109
+ " ",
110
+ t.inUseSuffix
111
+ ] }) : null
112
+ ] }),
113
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: grantedLabels.length ? grantedLabels.join(" \xB7 ") : t.noPermissions })
114
+ ] }),
115
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [
116
+ /* @__PURE__ */ jsx(
117
+ EditRolePermissionsDialog,
118
+ {
119
+ role,
120
+ updateAction,
121
+ onChanged
122
+ }
123
+ ),
124
+ !role.isSystem ? /* @__PURE__ */ jsx(
125
+ DeleteRoleButton,
126
+ {
127
+ role,
128
+ inUse,
129
+ deleteAction,
130
+ onChanged
131
+ }
132
+ ) : null
133
+ ] })
134
+ ] }) });
135
+ }
136
+ function EditRolePermissionsDialog({
137
+ role,
138
+ updateAction,
139
+ onChanged
140
+ }) {
141
+ const t = useCopy().admin.rolesPage;
142
+ const [open, setOpen] = React.useState(false);
143
+ const [label, setLabel] = React.useState(role.label);
144
+ const [perms, setPerms] = React.useState(
145
+ () => new Set(role.permissions)
146
+ );
147
+ const [saving, setSaving] = React.useState(false);
148
+ const isLocked = (p) => role.name === "SUPER_ADMIN" && SUPER_ADMIN_LOCKED_PERMISSIONS.includes(p);
149
+ function toggle(p, checked) {
150
+ if (isLocked(p)) return;
151
+ setPerms((prev) => {
152
+ const next = new Set(prev);
153
+ if (checked) next.add(p);
154
+ else next.delete(p);
155
+ return next;
156
+ });
157
+ }
158
+ async function submit(e) {
159
+ e.preventDefault();
160
+ setSaving(true);
161
+ try {
162
+ const res = await updateAction(role.name, {
163
+ label: role.isSystem ? void 0 : label.trim(),
164
+ permissions: [...perms]
165
+ });
166
+ if (res?.error) {
167
+ toast.error(res.error);
168
+ return;
169
+ }
170
+ toast.success(`${t.saved}: ${role.isSystem ? role.label : label.trim()}`);
171
+ setOpen(false);
172
+ onChanged();
173
+ } finally {
174
+ setSaving(false);
175
+ }
176
+ }
177
+ return /* @__PURE__ */ jsxs(
178
+ Dialog,
179
+ {
180
+ open,
181
+ onOpenChange: (o) => {
182
+ if (o) {
183
+ setLabel(role.label);
184
+ setPerms(new Set(role.permissions));
185
+ }
186
+ setOpen(o);
187
+ },
188
+ children: [
189
+ /* @__PURE__ */ jsxs(
190
+ DialogTrigger,
191
+ {
192
+ render: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "gap-1.5" }),
193
+ children: [
194
+ /* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-3.5" }),
195
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.editPermissions })
196
+ ]
197
+ }
198
+ ),
199
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-lg", children: [
200
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
201
+ /* @__PURE__ */ jsx(DialogTitle, { children: role.label }),
202
+ /* @__PURE__ */ jsx(DialogDescription, { children: t.editPermissions })
203
+ ] }),
204
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-4", children: [
205
+ !role.isSystem ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
206
+ /* @__PURE__ */ jsx(Label, { htmlFor: `role-label-${role.name}`, children: t.roleName }),
207
+ /* @__PURE__ */ jsx(
208
+ Input,
209
+ {
210
+ id: `role-label-${role.name}`,
211
+ value: label,
212
+ onChange: (e) => setLabel(e.target.value),
213
+ autoComplete: "off"
214
+ }
215
+ )
216
+ ] }) : null,
217
+ /* @__PURE__ */ jsx(
218
+ PermissionChecklist,
219
+ {
220
+ selected: perms,
221
+ onToggle: toggle,
222
+ locked: isLocked
223
+ }
224
+ ),
225
+ role.name === "SUPER_ADMIN" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t.lockedHint }) : null,
226
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
227
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline" }), children: t.cancel }),
228
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: saving, className: "gap-1.5", children: [
229
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : null,
230
+ saving ? t.saving : t.save
231
+ ] })
232
+ ] })
233
+ ] })
234
+ ] })
235
+ ]
236
+ }
237
+ );
238
+ }
239
+ function DeleteRoleButton({
240
+ role,
241
+ inUse,
242
+ deleteAction,
243
+ onChanged
244
+ }) {
245
+ const t = useCopy().admin.rolesPage;
246
+ const [deleting, setDeleting] = React.useState(false);
247
+ async function remove() {
248
+ setDeleting(true);
249
+ try {
250
+ const res = await deleteAction(role.name);
251
+ if (res?.error) {
252
+ toast.error(res.error);
253
+ return;
254
+ }
255
+ toast.success(`${t.delete}: ${role.label}`);
256
+ onChanged();
257
+ } finally {
258
+ setDeleting(false);
259
+ }
260
+ }
261
+ return /* @__PURE__ */ jsxs(Dialog, { children: [
262
+ /* @__PURE__ */ jsxs(
263
+ DialogTrigger,
264
+ {
265
+ render: /* @__PURE__ */ jsx(
266
+ Button,
267
+ {
268
+ variant: "destructive",
269
+ size: "sm",
270
+ className: "gap-1.5",
271
+ disabled: inUse > 0
272
+ }
273
+ ),
274
+ children: [
275
+ /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" }),
276
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.delete })
277
+ ]
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm", children: [
281
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
282
+ /* @__PURE__ */ jsx(DialogTitle, { children: t.deleteConfirmTitle }),
283
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
284
+ role.label,
285
+ " \u2014 ",
286
+ t.deleteConfirmBody
287
+ ] })
288
+ ] }),
289
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
290
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { variant: "outline" }), children: t.cancel }),
291
+ /* @__PURE__ */ jsxs(
292
+ DialogClose,
293
+ {
294
+ render: /* @__PURE__ */ jsx(
295
+ Button,
296
+ {
297
+ variant: "destructive",
298
+ className: "gap-2",
299
+ disabled: deleting
300
+ }
301
+ ),
302
+ onClick: remove,
303
+ children: [
304
+ /* @__PURE__ */ jsx(Trash2, { className: "size-4" }),
305
+ t.delete
306
+ ]
307
+ }
308
+ )
309
+ ] })
310
+ ] })
311
+ ] });
312
+ }
313
+ function NewRoleDialog({
314
+ createAction,
315
+ onChanged
316
+ }) {
317
+ const t = useCopy().admin.rolesPage;
318
+ const [open, setOpen] = React.useState(false);
319
+ const [label, setLabel] = React.useState("");
320
+ const [perms, setPerms] = React.useState(() => /* @__PURE__ */ new Set());
321
+ const [submitting, setSubmitting] = React.useState(false);
322
+ function reset() {
323
+ setLabel("");
324
+ setPerms(/* @__PURE__ */ new Set());
325
+ }
326
+ function toggle(p, checked) {
327
+ setPerms((prev) => {
328
+ const next = new Set(prev);
329
+ if (checked) next.add(p);
330
+ else next.delete(p);
331
+ return next;
332
+ });
333
+ }
334
+ async function submit(e) {
335
+ e.preventDefault();
336
+ if (!label.trim()) return toast.error(`${t.roleName} is required.`);
337
+ setSubmitting(true);
338
+ try {
339
+ const res = await createAction({
340
+ label: label.trim(),
341
+ permissions: [...perms]
342
+ });
343
+ if (res?.error) {
344
+ toast.error(res.error);
345
+ return;
346
+ }
347
+ toast.success(`${t.create}: ${label.trim()}`);
348
+ reset();
349
+ setOpen(false);
350
+ onChanged();
351
+ } finally {
352
+ setSubmitting(false);
353
+ }
354
+ }
355
+ return /* @__PURE__ */ jsxs(
356
+ Dialog,
357
+ {
358
+ open,
359
+ onOpenChange: (o) => {
360
+ if (!o) reset();
361
+ setOpen(o);
362
+ },
363
+ children: [
364
+ /* @__PURE__ */ jsxs(DialogTrigger, { render: /* @__PURE__ */ jsx(Button, { className: "gap-1.5" }), children: [
365
+ /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
366
+ t.newRole
367
+ ] }),
368
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-lg", children: [
369
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
370
+ /* @__PURE__ */ jsx(DialogTitle, { children: t.newRole }),
371
+ /* @__PURE__ */ jsx(DialogDescription, { children: t.subtitle })
372
+ ] }),
373
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-4", children: [
374
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
375
+ /* @__PURE__ */ jsx(Label, { htmlFor: "role-label", children: t.roleName }),
376
+ /* @__PURE__ */ jsx(
377
+ Input,
378
+ {
379
+ id: "role-label",
380
+ value: label,
381
+ onChange: (e) => setLabel(e.target.value),
382
+ placeholder: t.roleNamePlaceholder,
383
+ autoComplete: "off",
384
+ autoFocus: true
385
+ }
386
+ )
387
+ ] }),
388
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
389
+ /* @__PURE__ */ jsx(Label, { children: t.permissions }),
390
+ /* @__PURE__ */ jsx(PermissionChecklist, { selected: perms, onToggle: toggle })
391
+ ] }),
392
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
393
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline" }), children: t.cancel }),
394
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: submitting, className: "gap-1.5", children: [
395
+ submitting ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
396
+ submitting ? t.creating : t.create
397
+ ] })
398
+ ] })
399
+ ] })
400
+ ] })
401
+ ]
402
+ }
403
+ );
404
+ }
405
+
406
+ export { RolesManager };
@@ -1,13 +1,13 @@
1
1
  "use client";
2
- export { TicketPreview } from '../chunk-GDOCD7LT.js';
3
- import '../chunk-JEYT63LE.js';
4
- import '../chunk-BIQ2J75Y.js';
2
+ export { TicketPreview } from '../chunk-ZUMEOZ22.js';
3
+ import '../chunk-IBZVIUNI.js';
4
+ import '../chunk-GLIK5BHP.js';
5
5
  import '../chunk-NSCIBSCW.js';
6
- import '../chunk-SETIN6XP.js';
7
6
  import '../chunk-OE525ZER.js';
7
+ import '../chunk-BVI5XDDA.js';
8
+ import '../chunk-SETIN6XP.js';
8
9
  import '../chunk-OWCGEEAZ.js';
9
10
  import '../chunk-55FQP2DO.js';
10
- import '../chunk-3KIDW4LT.js';
11
- import '../chunk-E2D7QT6N.js';
11
+ import '../chunk-TJSNVTVB.js';
12
12
  import '../chunk-77QBZC7J.js';
13
- import '../chunk-B634JHKZ.js';
13
+ import '../chunk-BI4EGLPG.js';
@@ -1,49 +1,72 @@
1
1
  "use client";
2
2
  import { StatusBadge } from '../chunk-OE525ZER.js';
3
+ import { Money } from '../chunk-BVI5XDDA.js';
4
+ import { usePagination, Pagination } from '../chunk-6YFZLXFP.js';
3
5
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
4
6
  import '../chunk-55FQP2DO.js';
5
- import { Money } from '../chunk-3KIDW4LT.js';
6
- import { useFormatters } from '../chunk-E2D7QT6N.js';
7
+ import '../chunk-I4WDVYHX.js';
8
+ import { useFormatters } from '../chunk-TJSNVTVB.js';
7
9
  import '../chunk-77QBZC7J.js';
8
- import { formalName } from '../chunk-B634JHKZ.js';
9
- import Link from 'next/link';
10
+ import { formalName } from '../chunk-BI4EGLPG.js';
11
+ import { useRouter } from 'next/navigation';
10
12
  import { ChevronRight } from 'lucide-react';
11
- import { jsx, jsxs } from 'react/jsx-runtime';
13
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
14
 
13
15
  function TicketsTable({ tickets }) {
14
16
  const { formatDate } = useFormatters();
17
+ const router = useRouter();
18
+ const { pageItems, page, setPage, totalPages, from, to, total } = usePagination(tickets, 15);
15
19
  if (tickets.length === 0) {
16
20
  return /* @__PURE__ */ jsx("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: "No tickets match this view." });
17
21
  }
18
- return /* @__PURE__ */ jsxs(Table, { children: [
19
- /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
20
- /* @__PURE__ */ jsx(TableHead, { children: "Ticket No." }),
21
- /* @__PURE__ */ jsx(TableHead, { children: "Violator" }),
22
- /* @__PURE__ */ jsx(TableHead, { className: "hidden sm:table-cell", children: "Apprehended" }),
23
- /* @__PURE__ */ jsx(TableHead, { children: "Status" }),
24
- /* @__PURE__ */ jsx(TableHead, { className: "text-right", children: "Amount" }),
25
- /* @__PURE__ */ jsx(TableHead, { className: "w-8" })
26
- ] }) }),
27
- /* @__PURE__ */ jsx(TableBody, { children: tickets.map((t) => {
28
- const href = `/admin/tickets/${encodeURIComponent(t.ovrTicketNo)}`;
29
- const amount = t.status === "PAID" ? t.payment?.amount ?? 0 : t.totalAmountDue;
30
- return /* @__PURE__ */ jsxs(TableRow, { children: [
31
- /* @__PURE__ */ jsx(TableCell, { className: "font-mono text-xs", children: /* @__PURE__ */ jsx(Link, { href, className: "hover:underline", children: t.ovrTicketNo }) }),
32
- /* @__PURE__ */ jsx(TableCell, { className: "font-medium", children: formalName(t.violator) }),
33
- /* @__PURE__ */ jsx(TableCell, { className: "hidden text-muted-foreground sm:table-cell", children: formatDate(t.apprehendedAt) }),
34
- /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(StatusBadge, { status: t.status }) }),
35
- /* @__PURE__ */ jsx(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx(Money, { value: amount }) }),
36
- /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
37
- Link,
22
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ /* @__PURE__ */ jsxs(Table, { children: [
24
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
25
+ /* @__PURE__ */ jsx(TableHead, { children: "Ticket No." }),
26
+ /* @__PURE__ */ jsx(TableHead, { children: "Violator" }),
27
+ /* @__PURE__ */ jsx(TableHead, { className: "hidden sm:table-cell", children: "Apprehended" }),
28
+ /* @__PURE__ */ jsx(TableHead, { children: "Status" }),
29
+ /* @__PURE__ */ jsx(TableHead, { className: "text-right", children: "Amount" }),
30
+ /* @__PURE__ */ jsx(TableHead, { className: "w-8" })
31
+ ] }) }),
32
+ /* @__PURE__ */ jsx(TableBody, { children: pageItems.map((t) => {
33
+ const href = `/admin/tickets/${encodeURIComponent(t.ovrTicketNo)}`;
34
+ const amount = t.status === "PAID" ? t.payment?.amount ?? 0 : t.totalAmountDue;
35
+ return /* @__PURE__ */ jsxs(
36
+ TableRow,
38
37
  {
39
- href,
40
- className: "text-muted-foreground hover:text-foreground",
41
- "aria-label": `Open ${t.ovrTicketNo}`,
42
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
43
- }
44
- ) })
45
- ] }, t.ovrTicketNo);
46
- }) })
38
+ onClick: () => router.push(href),
39
+ onKeyDown: (e) => {
40
+ if (e.key === "Enter") router.push(href);
41
+ },
42
+ role: "link",
43
+ tabIndex: 0,
44
+ "aria-label": `Open ticket ${t.ovrTicketNo}`,
45
+ className: "cursor-pointer",
46
+ children: [
47
+ /* @__PURE__ */ jsx(TableCell, { className: "font-mono text-xs", children: t.ovrTicketNo }),
48
+ /* @__PURE__ */ jsx(TableCell, { className: "font-medium", children: formalName(t.violator) }),
49
+ /* @__PURE__ */ jsx(TableCell, { className: "hidden text-muted-foreground sm:table-cell", children: formatDate(t.apprehendedAt) }),
50
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(StatusBadge, { status: t.status }) }),
51
+ /* @__PURE__ */ jsx(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx(Money, { value: amount }) }),
52
+ /* @__PURE__ */ jsx(TableCell, { className: "text-muted-foreground", children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" }) })
53
+ ]
54
+ },
55
+ t.ovrTicketNo
56
+ );
57
+ }) })
58
+ ] }),
59
+ /* @__PURE__ */ jsx(
60
+ Pagination,
61
+ {
62
+ page,
63
+ totalPages,
64
+ from,
65
+ to,
66
+ total,
67
+ onPage: setPage
68
+ }
69
+ )
47
70
  ] });
48
71
  }
49
72
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { useCopy } from '../chunk-E2D7QT6N.js';
2
+ import { useCopy } from '../chunk-TJSNVTVB.js';
3
3
  import { cn } from '../chunk-77QBZC7J.js';
4
- import '../chunk-B634JHKZ.js';
4
+ import '../chunk-BI4EGLPG.js';
5
5
  import Link from 'next/link';
6
6
  import { usePathname } from 'next/navigation';
7
7
  import { jsx } from 'react/jsx-runtime';
@@ -1,14 +1,14 @@
1
1
  "use client";
2
- import { PaymentQrDialog } from '../chunk-5Z2IAD5I.js';
3
- import { Separator } from '../chunk-NSCIBSCW.js';
2
+ import { PaymentQrDialog } from '../chunk-TLG4C2XI.js';
4
3
  import { Alert, AlertDescription } from '../chunk-EYFZWQ4J.js';
4
+ import { Separator } from '../chunk-NSCIBSCW.js';
5
+ import { Money } from '../chunk-BVI5XDDA.js';
5
6
  import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../chunk-SETIN6XP.js';
6
- import { Money } from '../chunk-3KIDW4LT.js';
7
7
  import '../chunk-M35R6JLA.js';
8
8
  import { Button } from '../chunk-I4WDVYHX.js';
9
- import { useOvrConfig, useCopy } from '../chunk-E2D7QT6N.js';
9
+ import { useOvrConfig, useCopy } from '../chunk-TJSNVTVB.js';
10
10
  import { cn } from '../chunk-77QBZC7J.js';
11
- import '../chunk-B634JHKZ.js';
11
+ import '../chunk-BI4EGLPG.js';
12
12
  import * as React from 'react';
13
13
  import { toast } from 'sonner';
14
14
  import { Check, TriangleAlert, Loader2, Lock, Building2, Landmark, Smartphone, Wallet } from 'lucide-react';
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- export { PaymentQrDialog } from '../chunk-5Z2IAD5I.js';
2
+ export { PaymentQrDialog } from '../chunk-TLG4C2XI.js';
3
3
  import '../chunk-EYFZWQ4J.js';
4
- import '../chunk-3KIDW4LT.js';
4
+ import '../chunk-BVI5XDDA.js';
5
5
  import '../chunk-M35R6JLA.js';
6
6
  import '../chunk-I4WDVYHX.js';
7
- import '../chunk-E2D7QT6N.js';
7
+ import '../chunk-TJSNVTVB.js';
8
8
  import '../chunk-77QBZC7J.js';
9
- import '../chunk-B634JHKZ.js';
9
+ import '../chunk-BI4EGLPG.js';
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { Card, CardContent } from '../chunk-SETIN6XP.js';
3
3
  import { buttonVariants } from '../chunk-I4WDVYHX.js';
4
- import { useCopy } from '../chunk-E2D7QT6N.js';
4
+ import { useCopy } from '../chunk-TJSNVTVB.js';
5
5
  import '../chunk-77QBZC7J.js';
6
- import '../chunk-B634JHKZ.js';
6
+ import '../chunk-BI4EGLPG.js';
7
7
  import Link from 'next/link';
8
8
  import { FileQuestion } from 'lucide-react';
9
9
  import { jsx, jsxs } from 'react/jsx-runtime';
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { StatusBadge } from '../chunk-OE525ZER.js';
3
+ import { Money } from '../chunk-BVI5XDDA.js';
3
4
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
4
5
  import '../chunk-55FQP2DO.js';
5
- import { Money } from '../chunk-3KIDW4LT.js';
6
- import { useFormatters, useCopy } from '../chunk-E2D7QT6N.js';
6
+ import { useFormatters, useCopy } from '../chunk-TJSNVTVB.js';
7
7
  import { cn } from '../chunk-77QBZC7J.js';
8
- import '../chunk-B634JHKZ.js';
8
+ import '../chunk-BI4EGLPG.js';
9
9
  import Link from 'next/link';
10
10
  import { ChevronRight } from 'lucide-react';
11
11
  import { jsx, jsxs } from 'react/jsx-runtime';
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- export { AmountSummary } from '../chunk-BIQ2J75Y.js';
2
+ export { AmountSummary } from '../chunk-GLIK5BHP.js';
3
3
  import '../chunk-NSCIBSCW.js';
4
- import '../chunk-3KIDW4LT.js';
5
- import '../chunk-E2D7QT6N.js';
4
+ import '../chunk-BVI5XDDA.js';
5
+ import '../chunk-TJSNVTVB.js';
6
6
  import '../chunk-77QBZC7J.js';
7
- import '../chunk-B634JHKZ.js';
7
+ import '../chunk-BI4EGLPG.js';
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- export { Money } from '../chunk-3KIDW4LT.js';
3
- import '../chunk-E2D7QT6N.js';
2
+ export { Money } from '../chunk-BVI5XDDA.js';
3
+ import '../chunk-TJSNVTVB.js';
4
4
  import '../chunk-77QBZC7J.js';
5
- import '../chunk-B634JHKZ.js';
5
+ import '../chunk-BI4EGLPG.js';
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- export { MunicipalSeal } from '../chunk-IF5UAVIE.js';
2
+ export { MunicipalSeal } from '../chunk-YC7G2IOZ.js';
3
3
  import '../chunk-WOPU6DI7.js';
4
- import '../chunk-E2D7QT6N.js';
4
+ import '../chunk-TJSNVTVB.js';
5
5
  import '../chunk-77QBZC7J.js';
6
- import '../chunk-B634JHKZ.js';
6
+ import '../chunk-BI4EGLPG.js';
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- export { OfficialHeader } from '../chunk-4SZXBT56.js';
3
- import '../chunk-IF5UAVIE.js';
2
+ export { OfficialHeader } from '../chunk-NT72CQAI.js';
3
+ import '../chunk-YC7G2IOZ.js';
4
4
  import '../chunk-WOPU6DI7.js';
5
- import '../chunk-E2D7QT6N.js';
5
+ import '../chunk-TJSNVTVB.js';
6
6
  import '../chunk-77QBZC7J.js';
7
- import '../chunk-B634JHKZ.js';
7
+ import '../chunk-BI4EGLPG.js';