@propriety/court-calendar 1.0.153 → 1.0.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/DateDetailsHearingLink.test.d.ts +1 -0
- package/dist/__tests__/DateDetailsMotion.test.d.ts +1 -0
- package/dist/__tests__/DateHistory.test.d.ts +1 -0
- package/dist/__tests__/helpers/motionWindow.test.d.ts +1 -0
- package/dist/_components/Modal/DateDetails/DateHistory.d.ts +9 -3
- package/dist/_components/Modal/DateDetails/HearingLinkRow.d.ts +14 -0
- package/dist/_components/Modal/DateDetails/InfoBox.d.ts +3 -3
- package/dist/_components/Modal/DateDetails/MotionToPreclude.d.ts +22 -0
- package/dist/_components/Modal/DateEdit/EditUserFieldDropdown.d.ts +13 -1
- package/dist/helpers/api/cases.d.ts +9 -1
- package/dist/helpers/courtDates.d.ts +12 -0
- package/dist/helpers/dateHistory.d.ts +27 -0
- package/dist/helpers/formatter.d.ts +7 -0
- package/dist/helpers/motionWindow.d.ts +60 -0
- package/dist/index.mjs +7101 -6866
- package/package.json +1 -1
- package/src/__tests__/DateDetailsHearingLink.test.tsx +151 -0
- package/src/__tests__/DateDetailsMotion.test.tsx +310 -0
- package/src/__tests__/DateHistory.test.tsx +99 -0
- package/src/__tests__/helpers/api/cases.test.ts +67 -0
- package/src/__tests__/helpers/api/courtDates.test.ts +173 -0
- package/src/__tests__/helpers/courtDates.test.ts +119 -1
- package/src/__tests__/helpers/dateHistory.test.ts +88 -1
- package/src/__tests__/helpers/motionWindow.test.ts +227 -0
- package/src/_components/Modal/DateDetails/DateDetails.tsx +63 -135
- package/src/_components/Modal/DateDetails/DateHistory.tsx +123 -37
- package/src/_components/Modal/DateDetails/HearingLinkRow.tsx +64 -0
- package/src/_components/Modal/DateDetails/InfoBox.tsx +17 -11
- package/src/_components/Modal/DateDetails/MotionToPreclude.tsx +195 -0
- package/src/_components/Modal/DateEdit/CreateEditCase.tsx +28 -7
- package/src/_components/Modal/DateEdit/EditUserFieldDropdown.tsx +26 -7
- package/src/helpers/api/cases.ts +7 -2
- package/src/helpers/api/courtDates.ts +36 -2
- package/src/helpers/courtDates.ts +29 -0
- package/src/helpers/dateHistory.ts +81 -6
- package/src/helpers/formatter.ts +17 -0
- package/src/helpers/motionWindow.ts +131 -0
|
@@ -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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hearing link as readable text, with separate open and copy buttons.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not one large click target. The previous "Join Meeting" caption sat under a
|
|
5
|
+
* full-box hover overlay, so operators could not see which meeting a date pointed at and had
|
|
6
|
+
* no way to hand the URL to anyone — clicking anywhere just launched Teams.
|
|
7
|
+
*
|
|
8
|
+
* `link` must already be an absolute http(s) URL; run it through normalizeHearingLink first,
|
|
9
|
+
* or the browser resolves it relative to the origin.
|
|
10
|
+
*/
|
|
11
|
+
export default function HearingLinkRow({ link, onCopy }: {
|
|
12
|
+
link: string;
|
|
13
|
+
onCopy?: (ok: boolean) => void;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export default function InfoBox({ label, info, subInfo, color, dense, labelAsTooltip, tooltipTitle, onOverlayClick, }: {
|
|
2
3
|
label: string;
|
|
3
4
|
info: string;
|
|
4
|
-
subInfo?:
|
|
5
|
-
subInfoHref?: string;
|
|
5
|
+
subInfo?: ReactNode;
|
|
6
6
|
color?: string;
|
|
7
7
|
dense?: boolean;
|
|
8
8
|
labelAsTooltip?: boolean;
|
|
@@ -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;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { CourtDate } from '../../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Keys of CourtDate that hold a user reference — exactly the fields typed `number | null`.
|
|
4
|
+
*
|
|
5
|
+
* Derived rather than hand-listed so it follows the interface, and mutually-extends rather
|
|
6
|
+
* than one-way so a plain `number` field like CourtDateID doesn't qualify. Typing the prop as
|
|
7
|
+
* the bare `keyof CourtDate` checked assignments against the union of *every* field type,
|
|
8
|
+
* which is how a `''` reached FirstChair and shipped without a compiler error.
|
|
9
|
+
*/
|
|
10
|
+
type UserFieldKey = {
|
|
11
|
+
[K in keyof CourtDate]-?: [CourtDate[K]] extends [number | null] ? [number | null] extends [CourtDate[K]] ? K : never : never;
|
|
12
|
+
}[keyof CourtDate];
|
|
2
13
|
export default function EditUserFieldDropdown({ label, edited, setEdited, userPropertyName, }: {
|
|
3
14
|
label: string;
|
|
4
15
|
edited: CourtDate;
|
|
5
16
|
setEdited: (edited: CourtDate) => void;
|
|
6
|
-
userPropertyName:
|
|
17
|
+
userPropertyName: UserFieldKey;
|
|
7
18
|
}): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -38,6 +38,14 @@ export interface MotionUploadResult {
|
|
|
38
38
|
* Triggers the NYSCEF upload of the Motion to Preclude for every case on a court date.
|
|
39
39
|
* The backend returns 409 (skipped) when SkipMotion is set for the date — surfaced here as
|
|
40
40
|
* `{ ok: false, skipped: true }` rather than a hard error. Pass `test` to run without submitting.
|
|
41
|
+
*
|
|
42
|
+
* `userId` is who to attribute the filing to. Every browser shares one API key, so the request
|
|
43
|
+
* itself carries no identity — without this the backend cannot record who filed a motion that
|
|
44
|
+
* "cannot be undone". It goes over the wire as `edit_user`, the name this API uses everywhere
|
|
45
|
+
* else for the acting user, and lands in Court.CourtDateEditLog. Always pass it from the UI.
|
|
41
46
|
*/
|
|
42
|
-
export declare function uploadMotionToPreclude(courtDateId: number, apiKey: string, test?:
|
|
47
|
+
export declare function uploadMotionToPreclude(courtDateId: number, apiKey: string, { test, userId }?: {
|
|
48
|
+
test?: boolean;
|
|
49
|
+
userId?: number;
|
|
50
|
+
}): Promise<MotionUploadResult>;
|
|
43
51
|
export declare function updateCaseAdjournment(parcelId: string, scarIndexNumber: string, adjournedDate: Date | null, apiKey: string): Promise<boolean>;
|
|
@@ -33,3 +33,15 @@ export declare function dateKeyOf(dateType: DateType | null | undefined, courtDa
|
|
|
33
33
|
* type reads as SCAR — matching how the API omits the field for court dates.
|
|
34
34
|
*/
|
|
35
35
|
export declare function isScarDate(date: Pick<CourtDate, 'DateType'>): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* A hearing link safe to hand to window.open, or null.
|
|
38
|
+
*
|
|
39
|
+
* Two things make a stored link unusable, and both fail the same way — the browser
|
|
40
|
+
* resolves the value *relative to the origin*, so "null" opens <host>/null and a pasted
|
|
41
|
+
* "teams.microsoft.com/l/…" opens <host>/teams.microsoft.com/l/…. We drop the first and
|
|
42
|
+
* repair the second by assuming https.
|
|
43
|
+
*
|
|
44
|
+
* Non-http(s) schemes are rejected outright: this value is operator-entered and goes
|
|
45
|
+
* straight into window.open, so a javascript: URL would otherwise execute.
|
|
46
|
+
*/
|
|
47
|
+
export declare function normalizeHearingLink(link: string | null | undefined): string | null;
|
|
@@ -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
|
*
|
|
@@ -1,4 +1,11 @@
|
|
|
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;
|
|
@@ -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;
|