@gelabs/ovr 0.4.2 → 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 (76) 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-rate-limit.d.ts +11 -2
  7. package/dist/auth-rate-limit.js +1 -1
  8. package/dist/auth.d.ts +2 -2
  9. package/dist/auth.js +1 -1
  10. package/dist/chunk-4EDMZRGS.js +98 -0
  11. package/dist/chunk-4JMHQKMK.js +268 -0
  12. package/dist/chunk-6662IONX.js +69 -0
  13. package/dist/chunk-6WMPBAUH.js +18 -0
  14. package/dist/{chunk-77ULDXQX.js → chunk-6ZJSEM4Y.js} +6 -1
  15. package/dist/chunk-7GVZZWK3.js +74 -0
  16. package/dist/chunk-ACXED4UH.js +403 -0
  17. package/dist/chunk-BBTAG3FD.js +165 -0
  18. package/dist/chunk-HX7QT2FE.js +452 -0
  19. package/dist/chunk-LC3S47FM.js +468 -0
  20. package/dist/chunk-NPTR7GFZ.js +432 -0
  21. package/dist/chunk-SLQRZBMR.js +66 -0
  22. package/dist/chunk-UBUXPJLN.js +29 -0
  23. package/dist/chunk-UN6Z4WGF.js +31 -0
  24. package/dist/chunk-YE3D2DYY.js +83 -0
  25. package/dist/citizen.d.ts +5 -0
  26. package/dist/citizen.js +12 -0
  27. package/dist/config.d.ts +2 -2
  28. package/dist/core-i18n.d.ts +1 -1
  29. package/dist/core.d.ts +2 -2
  30. package/dist/dashboard.d.ts +10 -0
  31. package/dist/dashboard.js +3 -0
  32. package/dist/data-mock-store.d.ts +1 -1
  33. package/dist/{format-C7MSwUHK.d.ts → format-VyCUfF8R.d.ts} +1 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/notifications.d.ts +6 -0
  36. package/dist/notifications.js +11 -0
  37. package/dist/officers.d.ts +27 -0
  38. package/dist/officers.js +11 -0
  39. package/dist/offline.d.ts +1 -1
  40. package/dist/payments.d.ts +15 -0
  41. package/dist/payments.js +11 -0
  42. package/dist/roles.d.ts +37 -0
  43. package/dist/roles.js +12 -0
  44. package/dist/runtime.d.ts +1 -1
  45. package/dist/tickets.d.ts +6 -0
  46. package/dist/tickets.js +24 -0
  47. package/dist/ui-components-admin/accounts-manager.d.ts +3 -52
  48. package/dist/ui-components-admin/accounts-manager.js +11 -472
  49. package/dist/ui-components-admin/admin-nav.js +11 -83
  50. package/dist/ui-components-admin/issuance-form.js +15 -432
  51. package/dist/ui-components-admin/logs-viewer.d.ts +3 -13
  52. package/dist/ui-components-admin/logs-viewer.js +8 -98
  53. package/dist/ui-components-admin/notifications-list.js +6 -66
  54. package/dist/ui-components-admin/officers-manager.d.ts +3 -27
  55. package/dist/ui-components-admin/officers-manager.js +9 -268
  56. package/dist/ui-components-admin/roles-manager.d.ts +3 -37
  57. package/dist/ui-components-admin/roles-manager.js +10 -403
  58. package/dist/ui-components-admin/stat-card.d.ts +2 -10
  59. package/dist/ui-components-admin/stat-card.js +3 -29
  60. package/dist/ui-components-admin/ticket-preview.js +2 -2
  61. package/dist/ui-components-admin/tickets-table.js +7 -69
  62. package/dist/ui-components-admin/violations-manager.js +13 -452
  63. package/dist/ui-components-citizen/citizen-nav.js +3 -31
  64. package/dist/ui-components-citizen/payment-form.d.ts +3 -14
  65. package/dist/ui-components-citizen/payment-form.js +9 -165
  66. package/dist/ui-components-citizen/payment-qr-dialog.js +2 -2
  67. package/dist/ui-components-citizen/ticket-not-found.js +4 -18
  68. package/dist/ui-components-citizen/violation-history-table.js +6 -74
  69. package/dist/ui-config.d.ts +2 -2
  70. package/dist/ui-server.d.ts +2 -2
  71. package/dist/violations.d.ts +3 -0
  72. package/dist/violations.js +14 -0
  73. package/package.json +46 -6
  74. package/dist/{chunk-ZUMEOZ22.js → chunk-JTSTNZAB.js} +1 -1
  75. package/dist/{chunk-TLG4C2XI.js → chunk-QCAURREW.js} +1 -1
  76. package/dist/{schema-CdsFQxIg.d.ts → schema-BUhh_mKX.d.ts} +108 -108
@@ -0,0 +1,74 @@
1
+ import { StatusBadge } from './chunk-OE525ZER.js';
2
+ import { Money } from './chunk-BVI5XDDA.js';
3
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from './chunk-OWCGEEAZ.js';
4
+ import { useFormatters, useCopy } from './chunk-TJSNVTVB.js';
5
+ import { cn } from './chunk-77QBZC7J.js';
6
+ import Link from 'next/link';
7
+ import { ChevronRight } from 'lucide-react';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
9
+
10
+ function ViolationHistoryTable({
11
+ tickets,
12
+ currentTicketNo,
13
+ lastName
14
+ }) {
15
+ const { formatDate } = useFormatters();
16
+ const copy = useCopy();
17
+ const t = copy.citizen.ticket.history;
18
+ if (tickets.length === 0) {
19
+ return /* @__PURE__ */ jsx("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: t.empty });
20
+ }
21
+ return /* @__PURE__ */ jsxs(Table, { children: [
22
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
23
+ /* @__PURE__ */ jsx(TableHead, { children: t.colTicketNo }),
24
+ /* @__PURE__ */ jsx(TableHead, { className: "hidden sm:table-cell", children: t.colDate }),
25
+ /* @__PURE__ */ jsx(TableHead, { children: t.colViolations }),
26
+ /* @__PURE__ */ jsx(TableHead, { children: t.colStatus }),
27
+ /* @__PURE__ */ jsx(TableHead, { className: "text-right", children: t.colAmount }),
28
+ /* @__PURE__ */ jsx(TableHead, { className: "w-8" })
29
+ ] }) }),
30
+ /* @__PURE__ */ jsx(TableBody, { children: tickets.map((ticket) => {
31
+ const isCurrent = ticket.ovrTicketNo === currentTicketNo;
32
+ const href = `/citizen/ticket/${encodeURIComponent(
33
+ ticket.ovrTicketNo
34
+ )}?ln=${encodeURIComponent(lastName)}`;
35
+ const amount = ticket.status === "PAID" ? ticket.payment?.amount ?? 0 : ticket.totalAmountDue;
36
+ const first = ticket.violations[0];
37
+ const extra = ticket.violations.length - 1;
38
+ return /* @__PURE__ */ jsxs(
39
+ TableRow,
40
+ {
41
+ className: cn(isCurrent && "bg-muted/40"),
42
+ children: [
43
+ /* @__PURE__ */ jsx(TableCell, { className: "font-mono text-xs", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
44
+ /* @__PURE__ */ jsx(Link, { href, className: "hover:underline", children: ticket.ovrTicketNo }),
45
+ isCurrent ? /* @__PURE__ */ jsx("span", { className: "rounded bg-brand/12 px-1.5 py-0.5 text-[0.65rem] font-medium uppercase tracking-wide text-brand", children: t.current }) : null
46
+ ] }) }),
47
+ /* @__PURE__ */ jsx(TableCell, { className: "hidden text-muted-foreground sm:table-cell", children: formatDate(ticket.apprehendedAt) }),
48
+ /* @__PURE__ */ jsxs(TableCell, { className: "max-w-[16rem] truncate text-muted-foreground", children: [
49
+ first?.title ?? "\u2014",
50
+ extra > 0 ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
51
+ " +",
52
+ extra
53
+ ] }) : null
54
+ ] }),
55
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(StatusBadge, { status: ticket.status }) }),
56
+ /* @__PURE__ */ jsx(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx(Money, { value: amount }) }),
57
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
58
+ Link,
59
+ {
60
+ href,
61
+ className: "text-muted-foreground hover:text-foreground",
62
+ "aria-label": `Open ${ticket.ovrTicketNo}`,
63
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
64
+ }
65
+ ) })
66
+ ]
67
+ },
68
+ ticket.ovrTicketNo
69
+ );
70
+ }) })
71
+ ] });
72
+ }
73
+
74
+ export { ViolationHistoryTable };
@@ -0,0 +1,403 @@
1
+ import { Checkbox } from './chunk-BBQBKQA4.js';
2
+ import { Label } from './chunk-XQTVSNHC.js';
3
+ import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from './chunk-M35R6JLA.js';
4
+ import { Input } from './chunk-K3KIBHJF.js';
5
+ import { PERMISSION_CATALOG, SUPER_ADMIN_LOCKED_PERMISSIONS } from './chunk-QZRRFE6E.js';
6
+ import { Badge } from './chunk-55FQP2DO.js';
7
+ import { Button } from './chunk-I4WDVYHX.js';
8
+ import { useCopy } from './chunk-TJSNVTVB.js';
9
+ import { Card, CardContent } from './chunk-SETIN6XP.js';
10
+ import * as React from 'react';
11
+ import { toast } from 'sonner';
12
+ import { useRouter } from 'next/navigation';
13
+ import { ShieldCheck, SlidersHorizontal, Loader2, Trash2, Plus, Lock } from 'lucide-react';
14
+ import { jsxs, jsx } from 'react/jsx-runtime';
15
+
16
+ function RolesManager({
17
+ roles,
18
+ usage = {},
19
+ createAction,
20
+ updateAction,
21
+ deleteAction
22
+ }) {
23
+ const t = useCopy().admin.rolesPage;
24
+ const router = useRouter();
25
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6 lg:p-8", children: [
26
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between gap-3", children: [
27
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
28
+ /* @__PURE__ */ jsx("h1", { className: "font-heading text-2xl font-semibold tracking-tight", children: t.title }),
29
+ /* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm text-muted-foreground", children: t.subtitle })
30
+ ] }),
31
+ /* @__PURE__ */ jsx(
32
+ NewRoleDialog,
33
+ {
34
+ createAction,
35
+ onChanged: () => router.refresh()
36
+ }
37
+ )
38
+ ] }),
39
+ 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(
40
+ RoleCard,
41
+ {
42
+ role,
43
+ inUse: usage[role.name] ?? 0,
44
+ updateAction,
45
+ deleteAction,
46
+ onChanged: () => router.refresh()
47
+ },
48
+ role.name
49
+ )) })
50
+ ] });
51
+ }
52
+ function PermissionChecklist({
53
+ selected,
54
+ onToggle,
55
+ locked
56
+ }) {
57
+ return /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: PERMISSION_CATALOG.map((perm) => {
58
+ const isLocked = locked?.(perm.key) ?? false;
59
+ const checked = selected.has(perm.key) || isLocked;
60
+ return /* @__PURE__ */ jsxs(
61
+ "label",
62
+ {
63
+ className: "flex items-start gap-2.5 rounded-lg border p-2.5",
64
+ children: [
65
+ /* @__PURE__ */ jsx(
66
+ Checkbox,
67
+ {
68
+ checked,
69
+ disabled: isLocked,
70
+ onCheckedChange: (v) => onToggle(perm.key, v === true),
71
+ className: "mt-0.5"
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
75
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium", children: [
76
+ perm.label,
77
+ isLocked ? /* @__PURE__ */ jsx(Lock, { className: "size-3 text-muted-foreground" }) : null
78
+ ] }),
79
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: perm.description })
80
+ ] })
81
+ ]
82
+ },
83
+ perm.key
84
+ );
85
+ }) });
86
+ }
87
+ function RoleCard({
88
+ role,
89
+ inUse,
90
+ updateAction,
91
+ deleteAction,
92
+ onChanged
93
+ }) {
94
+ const t = useCopy().admin.rolesPage;
95
+ const grantedLabels = PERMISSION_CATALOG.filter(
96
+ (p) => role.permissions.includes(p.key)
97
+ ).map((p) => p.label);
98
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "flex items-center justify-between gap-3 py-4", children: [
99
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
100
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
101
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "size-4 shrink-0 text-muted-foreground" }),
102
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: role.label }),
103
+ /* @__PURE__ */ jsx(Badge, { variant: role.isSystem ? "secondary" : "outline", children: role.isSystem ? t.system : t.custom }),
104
+ inUse > 0 ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
105
+ inUse,
106
+ " ",
107
+ t.inUseSuffix
108
+ ] }) : null
109
+ ] }),
110
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: grantedLabels.length ? grantedLabels.join(" \xB7 ") : t.noPermissions })
111
+ ] }),
112
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [
113
+ /* @__PURE__ */ jsx(
114
+ EditRolePermissionsDialog,
115
+ {
116
+ role,
117
+ updateAction,
118
+ onChanged
119
+ }
120
+ ),
121
+ !role.isSystem ? /* @__PURE__ */ jsx(
122
+ DeleteRoleButton,
123
+ {
124
+ role,
125
+ inUse,
126
+ deleteAction,
127
+ onChanged
128
+ }
129
+ ) : null
130
+ ] })
131
+ ] }) });
132
+ }
133
+ function EditRolePermissionsDialog({
134
+ role,
135
+ updateAction,
136
+ onChanged
137
+ }) {
138
+ const t = useCopy().admin.rolesPage;
139
+ const [open, setOpen] = React.useState(false);
140
+ const [label, setLabel] = React.useState(role.label);
141
+ const [perms, setPerms] = React.useState(
142
+ () => new Set(role.permissions)
143
+ );
144
+ const [saving, setSaving] = React.useState(false);
145
+ const isLocked = (p) => role.name === "SUPER_ADMIN" && SUPER_ADMIN_LOCKED_PERMISSIONS.includes(p);
146
+ function toggle(p, checked) {
147
+ if (isLocked(p)) return;
148
+ setPerms((prev) => {
149
+ const next = new Set(prev);
150
+ if (checked) next.add(p);
151
+ else next.delete(p);
152
+ return next;
153
+ });
154
+ }
155
+ async function submit(e) {
156
+ e.preventDefault();
157
+ setSaving(true);
158
+ try {
159
+ const res = await updateAction(role.name, {
160
+ label: role.isSystem ? void 0 : label.trim(),
161
+ permissions: [...perms]
162
+ });
163
+ if (res?.error) {
164
+ toast.error(res.error);
165
+ return;
166
+ }
167
+ toast.success(`${t.saved}: ${role.isSystem ? role.label : label.trim()}`);
168
+ setOpen(false);
169
+ onChanged();
170
+ } finally {
171
+ setSaving(false);
172
+ }
173
+ }
174
+ return /* @__PURE__ */ jsxs(
175
+ Dialog,
176
+ {
177
+ open,
178
+ onOpenChange: (o) => {
179
+ if (o) {
180
+ setLabel(role.label);
181
+ setPerms(new Set(role.permissions));
182
+ }
183
+ setOpen(o);
184
+ },
185
+ children: [
186
+ /* @__PURE__ */ jsxs(
187
+ DialogTrigger,
188
+ {
189
+ render: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "gap-1.5" }),
190
+ children: [
191
+ /* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-3.5" }),
192
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.editPermissions })
193
+ ]
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-lg", children: [
197
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
198
+ /* @__PURE__ */ jsx(DialogTitle, { children: role.label }),
199
+ /* @__PURE__ */ jsx(DialogDescription, { children: t.editPermissions })
200
+ ] }),
201
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-4", children: [
202
+ !role.isSystem ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
203
+ /* @__PURE__ */ jsx(Label, { htmlFor: `role-label-${role.name}`, children: t.roleName }),
204
+ /* @__PURE__ */ jsx(
205
+ Input,
206
+ {
207
+ id: `role-label-${role.name}`,
208
+ value: label,
209
+ onChange: (e) => setLabel(e.target.value),
210
+ autoComplete: "off"
211
+ }
212
+ )
213
+ ] }) : null,
214
+ /* @__PURE__ */ jsx(
215
+ PermissionChecklist,
216
+ {
217
+ selected: perms,
218
+ onToggle: toggle,
219
+ locked: isLocked
220
+ }
221
+ ),
222
+ role.name === "SUPER_ADMIN" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t.lockedHint }) : null,
223
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
224
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline" }), children: t.cancel }),
225
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: saving, className: "gap-1.5", children: [
226
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : null,
227
+ saving ? t.saving : t.save
228
+ ] })
229
+ ] })
230
+ ] })
231
+ ] })
232
+ ]
233
+ }
234
+ );
235
+ }
236
+ function DeleteRoleButton({
237
+ role,
238
+ inUse,
239
+ deleteAction,
240
+ onChanged
241
+ }) {
242
+ const t = useCopy().admin.rolesPage;
243
+ const [deleting, setDeleting] = React.useState(false);
244
+ async function remove() {
245
+ setDeleting(true);
246
+ try {
247
+ const res = await deleteAction(role.name);
248
+ if (res?.error) {
249
+ toast.error(res.error);
250
+ return;
251
+ }
252
+ toast.success(`${t.delete}: ${role.label}`);
253
+ onChanged();
254
+ } finally {
255
+ setDeleting(false);
256
+ }
257
+ }
258
+ return /* @__PURE__ */ jsxs(Dialog, { children: [
259
+ /* @__PURE__ */ jsxs(
260
+ DialogTrigger,
261
+ {
262
+ render: /* @__PURE__ */ jsx(
263
+ Button,
264
+ {
265
+ variant: "destructive",
266
+ size: "sm",
267
+ className: "gap-1.5",
268
+ disabled: inUse > 0
269
+ }
270
+ ),
271
+ children: [
272
+ /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" }),
273
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.delete })
274
+ ]
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm", children: [
278
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
279
+ /* @__PURE__ */ jsx(DialogTitle, { children: t.deleteConfirmTitle }),
280
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
281
+ role.label,
282
+ " \u2014 ",
283
+ t.deleteConfirmBody
284
+ ] })
285
+ ] }),
286
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
287
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { variant: "outline" }), children: t.cancel }),
288
+ /* @__PURE__ */ jsxs(
289
+ DialogClose,
290
+ {
291
+ render: /* @__PURE__ */ jsx(
292
+ Button,
293
+ {
294
+ variant: "destructive",
295
+ className: "gap-2",
296
+ disabled: deleting
297
+ }
298
+ ),
299
+ onClick: remove,
300
+ children: [
301
+ /* @__PURE__ */ jsx(Trash2, { className: "size-4" }),
302
+ t.delete
303
+ ]
304
+ }
305
+ )
306
+ ] })
307
+ ] })
308
+ ] });
309
+ }
310
+ function NewRoleDialog({
311
+ createAction,
312
+ onChanged
313
+ }) {
314
+ const t = useCopy().admin.rolesPage;
315
+ const [open, setOpen] = React.useState(false);
316
+ const [label, setLabel] = React.useState("");
317
+ const [perms, setPerms] = React.useState(() => /* @__PURE__ */ new Set());
318
+ const [submitting, setSubmitting] = React.useState(false);
319
+ function reset() {
320
+ setLabel("");
321
+ setPerms(/* @__PURE__ */ new Set());
322
+ }
323
+ function toggle(p, checked) {
324
+ setPerms((prev) => {
325
+ const next = new Set(prev);
326
+ if (checked) next.add(p);
327
+ else next.delete(p);
328
+ return next;
329
+ });
330
+ }
331
+ async function submit(e) {
332
+ e.preventDefault();
333
+ if (!label.trim()) return toast.error(`${t.roleName} is required.`);
334
+ setSubmitting(true);
335
+ try {
336
+ const res = await createAction({
337
+ label: label.trim(),
338
+ permissions: [...perms]
339
+ });
340
+ if (res?.error) {
341
+ toast.error(res.error);
342
+ return;
343
+ }
344
+ toast.success(`${t.create}: ${label.trim()}`);
345
+ reset();
346
+ setOpen(false);
347
+ onChanged();
348
+ } finally {
349
+ setSubmitting(false);
350
+ }
351
+ }
352
+ return /* @__PURE__ */ jsxs(
353
+ Dialog,
354
+ {
355
+ open,
356
+ onOpenChange: (o) => {
357
+ if (!o) reset();
358
+ setOpen(o);
359
+ },
360
+ children: [
361
+ /* @__PURE__ */ jsxs(DialogTrigger, { render: /* @__PURE__ */ jsx(Button, { className: "gap-1.5" }), children: [
362
+ /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
363
+ t.newRole
364
+ ] }),
365
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-lg", children: [
366
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
367
+ /* @__PURE__ */ jsx(DialogTitle, { children: t.newRole }),
368
+ /* @__PURE__ */ jsx(DialogDescription, { children: t.subtitle })
369
+ ] }),
370
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-4", children: [
371
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
372
+ /* @__PURE__ */ jsx(Label, { htmlFor: "role-label", children: t.roleName }),
373
+ /* @__PURE__ */ jsx(
374
+ Input,
375
+ {
376
+ id: "role-label",
377
+ value: label,
378
+ onChange: (e) => setLabel(e.target.value),
379
+ placeholder: t.roleNamePlaceholder,
380
+ autoComplete: "off",
381
+ autoFocus: true
382
+ }
383
+ )
384
+ ] }),
385
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
386
+ /* @__PURE__ */ jsx(Label, { children: t.permissions }),
387
+ /* @__PURE__ */ jsx(PermissionChecklist, { selected: perms, onToggle: toggle })
388
+ ] }),
389
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
390
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline" }), children: t.cancel }),
391
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: submitting, className: "gap-1.5", children: [
392
+ submitting ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
393
+ submitting ? t.creating : t.create
394
+ ] })
395
+ ] })
396
+ ] })
397
+ ] })
398
+ ]
399
+ }
400
+ );
401
+ }
402
+
403
+ export { RolesManager };
@@ -0,0 +1,165 @@
1
+ import { PaymentQrDialog } from './chunk-QCAURREW.js';
2
+ import { Separator } from './chunk-NSCIBSCW.js';
3
+ import { Alert, AlertDescription } from './chunk-EYFZWQ4J.js';
4
+ import { Money } from './chunk-BVI5XDDA.js';
5
+ import { Button } from './chunk-I4WDVYHX.js';
6
+ import { useOvrConfig, useCopy } from './chunk-TJSNVTVB.js';
7
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent } from './chunk-SETIN6XP.js';
8
+ import { cn } from './chunk-77QBZC7J.js';
9
+ import * as React from 'react';
10
+ import { toast } from 'sonner';
11
+ import { Check, TriangleAlert, Loader2, Lock, Building2, Landmark, Smartphone, Wallet } from 'lucide-react';
12
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
13
+
14
+ var ICONS = {
15
+ GCASH: /* @__PURE__ */ jsx(Wallet, { className: "size-5" }),
16
+ MAYA: /* @__PURE__ */ jsx(Smartphone, { className: "size-5" }),
17
+ LANDBANK: /* @__PURE__ */ jsx(Landmark, { className: "size-5" }),
18
+ OVER_THE_COUNTER: /* @__PURE__ */ jsx(Building2, { className: "size-5" })
19
+ };
20
+ function PaymentForm({
21
+ ovrTicketNo,
22
+ lastName,
23
+ violatorName,
24
+ basicFines,
25
+ penalty,
26
+ amount,
27
+ payAction
28
+ }) {
29
+ const { paymentMethods } = useOvrConfig();
30
+ const copy = useCopy();
31
+ const payMethods = paymentMethods.filter((m) => m.id !== "OVER_THE_COUNTER");
32
+ const [method, setMethod] = React.useState(
33
+ () => payMethods[0]?.id ?? "GCASH"
34
+ );
35
+ const [error, setError] = React.useState(null);
36
+ const [isPending, startTransition] = React.useTransition();
37
+ const [qrOpen, setQrOpen] = React.useState(false);
38
+ function pay() {
39
+ setError(null);
40
+ startTransition(async () => {
41
+ const res = await payAction({ ovrTicketNo, lastName, method });
42
+ if (res?.error) {
43
+ setError(res.error);
44
+ toast.error(res.error);
45
+ }
46
+ });
47
+ }
48
+ const methodLabel = paymentMethods.find((m) => m.id === method)?.label ?? method;
49
+ function handlePay() {
50
+ if (method === "OVER_THE_COUNTER") {
51
+ pay();
52
+ } else {
53
+ setError(null);
54
+ setQrOpen(true);
55
+ }
56
+ }
57
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
58
+ /* @__PURE__ */ jsxs(Card, { className: "overflow-visible", children: [
59
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
60
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-xl", children: copy.citizen.pay.title }),
61
+ /* @__PURE__ */ jsxs(CardDescription, { children: [
62
+ violatorName,
63
+ " \xB7 ",
64
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: ovrTicketNo })
65
+ ] })
66
+ ] }),
67
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-5", children: [
68
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-lg border bg-muted/20 p-3 text-sm", children: [
69
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
70
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Basic fines" }),
71
+ /* @__PURE__ */ jsx(Money, { value: basicFines })
72
+ ] }),
73
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
74
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Penalty / surcharge" }),
75
+ /* @__PURE__ */ jsx(
76
+ Money,
77
+ {
78
+ value: penalty,
79
+ className: cn(penalty === 0 && "text-muted-foreground")
80
+ }
81
+ )
82
+ ] }),
83
+ /* @__PURE__ */ jsx(Separator, {}),
84
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
85
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Total to pay" }),
86
+ /* @__PURE__ */ jsx(Money, { value: amount, className: "text-xl font-bold" })
87
+ ] })
88
+ ] }),
89
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
90
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: copy.citizen.pay.choose }),
91
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2", children: payMethods.map((m) => {
92
+ const active = method === m.id;
93
+ return /* @__PURE__ */ jsxs(
94
+ "button",
95
+ {
96
+ type: "button",
97
+ onClick: () => setMethod(m.id),
98
+ "aria-pressed": active,
99
+ className: cn(
100
+ "flex items-center gap-2.5 rounded-lg border p-3 text-left transition-colors outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
101
+ active ? "border-primary bg-primary/5 ring-1 ring-primary/30" : "hover:bg-muted/40"
102
+ ),
103
+ children: [
104
+ /* @__PURE__ */ jsx(
105
+ "span",
106
+ {
107
+ className: cn(
108
+ "flex size-9 shrink-0 items-center justify-center rounded-lg",
109
+ active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
110
+ ),
111
+ children: ICONS[m.id]
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsx("span", { className: "min-w-0", children: /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: m.label }) }),
115
+ active ? /* @__PURE__ */ jsx(Check, { className: "ml-auto size-4 shrink-0 text-primary" }) : null
116
+ ]
117
+ },
118
+ m.id
119
+ );
120
+ }) })
121
+ ] }),
122
+ error ? /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
123
+ /* @__PURE__ */ jsx(TriangleAlert, {}),
124
+ /* @__PURE__ */ jsx(AlertDescription, { children: error })
125
+ ] }) : null,
126
+ /* @__PURE__ */ jsx(
127
+ Button,
128
+ {
129
+ onClick: handlePay,
130
+ disabled: isPending,
131
+ className: "h-11 w-full gap-2 text-base",
132
+ children: isPending ? /* @__PURE__ */ jsxs(Fragment, { children: [
133
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }),
134
+ copy.citizen.pay.processing
135
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
136
+ /* @__PURE__ */ jsx(Lock, { className: "size-4" }),
137
+ copy.citizen.pay.payNow,
138
+ " ",
139
+ /* @__PURE__ */ jsx(Money, { value: amount, className: "font-semibold" })
140
+ ] })
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-1.5 text-center text-xs text-muted-foreground", children: [
144
+ /* @__PURE__ */ jsx(Lock, { className: "size-3" }),
145
+ copy.citizen.pay.secure
146
+ ] })
147
+ ] })
148
+ ] }),
149
+ /* @__PURE__ */ jsx(
150
+ PaymentQrDialog,
151
+ {
152
+ open: qrOpen,
153
+ onOpenChange: setQrOpen,
154
+ method,
155
+ methodLabel,
156
+ amount,
157
+ ovrTicketNo,
158
+ lastName,
159
+ payAction
160
+ }
161
+ )
162
+ ] });
163
+ }
164
+
165
+ export { PaymentForm };