Merge branch 'feat/w-e-score-rings' into release/v1.27.7

This commit is contained in:
Marc-André Bombeck committed 2026-07-05 15:45:31 +02:00
commit 093a87e19e
21 files changed
+1230 -211

No files matched your search

+22 -5
View File
@@ -22,8 +22,11 @@ import {
DASHBOARD_WIDGET_CATALOGUE_IDS,
COMPARISON_BASELINES,
CHART_OVERLAY_KEYS,
SCORE_RING_IDS,
MAX_SELECTED_SCORE_RINGS,
type ChartOverlayPrefsMap,
type DashboardLayout,
type ScoreRingId,
} from "@/lib/dashboard-layout";
import { Prisma } from "@/generated/prisma/client";
import { z } from "zod/v4";
@@ -121,6 +124,13 @@ const layoutSchema = z.object({
// `chartOverlayPrefs` — a Settings save that doesn't know the field
// must not silently reset the user's choice).
heroVisible: z.boolean().optional(),
// v1.27.7 — hero score rings (max 3, closed id set). Optional with the
// same preserve-when-absent contract as `heroVisible`; the resolver
// dedupes on read/serialize, `.max()` bounds the wire length.
selectedScoreRings: z
.array(z.enum(SCORE_RING_IDS))
.max(MAX_SELECTED_SCORE_RINGS)
.optional(),
});
async function buildDashboardLayout(userId: string): Promise<DashboardLayout> {
@@ -262,16 +272,19 @@ export const PUT = apiHandler(async (request: NextRequest) => {
// didn't send. The dashboard-layout PUT typically saves widget
// visibility / order; chart prefs are PUT through their own route
// (`/api/dashboard/chart-overlay-prefs`) and would otherwise be
// wiped here on a subsequent layout save. `heroVisible` rides the
// same preserve-when-absent contract — an older client's layout
// save must not reset the hero toggle. One stored-layout read
// covers both fallbacks.
// wiped here on a subsequent layout save. `heroVisible` and
// `selectedScoreRings` ride the same preserve-when-absent contract —
// an older client's layout save must not reset either choice. One
// stored-layout read covers all fallbacks.
let mergedChartOverlayPrefs: ChartOverlayPrefsMap | undefined = parsed.data
.chartOverlayPrefs as ChartOverlayPrefsMap | undefined;
let mergedHeroVisible: boolean | undefined = parsed.data.heroVisible;
let mergedScoreRings: ScoreRingId[] | undefined =
parsed.data.selectedScoreRings;
if (
mergedChartOverlayPrefs === undefined ||
mergedHeroVisible === undefined
mergedHeroVisible === undefined ||
mergedScoreRings === undefined
) {
const existing = await prisma.user.findUnique({
where: { id: user.id },
@@ -286,11 +299,15 @@ export const PUT = apiHandler(async (request: NextRequest) => {
if (mergedHeroVisible === undefined) {
mergedHeroVisible = existingLayout.heroVisible === true;
}
if (mergedScoreRings === undefined) {
mergedScoreRings = existingLayout.selectedScoreRings;
}
}
const normalized = serializeDashboardLayout({
...parsed.data,
chartOverlayPrefs: mergedChartOverlayPrefs,
heroVisible: mergedHeroVisible,
selectedScoreRings: mergedScoreRings,
} as DashboardLayout);
await prisma.user.update({