@gelabs/ovr 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/auth-auth.js +1 -1
  2. package/dist/auth.js +1 -1
  3. package/dist/{chunk-MDTRBOPQ.js → chunk-2C3VCTYJ.js} +1 -1
  4. package/dist/chunk-3YKVH4Y7.js +126 -0
  5. package/dist/chunk-6YFZLXFP.js +84 -0
  6. package/dist/{chunk-3NZ2XUBO.js → chunk-AJ2RZTVX.js} +9 -2
  7. package/dist/chunk-BI4EGLPG.js +298 -0
  8. package/dist/{chunk-3KIDW4LT.js → chunk-BVI5XDDA.js} +1 -1
  9. package/dist/{chunk-BIQ2J75Y.js → chunk-GLIK5BHP.js} +2 -2
  10. package/dist/{chunk-5YYR37CF.js → chunk-HGWPA7FU.js} +119 -0
  11. package/dist/{chunk-JEYT63LE.js → chunk-IBZVIUNI.js} +1 -1
  12. package/dist/chunk-IS3THKTE.js +89 -0
  13. package/dist/{chunk-4SZXBT56.js → chunk-NT72CQAI.js} +2 -2
  14. package/dist/{chunk-E2D7QT6N.js → chunk-TJSNVTVB.js} +1 -1
  15. package/dist/{chunk-5Z2IAD5I.js → chunk-TLG4C2XI.js} +2 -2
  16. package/dist/chunk-V7VQVDWS.js +237 -0
  17. package/dist/{chunk-IF5UAVIE.js → chunk-YC7G2IOZ.js} +1 -1
  18. package/dist/{chunk-IB4JVGKJ.js → chunk-YGYA7KEG.js} +47 -3
  19. package/dist/{chunk-GDOCD7LT.js → chunk-ZUMEOZ22.js} +5 -5
  20. package/dist/core-i18n.d.ts +2 -2
  21. package/dist/core-i18n.js +1 -1
  22. package/dist/core.d.ts +61 -1
  23. package/dist/core.js +1 -1
  24. package/dist/data-mock-store.js +263 -9
  25. package/dist/data-prisma-store.js +251 -1
  26. package/dist/data-seed-runner.js +18 -15
  27. package/dist/data.d.ts +53 -3
  28. package/dist/generated/client/edge.js +28 -9
  29. package/dist/generated/client/index-browser.js +25 -6
  30. package/dist/generated/client/index.d.ts +3500 -552
  31. package/dist/generated/client/index.js +28 -9
  32. package/dist/generated/client/package.json +1 -1
  33. package/dist/generated/client/schema.prisma +46 -9
  34. package/dist/generated/client/wasm.js +28 -9
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +1 -1
  37. package/dist/offline.d.ts +34 -1
  38. package/dist/offline.js +2 -2
  39. package/dist/{types-CtBC5-TW.d.ts → types-BOgdk0Jw.d.ts} +119 -0
  40. package/dist/types.d.ts +93 -1
  41. package/dist/types.js +1 -1
  42. package/dist/ui-components-admin/accounts-manager.d.ts +52 -0
  43. package/dist/ui-components-admin/accounts-manager.js +471 -0
  44. package/dist/ui-components-admin/admin-nav.d.ts +15 -1
  45. package/dist/ui-components-admin/admin-nav.js +386 -60
  46. package/dist/ui-components-admin/issuance-form.js +72 -13
  47. package/dist/ui-components-admin/logs-viewer.d.ts +13 -0
  48. package/dist/ui-components-admin/logs-viewer.js +102 -0
  49. package/dist/ui-components-admin/notifications-list.d.ts +5 -0
  50. package/dist/ui-components-admin/notifications-list.js +70 -0
  51. package/dist/ui-components-admin/officers-manager.d.ts +27 -0
  52. package/dist/ui-components-admin/officers-manager.js +271 -0
  53. package/dist/ui-components-admin/roles-manager.d.ts +37 -0
  54. package/dist/ui-components-admin/roles-manager.js +406 -0
  55. package/dist/ui-components-admin/ticket-preview.js +7 -7
  56. package/dist/ui-components-admin/tickets-table.js +56 -33
  57. package/dist/ui-components-citizen/citizen-nav.js +2 -2
  58. package/dist/ui-components-citizen/payment-form.js +5 -5
  59. package/dist/ui-components-citizen/payment-qr-dialog.js +4 -4
  60. package/dist/ui-components-citizen/ticket-not-found.js +2 -2
  61. package/dist/ui-components-citizen/violation-history-table.js +3 -3
  62. package/dist/ui-components-shared/amount-summary.js +4 -4
  63. package/dist/ui-components-shared/money.js +3 -3
  64. package/dist/ui-components-shared/municipal-seal.js +3 -3
  65. package/dist/ui-components-shared/official-header.js +4 -4
  66. package/dist/ui-components-shared/site-header.js +4 -4
  67. package/dist/ui-components-shared/sonner.js +2 -2
  68. package/dist/ui-components-shared/theme-toggle.js +3 -3
  69. package/dist/ui-components-shared/ticket-receipt.js +13 -6
  70. package/dist/ui-components-shared/violations-table.js +4 -4
  71. package/dist/ui-components-ui/badge.d.ts +1 -1
  72. package/dist/ui-components-ui/button.d.ts +1 -1
  73. package/dist/ui-components-ui/dropdown-menu.js +2 -237
  74. package/dist/ui-components-ui/sheet.js +3 -126
  75. package/dist/ui-config.d.ts +1 -1
  76. package/dist/ui-config.js +2 -2
  77. package/dist/ui-server.d.ts +1 -1
  78. package/dist/ui-server.js +2 -2
  79. package/package.json +4 -4
  80. package/prisma/migrations/20260622010000_add_super_admin_role/migration.sql +3 -0
  81. package/prisma/migrations/20260622020000_add_apprehending_enforcer/migration.sql +4 -0
  82. package/prisma/migrations/20260622030000_custom_roles/migration.sql +30 -0
  83. package/prisma/migrations/20260622040000_add_activity_log/migration.sql +18 -0
  84. package/prisma/schema.prisma +46 -9
  85. package/dist/chunk-B634JHKZ.js +0 -181
@@ -95,7 +95,126 @@ var baseCopy = {
95
95
  dashboard: "Dashboard",
96
96
  tickets: "Tickets",
97
97
  issueTicket: "Issue ticket",
98
+ accounts: "Accounts",
99
+ roles: "Roles",
100
+ officers: "Officers",
101
+ logs: "Activity log",
102
+ more: "More",
103
+ signOut: "Sign out",
104
+ signOutConfirmTitle: "Sign out?",
105
+ signOutConfirmBody: "You'll need to sign in again to issue or manage tickets.",
106
+ cancel: "Cancel",
98
107
  newTicketTitle: "Issue Violation Ticket",
108
+ accountsPage: {
109
+ title: "Accounts",
110
+ subtitle: "Create and manage who can sign in to the enforcer portal. Each account's role decides what it can see and do.",
111
+ newAccount: "New account",
112
+ edit: "Edit",
113
+ editTitle: "Edit account",
114
+ saveChanges: "Save changes",
115
+ username: "Username",
116
+ password: "Password",
117
+ role: "Role",
118
+ officer: "Linked officer",
119
+ officerNone: "\u2014 None \u2014",
120
+ officerHint: "Link an enforcer to an officer record so issued tickets are attributed.",
121
+ status: "Status",
122
+ active: "Active",
123
+ inactive: "Inactive",
124
+ created: "Created",
125
+ actions: "Actions",
126
+ create: "Create account",
127
+ creating: "Creating\u2026",
128
+ deactivate: "Deactivate",
129
+ activate: "Activate",
130
+ resetPassword: "Reset password",
131
+ newPassword: "New password",
132
+ save: "Save",
133
+ saving: "Saving\u2026",
134
+ cancel: "Cancel",
135
+ empty: "No accounts yet \u2014 create the first one.",
136
+ you: "You"
137
+ },
138
+ rolesPage: {
139
+ title: "Roles & permissions",
140
+ subtitle: "Define what each role can do. Create custom roles, set their permissions, then assign them to accounts.",
141
+ newRole: "New role",
142
+ roleName: "Role name",
143
+ roleNamePlaceholder: "e.g. Cashier, Supervisor",
144
+ permissions: "Permissions",
145
+ editPermissions: "Edit permissions",
146
+ noPermissions: "No permissions",
147
+ permissionsCount: "permissions",
148
+ system: "System",
149
+ custom: "Custom",
150
+ create: "Create role",
151
+ creating: "Creating\u2026",
152
+ save: "Save",
153
+ saving: "Saving\u2026",
154
+ saved: "Saved",
155
+ cancel: "Cancel",
156
+ delete: "Delete",
157
+ deleteConfirmTitle: "Delete this role?",
158
+ deleteConfirmBody: "Move any accounts off this role first. This can't be undone.",
159
+ lockedHint: "Super Admin always keeps account & role management.",
160
+ empty: "No roles yet \u2014 create the first one.",
161
+ inUseSuffix: "in use"
162
+ },
163
+ logsPage: {
164
+ title: "Activity log",
165
+ subtitle: "Audit trail of what accounts did \u2014 sign-ins, ticket issuance, and account & role changes.",
166
+ allActions: "All actions",
167
+ search: "Search actor or detail\u2026",
168
+ actor: "Actor",
169
+ action: "Action",
170
+ detail: "Detail",
171
+ when: "When",
172
+ empty: "No activity recorded yet.",
173
+ system: "system"
174
+ },
175
+ officersPage: {
176
+ title: "Apprehending officers",
177
+ subtitle: "Officers who apprehend violations \u2014 shown in the Issue-Ticket form and linked to enforcer accounts.",
178
+ newOfficer: "New officer",
179
+ name: "Full name",
180
+ namePlaceholder: "e.g. DELA CRUZ, JUAN P.",
181
+ badge: "Badge no.",
182
+ badgePlaceholder: "e.g. A176",
183
+ office: "Office",
184
+ officePlaceholder: "e.g. POSO",
185
+ edit: "Edit",
186
+ editTitle: "Edit officer",
187
+ create: "Add officer",
188
+ creating: "Adding\u2026",
189
+ save: "Save",
190
+ saving: "Saving\u2026",
191
+ cancel: "Cancel",
192
+ delete: "Remove",
193
+ deleteConfirmTitle: "Remove this officer?",
194
+ deleteConfirmBody: "This can't be undone. Officers with issued tickets or a linked account can't be removed.",
195
+ empty: "No officers yet \u2014 add the first one.",
196
+ actions: "Actions"
197
+ },
198
+ notifications: {
199
+ title: "Notifications",
200
+ subtitle: "Tickets that need attention \u2014 overdue and outstanding.",
201
+ empty: "You're all caught up.",
202
+ overdue: "Overdue",
203
+ outstanding: "Outstanding",
204
+ viewAll: "View all"
205
+ },
206
+ changePassword: {
207
+ title: "Change password",
208
+ subtitle: "Enter your current password, then your new one.",
209
+ current: "Current password",
210
+ new: "New password",
211
+ confirm: "Confirm new password",
212
+ submit: "Update password",
213
+ saving: "Updating\u2026",
214
+ cancel: "Cancel",
215
+ mismatch: "New passwords don't match.",
216
+ success: "Password updated."
217
+ },
99
218
  sync: {
100
219
  offline: "Offline",
101
220
  syncing: "Syncing\u2026",
@@ -1,5 +1,5 @@
1
+ import { Money } from './chunk-BVI5XDDA.js';
1
2
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from './chunk-OWCGEEAZ.js';
2
- import { Money } from './chunk-3KIDW4LT.js';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
 
5
5
  function ViolationsTable({
@@ -0,0 +1,89 @@
1
+ // ../ovr-types/src/index.ts
2
+ var PERMISSION_CATALOG = [
3
+ { key: "dashboard:view", label: "View dashboard", description: "See the admin dashboard and statistics." },
4
+ { key: "tickets:view", label: "View tickets", description: "Browse and search issued tickets." },
5
+ { key: "tickets:create", label: "Issue tickets", description: "Create new violation tickets." },
6
+ { key: "accounts:manage", label: "Manage accounts", description: "Create and manage user accounts." },
7
+ { key: "roles:manage", label: "Manage roles", description: "Create roles and set their permissions." },
8
+ { key: "officers:manage", label: "Manage officers", description: "Add, edit, and remove apprehending officers." },
9
+ { key: "logs:view", label: "View activity logs", description: "Browse the audit trail of account actions." },
10
+ { key: "notifications:view", label: "View notifications", description: "See the alert bell for overdue / pending tickets." }
11
+ ];
12
+ var ALL_PERMISSIONS = PERMISSION_CATALOG.map((p) => p.key);
13
+ var SYSTEM_ROLES = [
14
+ {
15
+ name: "SUPER_ADMIN",
16
+ label: "Super Admin",
17
+ isSystem: true,
18
+ permissions: [
19
+ "dashboard:view",
20
+ "tickets:view",
21
+ "tickets:create",
22
+ "accounts:manage",
23
+ "roles:manage",
24
+ "officers:manage",
25
+ "logs:view",
26
+ "notifications:view"
27
+ ]
28
+ },
29
+ {
30
+ name: "ADMIN",
31
+ label: "Administrator",
32
+ isSystem: true,
33
+ permissions: [
34
+ "dashboard:view",
35
+ "tickets:view",
36
+ "tickets:create",
37
+ "officers:manage",
38
+ "logs:view",
39
+ "notifications:view"
40
+ ]
41
+ },
42
+ {
43
+ name: "ENFORCER",
44
+ label: "Enforcer",
45
+ isSystem: true,
46
+ permissions: ["tickets:view", "tickets:create", "notifications:view"]
47
+ }
48
+ ];
49
+ var DEFAULT_ROLE_NAME = "ENFORCER";
50
+ var SUPER_ADMIN_LOCKED_PERMISSIONS = ["accounts:manage", "roles:manage"];
51
+ function hasPermission(permissions, permission) {
52
+ return !!permissions && permissions.includes(permission);
53
+ }
54
+ var ACTIVITY_ACTIONS = [
55
+ "auth.login",
56
+ "auth.logout",
57
+ "ticket.issued",
58
+ "account.create",
59
+ "account.update",
60
+ "account.activate",
61
+ "account.deactivate",
62
+ "account.password_reset",
63
+ "account.password_change",
64
+ "role.create",
65
+ "role.update",
66
+ "role.delete",
67
+ "officer.create",
68
+ "officer.update",
69
+ "officer.delete"
70
+ ];
71
+ var ACTIVITY_ACTION_LABELS = {
72
+ "auth.login": "Signed in",
73
+ "auth.logout": "Signed out",
74
+ "ticket.issued": "Issued ticket",
75
+ "account.create": "Created account",
76
+ "account.update": "Updated account",
77
+ "account.activate": "Activated account",
78
+ "account.deactivate": "Deactivated account",
79
+ "account.password_reset": "Reset password",
80
+ "account.password_change": "Changed own password",
81
+ "role.create": "Created role",
82
+ "role.update": "Updated role",
83
+ "role.delete": "Deleted role",
84
+ "officer.create": "Added officer",
85
+ "officer.update": "Updated officer",
86
+ "officer.delete": "Removed officer"
87
+ };
88
+
89
+ export { ACTIVITY_ACTIONS, ACTIVITY_ACTION_LABELS, ALL_PERMISSIONS, DEFAULT_ROLE_NAME, PERMISSION_CATALOG, SUPER_ADMIN_LOCKED_PERMISSIONS, SYSTEM_ROLES, hasPermission };
@@ -1,5 +1,5 @@
1
- import { MunicipalSeal } from './chunk-IF5UAVIE.js';
2
- import { useOvrConfig } from './chunk-E2D7QT6N.js';
1
+ import { MunicipalSeal } from './chunk-YC7G2IOZ.js';
2
+ import { useOvrConfig } from './chunk-TJSNVTVB.js';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
 
5
5
  function OfficialHeader() {
@@ -1,4 +1,4 @@
1
- import { createFormatters } from './chunk-B634JHKZ.js';
1
+ import { createFormatters } from './chunk-BI4EGLPG.js';
2
2
  import * as React2 from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
@@ -1,8 +1,8 @@
1
1
  import { Alert, AlertDescription } from './chunk-EYFZWQ4J.js';
2
- import { Money } from './chunk-3KIDW4LT.js';
2
+ import { Money } from './chunk-BVI5XDDA.js';
3
3
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './chunk-M35R6JLA.js';
4
4
  import { Button } from './chunk-I4WDVYHX.js';
5
- import { useCopy } from './chunk-E2D7QT6N.js';
5
+ import { useCopy } from './chunk-TJSNVTVB.js';
6
6
  import * as React from 'react';
7
7
  import { toast } from 'sonner';
8
8
  import { QrCode, Loader2, Lock } from 'lucide-react';
@@ -0,0 +1,237 @@
1
+ import { cn } from './chunk-77QBZC7J.js';
2
+ import { Menu } from '@base-ui/react/menu';
3
+ import { ChevronRightIcon, CheckIcon } from 'lucide-react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ function DropdownMenu({ ...props }) {
7
+ return /* @__PURE__ */ jsx(Menu.Root, { "data-slot": "dropdown-menu", ...props });
8
+ }
9
+ function DropdownMenuPortal({ ...props }) {
10
+ return /* @__PURE__ */ jsx(Menu.Portal, { "data-slot": "dropdown-menu-portal", ...props });
11
+ }
12
+ function DropdownMenuTrigger({ ...props }) {
13
+ return /* @__PURE__ */ jsx(Menu.Trigger, { "data-slot": "dropdown-menu-trigger", ...props });
14
+ }
15
+ function DropdownMenuContent({
16
+ align = "start",
17
+ alignOffset = 0,
18
+ side = "bottom",
19
+ sideOffset = 4,
20
+ className,
21
+ ...props
22
+ }) {
23
+ return /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(
24
+ Menu.Positioner,
25
+ {
26
+ className: "isolate z-50 outline-none",
27
+ align,
28
+ alignOffset,
29
+ side,
30
+ sideOffset,
31
+ children: /* @__PURE__ */ jsx(
32
+ Menu.Popup,
33
+ {
34
+ "data-slot": "dropdown-menu-content",
35
+ className: cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className),
36
+ ...props
37
+ }
38
+ )
39
+ }
40
+ ) });
41
+ }
42
+ function DropdownMenuGroup({ ...props }) {
43
+ return /* @__PURE__ */ jsx(Menu.Group, { "data-slot": "dropdown-menu-group", ...props });
44
+ }
45
+ function DropdownMenuLabel({
46
+ className,
47
+ inset,
48
+ ...props
49
+ }) {
50
+ return /* @__PURE__ */ jsx(
51
+ Menu.GroupLabel,
52
+ {
53
+ "data-slot": "dropdown-menu-label",
54
+ "data-inset": inset,
55
+ className: cn(
56
+ "px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7",
57
+ className
58
+ ),
59
+ ...props
60
+ }
61
+ );
62
+ }
63
+ function DropdownMenuItem({
64
+ className,
65
+ inset,
66
+ variant = "default",
67
+ ...props
68
+ }) {
69
+ return /* @__PURE__ */ jsx(
70
+ Menu.Item,
71
+ {
72
+ "data-slot": "dropdown-menu-item",
73
+ "data-inset": inset,
74
+ "data-variant": variant,
75
+ className: cn(
76
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
77
+ className
78
+ ),
79
+ ...props
80
+ }
81
+ );
82
+ }
83
+ function DropdownMenuSub({ ...props }) {
84
+ return /* @__PURE__ */ jsx(Menu.SubmenuRoot, { "data-slot": "dropdown-menu-sub", ...props });
85
+ }
86
+ function DropdownMenuSubTrigger({
87
+ className,
88
+ inset,
89
+ children,
90
+ ...props
91
+ }) {
92
+ return /* @__PURE__ */ jsxs(
93
+ Menu.SubmenuTrigger,
94
+ {
95
+ "data-slot": "dropdown-menu-sub-trigger",
96
+ "data-inset": inset,
97
+ className: cn(
98
+ "flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
99
+ className
100
+ ),
101
+ ...props,
102
+ children: [
103
+ children,
104
+ /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ml-auto" })
105
+ ]
106
+ }
107
+ );
108
+ }
109
+ function DropdownMenuSubContent({
110
+ align = "start",
111
+ alignOffset = -3,
112
+ side = "right",
113
+ sideOffset = 0,
114
+ className,
115
+ ...props
116
+ }) {
117
+ return /* @__PURE__ */ jsx(
118
+ DropdownMenuContent,
119
+ {
120
+ "data-slot": "dropdown-menu-sub-content",
121
+ className: cn("w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
122
+ align,
123
+ alignOffset,
124
+ side,
125
+ sideOffset,
126
+ ...props
127
+ }
128
+ );
129
+ }
130
+ function DropdownMenuCheckboxItem({
131
+ className,
132
+ children,
133
+ checked,
134
+ inset,
135
+ ...props
136
+ }) {
137
+ return /* @__PURE__ */ jsxs(
138
+ Menu.CheckboxItem,
139
+ {
140
+ "data-slot": "dropdown-menu-checkbox-item",
141
+ "data-inset": inset,
142
+ className: cn(
143
+ "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
144
+ className
145
+ ),
146
+ checked,
147
+ ...props,
148
+ children: [
149
+ /* @__PURE__ */ jsx(
150
+ "span",
151
+ {
152
+ className: "pointer-events-none absolute right-2 flex items-center justify-center",
153
+ "data-slot": "dropdown-menu-checkbox-item-indicator",
154
+ children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(
155
+ CheckIcon,
156
+ {}
157
+ ) })
158
+ }
159
+ ),
160
+ children
161
+ ]
162
+ }
163
+ );
164
+ }
165
+ function DropdownMenuRadioGroup({ ...props }) {
166
+ return /* @__PURE__ */ jsx(
167
+ Menu.RadioGroup,
168
+ {
169
+ "data-slot": "dropdown-menu-radio-group",
170
+ ...props
171
+ }
172
+ );
173
+ }
174
+ function DropdownMenuRadioItem({
175
+ className,
176
+ children,
177
+ inset,
178
+ ...props
179
+ }) {
180
+ return /* @__PURE__ */ jsxs(
181
+ Menu.RadioItem,
182
+ {
183
+ "data-slot": "dropdown-menu-radio-item",
184
+ "data-inset": inset,
185
+ className: cn(
186
+ "relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
187
+ className
188
+ ),
189
+ ...props,
190
+ children: [
191
+ /* @__PURE__ */ jsx(
192
+ "span",
193
+ {
194
+ className: "pointer-events-none absolute right-2 flex items-center justify-center",
195
+ "data-slot": "dropdown-menu-radio-item-indicator",
196
+ children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(
197
+ CheckIcon,
198
+ {}
199
+ ) })
200
+ }
201
+ ),
202
+ children
203
+ ]
204
+ }
205
+ );
206
+ }
207
+ function DropdownMenuSeparator({
208
+ className,
209
+ ...props
210
+ }) {
211
+ return /* @__PURE__ */ jsx(
212
+ Menu.Separator,
213
+ {
214
+ "data-slot": "dropdown-menu-separator",
215
+ className: cn("-mx-1 my-1 h-px bg-border", className),
216
+ ...props
217
+ }
218
+ );
219
+ }
220
+ function DropdownMenuShortcut({
221
+ className,
222
+ ...props
223
+ }) {
224
+ return /* @__PURE__ */ jsx(
225
+ "span",
226
+ {
227
+ "data-slot": "dropdown-menu-shortcut",
228
+ className: cn(
229
+ "ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground",
230
+ className
231
+ ),
232
+ ...props
233
+ }
234
+ );
235
+ }
236
+
237
+ export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
@@ -1,5 +1,5 @@
1
1
  import { Seal } from './chunk-WOPU6DI7.js';
2
- import { useOvrConfig } from './chunk-E2D7QT6N.js';
2
+ import { useOvrConfig } from './chunk-TJSNVTVB.js';
3
3
  import { cn } from './chunk-77QBZC7J.js';
4
4
  import * as React from 'react';
5
5
  import { jsx } from 'react/jsx-runtime';
@@ -1,4 +1,4 @@
1
- import { makeOvrTicketNo, addDays, makeBillNo, makeOrderOfPaymentNo, enrich } from './chunk-B634JHKZ.js';
1
+ import { makeOvrTicketNo, addDays, makeBillNo, makeOrderOfPaymentNo, enrich, fullName } from './chunk-BI4EGLPG.js';
2
2
  import Dexie from 'dexie';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { useLiveQuery } from 'dexie-react-hooks';
@@ -186,6 +186,10 @@ async function issueTicketOffline(input, rules) {
186
186
  record.placeOfViolation = input.placeOfViolation.trim();
187
187
  if (input.remarks?.trim()) record.remarks = input.remarks.trim();
188
188
  if (input.issuedBy?.trim()) record.issuedBy = input.issuedBy.trim();
189
+ if (input.apprehendingEnforcerId)
190
+ record.apprehendingEnforcerId = input.apprehendingEnforcerId;
191
+ if (input.apprehendingEnforcerName?.trim())
192
+ record.apprehendingEnforcerName = input.apprehendingEnforcerName.trim();
189
193
  const ticket = enrich(record, now, rules.surchargeRatePerMonth);
190
194
  await db.transaction("rw", db.tickets, db.outbox, async () => {
191
195
  await db.tickets.put(ticket);
@@ -201,7 +205,9 @@ async function issueTicketOffline(input, rules) {
201
205
  ...record.placeOfViolation ? { placeOfViolation: record.placeOfViolation } : {},
202
206
  officerId: input.officerId,
203
207
  violations: input.violations,
204
- ...record.remarks ? { remarks: record.remarks } : {}
208
+ ...record.remarks ? { remarks: record.remarks } : {},
209
+ ...record.apprehendingEnforcerId ? { apprehendingEnforcerId: record.apprehendingEnforcerId } : {},
210
+ ...record.apprehendingEnforcerName ? { apprehendingEnforcerName: record.apprehendingEnforcerName } : {}
205
211
  }
206
212
  });
207
213
  });
@@ -320,6 +326,44 @@ function useIdentity() {
320
326
  async () => (await db.meta.get("identity"))?.value
321
327
  );
322
328
  }
329
+ function useNotifications() {
330
+ const tickets = useTickets();
331
+ if (!tickets) return [];
332
+ const items = [];
333
+ for (const t of tickets) {
334
+ const type = t.status === "OVERDUE" ? "overdue" : t.status === "OUTSTANDING" ? "outstanding" : null;
335
+ if (!type) continue;
336
+ items.push({
337
+ id: `${t.ovrTicketNo}:${type}`,
338
+ type,
339
+ ovrTicketNo: t.ovrTicketNo,
340
+ name: fullName(t.violator),
341
+ at: type === "overdue" ? t.dueDate : t.createdAt,
342
+ href: `/admin/tickets/${encodeURIComponent(t.ovrTicketNo)}`
343
+ });
344
+ }
345
+ const rank = (n) => n.type === "overdue" ? 0 : 1;
346
+ items.sort((a, b) => rank(a) - rank(b) || (a.at < b.at ? 1 : a.at > b.at ? -1 : 0));
347
+ return items;
348
+ }
349
+ function notifSignature(items) {
350
+ return items.map((n) => n.id).join("|");
351
+ }
352
+ function useNotificationsState() {
353
+ const items = useNotifications();
354
+ const seen = useLiveQuery(
355
+ async () => (await db.meta.get("notif_seen"))?.value
356
+ );
357
+ const signature = notifSignature(items);
358
+ return {
359
+ items,
360
+ total: items.length,
361
+ unseen: items.length > 0 && signature !== seen,
362
+ markSeen: () => {
363
+ void setMeta("notif_seen", signature);
364
+ }
365
+ };
366
+ }
323
367
  function useStats() {
324
368
  return useLiveQuery(
325
369
  async () => (await db.meta.get("stats"))?.value
@@ -376,4 +420,4 @@ function useSync() {
376
420
  return { syncing, online, error };
377
421
  }
378
422
 
379
- export { SessionExpired, cacheCredential, clearIdentity, db, ensureLease, getIdentity, getMeta, isOnline, issueTicketOffline, offlineApiBase, pullAll, pushOutbox, setMeta, setOfflineApiBase, sync, useAdminAuth, useCatalog, useIdentity, useOfficers, usePendingSync, useStats, useSync, useTicket, useTickets, verifyOffline };
423
+ export { SessionExpired, cacheCredential, clearIdentity, db, ensureLease, getIdentity, getMeta, isOnline, issueTicketOffline, offlineApiBase, pullAll, pushOutbox, setMeta, setOfflineApiBase, sync, useAdminAuth, useCatalog, useIdentity, useNotifications, useNotificationsState, useOfficers, usePendingSync, useStats, useSync, useTicket, useTickets, verifyOffline };
@@ -1,9 +1,9 @@
1
- import { ViolationsTable } from './chunk-JEYT63LE.js';
2
- import { AmountSummary } from './chunk-BIQ2J75Y.js';
3
- import { Card, CardHeader, CardTitle, CardContent } from './chunk-SETIN6XP.js';
1
+ import { ViolationsTable } from './chunk-IBZVIUNI.js';
2
+ import { AmountSummary } from './chunk-GLIK5BHP.js';
4
3
  import { StatusBadge } from './chunk-OE525ZER.js';
5
- import { useFormatters } from './chunk-E2D7QT6N.js';
6
- import { formalName, formatAddress } from './chunk-B634JHKZ.js';
4
+ import { Card, CardHeader, CardTitle, CardContent } from './chunk-SETIN6XP.js';
5
+ import { useFormatters } from './chunk-TJSNVTVB.js';
6
+ import { formalName, formatAddress } from './chunk-BI4EGLPG.js';
7
7
  import { jsxs, jsx } from 'react/jsx-runtime';
8
8
 
9
9
  function Field({ label, value }) {
@@ -1,5 +1,5 @@
1
- import { C as CopyOverrides, D as Dictionary } from './types-CtBC5-TW.js';
2
- export { b as baseCopy } from './types-CtBC5-TW.js';
1
+ import { C as CopyOverrides, D as Dictionary } from './types-BOgdk0Jw.js';
2
+ export { b as baseCopy } from './types-BOgdk0Jw.js';
3
3
  import { M as MunicipalityConfig } from './schema-CdsFQxIg.js';
4
4
  import 'zod';
5
5
 
package/dist/core-i18n.js CHANGED
@@ -1 +1 @@
1
- export { baseCopy, mergeCopy } from './chunk-5YYR37CF.js';
1
+ export { baseCopy, mergeCopy } from './chunk-HGWPA7FU.js';
package/dist/core.d.ts CHANGED
@@ -73,4 +73,64 @@ interface IssuanceDraft {
73
73
  type PreviewRules = Pick<RulesConfig, "dueWindowDays" | "surchargeRatePerMonth">;
74
74
  declare function toPreviewTicket(draft: IssuanceDraft, now: Date, rules: PreviewRules): Ticket;
75
75
 
76
- export { type ChargeInput, type ChargeResult, type IssuanceDraft, PREVIEW_PLACEHOLDER, type PreviewRules, computeCharges, enrich, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket };
76
+ /**
77
+ * Dashboard time-range presets (GE-010): 1D / 7D / MTD / prev-MTD (+ All).
78
+ *
79
+ * Ranges are computed in the LGU's timezone (config `timeZone`) so "today" / "this
80
+ * month" mean the local calendar, not the server's. Stats are derived from the
81
+ * on-device ticket list (offline-first) — no server round-trip — by ISSUE date
82
+ * (`createdAt`); each card reflects the CURRENT status of tickets issued in range.
83
+ */
84
+
85
+ type DashboardRangePreset = "1d" | "7d" | "mtd" | "prev-mtd" | "all";
86
+ interface DashboardPreset {
87
+ key: DashboardRangePreset;
88
+ label: string;
89
+ }
90
+ /** The presets, in display order. */
91
+ declare const DASHBOARD_PRESETS: DashboardPreset[];
92
+ interface DashboardRange {
93
+ /** Inclusive lower bound (ISO), or null for "all time" (no lower bound). */
94
+ fromISO: string | null;
95
+ /** Inclusive upper bound (ISO). */
96
+ toISO: string;
97
+ }
98
+ interface DashboardStats {
99
+ issued: number;
100
+ outstanding: number;
101
+ overdue: number;
102
+ paid: number;
103
+ collected: number;
104
+ }
105
+ /** Resolve a preset to a concrete [from, to] range, anchored to `now` in `timeZone`. */
106
+ declare function dashboardDateRange(preset: DashboardRangePreset, now: Date, timeZone: string): DashboardRange;
107
+ /** A custom date range from two `YYYY-MM-DD` local dates (either may be empty:
108
+ * empty `from` → no lower bound; empty `to` → up to `now`). Interpreted in
109
+ * `timeZone` as [from 00:00:00, to 23:59:59]. If from > to, they're swapped. */
110
+ declare function customDateRange(fromYMD: string | null | undefined, toYMD: string | null | undefined, now: Date, timeZone: string): DashboardRange;
111
+ /** Compute the dashboard cards from the local ticket list, scoped to `range`
112
+ * by issue date. Each card reflects each ticket's CURRENT status. */
113
+ declare function computeDashboardStats(tickets: Ticket[], range: DashboardRange): DashboardStats;
114
+
115
+ /**
116
+ * Philippine locations (PSGC) for the Issue-Ticket address dropdowns (GE-015).
117
+ *
118
+ * Provinces + their cities/municipalities ONLY (barangays omitted to keep the
119
+ * bundle small — barangay stays free-text). Bundled (no runtime API) so it works
120
+ * offline. NCR districts are flattened into a single "Metro Manila" province.
121
+ *
122
+ * Source (open data, CC0/public): flores-jacob/philippine-regions-provinces-
123
+ * cities-municipalities-barangays (2019v2, derived from PSA PSGC). Names are
124
+ * title-cased from the source's all-caps.
125
+ */
126
+ interface PhProvince {
127
+ province: string;
128
+ cities: string[];
129
+ }
130
+ declare const PH_PROVINCES: PhProvince[];
131
+ /** All province names (display order = alphabetical). */
132
+ declare function listProvinces(): string[];
133
+ /** Cities/municipalities of a province (case-insensitive match; [] if unknown). */
134
+ declare function citiesOfProvince(province: string | null | undefined): string[];
135
+
136
+ export { type ChargeInput, type ChargeResult, DASHBOARD_PRESETS, type DashboardPreset, type DashboardRange, type DashboardRangePreset, type DashboardStats, type IssuanceDraft, PH_PROVINCES, PREVIEW_PLACEHOLDER, type PhProvince, type PreviewRules, citiesOfProvince, computeCharges, computeDashboardStats, customDateRange, dashboardDateRange, enrich, listProvinces, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket };
package/dist/core.js CHANGED
@@ -1 +1 @@
1
- export { PREVIEW_PLACEHOLDER, addDays, computeCharges, createFormatters, enrich, formalName, formatAddress, fullName, localToManilaISO, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket } from './chunk-B634JHKZ.js';
1
+ export { DASHBOARD_PRESETS, PH_PROVINCES, PREVIEW_PLACEHOLDER, addDays, citiesOfProvince, computeCharges, computeDashboardStats, createFormatters, customDateRange, dashboardDateRange, enrich, formalName, formatAddress, fullName, listProvinces, localToManilaISO, makeBillNo, makeOrderOfPaymentNo, makeOvrTicketNo, makePaymentRef, round2, startedMonthsSince, toPreviewTicket } from './chunk-BI4EGLPG.js';