import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import { motion } from "framer-motion"; import ConfirmModal from "../components/ConfirmModal"; import AdminDatePicker from "../components/AdminDatePicker"; import BulkAttendanceModal from "../components/BulkAttendanceModal"; import ShiftFormModal from "../components/ShiftFormModal"; import AttendanceShiftTable from "../components/AttendanceShiftTable"; import useModalLock from "../hooks/useModalLock"; import useAttendanceAdmin from "../hooks/useAttendanceAdmin"; import FormField from "../components/FormField"; import { formatMinutes, formatHoursDecimal } from "../utils/attendanceHelpers"; interface UserTotalData { name: string; minutes: number; working: boolean; vacation_hours: number; sick_hours: number; unpaid_hours: number; fund: number | null; worked_hours: number; covered: number; missing: number; overtime: number; // mzda-style summary fields (set by computeUserTotals in useAttendanceAdmin) odpracovano?: number; vcetne_svatku?: number; prescas?: number; svatek?: number; worked_holiday_hours?: number; } function getFundBarBackground(data: UserTotalData) { // fondUsed mirrors the Fond "used" line in the IIFE below: // real_worked + vacation + worked_holiday + free_holiday // (delta is fondUsed - fund; 0 means exactly at the fund). const realWorked = data.worked_hours_raw ?? data.worked_hours; const fondUsed = realWorked + (data.vacation_hours ?? 0) + (data.worked_holiday_hours ?? 0) + (data.svatek ?? 0); const fundVal = data.fund ?? 0; const delta = fondUsed - fundVal; if (delta > 0) return "linear-gradient(135deg, var(--warning), #d97706)"; if (delta >= 0) return "linear-gradient(135deg, var(--success), #059669)"; return "var(--gradient)"; } export default function AttendanceAdmin() { const alert = useAlert(); const { hasPermission } = useAuth(); const { loading, month, setMonth, filterUserId, setFilterUserId, data, hasData, showBulkModal, setShowBulkModal, bulkSubmitting, bulkForm, setBulkForm, showCreateModal, setShowCreateModal, createForm, setCreateForm, showEditModal, setShowEditModal, editingRecord, editForm, setEditForm, deleteConfirm, setDeleteConfirm, projectList, createProjectLogs, setCreateProjectLogs, editProjectLogs, setEditProjectLogs, openCreateModal, handleCreateShiftDateChange, handleCreateSubmit, openBulkModal, toggleBulkUser, toggleAllBulkUsers, handleBulkSubmit, openEditModal, handleEditSubmit, handleDelete, handlePrint, } = useAttendanceAdmin({ alert }); useModalLock(showBulkModal); useModalLock(showEditModal); useModalLock(showCreateModal); if (!hasPermission("attendance.manage")) return ; // Show spinner only on initial load (no data yet), not on filter changes const isInitialLoad = loading && data.records.length === 0 && Object.keys(data.user_totals).length === 0; if (isInitialLoad) { return (
); } return (

Správa docházky

{hasData && ( )}
{/* Filters */}
setMonth(val)} />
{/* User Totals */} {Object.keys(data.user_totals).length > 0 && ( {Object.entries(data.user_totals).map(([uid, userData]) => { const ut = userData as UserTotalData; return (
{ut.name} {ut.working ? "\u2713" : "\u2717"}
{formatMinutes(ut.minutes)}
odpracováno
{ut.vacation_hours > 0 && ( Dov: {ut.vacation_hours}h )} {ut.sick_hours > 0 && ( Nem: {ut.sick_hours}h )} {ut.svatek !== undefined && ut.svatek > 0 && ( Sv: {Math.round(ut.svatek)}h )} {ut.unpaid_hours > 0 && ( Nep: {ut.unpaid_hours}h )}
{ut.fund !== null && (() => { // Fond "used" = real_worked + vacation + worked_holiday + free_holiday // (everything the user actually got paid for this month: // raw worked time with sub-day remainders, plus // vacation days, plus hours worked on holidays, plus // the 8h per unworked holiday from the fund). const realWorked = ut.worked_hours_raw ?? ut.worked_hours; const fondUsed = realWorked + (ut.vacation_hours ?? 0) + (ut.worked_holiday_hours ?? 0) + (ut.svatek ?? 0); const fundVal = ut.fund ?? 0; const delta = fondUsed - fundVal; return (
Fond: {formatHoursDecimal(fondUsed * 60)}h /{" "} {formatHoursDecimal(fundVal * 60)}h {delta > 0 && ( +{formatHoursDecimal(delta * 60)}h )} {delta <= 0 && delta < 0 && ( -{formatHoursDecimal(Math.abs(delta) * 60)}h )}
); })()}
{data.leave_balances[uid] ? ( <> Zbývá dovolené:{" "} {data.leave_balances[uid].vacation_remaining.toFixed(1)} h / {data.leave_balances[uid].vacation_total}h ) : ( )}
); })} )} {/* Records Table */}
setDeleteConfirm({ show: true, record })} />
{/* Modals */} setShowBulkModal(false)} form={bulkForm} setForm={setBulkForm} users={data.users} onSubmit={handleBulkSubmit} submitting={bulkSubmitting} toggleUser={toggleBulkUser} toggleAllUsers={toggleAllBulkUsers} /> setShowCreateModal(false)} onSubmit={handleCreateSubmit} form={createForm} setForm={setCreateForm} projectLogs={createProjectLogs} setProjectLogs={setCreateProjectLogs} projectList={projectList} users={data.users} onShiftDateChange={handleCreateShiftDateChange} editingRecord={null} /> setShowEditModal(false)} onSubmit={handleEditSubmit} form={editForm} setForm={setEditForm} projectLogs={editProjectLogs} setProjectLogs={setEditProjectLogs} projectList={projectList} users={data.users} onShiftDateChange={handleCreateShiftDateChange} editingRecord={ editingRecord ? { user_name: editingRecord.user_name ?? "", shift_date: editingRecord.shift_date, } : null } /> setDeleteConfirm({ show: false, record: null })} onConfirm={handleDelete} title="Smazat záznam" message="Opravdu chcete smazat tento záznam docházky?" confirmText="Smazat" confirmVariant="danger" />
); }