feat(cases): עמודת "מועד דיון" + מיון ברירת-מחדל לפי קרבת-הדיון #272
@@ -9,6 +9,7 @@ import {
|
|||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
type ColumnDef,
|
type ColumnDef,
|
||||||
|
type Row,
|
||||||
type SortingState,
|
type SortingState,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import {
|
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<HearingClass, string> = {
|
||||||
|
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<Case>, b: Row<Case>): 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<Case>[] = [
|
const columns: ColumnDef<Case>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "case_number",
|
accessorKey: "case_number",
|
||||||
@@ -82,6 +137,19 @@ const columns: ColumnDef<Case>[] = [
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "hearing_date",
|
||||||
|
header: "מועד דיון",
|
||||||
|
sortingFn: hearingDateSort,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const klass = classifyHearing(row.original.hearing_date);
|
||||||
|
return (
|
||||||
|
<span className={`tabular-nums text-sm ${HEARING_CLASS_STYLE[klass]}`}>
|
||||||
|
{formatDate(row.original.hearing_date ?? undefined)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "updated_at",
|
accessorKey: "updated_at",
|
||||||
header: "עודכן",
|
header: "עודכן",
|
||||||
@@ -104,8 +172,11 @@ export function CasesTable({
|
|||||||
emptyText?: string;
|
emptyText?: string;
|
||||||
searchPlaceholder?: 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<SortingState>([
|
const [sorting, setSorting] = useState<SortingState>([
|
||||||
{ id: "updated_at", desc: true },
|
{ id: "hearing_date", desc: false },
|
||||||
]);
|
]);
|
||||||
const [globalFilter, setGlobalFilter] = useState("");
|
const [globalFilter, setGlobalFilter] = useState("");
|
||||||
/* "all" = all cases; "blam" = only בל"מ; "regular" = exclude בל"מ */
|
/* "all" = all cases; "blam" = only בל"מ; "regular" = exclude בל"מ */
|
||||||
|
|||||||
Reference in New Issue
Block a user