feat(plan): grid renders up to 3 stacked records per cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -290,6 +290,17 @@ const PlanGridRoot = styled(Box)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
"& .plan-cell--pulse": { animation: "plan-cell-pulse 600ms ease-out" },
|
"& .plan-cell--pulse": { animation: "plan-cell-pulse 600ms ease-out" },
|
||||||
|
|
||||||
|
"& .plan-cell-record": {
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
minWidth: 0,
|
||||||
|
},
|
||||||
|
"& .plan-cell-record + .plan-cell-record": {
|
||||||
|
marginTop: 5,
|
||||||
|
paddingTop: 5,
|
||||||
|
borderTop: `1px dashed ${theme.vars!.palette.divider}`,
|
||||||
|
},
|
||||||
|
|
||||||
// Chips inside a cell
|
// Chips inside a cell
|
||||||
"& .plan-chip-block": {
|
"& .plan-chip-block": {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -373,11 +384,7 @@ interface Props {
|
|||||||
// the value so back-to-back mutations on the same cell re-trigger
|
// the value so back-to-back mutations on the same cell re-trigger
|
||||||
// the animation (React needs a new key to re-mount the class).
|
// the animation (React needs a new key to re-mount the class).
|
||||||
pulseKey?: { userId: number; date: string; nonce: number } | null;
|
pulseKey?: { userId: number; date: string; nonce: number } | null;
|
||||||
onCellClick: (
|
onCellClick: (userId: number, date: string, cells: ResolvedCell[]) => void;
|
||||||
userId: number,
|
|
||||||
date: string,
|
|
||||||
cell: ResolvedCell | null,
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full Czech weekday names, indexed by Date.getUTCDay() (0 = Sunday).
|
// Full Czech weekday names, indexed by Date.getUTCDay() (0 = Sunday).
|
||||||
@@ -572,29 +579,51 @@ export default function PlanGrid({
|
|||||||
} as CSSProperties)
|
} as CSSProperties)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onClick={() => onCellClick(u.id, date, cell)}
|
onClick={() => onCellClick(u.id, date, cellArr)}
|
||||||
aria-label={
|
aria-label={
|
||||||
cell
|
cell
|
||||||
? `${u.full_name}, ${date}, ${planCategoryLabel(cell.category, catMap)}`
|
? cellArr.length === 1
|
||||||
|
? `${u.full_name}, ${date}, ${planCategoryLabel(cell.category, catMap)}`
|
||||||
|
: `${u.full_name}, ${date}, ${cellArr.length} záznamy`
|
||||||
: isLocked
|
: isLocked
|
||||||
? `${u.full_name}, ${date}, ${past ? "uplynulý den" : "prázdné"} — bez záznamu`
|
? `${u.full_name}, ${date}, ${past ? "uplynulý den" : "prázdné"} — bez záznamu`
|
||||||
: `${u.full_name}, ${date}, prázdné — přidat záznam`
|
: `${u.full_name}, ${date}, prázdné — přidat záznam`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PlanRangeChips
|
{cellArr.map((c, i) => (
|
||||||
cell={cell}
|
<Box
|
||||||
project={
|
key={
|
||||||
cell?.project_id
|
c.entryId != null
|
||||||
? (projects.find(
|
? c.entryId
|
||||||
(p) => p.id === cell.project_id,
|
: c.overrideId != null
|
||||||
) ?? null)
|
? `o${c.overrideId}`
|
||||||
: null
|
: i
|
||||||
}
|
}
|
||||||
readonly={!canEdit}
|
className="plan-cell-record"
|
||||||
categoryLabel={
|
style={
|
||||||
cell ? planCategoryLabel(cell.category, catMap) : ""
|
{
|
||||||
}
|
"--cat-color": catMap[c.category]?.color,
|
||||||
/>
|
} as CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<PlanRangeChips
|
||||||
|
cell={c}
|
||||||
|
project={
|
||||||
|
c.project_id
|
||||||
|
? (projects.find(
|
||||||
|
(p) => p.id === c.project_id,
|
||||||
|
) ?? null)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
readonly={!canEdit}
|
||||||
|
categoryLabel={planCategoryLabel(
|
||||||
|
c.category,
|
||||||
|
catMap,
|
||||||
|
)}
|
||||||
|
showNote={cellArr.length === 1}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface Props {
|
|||||||
project: Project | null;
|
project: Project | null;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
categoryLabel: string;
|
categoryLabel: string;
|
||||||
|
showNote?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PlanRangeChips({
|
export default function PlanRangeChips({
|
||||||
@@ -13,6 +14,7 @@ export default function PlanRangeChips({
|
|||||||
project,
|
project,
|
||||||
readonly,
|
readonly,
|
||||||
categoryLabel,
|
categoryLabel,
|
||||||
|
showNote = true,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
void readonly;
|
void readonly;
|
||||||
if (!cell) return null;
|
if (!cell) return null;
|
||||||
@@ -36,7 +38,9 @@ export default function PlanRangeChips({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{cell.note && <div className="plan-cell-note">{cell.note}</div>}
|
{showNote && cell.note && (
|
||||||
|
<div className="plan-cell-note">{cell.note}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ import PlanCategoriesModal from "../components/PlanCategoriesModal";
|
|||||||
import Forbidden from "../components/Forbidden";
|
import Forbidden from "../components/Forbidden";
|
||||||
import { Button, Alert, LoadingState, PageEnter } from "../ui";
|
import { Button, Alert, LoadingState, PageEnter } from "../ui";
|
||||||
import { projectListOptions, type Project } from "../lib/queries/projects";
|
import { projectListOptions, type Project } from "../lib/queries/projects";
|
||||||
import { planCategoriesOptions, type PlanCategory } from "../lib/queries/plan";
|
import {
|
||||||
|
planCategoriesOptions,
|
||||||
|
type PlanCategory,
|
||||||
|
type ResolvedCell,
|
||||||
|
} from "../lib/queries/plan";
|
||||||
// plan.css is imported once globally in AdminApp.tsx — no per-page re-import.
|
// plan.css is imported once globally in AdminApp.tsx — no per-page re-import.
|
||||||
|
|
||||||
const MONTH_NAMES = [
|
const MONTH_NAMES = [
|
||||||
@@ -202,7 +206,8 @@ export default function PlanWork() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const openCell = useCallback(
|
const openCell = useCallback(
|
||||||
(userId: number, date: string) => {
|
(userId: number, date: string, _cells: ResolvedCell[]) => {
|
||||||
|
void _cells;
|
||||||
const cell = getCell(grid, userId, date);
|
const cell = getCell(grid, userId, date);
|
||||||
// Past-date guard: the server rejects create/edit on past dates with
|
// Past-date guard: the server rejects create/edit on past dates with
|
||||||
// a 403 (see assertNotPastDate in plan.service.ts). To keep the user
|
// a 403 (see assertNotPastDate in plan.service.ts). To keep the user
|
||||||
|
|||||||
Reference in New Issue
Block a user