feat(plan): day panel — list/add/edit up to 3 records per cell

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-08 11:12:30 +02:00
parent a146fc26a3
commit 9370423fb0
3 changed files with 159 additions and 47 deletions

View File

@@ -8,6 +8,7 @@ import { useAlert } from "../context/AlertContext";
import {
usePlanWork,
getCell,
getCells,
type ModalMode as HookModalMode,
} from "../hooks/usePlanWork";
import type { PlanCellModalMode } from "../components/PlanCellModal";
@@ -206,76 +207,68 @@ export default function PlanWork() {
);
const openCell = useCallback(
(userId: number, date: string, _cells: ResolvedCell[]) => {
void _cells;
const cell = getCell(grid, userId, date);
(userId: number, date: string, cells: ResolvedCell[]) => {
const primary = cells[0] ?? null;
// Past-date guard: the server rejects create/edit on past dates with
// a 403 (see assertNotPastDate in plan.service.ts). To keep the user
// out of the "click → modal opens → error toast" state, route any
// past-day click to view mode (or to no-op if there's no record).
if (isPastDate(date, todayIsoLocal())) {
if (!cell) {
// Empty past cell — nothing to show or edit.
return;
}
setModalCell(cell);
if (!primary) return;
setModalCell(primary);
setHookModal({ kind: "view", userId, date });
return;
}
if (!cell) {
// Empty cell — open the create modal if the user can edit, otherwise
// there's nothing to view.
if (cells.length === 0) {
if (canEdit) openCreate(userId, date);
return;
}
if (!canEdit) {
setModalCell(primary);
setHookModal({ kind: "view", userId, date });
return;
}
setHookModal({ kind: "day", userId, date });
},
[canEdit, openCreate, setHookModal],
);
// Open the correct editor for one record from the day panel. This is the
// per-record version of the old openCell branch logic: a multi-day entry
// routes through the day-in-range chooser; a single-day entry → edit-range;
// an override → edit-override.
const editRecord = useCallback(
(cell: ReturnType<typeof getCell>, userId: number, date: string) => {
if (!cell) return;
setModalCell(cell);
if (cell.source === "entry" && cell.entryId !== null) {
if (canEdit) {
// A multi-day entry clicked on a single day → "day-in-range" so the
// user can decide between editing the range or creating a one-day
// override.
if (
cell.rangeFrom &&
cell.rangeTo &&
cell.rangeFrom !== cell.rangeTo
) {
setHookModal({
kind: "day-in-range",
entryId: cell.entryId,
userId,
date,
});
return;
}
if (cell.rangeFrom && cell.rangeTo && cell.rangeFrom !== cell.rangeTo) {
setHookModal({
kind: "day-in-range",
entryId: cell.entryId,
userId,
date,
});
} else {
setHookModal({
kind: "edit-range",
entryId: cell.entryId,
userId,
date,
});
return;
}
// Read-only view
setHookModal({ kind: "view", userId, date });
return;
}
if (cell.source === "override" && cell.overrideId !== null) {
if (canEdit) {
setHookModal({
kind: "edit-override",
overrideId: cell.overrideId,
userId,
date,
});
} else {
setHookModal({ kind: "view", userId, date });
}
return;
setHookModal({
kind: "edit-override",
overrideId: cell.overrideId,
userId,
date,
});
}
// No source — treat as empty
if (canEdit) openCreate(userId, date);
},
[grid, canEdit, openCreate, setHookModal],
[setHookModal],
);
const closeModal = useCallback(() => {
@@ -545,8 +538,16 @@ export default function PlanWork() {
},
} as PlanCellModalMode;
}
if (hookModal.kind === "day") {
return {
kind: "day",
userId: hookModal.userId,
date: hookModal.date,
cells: getCells(grid, hookModal.userId, hookModal.date),
};
}
return { kind: "closed" };
}, [hookModal, modalCell]);
}, [hookModal, modalCell, grid]);
if (!canEdit && !hasPermission("attendance.record")) {
// Hard block for users who have neither manage nor record permission.
@@ -700,6 +701,8 @@ export default function PlanWork() {
onDeleteOverride={deleteOverrideFn}
onCreateOverrideFromRange={createOverrideFromRange}
onSwitchToEditRange={switchToEditRange}
onAddRecord={openCreate}
onEditRecord={editRecord}
/>
<PlanCategoriesModal
isOpen={catModalOpen}