import { useState, useEffect, useCallback, useRef } from 'react' import DOMPurify from 'dompurify' import { useAlert } from '../context/AlertContext' import { useAuth } from '../context/AuthContext' import Forbidden from '../components/Forbidden' import { motion } from 'framer-motion' import AdminDatePicker from '../components/AdminDatePicker' import { formatDate, formatDatetime, formatTime, calculateWorkMinutes, formatMinutes, getLeaveTypeName, getLeaveTypeBadgeClass, calculateWorkMinutesPrint, formatTimeOrDatetimePrint } from '../utils/attendanceHelpers' import FormField from '../components/FormField' import apiFetch from '../utils/api' const API_BASE = '/api/admin' const formatBreakRange = (record) => { if (record.break_start && record.break_end) { return `${formatTime(record.break_start)} - ${formatTime(record.break_end)}` } if (record.break_start) { return `${formatTime(record.break_start)} - ?` } return '—' } const renderProjectCell = (record) => { if (record.project_logs && record.project_logs.length > 0) { return (
{record.project_logs.map((log, i) => { let h, m, isActive = false if (log.hours !== null && log.hours !== undefined) { h = parseInt(log.hours) || 0 m = parseInt(log.minutes) || 0 } else { isActive = !log.ended_at const end = log.ended_at ? new Date(log.ended_at) : new Date() const mins = Math.floor((end - new Date(log.started_at)) / 60000) h = Math.floor(mins / 60) m = mins % 60 } return ( {log.project_name || `#${log.project_id}`} ({h}:{String(m).padStart(2, '0')}h{isActive ? ' ▸' : ''}) ) })}
) } if (record.project_name) { return {record.project_name} } return '—' } const renderPrintFundStatus = (fund) => { if (fund.overtime > 0) { return +{fund.overtime}h přesčas } if (fund.remaining > 0) { return −{fund.remaining}h } return splněno } export default function AttendanceHistory() { const alert = useAlert() const { user, hasPermission } = useAuth() const [loading, setLoading] = useState(true) const printRef = useRef(null) const [month, setMonth] = useState(() => { const now = new Date() return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}` }) const [data, setData] = useState({ records: [], month_name: '', year: new Date().getFullYear(), total_minutes: 0, vacation_hours: 0, sick_hours: 0, holiday_hours: 0, unpaid_hours: 0, leave_balance: null, monthly_fund: null }) const fetchData = useCallback(async () => { setLoading(true) try { const response = await apiFetch(`${API_BASE}/attendance.php?action=history&month=${month}`) if (response.status === 401) return const result = await response.json() if (result.success) { setData(result.data) } } catch { alert.error('Nepodařilo se načíst data') } finally { setLoading(false) } }, [month, alert]) useEffect(() => { fetchData() }, [fetchData]) if (!hasPermission('attendance.history')) return const handlePrint = () => { if (!printRef.current) return const printWindow = window.open('', '_blank') printWindow.document.write(` Docházka - ${data.month_name} ${DOMPurify.sanitize(printRef.current.innerHTML)} `) printWindow.document.close() printWindow.onload = () => { printWindow.print() } } return (

Historie docházky

{data.month_name}

{data.records.length > 0 && ( )}
{/* Filters */}
setMonth(val)} />
{/* Monthly Fund Card */}
{loading && (
)} {!loading && data.monthly_fund && (
Fond: {data.monthly_fund.worked}h / {data.monthly_fund.fund}h {data.monthly_fund.business_days} prac. dnů
0 ? (data.monthly_fund.covered / data.monthly_fund.fund) * 100 : 0)}%`, background: data.monthly_fund.covered >= data.monthly_fund.fund ? 'linear-gradient(135deg, var(--success), #059669)' : 'var(--gradient)' }} />
{'Pokryto: '}{data.monthly_fund.covered}h (práce {data.monthly_fund.worked}h {data.vacation_hours > 0 && ` + dovolená ${data.vacation_hours}h`} {data.sick_hours > 0 && ` + nemoc ${data.sick_hours}h`} {data.holiday_hours > 0 && ` + svátek ${data.holiday_hours}h`} {data.unpaid_hours > 0 && ` + neplacené ${data.unpaid_hours}h`} ) {data.monthly_fund.overtime > 0 ? ( Přesčas: +{data.monthly_fund.overtime}h ) : ( Zbývá: {data.monthly_fund.remaining}h )}
)} {!loading && !data.monthly_fund && (
Fond měsíce není k dispozici
)}
{/* Records Table */}
{loading && (
{[0, 1, 2, 3, 4].map(i => (
))}
)} {!loading && data.records.length === 0 && (

Za tento měsíc nejsou žádné záznamy.

)} {!loading && data.records.length > 0 && (
{data.records.map((record) => { const leaveType = record.leave_type || 'work' const isLeave = leaveType !== 'work' const workMinutes = isLeave ? (record.leave_hours || 8) * 60 : calculateWorkMinutes(record) return ( ) })}
Datum Typ Příchod Pauza Odchod Hodiny Projekty Poznámka
{formatDate(record.shift_date)} {getLeaveTypeName(leaveType)} {isLeave ? '—' : formatDatetime(record.arrival_time)} {isLeave ? '—' : formatBreakRange(record)} {isLeave ? '—' : formatDatetime(record.departure_time)} {workMinutes > 0 ? formatMinutes(workMinutes, true) : '—'} {renderProjectCell(record)} {record.notes || ''}
)}
{/* Hidden Print Content */} {data.records.length > 0 && (
BOHA

EVIDENCE DOCHÁZKY

BOHA Automation s.r.o.
{data.month_name}
Zaměstnanec: {user?.fullName || ''}
Vygenerováno: {new Date().toLocaleString('cs-CZ')}

{user?.fullName || ''}

Odpracováno: {formatMinutes(data.total_minutes, true)}
{data.leave_balance && (
Dovolená {data.year}: Zbývá {data.leave_balance.vacation_remaining.toFixed(1)}h z {data.leave_balance.vacation_total}h {data.vacation_hours > 0 && <> | Tento měsíc: {data.vacation_hours}h} {data.sick_hours > 0 && <> | Nemoc: {data.sick_hours}h} {data.holiday_hours > 0 && <> | Svátek: {data.holiday_hours}h} {data.monthly_fund?.overtime > 0 && <> | Přesčas: +{data.monthly_fund.overtime}h}
)} {[...data.records].sort((a, b) => a.shift_date.localeCompare(b.shift_date)).map((record) => { const leaveType = record.leave_type || 'work' const isLeave = leaveType !== 'work' const workMinutes = calculateWorkMinutesPrint(record) const hours = Math.floor(workMinutes / 60) const mins = workMinutes % 60 return ( ) })} {data.monthly_fund && ( )}
Datum Typ Příchod Pauza Odchod Hodiny Projekty Poznámka
{formatDate(record.shift_date)} {getLeaveTypeName(leaveType)} {isLeave ? '—' : formatTimeOrDatetimePrint(record.arrival_time, record.shift_date)} {isLeave || !record.break_start || !record.break_end ? '—' : `${formatTimeOrDatetimePrint(record.break_start, record.shift_date)} - ${formatTimeOrDatetimePrint(record.break_end, record.shift_date)}` } {isLeave ? '—' : formatTimeOrDatetimePrint(record.departure_time, record.shift_date)} {workMinutes > 0 ? `${hours}:${String(mins).padStart(2, '0')}` : '—'} {(record.project_logs && record.project_logs.length > 0) ? record.project_logs.map((log, i) => { let h, m if (log.hours !== null && log.hours !== undefined) { h = parseInt(log.hours) || 0; m = parseInt(log.minutes) || 0 } else if (log.started_at && log.ended_at) { const mins2 = Math.max(0, Math.floor((new Date(log.ended_at) - new Date(log.started_at)) / 60000)) h = Math.floor(mins2 / 60); m = mins2 % 60 } else { h = 0; m = 0 } return
{log.project_name || `#${log.project_id}`} ({h}:{String(m).padStart(2, '0')}h)
}) : record.project_name || '—'}
{record.notes || ''}
Odpracováno: {formatMinutes(data.total_minutes, true)}
Fond měsíce: {data.monthly_fund.covered}h / {data.monthly_fund.fund}h {renderPrintFundStatus(data.monthly_fund)}
)}
) }