From 8b77a0a905172adfd47146fa0a3643c5cd825656 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 00:47:53 +0200 Subject: [PATCH] feat(plan): render category colors from DB via --cat-color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 14 hardcoded per-category CSS rules with a single --cat-color custom property set inline on each filled cell button, driven by the DB categories list threaded from PlanWork → PlanGrid → PlanRangeChips. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/components/PlanGrid.tsx | 24 +++++- src/admin/components/PlanRangeChips.tsx | 18 ++-- src/admin/pages/PlanWork.tsx | 5 ++ src/admin/plan.css | 104 ++++-------------------- 4 files changed, 54 insertions(+), 97 deletions(-) diff --git a/src/admin/components/PlanGrid.tsx b/src/admin/components/PlanGrid.tsx index 6ae5b55..f3ca636 100644 --- a/src/admin/components/PlanGrid.tsx +++ b/src/admin/components/PlanGrid.tsx @@ -1,5 +1,12 @@ import { useMemo } from "react"; -import { GridData, ResolvedCell, planCategoryLabel } from "../lib/queries/plan"; +import type { CSSProperties } from "react"; +import { + GridData, + ResolvedCell, + planCategoryLabel, + categoryMap, + PlanCategory, +} from "../lib/queries/plan"; import type { Project } from "../lib/queries/projects"; import PlanRangeChips from "./PlanRangeChips"; @@ -7,6 +14,7 @@ interface Props { data: GridData | undefined; canEdit: boolean; projects: Project[]; + categories: PlanCategory[]; // The (userId, date) of the most recent successful mutation. The // matching cell gets a one-shot highlight pulse. `nonce` is part of // the value so back-to-back mutations on the same cell re-trigger @@ -92,10 +100,12 @@ export default function PlanGrid({ data, canEdit, projects, + categories, pulseKey, onCellClick, }: Props) { const today = useMemo(() => todayIso(), []); + const catMap = useMemo(() => categoryMap(categories), [categories]); if (!data) return ( @@ -206,10 +216,17 @@ export default function PlanGrid({ diff --git a/src/admin/components/PlanRangeChips.tsx b/src/admin/components/PlanRangeChips.tsx index e3fa700..c1723a7 100644 --- a/src/admin/components/PlanRangeChips.tsx +++ b/src/admin/components/PlanRangeChips.tsx @@ -1,17 +1,19 @@ -import { - ResolvedCell, - planCategoryLabel, - cellProjectLabel, -} from "../lib/queries/plan"; +import { ResolvedCell, cellProjectLabel } from "../lib/queries/plan"; import type { Project } from "../lib/queries/projects"; interface Props { cell: ResolvedCell | null; project: Project | null; readonly?: boolean; + categoryLabel: string; } -export default function PlanRangeChips({ cell, project, readonly }: Props) { +export default function PlanRangeChips({ + cell, + project, + readonly, + categoryLabel, +}: Props) { void readonly; if (!cell) return null; // Prefer the server-embedded project label (always present). Fall back to @@ -27,9 +29,7 @@ export default function PlanRangeChips({ cell, project, readonly }: Props) { return (
- - {planCategoryLabel(cell.category)} - + {categoryLabel} {projectLabel && ( {projectLabel} diff --git a/src/admin/pages/PlanWork.tsx b/src/admin/pages/PlanWork.tsx index 21b1e66..23f2d51 100644 --- a/src/admin/pages/PlanWork.tsx +++ b/src/admin/pages/PlanWork.tsx @@ -13,6 +13,7 @@ import PlanGrid from "../components/PlanGrid"; import PlanCellModal from "../components/PlanCellModal"; import Forbidden from "../components/Forbidden"; import { projectListOptions, type Project } from "../lib/queries/projects"; +import { planCategoriesOptions, type PlanCategory } from "../lib/queries/plan"; import "../plan.css"; const MONTH_NAMES = [ @@ -125,6 +126,9 @@ export default function PlanWork() { const { data: projectsData } = useQuery(projectListOptions({ perPage: 200 })); const projects: Project[] = (projectsData?.data as Project[]) ?? []; + const { data: categoriesData } = useQuery(planCategoriesOptions()); + const categories: PlanCategory[] = categoriesData ?? []; + // `lastMutated` is the (userId, date) of the most recent successful // mutation. We pass it to PlanGrid as `pulseKey`; the matching cell // gets a one-shot CSS pulse animation. We clear it after 700ms (the @@ -667,6 +671,7 @@ export default function PlanWork() {