feat(plan): resolveCell/resolveGrid return arrays; dashboard shows up to 3

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-08 10:53:26 +02:00
parent 80dc8a5c69
commit 72888bf9cd
9 changed files with 320 additions and 256 deletions

View File

@@ -529,7 +529,8 @@ export default function PlanGrid({
</span>
</td>
{users.map((u) => {
const cell = data.cells[u.id]?.[date] ?? null;
const cellArr = data.cells[u.id]?.[date] ?? [];
const cell = cellArr[0] ?? null;
const past = isPastDate(date, today);
// Past-day cells are read-only in the UI: an empty past
// cell is non-interactive (no create modal, no "+" hint),

View File

@@ -3,7 +3,7 @@ import Typography from "@mui/material/Typography";
import { Card } from "../../ui";
import { formatDate } from "../../utils/formatters";
/** The logged-in user's resolved work plan for today (see GET /api/admin/dashboard). */
/** One resolved plan record for today (see GET /api/admin/dashboard). */
export interface TodayPlan {
shift_date: string;
category: string;
@@ -23,15 +23,64 @@ function projectLabel(p: TodayPlan): string {
return parts.length > 0 ? parts.join(" — ") : "Bez projektu";
}
/**
* "Vaše dnešní zařazení" — shows the logged-in user's work-plan entry for today
* (category + project/site + note, with a range badge when the day is part of a
* multi-day range). `plan === null` = the user has plan access but nothing is
* scheduled today (muted empty state). Rendered near the clock-in on the
* dashboard; the caller gates it on the plan permission.
*/
export default function DashTodayPlan({ plan }: { plan: TodayPlan | null }) {
const color = plan?.category_color || "var(--mui-palette-primary-main)";
function PlanRow({ plan }: { plan: TodayPlan }) {
const color = plan.category_color || "var(--mui-palette-primary-main)";
return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Box
sx={{ display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }}
>
<Box
sx={{
display: "inline-flex",
alignItems: "center",
gap: 0.75,
px: 1.25,
py: 0.5,
borderRadius: 999,
border: 1,
borderColor: "divider",
bgcolor: `color-mix(in srgb, ${color} 12%, transparent)`,
}}
>
<Box
sx={{
width: 10,
height: 10,
borderRadius: "50%",
bgcolor: color,
flexShrink: 0,
}}
/>
<Typography
component="span"
sx={{ fontWeight: 700, fontSize: "0.8rem" }}
>
{plan.category_label}
</Typography>
</Box>
{plan.rangeFrom && plan.rangeTo && plan.rangeFrom !== plan.rangeTo && (
<Typography variant="caption" color="text.secondary">
součást rozsahu {formatDate(plan.rangeFrom)} {" "}
{formatDate(plan.rangeTo)}
</Typography>
)}
</Box>
<Typography variant="h6" sx={{ lineHeight: 1.25 }}>
{projectLabel(plan)}
</Typography>
{plan.note && (
<Typography variant="body2" color="text.secondary">
{plan.note}
</Typography>
)}
</Box>
);
}
/** "Vaše dnešní zařazení" — the logged-in user's resolved plan record(s) for
* today (up to 3). Empty array = nothing scheduled. */
export default function DashTodayPlan({ plans }: { plans: TodayPlan[] }) {
return (
<Card sx={{ mb: 3 }}>
<Typography
@@ -40,65 +89,20 @@ export default function DashTodayPlan({ plan }: { plan: TodayPlan | null }) {
>
Vaše dnešní zařazení
</Typography>
{plan ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
flexWrap: "wrap",
}}
>
{plans.length > 0 ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{plans.map((p, i) => (
<Box
key={i}
sx={{
display: "inline-flex",
alignItems: "center",
gap: 0.75,
px: 1.25,
py: 0.5,
borderRadius: 999,
border: 1,
pt: i === 0 ? 0 : 2,
borderTop: i === 0 ? 0 : 1,
borderColor: "divider",
bgcolor: `color-mix(in srgb, ${color} 12%, transparent)`,
}}
>
<Box
sx={{
width: 10,
height: 10,
borderRadius: "50%",
bgcolor: color,
flexShrink: 0,
}}
/>
<Typography
component="span"
sx={{ fontWeight: 700, fontSize: "0.8rem" }}
>
{plan.category_label}
</Typography>
<PlanRow plan={p} />
</Box>
{plan.rangeFrom &&
plan.rangeTo &&
plan.rangeFrom !== plan.rangeTo && (
<Typography variant="caption" color="text.secondary">
součást rozsahu {formatDate(plan.rangeFrom)} {" "}
{formatDate(plan.rangeTo)}
</Typography>
)}
</Box>
<Typography variant="h6" sx={{ lineHeight: 1.25 }}>
{projectLabel(plan)}
</Typography>
{plan.note && (
<Typography variant="body2" color="text.secondary">
{plan.note}
</Typography>
)}
))}
</Box>
) : (
<Typography variant="body2" color="text.secondary">