feat(tables): share column-sort state across the value lists

Lift the byte-identical sortBy/sortDir/toggleSort triple the measurement
and mood lists each carried inline into a shared useTableSort hook. Both
lists now compose their page-reset and selection-clear side effects around
the hook's toggle rather than re-implementing the direction math, and the
date columns keep their descending-first default via descColumns.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 13:11:03 +02:00
1 parent bc617adc2c
commit 9c748382b9
3 files changed
+88 -16

No files matched your search

@@ -52,6 +52,7 @@ import {
MoreHorizontal,
} from "lucide-react";
import { Fragment, useCallback, useId, useState } from "react";
import { useTableSort } from "@/hooks/use-table-sort";
import { createPortal } from "react-dom";
import { toast } from "sonner";
import { formatDateOrRelative, formatDateTime } from "@/lib/format";
@@ -105,6 +106,10 @@ import {
*/
const CUMULATIVE_TYPES = new Set<string>(CUMULATIVE_DAY_SUM_TYPES);
// Columns that open descending when first selected. The date column reads
// newest-first by default; every other column opens ascending.
const MEASUREMENT_DESC_COLUMNS: ReadonlySet<string> = new Set(["measuredAt"]);
interface Measurement {
id: string;
type: string;
@@ -313,8 +318,16 @@ export function MeasurementList({
const [valueMinInput, setValueMinInputRaw] = useState<string>("");
const [valueMaxInput, setValueMaxInputRaw] = useState<string>("");
const [page, setPage] = useState(1);
const [sortBy, setSortBy] = useState<string>("measuredAt");
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
// Shared column-sort state (measuredAt opens descending).
const {
sortBy,
sortDir,
toggleSort: applySort,
} = useTableSort({
defaultColumn: "measuredAt",
defaultDir: "desc",
descColumns: MEASUREMENT_DESC_COLUMNS,
});
// v1.15.13 — page-scoped multi-select. Holds the ids selected on the
// CURRENT page; cleared on any page / filter / sort change (per the
@@ -399,13 +412,11 @@ export function MeasurementList({
clearSelection();
};
// Compose the page-reset + selection-clear side effects around the
// shared sort toggle (the rows the selection referred to are about to
// re-order and the list snaps back to page 1).
function toggleSort(column: string) {
if (sortBy === column) {
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
} else {
setSortBy(column);
setSortDir(column === "measuredAt" ? "desc" : "asc");
}
applySort(column);
setPage(1);
clearSelection();
}
+18 -8
View File
@@ -51,6 +51,11 @@ import {
} from "lucide-react";
import { useCallback, useEffect, useId, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useTableSort } from "@/hooks/use-table-sort";
// Columns that open descending when first selected. The logged-at column
// reads newest-first by default; every other column opens ascending.
const MOOD_DESC_COLUMNS: ReadonlySet<string> = new Set(["moodLoggedAt"]);
import { toast } from "sonner";
import { formatDateTime } from "@/lib/format";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
@@ -128,8 +133,16 @@ export function MoodList({ onAddFirst }: MoodListProps = {}) {
const [fromDay, setFromDayRaw] = useState<string>("");
const [toDay, setToDayRaw] = useState<string>("");
const [page, setPage] = useState(1);
const [sortBy, setSortBy] = useState<string>("moodLoggedAt");
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
// Shared column-sort state (moodLoggedAt opens descending).
const {
sortBy,
sortDir,
toggleSort: applySort,
} = useTableSort({
defaultColumn: "moodLoggedAt",
defaultDir: "desc",
descColumns: MOOD_DESC_COLUMNS,
});
// v1.15.13 — page-scoped multi-select selection (current page ids only).
const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());
@@ -201,13 +214,10 @@ export function MoodList({ onAddFirst }: MoodListProps = {}) {
clearSelection();
};
// Compose the page-reset + selection-clear side effects around the
// shared sort toggle.
function toggleSort(column: string) {
if (sortBy === column) {
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
} else {
setSortBy(column);
setSortDir(column === "moodLoggedAt" ? "desc" : "asc");
}
applySort(column);
setPage(1);
clearSelection();
}
+51
View File
@@ -0,0 +1,51 @@
"use client";
import { useCallback, useState } from "react";
export type SortDirection = "asc" | "desc";
/**
* Shared column-sort state for the data-management tables (measurements,
* mood, lab history). Lifted from the byte-identical `sortBy` / `sortDir` /
* `toggleSort` triple the measurement + mood lists each carried inline.
*
* `toggleSort(column)` flips the direction when the active column is tapped
* again, otherwise switches to the new column with its default direction:
* a column listed in `descColumns` (typically the date column) opens
* descending, every other column ascending. Callers that need extra side
* effects on a sort change (reset the page, clear a selection) compose them
* around the returned `toggleSort` rather than re-implementing the math.
*/
export function useTableSort({
defaultColumn,
defaultDir = "desc",
descColumns,
}: {
/** Column the table sorts by on first render. */
defaultColumn: string;
/** Direction for the initial column. */
defaultDir?: SortDirection;
/** Columns that open descending when first selected (e.g. the date column). */
descColumns?: ReadonlySet<string>;
}): {
sortBy: string;
sortDir: SortDirection;
toggleSort: (column: string) => void;
} {
const [sortBy, setSortBy] = useState<string>(defaultColumn);
const [sortDir, setSortDir] = useState<SortDirection>(defaultDir);
const toggleSort = useCallback(
(column: string) => {
if (sortBy === column) {
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
} else {
setSortBy(column);
setSortDir(descColumns?.has(column) ? "desc" : "asc");
}
},
[sortBy, descColumns],
);
return { sortBy, sortDir, toggleSort };
}