feat(plan): bulk assignment modal on /plan-work

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-08 12:06:19 +02:00
parent 95ca258718
commit 13d1fc2be3
3 changed files with 308 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ import type { PlanCellModalMode } from "../components/PlanCellModal";
import PlanGrid from "../components/PlanGrid";
import PlanCellModal from "../components/PlanCellModal";
import PlanCategoriesModal from "../components/PlanCategoriesModal";
import BulkPlanModal, { type BulkPlanForm } from "../components/BulkPlanModal";
import Forbidden from "../components/Forbidden";
import { Button, Alert, LoadingState, PageEnter } from "../ui";
import { projectListOptions, type Project } from "../lib/queries/projects";
@@ -128,6 +129,7 @@ export default function PlanWork() {
createOverride,
updateOverride,
deleteOverride,
bulkCreate,
rollbackMutation,
} = usePlanWork({ canEdit });
@@ -140,6 +142,84 @@ export default function PlanWork() {
const [catModalOpen, setCatModalOpen] = useState(false);
const [bulkOpen, setBulkOpen] = useState(false);
const [bulkSubmitting, setBulkSubmitting] = useState(false);
const [bulkForm, setBulkForm] = useState<BulkPlanForm>(() => {
const today = todayIsoLocal();
return {
date_from: today,
date_to: today,
is_range: false,
user_ids: [],
include_weekends: false,
project_id: "",
category: "work",
note: "",
};
});
const planUsers = useMemo(() => grid?.users ?? [], [grid]);
const toggleBulkUser = useCallback((userId: number) => {
setBulkForm((prev) => ({
...prev,
user_ids: prev.user_ids.includes(String(userId))
? prev.user_ids.filter((u) => u !== String(userId))
: [...prev.user_ids, String(userId)],
}));
}, []);
const toggleAllBulkUsers = useCallback(() => {
const allIds = (grid?.users ?? []).map((u) => String(u.id));
if (allIds.length === 0) return; // grid not yet loaded — no-op
setBulkForm((prev) => ({
...prev,
user_ids: prev.user_ids.length === allIds.length ? [] : allIds,
}));
}, [grid]);
const submitBulk = useCallback(async () => {
if (bulkForm.user_ids.length === 0) return;
setBulkSubmitting(true);
try {
const data: any = await bulkCreate.mutateAsync({
user_ids: bulkForm.user_ids.map(Number),
date_from: bulkForm.date_from,
date_to: bulkForm.is_range ? bulkForm.date_to : bulkForm.date_from,
include_weekends: bulkForm.include_weekends,
project_id: bulkForm.project_id ? Number(bulkForm.project_id) : null,
category: bulkForm.category,
note: bulkForm.note,
});
// apiCall returns the full envelope { success, data, message };
// the summary counts are under .data
const summary = data?.data ?? data;
const skipped =
summary?.skipped_days > 0
? ` ${summary.skipped_days} dní přeskočeno (limit 3 na den).`
: "";
alert.success(
`Vytvořeno ${summary?.created_days ?? 0} záznamů pro ${summary?.users ?? 0} zaměstnanců.${skipped}`,
);
setBulkOpen(false);
// Reset the volatile parts so reopening starts clean (project /
// category / weekends stay sticky — users often repeat those).
const today = todayIsoLocal();
setBulkForm((prev) => ({
...prev,
date_from: today,
date_to: today,
is_range: false,
user_ids: [],
note: "",
}));
} catch (e) {
alert.error(e instanceof Error ? e.message : "Chyba při ukládání");
} finally {
setBulkSubmitting(false);
}
}, [bulkForm, bulkCreate, alert]);
// `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
@@ -673,6 +753,15 @@ export default function PlanWork() {
Správa kategorií
</Button>
)}
{canEdit && (
<Button
variant="outlined"
color="inherit"
onClick={() => setBulkOpen(true)}
>
Hromadné přiřazení
</Button>
)}
</Box>
{gridLoading && <LoadingState />}
@@ -727,6 +816,19 @@ export default function PlanWork() {
onClose={() => setCatModalOpen(false)}
categories={categories}
/>
<BulkPlanModal
isOpen={bulkOpen}
onClose={() => setBulkOpen(false)}
form={bulkForm}
setForm={setBulkForm}
users={planUsers}
projects={projects}
categories={categories}
onSubmit={submitBulk}
submitting={bulkSubmitting}
toggleUser={toggleBulkUser}
toggleAllUsers={toggleAllBulkUsers}
/>
</PageEnter>
);
}