diff --git a/src/admin/hooks/useAttendanceAdmin.ts b/src/admin/hooks/useAttendanceAdmin.ts index 5bb4560..a3ebaed 100644 --- a/src/admin/hooks/useAttendanceAdmin.ts +++ b/src/admin/hooks/useAttendanceAdmin.ts @@ -6,6 +6,12 @@ import { calculateWorkMinutes, getDatePart, getTimePart, + formatDate, + formatMinutes, + getLeaveTypeName, + getLeaveTypeBadgeClass, + formatTimeOrDatetimePrint, + calculateWorkMinutesPrint, } from '../utils/attendanceHelpers' import type { ShiftFormData, ProjectLog, Project, User } from '../components/ShiftFormModal' @@ -200,6 +206,153 @@ function computeUserTotals( return totals } +// --------------------------------------------------------------------------- +// Print helpers +// --------------------------------------------------------------------------- + +function renderFundStatus(userData: Record): string { + if (userData.overtime > 0) return `+${userData.overtime}h přesčas` + if (userData.missing > 0) return `−${userData.missing}h` + return 'splněno' +} + +function buildProjectLogsHtml(record: Record): string { + if (record.project_logs && record.project_logs.length > 0) { + return record.project_logs.map((log: any) => { + let h: number, m: number + 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 mins = Math.max(0, Math.floor((new Date(log.ended_at).getTime() - new Date(log.started_at).getTime()) / 60000)) + h = Math.floor(mins / 60) + m = mins % 60 + } else { h = 0; m = 0 } + return `
${log.project_name || `#${log.project_id}`} (${h}:${String(m).padStart(2, '0')}h)
` + }).join('') + } + return record.project_name || '—' +} + +function buildLeaveSummaryHtml(userId: string, userData: Record, printData: Record): string { + const bal = printData.leave_balances[userId] + let parts = `Dovolená ${printData.year}: Zbývá ${bal.vacation_remaining.toFixed(1)}h z ${bal.vacation_total}h` + if (userData.vacation_hours > 0) parts += ` | Tento měsíc: ${userData.vacation_hours}h` + if (userData.sick_hours > 0) parts += ` | Nemoc: ${userData.sick_hours}h` + if (userData.holiday_hours > 0) parts += ` | Svátek: ${userData.holiday_hours}h` + if (userData.overtime > 0) parts += ` | Přesčas: +${userData.overtime}h` + return `
${parts}
` +} + +function buildUserSectionHtml(userId: string, userData: Record, printData: Record): string { + const records = (printData.records || []).filter((r: any) => String(r.user_id) === userId) + let rows = '' + for (const record of records) { + const leaveType = record.leave_type || 'work' + const typeBadge = leaveType === 'work' + ? 'Práce' + : `${getLeaveTypeName(leaveType)}` + const workMins = calculateWorkMinutesPrint(record) + const hoursStr = workMins > 0 ? `${Math.floor(workMins / 60)}:${String(workMins % 60).padStart(2, '0')}` : (leaveType !== 'work' ? `${record.leave_hours || 8}h` : '—') + rows += ` + ${formatDate(record.shift_date)} + ${typeBadge} + ${formatTimeOrDatetimePrint(record.arrival_time, record.shift_date)} + ${record.break_start ? formatTimeOrDatetimePrint(record.break_start, record.shift_date) + ' – ' + formatTimeOrDatetimePrint(record.break_end, record.shift_date) : '—'} + ${formatTimeOrDatetimePrint(record.departure_time, record.shift_date)} + ${hoursStr} + ${buildProjectLogsHtml(record)} + ${record.notes || ''} + ` + } + + const leaveSummary = printData.leave_balances[userId] ? buildLeaveSummaryHtml(userId, userData, printData) : '' + + return `
+

${userData.name}

+ ${leaveSummary} + + + + + + + ${rows} + + + + + + + + + + + +
DatumTypPříchodPauzaOdchodHodinyProjektyPoznámka
Celkem odpracováno${formatMinutes(userData.minutes)}
Fond: ${userData.fund ?? '—'}h (${userData.business_days ?? '—'} prac. dnů)${renderFundStatus(userData)}
+
` +} + +function buildPrintHtml(pData: Record, userSections: string, emptyMsg: string, filterNote: string): string { + const now = new Date() + const generated = now.toLocaleDateString('cs-CZ') + ' ' + now.toLocaleTimeString('cs-CZ', { hour: '2-digit', minute: '2-digit' }) + return ` + + + +Docházka - ${pData.month_name} + + + +
+
+ Logo +
+
EVIDENCE DOCHÁZKY
+
BOHA Automation s.r.o.
+
+
+
+
Období: ${pData.month_name}
+
Vygenerováno: ${generated}
+
+
+ ${filterNote} + ${emptyMsg} + ${userSections} + +` +} + // --------------------------------------------------------------------------- // Hook // --------------------------------------------------------------------------- @@ -706,11 +859,38 @@ export default function useAttendanceAdmin({ alert }: AlertContext) { } // ========================================================================= - // Print (stub) + // Print // ========================================================================= const handlePrint = async () => { - // TODO: implement print functionality - alert.success('Funkce tisku bude brzy dostupná') + try { + let url = `${API_BASE}/attendance?action=print&month=${month}` + if (filterUserId) url += `&user_id=${filterUserId}` + const response = await apiFetch(url) + if (response.status === 401) return + const result = await response.json() + if (result.success) { + const pData = result.data + const userSections = Object.entries(pData.user_totals) + .map(([uid, uData]) => buildUserSectionHtml(uid, uData as Record, pData)) + .join('') + const emptyMsg = Object.keys(pData.user_totals).length === 0 + ? '

Za vybrané období nejsou žádné záznamy.

' + : '' + const filterNote = pData.selected_user_name + ? `
Zaměstnanec: ${pData.selected_user_name}
` + : '' + const bodyContent = buildPrintHtml(pData, userSections, emptyMsg, filterNote) + const printWindow = window.open('', '_blank') + if (printWindow) { + printWindow.document.open() + printWindow.document.write(bodyContent) + printWindow.document.close() + printWindow.onload = () => printWindow.print() + } + } + } catch { + alert.error('Nepodařilo se připravit tisk') + } } // =========================================================================