@propriety/court-calendar 1.0.154 → 1.0.156

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 (45) hide show
  1. package/dist/__tests__/CalendarLegend.test.d.ts +1 -0
  2. package/dist/__tests__/CalendarListLabels.test.d.ts +1 -0
  3. package/dist/__tests__/DateHistory.test.d.ts +1 -0
  4. package/dist/__tests__/ReferenceDataLookups.test.d.ts +1 -0
  5. package/dist/__tests__/helpers/eventLabel.test.d.ts +1 -0
  6. package/dist/__tests__/helpers/motionWindow.test.d.ts +1 -0
  7. package/dist/_components/Modal/DateDetails/DateHistory.d.ts +9 -3
  8. package/dist/_components/Modal/DateDetails/MotionToPreclude.d.ts +22 -0
  9. package/dist/_components/Shared/CountyDropdown.d.ts +15 -0
  10. package/dist/context/ReferenceDataContext.d.ts +1 -0
  11. package/dist/helpers/api/counties.d.ts +12 -0
  12. package/dist/helpers/dateHistory.d.ts +27 -0
  13. package/dist/helpers/eventLabel.d.ts +52 -0
  14. package/dist/helpers/formatter.d.ts +15 -0
  15. package/dist/helpers/motionWindow.d.ts +60 -0
  16. package/dist/index.mjs +8296 -8009
  17. package/package.json +1 -1
  18. package/src/__tests__/CalendarLegend.test.tsx +42 -0
  19. package/src/__tests__/CalendarListLabels.test.tsx +111 -0
  20. package/src/__tests__/DateDetailsMotion.test.tsx +162 -81
  21. package/src/__tests__/DateHistory.test.tsx +99 -0
  22. package/src/__tests__/ReferenceDataLookups.test.tsx +99 -0
  23. package/src/__tests__/WeekWidget.test.tsx +4 -1
  24. package/src/__tests__/collectionsPipeline.test.ts +36 -0
  25. package/src/__tests__/helpers/dateHistory.test.ts +88 -1
  26. package/src/__tests__/helpers/eventLabel.test.ts +133 -0
  27. package/src/__tests__/helpers/formatter.test.ts +37 -1
  28. package/src/__tests__/helpers/motionWindow.test.ts +227 -0
  29. package/src/__tests__/hooks/UseModalActions.test.ts +1 -0
  30. package/src/_components/CCalendar.tsx +9 -0
  31. package/src/_components/CalendarLegend.tsx +11 -3
  32. package/src/_components/List/CalendarList.tsx +22 -5
  33. package/src/_components/Modal/DateDetails/DateDetails.tsx +32 -171
  34. package/src/_components/Modal/DateDetails/DateHistory.tsx +123 -37
  35. package/src/_components/Modal/DateDetails/MotionToPreclude.tsx +195 -0
  36. package/src/_components/Modal/DateEdit/CreateEditCase.tsx +44 -19
  37. package/src/_components/Shared/CountyDropdown.tsx +73 -0
  38. package/src/_components/WeekWidget/WeekWidget.tsx +24 -14
  39. package/src/context/ReferenceDataContext.tsx +61 -27
  40. package/src/helpers/api/counties.ts +32 -0
  41. package/src/helpers/dateHistory.ts +81 -6
  42. package/src/helpers/eventLabel.ts +104 -0
  43. package/src/helpers/formatter.ts +35 -0
  44. package/src/helpers/motionWindow.ts +131 -0
  45. package/src/hooks/UseCalendarEvents.ts +34 -14
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,8 +1,14 @@
1
1
  import { HistoryEntry } from '../../../helpers/dateHistory';
2
2
  /**
3
- * Compact, newest-first trail of everything that has moved this court date — reschedules
4
- * and bulk case adjournments. Renders nothing when there is no history, so unchanged court
5
- * dates keep their header exactly as it was.
3
+ * The trail of everything that has moved this court date.
4
+ *
5
+ * Reschedules render as one left-to-right path — `Feb 23 → Mar 23 → Aug 5` — rather than
6
+ * one chip per log row, so the order reads the way the arrows point and a date shared by
7
+ * two consecutive moves is only printed once. Bulk case adjournments are a different kind
8
+ * of event (they move cases off this date, not the date itself) and keep their own row.
9
+ *
10
+ * Renders nothing when there is no history, so unchanged court dates keep their header
11
+ * exactly as it was.
6
12
  */
7
13
  export default function DateHistory({ entries, align, }: {
8
14
  entries: HistoryEntry[];
@@ -0,0 +1,22 @@
1
+ import { AdjournmentEntry, Case, CourtDate } from '../../../types';
2
+ export interface MotionToast {
3
+ severity: 'success' | 'error' | 'info';
4
+ text: string;
5
+ }
6
+ /**
7
+ * The Motion to Preclude panel: window status and the two actions.
8
+ *
9
+ * Both buttons render for every SCAR date rather than appearing only when usable. The
10
+ * window is a few weeks out of a court date's months-long life, and when the controls were
11
+ * hidden outside it nobody discovered the feature existed — so they stay put and explain
12
+ * themselves through the status line and their tooltips.
13
+ *
14
+ * Callers gate on `isScarDate`: negotiation and collections dates never carry a motion.
15
+ */
16
+ export default function MotionToPreclude({ courtDate, township, adjournments, cases, onNotify, }: {
17
+ courtDate: CourtDate;
18
+ township: string;
19
+ adjournments?: AdjournmentEntry[];
20
+ cases?: Pick<Case, 'AdjournedDate'>[];
21
+ onNotify: (toast: MotionToast) => void;
22
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * County picker for collections dates, which are scheduled for a whole county rather
3
+ * than one municipality. The selected value is stored in the same MuniCode field every
4
+ * other date type uses, but holds a county code ('N') instead of a muni code ('N2000').
5
+ *
6
+ * Deliberately not part of MuniDropdown: only collections may pick a county, and county
7
+ * entries must never appear in the municipality list.
8
+ */
9
+ export default function CountyDropdown({ selectedCounty, setSelectedCounty, size, onOpen, onClose, }: {
10
+ selectedCounty: string | null;
11
+ setSelectedCounty: (countyCode: string) => void;
12
+ size: 'small' | 'medium';
13
+ onOpen?: () => void;
14
+ onClose?: () => void;
15
+ }): import("react/jsx-runtime").JSX.Element;
@@ -5,6 +5,7 @@ interface ReferenceDataContextType {
5
5
  allUsers: Record<number, User>;
6
6
  allHearingOfficers: Record<number, HearingOfficer>;
7
7
  allMuniNames: Record<string, Muni>;
8
+ allCounties: Record<string, string>;
8
9
  getTownshipName: (muniCode: string) => string;
9
10
  getCountyName: (muniCode: string) => string;
10
11
  isLoading: boolean;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * County code -> county name, e.g. { N: 'Nassau', W: 'Westchester' }.
3
+ *
4
+ * A county code is the single letter every municipality code starts with, so 'N'
5
+ * covers all of Nassau just as 'N2000' covers Hempstead. Collections dates are
6
+ * scheduled per county rather than per municipality and store one of these codes
7
+ * in MuniCode.
8
+ *
9
+ * Deliberately a separate endpoint from /utils/get-muni-names: county codes must
10
+ * not appear in the municipality dropdown.
11
+ */
12
+ export declare function getAllCounties(apiKey: string): Promise<Record<string, string>>;
@@ -31,6 +31,8 @@ export declare function parseHistoryDate(value: Date | string | null | undefined
31
31
  * than a meaningless "User 420".
32
32
  */
33
33
  export declare function resolveUserName(updatedBy: number | string | null | undefined, allUsers: Record<number, User>): string;
34
+ /** Same calendar day, ignoring the time component the edit log carries. */
35
+ export declare function isSameDay(a: Date | null, b: Date | null): boolean;
34
36
  /**
35
37
  * Merge the court date's reschedule trail with its case adjournments into one
36
38
  * time-ordered list, newest first.
@@ -40,6 +42,31 @@ export declare function resolveUserName(updatedBy: number | string | null | unde
40
42
  * be 20 identical chips — CourtDateID 296 is exactly that shape in production.
41
43
  */
42
44
  export declare function buildDateHistory(courtDateHistory: DateEditItem[], adjournments: AdjournmentEntry[], allUsers: Record<number, User>): HistoryEntry[];
45
+ /** One date the court date has sat on, as a stop along the reschedule trail. */
46
+ export interface TrailNode {
47
+ /** Stable React key. */
48
+ key: string;
49
+ date: Date | null;
50
+ /** The reschedule that landed on this date. Null on the stop a run starts from. */
51
+ entry: HistoryEntry | null;
52
+ /** False when this stop does not continue the one before it — the log has a hole. */
53
+ connected: boolean;
54
+ }
55
+ /**
56
+ * Flatten the reschedule entries into the ordered path of dates this court date has
57
+ * actually sat on, oldest first.
58
+ *
59
+ * Consecutive log rows chain: a row's `OldValue` is the previous row's `NewValue`. So
60
+ * three separate "A → B", "B → C", "C → D" chips are really one path, A → B → C → D.
61
+ * Rendering the path instead of the edges drops the repeated endpoints and puts the
62
+ * events in reading order, which the newest-first chip list could not do.
63
+ *
64
+ * When a row's origin does not match the previous stop the trail has a hole — rows are
65
+ * only written for edits made through the app, so a direct DB change leaves no row. That
66
+ * stop is marked `connected: false` so the UI can show a break rather than imply a move
67
+ * that never happened.
68
+ */
69
+ export declare function buildRescheduleTrail(entries: HistoryEntry[]): TrailNode[];
43
70
  /**
44
71
  * The adjournment rows belonging to a single case.
45
72
  *
@@ -0,0 +1,52 @@
1
+ import { DateType } from '../types';
2
+ /**
3
+ * The pieces every surface builds a date's label from.
4
+ *
5
+ * Kept as parts rather than a finished string because the surfaces compose them
6
+ * differently: the calendar wants one line, the week widget stacks two, and the table
7
+ * already has its own Type and County columns and only needs the place.
8
+ */
9
+ export interface DateLabelParts {
10
+ /** 'Collections' | 'Negotiations'. Null for SCAR — a court date is the default. */
11
+ typeWord: string | null;
12
+ /** County for Collections, township/village otherwise. '' when unresolvable. */
13
+ place: string;
14
+ /** ' (3100)' — SCAR only. Non-court types drop the code. */
15
+ codeSuffix: string;
16
+ /**
17
+ * Whether a case count means anything. SCAR only: collections are scheduled
18
+ * without cases, and negotiation dates are never linked to any, so both would
19
+ * show a permanent "x0".
20
+ */
21
+ showCaseCount: boolean;
22
+ }
23
+ /**
24
+ * Resolve a date's label parts.
25
+ *
26
+ * `getTownshipName` and `getCountyName` come from `useReferenceData()` — passed in
27
+ * rather than imported so this stays a pure function that tests can drive directly.
28
+ */
29
+ export declare function dateLabelParts(dateType: DateType | null | undefined, muniCode: string, getTownshipName: (muniCode: string) => string, getCountyName: (muniCode: string) => string): DateLabelParts;
30
+ /**
31
+ * One line naming the date, with no counts or day/adjournment markers.
32
+ * Used by the tooltip, the details modal header and anywhere a plain title is wanted.
33
+ */
34
+ export declare function dateHeadline(parts: DateLabelParts): string;
35
+ export interface EventTitleOptions {
36
+ /** Marks the occurrence as an adjournment: "Greenburgh (A) (3100)". */
37
+ adjourned?: boolean;
38
+ /** 1-based day for a multi-date hearing; omitted for a single-day date. */
39
+ dayNumber?: number;
40
+ /** Open case count, appended as "x4" when the type supports counts. */
41
+ unsettled?: number;
42
+ /** Leading verb for derived events, e.g. 'Upload' or 'Can Preclude'. */
43
+ prefix?: string;
44
+ }
45
+ /**
46
+ * The FullCalendar event title.
47
+ *
48
+ * Carries no time — FullCalendar renders that itself from the event's `start`, using
49
+ * the `eventTimeFormat` set on the calendar, and prepends it. So a collections event
50
+ * reads "1pm Collections Nassau" on screen while this returns "Collections Nassau".
51
+ */
52
+ export declare function formatEventTitle(parts: DateLabelParts, options?: EventTitleOptions): string;
@@ -1,5 +1,20 @@
1
1
  import { Evidence } from '../types';
2
+ /**
3
+ * MM/DD/YYYY for display, or "-" when there is no date.
4
+ *
5
+ * Routed through `parseLocalDate` so a date-only string keeps its calendar day: `new
6
+ * Date("2026-08-14")` is UTC midnight, which renders as 08/13 in any US timezone.
7
+ */
8
+ export declare function formatDisplayDate(date: Date | string | null | undefined): string;
2
9
  export declare function formatDateForAPI(date: Date): string;
3
10
  export declare function formatDateTimeForAPI(date: Date, hearingTime?: string): string;
4
11
  export declare function to24Hour(time: string): string;
12
+ /**
13
+ * A stored hearing time as "1pm" / "9:30am", or "" when there is nothing to show.
14
+ *
15
+ * Matches the `eventTimeFormat` the calendar passes to FullCalendar, so a chip rendered
16
+ * outside FullCalendar reads the same as the event it refers to. Accepts anything
17
+ * `to24Hour` accepts — "13:00:00" from Collections, "2:30 PM" from a picker, "10:30".
18
+ */
19
+ export declare function formatClockTime(time: string | null | undefined): string;
5
20
  export declare function formatEvidence(evidence: Evidence): string;
@@ -0,0 +1,60 @@
1
+ import { AdjournmentEntry, Case, CourtDate } from '../types';
2
+ /**
3
+ * Where a court date sits relative to its Motion to Preclude upload window.
4
+ *
5
+ * The window runs from `MotionDate` — the *first* day a motion can be uploaded, which the
6
+ * backend computes as four business days before the hearing — through the hearing itself.
7
+ * It is not a countdown to a deadline: `MotionDate` opens the window, the hearing closes it.
8
+ */
9
+ export type MotionWindowState = 'no-motion-date' | 'skipped' | 'not-yet-open' | 'open' | 'closed';
10
+ export interface MotionWindow {
11
+ state: MotionWindowState;
12
+ /** A draft PDF can be produced for review. Never files anything. */
13
+ canDownload: boolean;
14
+ /** The motion can be uploaded to NYSCEF right now. */
15
+ canUpload: boolean;
16
+ /** First day uploading is possible — the court date's MotionDate. */
17
+ opensOn: Date | null;
18
+ /** Last day uploading is possible — the hearing, pushed out by any adjournment. */
19
+ closesOn: Date | null;
20
+ /** Last *scheduled* hearing day: CourtDate, or the final entry of MultipleDates. */
21
+ hearingOn: Date | null;
22
+ /** Set only when an adjournment moved the hearing past `hearingOn`, else null. */
23
+ adjournedTo: Date | null;
24
+ }
25
+ /**
26
+ * Midnight-local for any shape the API hands back, without mutating the input.
27
+ *
28
+ * Date-only strings must not go through `new Date(s)` — that reads "2026-08-14" as UTC
29
+ * midnight, which is the 13th anywhere west of Greenwich. Every comparison in this module
30
+ * is day-granular, so one bad parse is a whole day of wrong answer.
31
+ */
32
+ export declare function startOfDay(value: Date | string | null | undefined): Date | null;
33
+ /**
34
+ * Resolves the Motion to Preclude upload window for a court date.
35
+ *
36
+ * The close boundary is the *effective* hearing day, which is the latest of:
37
+ * - `CourtDate`
38
+ * - every entry in `MultipleDates` (a multi-day hearing is not over until its last day)
39
+ * - every adjourned-to date belonging to this court date
40
+ *
41
+ * Taking the maximum across adjournments is deliberate. Adjournments are per case, so a
42
+ * date can be part-adjourned; as long as one case still has a hearing ahead of it the
43
+ * motion is live. Which cases actually get a motion is the backend's call —
44
+ * `_get_active_indices_for_motion` filters them server-side — so gating the button on the
45
+ * most permissive boundary avoids the frontend refusing an upload the backend would accept.
46
+ *
47
+ * @param courtDate The selected date. Non-SCAR dates never carry a motion; callers gate
48
+ * on `isScarDate` before rendering the control at all.
49
+ * @param adjournments Rows from `GET /court-dates/{id}` — already scoped to this date, but
50
+ * re-checked here because `AdjournmentEntry.HearingDate` holds the
51
+ * originating CourtDateID (yes, an id in a field named for a date).
52
+ * @param cases The date's cases; `AdjournedDate` is a second, independent record of
53
+ * the same adjournment and covers a detail fetch that has not landed.
54
+ * @param today Injectable for tests. Defaults to now.
55
+ */
56
+ export declare function computeMotionWindow(courtDate: CourtDate | null | undefined, { adjournments, cases, today, }?: {
57
+ adjournments?: AdjournmentEntry[];
58
+ cases?: Pick<Case, 'AdjournedDate'>[];
59
+ today?: Date;
60
+ }): MotionWindow;