@extends('layouts.app') @push('page-styles') @endpush @section('page-content')
{{ __('Attendances') }}
@for ($day = 1; $day <= $days_in_month; $day++) @php $currentMonth = request()->month ?? now()->month; $year = request()->year ?? now()->year; $date = \Carbon\Carbon::createFromDate($year, $currentMonth, $day); $dayName = $date->format('D'); // Mon, Tue, Wed, etc. @endphp @endfor @if (!empty($employees)) @foreach ($employees as $employee) @for ($day = 1; $day <= $days_in_month; $day++) @php $currentMonth = request()->month ?? now()->month; $year = request()->year ?? now()->year; $currentDate = \Carbon\Carbon::createFromDate($year, $currentMonth, $day)->toDateString(); // Pre-bucket attendances by 09:40 AM shift logic using device punches static $attendanceCache = []; $employeeKey = $employee->id; if (!isset($attendanceCache[$employeeKey])) { $attendanceCache[$employeeKey] = []; // 1. Process Device Punches $punches = $employee->devicePunches; // Note: Controller filters these roughly by month, // but we should iterate all available to ensuring binding. foreach ($punches as $punch) { $time = \Carbon\Carbon::parse($punch->punch_time); // Determine "Logical Date": If before 09:40 AM, it belongs to previous day if ($time->format('H:i') < '09:40') { $dateKey = $time->copy()->subDay()->toDateString(); } else { $dateKey = $time->toDateString(); } if (!isset($attendanceCache[$employeeKey][$dateKey])) { $attendanceCache[$employeeKey][$dateKey] = [ 'punches' => [], 'attendance_id' => null // will try to find matching attendance ]; } $attendanceCache[$employeeKey][$dateKey]['punches'][] = $time; } // 2. Map existing Attendances to dates to get IDs for the popup link foreach ($employee->attendances as $att) { if ($att->attendance_date) { $dKey = $att->attendance_date->toDateString(); if (!isset($attendanceCache[$employeeKey][$dKey])) { $attendanceCache[$employeeKey][$dKey] = [ 'punches' => [], 'attendance_id' => $att->id ]; } else { $attendanceCache[$employeeKey][$dKey]['attendance_id'] = $att->id; } } } } // Retrieve for current cell $dayData = $attendanceCache[$employeeKey][$currentDate] ?? null; $displayCheckIn = null; $displayCheckOut = null; $attendanceId = $dayData['attendance_id'] ?? null; if ($dayData && !empty($dayData['punches'])) { $sortedPunches = collect($dayData['punches'])->sort(); $displayCheckIn = $sortedPunches->first(); $displayCheckOut = $sortedPunches->count() > 1 ? $sortedPunches->last() : null; // If only one punch, it's check-in. Check-out stays null. } else { // Fallback to strict attendance object dates if no punches found but attendance exists // (Already handled by step 2 above, if punches array is empty) // But if we want to trust the attendance object's checks: if ($dayData && $attendanceId) { // We have attendance ID but no punches found in that bucket. // This might happen if punches are not synced or deleted. // Ideally we should have loaded attendance times earlier if we wanted this fallback. // For now, let's rely on punches as primary source of truth for 11AM logic. } } @endphp @endfor @endforeach @endif
{{ __('Employee') }}{{ $day }}
({{ $dayName }})
@php $img = !empty($employee->avatar) ? asset('storage/users/'.$employee->avatar): asset('images/user.jpg'); $link = route('employees.show', ['employee' => Crypt::encrypt($employee->id)]); $departmentName = $employee->employeeDetail && $employee->employeeDetail->department ? $employee->employeeDetail->department->name : ''; @endphp
{!! \Spatie\Menu\Laravel\Html::userAvatar($employee->fullname, $img, $link) !!} @if($departmentName)
{{ $departmentName }}
@endif
@if ($displayCheckIn) {{-- We need an ID for the link. If we have derived formatted times but no DB attendance record yet, we can't show details --}} {{-- However, usually an attendance record exists if punches exist. If not, link might be broken or we leave it. --}} @if ($attendanceId) @else @endif
{{ $displayCheckIn->format('h:i A') }} @php // Lateness Calculation with Grace Period Color Coding $startTimeStr = $employee->employeeDetail ? $employee->employeeDetail->start_time : null; $lateText = ''; $lateColorClass = 'text-danger'; // Default red $gracePeriodMinutes = 15; if ($startTimeStr) { // Parse start time (it's usually H:i:s from DB) // We need to combine it with the current date of the punch to compare apples to apples $scheduleStartTime = \Carbon\Carbon::parse($displayCheckIn->format('Y-m-d') . ' ' . $startTimeStr); if ($displayCheckIn->gt($scheduleStartTime)) { $diffInMinutes = $scheduleStartTime->diffInMinutes($displayCheckIn); if ($diffInMinutes > 0) { $hours = intdiv($diffInMinutes, 60); $minutes = $diffInMinutes % 60; // Set color based on grace period: Red only for 16 minutes or more if ($diffInMinutes >= 16) { $lateColorClass = 'text-danger'; // Red for 16+ minutes late } else { $lateColorClass = 'text-success'; // Green for 1-15 minutes late } if ($hours > 0) { if ($minutes > 0) { $lateText = sprintf('%d hr %d min late', $hours, $minutes); } else { $lateText = sprintf('%d hr late', $hours); } } else { $lateText = sprintf('%d min late', $minutes); } } } } @endphp @if ($lateText)
{{ $lateText }}
@endif
@if ($displayCheckOut) @php $checkoutNextDay = $displayCheckOut->gt($displayCheckIn->copy()->endOfDay()); @endphp
{{ $displayCheckOut->format('h:i A') }} @if ($checkoutNextDay) next day @endif
@endif
@else - @endif
{{-- Edit Time Modal - Placed here to avoid nested modal issues --}} @endsection @push('page-scripts') @endpush