import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link as RouterLink } from "react-router-dom"; import { useNavigate } from "react-router-dom"; import Box from "@mui/material/Box"; import { userListOptions } from "../lib/queries/users"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import { useApiMutation } from "../lib/queries/mutations"; import { todayLocalStr } from "../utils/formatters"; import { combineDatetime } from "../utils/attendanceHelpers"; import { Button, Card, DateField, Field, LoadingState, PageEnter, PageHeader, Select, TextField, TimeField, } from "../ui"; const API_BASE = "/api/admin"; interface CreateForm { user_id: string; shift_date: string; leave_type: string; leave_hours: number; arrival_date: string; arrival_time: string; break_start_date: string; break_start_time: string; break_end_date: string; break_end_time: string; departure_date: string; departure_time: string; notes: string; } /** * Wire payload for POST /attendance (CreateAttendanceSchema). The raw form * above is NOT sent directly: its separate date+time inputs are combined into * "YYYY-MM-DDTHH:MM:00" datetimes (same semantics as useAttendanceAdmin's * create modal), and unset optional fields are null — never "". */ interface CreatePayload { user_id: number; shift_date: string; leave_type: string; notes: string | null; leave_hours?: number; arrival_time?: string | null; departure_time?: string | null; break_start?: string | null; break_end?: string | null; } export default function AttendanceCreate() { const alert = useAlert(); const { hasPermission } = useAuth(); const navigate = useNavigate(); const { data: usersData, isPending: loading } = useQuery(userListOptions()); const users = (usersData ?? []).map((u) => ({ id: u.id, name: `${u.first_name} ${u.last_name}`.trim() || u.username, })); const [submitting, setSubmitting] = useState(false); const createMutation = useApiMutation({ url: () => `${API_BASE}/attendance`, method: () => "POST", invalidate: ["attendance", "users", "dashboard"], }); const [form, setForm] = useState(() => { const today = todayLocalStr(); return { user_id: "", shift_date: today, leave_type: "work", leave_hours: 8, arrival_date: today, arrival_time: "", break_start_date: today, break_start_time: "", break_end_date: today, break_end_time: "", departure_date: today, departure_time: "", notes: "", }; }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!form.user_id || !form.shift_date) { alert.error("Vyplňte zaměstnance a datum směny"); return; } setSubmitting(true); try { const isLeave = form.leave_type !== "work"; const payload: CreatePayload = { user_id: Number(form.user_id), shift_date: form.shift_date, leave_type: form.leave_type, notes: form.notes || null, }; if (isLeave) { payload.leave_hours = form.leave_hours || 8; } else { payload.arrival_time = combineDatetime( form.arrival_date, form.arrival_time, ); payload.departure_time = combineDatetime( form.departure_date, form.departure_time, ); payload.break_start = combineDatetime( form.break_start_date, form.break_start_time, ); payload.break_end = combineDatetime( form.break_end_date, form.break_end_time, ); } const result = await createMutation.mutateAsync(payload); alert.success(result?.message || "Uloženo"); navigate(`/attendance/admin?month=${form.shift_date.substring(0, 7)}`); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } finally { setSubmitting(false); } }; const handleShiftDateChange = (newDate: string) => { setForm({ ...form, shift_date: newDate, arrival_date: newDate, break_start_date: newDate, break_end_date: newDate, departure_date: newDate, }); }; const isWorkType = form.leave_type === "work"; if (!hasPermission("attendance.manage")) return ; if (loading) { return ; } return ( ← Zpět na správu } /> {/* Employee + Shift date */} setForm({ ...form, leave_type: val })} options={[ { value: "work", label: "Práce" }, { value: "vacation", label: "Dovolená" }, { value: "sick", label: "Nemoc" }, { value: "unpaid", label: "Neplacené volno" }, ]} /> {/* Leave hours — shown only for non-work types */} {!isWorkType && ( setForm({ ...form, leave_hours: parseFloat(e.target.value) || 0, }) } slotProps={{ htmlInput: { min: 0.5, max: 24, step: 0.5 } }} /> )} {/* Work-type time fields */} {isWorkType && ( <> {/* Arrival */} setForm({ ...form, arrival_date: val })} /> setForm({ ...form, arrival_time: val })} /> {/* Break start */} setForm({ ...form, break_start_date: val }) } /> setForm({ ...form, break_start_time: val }) } /> {/* Break end */} setForm({ ...form, break_end_date: val }) } /> setForm({ ...form, break_end_time: val }) } /> {/* Departure */} setForm({ ...form, departure_date: val }) } /> setForm({ ...form, departure_time: val }) } /> )} {/* Notes */} setForm({ ...form, notes: e.target.value })} /> {/* Actions */} ); }