From 576a4b916bfcb7bba2df1d2b8fc0f5908ecdf094 Mon Sep 17 00:00:00 2001 From: Chaim Date: Tue, 16 Jun 2026 07:41:38 +0000 Subject: [PATCH] =?UTF-8?q?feat(cases):=20=D7=A2=D7=9E=D7=95=D7=93=D7=AA?= =?UTF-8?q?=20"=D7=9E=D7=95=D7=A2=D7=93=20=D7=93=D7=99=D7=95=D7=9F"=20+=20?= =?UTF-8?q?=D7=9E=D7=99=D7=95=D7=9F=20=D7=91=D7=A8=D7=99=D7=A8=D7=AA-?= =?UTF-8?q?=D7=9E=D7=97=D7=93=D7=9C=20=D7=9C=D7=A4=D7=99=20=D7=A7=D7=A8?= =?UTF-8?q?=D7=91=D7=AA-=D7=94=D7=93=D7=99=D7=95=D7=9F=20(#272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Chaim Co-committed-by: Chaim --- web-ui/src/components/cases/cases-table.tsx | 73 ++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/web-ui/src/components/cases/cases-table.tsx b/web-ui/src/components/cases/cases-table.tsx index b5f581a..6e15f2d 100644 --- a/web-ui/src/components/cases/cases-table.tsx +++ b/web-ui/src/components/cases/cases-table.tsx @@ -9,6 +9,7 @@ import { getSortedRowModel, useReactTable, type ColumnDef, + type Row, type SortingState, } from "@tanstack/react-table"; import { @@ -37,6 +38,60 @@ function formatDate(iso?: string) { } } +/** Midnight today, in ms — the pivot between an upcoming and a past hearing. */ +function startOfToday() { + const d = new Date(); + d.setHours(0, 0, 0, 0); + return d.getTime(); +} + +/** Day-precision ms for a hearing date, or null when absent/unparseable. */ +function hearingMs(iso?: string | null): number | null { + if (!iso) return null; + const t = new Date(iso).setHours(0, 0, 0, 0); + return Number.isNaN(t) ? null : t; +} + +type HearingClass = "soon" | "upcoming" | "past" | "none"; + +/** Classify a hearing date for the cell's colour cue (within ~7 days = "soon"). */ +function classifyHearing(iso?: string | null): HearingClass { + const t = hearingMs(iso); + if (t === null) return "none"; + const today = startOfToday(); + if (t < today) return "past"; + return t - today <= 7 * 86_400_000 ? "soon" : "upcoming"; +} + +const HEARING_CLASS_STYLE: Record = { + soon: "text-gold-deep font-semibold", + upcoming: "text-navy font-semibold", + past: "text-ink-light", + none: "text-ink-light", +}; + +/** + * Default ordering for the case list: the nearest upcoming hearing on top, + * past hearings sinking below it (most-recently-passed first), and cases with + * no hearing date last. Authored as a non-inverting comparator, so the column's + * initial (ascending) sort yields exactly this sequence. + */ +function hearingDateSort(a: Row, b: Row): number { + const today = startOfToday(); + // group: 0 = upcoming (incl. today), 1 = past, 2 = no date + const rank = (iso?: string | null): { g: number; t: number } => { + const t = hearingMs(iso); + if (t === null) return { g: 2, t: 0 }; + return t >= today ? { g: 0, t } : { g: 1, t }; + }; + const ra = rank(a.original.hearing_date); + const rb = rank(b.original.hearing_date); + if (ra.g !== rb.g) return ra.g - rb.g; + if (ra.g === 0) return ra.t - rb.t; // upcoming: ascending (nearest first) + if (ra.g === 1) return rb.t - ra.t; // past: descending (most recent first) + return 0; // both undated — keep stable order +} + const columns: ColumnDef[] = [ { accessorKey: "case_number", @@ -82,6 +137,19 @@ const columns: ColumnDef[] = [ ), }, + { + accessorKey: "hearing_date", + header: "מועד דיון", + sortingFn: hearingDateSort, + cell: ({ row }) => { + const klass = classifyHearing(row.original.hearing_date); + return ( + + {formatDate(row.original.hearing_date ?? undefined)} + + ); + }, + }, { accessorKey: "updated_at", header: "עודכן", @@ -104,8 +172,11 @@ export function CasesTable({ emptyText?: string; searchPlaceholder?: string; }) { + // Default: nearest upcoming hearing on top; past hearings sink below + // (most-recently-passed first); undated cases last. desc:false keeps the + // hearingDateSort comparator's intended (non-inverted) sequence. const [sorting, setSorting] = useState([ - { id: "updated_at", desc: true }, + { id: "hearing_date", desc: false }, ]); const [globalFilter, setGlobalFilter] = useState(""); /* "all" = all cases; "blam" = only בל"מ; "regular" = exclude בל"מ */