@propriety/court-calendar 1.0.156 → 1.0.158

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/.vscode/extensions.json +108 -0
  2. package/dist/__tests__/ApiFailureReporter.test.d.ts +1 -0
  3. package/dist/__tests__/CopyChip.test.d.ts +1 -0
  4. package/dist/__tests__/CreateEditCaseOpenCases.test.d.ts +1 -0
  5. package/dist/__tests__/fixtures.d.ts +2 -0
  6. package/dist/__tests__/helpers/api/apiFailures.test.d.ts +1 -0
  7. package/dist/__tests__/helpers/openCases.test.d.ts +1 -0
  8. package/dist/_components/ErrorReport/ApiFailureReporter.d.ts +14 -0
  9. package/dist/_components/Modal/CaseDetails/CopyChip.d.ts +13 -0
  10. package/dist/_components/Modal/DateEdit/OpenCaseSelector.d.ts +20 -0
  11. package/dist/_components/Modal/DateEdit/TextFieldList.d.ts +4 -1
  12. package/dist/_components/Shared/SearchBar.d.ts +2 -1
  13. package/dist/helpers/api/apiFailures.d.ts +68 -0
  14. package/dist/helpers/api/cases.d.ts +32 -0
  15. package/dist/helpers/clipboard.d.ts +9 -0
  16. package/dist/helpers/openCases.d.ts +108 -0
  17. package/dist/hooks/UseModalActions.d.ts +3 -2
  18. package/dist/index.mjs +10172 -9532
  19. package/package.json +1 -1
  20. package/src/__tests__/ApiFailureReporter.test.tsx +171 -0
  21. package/src/__tests__/CopyChip.test.tsx +54 -0
  22. package/src/__tests__/CreateEditCaseOpenCases.test.tsx +304 -0
  23. package/src/__tests__/fixtures.ts +14 -0
  24. package/src/__tests__/helpers/api/apiFailures.test.ts +305 -0
  25. package/src/__tests__/helpers/api/cases.test.ts +71 -0
  26. package/src/__tests__/helpers/openCases.test.ts +504 -0
  27. package/src/__tests__/hooks/UseModalActions.test.ts +39 -0
  28. package/src/_components/CCalendar.tsx +5 -0
  29. package/src/_components/DebugModal/Dashboard.tsx +6 -2
  30. package/src/_components/ErrorReport/ApiFailureReporter.tsx +151 -0
  31. package/src/_components/List/CalendarList.tsx +6 -3
  32. package/src/_components/Modal/CaseDetails/CaseDetails.tsx +11 -27
  33. package/src/_components/Modal/CaseDetails/CopyChip.tsx +55 -0
  34. package/src/_components/Modal/DateDetails/CaseViewer.tsx +24 -4
  35. package/src/_components/Modal/DateEdit/CreateEditCase.tsx +91 -13
  36. package/src/_components/Modal/DateEdit/OpenCaseSelector.tsx +338 -0
  37. package/src/_components/Modal/DateEdit/TextFieldList.tsx +5 -1
  38. package/src/_components/Modal/Modal.tsx +13 -2
  39. package/src/_components/Shared/SearchBar.tsx +3 -1
  40. package/src/helpers/api/apiFailures.ts +234 -0
  41. package/src/helpers/api/cases.ts +148 -62
  42. package/src/helpers/api/courtDates.ts +57 -42
  43. package/src/helpers/clipboard.ts +36 -0
  44. package/src/helpers/openCases.ts +271 -0
  45. package/src/hooks/UseModalActions.ts +29 -15
@@ -0,0 +1,108 @@
1
+ {
2
+ // Stack for this repo: React 19 + TypeScript + Vite (library mode) + Biome + Vitest + MUI 5.
3
+ // NOTE: "unwantedRecommendations" only suppresses VS Code's recommendation prompts.
4
+ // It does NOT disable an installed extension. To actually disable, either:
5
+ // a) Extensions view -> right-click -> "Disable (Workspace)", or
6
+ // b) import the profile at .vscode/court-calendar.code-profile (see README note below).
7
+ "recommendations": [
8
+ "biomejs.biome",
9
+ "yoavbls.pretty-ts-errors",
10
+ "antfu.vite",
11
+ "christian-kohler.path-intellisense",
12
+ "christian-kohler.npm-intellisense",
13
+ "eamodio.gitlens",
14
+ "github.vscode-pull-request-github"
15
+ ],
16
+
17
+ "unwantedRecommendations": [
18
+ // --- Formatter/linter conflicts: this repo uses Biome exclusively ---
19
+ "esbenp.prettier-vscode",
20
+ "dbaeumer.vscode-eslint",
21
+ "inferrinizzard.prettier-sql-vscode",
22
+
23
+ // --- Python stack (lives in ../adminAPI, ../Utility-API, ../pdfOcr) ---
24
+ "ms-python.python",
25
+ "ms-python.vscode-pylance",
26
+ "ms-python.debugpy",
27
+ "ms-python.black-formatter",
28
+ "ms-python.isort",
29
+ "ms-python.vscode-python-envs",
30
+ "charliermarsh.ruff",
31
+ "kevinrose.vsc-python-indent",
32
+
33
+ // --- Jupyter ---
34
+ "ms-toolsai.jupyter",
35
+ "ms-toolsai.jupyter-renderers",
36
+ "ms-toolsai.jupyter-keymap",
37
+ "ms-toolsai.vscode-jupyter-cell-tags",
38
+ "ms-toolsai.vscode-jupyter-slideshow",
39
+
40
+ // --- .NET / C# ---
41
+ "ms-dotnettools.csharp",
42
+ "ms-dotnettools.csdevkit",
43
+ "ms-dotnettools.vscode-dotnet-runtime",
44
+
45
+ // --- C / C++ / shaders ---
46
+ "ms-vscode.cpptools",
47
+ "jbenden.c-cpp-flylint",
48
+ "slevesque.shader",
49
+
50
+ // --- AutoHotkey (three overlapping extensions) ---
51
+ "cweijan.vscode-autohotkey-plus",
52
+ "mark-wiemer.vscode-autohotkey-plus-plus",
53
+ "thqby.vscode-autohotkey2-lsp",
54
+
55
+ // --- Other frameworks not used here (MUI + Emotion, no Tailwind/Vue/Sass) ---
56
+ "vue.volar",
57
+ "bradlc.vscode-tailwindcss",
58
+ "austenc.tailwind-docs",
59
+ "syler.sass-indented",
60
+ "glenn2223.live-sass",
61
+
62
+ // --- AWS / containers (belongs to ../lambdas) ---
63
+ "amazonwebservices.aws-toolkit-vscode",
64
+ "ms-azuretools.vscode-containers",
65
+ "ms-vscode-remote.remote-containers",
66
+
67
+ // --- Static/live servers: superseded by `npm run dev` (Vite) ---
68
+ "ritwickdey.liveserver",
69
+ "ms-vscode.live-server",
70
+ "george-alisson.html-preview-vscode",
71
+ "antfu.browse-lite",
72
+
73
+ // --- Redundant with built-in editor features (see .vscode/settings.json) ---
74
+ "formulahendry.auto-rename-tag",
75
+ "formulahendry.auto-close-tag",
76
+ "steoates.autoimport",
77
+ "kevinmcgowan.typescriptimport",
78
+ "donjayamanne.githistory",
79
+ "xyz.local-history",
80
+
81
+ // --- Continuous-CPU novelty extensions ---
82
+ "tonybaloney.vscode-pets",
83
+ "hoovercj.vscode-power-mode",
84
+
85
+ // --- Heavy eager extensions with no role in this repo ---
86
+ "shd101wyy.markdown-preview-enhanced",
87
+ "zignd.html-css-class-completion",
88
+ "ecmel.vscode-html-css",
89
+ "hwencc.html-tag-wrapper",
90
+ "kisstkondoros.vscode-gutter-preview",
91
+ "alefragnani.project-manager",
92
+ "ms-vsliveshare.vsliveshare",
93
+ "tamasfe.even-better-toml",
94
+
95
+ // --- Data/document viewers ---
96
+ "grapecity.gc-excelviewer",
97
+ "muhammad-ahmad.xlsx-viewer",
98
+ "mechatroner.rainbow-csv",
99
+ "tomoki1207.pdf",
100
+
101
+ // --- Misc unused languages ---
102
+ "eww-yuck.yuck",
103
+ "andrejunges.handlebars",
104
+ "mrmlnc.vscode-json5",
105
+ "idered.npm",
106
+ "petekinnecom.terminal-command-keys"
107
+ ]
108
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,8 @@
1
1
  import { Case, CourtDate, CalendarFilterCtx } from '../types';
2
+ import { OpenCase } from '../helpers/api/cases';
2
3
  export declare const MOCK_API_KEY = "test-api-key-123";
3
4
  export declare const MOCK_USER_ID = 42;
4
5
  export declare function makeCourtDate(overrides?: Partial<CourtDate>): CourtDate;
6
+ export declare function makeOpenCase(overrides?: Partial<OpenCase>): OpenCase;
5
7
  export declare function makeCase(overrides?: Partial<Case>): Case;
6
8
  export declare const DEFAULT_FILTER_CTX: CalendarFilterCtx;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The single place a failed API call becomes visible.
3
+ *
4
+ * Deliberately **persistent** — no `autoHideDuration`. A four-second toast is how a failed
5
+ * delete went unnoticed in the first place; an error the user needs to report has to wait for
6
+ * them. Anchored top-center so it never collides with the transient bottom-center snackbars
7
+ * (saved / hidden-by-filter / bootstrap) that the calendar already uses.
8
+ *
9
+ * Mount once, high in the tree. It subscribes to the module-level failure bus in
10
+ * `helpers/api/apiFailures`, so it needs no props from the call sites that fail.
11
+ */
12
+ export default function ApiFailureReporter({ activeUser }: {
13
+ activeUser?: number;
14
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Click-to-copy chip used for the identity fields on the Case Details panel
3
+ * (parcel ID, SCAR index number). Each instance owns its own "Copied!" state so
4
+ * copying one field never flips the tooltip on another.
5
+ *
6
+ * Renders as a `span` by default: `InfoField` wraps its value in a `Typography`
7
+ * (a `<p>`), and a `div` inside a `<p>` is invalid nesting.
8
+ */
9
+ export default function CopyChip({ value, size, component, }: {
10
+ value: string;
11
+ size?: 'small' | 'medium';
12
+ component?: React.ElementType;
13
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { Case } from '../../../types';
2
+ /**
3
+ * The Case IDs list rendered as a pick-list of the muni's open cases, with the client name
4
+ * alongside each one — so a hearing notice that names only "Bonaimo" can be resolved to an
5
+ * index number without leaving the form.
6
+ *
7
+ * There is no selection state of its own: a row is checked exactly when its case is already
8
+ * in `editedCases`, which is the same array the manual text fields edit. That is what makes
9
+ * switching between the two modes seamless, and it means a checked row can never drift out
10
+ * of sync with what will actually be saved.
11
+ */
12
+ export default function OpenCaseSelector({ muniCode, isVillage, editedCases, setEditedCases, isParcelIdMode, setIsParcelIdMode, defaultYear, }: {
13
+ muniCode: string;
14
+ isVillage: boolean;
15
+ editedCases: Case[];
16
+ setEditedCases: (cases: Case[]) => void;
17
+ isParcelIdMode: boolean[];
18
+ setIsParcelIdMode: (toggles: boolean[]) => void;
19
+ defaultYear: number;
20
+ }): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,5 @@
1
- export default function TextFieldList({ textFields, setTextFields, label, fieldLabelPrefix, toggleStates, setToggleStates, alternateFieldLabelPrefix, }: {
1
+ import { default as React } from 'react';
2
+ export default function TextFieldList({ textFields, setTextFields, label, fieldLabelPrefix, toggleStates, setToggleStates, alternateFieldLabelPrefix, headerAction, }: {
2
3
  textFields: string[];
3
4
  setTextFields: (fields: string[], toggles?: boolean[]) => void;
4
5
  label: string;
@@ -6,4 +7,6 @@ export default function TextFieldList({ textFields, setTextFields, label, fieldL
6
7
  toggleStates?: boolean[];
7
8
  setToggleStates?: (toggles: boolean[]) => void;
8
9
  alternateFieldLabelPrefix?: string;
10
+ /** Rendered at the end of the header row — e.g. an alternative way to fill the list. */
11
+ headerAction?: React.ReactNode;
9
12
  }): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,6 @@
1
- export default function SearchBar({ searchTerm, setSearchTerm, width, }: {
1
+ export default function SearchBar({ searchTerm, setSearchTerm, width, placeholder, }: {
2
2
  searchTerm: string;
3
3
  setSearchTerm: (term: string) => void;
4
4
  width?: string | number;
5
+ placeholder?: string;
5
6
  }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * A cross-cutting sink for API failures, so a request the server rejected always reaches the
3
+ * screen instead of only the console.
4
+ *
5
+ * Every helper in this folder reports failure by returning `false`/`null` and writing a
6
+ * `console.error` — which is why a delete the server refused looked identical to one that
7
+ * worked. Rather than change each helper's signature (and every call site, and every
8
+ * `.then(ok => …)` that depends on the boolean), each failing branch now *also* publishes an
9
+ * `ApiFailure` here. `ApiFailureReporter` subscribes on mount and renders whatever arrives.
10
+ *
11
+ * A module-level bus rather than React context because these fire from plain async functions
12
+ * that have no access to a provider — the same reason crash reporters are built this way.
13
+ *
14
+ * NOTE: nothing published here may contain the API key. The report is written to be pasted
15
+ * into a chat or ticket, so it carries the request *shape* (method, URL, status, response
16
+ * body) but never a credential.
17
+ */
18
+ export interface ApiFailure {
19
+ /** What the user was trying to do, in their words: "Delete court date". */
20
+ operation: string;
21
+ /** Which record it was, e.g. "#1234 (Negotiations)". */
22
+ subject?: string;
23
+ method?: string;
24
+ url?: string;
25
+ /** `null` when the request never reached the server — offline, DNS, CORS, or a timeout. */
26
+ status: number | null;
27
+ /** One-line plain-English cause, shown to the user. */
28
+ detail: string;
29
+ /** Raw response body (or the thrown error's message), truncated. For the tech team. */
30
+ body?: string;
31
+ at: Date;
32
+ }
33
+ type Listener = (failure: ApiFailure) => void;
34
+ /** Subscribe to API failures. Returns an unsubscribe function suitable for a `useEffect`. */
35
+ export declare function onApiFailure(listener: Listener): () => void;
36
+ /** Publish a failure to every subscriber. A throwing listener must not break the others. */
37
+ export declare function publishApiFailure(failure: ApiFailure): void;
38
+ /** Log and publish a non-2xx response. */
39
+ export declare function reportHttpFailure(operation: string, method: string, url: string, res: Response, subject?: string): Promise<void>;
40
+ /**
41
+ * Log and publish a request that never completed.
42
+ *
43
+ * `fetch` *rejects* rather than resolving with `ok: false` when the browser cannot reach the
44
+ * server at all — offline, DNS failure, a CORS preflight refusal, a dropped connection. Those
45
+ * used to escape as unhandled promise rejections from a click handler: no console error, no UI.
46
+ */
47
+ export declare function reportNetworkFailure(operation: string, method: string, url: string, error: unknown, subject?: string): void;
48
+ /**
49
+ * Publish an application-level problem that is not a single failed request — a partially
50
+ * applied save, for example, where each individual call succeeded or was already reported.
51
+ */
52
+ export declare function reportProblem(problem: {
53
+ operation: string;
54
+ subject?: string;
55
+ detail: string;
56
+ body?: string;
57
+ }): void;
58
+ /**
59
+ * The copy-paste block handed to the tech team.
60
+ *
61
+ * Written for a chat message or ticket: fixed-width, no markdown, and self-contained — whoever
62
+ * receives it should not have to ask "which record?", "what time?", or "what did the server
63
+ * actually say?". Carries no API key (see the file header).
64
+ */
65
+ export declare function formatFailureReport(failures: ApiFailure[], context?: {
66
+ activeUser?: number;
67
+ }): string;
68
+ export {};
@@ -48,4 +48,36 @@ export declare function uploadMotionToPreclude(courtDateId: number, apiKey: stri
48
48
  test?: boolean;
49
49
  userId?: number;
50
50
  }): Promise<MotionUploadResult>;
51
+ /**
52
+ * A case returned by /court-cases/open-cases — a much thinner record than `Case`, carrying
53
+ * only what the picker needs to identify a property by owner name.
54
+ *
55
+ * `SCARIndexNumber` holds whichever index applies to the level queried: the backend aliases
56
+ * `VillageSCARIndexNumber AS SCARIndexNumber` for village munis, so there is one index field
57
+ * and it is always the right one. `retainer_name` is the *retainer*, not necessarily the
58
+ * assessment-roll owner, and both it and `Address` are nullable (they come from LEFT JOINs).
59
+ */
60
+ export interface OpenCase {
61
+ ParcelID: string;
62
+ SCARIndexNumber: string;
63
+ Year: number;
64
+ retainer_name: string | null;
65
+ Address: string | null;
66
+ }
67
+ /**
68
+ * Open (filed, unscheduled, unsettled) cases for a municipality and tax year.
69
+ *
70
+ * **Pass the court date's `MuniCode` verbatim — do not pad it.** The backend derives two
71
+ * things from this one string, and padding breaks both:
72
+ * - It decides town vs village by `len(muni_code) > 3`, so padding "S05" to "S0500" makes it
73
+ * run the *village* query for a town date.
74
+ * - It matches `ParcelID LIKE '<muni_code>%'`. A town's SCAR calendar legitimately includes
75
+ * parcels sitting inside its villages ("S0501-…" carries a town index number), so the
76
+ * short code is what selects the right population, not an over-broad one.
77
+ *
78
+ * Unlike its siblings this *throws* on a non-OK response instead of returning []. The picker
79
+ * has to tell "the request failed" apart from "this muni has no open cases", and a silent
80
+ * empty array reads as the latter.
81
+ */
82
+ export declare function fetchOpenCases(muniCode: string, year: number, apiKey: string): Promise<OpenCase[]>;
51
83
  export declare function updateCaseAdjournment(parcelId: string, scarIndexNumber: string, adjournedDate: Date | null, apiKey: string): Promise<boolean>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copy text to the clipboard, reporting honestly whether it worked.
3
+ *
4
+ * `navigator.clipboard` is unavailable on insecure origins and *rejects* when the document
5
+ * isn't focused or the permission was denied — so a bare `writeText()` can silently leave the
6
+ * clipboard empty while the UI says "Copied". Both paths are tried and the caller is told
7
+ * which happened, so a failure can fall back to showing the text for manual selection.
8
+ */
9
+ export declare function copyText(text: string): Promise<boolean>;
@@ -0,0 +1,108 @@
1
+ import { Case } from '../types';
2
+ import { OpenCase } from './api/cases';
3
+ /** A grid row: an OpenCase plus the stable id the DataGrid and the selection Map key on. */
4
+ export type OpenCaseRow = OpenCase & {
5
+ id: string;
6
+ };
7
+ /**
8
+ * June (0-based). A SCAR hearing held in Jan–May of calendar year N is leftover work for
9
+ * tax year N-1; hearings for tax year N start in the summer. Named because it is a
10
+ * heuristic, not a rule — the picker always lets the user change the year.
11
+ */
12
+ export declare const TAX_YEAR_ROLLOVER_MONTH = 5;
13
+ export declare function defaultTaxYear(courtDate: Date | string | null | undefined, now?: Date): number;
14
+ /**
15
+ * Years offered in the picker, newest first, centred on `base` so a wrong guess is one click
16
+ * to fix. `current` is folded in so a year the user has already navigated to always remains a
17
+ * valid option for the Select, even once it falls outside the window.
18
+ */
19
+ export declare function taxYearOptions(base: number, current?: number): number[];
20
+ /**
21
+ * Matches a case against the search box — owner name, address, index number or parcel,
22
+ * case-insensitive substring. An empty term matches everything.
23
+ *
24
+ * Fields are tested individually rather than joined into one string: joining lets a
25
+ * multi-word term straddle two fields and match a case it has nothing to do with.
26
+ */
27
+ export declare function openCaseMatchesSearchTerm(oc: OpenCase, term: string): boolean;
28
+ export declare function openCaseRowId(oc: OpenCase): string;
29
+ /**
30
+ * Adds grid ids and drops duplicate rows. The endpoint has no DISTINCT and joins Retainers
31
+ * on (ParcelID, Year), so a parcel with more than one retainer row comes back more than
32
+ * once — and a duplicate id makes DataGrid throw rather than merely look odd.
33
+ */
34
+ export declare function toOpenCaseRows(cases: OpenCase[]): OpenCaseRow[];
35
+ /**
36
+ * Every string by which a case can be recognised as "already here".
37
+ *
38
+ * Deliberately keyed on the *index numbers and parcel alone, never the year*: rows the user
39
+ * typed by hand inherit DEFAULT_CASE.Year (the current calendar year), which will not equal
40
+ * the tax year they are browsing, so a year-qualified parcel key would never match and the
41
+ * same property could be added twice.
42
+ */
43
+ export declare function openCaseDedupeKeys(oc: OpenCase): string[];
44
+ /**
45
+ * The same keys for a row already in the form. Emits *both* index forms plus the one
46
+ * `caseIndexForMuni` resolves, so a village case typed in as a county index still matches.
47
+ */
48
+ export declare function caseDedupeKeys(c: Case, muniCode: string): string[];
49
+ /** True for the empty row CREATE mode seeds, and for any row the user cleared out. */
50
+ export declare function isBlankCaseRow(c: Case): boolean;
51
+ /**
52
+ * The index this case will be linked under.
53
+ *
54
+ * There is only one index field to read: the backend queries the town or village columns
55
+ * based on the muni code's length and aliases either one to `SCARIndexNumber`. Which *form*
56
+ * field it lands in on the way back is `openCaseToCase`'s job.
57
+ */
58
+ export declare function openCaseIndexFor(oc: OpenCase): string;
59
+ /** A row can be checked only if it has a usable index and is not already in the form. */
60
+ export declare function isRowPickable(oc: OpenCase, existingKeys: Set<string>): boolean;
61
+ /**
62
+ * Normalises the MUI X v8 selection model (`{ type: 'include' | 'exclude', ids: Set }`) into
63
+ * a plain set of visible, selected ids. `ids` is defensively checked for being a Set — the
64
+ * same guard CaseViewer makes, since the shape changed between major versions.
65
+ */
66
+ export declare function resolveSelectedIds(model: {
67
+ type?: 'include' | 'exclude';
68
+ ids?: unknown;
69
+ } | null | undefined, visibleIds: string[]): Set<string>;
70
+ /**
71
+ * A draft Case carrying just enough to be saved and displayed. Municipality and the
72
+ * determination/valuation fields are left at their defaults on purpose: these rows exist
73
+ * only to contribute index numbers to the `court_cases` payload, and the calendar refetches
74
+ * the real cases from the API once the date is saved.
75
+ */
76
+ export declare function openCaseToCase(oc: OpenCase, isVillage: boolean): Case;
77
+ /**
78
+ * Adds and removes picked cases in one pass, keeping `toggles` aligned with `cases`.
79
+ *
80
+ * This is what makes the two Case-ID modes interchangeable: the checkbox list and the text
81
+ * fields are two views of the same `editedCases`, so a row checked in one is a field in the
82
+ * other, and "what have I selected" never needs its own state to drift out of sync.
83
+ */
84
+ export declare function applyOpenCaseSelection(existingCases: Case[], existingToggles: boolean[], toAdd: OpenCase[], toRemove: OpenCase[], muniCode: string): OpenCaseMergeResult;
85
+ export interface OpenCaseMergeResult {
86
+ cases: Case[];
87
+ /** Parcel-ID-mode flags, positionally aligned with `cases`. Always the same length. */
88
+ toggles: boolean[];
89
+ added: number;
90
+ /** Picks already present in the form. */
91
+ skipped: number;
92
+ /**
93
+ * Picks the API returned with no index number. The backend filters these out, so this is
94
+ * a backstop rather than a path the UI walks — the grid deliberately does not mark such
95
+ * rows unselectable, because a row already on the date is also "unpickable" and must stay
96
+ * clickable in order to be unchecked.
97
+ */
98
+ unusable: number;
99
+ }
100
+ /**
101
+ * The single place picked cases are deduplicated and folded into the form's state.
102
+ *
103
+ * Returns `toggles` alongside `cases` because the two arrays are positionally coupled in
104
+ * CreateEditCase and its length-sync effect only fires when the *length* changes — replacing
105
+ * one blank Parcel-ID-mode row with one picked case would otherwise leave a stale `true`
106
+ * behind and render the new row's parcel instead of its index.
107
+ */
108
+ export declare function mergeOpenCases(existingCases: Case[], existingToggles: boolean[], picked: OpenCase[], muniCode: string): OpenCaseMergeResult;
@@ -17,7 +17,8 @@ import { ModalMode } from '../_components/Modal/types';
17
17
  *
18
18
  * @returns
19
19
  * - `handleSave` – Validates and persists the edited court date (creates or updates).
20
- * - `handleDelete` – Confirms, then deletes the selected court date.
20
+ * - `handleDelete` – Confirms, then deletes the selected court date. Returns
21
+ * false and leaves local state untouched if the server refused.
21
22
  * - `handleViewUpdateChair` – Optimistically updates a first/second chair in view mode.
22
23
  * - `handleSelectedCasesAdjournmentChange` – Adjourns a set of cases (sets AdjournedDate + `OA` determination), then reconciles with the server.
23
24
  * - `handleSelectedCasesClearDetermination` – Clears the determination for a set of cases (backend only clears `D`), then reconciles with the server.
@@ -33,7 +34,7 @@ export declare function useModalActions({ modalMode, selectedCourtDate, selected
33
34
  pendingNoticeFiles?: File[];
34
35
  }): {
35
36
  handleSave: () => Promise<boolean>;
36
- handleDelete: () => Promise<void>;
37
+ handleDelete: () => Promise<boolean>;
37
38
  handleViewUpdateChair: (position: "first" | "second", userId: number | null) => void;
38
39
  handleSelectedCasesAdjournmentChange: (casesToUpdate: Case[], date: Date | null) => Promise<boolean>;
39
40
  handleSelectedCasesClearDetermination: (casesToUpdate: Case[]) => Promise<boolean>;