@gelabs/ovr 0.1.1 → 0.2.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 (101) hide show
  1. package/dist/chunk-3KIDW4LT.js +13 -0
  2. package/dist/chunk-4SZXBT56.js +26 -0
  3. package/dist/chunk-55FQP2DO.js +46 -0
  4. package/dist/{chunk-JV7QQ22Q.js → chunk-5YYR37CF.js} +10 -1
  5. package/dist/chunk-5Z2IAD5I.js +72 -0
  6. package/dist/chunk-BBQBKQA4.js +31 -0
  7. package/dist/chunk-BIQ2J75Y.js +54 -0
  8. package/dist/chunk-E2D7QT6N.js +92 -0
  9. package/dist/chunk-EGKFELO3.js +15 -0
  10. package/dist/chunk-EYFZWQ4J.js +74 -0
  11. package/dist/chunk-GDOCD7LT.js +46 -0
  12. package/dist/chunk-IF5UAVIE.js +25 -0
  13. package/dist/chunk-JEYT63LE.js +25 -0
  14. package/dist/chunk-K3KIBHJF.js +20 -0
  15. package/dist/chunk-M35R6JLA.js +142 -0
  16. package/dist/chunk-MDTRBOPQ.js +22 -0
  17. package/dist/chunk-NSCIBSCW.js +24 -0
  18. package/dist/chunk-OE525ZER.js +31 -0
  19. package/dist/chunk-OWCGEEAZ.js +107 -0
  20. package/dist/chunk-QCRVT2SS.js +18 -0
  21. package/dist/chunk-SETIN6XP.js +95 -0
  22. package/dist/chunk-WOPU6DI7.js +77 -0
  23. package/dist/chunk-XQTVSNHC.js +18 -0
  24. package/dist/core-i18n.d.ts +2 -2
  25. package/dist/core-i18n.js +1 -1
  26. package/dist/data-db.d.ts +1 -4
  27. package/dist/data-mock-store.js +53 -1
  28. package/dist/data-prisma-store.d.ts +3 -1
  29. package/dist/data-prisma-store.js +84 -1
  30. package/dist/data-seed-runner.d.ts +2 -1
  31. package/dist/data.d.ts +13 -1
  32. package/dist/offline.d.ts +126 -0
  33. package/dist/offline.js +376 -0
  34. package/dist/{types-DrrNO_Ak.d.ts → types-CtBC5-TW.d.ts} +8 -0
  35. package/dist/ui-components-admin/admin-nav.d.ts +7 -0
  36. package/dist/ui-components-admin/admin-nav.js +83 -0
  37. package/dist/ui-components-admin/issuance-form.d.ts +15 -0
  38. package/dist/ui-components-admin/issuance-form.js +367 -0
  39. package/dist/ui-components-admin/stat-card.d.ts +10 -0
  40. package/dist/ui-components-admin/stat-card.js +29 -0
  41. package/dist/ui-components-admin/ticket-preview.d.ts +9 -0
  42. package/dist/ui-components-admin/ticket-preview.js +13 -0
  43. package/dist/ui-components-admin/tickets-table.d.ts +8 -0
  44. package/dist/ui-components-admin/tickets-table.js +50 -0
  45. package/dist/ui-components-citizen/citizen-nav.d.ts +5 -0
  46. package/dist/ui-components-citizen/citizen-nav.js +33 -0
  47. package/dist/ui-components-citizen/payment-form.d.ts +14 -0
  48. package/dist/ui-components-citizen/payment-form.js +168 -0
  49. package/dist/ui-components-citizen/payment-qr-dialog.d.ts +30 -0
  50. package/dist/ui-components-citizen/payment-qr-dialog.js +9 -0
  51. package/dist/ui-components-citizen/ticket-not-found.d.ts +5 -0
  52. package/dist/ui-components-citizen/ticket-not-found.js +21 -0
  53. package/dist/ui-components-citizen/violation-history-table.d.ts +16 -0
  54. package/dist/ui-components-citizen/violation-history-table.js +77 -0
  55. package/dist/ui-components-shared/amount-summary.d.ts +8 -0
  56. package/dist/ui-components-shared/amount-summary.js +7 -0
  57. package/dist/ui-components-shared/money.d.ts +9 -0
  58. package/dist/ui-components-shared/money.js +5 -0
  59. package/dist/ui-components-shared/municipal-seal.d.ts +11 -0
  60. package/dist/ui-components-shared/municipal-seal.js +6 -0
  61. package/dist/ui-components-shared/official-header.d.ts +5 -0
  62. package/dist/ui-components-shared/official-header.js +7 -0
  63. package/dist/ui-components-shared/print-button.d.ts +8 -0
  64. package/dist/ui-components-shared/print-button.js +17 -0
  65. package/dist/ui-components-shared/seal.d.ts +12 -0
  66. package/dist/ui-components-shared/seal.js +2 -0
  67. package/dist/ui-components-shared/site-header.d.ts +13 -0
  68. package/dist/ui-components-shared/site-header.js +40 -0
  69. package/dist/ui-components-shared/sonner.d.ts +6 -0
  70. package/dist/ui-components-shared/sonner.js +38 -0
  71. package/dist/ui-components-shared/status-badge.d.ts +9 -0
  72. package/dist/ui-components-shared/status-badge.js +3 -0
  73. package/dist/ui-components-shared/theme-toggle.d.ts +7 -0
  74. package/dist/ui-components-shared/theme-toggle.js +6 -0
  75. package/dist/ui-components-shared/ticket-receipt.d.ts +12 -0
  76. package/dist/ui-components-shared/ticket-receipt.js +158 -0
  77. package/dist/ui-components-shared/violations-table.d.ts +8 -0
  78. package/dist/ui-components-shared/violations-table.js +7 -0
  79. package/dist/ui-components-ui/alert.js +2 -74
  80. package/dist/ui-components-ui/badge.d.ts +1 -1
  81. package/dist/ui-components-ui/badge.js +2 -46
  82. package/dist/ui-components-ui/button.d.ts +2 -2
  83. package/dist/ui-components-ui/card.js +2 -95
  84. package/dist/ui-components-ui/checkbox.js +2 -31
  85. package/dist/ui-components-ui/dialog.js +3 -142
  86. package/dist/ui-components-ui/input.js +2 -20
  87. package/dist/ui-components-ui/label.js +2 -18
  88. package/dist/ui-components-ui/separator.js +2 -24
  89. package/dist/ui-components-ui/skeleton.js +2 -15
  90. package/dist/ui-components-ui/table.js +2 -107
  91. package/dist/ui-components-ui/textarea.js +2 -18
  92. package/dist/ui-config.d.ts +19 -2
  93. package/dist/ui-config.js +2 -36
  94. package/dist/ui-server.d.ts +1 -1
  95. package/dist/ui-server.js +1 -1
  96. package/package.json +24 -4
  97. package/prisma/migrations/20260611112111_init/migration.sql +142 -0
  98. package/prisma/migrations/20260611112121_add_ticket_sequence/migration.sql +4 -0
  99. package/prisma/migrations/20260612000000_split_violator_address/migration.sql +26 -0
  100. package/prisma/migrations/20260612000100_barangay_optional/migration.sql +2 -0
  101. package/prisma/migrations/migration_lock.toml +3 -0
@@ -0,0 +1,13 @@
1
+ import { useFormatters } from './chunk-E2D7QT6N.js';
2
+ import { cn } from './chunk-77QBZC7J.js';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ function Money({
6
+ value,
7
+ className
8
+ }) {
9
+ const { formatPeso } = useFormatters();
10
+ return /* @__PURE__ */ jsx("span", { className: cn("tabular-nums", className), children: formatPeso(value) });
11
+ }
12
+
13
+ export { Money };
@@ -0,0 +1,26 @@
1
+ import { MunicipalSeal } from './chunk-IF5UAVIE.js';
2
+ import { useOvrConfig } from './chunk-E2D7QT6N.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ function OfficialHeader() {
6
+ const { municipality, offices } = useOvrConfig();
7
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4 text-center", children: [
8
+ /* @__PURE__ */ jsx(MunicipalSeal, { className: "size-16 shrink-0" }),
9
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
10
+ /* @__PURE__ */ jsx("p", { className: "text-[0.7rem] uppercase tracking-wide text-muted-foreground", children: municipality.country }),
11
+ /* @__PURE__ */ jsxs("p", { className: "text-[0.7rem] uppercase tracking-wide text-muted-foreground", children: [
12
+ "Province of ",
13
+ municipality.province
14
+ ] }),
15
+ /* @__PURE__ */ jsx("p", { className: "font-heading text-lg font-semibold leading-tight", children: municipality.name }),
16
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
17
+ offices.enforcement.name,
18
+ " (",
19
+ offices.enforcement.abbr,
20
+ ")"
21
+ ] })
22
+ ] })
23
+ ] });
24
+ }
25
+
26
+ export { OfficialHeader };
@@ -0,0 +1,46 @@
1
+ import { cn } from './chunk-77QBZC7J.js';
2
+ import { mergeProps } from '@base-ui/react/merge-props';
3
+ import { useRender } from '@base-ui/react/use-render';
4
+ import { cva } from 'class-variance-authority';
5
+
6
+ var badgeVariants = cva(
7
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
12
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
13
+ destructive: "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
14
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
15
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
16
+ link: "text-primary underline-offset-4 hover:underline"
17
+ }
18
+ },
19
+ defaultVariants: {
20
+ variant: "default"
21
+ }
22
+ }
23
+ );
24
+ function Badge({
25
+ className,
26
+ variant = "default",
27
+ render,
28
+ ...props
29
+ }) {
30
+ return useRender({
31
+ defaultTagName: "span",
32
+ props: mergeProps(
33
+ {
34
+ className: cn(badgeVariants({ variant }), className)
35
+ },
36
+ props
37
+ ),
38
+ render,
39
+ state: {
40
+ slot: "badge",
41
+ variant
42
+ }
43
+ });
44
+ }
45
+
46
+ export { Badge, badgeVariants };
@@ -95,7 +95,16 @@ var baseCopy = {
95
95
  dashboard: "Dashboard",
96
96
  tickets: "Tickets",
97
97
  issueTicket: "Issue ticket",
98
- newTicketTitle: "Issue Violation Ticket"
98
+ newTicketTitle: "Issue Violation Ticket",
99
+ sync: {
100
+ offline: "Offline",
101
+ syncing: "Syncing\u2026",
102
+ synced: "Synced",
103
+ pending: "pending sync",
104
+ // e.g. "3 pending sync"
105
+ notSynced: "Not synced",
106
+ willSync: "Saved on this device \u2014 will sync when back online."
107
+ }
99
108
  }
100
109
  };
101
110
 
@@ -0,0 +1,72 @@
1
+ import { Alert, AlertDescription } from './chunk-EYFZWQ4J.js';
2
+ import { Money } from './chunk-3KIDW4LT.js';
3
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './chunk-M35R6JLA.js';
4
+ import { Button } from './chunk-I4WDVYHX.js';
5
+ import { useCopy } from './chunk-E2D7QT6N.js';
6
+ import * as React from 'react';
7
+ import { toast } from 'sonner';
8
+ import { QrCode, Loader2, Lock } from 'lucide-react';
9
+ import { jsx, jsxs } from 'react/jsx-runtime';
10
+
11
+ function PaymentQrDialog({
12
+ open,
13
+ onOpenChange,
14
+ method,
15
+ methodLabel,
16
+ amount,
17
+ ovrTicketNo,
18
+ lastName,
19
+ payAction
20
+ }) {
21
+ const copy = useCopy();
22
+ const t = copy.citizen.pay.qr;
23
+ const [error, setError] = React.useState(null);
24
+ const [isPending, startTransition] = React.useTransition();
25
+ function confirm() {
26
+ setError(null);
27
+ startTransition(async () => {
28
+ const res = await payAction({ ovrTicketNo, lastName, method });
29
+ if (res?.error) {
30
+ setError(res.error);
31
+ toast.error(res.error);
32
+ }
33
+ });
34
+ }
35
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
36
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
37
+ /* @__PURE__ */ jsxs(DialogTitle, { children: [
38
+ t.title,
39
+ " \xB7 ",
40
+ methodLabel
41
+ ] }),
42
+ /* @__PURE__ */ jsx(DialogDescription, { children: t.instruction })
43
+ ] }),
44
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 py-1", children: [
45
+ /* @__PURE__ */ jsx("div", { className: "flex size-44 items-center justify-center rounded-xl border-2 border-dashed bg-muted/30", children: /* @__PURE__ */ jsx(QrCode, { className: "size-24 text-muted-foreground", "aria-hidden": true }) }),
46
+ /* @__PURE__ */ jsx("p", { className: "max-w-xs text-center text-xs text-muted-foreground", children: t.placeholderNote }),
47
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-baseline justify-between rounded-lg border bg-muted/20 px-3 py-2 text-sm", children: [
48
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t.amountToPay }),
49
+ /* @__PURE__ */ jsx(Money, { value: amount, className: "text-lg font-bold" })
50
+ ] }),
51
+ /* @__PURE__ */ jsx("p", { className: "font-mono text-xs text-muted-foreground", children: ovrTicketNo })
52
+ ] }),
53
+ error ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null,
54
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [
55
+ /* @__PURE__ */ jsx(
56
+ Button,
57
+ {
58
+ variant: "outline",
59
+ onClick: () => onOpenChange(false),
60
+ disabled: isPending,
61
+ children: t.cancel
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsxs(Button, { onClick: confirm, disabled: isPending, className: "gap-2", children: [
65
+ isPending ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Lock, { className: "size-4" }),
66
+ t.confirm
67
+ ] })
68
+ ] })
69
+ ] }) });
70
+ }
71
+
72
+ export { PaymentQrDialog };
@@ -0,0 +1,31 @@
1
+ import { cn } from './chunk-77QBZC7J.js';
2
+ import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
3
+ import { CheckIcon } from 'lucide-react';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ function Checkbox({ className, ...props }) {
7
+ return /* @__PURE__ */ jsx(
8
+ Checkbox$1.Root,
9
+ {
10
+ "data-slot": "checkbox",
11
+ className: cn(
12
+ "peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
13
+ className
14
+ ),
15
+ ...props,
16
+ children: /* @__PURE__ */ jsx(
17
+ Checkbox$1.Indicator,
18
+ {
19
+ "data-slot": "checkbox-indicator",
20
+ className: "grid place-content-center text-current transition-none [&>svg]:size-3.5",
21
+ children: /* @__PURE__ */ jsx(
22
+ CheckIcon,
23
+ {}
24
+ )
25
+ }
26
+ )
27
+ }
28
+ );
29
+ }
30
+
31
+ export { Checkbox };
@@ -0,0 +1,54 @@
1
+ import { Separator } from './chunk-NSCIBSCW.js';
2
+ import { Money } from './chunk-3KIDW4LT.js';
3
+ import { useFormatters, useOvrConfig } from './chunk-E2D7QT6N.js';
4
+ import { cn } from './chunk-77QBZC7J.js';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function Row({
8
+ label,
9
+ value,
10
+ muted
11
+ }) {
12
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
13
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
14
+ /* @__PURE__ */ jsx(Money, { value, className: cn(muted && "text-muted-foreground") })
15
+ ] });
16
+ }
17
+ function AmountSummary({ ticket }) {
18
+ const { formatDate } = useFormatters();
19
+ const { rules } = useOvrConfig();
20
+ const paid = ticket.status === "PAID";
21
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5 text-sm", children: [
22
+ /* @__PURE__ */ jsx(Row, { label: "Basic fines", value: ticket.basicFinesTotal }),
23
+ /* @__PURE__ */ jsx(
24
+ Row,
25
+ {
26
+ label: "Penalty / surcharge",
27
+ value: ticket.penaltyAmount,
28
+ muted: ticket.penaltyAmount === 0
29
+ }
30
+ ),
31
+ /* @__PURE__ */ jsx(Separator, {}),
32
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
33
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: paid ? "Amount paid" : "Total amount due" }),
34
+ /* @__PURE__ */ jsx(
35
+ Money,
36
+ {
37
+ value: paid ? ticket.payment?.amount ?? 0 : ticket.totalAmountDue,
38
+ className: "text-xl font-semibold"
39
+ }
40
+ )
41
+ ] }),
42
+ !paid ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
43
+ "Pay on or before",
44
+ " ",
45
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatDate(ticket.dueDate) }),
46
+ " ",
47
+ "to avoid a ",
48
+ rules.surchargeRatePerMonth * 100,
49
+ "% monthly surcharge."
50
+ ] }) : null
51
+ ] });
52
+ }
53
+
54
+ export { AmountSummary };
@@ -0,0 +1,92 @@
1
+ import { createFormatters } from './chunk-B634JHKZ.js';
2
+ import * as React2 from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var OvrConfigContext = React2.createContext(null);
6
+ function OvrConfigProvider({
7
+ config,
8
+ copy,
9
+ children
10
+ }) {
11
+ const value = React2.useMemo(
12
+ () => ({ config, copy, formatters: createFormatters(config.rules) }),
13
+ [config, copy]
14
+ );
15
+ return /* @__PURE__ */ jsx(OvrConfigContext.Provider, { value, children });
16
+ }
17
+ function useCtx() {
18
+ const ctx = React2.useContext(OvrConfigContext);
19
+ if (!ctx) {
20
+ throw new Error(
21
+ "[@gelabs/ovr-ui] hook used outside <OvrConfigProvider> \u2014 mount it in your root layout."
22
+ );
23
+ }
24
+ return ctx;
25
+ }
26
+ function useOvrConfig() {
27
+ return useCtx().config;
28
+ }
29
+ function useCopy() {
30
+ return useCtx().copy;
31
+ }
32
+ function useFormatters() {
33
+ return useCtx().formatters;
34
+ }
35
+ var STORAGE_KEY = "theme";
36
+ var ThemeContext = React2.createContext(null);
37
+ function getSystemTheme() {
38
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
39
+ }
40
+ function applyTheme(theme) {
41
+ const resolved = theme === "system" ? getSystemTheme() : theme;
42
+ const root = document.documentElement;
43
+ root.classList.toggle("dark", resolved === "dark");
44
+ root.style.colorScheme = resolved;
45
+ return resolved;
46
+ }
47
+ function withoutTransitions(fn) {
48
+ const style = document.createElement("style");
49
+ style.appendChild(
50
+ document.createTextNode("*,*::before,*::after{transition:none!important}")
51
+ );
52
+ document.head.appendChild(style);
53
+ fn();
54
+ window.getComputedStyle(document.body);
55
+ requestAnimationFrame(() => document.head.removeChild(style));
56
+ }
57
+ function ThemeProvider({ children }) {
58
+ const [theme, setThemeState] = React2.useState("system");
59
+ const [resolvedTheme, setResolvedTheme] = React2.useState("light");
60
+ React2.useEffect(() => {
61
+ const stored = localStorage.getItem(STORAGE_KEY) ?? "system";
62
+ setThemeState(stored);
63
+ setResolvedTheme(applyTheme(stored));
64
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
65
+ const onChange = () => {
66
+ const current = localStorage.getItem(STORAGE_KEY) ?? "system";
67
+ if (current === "system") setResolvedTheme(applyTheme("system"));
68
+ };
69
+ mq.addEventListener("change", onChange);
70
+ return () => mq.removeEventListener("change", onChange);
71
+ }, []);
72
+ const setTheme = React2.useCallback((next) => {
73
+ try {
74
+ localStorage.setItem(STORAGE_KEY, next);
75
+ } catch {
76
+ }
77
+ setThemeState(next);
78
+ withoutTransitions(() => setResolvedTheme(applyTheme(next)));
79
+ }, []);
80
+ const value = React2.useMemo(
81
+ () => ({ theme, resolvedTheme, setTheme }),
82
+ [theme, resolvedTheme, setTheme]
83
+ );
84
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
85
+ }
86
+ function useTheme() {
87
+ const ctx = React2.useContext(ThemeContext);
88
+ if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
89
+ return ctx;
90
+ }
91
+
92
+ export { OvrConfigProvider, ThemeProvider, useCopy, useFormatters, useOvrConfig, useTheme };
@@ -0,0 +1,15 @@
1
+ import { cn } from './chunk-77QBZC7J.js';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ function Skeleton({ className, ...props }) {
5
+ return /* @__PURE__ */ jsx(
6
+ "div",
7
+ {
8
+ "data-slot": "skeleton",
9
+ className: cn("animate-pulse rounded-md bg-muted", className),
10
+ ...props
11
+ }
12
+ );
13
+ }
14
+
15
+ export { Skeleton };
@@ -0,0 +1,74 @@
1
+ import { cn } from './chunk-77QBZC7J.js';
2
+ import { cva } from 'class-variance-authority';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var alertVariants = cva(
6
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-card text-card-foreground",
11
+ destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current"
12
+ }
13
+ },
14
+ defaultVariants: {
15
+ variant: "default"
16
+ }
17
+ }
18
+ );
19
+ function Alert({
20
+ className,
21
+ variant,
22
+ ...props
23
+ }) {
24
+ return /* @__PURE__ */ jsx(
25
+ "div",
26
+ {
27
+ "data-slot": "alert",
28
+ role: "alert",
29
+ className: cn(alertVariants({ variant }), className),
30
+ ...props
31
+ }
32
+ );
33
+ }
34
+ function AlertTitle({ className, ...props }) {
35
+ return /* @__PURE__ */ jsx(
36
+ "div",
37
+ {
38
+ "data-slot": "alert-title",
39
+ className: cn(
40
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
41
+ className
42
+ ),
43
+ ...props
44
+ }
45
+ );
46
+ }
47
+ function AlertDescription({
48
+ className,
49
+ ...props
50
+ }) {
51
+ return /* @__PURE__ */ jsx(
52
+ "div",
53
+ {
54
+ "data-slot": "alert-description",
55
+ className: cn(
56
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
57
+ className
58
+ ),
59
+ ...props
60
+ }
61
+ );
62
+ }
63
+ function AlertAction({ className, ...props }) {
64
+ return /* @__PURE__ */ jsx(
65
+ "div",
66
+ {
67
+ "data-slot": "alert-action",
68
+ className: cn("absolute top-2 right-2", className),
69
+ ...props
70
+ }
71
+ );
72
+ }
73
+
74
+ export { Alert, AlertAction, AlertDescription, AlertTitle };
@@ -0,0 +1,46 @@
1
+ import { ViolationsTable } from './chunk-JEYT63LE.js';
2
+ import { AmountSummary } from './chunk-BIQ2J75Y.js';
3
+ import { Card, CardHeader, CardTitle, CardContent } from './chunk-SETIN6XP.js';
4
+ import { StatusBadge } from './chunk-OE525ZER.js';
5
+ import { useFormatters } from './chunk-E2D7QT6N.js';
6
+ import { formalName, formatAddress } from './chunk-B634JHKZ.js';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ function Field({ label, value }) {
10
+ return /* @__PURE__ */ jsxs("div", { children: [
11
+ /* @__PURE__ */ jsx("dt", { className: "text-[0.7rem] uppercase tracking-wide text-muted-foreground", children: label }),
12
+ /* @__PURE__ */ jsx("dd", { className: "truncate font-medium", children: value || "\u2014" })
13
+ ] });
14
+ }
15
+ function TicketPreview({ ticket }) {
16
+ const { formatDateTime } = useFormatters();
17
+ const hasName = ticket.violator.firstName || ticket.violator.lastName;
18
+ return /* @__PURE__ */ jsxs(Card, { children: [
19
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
20
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
21
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Live ticket preview" }),
22
+ /* @__PURE__ */ jsx(StatusBadge, { status: ticket.status })
23
+ ] }),
24
+ /* @__PURE__ */ jsx("p", { className: "font-heading text-lg font-semibold uppercase", children: hasName ? formalName(ticket.violator) : "New violator" }),
25
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-muted-foreground", children: formatAddress(ticket.violator) || "\u2014" })
26
+ ] }),
27
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
28
+ /* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-2 gap-3 text-xs", children: [
29
+ /* @__PURE__ */ jsx(Field, { label: "OVR Ticket No.", value: ticket.ovrTicketNo }),
30
+ /* @__PURE__ */ jsx(Field, { label: "License No.", value: ticket.violator.licenseNumber }),
31
+ /* @__PURE__ */ jsx(
32
+ Field,
33
+ {
34
+ label: "Apprehended",
35
+ value: ticket.apprehendedAt ? formatDateTime(ticket.apprehendedAt) : "\u2014"
36
+ }
37
+ ),
38
+ /* @__PURE__ */ jsx(Field, { label: "Officer", value: ticket.officer.name })
39
+ ] }),
40
+ ticket.violations.length > 0 ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border", children: /* @__PURE__ */ jsx(ViolationsTable, { violations: ticket.violations }) }) : /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed p-4 text-center text-xs text-muted-foreground", children: "No violations selected yet." }),
41
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-muted/20 p-3", children: /* @__PURE__ */ jsx(AmountSummary, { ticket }) })
42
+ ] })
43
+ ] });
44
+ }
45
+
46
+ export { TicketPreview };
@@ -0,0 +1,25 @@
1
+ import { Seal } from './chunk-WOPU6DI7.js';
2
+ import { useOvrConfig } from './chunk-E2D7QT6N.js';
3
+ import { cn } from './chunk-77QBZC7J.js';
4
+ import * as React from 'react';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ function MunicipalSeal({ className }) {
8
+ const { municipality } = useOvrConfig();
9
+ const [failed, setFailed] = React.useState(false);
10
+ if (failed) return /* @__PURE__ */ jsx(Seal, { className });
11
+ return (
12
+ // eslint-disable-next-line @next/next/no-img-element
13
+ /* @__PURE__ */ jsx(
14
+ "img",
15
+ {
16
+ src: municipality.sealSrc,
17
+ alt: `${municipality.name} seal`,
18
+ className: cn("object-contain", className),
19
+ onError: () => setFailed(true)
20
+ }
21
+ )
22
+ );
23
+ }
24
+
25
+ export { MunicipalSeal };
@@ -0,0 +1,25 @@
1
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from './chunk-OWCGEEAZ.js';
2
+ import { Money } from './chunk-3KIDW4LT.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ function ViolationsTable({
6
+ violations
7
+ }) {
8
+ return /* @__PURE__ */ jsxs(Table, { children: [
9
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
10
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[38%]", children: "Ordinance / Code" }),
11
+ /* @__PURE__ */ jsx(TableHead, { children: "Violation" }),
12
+ /* @__PURE__ */ jsx(TableHead, { className: "text-right", children: "Basic Fine" })
13
+ ] }) }),
14
+ /* @__PURE__ */ jsx(TableBody, { children: violations.map((v, i) => /* @__PURE__ */ jsxs(TableRow, { children: [
15
+ /* @__PURE__ */ jsx(TableCell, { className: "align-top whitespace-normal font-mono text-xs text-muted-foreground", children: v.catalogCode }),
16
+ /* @__PURE__ */ jsxs(TableCell, { className: "align-top whitespace-normal", children: [
17
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: v.title }),
18
+ v.details ? /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: v.details }) : null
19
+ ] }),
20
+ /* @__PURE__ */ jsx(TableCell, { className: "align-top text-right", children: /* @__PURE__ */ jsx(Money, { value: v.basicFine }) })
21
+ ] }, `${v.catalogCode}-${i}`)) })
22
+ ] });
23
+ }
24
+
25
+ export { ViolationsTable };
@@ -0,0 +1,20 @@
1
+ import { cn } from './chunk-77QBZC7J.js';
2
+ import { Input as Input$1 } from '@base-ui/react/input';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ function Input({ className, type, ...props }) {
6
+ return /* @__PURE__ */ jsx(
7
+ Input$1,
8
+ {
9
+ type,
10
+ "data-slot": "input",
11
+ className: cn(
12
+ "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
13
+ className
14
+ ),
15
+ ...props
16
+ }
17
+ );
18
+ }
19
+
20
+ export { Input };