Changes to holidays and the overlap between this year and the previous year

This commit is contained in:
2026-09-23 12:20:39 +03:30
parent 5e306564b2
commit 0fd5867701
15 changed files with 653 additions and 62 deletions
+20 -4
View File
@@ -3,7 +3,10 @@
use Morilog\Jalali\Jalalian;
use Carbon\Carbon;
$previousMonthName = Jalalian::now()->subMonths(1)->format('%B');
$previous = Jalalian::now()->subMonths(1);
$previousMonthKey = sprintf('%04d-%02d', $previous->getYear(), $previous->getMonth());
$previousMonthName = $previous->format('%B');
$currentJalaliYear = Jalalian::now()->getYear();
$employeeTotals = [];
@@ -267,7 +270,7 @@ foreach ($data as $employee) {
<h2>{{ $employee->name }}</h2>
<div class="employee-stat">
<span>کارکرد ماه قبل ({{ $previousMonthName }}):</span>
<strong>{{ $employeeTotals[$employee->id][$previousMonthName] ?? '00:00' }}</strong>
<strong>{{ $employeeTotals[$employee->id][$previousMonthKey] ?? '00:00' }}</strong>
</div>
</div>
@@ -282,10 +285,23 @@ foreach ($data as $employee) {
@else
<div class="month-tabs">
@foreach(array_keys($recordsGrouped) as $month)
@php
$monthLabel = $month;
if (preg_match('/^(\d{4})-(\d{2})$/', (string) $month, $mm)) {
try {
$jj = new Jalalian((int) $mm[1], (int) $mm[2], 1);
$monthLabel = ((int) $mm[1] === (int) $currentJalaliYear)
? $jj->format('%B')
: $jj->format('%B') . ' ' . $mm[1];
} catch (\Throwable $e) {
$monthLabel = $month;
}
}
@endphp
<button type="button"
class="month-tab tablinks-{{ $employee->id }}"
onclick="openMonth(event, 'month_{{ $employee->id }}_{{ $month }}', '{{ $employee->id }}')">
{{ $month }}
{{ $monthLabel }}
</button>
@endforeach
</div>
@@ -307,7 +323,7 @@ foreach ($data as $employee) {
<tbody>
@foreach($records as $row)
<tr>
<td>{{ \App\Support\JalaliHelper::format($row->entered_at, 'm/d') }}</td>
<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>
+33
View File
@@ -947,6 +947,39 @@
display: none;
}
.ui-month-more {
position: relative;
flex-shrink: 0;
}
.ui-month-more-menu {
position: absolute;
top: calc(100% + 0.35rem);
left: 0;
z-index: 40;
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.12);
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.ui-month-more-menu.hidden {
display: none;
}
.ui-month-more-item {
width: 100%;
text-align: right;
justify-content: flex-start;
}
.ui-month-tab,
.tablinks {
flex-shrink: 0;
+115 -24
View File
@@ -155,18 +155,59 @@ if (!empty($user->tasks)) {
<div class="w-[350px]">
@if(isset($_COOKIE['user_identity']))
{{-- قسمت پردازش داده‌ها: گروه‌بندی لیست حضور و غیاب بر اساس نام ماه شمسی --}}
{{-- قسمت پردازش داده‌ها: گروه‌بندی لیست حضور و غیاب بر اساس ماه+سال شمسی --}}
@php
// بررسی وجود داده
$attendances = $user->attendances ?? collect();
$currentJalaliYear = \Morilog\Jalali\Jalalian::now()->getYear();
// گروه‌بندی داده‌ها بر اساس نام ماه شمسی
$groupedData = $attendances
->filter(fn ($item) => !empty($item->entered_at))
->groupBy(function ($item) {
$month = \App\Support\JalaliHelper::format($item->entered_at, '%B', '');
return $month !== '' ? $month : 'نامعتبر';
});
$j = \App\Support\JalaliHelper::fromDateTime($item->entered_at);
if (!$j) {
return 'invalid';
}
return sprintf('%04d-%02d', $j->getYear(), $j->getMonth());
})
->reject(fn ($_, $key) => $key === 'invalid')
->sortKeysDesc();
$monthLabels = [];
foreach ($groupedData->keys() as $monthKey) {
[$y, $m] = array_map('intval', explode('-', $monthKey));
try {
$j = new \Morilog\Jalali\Jalalian($y, $m, 1);
$name = $j->format('%B');
$monthLabels[$monthKey] = ((int) $y === (int) $currentJalaliYear)
? $name
: $name . ' ' . $y;
} catch (\Throwable $e) {
$monthLabels[$monthKey] = $monthKey;
}
}
$recentMonthKeys = $groupedData->keys()->take(3);
$olderMonthKeys = $groupedData->keys()->slice(3);
$attendanceDates = $attendances
->map(fn ($a) => $a->entered_at ? \Carbon\Carbon::parse($a->entered_at)->toDateString() : null)
->filter()
->unique()
->values();
$holidayDates = collect();
try {
$holidayDates = $attendanceDates->isEmpty()
? collect()
: \App\Models\Holiday::query()
->whereIn('date', $attendanceDates)
->pluck('date')
->map(fn ($d) => \Carbon\Carbon::parse($d)->toDateString())
->flip();
} catch (\Throwable $e) {
$holidayDates = collect();
}
@endphp
<div class="container inline-flex flex-col items-center justify-center">
@@ -175,18 +216,36 @@ if (!empty($user->tasks)) {
<h1>اطلاعاتی موجود نمیباشد</h1>
</div>
@else
{{-- دکمه‌های تب (نام ماه‌ها) --}}
{{-- سه ماه اخیر + منوی بقیه --}}
<div class="ui-month-tabs">
@foreach($groupedData->keys() as $month)
@foreach($recentMonthKeys as $monthKey)
<button type="button" class="tablinks"
onclick="openCity(event, '{{$month}}')">
{{$month}}
data-month-id="m-{{ $monthKey }}"
onclick="openCity(event, 'm-{{ $monthKey }}')">
{{ $monthLabels[$monthKey] ?? $monthKey }}
</button>
@endforeach
@if($olderMonthKeys->isNotEmpty())
<div class="ui-month-more">
<button type="button" class="tablinks ui-month-more-btn"
onclick="toggleMonthMore(event)"
title="ماه‌های دیگر">⋯</button>
<div id="month-more-menu" class="ui-month-more-menu hidden">
@foreach($olderMonthKeys as $monthKey)
<button type="button" class="tablinks ui-month-more-item"
data-month-id="m-{{ $monthKey }}"
onclick="openCity(event, 'm-{{ $monthKey }}'); closeMonthMore();">
{{ $monthLabels[$monthKey] ?? $monthKey }}
</button>
@endforeach
</div>
</div>
@endif
</div>
@foreach($groupedData as $month => $records)
<div id="{{$month}}" class="tabcontent ui-attendance-card">
@foreach($groupedData as $monthKey => $records)
<div id="m-{{ $monthKey }}" class="tabcontent ui-attendance-card">
<table class="ui-attendance-table">
<thead>
<tr>
@@ -205,11 +264,11 @@ if (!empty($user->tasks)) {
@php
$monthLateMinutes = 0;
$monthDailyFine = 0;
$monthKey = null;
$monthKeyCarbon = null;
$firstEntered = $records->first()?->entered_at;
if ($firstEntered && \App\Support\JalaliHelper::fromDateTime($firstEntered)) {
$monthKey = \Carbon\Carbon::parse($firstEntered);
[$mStart, $mEnd] = app(\App\Services\LatePenaltyService::class)->jalaliMonthBounds($monthKey);
$monthKeyCarbon = \Carbon\Carbon::parse($firstEntered);
[$mStart, $mEnd] = app(\App\Services\LatePenaltyService::class)->jalaliMonthBounds($monthKeyCarbon);
$monthWeeklyFine = (int) \App\Models\LatePeriodFine::query()
->where('user_identity', $user->user_name)
->where('period_type', 'weekly')
@@ -223,6 +282,11 @@ if (!empty($user->tasks)) {
@php
$monthLateMinutes += (int) ($item->late_minutes ?? 0);
$monthDailyFine += (int) ($item->daily_fine_amount ?? 0);
$enteredCarbon = $item->entered_at ? \Carbon\Carbon::parse($item->entered_at) : null;
$isNonWorkDay = $enteredCarbon && (
$enteredCarbon->isFriday()
|| isset($holidayDates[$enteredCarbon->toDateString()])
);
@endphp
<tr class="hover:bg-cyan-500 cursor-pointer transition duration-150"
style="border-bottom: 1px solid #ddd; background-color: {{$item->requested ? '#bfdbfe' : '#ffffff' }}"
@@ -237,9 +301,12 @@ if (!empty($user->tasks)) {
{{-- تاریخ --}}
<td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{"#e6e6e6" }};">
{{ \App\Support\JalaliHelper::format($item->entered_at, 'm/d') }}
{{ \App\Support\JalaliHelper::format($item->entered_at, 'Y/m/d') }}
<span class="text-xs text-gray-500 block">
{{ \App\Support\JalaliHelper::format($item->entered_at, '%A', '') }}
@if($isNonWorkDay)
· اضافه‌کاری
@endif
</span>
</td>
@@ -260,7 +327,9 @@ if (!empty($user->tasks)) {
{{-- تأخیر --}}
<td style="border-right: 1px solid #ddd; padding: 4px; {{ ($item->daily_fine_minutes ?? 0) > 0 ? 'color:#dc2626;font-weight:600;' : (($item->late_minutes ?? 0) > 0 ? 'color:#d97706;' : '') }}">
@if(($item->late_minutes ?? 0) > 0)
@if($isNonWorkDay)
<span class="text-[10px] text-emerald-600">اضافه‌کاری</span>
@elseif(($item->late_minutes ?? 0) > 0)
{{ $item->late_minutes }}د
@if(($item->daily_fine_amount ?? 0) > 0)
<span class="text-[10px] block">{{ number_format($item->daily_fine_amount) }}ت</span>
@@ -707,30 +776,52 @@ if (!empty($user->tasks)) {
function openCity(evt, Month) {
var i, tabcontent, tablinks;
// مخفی کردن تمام محتواها
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// غیرفعال کردن تمام تب‌ها
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active bg-gray-300 font-bold", "");
// اضافه کردن استایل دیفالت اگر کلاس اکتیو حذف شد
if (!tablinks[i].className.includes("active")) {
tablinks[i].style.backgroundColor = "";
}
}
// نمایش تب انتخاب شده
document.getElementById(Month).style.display = "block";
evt.currentTarget.className += " active bg-gray-300 font-bold";
const panel = document.getElementById(Month);
if (panel) {
panel.style.display = "block";
}
if (evt && evt.currentTarget) {
evt.currentTarget.className += " active bg-gray-300 font-bold";
}
}
function toggleMonthMore(evt) {
if (evt) {
evt.stopPropagation();
}
const menu = document.getElementById('month-more-menu');
if (menu) {
menu.classList.toggle('hidden');
}
}
function closeMonthMore() {
const menu = document.getElementById('month-more-menu');
if (menu) {
menu.classList.add('hidden');
}
}
document.addEventListener('click', function () {
closeMonthMore();
});
// باز کردن اولین تب به صورت خودکار هنگام لود صفحه
document.addEventListener("DOMContentLoaded", function () {
let firstTab = document.querySelector(".tablinks");
let firstTab = document.querySelector(".ui-month-tabs > .tablinks");
if (firstTab) {
firstTab.click();
}
+58 -13
View File
@@ -4,8 +4,10 @@ use Morilog\Jalali\Jalalian;
use Carbon\Carbon;
$monthlyTotals = [];
$currentJalaliYear = Jalalian::now()->getYear();
$monthLabels = [];
foreach ($data as $month => $attendances) {
foreach ($data as $monthKey => $attendances) {
$tsec = 0;
foreach ($attendances as $attendance) {
@@ -18,9 +20,27 @@ foreach ($data as $month => $attendances) {
$hours = floor($tsec / 3600);
$minutes = floor(($tsec % 3600) / 60);
$monthlyTotals[$month] = sprintf('%02d:%02d', $hours, $minutes);
$monthlyTotals[$monthKey] = sprintf('%02d:%02d', $hours, $minutes);
if (preg_match('/^(\d{4})-(\d{2})$/', (string) $monthKey, $m)) {
try {
$j = new Jalalian((int) $m[1], (int) $m[2], 1);
$name = $j->format('%B');
$monthLabels[$monthKey] = ((int) $m[1] === (int) $currentJalaliYear)
? $name
: $name . ' ' . $m[1];
} catch (\Throwable $e) {
$monthLabels[$monthKey] = $monthKey;
}
} else {
$monthLabels[$monthKey] = $monthKey;
}
}
$monthKeys = array_keys($data);
$recentMonthKeys = array_slice($monthKeys, 0, 3);
$olderMonthKeys = array_slice($monthKeys, 3);
?>
@extends('app')
@@ -32,13 +52,27 @@ foreach ($data as $month => $attendances) {
</div>
@else
<div class="ui-month-tabs" style="margin-top: 5rem;">
@foreach(array_keys($data) as $month)
<button type="button" class="tablinks" onclick="openCity(event, '{{ $month }}')">{{ $month }}</button>
@foreach($recentMonthKeys as $monthKey)
<button type="button" class="tablinks"
onclick="openCity(event, 'm-{{ $monthKey }}')">{{ $monthLabels[$monthKey] ?? $monthKey }}</button>
@endforeach
@if(!empty($olderMonthKeys))
<div class="ui-month-more">
<button type="button" class="tablinks ui-month-more-btn" onclick="toggleMonthMore(event)">⋯</button>
<div id="month-more-menu" class="ui-month-more-menu hidden">
@foreach($olderMonthKeys as $monthKey)
<button type="button" class="tablinks ui-month-more-item"
onclick="openCity(event, 'm-{{ $monthKey }}'); closeMonthMore();">
{{ $monthLabels[$monthKey] ?? $monthKey }}
</button>
@endforeach
</div>
</div>
@endif
</div>
@foreach($data as $month => $records)
<div id="{{ $month }}" class="tabcontent ui-attendance-card">
@foreach($data as $monthKey => $records)
<div id="m-{{ $monthKey }}" class="tabcontent ui-attendance-card">
<table class="ui-attendance-table">
<thead>
<tr>
@@ -52,7 +86,7 @@ foreach ($data as $month => $attendances) {
<tbody>
@foreach($records as $row)
<tr>
<td>{{ \App\Support\JalaliHelper::format($row->entered_at, 'm/d') }}</td>
<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>
@@ -61,7 +95,7 @@ foreach ($data as $month => $attendances) {
@endforeach
<tr>
<td colspan="5" class="table-summary">
مجموع کارکرد این ماه: {{ $monthlyTotals[$month] }}
مجموع کارکرد این ماه: {{ $monthlyTotals[$monthKey] }}
</td>
</tr>
</tbody>
@@ -78,13 +112,24 @@ foreach ($data as $month => $attendances) {
const panel = document.getElementById(Month);
if (panel) panel.style.display = 'block';
if (evt.currentTarget) evt.currentTarget.classList.add('active');
if (evt && evt.currentTarget) evt.currentTarget.classList.add('active');
}
document.addEventListener('DOMContentLoaded', function () {
const firstTab = document.querySelector('.tablinks');
if (firstTab) firstTab.click();
function toggleMonthMore(evt) {
if (evt) evt.stopPropagation();
const menu = document.getElementById('month-more-menu');
if (menu) menu.classList.toggle('hidden');
}
function closeMonthMore() {
const menu = document.getElementById('month-more-menu');
if (menu) menu.classList.add('hidden');
}
document.addEventListener('click', closeMonthMore);
document.addEventListener('DOMContentLoaded', () => {
const first = document.querySelector('.ui-month-tabs > .tablinks');
if (first) first.click();
});
</script>
@endsection