mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean baseline. Formatting only — no behaviour change. Generator-owned and vendored artifacts stay untouched via .prettierignore.
This commit is contained in:
1 parent
b3fea2ef59
commit
1f4ba5939d
1000 files changed
+12130
-8385
No files matched your search
@@ -86,9 +86,7 @@ describe("BD-Zielbereich tile (v1.4.28 FB-C1/C2 rewrite)", () => {
|
||||
);
|
||||
|
||||
// Same primitive slots on both tiles.
|
||||
for (const slot of [
|
||||
"trend-card-value-row",
|
||||
]) {
|
||||
for (const slot of ["trend-card-value-row"]) {
|
||||
expect(bdHtml).toContain(`data-slot="${slot}"`);
|
||||
expect(weightHtml).toContain(`data-slot="${slot}"`);
|
||||
}
|
||||
|
||||
@@ -100,9 +100,8 @@ describe("chart isError rendering — error state, not empty state", () => {
|
||||
mockQueryError();
|
||||
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { MedicationComplianceChart } = await import(
|
||||
"../medication-compliance-chart"
|
||||
);
|
||||
const { MedicationComplianceChart } =
|
||||
await import("../medication-compliance-chart");
|
||||
|
||||
const html = renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="de" initialMessages={deMessages}>
|
||||
@@ -125,9 +124,7 @@ describe("<ChartErrorState> — announcement, sizing, accessible name", () => {
|
||||
});
|
||||
|
||||
async function renderState(
|
||||
props: Partial<
|
||||
import("../chart-error-state").ChartErrorStateProps
|
||||
> = {},
|
||||
props: Partial<import("../chart-error-state").ChartErrorStateProps> = {},
|
||||
) {
|
||||
const { ChartErrorState } = await import("../chart-error-state");
|
||||
return renderToStaticMarkup(
|
||||
|
||||
+4
-7
@@ -29,7 +29,6 @@ describe("<MedicationComplianceChart> — empty-state gate by raw count", () =>
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
|
||||
it("renders need-more-days copy when chartData.length < 3 but rawCount >= 3", async () => {
|
||||
const data = [
|
||||
{ date: "2026-05-19", scheduled: 10, taken: 8 },
|
||||
@@ -48,9 +47,8 @@ describe("<MedicationComplianceChart> — empty-state gate by raw count", () =>
|
||||
}));
|
||||
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { MedicationComplianceChart } = await import(
|
||||
"../medication-compliance-chart"
|
||||
);
|
||||
const { MedicationComplianceChart } =
|
||||
await import("../medication-compliance-chart");
|
||||
|
||||
const html = renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="de" initialMessages={deMessages}>
|
||||
@@ -79,9 +77,8 @@ describe("<MedicationComplianceChart> — empty-state gate by raw count", () =>
|
||||
}));
|
||||
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { MedicationComplianceChart } = await import(
|
||||
"../medication-compliance-chart"
|
||||
);
|
||||
const { MedicationComplianceChart } =
|
||||
await import("../medication-compliance-chart");
|
||||
|
||||
const html = renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="de" initialMessages={deMessages}>
|
||||
|
||||
@@ -28,7 +28,6 @@ describe("<MoodChart> — empty-state gate by raw count", () => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
|
||||
it("renders need-more-days copy when chartData.length < 3 but rawCount >= 3", async () => {
|
||||
const data = {
|
||||
entries: [
|
||||
|
||||
@@ -209,8 +209,10 @@ describe("dashboard.*Short keys exist across all six locales", () => {
|
||||
.default;
|
||||
const dashboard = messages.dashboard ?? {};
|
||||
for (const key of REQUIRED_KEYS) {
|
||||
expect(dashboard[key], `dashboard.${key} missing in ${locale}.json`)
|
||||
.toBeTypeOf("string");
|
||||
expect(
|
||||
dashboard[key],
|
||||
`dashboard.${key} missing in ${locale}.json`,
|
||||
).toBeTypeOf("string");
|
||||
expect(
|
||||
(dashboard[key] as string).length,
|
||||
`dashboard.${key} empty in ${locale}.json`,
|
||||
|
||||
@@ -36,12 +36,12 @@ const baseProps = {
|
||||
describe("<TrendCard staleDays>", () => {
|
||||
it("stays silent when the metric is fresh (<= 7 days)", () => {
|
||||
const html = render(<TrendCard {...baseProps} staleDays={3} />);
|
||||
expect(html).not.toContain("data-slot=\"tile-stale-hint\"");
|
||||
expect(html).not.toContain('data-slot="tile-stale-hint"');
|
||||
});
|
||||
|
||||
it("renders day-bucket copy when staleDays is in (7, 30]", () => {
|
||||
const html = render(<TrendCard {...baseProps} staleDays={12} />, "en");
|
||||
expect(html).toContain("data-slot=\"tile-stale-hint\"");
|
||||
expect(html).toContain('data-slot="tile-stale-hint"');
|
||||
expect(html).toContain("Last reading 12 d ago");
|
||||
});
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ export function ChartOverlayControlsBody({
|
||||
prefs.comparisonBaseline === value ? "default" : "outline"
|
||||
}
|
||||
size="sm"
|
||||
className={`min-h-11 sm:min-h-9 px-2 text-[11px] ${isGreyed ? "opacity-50" : ""}`.trim()}
|
||||
className={`min-h-11 px-2 text-[11px] sm:min-h-9 ${isGreyed ? "opacity-50" : ""}`.trim()}
|
||||
onClick={() => setComparisonBaseline(value)}
|
||||
aria-pressed={prefs.comparisonBaseline === value}
|
||||
aria-disabled={isGreyed || undefined}
|
||||
|
||||
@@ -69,10 +69,7 @@ export function ChartSkeleton({
|
||||
const [showSlowHint, setShowSlowHint] = useState(false);
|
||||
useEffect(() => {
|
||||
if (mini) return undefined;
|
||||
const handle = setTimeout(
|
||||
() => setShowSlowHint(true),
|
||||
SLOW_HINT_DELAY_MS,
|
||||
);
|
||||
const handle = setTimeout(() => setShowSlowHint(true), SLOW_HINT_DELAY_MS);
|
||||
return () => clearTimeout(handle);
|
||||
}, [mini]);
|
||||
|
||||
|
||||
@@ -259,7 +259,11 @@ export function ComplianceHeatmap({
|
||||
only kicks in for the non-stretch (static 18 px cell) case
|
||||
when the parent column is narrower than `weeks * 21 px`. */}
|
||||
<div
|
||||
className={stretch ? "w-full sm:w-full overflow-x-auto sm:overflow-visible" : "overflow-x-auto"}
|
||||
className={
|
||||
stretch
|
||||
? "w-full overflow-x-auto sm:w-full sm:overflow-visible"
|
||||
: "overflow-x-auto"
|
||||
}
|
||||
>
|
||||
<svg
|
||||
width={svgWidth}
|
||||
@@ -269,7 +273,9 @@ export function ComplianceHeatmap({
|
||||
// off `containerWidth`, so a full window still fills the row) rather
|
||||
// than CSS-stretching ~5 columns of capped cells into wide rectangles.
|
||||
className={stretch ? "block max-w-full" : "block"}
|
||||
onMouseLeave={() => setTooltip((prev) => (prev?.pinned ? prev : null))}
|
||||
onMouseLeave={() =>
|
||||
setTooltip((prev) => (prev?.pinned ? prev : null))
|
||||
}
|
||||
>
|
||||
{/* Month labels */}
|
||||
{monthMarkers.map((m, i) => (
|
||||
|
||||
@@ -740,7 +740,9 @@ export function HealthChart({
|
||||
// request so a longer comparison window still resolves.
|
||||
sleepParams.set(
|
||||
"days",
|
||||
String(Math.max(1, Math.min(3650, Math.ceil(fetchWindow.windowDays)))),
|
||||
String(
|
||||
Math.max(1, Math.min(3650, Math.ceil(fetchWindow.windowDays))),
|
||||
),
|
||||
);
|
||||
// v1.16.8 — a failed fetch rejects the whole query instead of
|
||||
// silently resolving an empty series: the swallowed-catch
|
||||
@@ -872,9 +874,13 @@ export function HealthChart({
|
||||
const scaledMin = measurement.minValue * valueScale;
|
||||
const scaledMax = measurement.maxValue * valueScale;
|
||||
current.min =
|
||||
current.min === null ? scaledMin : Math.min(current.min, scaledMin);
|
||||
current.min === null
|
||||
? scaledMin
|
||||
: Math.min(current.min, scaledMin);
|
||||
current.max =
|
||||
current.max === null ? scaledMax : Math.max(current.max, scaledMax);
|
||||
current.max === null
|
||||
? scaledMax
|
||||
: Math.max(current.max, scaledMax);
|
||||
}
|
||||
bucket.values[type] = current;
|
||||
dailyAggregates.set(dayKey, bucket);
|
||||
@@ -1063,20 +1069,20 @@ export function HealthChart({
|
||||
// recomputes whenever the range tab changes the visible slice, so the
|
||||
// `<MetricStatStrip>` a sub-page lifts the callback into always reflects the
|
||||
// same window the chart paints. Skipped in mini mode (no stat strip).
|
||||
const visibleStatsByType = useMemo<Record<string, MetricWindowStats> | null>(
|
||||
() => {
|
||||
if (mini || !chartData?.length) return null;
|
||||
const out: Record<string, MetricWindowStats> = {};
|
||||
for (const type of types) {
|
||||
const stats = computeWindowStats(
|
||||
chartData.map((point) => point[type] as number | undefined),
|
||||
);
|
||||
if (stats.count > 0) out[type] = stats;
|
||||
}
|
||||
return Object.keys(out).length > 0 ? out : null;
|
||||
},
|
||||
[mini, chartData, types],
|
||||
);
|
||||
const visibleStatsByType = useMemo<Record<
|
||||
string,
|
||||
MetricWindowStats
|
||||
> | null>(() => {
|
||||
if (mini || !chartData?.length) return null;
|
||||
const out: Record<string, MetricWindowStats> = {};
|
||||
for (const type of types) {
|
||||
const stats = computeWindowStats(
|
||||
chartData.map((point) => point[type] as number | undefined),
|
||||
);
|
||||
if (stats.count > 0) out[type] = stats;
|
||||
}
|
||||
return Object.keys(out).length > 0 ? out : null;
|
||||
}, [mini, chartData, types]);
|
||||
|
||||
// Report the visible-range stats up to the sub-page so the shared
|
||||
// `<MetricStatStrip>` can read them. Effect (not render-time call) so the
|
||||
@@ -1674,10 +1680,7 @@ export function HealthChart({
|
||||
// positions through the `ticks` prop so the legacy
|
||||
// day-aware density policy stays effective on the
|
||||
// pulse chart.
|
||||
ticks={computeTickPositions(
|
||||
chartData ?? [],
|
||||
viewportWidth,
|
||||
)}
|
||||
ticks={computeTickPositions(chartData ?? [], viewportWidth)}
|
||||
padding={{ left: 10, right: 10 }}
|
||||
tickMargin={10}
|
||||
/>
|
||||
@@ -1690,7 +1693,9 @@ export function HealthChart({
|
||||
domain={yDomain}
|
||||
tickFormatter={(value) =>
|
||||
typeof value === "number"
|
||||
? formatAxisValue(useDecimalAxis ? value : Math.round(value))
|
||||
? formatAxisValue(
|
||||
useDecimalAxis ? value : Math.round(value),
|
||||
)
|
||||
: String(value)
|
||||
}
|
||||
unit={
|
||||
|
||||
@@ -246,7 +246,9 @@ export function MedicationComplianceChart({
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: queryKeys.dashboardMedicationCompliance(days),
|
||||
queryFn: async (): Promise<DailyCompliancePoint[]> => {
|
||||
return apiGet<DailyCompliancePoint[]>(`/api/medications/intake?scope=compliance&days=${days}`);
|
||||
return apiGet<DailyCompliancePoint[]>(
|
||||
`/api/medications/intake?scope=compliance&days=${days}`,
|
||||
);
|
||||
},
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
@@ -452,7 +454,7 @@ export function MedicationComplianceChart({
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="h-[var(--chart-height,240px)] md:h-[var(--chart-height-md,280px)] touch-pan-y">
|
||||
<div className="h-[var(--chart-height,240px)] touch-pan-y md:h-[var(--chart-height-md,280px)]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart
|
||||
data={chartData}
|
||||
|
||||
@@ -207,7 +207,7 @@ export function MoodHeatmap({
|
||||
<div
|
||||
className={
|
||||
stretch
|
||||
? "w-full sm:w-full overflow-x-auto sm:overflow-visible"
|
||||
? "w-full overflow-x-auto sm:w-full sm:overflow-visible"
|
||||
: "overflow-x-auto"
|
||||
}
|
||||
>
|
||||
@@ -218,7 +218,9 @@ export function MoodHeatmap({
|
||||
// natural, square, left-aligned grid rather than CSS-stretching a few
|
||||
// capped columns into wide rectangles. Mirrors `compliance-heatmap`.
|
||||
className={stretch ? "block max-w-full" : "block"}
|
||||
onMouseLeave={() => setTooltip((prev) => (prev?.pinned ? prev : null))}
|
||||
onMouseLeave={() =>
|
||||
setTooltip((prev) => (prev?.pinned ? prev : null))
|
||||
}
|
||||
>
|
||||
{monthMarkers.map((m, i) => (
|
||||
<text
|
||||
@@ -254,7 +256,9 @@ export function MoodHeatmap({
|
||||
if (!cell.cell) {
|
||||
return `${formatDateDE(cell.dateKey)}: ${t("insights.mood.heatmapNoEntry")}`;
|
||||
}
|
||||
const labelKey = moodLabelKeyForScore(Math.round(cell.cell.score));
|
||||
const labelKey = moodLabelKeyForScore(
|
||||
Math.round(cell.cell.score),
|
||||
);
|
||||
const moodLabel = labelKey ? t(labelKey) : "";
|
||||
return `${formatDateDE(cell.dateKey)}: ${cell.cell.score.toFixed(1)}${moodLabel ? ` · ${moodLabel}` : ""}`;
|
||||
};
|
||||
@@ -310,9 +314,18 @@ export function MoodHeatmap({
|
||||
style={{ marginLeft: stretch ? 0 : labelWidth }}
|
||||
>
|
||||
{[
|
||||
{ color: "var(--dracula-green)", label: t("insights.mood.legendGreat") },
|
||||
{ color: "var(--dracula-yellow)", label: t("insights.mood.legendGood") },
|
||||
{ color: "var(--dracula-orange)", label: t("insights.mood.legendOkay") },
|
||||
{
|
||||
color: "var(--dracula-green)",
|
||||
label: t("insights.mood.legendGreat"),
|
||||
},
|
||||
{
|
||||
color: "var(--dracula-yellow)",
|
||||
label: t("insights.mood.legendGood"),
|
||||
},
|
||||
{
|
||||
color: "var(--dracula-orange)",
|
||||
label: t("insights.mood.legendOkay"),
|
||||
},
|
||||
{ color: "var(--dracula-red)", label: t("insights.mood.legendLow") },
|
||||
].map(({ color, label }) => (
|
||||
<span key={label} className="flex items-center gap-1">
|
||||
|
||||
@@ -97,7 +97,7 @@ export function ScatterCorrelationChart<T extends Record<string, number>>({
|
||||
// shape once layout settles; this floor only prevents the brief
|
||||
// zero-height window that fires Recharts' width(-1)/height(-1)
|
||||
// console warning when the chart swaps in after the skeleton.
|
||||
className="touch-pan-y aspect-square min-h-[180px] sm:aspect-[3/2] sm:h-auto"
|
||||
className="aspect-square min-h-[180px] touch-pan-y sm:aspect-[3/2] sm:h-auto"
|
||||
style={{ height: undefined }}
|
||||
data-explicit-height={height}
|
||||
>
|
||||
|
||||
@@ -134,11 +134,7 @@ export function TrendCard({
|
||||
// appeared on the same tile because slope30 and trend7Delta could
|
||||
// legitimately disagree (30-day downward arc, 7-day uptick).
|
||||
const primarySignal: number | null =
|
||||
trend7Delta != null
|
||||
? trend7Delta
|
||||
: slope30
|
||||
? slope30.slope * 7
|
||||
: null;
|
||||
trend7Delta != null ? trend7Delta : slope30 ? slope30.slope * 7 : null;
|
||||
|
||||
const TrendIcon = ((): typeof ArrowUp => {
|
||||
if (primarySignal == null) return Minus;
|
||||
@@ -233,51 +229,51 @@ export function TrendCard({
|
||||
// deterministic height. No unit; the arrow slot below still
|
||||
// renders its muted placeholder so the row width stays stable.
|
||||
<span
|
||||
className="text-muted-foreground min-w-0 truncate text-base font-medium leading-none"
|
||||
className="text-muted-foreground min-w-0 truncate text-base leading-none font-medium"
|
||||
data-slot="trend-card-empty-hint"
|
||||
>
|
||||
{emptyHint}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 whitespace-nowrap font-semibold tracking-tight tabular-nums",
|
||||
// v1.8.7 — the value NEVER truncates, clips, or wraps
|
||||
// mid-number. The maintainer is emphatic: every digit of
|
||||
// the reading must render in full ("130 mmHg" must read as
|
||||
// "130", never "13" then "3 mmHg"). The headline value span
|
||||
// is `shrink-0 whitespace-nowrap` so it keeps its full
|
||||
// intrinsic width and wins the horizontal-space contest
|
||||
// against the unit. When the strip is dense (many tiles →
|
||||
// narrow column) the UNIT yields — it shrinks (`min-w-0
|
||||
// truncate`) and may ellipsis — rather than losing any
|
||||
// digit of the number. The earlier `min-w-0 truncate` on
|
||||
// this span was the bug: it let the value shrink below its
|
||||
// content width and clip the number.
|
||||
//
|
||||
// v1.4.49.4 — paired values (BP systolic/diastolic) render
|
||||
// as `131/85` and outgrow the `text-3xl` footprint on the
|
||||
// narrowest grid columns. Paired tiles drop one font step
|
||||
// (text-3xl → text-2xl) so the full pair fits; single-value
|
||||
// tiles (weight, pulse, glucose, …) keep text-3xl.
|
||||
secondary ? "text-2xl" : "text-3xl",
|
||||
// `leading-none` must come AFTER the font-size class in the
|
||||
// cn() arg list. Tailwind v4 `text-3xl` / `text-2xl` ship
|
||||
// their own default `line-height`, so `twMerge` would
|
||||
// otherwise drop the explicit `leading-none` per the
|
||||
// last-wins rule and break the W20a across-tile baseline
|
||||
// alignment (digits line up at the same y-coordinate
|
||||
// across every tile in the strip).
|
||||
"leading-none",
|
||||
)}
|
||||
data-slot="trend-card-value"
|
||||
>
|
||||
{latest !== null ? renderPair(latest, secondary?.latest) : "—"}
|
||||
</span>
|
||||
<span className="text-muted-foreground min-w-0 truncate text-sm tabular-nums">
|
||||
{unit}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0 font-semibold tracking-tight whitespace-nowrap tabular-nums",
|
||||
// v1.8.7 — the value NEVER truncates, clips, or wraps
|
||||
// mid-number. The maintainer is emphatic: every digit of
|
||||
// the reading must render in full ("130 mmHg" must read as
|
||||
// "130", never "13" then "3 mmHg"). The headline value span
|
||||
// is `shrink-0 whitespace-nowrap` so it keeps its full
|
||||
// intrinsic width and wins the horizontal-space contest
|
||||
// against the unit. When the strip is dense (many tiles →
|
||||
// narrow column) the UNIT yields — it shrinks (`min-w-0
|
||||
// truncate`) and may ellipsis — rather than losing any
|
||||
// digit of the number. The earlier `min-w-0 truncate` on
|
||||
// this span was the bug: it let the value shrink below its
|
||||
// content width and clip the number.
|
||||
//
|
||||
// v1.4.49.4 — paired values (BP systolic/diastolic) render
|
||||
// as `131/85` and outgrow the `text-3xl` footprint on the
|
||||
// narrowest grid columns. Paired tiles drop one font step
|
||||
// (text-3xl → text-2xl) so the full pair fits; single-value
|
||||
// tiles (weight, pulse, glucose, …) keep text-3xl.
|
||||
secondary ? "text-2xl" : "text-3xl",
|
||||
// `leading-none` must come AFTER the font-size class in the
|
||||
// cn() arg list. Tailwind v4 `text-3xl` / `text-2xl` ship
|
||||
// their own default `line-height`, so `twMerge` would
|
||||
// otherwise drop the explicit `leading-none` per the
|
||||
// last-wins rule and break the W20a across-tile baseline
|
||||
// alignment (digits line up at the same y-coordinate
|
||||
// across every tile in the strip).
|
||||
"leading-none",
|
||||
)}
|
||||
data-slot="trend-card-value"
|
||||
>
|
||||
{latest !== null ? renderPair(latest, secondary?.latest) : "—"}
|
||||
</span>
|
||||
<span className="text-muted-foreground min-w-0 truncate text-sm tabular-nums">
|
||||
{unit}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
|
||||
Reference in new issue
Block a user