@propriety/court-calendar 1.0.103 → 1.0.105
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/README.md +294 -103
- package/dist/__tests__/helpers/routing.test.d.ts +1 -0
- package/dist/__tests__/hooks/UseRouteSync.test.d.ts +1 -0
- package/dist/_components/CCalendar.d.ts +4 -1
- package/dist/_components/Modal/Modal.d.ts +7 -1
- package/dist/helpers/api/cases.d.ts +1 -0
- package/dist/helpers/routing.d.ts +7 -0
- package/dist/hooks/UseRouteSync.d.ts +31 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +6220 -5954
- package/dist/types.d.ts +18 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/routing.test.ts +569 -0
- package/src/__tests__/hooks/UseRouteSync.test.ts +764 -0
- package/src/_components/CCalendar.tsx +59 -1
- package/src/_components/Modal/DateDetails/CaseViewer.tsx +16 -6
- package/src/_components/Modal/Modal.tsx +12 -8
- package/src/_components/Toolbar/UserFilter.tsx +3 -0
- package/src/helpers/api/cases.ts +21 -0
- package/src/helpers/routing.ts +158 -0
- package/src/hooks/UseCalendarEvents.ts +4 -1
- package/src/hooks/UseRouteSync.ts +258 -0
- package/src/index.ts +2 -1
- package/src/types.ts +24 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A React component library for managing court dates, cases, and evidence. Built w
|
|
|
8
8
|
npm install @propriety/court-calendar
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Peer dependencies:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install react react-dom @mui/material @mui/icons-material @mui/x-data-grid @mui/x-date-pickers @emotion/react @emotion/styled
|
|
@@ -25,14 +25,7 @@ function App() {
|
|
|
25
25
|
}
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
###
|
|
29
|
-
|
|
30
|
-
| Prop | Type | Description |
|
|
31
|
-
|------|------|-------------|
|
|
32
|
-
| `apiKey` | `string` | API key for authenticating with the Aventine API |
|
|
33
|
-
| `activeUser` | `number` | The current user's ID (used for chair assignments and edit tracking) |
|
|
34
|
-
|
|
35
|
-
### CSS Import
|
|
28
|
+
### CSS import
|
|
36
29
|
|
|
37
30
|
You **must** import the stylesheet separately:
|
|
38
31
|
|
|
@@ -40,110 +33,166 @@ You **must** import the stylesheet separately:
|
|
|
40
33
|
import '@propriety/court-calendar/styles.css';
|
|
41
34
|
```
|
|
42
35
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
The component supports light and dark themes via the `data-theme` attribute on the document body:
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
document.body.setAttribute('data-theme', 'dark'); // or 'light'
|
|
49
|
-
```
|
|
36
|
+
### Props
|
|
50
37
|
|
|
51
|
-
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
|------|------|---------|-------------|
|
|
40
|
+
| `apiKey` | `string` | required | API key for authenticating with the Aventine API |
|
|
41
|
+
| `activeUser` | `number` | required | The current user's ID (used for chair assignments and edit tracking) |
|
|
42
|
+
| `showLegend` | `boolean` | `true` | Whether to render the hearing-type legend below the calendar |
|
|
43
|
+
| `mode` | `'light' \| 'dark'` | `'light'` | Color scheme |
|
|
44
|
+
| `themeOverrides` | `ThemeOptions` | — | MUI theme overrides applied on top of the default theme |
|
|
45
|
+
| `routeParams` | `CalendarRouteParams` | — | Current URL state, parsed and passed in by the host app's router. See [URL routing](#url-routing--deep-linking). |
|
|
46
|
+
| `onRouteChange` | `(params: CalendarRouteParams) => void` | — | Called whenever navigable state changes. The host app should write these params to the URL. See [URL routing](#url-routing--deep-linking). |
|
|
52
47
|
|
|
53
48
|
---
|
|
54
49
|
|
|
55
|
-
##
|
|
50
|
+
## URL routing / deep linking
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
By default the calendar is stateless from a URL perspective — navigating to a court date or applying a filter leaves no trace in the browser address bar. The `routeParams` / `onRouteChange` props opt into URL sync without coupling the library to any specific router.
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
- npm
|
|
54
|
+
### How it works
|
|
61
55
|
|
|
62
|
-
|
|
56
|
+
The library never reads or writes `window.location` directly when `onRouteChange` is provided. Instead:
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
cd court-calendar
|
|
67
|
-
npm install
|
|
68
|
-
```
|
|
58
|
+
- **Outbound:** whenever navigable state changes (view, date, open modal, active filters), `onRouteChange` is called with a `CalendarRouteParams` object describing the full current state.
|
|
59
|
+
- **Inbound:** on mount, the library reads `routeParams` once and restores the saved state — including re-opening a court date modal or drilling straight into a case.
|
|
69
60
|
|
|
70
|
-
|
|
61
|
+
The host app owns the URL. It parses params out of its router, passes them as `routeParams`, and writes new params back in `onRouteChange`. Two utility functions handle the encoding:
|
|
71
62
|
|
|
72
|
-
```
|
|
73
|
-
|
|
63
|
+
```ts
|
|
64
|
+
import {
|
|
65
|
+
CCalendar,
|
|
66
|
+
parseCalendarSearchParams,
|
|
67
|
+
serializeCalendarParams,
|
|
68
|
+
} from '@propriety/court-calendar';
|
|
74
69
|
```
|
|
75
70
|
|
|
76
|
-
|
|
71
|
+
### Integration examples
|
|
77
72
|
|
|
78
|
-
**
|
|
73
|
+
**React Router v6**
|
|
79
74
|
|
|
80
|
-
|
|
75
|
+
```tsx
|
|
76
|
+
import { useSearchParams } from 'react-router-dom';
|
|
77
|
+
import { CCalendar, parseCalendarSearchParams, serializeCalendarParams } from '@propriety/court-calendar';
|
|
78
|
+
|
|
79
|
+
function CalendarPage() {
|
|
80
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<CCalendar
|
|
84
|
+
apiKey={API_KEY}
|
|
85
|
+
activeUser={userId}
|
|
86
|
+
routeParams={parseCalendarSearchParams(searchParams)}
|
|
87
|
+
onRouteChange={(params) =>
|
|
88
|
+
setSearchParams(serializeCalendarParams(params), { replace: true })
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
**TanStack Router**
|
|
83
96
|
|
|
84
|
-
```
|
|
85
|
-
|
|
97
|
+
```tsx
|
|
98
|
+
import { useSearch, useNavigate } from '@tanstack/react-router';
|
|
99
|
+
import { CCalendar, parseCalendarSearchParams, serializeCalendarParams } from '@propriety/court-calendar';
|
|
100
|
+
|
|
101
|
+
function CalendarPage() {
|
|
102
|
+
const search = useSearch({ from: '/calendar' });
|
|
103
|
+
const navigate = useNavigate();
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<CCalendar
|
|
107
|
+
apiKey={API_KEY}
|
|
108
|
+
activeUser={userId}
|
|
109
|
+
routeParams={parseCalendarSearchParams(search as Record<string, string>)}
|
|
110
|
+
onRouteChange={(params) =>
|
|
111
|
+
navigate({ search: Object.fromEntries(serializeCalendarParams(params)), replace: true })
|
|
112
|
+
}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
86
116
|
```
|
|
87
117
|
|
|
88
|
-
|
|
118
|
+
**Next.js App Router**
|
|
89
119
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
```tsx
|
|
121
|
+
'use client';
|
|
122
|
+
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
|
|
123
|
+
import { CCalendar, parseCalendarSearchParams, serializeCalendarParams } from '@propriety/court-calendar';
|
|
124
|
+
|
|
125
|
+
export default function CalendarPage() {
|
|
126
|
+
const router = useRouter();
|
|
127
|
+
const pathname = usePathname();
|
|
128
|
+
const searchParams = useSearchParams();
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<CCalendar
|
|
132
|
+
apiKey={API_KEY}
|
|
133
|
+
activeUser={userId}
|
|
134
|
+
routeParams={parseCalendarSearchParams(searchParams)}
|
|
135
|
+
onRouteChange={(params) => {
|
|
136
|
+
const qs = serializeCalendarParams(params).toString();
|
|
137
|
+
router.replace(qs ? `${pathname}?${qs}` : pathname);
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
94
142
|
```
|
|
95
143
|
|
|
96
|
-
|
|
144
|
+
### Standalone mode (no router)
|
|
97
145
|
|
|
98
|
-
|
|
99
|
-
- `dist/index.d.ts` - TypeScript type definitions
|
|
100
|
-
- `dist/court-calendar.css` - Compiled styles
|
|
146
|
+
When neither `routeParams` nor `onRouteChange` is provided, the library manages the URL itself using `window.history.replaceState`. This is suitable for single-page apps without a router or for standalone embedding. The URL updates in place without adding history entries.
|
|
101
147
|
|
|
102
|
-
|
|
148
|
+
### URL parameter reference
|
|
103
149
|
|
|
104
|
-
|
|
150
|
+
Parameters use short keys to keep the address bar readable. Only non-default values appear in the URL — a fully default calendar state produces a clean URL with no search params.
|
|
105
151
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
152
|
+
| URL key | `CalendarRouteParams` field | Default (omitted when) | Notes |
|
|
153
|
+
|---------|----------------------------|------------------------|-------|
|
|
154
|
+
| `view` | `view` | `'dayGridMonth'` | One of `dayGridMonth`, `timeGridWeek`, `timeGridDay`, `listDay`, `tableView` |
|
|
155
|
+
| `date` | `date` | today | ISO `YYYY-MM-DD` |
|
|
156
|
+
| `cdId` | `courtDateId` | absent | Integer court date ID — opens the details modal on load |
|
|
157
|
+
| `ci` | `caseIdx` | absent | `Case.SCARIndexNumber` — opens a specific case (requires `cdId`) |
|
|
158
|
+
| `ht` | `showVirtual` / `showInPerson` / `showUnknown` | all visible | Comma-separated list of **hidden** hearing types, e.g. `ht=virtual,inPerson` |
|
|
159
|
+
| `user` | `showOnlyAssignedToUser` | `null` | UserID integer, or `-1` for "Unassigned" |
|
|
160
|
+
| `dl` | `showUploadDeadlines` | `false` | `dl=1` when true |
|
|
161
|
+
| `adj` | `showAdjournmentDates` | `true` | `adj=0` when false |
|
|
162
|
+
| `dt` | `dateTypeFilter` | `'all'` | `scar` or `negotiations` |
|
|
163
|
+
| `muni` | `municode` | `null` | Municipality code string |
|
|
164
|
+
| `settled` | `showOnlyUnsettled` | `true` | `settled=1` when the filter is **off** (inverted because default is on) |
|
|
165
|
+
| `noEvid` | `showOnlyWithoutEvidence` | `false` | `noEvid=1` when true |
|
|
166
|
+
| `unrev` | `showOnlyUnreviewed` | `false` | `unrev=1` when true |
|
|
167
|
+
| `q` | `searchTerm` | `''` | Free-text search |
|
|
111
168
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
## Testing
|
|
169
|
+
Example URL after opening a case with a filter applied:
|
|
115
170
|
|
|
116
|
-
|
|
171
|
+
```
|
|
172
|
+
/calendar?cdId=1234&ci=2024-98765&view=listDay&date=2025-06-15&dt=scar
|
|
173
|
+
```
|
|
117
174
|
|
|
118
|
-
|
|
175
|
+
### What is and isn't bookmarkable
|
|
119
176
|
|
|
120
|
-
|
|
177
|
+
The URL captures:
|
|
178
|
+
- Current calendar view and visible date
|
|
179
|
+
- Which court date modal is open (`cdId`)
|
|
180
|
+
- Which case is open within that modal (`ci`)
|
|
181
|
+
- All active filter state
|
|
121
182
|
|
|
122
|
-
|
|
183
|
+
The URL does **not** capture:
|
|
184
|
+
- Edit mode or Create mode (transient form state — these are suppressed from `onRouteChange`)
|
|
123
185
|
|
|
124
|
-
###
|
|
186
|
+
### Bootstrap sequence
|
|
125
187
|
|
|
126
|
-
|
|
127
|
-
|----------|--------|-------------|
|
|
128
|
-
| `/court-dates/all` | GET | Fetch all court dates |
|
|
129
|
-
| `/court-dates/create` | POST | Create a new court date |
|
|
130
|
-
| `/court-dates/{id}/update` | PUT | Update a court date |
|
|
131
|
-
| `/court-dates/{id}` | DELETE | Delete a court date |
|
|
132
|
-
| `/court-dates/{id}/cases` | GET | Fetch cases for a court date |
|
|
133
|
-
| `/court-cases/search?term=...&page=...` | GET | Search cases (paginated) |
|
|
134
|
-
| `/court-cases/filtering?page=...&page_size=...` | GET | List all cases (paginated) |
|
|
135
|
-
| `/court-cases/snooze/upload/{id}` | GET | Snooze upload deadline by 1 business day |
|
|
136
|
-
| `/users/all` | GET | Fetch all users |
|
|
137
|
-
| `/users/hearing-officers` | GET | Fetch hearing officers |
|
|
138
|
-
| `/utils/get-muni-names` | GET | Fetch municipality names |
|
|
188
|
+
When the page loads with params in the URL, the library restores state in phases to handle async data loading:
|
|
139
189
|
|
|
140
|
-
|
|
190
|
+
1. **Phase 1 (synchronous, on mount):** View, date, and filter state are applied immediately from `routeParams`.
|
|
191
|
+
2. **Phase 1b:** If `user=N` is in the URL, the User object is looked up from the reference data once it loads and merged into filter context. `user=-1` (Unassigned) is handled as a special case — no lookup is needed.
|
|
192
|
+
3. **Phase 2:** Once court date data finishes loading, the target court date is found and its details modal opened. If the court date has been filtered out of view (e.g. all its cases are settled and the unsettled filter is on), a minimal stub is built from the raw court date data so the modal still opens correctly.
|
|
193
|
+
4. **Phase 3:** Once cases for the opened court date finish loading, the target case is found and opened. This works even for settled cases that are hidden from the calendar view — `selectedCases` always contains all cases regardless of active filters.
|
|
141
194
|
|
|
142
|
-
|
|
143
|
-
- **No offline support:** All CRUD operations require an active connection to the API. The IndexedDB cache is read-only (used for faster page loads, not offline editing).
|
|
144
|
-
- **Cache expiry:** Court dates cache expires after 5 minutes, cases cache after 1 hour. After expiry, data is re-fetched from the API on the next page load.
|
|
145
|
-
- **No pagination for court dates:** `getAllDates` fetches every court date in a single request. For large datasets this could be slow.
|
|
146
|
-
- **Municipality and user data loads on import:** `munis.ts` and `people.ts` fire fetch requests at module load time, not lazily.
|
|
195
|
+
If a `cdId` or `ci` value in the URL doesn't match any loaded data, a brief error snackbar is shown.
|
|
147
196
|
|
|
148
197
|
---
|
|
149
198
|
|
|
@@ -161,38 +210,87 @@ All data is fetched from `https://utils.aventine.ai`. Every request requires the
|
|
|
161
210
|
| `CaseViewer` | DataGrid listing cases for a selected court date. |
|
|
162
211
|
| `CaseDetails` | Read-only view of a single case with evidence and SCAR data. |
|
|
163
212
|
|
|
213
|
+
### Hooks
|
|
214
|
+
|
|
215
|
+
| Hook | Description |
|
|
216
|
+
|------|-------------|
|
|
217
|
+
| `useCourtDates` | Fetches and caches court dates and negotiations. Handles polling and optimistic in-memory updates. |
|
|
218
|
+
| `useCaseData` | Fetches and caches cases for all court dates. Maintains a `Record<caseKey, Case[]>` memory store. |
|
|
219
|
+
| `useCalendarEvents` | Derives FullCalendar event objects and `CalEvent` buckets from court date + case data. Applies filter context. |
|
|
220
|
+
| `useModalSelection` | Tracks which court date event is selected and manages `openDetailsModal` / `openCreateModal`. |
|
|
221
|
+
| `useModalState` | Manages form state for Edit/Create modes and `clickedCase` for Case Details mode. |
|
|
222
|
+
| `useModalActions` | Handles save, delete, and chair-update API calls. |
|
|
223
|
+
| `useRouteSync` | Syncs navigable state to/from the URL. See [URL routing](#url-routing--deep-linking). |
|
|
224
|
+
| `useScrollbarClickDetection` | Prevents date-click from firing when the user clicks a scrollbar. |
|
|
225
|
+
|
|
164
226
|
### Helpers
|
|
165
227
|
|
|
166
228
|
| Helper | Description |
|
|
167
229
|
|--------|-------------|
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
230
|
+
| `helpers/routing.ts` | `parseCalendarSearchParams`, `serializeCalendarParams`, `buildCalendarRouteParams`, `extractFilterFields`. Pure functions for URL encoding/decoding. |
|
|
231
|
+
| `helpers/CalEvent.ts` | `buildCalEvents` — derives main, extra-day, and adjournment `CalEvent` buckets from court date + case data. |
|
|
232
|
+
| `helpers/cases.ts` | Case fetching (by court date, paginated, search). Settlement and evidence checks. |
|
|
233
|
+
| `helpers/courtDates.ts` | `caseKey` (memory-map key), `isVillageDate` (municipality code check). |
|
|
234
|
+
| `helpers/documentActions.ts` | Open, download, and print document helpers (used by DocCard). |
|
|
235
|
+
| `helpers/formatter.ts` | Date formatting for API (`YYYY-MM-DD` or `YYYY-MM-DD HH:mm:00`). Evidence string formatting. |
|
|
236
|
+
| `helpers/cache.ts` | Dexie-based IndexedDB caching with TTL. |
|
|
237
|
+
| `helpers/api/` | Fetch wrappers for each API endpoint group (court dates, cases, munis, people). |
|
|
238
|
+
|
|
239
|
+
### CalEvent
|
|
174
240
|
|
|
175
|
-
|
|
241
|
+
`CalEvent` is the internal representation of a single calendar entry. One `CourtDate` can produce multiple `CalEvent` objects:
|
|
242
|
+
|
|
243
|
+
| Type | `isAdjourned` | `isExtraDay` | Description |
|
|
244
|
+
|------|---------------|--------------|-------------|
|
|
245
|
+
| Main | `false` | `false` | The primary entry on the original court date. Holds all non-adjourned cases. |
|
|
246
|
+
| Extra-day | `false` | `true` | Additional days from `CourtDate.MultipleDates`. Same cases as the main event. |
|
|
247
|
+
| Adjournment | `true` | `false` | One entry per unique adjourned date, holding the cases adjourned to that date. |
|
|
248
|
+
|
|
249
|
+
URL routing always targets the main `CalEvent` when restoring a modal — adjournment and extra-day entries are never used as the bootstrap target.
|
|
250
|
+
|
|
251
|
+
### Key types
|
|
176
252
|
|
|
177
253
|
```typescript
|
|
254
|
+
interface CalendarRouteParams {
|
|
255
|
+
view?: 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay' | 'listDay' | 'tableView';
|
|
256
|
+
date?: string; // ISO: "YYYY-MM-DD"
|
|
257
|
+
courtDateId?: number;
|
|
258
|
+
caseIdx?: string; // Case.SCARIndexNumber
|
|
259
|
+
showVirtual?: boolean;
|
|
260
|
+
showInPerson?: boolean;
|
|
261
|
+
showUnknown?: boolean;
|
|
262
|
+
showOnlyAssignedToUser?: number | null;
|
|
263
|
+
showUploadDeadlines?: boolean;
|
|
264
|
+
showAdjournmentDates?: boolean;
|
|
265
|
+
dateTypeFilter?: 'all' | 'scar' | 'negotiations';
|
|
266
|
+
municode?: string | null;
|
|
267
|
+
showOnlyUnsettled?: boolean;
|
|
268
|
+
showOnlyWithoutEvidence?: boolean;
|
|
269
|
+
showOnlyUnreviewed?: boolean;
|
|
270
|
+
searchTerm?: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
178
273
|
interface CourtDate {
|
|
179
274
|
CourtDateID: number;
|
|
180
275
|
CourtDate: Date;
|
|
181
276
|
MuniCode: string;
|
|
182
277
|
UploadDeadline: Date | null;
|
|
183
278
|
CourtCases: number;
|
|
184
|
-
Lifecycle:
|
|
279
|
+
Lifecycle: 'Scheduled' | 'Assigned' | 'Uploaded' | 'Adjourned' | null;
|
|
280
|
+
DateType: '' | 'Negotiations' | null; // '' = SCAR
|
|
185
281
|
HearingTime: string;
|
|
186
282
|
HearingLink: string | null;
|
|
187
|
-
Source:
|
|
188
|
-
Type:
|
|
283
|
+
Source: 'manual' | 'email' | 'etrack' | null;
|
|
284
|
+
Type: 'Other' | 'Virtual' | 'InPerson' | null;
|
|
189
285
|
FirstChair: number | null;
|
|
190
286
|
SecondChair: number | null;
|
|
191
287
|
HearingOfficer: number | null;
|
|
192
|
-
|
|
193
|
-
AdjournmentDate: Date | null;
|
|
194
|
-
Notes: string | null;
|
|
288
|
+
Notes: Notes[] | null;
|
|
195
289
|
NoticeFile: string | null;
|
|
290
|
+
MultipleDates: Date[] | null;
|
|
291
|
+
Address: string | null;
|
|
292
|
+
Room: string | null;
|
|
293
|
+
SkipMotion: boolean | null;
|
|
196
294
|
}
|
|
197
295
|
|
|
198
296
|
interface Case {
|
|
@@ -202,18 +300,112 @@ interface Case {
|
|
|
202
300
|
SCARDeterminationAction: string;
|
|
203
301
|
property_data: { Address: string; PropertyOwnerFull: string };
|
|
204
302
|
evidence: Evidence | null;
|
|
205
|
-
|
|
206
|
-
// ... plus Village SCAR fields
|
|
303
|
+
AdjournedDate: Date | null;
|
|
304
|
+
// ... plus Village SCAR fields and negotiation/stip tracking
|
|
207
305
|
}
|
|
208
306
|
```
|
|
209
307
|
|
|
210
308
|
---
|
|
211
309
|
|
|
310
|
+
## Development
|
|
311
|
+
|
|
312
|
+
### Prerequisites
|
|
313
|
+
|
|
314
|
+
- Node.js 20+
|
|
315
|
+
- npm
|
|
316
|
+
|
|
317
|
+
### Setup
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
git clone git@github.com:Aventine-Git/court-calendar.git
|
|
321
|
+
cd court-calendar
|
|
322
|
+
npm install
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Dev server
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm run dev
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Starts the Vite dev server using `dev/App.tsx` as the entry point. **The API only accepts requests from `localhost:8000`** — the dev server must run on port 8000 or API calls will fail with CORS errors.
|
|
332
|
+
|
|
333
|
+
### Environment variables
|
|
334
|
+
|
|
335
|
+
Create a `.env` file in the project root:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
VITE_CALENDAR_API_KEY=your-api-key-here
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Build
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
npm run build
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Runs `tsc -b` then the Vite library build. Output in `dist/`:
|
|
348
|
+
|
|
349
|
+
- `dist/index.mjs` — ES module bundle
|
|
350
|
+
- `dist/index.d.ts` — TypeScript declarations
|
|
351
|
+
- `dist/court-calendar.css` — compiled styles
|
|
352
|
+
|
|
353
|
+
React, React-DOM, MUI, and Emotion are externalized (peer dependencies, not bundled).
|
|
354
|
+
|
|
355
|
+
### Lint
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
npm run lint
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
ESLint with TypeScript plugin. Biome is configured for formatting (4-space indent, 120-char line width, single quotes).
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Testing
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
npm run test
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Vitest + `@testing-library/react` + jsdom. Test files live in `src/__tests__/` mirroring the source tree:
|
|
372
|
+
|
|
373
|
+
| Test file | Covers |
|
|
374
|
+
|-----------|--------|
|
|
375
|
+
| `helpers/CalEvent.test.ts` | `buildCalEvents`, `parseLocalDate` |
|
|
376
|
+
| `helpers/routing.test.ts` | `serializeCalendarParams`, `parseCalendarSearchParams`, `buildCalendarRouteParams`, `extractFilterFields` |
|
|
377
|
+
| `hooks/UseModalState.test.ts` | `useModalState` — edit/create init, modal close effects, clickedCase transitions |
|
|
378
|
+
| `hooks/UseRouteSync.test.ts` | `useRouteSync` — outbound sync, replaceState fallback, bootstrap Phases 1–3, bootstrap errors |
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## API
|
|
383
|
+
|
|
384
|
+
All data is fetched from `https://utils.aventine.ai`. Every request requires the `x-api-key` header.
|
|
385
|
+
|
|
386
|
+
### Endpoints
|
|
387
|
+
|
|
388
|
+
| Endpoint | Method | Description |
|
|
389
|
+
|----------|--------|-------------|
|
|
390
|
+
| `/court-dates/all` | GET | Fetch all court dates |
|
|
391
|
+
| `/court-dates/create` | POST | Create a new court date |
|
|
392
|
+
| `/court-dates/{id}/update` | PUT | Update a court date |
|
|
393
|
+
| `/court-dates/{id}` | DELETE | Delete a court date |
|
|
394
|
+
| `/court-dates/{id}/cases` | GET | Fetch cases for a court date |
|
|
395
|
+
| `/court-cases/search?term=...&page=...` | GET | Search cases (paginated) |
|
|
396
|
+
| `/court-cases/filtering?page=...&page_size=...` | GET | List all cases (paginated) |
|
|
397
|
+
| `/court-cases/snooze/upload/{id}` | GET | Snooze upload deadline by 1 business day |
|
|
398
|
+
| `/users/all` | GET | Fetch all users |
|
|
399
|
+
| `/users/hearing-officers` | GET | Fetch hearing officers |
|
|
400
|
+
| `/utils/get-muni-names` | GET | Fetch municipality names |
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
212
404
|
## CI/CD
|
|
213
405
|
|
|
214
406
|
Publishing is automated via GitHub Actions (`.github/workflows/publish.yml`).
|
|
215
407
|
|
|
216
|
-
**Trigger:**
|
|
408
|
+
**Trigger:** every merged PR to `main`.
|
|
217
409
|
|
|
218
410
|
**Steps:**
|
|
219
411
|
1. Checkout code
|
|
@@ -223,15 +415,14 @@ Publishing is automated via GitHub Actions (`.github/workflows/publish.yml`).
|
|
|
223
415
|
5. Publish to npm with provenance
|
|
224
416
|
|
|
225
417
|
**Required secrets:**
|
|
226
|
-
- `NPM_TOKEN`
|
|
418
|
+
- `NPM_TOKEN` — npm access token with publish rights for the `@propriety` scope
|
|
227
419
|
|
|
228
420
|
---
|
|
229
421
|
|
|
230
|
-
## Known
|
|
422
|
+
## Known limitations
|
|
231
423
|
|
|
232
|
-
- **Port 8000 only:** The backend API enforces CORS
|
|
233
|
-
- **
|
|
234
|
-
- **
|
|
235
|
-
- **
|
|
236
|
-
- **
|
|
237
|
-
- **No error UI:** API failures are logged to console but there are no user-facing error notifications (marked with TODO comments in the codebase).
|
|
424
|
+
- **Port 8000 only:** The backend API enforces CORS that only allows requests from `localhost:8000`. Applies to the dev server and any locally running consumer.
|
|
425
|
+
- **Cache is not invalidated on external changes:** If another user modifies a court date, the change won't appear until the local cache expires (1 minute for dates, 2 minutes for cases).
|
|
426
|
+
- **No pagination for court dates:** `getAllDates` fetches every court date in a single request.
|
|
427
|
+
- **Single API key:** No built-in token refresh or rotation.
|
|
428
|
+
- **No error UI for API failures:** Network errors are logged to the console but not surfaced to the user (except for bootstrap errors, which show a brief snackbar).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { CalendarRouteParams } from '../types';
|
|
2
|
+
export default function CCalendar({ apiKey, activeUser, showLegend, mode, themeOverrides, routeParams, onRouteChange, }: {
|
|
2
3
|
apiKey: string;
|
|
3
4
|
activeUser: number;
|
|
4
5
|
showLegend?: boolean;
|
|
5
6
|
mode?: 'light' | 'dark';
|
|
6
7
|
themeOverrides?: import('@mui/material/styles').ThemeOptions;
|
|
8
|
+
routeParams?: CalendarRouteParams;
|
|
9
|
+
onRouteChange?: (params: CalendarRouteParams) => void;
|
|
7
10
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Case, CourtDate } from '../../types';
|
|
2
2
|
import { CalEvent } from '../../helpers/CalEvent';
|
|
3
3
|
import { ModalMode } from './types';
|
|
4
|
-
export default function CCModal({ modalIsOpen, modalMode, setModalMode, setIsOpen, selectedCourtDate, selectedDate, selectedCalEvent, selectedCases, isFetchingCases, }: {
|
|
4
|
+
export default function CCModal({ modalIsOpen, modalMode, setModalMode, setIsOpen, selectedCourtDate, selectedDate, selectedCalEvent, selectedCases, isFetchingCases, editedData, setEditedData, editedCases, setEditedCases, clickedCase, setClickedCase, }: {
|
|
5
5
|
modalIsOpen: boolean;
|
|
6
6
|
modalMode: ModalMode;
|
|
7
7
|
setModalMode: (mode: ModalMode) => void;
|
|
@@ -11,4 +11,10 @@ export default function CCModal({ modalIsOpen, modalMode, setModalMode, setIsOpe
|
|
|
11
11
|
selectedCalEvent: CalEvent | null;
|
|
12
12
|
selectedCases: Case[];
|
|
13
13
|
isFetchingCases: boolean;
|
|
14
|
+
editedData: CourtDate | undefined;
|
|
15
|
+
setEditedData: (data: CourtDate) => void;
|
|
16
|
+
editedCases: Case[];
|
|
17
|
+
setEditedCases: (cases: Case[]) => void;
|
|
18
|
+
clickedCase: Case | null;
|
|
19
|
+
setClickedCase: (c: Case | null) => void;
|
|
14
20
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,6 +6,7 @@ export declare function adjournCases(cases: {
|
|
|
6
6
|
indexNumber: string;
|
|
7
7
|
year: number;
|
|
8
8
|
}[], adjournedDate: Date | null, apiKey: string, editUser?: number): Promise<boolean>;
|
|
9
|
+
export declare function fetchFoilClerkEmails(parcelIds: string[], year: number, apiKey: string): Promise<string[]>;
|
|
9
10
|
export interface FoilAffidavitResult {
|
|
10
11
|
affidavit_key: string;
|
|
11
12
|
sent: string[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CalendarFilterCtx, CalendarRouteParams } from '../types';
|
|
2
|
+
import { DEFAULT_FILTER_CTX } from '../constants';
|
|
3
|
+
export declare function serializeCalendarParams(params: CalendarRouteParams): URLSearchParams;
|
|
4
|
+
export declare function parseCalendarSearchParams(params: URLSearchParams | Record<string, string | undefined>): CalendarRouteParams;
|
|
5
|
+
export declare function buildCalendarRouteParams(view: string, date: Date, courtDateId: number | null, caseIdx: string | null, filterCtx: CalendarFilterCtx): CalendarRouteParams;
|
|
6
|
+
export declare function extractFilterFields(params: CalendarRouteParams): Partial<CalendarFilterCtx>;
|
|
7
|
+
export { DEFAULT_FILTER_CTX };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Case, CalendarFilterCtx, CourtDate, User, CalendarRouteParams } from '../types';
|
|
2
|
+
import { ModalMode } from '../_components/Modal/types';
|
|
3
|
+
import { CalEvent } from '../helpers/CalEvent';
|
|
4
|
+
interface UseRouteSyncOptions {
|
|
5
|
+
currentView: string;
|
|
6
|
+
currentDate: Date;
|
|
7
|
+
selectedCourtDate: CourtDate | null;
|
|
8
|
+
modalIsOpen: boolean;
|
|
9
|
+
modalMode: ModalMode;
|
|
10
|
+
clickedCase: Case | null;
|
|
11
|
+
filterCtx: CalendarFilterCtx;
|
|
12
|
+
allDates: CourtDate[];
|
|
13
|
+
filteredDates: CalEvent[];
|
|
14
|
+
selectedCases: Case[];
|
|
15
|
+
isFetchingDates: boolean;
|
|
16
|
+
isFetchingCases: boolean;
|
|
17
|
+
lastFetchedAt: Date | null;
|
|
18
|
+
allUsers: Record<number, User>;
|
|
19
|
+
setCurrentView: (v: string) => void;
|
|
20
|
+
setCurrentDate: (d: Date) => void;
|
|
21
|
+
setFilterCtx: (ctx: CalendarFilterCtx) => void;
|
|
22
|
+
openDetailsModal: (calEvent: CalEvent) => void;
|
|
23
|
+
setClickedCase: (c: Case | null) => void;
|
|
24
|
+
routeParams?: CalendarRouteParams;
|
|
25
|
+
onRouteChange?: (params: CalendarRouteParams) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function useRouteSync({ currentView, currentDate, selectedCourtDate, modalIsOpen, modalMode, clickedCase, filterCtx, allDates, filteredDates, selectedCases, isFetchingDates, isFetchingCases, lastFetchedAt, allUsers, setCurrentView, setCurrentDate, setFilterCtx, openDetailsModal, setClickedCase, routeParams, onRouteChange, }: UseRouteSyncOptions): {
|
|
28
|
+
bootstrapError: string | null;
|
|
29
|
+
clearBootstrapError: () => void;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
package/dist/index.d.ts
CHANGED