@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 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
- You must also have the following peer dependencies installed:
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
- ### Props
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
- ### Theming
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
- Components use the `.themed` CSS class to pick up theme variables.
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
- ## Development
50
+ ## URL routing / deep linking
56
51
 
57
- ### Prerequisites
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
- - Node.js 20+
60
- - npm
54
+ ### How it works
61
55
 
62
- ### Setup
56
+ The library never reads or writes `window.location` directly when `onRouteChange` is provided. Instead:
63
57
 
64
- ```bash
65
- git clone git@github.com:Aventine-Git/court-calendar.git
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
- ### Dev Server
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
- ```bash
73
- npm run dev
63
+ ```ts
64
+ import {
65
+ CCalendar,
66
+ parseCalendarSearchParams,
67
+ serializeCalendarParams,
68
+ } from '@propriety/court-calendar';
74
69
  ```
75
70
 
76
- This starts the Vite dev server using the `dev/` directory as the entry point (`dev/App.tsx` and `dev/main.tsx`). The dev app wraps the `CCalendar` component for local testing.
71
+ ### Integration examples
77
72
 
78
- **Important:** The API only accepts requests from `localhost:8000`. The dev server must run on port 8000 or API calls will be rejected with CORS errors.
73
+ **React Router v6**
79
74
 
80
- ### Environment Variables
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
- Create a `.env` file in the project root:
95
+ **TanStack Router**
83
96
 
84
- ```
85
- VITE_CALENDAR_API_KEY=your-api-key-here
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
- This key is used by the dev app and is accessed via `import.meta.env.VITE_CALENDAR_API_KEY`.
118
+ **Next.js App Router**
89
119
 
90
- ### Build
91
-
92
- ```bash
93
- npm run build
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
- Runs TypeScript compilation (`tsc -b`) followed by the Vite library build. Output goes to `dist/`:
144
+ ### Standalone mode (no router)
97
145
 
98
- - `dist/index.mjs` - ES module bundle
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
- The build externalizes React, React-DOM, MUI, and Emotion packages (they are peer dependencies and not bundled).
148
+ ### URL parameter reference
103
149
 
104
- ### Lint
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
- ```bash
107
- npm run lint
108
- ```
109
-
110
- Uses ESLint with TypeScript plugin. Biome is also configured for formatting (4-space indent, 120 char line width, single quotes).
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
- There are currently no automated tests. Testing is done manually through the dev server.
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
- ## API
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
- All data is fetched from `https://utils.aventine.ai`. Every request requires the `x-api-key` header.
183
+ The URL does **not** capture:
184
+ - Edit mode or Create mode (transient form state — these are suppressed from `onRouteChange`)
123
185
 
124
- ### Endpoints
186
+ ### Bootstrap sequence
125
187
 
126
- | Endpoint | Method | Description |
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
- ### Limitations
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
- - **Port restriction:** The API only accepts requests from `localhost:8000`. Running the dev server on any other port will result in CORS failures.
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
- | `courtDates.ts` | CRUD operations for court dates. Sends datetime strings combining date + hearing time. |
169
- | `cases.ts` | Case fetching (by court date, paginated, search). Settlement and evidence checks. |
170
- | `cache.ts` | Dexie-based IndexedDB caching with TTL (5 min for dates, 1 hour for cases). |
171
- | `formatter.ts` | Date formatting for API (`YYYY-MM-DD` or `YYYY-MM-DD HH:mm:00`). 12h/24h time conversion. Evidence string formatting. |
172
- | `munis.ts` | Municipality name lookups. Fetches data once on module load. |
173
- | `people.ts` | User and hearing officer lookups. Fetches data once on module load. |
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
- ### Key Types
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: Lifecycle | null; // Scheduled | Assigned | Uploaded | Adjourned
279
+ Lifecycle: 'Scheduled' | 'Assigned' | 'Uploaded' | 'Adjourned' | null;
280
+ DateType: '' | 'Negotiations' | null; // '' = SCAR
185
281
  HearingTime: string;
186
282
  HearingLink: string | null;
187
- Source: SourceType | null; // manual | email | etrack
188
- Type: HearingType | null; // Other | Virtual | InPerson
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
- IsAdjourned: boolean;
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
- DateCompleted: Date | null;
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:** Every merged PR to `main`.
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` - npm access token with publish permissions for the `@propriety` scope
418
+ - `NPM_TOKEN` npm access token with publish rights for the `@propriety` scope
227
419
 
228
420
  ---
229
421
 
230
- ## Known Limitations
422
+ ## Known limitations
231
423
 
232
- - **Port 8000 only:** The backend API enforces CORS restrictions that only allow requests from `localhost:8000`. This applies to the dev server and any consuming application running locally.
233
- - **No test suite:** The project has no automated tests.
234
- - **Module-level side effects:** Municipality names and user data are fetched automatically when their modules are imported, which means network requests happen at import time rather than on demand.
235
- - **Cache is not invalidated on external changes:** If another user modifies a court date, the change won't appear until the local cache expires (5 minutes for dates, 1 hour for cases).
236
- - **Single API key:** The component expects a single API key passed as a prop. There is no built-in token refresh or rotation mechanism.
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
- export default function CCalendar({ apiKey, activeUser, showLegend, mode, themeOverrides, }: {
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
@@ -1 +1,3 @@
1
1
  export { default as CCalendar } from './_components/CCalendar';
2
+ export type { CalendarRouteParams } from './types';
3
+ export { parseCalendarSearchParams, serializeCalendarParams } from './helpers/routing';