@gelabs/ovr 0.1.1 → 0.2.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.
- package/dist/chunk-3KIDW4LT.js +13 -0
- package/dist/chunk-4SZXBT56.js +26 -0
- package/dist/chunk-55FQP2DO.js +46 -0
- package/dist/{chunk-JV7QQ22Q.js → chunk-5YYR37CF.js} +10 -1
- package/dist/chunk-5Z2IAD5I.js +72 -0
- package/dist/chunk-BBQBKQA4.js +31 -0
- package/dist/chunk-BIQ2J75Y.js +54 -0
- package/dist/chunk-E2D7QT6N.js +92 -0
- package/dist/chunk-EGKFELO3.js +15 -0
- package/dist/chunk-EYFZWQ4J.js +74 -0
- package/dist/chunk-GDOCD7LT.js +46 -0
- package/dist/chunk-IF5UAVIE.js +25 -0
- package/dist/chunk-JEYT63LE.js +25 -0
- package/dist/chunk-K3KIBHJF.js +20 -0
- package/dist/chunk-M35R6JLA.js +142 -0
- package/dist/chunk-MDTRBOPQ.js +22 -0
- package/dist/chunk-NSCIBSCW.js +24 -0
- package/dist/chunk-OE525ZER.js +31 -0
- package/dist/chunk-OWCGEEAZ.js +107 -0
- package/dist/chunk-QCRVT2SS.js +18 -0
- package/dist/chunk-SETIN6XP.js +95 -0
- package/dist/chunk-WOPU6DI7.js +77 -0
- package/dist/chunk-XQTVSNHC.js +18 -0
- package/dist/core-i18n.d.ts +2 -2
- package/dist/core-i18n.js +1 -1
- package/dist/data-db.d.ts +1 -4
- package/dist/data-mock-store.js +53 -1
- package/dist/data-prisma-store.d.ts +3 -1
- package/dist/data-prisma-store.js +84 -1
- package/dist/data-seed-runner.d.ts +2 -1
- package/dist/data.d.ts +13 -1
- package/dist/offline.d.ts +126 -0
- package/dist/offline.js +376 -0
- package/dist/{types-DrrNO_Ak.d.ts → types-CtBC5-TW.d.ts} +8 -0
- package/dist/ui-components-admin/admin-nav.d.ts +7 -0
- package/dist/ui-components-admin/admin-nav.js +83 -0
- package/dist/ui-components-admin/issuance-form.d.ts +15 -0
- package/dist/ui-components-admin/issuance-form.js +367 -0
- package/dist/ui-components-admin/stat-card.d.ts +10 -0
- package/dist/ui-components-admin/stat-card.js +29 -0
- package/dist/ui-components-admin/ticket-preview.d.ts +9 -0
- package/dist/ui-components-admin/ticket-preview.js +13 -0
- package/dist/ui-components-admin/tickets-table.d.ts +8 -0
- package/dist/ui-components-admin/tickets-table.js +50 -0
- package/dist/ui-components-citizen/citizen-nav.d.ts +5 -0
- package/dist/ui-components-citizen/citizen-nav.js +33 -0
- package/dist/ui-components-citizen/payment-form.d.ts +14 -0
- package/dist/ui-components-citizen/payment-form.js +168 -0
- package/dist/ui-components-citizen/payment-qr-dialog.d.ts +30 -0
- package/dist/ui-components-citizen/payment-qr-dialog.js +9 -0
- package/dist/ui-components-citizen/ticket-not-found.d.ts +5 -0
- package/dist/ui-components-citizen/ticket-not-found.js +21 -0
- package/dist/ui-components-citizen/violation-history-table.d.ts +16 -0
- package/dist/ui-components-citizen/violation-history-table.js +77 -0
- package/dist/ui-components-shared/amount-summary.d.ts +8 -0
- package/dist/ui-components-shared/amount-summary.js +7 -0
- package/dist/ui-components-shared/money.d.ts +9 -0
- package/dist/ui-components-shared/money.js +5 -0
- package/dist/ui-components-shared/municipal-seal.d.ts +11 -0
- package/dist/ui-components-shared/municipal-seal.js +6 -0
- package/dist/ui-components-shared/official-header.d.ts +5 -0
- package/dist/ui-components-shared/official-header.js +7 -0
- package/dist/ui-components-shared/print-button.d.ts +8 -0
- package/dist/ui-components-shared/print-button.js +17 -0
- package/dist/ui-components-shared/seal.d.ts +12 -0
- package/dist/ui-components-shared/seal.js +2 -0
- package/dist/ui-components-shared/site-header.d.ts +13 -0
- package/dist/ui-components-shared/site-header.js +40 -0
- package/dist/ui-components-shared/sonner.d.ts +6 -0
- package/dist/ui-components-shared/sonner.js +38 -0
- package/dist/ui-components-shared/status-badge.d.ts +9 -0
- package/dist/ui-components-shared/status-badge.js +3 -0
- package/dist/ui-components-shared/theme-toggle.d.ts +7 -0
- package/dist/ui-components-shared/theme-toggle.js +6 -0
- package/dist/ui-components-shared/ticket-receipt.d.ts +12 -0
- package/dist/ui-components-shared/ticket-receipt.js +158 -0
- package/dist/ui-components-shared/violations-table.d.ts +8 -0
- package/dist/ui-components-shared/violations-table.js +7 -0
- package/dist/ui-components-ui/alert.js +2 -74
- package/dist/ui-components-ui/badge.d.ts +1 -1
- package/dist/ui-components-ui/badge.js +2 -46
- package/dist/ui-components-ui/button.d.ts +2 -2
- package/dist/ui-components-ui/card.js +2 -95
- package/dist/ui-components-ui/checkbox.js +2 -31
- package/dist/ui-components-ui/dialog.js +3 -142
- package/dist/ui-components-ui/input.js +2 -20
- package/dist/ui-components-ui/label.js +2 -18
- package/dist/ui-components-ui/separator.js +2 -24
- package/dist/ui-components-ui/skeleton.js +2 -15
- package/dist/ui-components-ui/table.js +2 -107
- package/dist/ui-components-ui/textarea.js +2 -18
- package/dist/ui-config.d.ts +19 -2
- package/dist/ui-config.js +2 -36
- package/dist/ui-server.d.ts +1 -1
- package/dist/ui-server.js +1 -1
- package/package.json +24 -4
|
@@ -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 };
|