Files

479 lines
18 KiB
PHP
Executable File

<?php
use Morilog\Jalali\Jalalian;
use Carbon\Carbon;
$previous = Jalalian::now()->subMonths(1);
$previousMonthKey = sprintf('%04d-%02d', $previous->getYear(), $previous->getMonth());
$previousMonthName = $previous->format('%B');
$currentJalaliYear = Jalalian::now()->getYear();
$employeeTotals = [];
foreach ($data as $employee) {
$recordsGrouped = is_array($employee->records) ? $employee->records : (array)$employee->records;
$monthlyTotals = [];
foreach ($recordsGrouped as $month => $attendances) {
$tsec = 0;
foreach ($attendances as $attendance) {
if (!empty($attendance->duration)) {
[$h, $m, $s] = explode(':', $attendance->duration);
$tsec += (int)$h * 3600 + (int)$m * 60 + (int)$s;
}
}
$hours = floor($tsec / 3600);
$minutes = floor(($tsec % 3600) / 60);
$monthlyTotals[$month] = sprintf('%02d:%02d', $hours, $minutes);
}
$employeeTotals[$employee->id] = $monthlyTotals;
}
?>
@extends('app')
@section('style')
<style>
.admin-show-page,
.ui-page {
width: 100%;
max-width: 420px;
margin: 0 auto;
padding: 0 0.5rem 1.5rem;
}
.admin-show-empty,
.ui-empty {
margin-top: 5rem;
text-align: center;
color: var(--text-muted);
font-size: 1.1rem;
font-weight: 700;
}
.employee-picker {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.45rem;
margin: 0.75rem 0 1rem;
padding: 0.75rem;
background: var(--surface-glass);
border: 1px solid var(--border-glass);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-soft);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.emp-btn {
padding: 0.5rem 0.85rem;
border-radius: 9999px;
font-size: 0.82rem;
font-weight: 700;
border: 1px solid var(--border-input);
background: var(--surface-input);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.emp-btn:hover {
color: var(--text-primary);
background: var(--surface-tab-hover);
border-color: color-mix(in srgb, var(--primary-color) 35%, var(--border-input));
}
.emp-btn-active {
color: #fff;
background: var(--primary-color);
border-color: color-mix(in srgb, var(--primary-color) 80%, white);
box-shadow: 0 6px 18px color-mix(in srgb, var(--primary-color) 35%, transparent);
}
.employee-panel {
display: none;
flex-direction: column;
align-items: center;
width: 100%;
}
.employee-panel.is-visible {
display: flex;
}
.employee-header {
width: 100%;
max-width: 350px;
display: flex;
flex-direction: column;
gap: 0.65rem;
margin-bottom: 0.85rem;
}
.employee-header h2 {
margin: 0;
font-size: 1.15rem;
font-weight: 800;
color: var(--text-primary);
}
.employee-stat {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 0.35rem;
padding: 0.55rem 0.85rem;
border-radius: var(--radius-lg);
background: color-mix(in srgb, var(--primary-color) 12%, var(--surface-card));
border: 1px solid color-mix(in srgb, var(--primary-color) 28%, var(--border-subtle));
color: var(--text-secondary);
font-size: 0.78rem;
}
.employee-stat strong {
font-size: 0.95rem;
font-weight: 800;
color: color-mix(in srgb, var(--primary-color) 70%, var(--text-primary));
font-variant-numeric: tabular-nums;
}
.month-tabs {
width: 100%;
max-width: 350px;
display: flex;
gap: 0.35rem;
overflow: visible;
position: relative;
z-index: 50;
padding: 0.45rem;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
background: var(--surface-tabs);
border: 1px solid var(--border-glass);
border-bottom: none;
scrollbar-width: none;
}
.month-tabs::-webkit-scrollbar {
display: none;
}
.ui-month-more {
position: relative;
flex-shrink: 0;
z-index: 60;
}
.ui-month-more-menu {
position: absolute;
top: calc(100% + 0.35rem);
right: 0;
left: auto;
z-index: 100;
min-width: 9rem;
max-height: 16rem;
overflow-y: auto;
padding: 0.35rem;
border-radius: 0.75rem;
background: var(--surface-panel, #fff);
border: 1px solid var(--border-glass, #e5e7eb);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.ui-month-more-menu.hidden {
display: none !important;
}
.ui-month-more-item {
width: 100%;
text-align: right;
}
.month-tab {
flex-shrink: 0;
padding: 0.5rem 0.85rem;
border: none;
border-radius: 0.75rem;
background: transparent;
color: var(--text-muted);
font-size: 0.82rem;
font-weight: 700;
cursor: pointer;
transition: all 0.2s ease;
}
.month-tab:hover {
color: var(--text-primary);
background: var(--surface-tab-hover);
}
.month-tab-active {
color: color-mix(in srgb, var(--primary-color) 78%, var(--text-primary));
background: var(--surface-tab-active);
box-shadow: var(--shadow-soft);
}
.attendance-card {
width: 100%;
max-width: 350px;
background: var(--surface-glass);
border: 1px solid var(--border-glass);
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
box-shadow: var(--shadow-soft);
overflow: hidden;
position: relative;
z-index: 1;
}
.attendance-table {
width: 100%;
border-collapse: collapse;
text-align: center;
}
.attendance-table thead th {
padding: 0.65rem 0.35rem;
font-size: 0.72rem;
font-weight: 700;
color: var(--text-muted);
background: var(--surface-tabs);
border-bottom: 1px solid var(--border-subtle);
}
.attendance-table thead th:not(:last-child) {
border-left: 1px solid var(--border-subtle);
}
.attendance-table tbody td {
padding: 0.5rem 0.25rem;
font-size: 0.82rem;
color: var(--text-primary);
border-bottom: 1px solid var(--border-subtle);
font-variant-numeric: tabular-nums;
}
.attendance-table tbody td:not(:last-child) {
border-left: 1px solid var(--border-subtle);
}
.attendance-table tbody tr:hover td {
background: var(--surface-tab-hover);
}
.attendance-table tfoot td {
padding: 0.75rem 0.5rem;
font-size: 0.85rem;
font-weight: 800;
color: var(--text-primary);
background: color-mix(in srgb, var(--primary-color) 10%, var(--surface-card));
border-top: 2px solid var(--border-subtle);
}
.panel-empty {
margin: 2.5rem 0;
text-align: center;
color: var(--text-muted);
font-weight: 700;
}
</style>
@endsection
@section('content')
<div class="admin-show-page">
@if(empty($data) || count($data) == 0)
<div class="admin-show-empty">
<h1>اطلاعاتی موجود نمیباشد</h1>
</div>
@else
<div class="employee-picker">
@foreach($data as $employee)
<button type="button"
class="emp-btn"
data-emp-id="{{ $employee->id }}"
onclick="openEmployee(event, 'emp_{{ $employee->id }}', '{{ $employee->id }}')">
{{ $employee->name }}
</button>
@endforeach
</div>
@foreach($data as $employee)
<div id="emp_{{ $employee->id }}" class="employee-panel">
<div class="employee-header">
<h2>{{ $employee->name }}</h2>
<div class="employee-stat">
<span>کارکرد ماه قبل ({{ $previousMonthName }}):</span>
<strong>{{ $employeeTotals[$employee->id][$previousMonthKey] ?? '00:00' }}</strong>
</div>
</div>
@php
$recordsGrouped = is_array($employee->records) ? $employee->records : (array)$employee->records;
@endphp
@if(empty($recordsGrouped))
<div class="panel-empty">
رکورد ترددی برای این شخص ثبت نشده است.
</div>
@else
@php
$allMonthKeys = array_keys($recordsGrouped);
$recentMonthKeys = array_slice($allMonthKeys, 0, 3);
$olderMonthKeys = array_slice($allMonthKeys, 3);
$monthLabelOf = function ($month) use ($currentJalaliYear) {
if (preg_match('/^(\d{4})-(\d{2})$/', (string) $month, $mm)) {
try {
$jj = new Jalalian((int) $mm[1], (int) $mm[2], 1);
return ((int) $mm[1] === (int) $currentJalaliYear)
? $jj->format('%B')
: $jj->format('%B') . ' ' . $mm[1];
} catch (\Throwable $e) {
return $month;
}
}
return $month;
};
@endphp
<div class="month-tabs">
@foreach($recentMonthKeys as $month)
<button type="button"
class="month-tab tablinks-{{ $employee->id }}"
onclick="openMonth(event, 'month_{{ $employee->id }}_{{ $month }}', '{{ $employee->id }}')">
{{ $monthLabelOf($month) }}
</button>
@endforeach
@if(!empty($olderMonthKeys))
<div class="ui-month-more">
<button type="button"
class="month-tab ui-month-more-btn"
onclick="toggleAdminMonthMore(event, 'month-more-{{ $employee->id }}')"
title="ماه‌های دیگر">⋯</button>
<div id="month-more-{{ $employee->id }}" class="ui-month-more-menu hidden">
@foreach($olderMonthKeys as $month)
<button type="button"
class="month-tab tablinks-{{ $employee->id }} ui-month-more-item"
onclick="openMonth(event, 'month_{{ $employee->id }}_{{ $month }}', '{{ $employee->id }}'); closeAdminMonthMore('month-more-{{ $employee->id }}');">
{{ $monthLabelOf($month) }}
</button>
@endforeach
</div>
</div>
@endif
</div>
@foreach($recordsGrouped as $month => $records)
<div id="month_{{ $employee->id }}_{{ $month }}"
class="attendance-card tabcontent-{{ $employee->id }}"
style="display: none;">
<table class="attendance-table">
<thead>
<tr>
<th>تاریخ</th>
<th>روز</th>
<th>ورود</th>
<th>خروج</th>
<th>مدت</th>
</tr>
</thead>
<tbody>
@foreach($records as $row)
<tr>
<td>{{ \App\Support\JalaliHelper::format($row->entered_at, 'Y/m/d') }}</td>
<td>{{ \App\Support\JalaliHelper::format($row->entered_at, '%A') }}</td>
<td>{{ Carbon::parse($row->entered_at)->format('H:i') ?? '-' }}</td>
<td>{{ $row->exited_at ? Carbon::parse($row->exited_at)->format('H:i') : '-' }}</td>
<td>{{ $row->duration ? Carbon::parse($row->duration)->format('H:i') : '-' }}</td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="5">
مجموع کارکرد این ماه: {{ $employeeTotals[$employee->id][$month] ?? '00:00' }}
</td>
</tr>
</tfoot>
</table>
</div>
@endforeach
@endif
</div>
@endforeach
@endif
</div>
<script>
function openEmployee(evt, empContainerId, empId) {
document.querySelectorAll('.employee-panel').forEach(el => el.classList.remove('is-visible'));
document.querySelectorAll('.emp-btn').forEach(btn => btn.classList.remove('emp-btn-active'));
const panel = document.getElementById(empContainerId);
if (panel) panel.classList.add('is-visible');
if (evt.currentTarget) {
evt.currentTarget.classList.add('emp-btn-active');
}
const firstMonthTab = document.querySelector('#' + empContainerId + ' .tablinks-' + empId);
if (firstMonthTab) {
firstMonthTab.click();
}
}
function openMonth(evt, monthId, empId) {
document.querySelectorAll('.tabcontent-' + empId).forEach(el => {
el.style.display = 'none';
});
document.querySelectorAll('.tablinks-' + empId).forEach(btn => {
btn.classList.remove('month-tab-active');
});
const monthPanel = document.getElementById(monthId);
if (monthPanel) monthPanel.style.display = 'block';
if (evt.currentTarget) {
evt.currentTarget.classList.add('month-tab-active');
}
}
function toggleAdminMonthMore(evt, menuId) {
if (evt) evt.stopPropagation();
document.querySelectorAll('.ui-month-more-menu').forEach(menu => {
if (menu.id !== menuId) menu.classList.add('hidden');
});
const menu = document.getElementById(menuId);
if (menu) menu.classList.toggle('hidden');
}
function closeAdminMonthMore(menuId) {
const menu = document.getElementById(menuId);
if (menu) menu.classList.add('hidden');
}
document.addEventListener('click', function () {
document.querySelectorAll('.ui-month-more-menu').forEach(menu => menu.classList.add('hidden'));
});
document.querySelectorAll('.ui-month-more-menu').forEach(menu => {
menu.addEventListener('click', function (e) {
e.stopPropagation();
});
});
document.addEventListener('DOMContentLoaded', function () {
const firstEmployeeBtn = document.querySelector('.emp-btn');
if (firstEmployeeBtn) {
firstEmployeeBtn.click();
}
});
</script>
@endsection