@gelabs/ovr 0.3.0 → 0.4.1

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 (43) hide show
  1. package/dist/{chunk-HGWPA7FU.js → chunk-6AXIWTMW.js} +36 -0
  2. package/dist/{chunk-IS3THKTE.js → chunk-QZRRFE6E.js} +13 -2
  3. package/dist/{chunk-ZUMEOZ22.js → chunk-VH4J2SIV.js} +2 -2
  4. package/dist/core-i18n.d.ts +2 -2
  5. package/dist/core-i18n.js +1 -1
  6. package/dist/data-mock-store.js +93 -7
  7. package/dist/data-prisma-store.js +87 -9
  8. package/dist/data.d.ts +16 -1
  9. package/dist/generated/client/edge.js +6 -4
  10. package/dist/generated/client/index-browser.js +3 -1
  11. package/dist/generated/client/index.d.ts +97 -39
  12. package/dist/generated/client/index.js +6 -4
  13. package/dist/generated/client/package.json +1 -1
  14. package/dist/generated/client/schema.prisma +2 -0
  15. package/dist/generated/client/wasm.js +6 -4
  16. package/dist/index.d.ts +1 -1
  17. package/dist/{types-BOgdk0Jw.d.ts → types-DNEO6wrO.d.ts} +36 -0
  18. package/dist/types.d.ts +14 -3
  19. package/dist/types.js +1 -1
  20. package/dist/ui-components-admin/accounts-manager.js +5 -5
  21. package/dist/ui-components-admin/admin-nav.js +6 -4
  22. package/dist/ui-components-admin/issuance-form.js +10 -10
  23. package/dist/ui-components-admin/logs-viewer.js +4 -4
  24. package/dist/ui-components-admin/notifications-list.js +1 -1
  25. package/dist/ui-components-admin/officers-manager.js +3 -3
  26. package/dist/ui-components-admin/roles-manager.js +4 -4
  27. package/dist/ui-components-admin/ticket-preview.js +6 -6
  28. package/dist/ui-components-admin/tickets-table.js +3 -3
  29. package/dist/ui-components-admin/violations-manager.d.ts +36 -0
  30. package/dist/ui-components-admin/violations-manager.js +454 -0
  31. package/dist/ui-components-citizen/payment-form.js +3 -3
  32. package/dist/ui-components-citizen/payment-qr-dialog.js +2 -2
  33. package/dist/ui-components-citizen/violation-history-table.js +2 -2
  34. package/dist/ui-components-shared/ticket-receipt.js +4 -4
  35. package/dist/ui-components-shared/violations-table.js +2 -2
  36. package/dist/ui-config.d.ts +1 -1
  37. package/dist/ui-server.d.ts +1 -1
  38. package/dist/ui-server.js +1 -1
  39. package/package.json +6 -6
  40. package/prisma/migrations/20260622050000_violation_catalog_management/migration.sql +5 -0
  41. package/prisma/schema.prisma +2 -0
  42. package/dist/{chunk-IBZVIUNI.js → chunk-6BH4EFP3.js} +1 -1
  43. package/dist/{chunk-TLG4C2XI.js → chunk-QCAURREW.js} +1 -1
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { Card, CardContent } from '../chunk-SETIN6XP.js';
3
2
  import { usePagination, Pagination } from '../chunk-6YFZLXFP.js';
4
- import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
5
3
  import { Label } from '../chunk-XQTVSNHC.js';
6
- import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '../chunk-M35R6JLA.js';
7
4
  import { Input } from '../chunk-K3KIBHJF.js';
5
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
6
+ import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '../chunk-M35R6JLA.js';
7
+ import { Card, CardContent } from '../chunk-SETIN6XP.js';
8
8
  import { Button } from '../chunk-I4WDVYHX.js';
9
9
  import { useCopy } from '../chunk-TJSNVTVB.js';
10
10
  import '../chunk-77QBZC7J.js';
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import { Checkbox } from '../chunk-BBQBKQA4.js';
3
- import { Card, CardContent } from '../chunk-SETIN6XP.js';
4
- import { Badge } from '../chunk-55FQP2DO.js';
5
3
  import { Label } from '../chunk-XQTVSNHC.js';
6
- import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '../chunk-M35R6JLA.js';
7
4
  import { Input } from '../chunk-K3KIBHJF.js';
5
+ import { Badge } from '../chunk-55FQP2DO.js';
6
+ import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '../chunk-M35R6JLA.js';
7
+ import { Card, CardContent } from '../chunk-SETIN6XP.js';
8
8
  import { Button } from '../chunk-I4WDVYHX.js';
9
9
  import { useCopy } from '../chunk-TJSNVTVB.js';
10
10
  import '../chunk-77QBZC7J.js';
11
- import { PERMISSION_CATALOG, SUPER_ADMIN_LOCKED_PERMISSIONS } from '../chunk-IS3THKTE.js';
11
+ import { PERMISSION_CATALOG, SUPER_ADMIN_LOCKED_PERMISSIONS } from '../chunk-QZRRFE6E.js';
12
12
  import '../chunk-BI4EGLPG.js';
13
13
  import * as React from 'react';
14
14
  import { toast } from 'sonner';
@@ -1,13 +1,13 @@
1
1
  "use client";
2
- export { TicketPreview } from '../chunk-ZUMEOZ22.js';
3
- import '../chunk-IBZVIUNI.js';
2
+ export { TicketPreview } from '../chunk-VH4J2SIV.js';
3
+ import '../chunk-6BH4EFP3.js';
4
+ import '../chunk-OE525ZER.js';
5
+ import '../chunk-55FQP2DO.js';
6
+ import '../chunk-OWCGEEAZ.js';
4
7
  import '../chunk-GLIK5BHP.js';
8
+ import '../chunk-SETIN6XP.js';
5
9
  import '../chunk-NSCIBSCW.js';
6
- import '../chunk-OE525ZER.js';
7
10
  import '../chunk-BVI5XDDA.js';
8
- import '../chunk-SETIN6XP.js';
9
- import '../chunk-OWCGEEAZ.js';
10
- import '../chunk-55FQP2DO.js';
11
11
  import '../chunk-TJSNVTVB.js';
12
12
  import '../chunk-77QBZC7J.js';
13
13
  import '../chunk-BI4EGLPG.js';
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { StatusBadge } from '../chunk-OE525ZER.js';
3
- import { Money } from '../chunk-BVI5XDDA.js';
4
2
  import { usePagination, Pagination } from '../chunk-6YFZLXFP.js';
5
- import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
3
+ import { StatusBadge } from '../chunk-OE525ZER.js';
6
4
  import '../chunk-55FQP2DO.js';
5
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
6
+ import { Money } from '../chunk-BVI5XDDA.js';
7
7
  import '../chunk-I4WDVYHX.js';
8
8
  import { useFormatters } from '../chunk-TJSNVTVB.js';
9
9
  import '../chunk-77QBZC7J.js';
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { NewViolationInput, ViolationCatalogItem } from '../types.js';
3
+
4
+ /**
5
+ * Violation / ordinance catalog management (GE-031). Add / edit / archive the
6
+ * catalog entries enforcers issue against. Archiving is a soft-delete: the entry
7
+ * is hidden from the Issue-Ticket form but kept so already-issued tickets stay
8
+ * resolvable, and it can be restored. Server actions are INJECTED by the page
9
+ * (mirrors OfficersManager / AccountsManager).
10
+ */
11
+
12
+ type ViolationPatch = Partial<Omit<NewViolationInput, "code">> & {
13
+ active?: boolean;
14
+ };
15
+ type CreateViolationAction = (input: NewViolationInput) => Promise<{
16
+ error?: string;
17
+ } | void>;
18
+ type UpdateViolationAction = (code: string, patch: ViolationPatch) => Promise<{
19
+ error?: string;
20
+ } | void>;
21
+ type DeleteViolationAction = (code: string) => Promise<{
22
+ error?: string;
23
+ } | void>;
24
+ interface ViolationsManagerProps {
25
+ violations: ViolationCatalogItem[];
26
+ createAction: CreateViolationAction;
27
+ updateAction: UpdateViolationAction;
28
+ deleteAction: DeleteViolationAction;
29
+ /** Catalog codes used by ≥1 ticket — those can only be archived, not deleted. */
30
+ usedCodes?: string[];
31
+ /** Permanent delete (only offered for never-used entries). */
32
+ purgeAction?: DeleteViolationAction;
33
+ }
34
+ declare function ViolationsManager({ violations, createAction, updateAction, deleteAction, usedCodes, purgeAction, }: ViolationsManagerProps): React.JSX.Element;
35
+
36
+ export { type CreateViolationAction, type DeleteViolationAction, type UpdateViolationAction, ViolationsManager, type ViolationsManagerProps };
@@ -0,0 +1,454 @@
1
+ "use client";
2
+ import { Textarea } from '../chunk-QCRVT2SS.js';
3
+ import { usePagination, Pagination } from '../chunk-6YFZLXFP.js';
4
+ import { Label } from '../chunk-XQTVSNHC.js';
5
+ import { Input } from '../chunk-K3KIBHJF.js';
6
+ import { Badge } from '../chunk-55FQP2DO.js';
7
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
8
+ import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '../chunk-M35R6JLA.js';
9
+ import { Card, CardContent } from '../chunk-SETIN6XP.js';
10
+ import { Money } from '../chunk-BVI5XDDA.js';
11
+ import { Button } from '../chunk-I4WDVYHX.js';
12
+ import { useCopy } from '../chunk-TJSNVTVB.js';
13
+ import { cn } from '../chunk-77QBZC7J.js';
14
+ import '../chunk-BI4EGLPG.js';
15
+ import * as React from 'react';
16
+ import { toast } from 'sonner';
17
+ import { useRouter } from 'next/navigation';
18
+ import { Plus, Pencil, Loader2, Archive, ArchiveRestore, Trash2 } from 'lucide-react';
19
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
20
+
21
+ 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";
22
+ function ViolationsManager({
23
+ violations,
24
+ createAction,
25
+ updateAction,
26
+ deleteAction,
27
+ usedCodes = [],
28
+ purgeAction
29
+ }) {
30
+ const t = useCopy().admin.violationsPage;
31
+ const router = useRouter();
32
+ const used = React.useMemo(() => new Set(usedCodes), [usedCodes]);
33
+ const { pageItems, page, setPage, totalPages, from, to, total } = usePagination(violations, 12);
34
+ const categoryLabel = (c) => c === "TRAFFIC" ? t.categoryTraffic : t.categoryOrdinance;
35
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-5xl p-4 sm:p-6 lg:p-8", children: [
36
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between gap-3", children: [
37
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
38
+ /* @__PURE__ */ jsx("h1", { className: "font-heading text-2xl font-semibold tracking-tight", children: t.title }),
39
+ /* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm text-muted-foreground", children: t.subtitle })
40
+ ] }),
41
+ /* @__PURE__ */ jsx(
42
+ ViolationDialog,
43
+ {
44
+ mode: "create",
45
+ createAction,
46
+ onDone: () => router.refresh()
47
+ }
48
+ )
49
+ ] }),
50
+ /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-0", children: violations.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-8 text-center text-sm text-muted-foreground", children: t.empty }) : /* @__PURE__ */ jsxs(Fragment, { children: [
51
+ /* @__PURE__ */ jsxs(Table, { children: [
52
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
53
+ /* @__PURE__ */ jsx(TableHead, { children: t.code }),
54
+ /* @__PURE__ */ jsx(TableHead, { children: t.titleLabel }),
55
+ /* @__PURE__ */ jsx(TableHead, { className: "hidden sm:table-cell", children: t.category }),
56
+ /* @__PURE__ */ jsx(TableHead, { className: "text-right", children: t.fine }),
57
+ /* @__PURE__ */ jsx(TableHead, { children: t.status }),
58
+ /* @__PURE__ */ jsx(TableHead, { className: "text-right", children: t.actions })
59
+ ] }) }),
60
+ /* @__PURE__ */ jsx(TableBody, { children: pageItems.map((v) => {
61
+ const archived = v.active === false;
62
+ return /* @__PURE__ */ jsxs(TableRow, { className: cn(archived && "opacity-60"), children: [
63
+ /* @__PURE__ */ jsx(TableCell, { className: "font-mono text-xs", children: v.code }),
64
+ /* @__PURE__ */ jsx(TableCell, { className: "font-medium", children: v.title }),
65
+ /* @__PURE__ */ jsx(TableCell, { className: "hidden text-muted-foreground sm:table-cell", children: categoryLabel(v.category) }),
66
+ /* @__PURE__ */ jsx(TableCell, { className: "text-right", children: /* @__PURE__ */ jsx(Money, { value: v.basicFine }) }),
67
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Badge, { variant: archived ? "outline" : "secondary", children: archived ? t.archived : t.active }) }),
68
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1.5", children: [
69
+ /* @__PURE__ */ jsx(
70
+ ViolationDialog,
71
+ {
72
+ mode: "edit",
73
+ violation: v,
74
+ updateAction,
75
+ onDone: () => router.refresh()
76
+ }
77
+ ),
78
+ archived ? /* @__PURE__ */ jsx(
79
+ RestoreButton,
80
+ {
81
+ violation: v,
82
+ updateAction,
83
+ onDone: () => router.refresh()
84
+ }
85
+ ) : /* @__PURE__ */ jsx(
86
+ ArchiveButton,
87
+ {
88
+ violation: v,
89
+ deleteAction,
90
+ onDone: () => router.refresh()
91
+ }
92
+ ),
93
+ purgeAction && !used.has(v.code) ? /* @__PURE__ */ jsx(
94
+ DeleteButton,
95
+ {
96
+ violation: v,
97
+ purgeAction,
98
+ onDone: () => router.refresh()
99
+ }
100
+ ) : null
101
+ ] }) })
102
+ ] }, v.code);
103
+ }) })
104
+ ] }),
105
+ /* @__PURE__ */ jsx(
106
+ Pagination,
107
+ {
108
+ page,
109
+ totalPages,
110
+ from,
111
+ to,
112
+ total,
113
+ onPage: setPage
114
+ }
115
+ )
116
+ ] }) }) })
117
+ ] });
118
+ }
119
+ function ViolationDialog({
120
+ mode,
121
+ violation,
122
+ createAction,
123
+ updateAction,
124
+ onDone
125
+ }) {
126
+ const t = useCopy().admin.violationsPage;
127
+ const [open, setOpen] = React.useState(false);
128
+ const [code, setCode] = React.useState(violation?.code ?? "");
129
+ const [title, setTitle] = React.useState(violation?.title ?? "");
130
+ const [category, setCategory] = React.useState(
131
+ violation?.category ?? "TRAFFIC"
132
+ );
133
+ const [fine, setFine] = React.useState(
134
+ violation ? String(violation.basicFine) : ""
135
+ );
136
+ const [legalText, setLegalText] = React.useState(violation?.legalText ?? "");
137
+ const [submitting, setSubmitting] = React.useState(false);
138
+ function resetTo(v) {
139
+ setCode(v?.code ?? "");
140
+ setTitle(v?.title ?? "");
141
+ setCategory(v?.category ?? "TRAFFIC");
142
+ setFine(v ? String(v.basicFine) : "");
143
+ setLegalText(v?.legalText ?? "");
144
+ }
145
+ async function submit(e) {
146
+ e.preventDefault();
147
+ if (mode === "create" && !code.trim())
148
+ return toast.error(`${t.code} is required.`);
149
+ if (!title.trim()) return toast.error(`${t.titleLabel} is required.`);
150
+ const fineNum = Number(fine);
151
+ if (!Number.isFinite(fineNum) || fineNum < 0)
152
+ return toast.error(`${t.fine} must be a number \u2265 0.`);
153
+ setSubmitting(true);
154
+ try {
155
+ let res;
156
+ if (mode === "create") {
157
+ const input = {
158
+ code: code.trim(),
159
+ title: title.trim(),
160
+ category,
161
+ basicFine: fineNum,
162
+ legalText: legalText.trim() || void 0
163
+ };
164
+ res = await createAction?.(input);
165
+ } else {
166
+ res = await updateAction?.(violation.code, {
167
+ title: title.trim(),
168
+ category,
169
+ basicFine: fineNum,
170
+ legalText: legalText.trim() || void 0
171
+ });
172
+ }
173
+ if (res?.error) {
174
+ toast.error(res.error);
175
+ return;
176
+ }
177
+ toast.success(
178
+ `${mode === "create" ? t.create : t.editTitle}: ${title.trim()}`
179
+ );
180
+ setOpen(false);
181
+ onDone();
182
+ } finally {
183
+ setSubmitting(false);
184
+ }
185
+ }
186
+ return /* @__PURE__ */ jsxs(
187
+ Dialog,
188
+ {
189
+ open,
190
+ onOpenChange: (o) => {
191
+ if (o) resetTo(violation);
192
+ setOpen(o);
193
+ },
194
+ children: [
195
+ /* @__PURE__ */ jsxs(
196
+ DialogTrigger,
197
+ {
198
+ render: mode === "create" ? /* @__PURE__ */ jsx(Button, { className: "gap-1.5" }) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "gap-1.5" }),
199
+ children: [
200
+ mode === "create" ? /* @__PURE__ */ jsx(Plus, { className: "size-4" }) : /* @__PURE__ */ jsx(Pencil, { className: "size-3.5" }),
201
+ /* @__PURE__ */ jsx("span", { className: mode === "create" ? "" : "hidden sm:inline", children: mode === "create" ? t.newViolation : t.edit })
202
+ ]
203
+ }
204
+ ),
205
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
206
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
207
+ /* @__PURE__ */ jsx(DialogTitle, { children: mode === "create" ? t.newViolation : t.editTitle }),
208
+ /* @__PURE__ */ jsx(DialogDescription, { children: t.subtitle })
209
+ ] }),
210
+ /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-4", children: [
211
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
212
+ /* @__PURE__ */ jsx(Label, { htmlFor: "vio-code", children: t.code }),
213
+ /* @__PURE__ */ jsx(
214
+ Input,
215
+ {
216
+ id: "vio-code",
217
+ value: code,
218
+ onChange: (e) => setCode(e.target.value),
219
+ placeholder: t.codePlaceholder,
220
+ autoComplete: "off",
221
+ disabled: mode === "edit",
222
+ autoFocus: mode === "create"
223
+ }
224
+ )
225
+ ] }),
226
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
227
+ /* @__PURE__ */ jsx(Label, { htmlFor: "vio-title", children: t.titleLabel }),
228
+ /* @__PURE__ */ jsx(
229
+ Input,
230
+ {
231
+ id: "vio-title",
232
+ value: title,
233
+ onChange: (e) => setTitle(e.target.value),
234
+ placeholder: t.titlePlaceholder,
235
+ autoComplete: "off",
236
+ autoFocus: mode === "edit"
237
+ }
238
+ )
239
+ ] }),
240
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
241
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
242
+ /* @__PURE__ */ jsx(Label, { htmlFor: "vio-category", children: t.category }),
243
+ /* @__PURE__ */ jsxs(
244
+ "select",
245
+ {
246
+ id: "vio-category",
247
+ value: category,
248
+ onChange: (e) => setCategory(e.target.value),
249
+ className: fieldClass,
250
+ children: [
251
+ /* @__PURE__ */ jsx("option", { value: "TRAFFIC", children: t.categoryTraffic }),
252
+ /* @__PURE__ */ jsx("option", { value: "ORDINANCE", children: t.categoryOrdinance })
253
+ ]
254
+ }
255
+ )
256
+ ] }),
257
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
258
+ /* @__PURE__ */ jsx(Label, { htmlFor: "vio-fine", children: t.fine }),
259
+ /* @__PURE__ */ jsx(
260
+ Input,
261
+ {
262
+ id: "vio-fine",
263
+ type: "number",
264
+ min: "0",
265
+ step: "1",
266
+ inputMode: "numeric",
267
+ value: fine,
268
+ onChange: (e) => setFine(e.target.value),
269
+ placeholder: t.finePlaceholder,
270
+ autoComplete: "off"
271
+ }
272
+ )
273
+ ] })
274
+ ] }),
275
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
276
+ /* @__PURE__ */ jsx(Label, { htmlFor: "vio-legal", children: t.legalText }),
277
+ /* @__PURE__ */ jsx(
278
+ Textarea,
279
+ {
280
+ id: "vio-legal",
281
+ value: legalText,
282
+ onChange: (e) => setLegalText(e.target.value),
283
+ placeholder: t.legalTextPlaceholder,
284
+ rows: 2
285
+ }
286
+ )
287
+ ] }),
288
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
289
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline" }), children: t.cancel }),
290
+ /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: submitting, className: "gap-1.5", children: [
291
+ submitting ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : null,
292
+ submitting ? mode === "create" ? t.creating : t.saving : mode === "create" ? t.create : t.save
293
+ ] })
294
+ ] })
295
+ ] })
296
+ ] })
297
+ ]
298
+ }
299
+ );
300
+ }
301
+ function ArchiveButton({
302
+ violation,
303
+ deleteAction,
304
+ onDone
305
+ }) {
306
+ const t = useCopy().admin.violationsPage;
307
+ const [busy, setBusy] = React.useState(false);
308
+ async function archive() {
309
+ setBusy(true);
310
+ try {
311
+ const res = await deleteAction(violation.code);
312
+ if (res?.error) {
313
+ toast.error(res.error);
314
+ return;
315
+ }
316
+ toast.success(`${t.archive}: ${violation.title}`);
317
+ onDone();
318
+ } finally {
319
+ setBusy(false);
320
+ }
321
+ }
322
+ return /* @__PURE__ */ jsxs(Dialog, { children: [
323
+ /* @__PURE__ */ jsxs(
324
+ DialogTrigger,
325
+ {
326
+ render: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "gap-1.5" }),
327
+ children: [
328
+ /* @__PURE__ */ jsx(Archive, { className: "size-3.5" }),
329
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.archive })
330
+ ]
331
+ }
332
+ ),
333
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm", children: [
334
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
335
+ /* @__PURE__ */ jsx(DialogTitle, { children: t.archiveConfirmTitle }),
336
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
337
+ violation.title,
338
+ " \u2014 ",
339
+ t.archiveConfirmBody
340
+ ] })
341
+ ] }),
342
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
343
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { variant: "outline" }), children: t.cancel }),
344
+ /* @__PURE__ */ jsxs(
345
+ DialogClose,
346
+ {
347
+ render: /* @__PURE__ */ jsx(Button, { className: "gap-2", disabled: busy }),
348
+ onClick: archive,
349
+ children: [
350
+ /* @__PURE__ */ jsx(Archive, { className: "size-4" }),
351
+ t.archive
352
+ ]
353
+ }
354
+ )
355
+ ] })
356
+ ] })
357
+ ] });
358
+ }
359
+ function RestoreButton({
360
+ violation,
361
+ updateAction,
362
+ onDone
363
+ }) {
364
+ const t = useCopy().admin.violationsPage;
365
+ const [busy, setBusy] = React.useState(false);
366
+ async function restore() {
367
+ setBusy(true);
368
+ try {
369
+ const res = await updateAction(violation.code, { active: true });
370
+ if (res?.error) {
371
+ toast.error(res.error);
372
+ return;
373
+ }
374
+ toast.success(`${t.restore}: ${violation.title}`);
375
+ onDone();
376
+ } finally {
377
+ setBusy(false);
378
+ }
379
+ }
380
+ return /* @__PURE__ */ jsxs(
381
+ Button,
382
+ {
383
+ variant: "outline",
384
+ size: "sm",
385
+ className: "gap-1.5",
386
+ disabled: busy,
387
+ onClick: restore,
388
+ children: [
389
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(ArchiveRestore, { className: "size-3.5" }),
390
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.restore })
391
+ ]
392
+ }
393
+ );
394
+ }
395
+ function DeleteButton({
396
+ violation,
397
+ purgeAction,
398
+ onDone
399
+ }) {
400
+ const t = useCopy().admin.violationsPage;
401
+ const [busy, setBusy] = React.useState(false);
402
+ async function purge() {
403
+ setBusy(true);
404
+ try {
405
+ const res = await purgeAction(violation.code);
406
+ if (res?.error) {
407
+ toast.error(res.error);
408
+ return;
409
+ }
410
+ toast.success(`${t.delete}: ${violation.title}`);
411
+ onDone();
412
+ } finally {
413
+ setBusy(false);
414
+ }
415
+ }
416
+ return /* @__PURE__ */ jsxs(Dialog, { children: [
417
+ /* @__PURE__ */ jsxs(
418
+ DialogTrigger,
419
+ {
420
+ render: /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", className: "gap-1.5" }),
421
+ children: [
422
+ /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" }),
423
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: t.delete })
424
+ ]
425
+ }
426
+ ),
427
+ /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm", children: [
428
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
429
+ /* @__PURE__ */ jsx(DialogTitle, { children: t.deleteConfirmTitle }),
430
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
431
+ violation.title,
432
+ " \u2014 ",
433
+ t.deleteConfirmBody
434
+ ] })
435
+ ] }),
436
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
437
+ /* @__PURE__ */ jsx(DialogClose, { render: /* @__PURE__ */ jsx(Button, { variant: "outline" }), children: t.cancel }),
438
+ /* @__PURE__ */ jsxs(
439
+ DialogClose,
440
+ {
441
+ render: /* @__PURE__ */ jsx(Button, { variant: "destructive", className: "gap-2", disabled: busy }),
442
+ onClick: purge,
443
+ children: [
444
+ /* @__PURE__ */ jsx(Trash2, { className: "size-4" }),
445
+ t.delete
446
+ ]
447
+ }
448
+ )
449
+ ] })
450
+ ] })
451
+ ] });
452
+ }
453
+
454
+ export { ViolationsManager };
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { PaymentQrDialog } from '../chunk-TLG4C2XI.js';
2
+ import { PaymentQrDialog } from '../chunk-QCAURREW.js';
3
3
  import { Alert, AlertDescription } from '../chunk-EYFZWQ4J.js';
4
+ import '../chunk-M35R6JLA.js';
5
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../chunk-SETIN6XP.js';
4
6
  import { Separator } from '../chunk-NSCIBSCW.js';
5
7
  import { Money } from '../chunk-BVI5XDDA.js';
6
- import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../chunk-SETIN6XP.js';
7
- import '../chunk-M35R6JLA.js';
8
8
  import { Button } from '../chunk-I4WDVYHX.js';
9
9
  import { useOvrConfig, useCopy } from '../chunk-TJSNVTVB.js';
10
10
  import { cn } from '../chunk-77QBZC7J.js';
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- export { PaymentQrDialog } from '../chunk-TLG4C2XI.js';
2
+ export { PaymentQrDialog } from '../chunk-QCAURREW.js';
3
3
  import '../chunk-EYFZWQ4J.js';
4
- import '../chunk-BVI5XDDA.js';
5
4
  import '../chunk-M35R6JLA.js';
5
+ import '../chunk-BVI5XDDA.js';
6
6
  import '../chunk-I4WDVYHX.js';
7
7
  import '../chunk-TJSNVTVB.js';
8
8
  import '../chunk-77QBZC7J.js';
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { StatusBadge } from '../chunk-OE525ZER.js';
3
- import { Money } from '../chunk-BVI5XDDA.js';
4
- import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
5
3
  import '../chunk-55FQP2DO.js';
4
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../chunk-OWCGEEAZ.js';
5
+ import { Money } from '../chunk-BVI5XDDA.js';
6
6
  import { useFormatters, useCopy } from '../chunk-TJSNVTVB.js';
7
7
  import { cn } from '../chunk-77QBZC7J.js';
8
8
  import '../chunk-BI4EGLPG.js';
@@ -1,13 +1,13 @@
1
1
  "use client";
2
+ import { ViolationsTable } from '../chunk-6BH4EFP3.js';
3
+ import { StatusBadge } from '../chunk-OE525ZER.js';
4
+ import '../chunk-55FQP2DO.js';
5
+ import '../chunk-OWCGEEAZ.js';
2
6
  import { OfficialHeader } from '../chunk-NT72CQAI.js';
3
7
  import '../chunk-YC7G2IOZ.js';
4
8
  import '../chunk-WOPU6DI7.js';
5
- import { ViolationsTable } from '../chunk-IBZVIUNI.js';
6
9
  import { Separator } from '../chunk-NSCIBSCW.js';
7
- import { StatusBadge } from '../chunk-OE525ZER.js';
8
10
  import { Money } from '../chunk-BVI5XDDA.js';
9
- import '../chunk-OWCGEEAZ.js';
10
- import '../chunk-55FQP2DO.js';
11
11
  import { useFormatters, useOvrConfig } from '../chunk-TJSNVTVB.js';
12
12
  import { cn } from '../chunk-77QBZC7J.js';
13
13
  import { formalName, formatAddress } from '../chunk-BI4EGLPG.js';
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- export { ViolationsTable } from '../chunk-IBZVIUNI.js';
3
- import '../chunk-BVI5XDDA.js';
2
+ export { ViolationsTable } from '../chunk-6BH4EFP3.js';
4
3
  import '../chunk-OWCGEEAZ.js';
4
+ import '../chunk-BVI5XDDA.js';
5
5
  import '../chunk-TJSNVTVB.js';
6
6
  import '../chunk-77QBZC7J.js';
7
7
  import '../chunk-BI4EGLPG.js';
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { b as OvrConfig } from './schema-CdsFQxIg.js';
3
3
  import { a as Formatters } from './format-C7MSwUHK.js';
4
- import { D as Dictionary } from './types-BOgdk0Jw.js';
4
+ import { D as Dictionary } from './types-DNEO6wrO.js';
5
5
  import 'zod';
6
6
  import './types.js';
7
7
 
@@ -1,6 +1,6 @@
1
1
  import { b as OvrConfig } from './schema-CdsFQxIg.js';
2
2
  import { a as Formatters } from './format-C7MSwUHK.js';
3
- import { D as Dictionary, C as CopyOverrides } from './types-BOgdk0Jw.js';
3
+ import { D as Dictionary, C as CopyOverrides } from './types-DNEO6wrO.js';
4
4
  import 'zod';
5
5
  import './types.js';
6
6
 
package/dist/ui-server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mergeCopy } from './chunk-HGWPA7FU.js';
1
+ import { mergeCopy } from './chunk-6AXIWTMW.js';
2
2
  import { createFormatters } from './chunk-BI4EGLPG.js';
3
3
  import 'server-only';
4
4