Improve tasks UI, dark theme, performance fixes, and admin pages polish
This commit is contained in:
@@ -1,63 +1,9 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Attendance;
|
||||
use App\Models\AttendanceBreak;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Carbon\Carbon;
|
||||
|
||||
//dd($reqs);
|
||||
$now = Jalalian::fromCarbon(Carbon::now());
|
||||
foreach ($users as $user) {
|
||||
$totaltime = 0;
|
||||
$tsec = 0;
|
||||
foreach ($user->attendances as $att) {
|
||||
$date = $att->entered_at != null ? Jalalian::fromCarbon(Carbon::parse($att->entered_at)) : null;
|
||||
$att->date = $date != null ? $date->format('Y/m/d') : null;
|
||||
if ($att->duration != null && $date != null && $now->getMonth() == $date->getMonth()) {
|
||||
[$h, $m, $s] = explode(':', $att->duration);
|
||||
$tsec += $h * 3600 + $m * 60 + $s;
|
||||
}
|
||||
}
|
||||
$hours = floor($tsec / 3600);
|
||||
$minutes = floor(($tsec % 3600) / 60);
|
||||
$seconds = $tsec % 60;
|
||||
|
||||
$totalTime = sprintf('%02d:%02d', $hours, $minutes);
|
||||
$user->totaltime = $totalTime;
|
||||
|
||||
}
|
||||
//dd($users);
|
||||
$now = now()->format('Y-m-d');
|
||||
foreach ($users as $u) {
|
||||
|
||||
$us = $u->attendances()
|
||||
->whereNull('exited_at')
|
||||
->latest('entered_at')
|
||||
->first();
|
||||
// dump($us);
|
||||
if ($us) {
|
||||
$us->entered_at = Carbon::parse($us->entered_at)->format('Y-m-d');
|
||||
if ($us->entered_at == $now)
|
||||
$us = true;
|
||||
else
|
||||
$us = false;
|
||||
$u->working = $us ? 1 : 0;
|
||||
} else
|
||||
$u->working = 0;
|
||||
|
||||
foreach ($u->attendances as $u_att) {
|
||||
$break = AttendanceBreak::wherenull('break_end')->where('attendance_id', $u_att->id)->exists();
|
||||
if ($break)
|
||||
$u->working = 2;
|
||||
}
|
||||
|
||||
}
|
||||
//dd($users);
|
||||
$users = $users->sortByDesc('working');
|
||||
//$users = json_decode(json: $users);
|
||||
// $users = collect($users);
|
||||
$usersChunked = $users->chunk(3);
|
||||
//dd($users);
|
||||
?>
|
||||
|
||||
|
||||
@@ -66,43 +12,35 @@ $usersChunked = $users->chunk(3);
|
||||
<div class="container w-full flex flex-col items-center">
|
||||
|
||||
@if(!collect($reqs)->pluck('attendances')->flatten()->isEmpty())
|
||||
<div class="mt-10 bg-white rounded-lg shadow-md max-w-[350px] flex flex-col items-center">
|
||||
<p style="min-font-size: 30; text-align: center; margin-bottom: 10px">درخواست ها</p>
|
||||
{{-- {{dd($reqs)}}--}}
|
||||
<div class="ui-requests-card flex flex-col items-center">
|
||||
<p>درخواست ها</p>
|
||||
|
||||
@foreach ($reqs as $u)
|
||||
@if(!empty($u['attendances']))
|
||||
|
||||
<button type="button"
|
||||
class="w-[150px] p-3 font-bold text-white bg-blue-500 hover:bg-blue-700 rounded transition duration-150 text-center "
|
||||
onclick="togglerequests('attendance-{{ $loop->index }}')"
|
||||
style="margin: 10px 0; text-align: center; background-color: #00bcd4">
|
||||
class="ui-chip-btn"
|
||||
onclick="togglerequests('attendance-{{ $loop->index }}')">
|
||||
{{ $u['name'] }}
|
||||
</button>
|
||||
{{-- @else--}}
|
||||
{{-- <p class="text-stone-400" style="text-align: center">درخواستی ثبت نشده</p>--}}
|
||||
{{-- @break--}}
|
||||
@endif
|
||||
<div id="attendance-{{ $loop->index }}" class="hidden-content pt-4">
|
||||
<table style="max-width: 350px; margin: auto; text-align: center" class="w-[350px] table-auto">
|
||||
<div id="attendance-{{ $loop->index }}" class="hidden-content pt-4 w-full">
|
||||
<div class="ui-table-wrap">
|
||||
<table class="w-full table-auto">
|
||||
|
||||
<thead>
|
||||
<tr class="text-sm" style="background-color: #00a3ba; color: white; text-align: center;">
|
||||
|
||||
<th class="px-2 py-2 border w-1/6">درخواست</th>
|
||||
<th class="px-2 py-2 border w-1/6">تاریخ</th>
|
||||
<th class="px-2 py-2 border w-1/6">ورود</th>
|
||||
<th class="px-2 py-2 border w-1/6">خروج</th>
|
||||
{{-- @if($userType == 'admin')--}}
|
||||
<th class="px-2 py-2 border w-1/6">تعیین وضعیت</th>
|
||||
{{-- @endif--}}
|
||||
<tr>
|
||||
<th class="px-2 py-2 w-1/6">درخواست</th>
|
||||
<th class="px-2 py-2 w-1/6">تاریخ</th>
|
||||
<th class="px-2 py-2 w-1/6">ورود</th>
|
||||
<th class="px-2 py-2 w-1/6">خروج</th>
|
||||
<th class="px-2 py-2 w-1/6">تعیین وضعیت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{-- {{dd($u['attendances'])}}--}}
|
||||
@forelse($u['attendances'] as $rec)
|
||||
<tr style="background-color: {{$rec->id=='new' ? 'lightblue' : 'white'}}">
|
||||
<tr class="{{ $rec->id=='new' ? 'is-new' : '' }}">
|
||||
{{--{{dd($rec)}}--}}
|
||||
<td class="px-4 py-2 border">{{ match ($rec->function) {
|
||||
'edit' => "تغییر ساعت",
|
||||
@@ -138,8 +76,7 @@ $usersChunked = $users->chunk(3);
|
||||
<input type="hidden" name="update" id="update" value="approve">
|
||||
<input type="hidden" name="data" id="data"
|
||||
value="{{json_encode($rec)}}">
|
||||
<button class="bg-white rounded w-full"
|
||||
style="background-color: #81ff65; margin-bottom: 5px">
|
||||
<button type="submit" class="ui-btn-approve">
|
||||
تایید
|
||||
</button>
|
||||
</form>
|
||||
@@ -148,8 +85,7 @@ $usersChunked = $users->chunk(3);
|
||||
<input type="hidden" name="update" id="update" value="reject">
|
||||
<input type="hidden" name="data" id="data"
|
||||
value="{{json_encode($rec)}}">
|
||||
<button class="bg-white rounded w-full"
|
||||
style="background-color: #ff6565">
|
||||
<button type="submit" class="ui-btn-reject">
|
||||
رد
|
||||
</button>
|
||||
</form>
|
||||
@@ -163,7 +99,8 @@ $usersChunked = $users->chunk(3);
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div> {{-- پایان hidden-content --}}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
{{-- @if(collect($reqs)->pluck('attendances')->flatten()->isEmpty())--}}
|
||||
{{-- <p class="text-stone-400" style="text-align: center">درخواستی ثبت نشده</p>--}}
|
||||
@@ -175,38 +112,29 @@ $usersChunked = $users->chunk(3);
|
||||
<div class="grid grid-cols-2 grid-rows-none ">
|
||||
@foreach ($usersChunked as $chunk)
|
||||
@foreach ($chunk as $u)
|
||||
<div class="form m-3 justify-evenly rounded-lg shadow-md "
|
||||
style="align-items: center; text-align: center; border: 2px solid
|
||||
{{ $u->active_task ? '#00ff15' : ($u->working == 1 ? '#16c8ce' : ($u->working == 2 ? 'orange' : 'white' ))}}; padding: 10px; ">
|
||||
@php
|
||||
@endphp
|
||||
<a href="/admin/{{ $u->user_name }}"
|
||||
class=" rounded-xl"
|
||||
style="text-decoration: none; color: inherit;">
|
||||
<p style="font-weight: bold ; color:#333;">
|
||||
{{ $u->name ?? $u->user_name }}
|
||||
</p>
|
||||
@php
|
||||
$cardClass = 'ui-user-card m-3';
|
||||
if ($u->active_task) {
|
||||
$cardClass .= ' is-active';
|
||||
} elseif ($u->working == 1) {
|
||||
$cardClass .= ' is-working';
|
||||
} elseif ($u->working == 2) {
|
||||
$cardClass .= ' is-break';
|
||||
}
|
||||
@endphp
|
||||
<div class="{{ $cardClass }}">
|
||||
<a href="/admin/{{ $u->user_name }}">
|
||||
<p class="ui-user-name">{{ $u->name ?? $u->user_name }}</p>
|
||||
@if($u->active_task)
|
||||
<p>وظیفه: {{ $u->active_task }}</p>
|
||||
@endif
|
||||
<p>
|
||||
کارکرد ماه جاری:
|
||||
{{ $u->totaltime }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
تعداد درخواست ها:
|
||||
{{ $u->req_counts }}
|
||||
</p>
|
||||
|
||||
<p>کارکرد ماه جاری: {{ $u->totaltime }}</p>
|
||||
<p>تعداد درخواست ها: {{ $u->req_counts }}</p>
|
||||
</a>
|
||||
@if($userType == 'admin')
|
||||
|
||||
<button type="button"
|
||||
class="rounded-lg bg-cyan-500 hover:bg-cyan-600"
|
||||
onclick="window.location.href='{{ url('attendance/profile').'?username='.$u->user_name }}'"
|
||||
style=" border: none; color: white; padding: 5px 10px; text-align: center; text-decoration: none;
|
||||
display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; ">
|
||||
class="ui-chip-btn mt-2 !min-w-0 !w-auto !text-sm"
|
||||
onclick="window.location.href='{{ url('attendance/profile').'?username='.$u->user_name }}'">
|
||||
ویرایش
|
||||
</button>
|
||||
@endif
|
||||
@@ -214,10 +142,8 @@ $usersChunked = $users->chunk(3);
|
||||
|
||||
@endforeach
|
||||
@endforeach
|
||||
<a class="form m-3 justify-evenly rounded-lg shadow-md " href="{{route('admin.new')}}"
|
||||
style="align-items: center; text-align: center; ">
|
||||
<i class="fa-regular fa-plus "
|
||||
style="font-size: 80px; width: 90%; height: 90%;"></i>
|
||||
<a class="ui-user-card m-3 flex items-center justify-center min-h-[140px]" href="{{route('admin.new')}}">
|
||||
<i class="fa-regular fa-plus text-5xl text-gray-400"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ SVG;
|
||||
|
||||
@else
|
||||
|
||||
<div class="bg-white flex justify-flex p-4 rounded-xl max-w-[350px] flex-col items-center">
|
||||
<div class="ui-panel flex p-4 rounded-xl max-w-[350px] flex-col items-center">
|
||||
|
||||
<img src="{{$user->picture ?? $gravatarUrl}}" alt="Profile Picture" class="w-16 h-16 rounded-full ">
|
||||
<label>
|
||||
@@ -361,7 +361,7 @@ SVG;
|
||||
|
||||
|
||||
<div class="w-full flex justify-center mt-4" style="">
|
||||
<div class="overflow-hidden border border-gray-300 rounded-[23px] bg-white max-w-[350px]">
|
||||
<div class="overflow-hidden border border-gray-300 rounded-[23px] ui-panel max-w-[350px]">
|
||||
<div class=" border-b border-gray-300 text-center w-full">
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ SVG;
|
||||
<div class="w-[350px]">
|
||||
|
||||
@if(isset($_COOKIE['user_identity']))
|
||||
<table class="min-w-full bg-white shadow-md rounded-lg table-auto">
|
||||
<table class="min-w-full ui-attendance-table table-auto">
|
||||
<thead class=" text-black sticky top-0 z-10"
|
||||
style="background: rgba(244,244,244,0.82);border-bottom: 1px solid;border-color: #e1e1e1 ">
|
||||
<tr>
|
||||
@@ -456,10 +456,10 @@ SVG;
|
||||
|
||||
<!-- ATTENDANCE Modal -->
|
||||
<div id="modal-container"
|
||||
class="fixed inset-0 z-50 hidden bg-black bg-opacity-60 flex items-center justify-center p-4">
|
||||
<div id="modal-content" class=" bg-white p-4 sm:p-6 rounded-lg shadow-lg max-w-sm w-full relative">
|
||||
<h2 id="modal-title" class="text-xl sm:text-2xl font-semibold mb-4 text-center"></h2>
|
||||
<p id="modal-body" class="text-gray-700 mb-4 text-center"></p>
|
||||
class="ui-modal-backdrop centered hidden">
|
||||
<div id="modal-content" class="ui-modal-panel max-w-sm w-full">
|
||||
<h2 id="modal-title" class="ui-title"></h2>
|
||||
<p id="modal-body" class="ui-subtitle"></p>
|
||||
<div class="flex flex-col gap-4" style="align-items: center">
|
||||
|
||||
<div class="flex justify-center gap-2">
|
||||
@@ -559,16 +559,14 @@ SVG;
|
||||
|
||||
{{-- START TASK MODAL --}}
|
||||
<div id="taskModal"
|
||||
class="hidden fixed inset-0 bg-black/40 items-start justify-center z-50 flex backdrop-blur-sm scroll-auto pt-10">
|
||||
<div class="relative bg-white w-[350px] rounded-2xl shadow-lg p-5 text-center">
|
||||
|
||||
<button onclick="closeTaskModal()"
|
||||
class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 transition">
|
||||
class="ui-modal-backdrop hidden">
|
||||
<div class="ui-modal-panel">
|
||||
<button type="button" onclick="closeTaskModal()" class="ui-modal-close" aria-label="بستن">
|
||||
×
|
||||
</button>
|
||||
|
||||
<h2 class="text-lg font-bold mb-3" id="modalTitle">انتخاب وظیفه</h2>
|
||||
<p class="text-gray-600 mb-4" id="modalDesc">یکی از وظایف زیر را انتخاب کنید:</p>
|
||||
<h2 class="ui-title" id="modalTitle">انتخاب وظیفه</h2>
|
||||
<p class="ui-subtitle" id="modalDesc">یکی از وظایف زیر را انتخاب کنید:</p>
|
||||
|
||||
<div class="grid flex-col gap-2 grid-cols-2 pb-3 items-center justify-items-end">
|
||||
@if(!empty($user->tasks))
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
<div class=" flex flex-col items-center justify-center p-4 " dir="rtl"
|
||||
style="padding-top: 50px">
|
||||
|
||||
<div class="bg-white p-6 md:p-8 rounded-2xl shadow-2xl w-[350px] max-w-sm border-t-8 border-[#16c8ce]">
|
||||
|
||||
<div class="ui-card ui-card-body w-[350px] max-w-sm" style="border-top: 4px solid var(--primary-color);">
|
||||
<div class="text-center mb-6">
|
||||
<h1 class="text-2xl font-extrabold text-gray-800 border-b pb-2">کاربر جدید</h1>
|
||||
<h1 class="ui-title border-b border-gray-300 pb-2">کاربر جدید</h1>
|
||||
</div>
|
||||
|
||||
<form id="attendanceForm" action="{{route('admin.new')}}" method="post" class="space-y-4">
|
||||
|
||||
@@ -3,21 +3,17 @@
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Carbon\Carbon;
|
||||
|
||||
// پیدا کردن نام ماه قبل به تاریخ شمسی (مثلا اگر الان اسفند است، خروجی "بهمن" خواهد بود)
|
||||
$previousMonthName = Jalalian::now()->subMonths(1)->format('%B');
|
||||
|
||||
// محاسبه مجموع کارکرد برای هر کارمند به تفکیک ماه
|
||||
$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) {
|
||||
// اگر duration موجود باشه استفاده میکنیم
|
||||
if (!empty($attendance->duration)) {
|
||||
[$h, $m, $s] = explode(':', $attendance->duration);
|
||||
$tsec += (int)$h * 3600 + (int)$m * 60 + (int)$s;
|
||||
@@ -26,29 +22,239 @@ foreach ($data as $employee) {
|
||||
|
||||
$hours = floor($tsec / 3600);
|
||||
$minutes = floor(($tsec % 3600) / 60);
|
||||
|
||||
// ذخیره زمان برای این ماه
|
||||
$monthlyTotals[$month] = sprintf('%02d:%02d', $hours, $minutes);
|
||||
}
|
||||
// ذخیره کل ماهها برای این کارمند
|
||||
$employeeTotals[$employee->id] = $monthlyTotals;
|
||||
}
|
||||
?>
|
||||
|
||||
@extends('app')
|
||||
@section('content')
|
||||
|
||||
<div class="container flex flex-col items-center justify-center mx-auto pb-10">
|
||||
@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-x: auto;
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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="mt-80 text-gray-500 text-xl font-bold italic">
|
||||
<div class="admin-show-empty">
|
||||
<h1>اطلاعاتی موجود نمیباشد</h1>
|
||||
</div>
|
||||
@else
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-2 mt-10 mb-6 max-w-3xl">
|
||||
<div class="employee-picker">
|
||||
@foreach($data as $employee)
|
||||
<button class="emp-btn px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-cyan-500 hover:text-white transition-colors duration-300 font-bold shadow-sm"
|
||||
<button type="button"
|
||||
class="emp-btn"
|
||||
data-emp-id="{{ $employee->id }}"
|
||||
onclick="openEmployee(event, 'emp_{{ $employee->id }}', '{{ $employee->id }}')">
|
||||
{{ $employee->name }}
|
||||
</button>
|
||||
@@ -56,15 +262,13 @@ foreach ($data as $employee) {
|
||||
</div>
|
||||
|
||||
@foreach($data as $employee)
|
||||
<div id="emp_{{ $employee->id }}" class="employee-content hidden flex-col items-center justify-center w-full">
|
||||
|
||||
<div class="flex justify-between items-center mb-4 w-[350px]">
|
||||
<h2 class="text-xl font-bold text-cyan-700">{{ $employee->name }}</h2>
|
||||
|
||||
<span class="text-xs bg-cyan-50 text-cyan-800 px-3 py-1.5 rounded-full border border-cyan-200 shadow-sm flex items-center gap-1">
|
||||
<div id="emp_{{ $employee->id }}" class="employee-panel">
|
||||
<div class="employee-header">
|
||||
<h2>{{ $employee->name }}</h2>
|
||||
<div class="employee-stat">
|
||||
<span>کارکرد ماه قبل ({{ $previousMonthName }}):</span>
|
||||
<strong class="text-sm font-black">{{ $employeeTotals[$employee->id][$previousMonthName] ?? '00:00' }}</strong>
|
||||
</span>
|
||||
<strong>{{ $employeeTotals[$employee->id][$previousMonthName] ?? '00:00' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php
|
||||
@@ -72,13 +276,14 @@ foreach ($data as $employee) {
|
||||
@endphp
|
||||
|
||||
@if(empty($recordsGrouped))
|
||||
<div class="my-10 text-gray-500 text-lg font-bold italic">
|
||||
<div class="panel-empty">
|
||||
رکورد ترددی برای این شخص ثبت نشده است.
|
||||
</div>
|
||||
@else
|
||||
<div class="tab rounded-t-2xl max-w-[350px] mx-auto flex overflow-x-auto whitespace-nowrap hide-scrollbar" style="max-width: 350px">
|
||||
<div class="month-tabs">
|
||||
@foreach(array_keys($recordsGrouped) as $month)
|
||||
<button class="tablinks-{{ $employee->id }} active rounded-t-2xl cursor-pointer px-4 py-2 bg-gray-100 border-b-2 border-transparent hover:bg-gray-200 flex-shrink-0"
|
||||
<button type="button"
|
||||
class="month-tab tablinks-{{ $employee->id }}"
|
||||
onclick="openMonth(event, 'month_{{ $employee->id }}_{{ $month }}', '{{ $employee->id }}')">
|
||||
{{ $month }}
|
||||
</button>
|
||||
@@ -86,43 +291,37 @@ foreach ($data as $employee) {
|
||||
</div>
|
||||
|
||||
@foreach($recordsGrouped as $month => $records)
|
||||
<div id="month_{{ $employee->id }}_{{ $month }}" class="tabcontent-{{ $employee->id }} hidden w-[350px] bg-white rounded-b-xl rounded-tl-2xl cursor-default leading-7 shadow-lg border border-gray-100">
|
||||
<table style="width:100%; border-collapse: collapse; text-align: center">
|
||||
<div id="month_{{ $employee->id }}_{{ $month }}"
|
||||
class="attendance-card tabcontent-{{ $employee->id }}"
|
||||
style="display: none;">
|
||||
<table class="attendance-table">
|
||||
<thead>
|
||||
<tr style="border-bottom: 1px solid #ddd; background-color: #f8f9fa;">
|
||||
<th style="border-right: 1px solid #ddd; padding: 8px 0;">تاریخ</th>
|
||||
<th style="border-right: 1px solid #ddd;">روز</th>
|
||||
<th style="border-right: 1px solid #ddd;">ورود</th>
|
||||
<th style="border-right: 1px solid #ddd;">خروج</th>
|
||||
<th style="border-right: 1px solid #ddd;">مدت</th>
|
||||
<tr>
|
||||
<th>تاریخ</th>
|
||||
<th>روز</th>
|
||||
<th>ورود</th>
|
||||
<th>خروج</th>
|
||||
<th>مدت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($records as $row)
|
||||
<tr class="hover:bg-cyan-100 transition-colors" style="border-bottom: 1px solid #ddd;">
|
||||
<td style="border-right: 1px solid #ddd; padding: 4px 0;">
|
||||
{{ Jalalian::fromCarbon(\Carbon\Carbon::parse($row->entered_at))->format('m/d') ?? '-' }}
|
||||
</td>
|
||||
<td style="border-right: 1px solid #ddd;">
|
||||
{{ Jalalian::fromCarbon(\Carbon\Carbon::parse($row->entered_at))->format('%A') ?? '-' }}
|
||||
</td>
|
||||
<td style="border-right: 1px solid #ddd;">
|
||||
{{ \Carbon\Carbon::parse($row->entered_at)->format('H:i') ?? '-' }}
|
||||
</td>
|
||||
<td style="border-right: 1px solid #ddd;">
|
||||
{{ $row->exited_at ? \Carbon\Carbon::parse($row->exited_at)->format('H:i') : '-' }}
|
||||
</td>
|
||||
<td style="border-right: 1px solid #ddd;">
|
||||
{{ $row->duration ? \Carbon\Carbon::parse($row->duration)->format('H:i') : '-' }}
|
||||
</td>
|
||||
<tr>
|
||||
<td>{{ Jalalian::fromCarbon(Carbon::parse($row->entered_at))->format('m/d') ?? '-' }}</td>
|
||||
<td>{{ Jalalian::fromCarbon(Carbon::parse($row->entered_at))->format('%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" class="bg-cyan-50 font-bold text-gray-700 rounded-b-xl" style="border-top: 2px solid #ddd; text-align: center; padding: 10px 0;">
|
||||
<td colspan="5">
|
||||
مجموع کارکرد این ماه: {{ $employeeTotals[$employee->id][$month] ?? '00:00' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@endforeach
|
||||
@@ -132,58 +331,46 @@ foreach ($data as $employee) {
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* مخفی کردن اسکرولبار برای تبهای ماهها اگر تعدادشان زیاد شد */
|
||||
.hide-scrollbar::-webkit-scrollbar { display: none; }
|
||||
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function openEmployee(evt, empContainerId, empId) {
|
||||
let employeeContents = document.getElementsByClassName("employee-content");
|
||||
for (let i = 0; i < employeeContents.length; i++) {
|
||||
employeeContents[i].style.display = "none";
|
||||
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');
|
||||
}
|
||||
|
||||
let empBtns = document.getElementsByClassName("emp-btn");
|
||||
for (let i = 0; i < empBtns.length; i++) {
|
||||
empBtns[i].classList.remove("bg-cyan-500", "text-white", "shadow-md");
|
||||
empBtns[i].classList.add("bg-gray-200", "text-gray-700");
|
||||
}
|
||||
|
||||
document.getElementById(empContainerId).style.display = "flex";
|
||||
evt.currentTarget.classList.remove("bg-gray-200", "text-gray-700");
|
||||
evt.currentTarget.classList.add("bg-cyan-500", "text-white", "shadow-md");
|
||||
|
||||
let firstMonthTab = document.querySelector("#" + empContainerId + " .tablinks-" + empId);
|
||||
const firstMonthTab = document.querySelector('#' + empContainerId + ' .tablinks-' + empId);
|
||||
if (firstMonthTab) {
|
||||
firstMonthTab.click();
|
||||
}
|
||||
}
|
||||
|
||||
function openMonth(evt, monthId, empId) {
|
||||
let tabcontents = document.getElementsByClassName("tabcontent-" + empId);
|
||||
for (let i = 0; i < tabcontents.length; i++) {
|
||||
tabcontents[i].style.display = "none";
|
||||
}
|
||||
document.querySelectorAll('.tabcontent-' + empId).forEach(el => {
|
||||
el.style.display = 'none';
|
||||
});
|
||||
|
||||
let tablinks = document.getElementsByClassName("tablinks-" + empId);
|
||||
for (let i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].classList.remove("bg-white", "border-cyan-500");
|
||||
tablinks[i].classList.add("bg-gray-100", "border-transparent");
|
||||
}
|
||||
document.querySelectorAll('.tablinks-' + empId).forEach(btn => {
|
||||
btn.classList.remove('month-tab-active');
|
||||
});
|
||||
|
||||
document.getElementById(monthId).style.display = "block";
|
||||
evt.currentTarget.classList.remove("bg-gray-100", "border-transparent");
|
||||
evt.currentTarget.classList.add("bg-white", "border-cyan-500");
|
||||
const monthPanel = document.getElementById(monthId);
|
||||
if (monthPanel) monthPanel.style.display = 'block';
|
||||
|
||||
if (evt.currentTarget) {
|
||||
evt.currentTarget.classList.add('month-tab-active');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
let firstEmployeeBtn = document.querySelector(".emp-btn");
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const firstEmployeeBtn = document.querySelector('.emp-btn');
|
||||
if (firstEmployeeBtn) {
|
||||
firstEmployeeBtn.click();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
@endsection
|
||||
|
||||
+1152
-111
File diff suppressed because it is too large
Load Diff
@@ -37,17 +37,7 @@ if (!empty($user->tasks)) {
|
||||
return array_search($task->state, $state_order);
|
||||
});
|
||||
}
|
||||
//dd(count($break = $user->attendanceBreaks));
|
||||
if (count($user->attendanceBreaks) > 0) {
|
||||
$break = $user->attendanceBreaks[0];
|
||||
if ($break->break_end == null) {
|
||||
$now = Carbon::now()->format('Y-m-d H:i:s');
|
||||
$attendances = new Attendance();
|
||||
$duration = $attendances->duration($break->break_start, $now);
|
||||
}
|
||||
}
|
||||
//dd($now, $break->break_start, $duration);
|
||||
|
||||
// $duration for break timer is passed from AttendanceController
|
||||
|
||||
?>
|
||||
@extends('app')
|
||||
@@ -123,7 +113,7 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
|
||||
@else
|
||||
|
||||
<div class="bg-white flex justify-flex p-4 rounded-xl max-w-[350px] flex-col items-center">
|
||||
<div class="ui-panel flex p-4 rounded-xl max-w-[350px] flex-col items-center">
|
||||
|
||||
|
||||
<label class="text-xl font-bold pb-2">
|
||||
@@ -169,33 +159,27 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
</div>
|
||||
@else
|
||||
{{-- دکمههای تب (نام ماهها) --}}
|
||||
<div class="tab rounded-t-2xl max-w-[350px] mx-auto flex overflow-x-auto"
|
||||
style="margin-top: 0px; max-width: 350px">
|
||||
<div class="ui-month-tabs">
|
||||
@foreach($groupedData->keys() as $month)
|
||||
<button class="tablinks rounded-t-2xl cursor-pointer px-4 py-2 hover:bg-gray-200 transition flex-shrink-0"
|
||||
<button type="button" class="tablinks"
|
||||
onclick="openCity(event, '{{$month}}')">
|
||||
{{$month}}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- محتوای تبها (جدولها) --}}
|
||||
@foreach($groupedData as $month => $records)
|
||||
<div id="{{$month}}"
|
||||
class="tabcontent w-[350px] bg-white rounded-b-xl rounded-tl-2xl cursor-default leading-7 shadow-md">
|
||||
|
||||
<table class="table-auto"
|
||||
style="width:100%; border-collapse: collapse; text-align: center">
|
||||
<div id="{{$month}}" class="tabcontent ui-attendance-card">
|
||||
<table class="ui-attendance-table">
|
||||
<thead>
|
||||
<tr style="border-bottom: 1px solid #ddd; background: rgba(244,244,244,0.82);"
|
||||
class="border-1 border-slate-300">
|
||||
<tr>
|
||||
@if($_COOKIE['user_identity'] == 1370)
|
||||
<th style="border-right: 1px solid #ddd; padding: 8px;">نام</th>
|
||||
<th>نام</th>
|
||||
@endif
|
||||
<th style="border-right: 1px solid #ddd; padding: 4px;">تاریخ</th>
|
||||
<th style="border-right: 1px solid #ddd; padding: 4px;">ورود</th>
|
||||
<th style="border-right: 1px solid #ddd; padding: 4px;">خروج</th>
|
||||
<th style="border-right: 1px solid #ddd; padding: 4px;">مدت</th>
|
||||
<th>تاریخ</th>
|
||||
<th>ورود</th>
|
||||
<th>خروج</th>
|
||||
<th>مدت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -240,8 +224,7 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
{{-- ردیف جمع کل (اختیاری - چون متغیر آن موجود نبود کامنت کردم که خطا ندهد) --}}
|
||||
|
||||
<tr>
|
||||
<td colspan="5" class="rounded-b-xl bg-gray-50 font-bold"
|
||||
style="border-top: 1px solid #ddd; text-align: center; padding: 10px;">
|
||||
<td colspan="5" class="table-summary">
|
||||
تعداد رکورد: {{ count($records) }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -265,10 +248,10 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
|
||||
<!-- ATTENDANCE Modal -->
|
||||
<div id="modal-container"
|
||||
class="fixed inset-0 z-50 hidden bg-black bg-opacity-60 flex items-center justify-center p-4">
|
||||
<div id="modal-content" class=" bg-white p-4 sm:p-6 rounded-lg shadow-lg max-w-sm w-full relative">
|
||||
<h2 id="modal-title" class="text-xl sm:text-2xl font-semibold mb-4 text-center"></h2>
|
||||
<p id="modal-body" class="text-gray-700 mb-4 text-center"></p>
|
||||
class="ui-modal-backdrop centered hidden">
|
||||
<div id="modal-content" class="ui-modal-panel max-w-sm w-full">
|
||||
<h2 id="modal-title" class="ui-title"></h2>
|
||||
<p id="modal-body" class="ui-subtitle"></p>
|
||||
<div class="flex flex-col gap-4" style="align-items: center">
|
||||
|
||||
<div class="flex justify-center gap-2">
|
||||
@@ -376,23 +359,18 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
|
||||
{{-- START TASK MODAL --}}
|
||||
<div id="taskModal"
|
||||
class="hidden fixed inset-0 bg-black/40 items-start justify-center z-50 flex backdrop-blur-sm scroll-auto pt-10"
|
||||
class="ui-modal-backdrop hidden"
|
||||
onclick="closeOnOutsideClick(event)">
|
||||
<div class="relative bg-white w-[350px] rounded-2xl shadow-lg p-5 text-center">
|
||||
<button onclick="closeTaskModal()"
|
||||
class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 transition">
|
||||
<div class="ui-modal-panel">
|
||||
<button type="button" onclick="closeTaskModal()" class="ui-modal-close" aria-label="بستن">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
|
||||
<h2 class="text-lg font-bold mb-3" id="modalTitle">انتخاب وظیفه</h2>
|
||||
|
||||
<p class="text-gray-600 mb-4" id="modalDesc">یکی از وظایف زیر را انتخاب کنید:</p>
|
||||
<input class="h-12 w-full bg-transparent pl-11 pr-4 text-sm text-custom-text-100
|
||||
placeholder:text-custom-text-400 focus:ring-0 rounded-3xl border-2 border-gray-300 py-2"
|
||||
placeholder="جست و جو..." tabindex="1" id="headlessui-combobox-input-:r13m:" role="combobox"
|
||||
aria-expanded="false" aria-autocomplete="list" data-headlessui-state="" type="text" value=""
|
||||
aria-controls="headlessui-combobox-options-:r13n:"
|
||||
aria-activedescendant="headlessui-combobox-option-:r13s:" data-sharkid="__4">
|
||||
<h2 class="ui-title" id="modalTitle">انتخاب وظیفه</h2>
|
||||
<p class="ui-subtitle" id="modalDesc">یکی از وظایف زیر را انتخاب کنید:</p>
|
||||
<input class="ui-input mb-3"
|
||||
placeholder="جست و جو..." type="text" value=""
|
||||
id="taskSearchInput">
|
||||
|
||||
{{-- <div class="grid flex-col gap-2 grid-cols-2 pb-3 items-center justify-items-end">--}}
|
||||
@if(isset($user->tasks) && count($user->tasks) >= 1)
|
||||
@@ -400,11 +378,9 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
<form action="{{url("active_task")}}" method="POST" class=" ">@csrf
|
||||
{{-- <input type="hidden" name="pid" value="{{ $user->tasks->pivot->id }}" class="">--}}
|
||||
<input type="hidden" name="uid" value="{{ $user->id }}" class="">
|
||||
<div class=" overflow-y-scroll"
|
||||
style="max-height: calc(100vh - 300px);">
|
||||
<div class="ui-task-list">
|
||||
@foreach($user->tasks as $item)
|
||||
<div class="inline-flex items-center bg-gray-300 hover:bg-cyan-600 rounded-lg m-2 p-2 justify-between"
|
||||
style="display: flex">
|
||||
<label class="ui-task-item">
|
||||
@if($user->active_task)
|
||||
@if( $user->active_task == $item->title )
|
||||
<input type="radio" name="task_id" checked="true"
|
||||
@@ -425,8 +401,7 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
<div class="px-2.5" style="direction: rtl">{{ $item->title }}</div>
|
||||
<span class="block h-1.5 w-1.5 flex-shrink-0 rounded-full" style="background-color:
|
||||
{!! array_key_exists($item->state,$state) ? $state[$item->state] : "" !!};"></span>
|
||||
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -446,21 +421,17 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
|
||||
</form>
|
||||
@else
|
||||
<div class="inline-flex justify-center items-center"
|
||||
style="height: calc(100vh - 400px);">
|
||||
<div class="flex justify-center items-center py-10">
|
||||
<p class="font-bold text-gray-400">
|
||||
وظیفه ای وجود ندارد
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
{{-- افزودن تسک--}}
|
||||
<form>
|
||||
<input class="h-12 w-[200px] bg-transparent pl-11 pr-4 text-sm text-custom-text-100
|
||||
placeholder:text-custom-text-400 focus:ring-0 rounded-3xl border-2 border-gray-300 py-2"
|
||||
placeholder="عنوان وظیفه" tabindex="1" id="taskTitle" role="combobox"
|
||||
aria-expanded="false" aria-autocomplete="list" data-headlessui-state="" type="text" value=""
|
||||
aria-controls="headlessui-combobox-options-:r13n:"
|
||||
aria-activedescendant="headlessui-combobox-option-:r13s:" data-sharkid="__4">
|
||||
<form class="mt-3 flex items-center justify-center gap-2 flex-wrap">
|
||||
<input class="ui-input !w-[200px]"
|
||||
placeholder="عنوان وظیفه" type="text" value=""
|
||||
id="taskTitle">
|
||||
<button id="createTask"
|
||||
class="mt-4 inline-block bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-2.5 fa-notdog fa-solid fa-plus rounded-full">
|
||||
</button>
|
||||
@@ -468,10 +439,13 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
</div>
|
||||
|
||||
@section('script')
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
<script>
|
||||
|
||||
const existed = {{ $existed ? 'true' : 'false' }};
|
||||
@@ -735,23 +709,21 @@ if (count($user->attendanceBreaks) > 0) {
|
||||
}
|
||||
|
||||
//SEARCH BAR IN TASK MODAL
|
||||
const searchInput = document.getElementById("headlessui-combobox-input-:r13m:");
|
||||
const listItems = document.querySelectorAll('#taskModal .inline-flex.items-center');
|
||||
const searchInput = document.getElementById("taskSearchInput");
|
||||
const listItems = document.querySelectorAll('#taskModal .ui-task-item');
|
||||
|
||||
searchInput.addEventListener("input", function () {
|
||||
const filter = searchInput.value.toLowerCase();
|
||||
listItems.forEach(item => {
|
||||
const nameDiv = item.querySelector('div');
|
||||
if (nameDiv) {
|
||||
const name = nameDiv.textContent.toLowerCase();
|
||||
if (name.includes(filter)) {
|
||||
item.style.display = 'flex';
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
if (searchInput && listItems.length) {
|
||||
searchInput.addEventListener("input", function () {
|
||||
const filter = searchInput.value.toLowerCase();
|
||||
listItems.forEach(item => {
|
||||
const nameDiv = item.querySelector('div');
|
||||
if (nameDiv) {
|
||||
const name = nameDiv.textContent.toLowerCase();
|
||||
item.style.display = name.includes(filter) ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const taskTitle = document.getElementById('taskTitle');
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
@section('content')
|
||||
{{-- {{dd($user)}}--}}
|
||||
<div class="w-full mt-[90px] justify-center container flex">
|
||||
<div class="bg-white w-[350px] p-6 rounded-xl ">
|
||||
<div class="ui-card">
|
||||
<div class="ui-card-body">
|
||||
<img src="/assets/img/bg/bg-shape.jpg" class="rounded-full w-32 h-32 mx-auto pb-6" alt="">
|
||||
{{-- <p class=""> تاریخ شروع همکاری:--}}
|
||||
{{-- {{\Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($user->created_at))->format('Y/m/d')}}</p>--}}
|
||||
@@ -144,9 +145,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Carbon\Carbon;
|
||||
|
||||
//dd($data);
|
||||
$monthlyTotals = [];
|
||||
|
||||
foreach ($data as $month => $attendances) {
|
||||
$tsec = 0;
|
||||
|
||||
foreach ($attendances as $attendance) {
|
||||
// اگر duration موجود باشه استفاده میکنیم
|
||||
if (!empty($attendance->duration)) {
|
||||
[$h, $m, $s] = explode(':', $attendance->duration);
|
||||
$tsec += (int)$h * 3600 + (int)$m * 60 + (int)$s;
|
||||
@@ -19,88 +17,74 @@ foreach ($data as $month => $attendances) {
|
||||
|
||||
$hours = floor($tsec / 3600);
|
||||
$minutes = floor(($tsec % 3600) / 60);
|
||||
$seconds = $tsec % 60;
|
||||
|
||||
// ذخیره در آرایه جدید با کل زمان ماه
|
||||
$monthlyTotals[$month] = sprintf('%02d:%02d', $hours, $minutes);
|
||||
}
|
||||
|
||||
|
||||
//dd($monthlyTotals);
|
||||
|
||||
?>
|
||||
@extends('app')
|
||||
@section('content')
|
||||
|
||||
<div class="container inline-flex flex-col items-center justify-center ">
|
||||
@section('content')
|
||||
<div class="ui-page">
|
||||
@if(empty($data))
|
||||
<div class="mt-80 text-gray-500 text-xl font-bold italic">
|
||||
<div class="ui-empty">
|
||||
<h1>اطلاعاتی موجود نمیباشد</h1>
|
||||
</div>
|
||||
@else
|
||||
<div class="tab rounded-t-2xl max-w-[350px] mx-auto flex"
|
||||
style="margin-top: 80px; max-width: 350px">
|
||||
@foreach(array_keys($data) as $month)
|
||||
<button class="tablinks active rounded-t-2xl cursor-pointer" onclick="openCity(event, '{{$month}}')">{{$month}}</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@foreach($data as $month => $records)
|
||||
<div id="{{$month}}" class="tabcontent w-[350px] bg-white rounded-b-xl rounded-tl-2xl cursor-default leading-7" style="">
|
||||
|
||||
<table class="" style="width:100%; border-collapse: collapse;text-align: center">
|
||||
<thead>
|
||||
<tr class="" style="border-bottom: 1px solid #ddd;">
|
||||
<th style="border-right: 1px solid #ddd;">تاریخ</th>
|
||||
<th style="border-right: 1px solid #ddd;">روز</th>
|
||||
<th style="border-right: 1px solid #ddd;">ورود</th>
|
||||
<th style="border-right: 1px solid #ddd;">خروج</th>
|
||||
<th style="border-right: 1px solid #ddd;">مدت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody >
|
||||
@foreach($records as $row)
|
||||
<tr class="hover:bg-cyan-500" style="border-bottom: 1px solid #ddd;">
|
||||
<td style="border-right: 1px solid #ddd;">{{ Jalalian::fromCarbon(\Carbon\Carbon::parse($row->entered_at))->format('m/d') ?? '-' }}</td>
|
||||
<td style="border-right: 1px solid #ddd;">{{ Jalalian::fromCarbon(\Carbon\Carbon::parse($row->entered_at))->format('%A') ?? '-' }}</td>
|
||||
<td style="border-right: 1px solid #ddd;">{{ \Carbon\Carbon::parse($row->entered_at)->format('H:i') ?? '-' }}</td>
|
||||
<td style="border-right: 1px solid #ddd;">{{ $row->exited_at ? \Carbon\Carbon::parse($row->exited_at)->format('H:i') : '-' }}</td>
|
||||
<td style="border-right: 1px solid #ddd;">{{ $row->exited_at ? \Carbon\Carbon::parse($row->duration)->format('H:i') : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr>
|
||||
<td colspan="5" class="hover:bg-cyan-500 rounded-b-xl" style="border-top: 1px solid #ddd; text-align: center; ">
|
||||
مجموع کارکرد این ماه: {{$monthlyTotals[$month]}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<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>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@foreach($data as $month => $records)
|
||||
<div id="{{ $month }}" class="tabcontent ui-attendance-card">
|
||||
<table class="ui-attendance-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>تاریخ</th>
|
||||
<th>روز</th>
|
||||
<th>ورود</th>
|
||||
<th>خروج</th>
|
||||
<th>مدت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($records as $row)
|
||||
<tr>
|
||||
<td>{{ Jalalian::fromCarbon(Carbon::parse($row->entered_at))->format('m/d') ?? '-' }}</td>
|
||||
<td>{{ Jalalian::fromCarbon(Carbon::parse($row->entered_at))->format('%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->exited_at ? Carbon::parse($row->duration)->format('H:i') : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr>
|
||||
<td colspan="5" class="table-summary">
|
||||
مجموع کارکرد این ماه: {{ $monthlyTotals[$month] }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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", "");
|
||||
}
|
||||
document.getElementById(Month).style.display = "block";
|
||||
evt.currentTarget.className += " active";
|
||||
document.querySelectorAll('.tabcontent').forEach(el => el.style.display = 'none');
|
||||
document.querySelectorAll('.tablinks').forEach(el => el.classList.remove('active'));
|
||||
|
||||
const panel = document.getElementById(Month);
|
||||
if (panel) panel.style.display = 'block';
|
||||
|
||||
if (evt.currentTarget) evt.currentTarget.classList.add('active');
|
||||
}
|
||||
|
||||
// باز کردن اولین تب به صورت پیشفرض
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
let firstTab = document.querySelector(".tablinks"); // اولین دکمه
|
||||
if (firstTab) {
|
||||
firstTab.click(); // همون اول کلیک مجازی بخوره
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const firstTab = document.querySelector('.tablinks');
|
||||
if (firstTab) firstTab.click();
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
||||
@@ -19,11 +19,10 @@ if (isset($task)) {
|
||||
?>
|
||||
@extends('app')
|
||||
@section('content')
|
||||
<div class="w-full mt-[70px] justify-center container flex ">
|
||||
<div class="bg-white w-[350px] rounded-xl ">
|
||||
<h1 class="w-[350px] bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}
|
||||
</h1>
|
||||
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class=" p-6 grid-cols-2 gap-4 items-center">
|
||||
<div class="w-full mt-[70px] justify-center container flex">
|
||||
<div class="ui-card">
|
||||
<div class="ui-card-header">{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}</div>
|
||||
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class="ui-card-body grid grid-cols-2 gap-4 items-center">
|
||||
@csrf
|
||||
@if(isset($task))
|
||||
<input type="hidden" id="id" name="id" value="{{$task->id}}">
|
||||
@@ -87,7 +86,7 @@ if (isset($task)) {
|
||||
<div class="relative" id="dropdown-container">
|
||||
{{-- دکمه --}}
|
||||
<button type="button" id="dropdown-btn"
|
||||
class="w-full bg-white border border-gray-300 rounded-lg px-4 py-2 text-right flex justify-between items-center">
|
||||
class="ui-input text-right flex justify-between items-center">
|
||||
<span id="selected-count">انتخاب کاربران</span>
|
||||
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -96,7 +95,7 @@ if (isset($task)) {
|
||||
</button>
|
||||
|
||||
<div id="dropdown-list"
|
||||
class="hidden absolute z-20 w-full bg-white border border-gray-300 rounded-lg shadow-lg">
|
||||
class="hidden absolute z-20 w-full ui-panel border border-gray-300 rounded-lg shadow-lg">
|
||||
|
||||
<div class="p-2 border-b">
|
||||
<input type="text" id="search-input"
|
||||
|
||||
@@ -64,22 +64,341 @@ $state = [
|
||||
|
||||
?>
|
||||
@extends('app')
|
||||
@section('content')
|
||||
|
||||
<div class="container w-full flex justify-center items-center flex-col ">
|
||||
{{-- {{dd(count($tasks))}}--}}
|
||||
@section('style')
|
||||
<style>
|
||||
.tasks-page {
|
||||
width: 100%;
|
||||
padding: 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.tasks-card {
|
||||
background: var(--surface-glass);
|
||||
backdrop-filter: blur(16px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(160%);
|
||||
border: 1px solid var(--border-glass);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-soft);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tasks-filter {
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.875rem 1rem;
|
||||
background: var(--surface-glass);
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
border: 1px solid var(--border-glass);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.tasks-filter select {
|
||||
border-radius: 0.875rem;
|
||||
border: 1px solid var(--border-input);
|
||||
background: var(--surface-input);
|
||||
color: var(--text-primary);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.tasks-filter select:focus {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--primary-color) 55%, white);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
|
||||
}
|
||||
|
||||
.tasks-tabs {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.35rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--surface-tabs);
|
||||
border-bottom: 1px solid var(--border-input);
|
||||
}
|
||||
|
||||
.task-tab {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.55rem 0.4rem;
|
||||
border-radius: 0.875rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.task-tab:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--surface-tab-hover);
|
||||
}
|
||||
|
||||
.task-tab-active {
|
||||
color: color-mix(in srgb, var(--primary-color) 82%, var(--text-primary));
|
||||
background: var(--surface-tab-active);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.task-tab-label {
|
||||
width: 100%;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.25;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-tab-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.35rem;
|
||||
padding: 0.12rem 0.45rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
background: var(--badge-bg);
|
||||
color: var(--badge-text);
|
||||
}
|
||||
|
||||
.task-tab-active .task-tab-count {
|
||||
background: color-mix(in srgb, var(--primary-color) 18%, var(--surface-tab-active));
|
||||
color: color-mix(in srgb, var(--primary-color) 70%, var(--text-primary));
|
||||
}
|
||||
|
||||
.tasks-table-wrap {
|
||||
padding: 0.5rem 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.tasks-cards-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.85rem 0.9rem;
|
||||
background: var(--surface-card);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
.task-card:hover {
|
||||
background: var(--surface-card-hover);
|
||||
box-shadow: var(--row-shadow);
|
||||
}
|
||||
|
||||
.task-card.is-active {
|
||||
border-inline-start: 4px solid var(--primary-color);
|
||||
padding-inline-start: calc(0.9rem - 3px);
|
||||
}
|
||||
|
||||
.task-card-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.task-card-icon {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
.task-card-title {
|
||||
margin: 0;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-card-desc {
|
||||
margin: 0 0 0.65rem;
|
||||
padding-inline-start: 1.75rem;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.55;
|
||||
color: var(--text-muted);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-card-fields {
|
||||
margin: 0;
|
||||
padding: 0.55rem 0 0;
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.task-card-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.task-card-label {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.task-card-value {
|
||||
color: var(--text-secondary);
|
||||
text-align: left;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-card-value.font-mono {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.tasks-empty {
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
color: var(--text-faint);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
#taskDetailsModal {
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
#taskDetailsModal > div {
|
||||
background: var(--modal-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--modal-border);
|
||||
box-shadow: var(--modal-shadow);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
#taskDetailsModal .modal-tab-btn-active {
|
||||
background: color-mix(in srgb, var(--primary-color) 18%, var(--surface-tab-active)) !important;
|
||||
color: color-mix(in srgb, var(--primary-color) 82%, var(--text-primary)) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #taskDetailsModal {
|
||||
background-color: rgba(2, 6, 23, 0.72) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .task-card svg [stroke="#60646C"] {
|
||||
stroke: #c5d0e0 !important;
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<script>
|
||||
window.tasksById = @json($tasksPayload ?? []);
|
||||
function switchTaskTab(tabId) {
|
||||
document.querySelectorAll('.task-table-container').forEach(el => el.classList.add('hidden'));
|
||||
const panel = document.getElementById('tab-' + tabId);
|
||||
if (panel) panel.classList.remove('hidden');
|
||||
|
||||
document.querySelectorAll('.task-tab').forEach(tab => tab.classList.remove('task-tab-active'));
|
||||
const activeTab = document.querySelector('.task-tab[data-tab="' + tabId + '"]');
|
||||
if (activeTab) activeTab.classList.add('task-tab-active');
|
||||
}
|
||||
|
||||
function updateTaskTabCounts() {
|
||||
document.querySelectorAll('.task-tab').forEach(tab => {
|
||||
const tabId = tab.dataset.tab;
|
||||
const panel = document.getElementById('tab-' + tabId);
|
||||
if (!panel) return;
|
||||
const visibleCount = panel.querySelectorAll('.task-row:not(.hidden)').length;
|
||||
const badge = tab.querySelector('.task-tab-count');
|
||||
if (badge) badge.textContent = visibleCount;
|
||||
});
|
||||
}
|
||||
|
||||
function filterTasksByAssignee(assigneeId) {
|
||||
document.querySelectorAll('.task-row').forEach(row => {
|
||||
if (!assigneeId) {
|
||||
row.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
let ids = [];
|
||||
try {
|
||||
ids = JSON.parse(row.dataset.assigneeIds || '[]');
|
||||
} catch (e) {
|
||||
ids = [];
|
||||
}
|
||||
row.classList.toggle('hidden', !ids.includes(String(assigneeId)));
|
||||
});
|
||||
|
||||
document.querySelectorAll('.task-table-container').forEach(panel => {
|
||||
const emptyMsg = panel.querySelector('.task-filter-empty');
|
||||
const visibleRows = panel.querySelectorAll('.task-row:not(.hidden)').length;
|
||||
const hasRows = panel.querySelectorAll('.task-row').length > 0;
|
||||
if (emptyMsg) {
|
||||
emptyMsg.classList.toggle('hidden', !assigneeId || !hasRows || visibleRows > 0);
|
||||
}
|
||||
});
|
||||
|
||||
updateTaskTabCounts();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container tasks-page w-full flex justify-center items-center flex-col">
|
||||
@if($count > 0)
|
||||
<div class="w-full max-w-6xl mx-auto mt-10 px-2">
|
||||
<div class="flex flex-nowrap gap-1 border-b border-gray-200">
|
||||
<div class="w-full max-w-6xl mx-auto mt-4">
|
||||
@if(!empty($assigneeFilterOptions))
|
||||
<div class="tasks-filter flex flex-wrap items-center gap-3">
|
||||
<label for="assigneeFilter" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس مسئول:
|
||||
</label>
|
||||
<select id="assigneeFilter"
|
||||
onchange="filterTasksByAssignee(this.value)"
|
||||
class="flex-1 min-w-[180px] px-3 py-2 text-sm text-gray-800">
|
||||
<option value="">همه مسئولین</option>
|
||||
@foreach($assigneeFilterOptions as $option)
|
||||
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="tasks-card mt-3">
|
||||
<div class="tasks-tabs">
|
||||
@foreach(array_keys($tasks) as $stateKey)
|
||||
<button onclick="switchTaskTab('{{ $stateKey }}')"
|
||||
class="task-tab px-4 py-2 rounded-t-lg font-medium text-sm transition
|
||||
{{ $loop->first ? 'bg-white text-blue-600 border-t border-x border-gray-200' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100' }}"
|
||||
<button type="button" onclick="switchTaskTab('{{ $stateKey }}')"
|
||||
class="task-tab {{ $loop->first ? 'task-tab-active' : '' }}"
|
||||
data-tab="{{ $stateKey }}">
|
||||
{{ $stateKey }}
|
||||
<span class="mr-1 text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded-full">
|
||||
{{ count($tasks[$stateKey] ?? []) }}
|
||||
</span>
|
||||
<span class="task-tab-label">{{ $stateKey }}</span>
|
||||
<span class="task-tab-count">{{ count($tasks[$stateKey] ?? []) }}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -87,142 +406,90 @@ $state = [
|
||||
{{-- جداول هر وضعیت --}}
|
||||
@foreach($tasks as $stateKey => $stateTasks)
|
||||
<div id="tab-{{ $stateKey }}"
|
||||
class="task-table-container {{ $loop->first ? '' : 'hidden' }} overflow-x-auto bg-white rounded-b-lg shadow">
|
||||
class="task-table-container tasks-table-wrap {{ $loop->first ? '' : 'hidden' }}">
|
||||
@if(count($stateTasks) > 0)
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">عنوان
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">
|
||||
اولویت
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">{{ $isAdminLevel1 ? 'نام اپ' : 'تاریخ شروع' }}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">مهلت
|
||||
</th>
|
||||
{{-- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">جزئیات</th>--}}
|
||||
|
||||
{{-- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">مدت زمان</th>--}}
|
||||
{{-- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">عملیات</th>--}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<div class="tasks-cards-list">
|
||||
@foreach($stateTasks as $task)
|
||||
@php
|
||||
// دریافت نام ایجادکننده (با فرض وجود رابطه 'creator' در مدل Tasks)
|
||||
$creatorName = 'نامشخص';
|
||||
if ($task->relationLoaded('creator') && $task->creator) {
|
||||
$creatorName = $task->creator->name;
|
||||
} elseif ($task->created_by) {
|
||||
// اگر رابطه لود نشده، یک بار کاربر را پیدا کن (توصیه میشود در کنترلر eager load شود)
|
||||
$user = \App\Models\User::find($task->created_by);
|
||||
$creatorName = $user ? $user->name : $task->created_by;
|
||||
}
|
||||
$task->creator_name = $creatorName;
|
||||
// آیکون وضعیت
|
||||
$stateIcon = '';
|
||||
switch($task->state){
|
||||
case 1: $stateIcon = $backlog; break;
|
||||
case 2: $stateIcon = $todo; break;
|
||||
case 3: $stateIcon = $in_progress; break;
|
||||
case 4: $stateIcon = $done; break;
|
||||
default: $stateIcon = '';
|
||||
}
|
||||
|
||||
// تعیین وضعیت شروع/پایان برای دکمه
|
||||
$started_at = $lastDuration?->start ?? null;
|
||||
$ended_at = $lastDuration?->end ?? null;
|
||||
$actionType = $ended_at ? 'start' : ($started_at ? 'end' : 'start');
|
||||
$p_id = $lastDuration?->id ?? '';
|
||||
$stateIcon = match((int)$task->state) {
|
||||
1 => $backlog,
|
||||
2 => $todo,
|
||||
3 => $in_progress,
|
||||
4 => $done,
|
||||
default => '',
|
||||
};
|
||||
@endphp
|
||||
{{--@if($_COOKIE['user_identity'] == 'naser_me') {{dump($task)}} @endif--}}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-2 py-2 {{ $task->active ? 'border-s-4 border-cyan-400 ' : '' }}">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center justify-center w-5 h-5">{!! $stateIcon !!}</span>
|
||||
<div>
|
||||
<div class="font-small text-gray-900 text-sm font-bold">{{ $task->title }}</div>
|
||||
@if($task->description)
|
||||
<div class="text-xs text-gray-500 mt-1 line-clamp-2">{{ \Illuminate\Support\Str::limit(strip_tags($task->description), 50) }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-2">
|
||||
<span class="px-2 py-1 text-xs rounded-full {{ $priorityColors[$task->priority] ?? 'bg-gray-100 text-gray-800' }}">
|
||||
{{ $priorityLabels[$task->priority] ?? $task->priority }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-2 text-sm text-gray-600">
|
||||
{{ $isAdminLevel1 ? $task->appName->name_fa ?? null : Jalalian::fromCarbon(Carbon::parse($task->start_date))->format('Y/m/d') }}
|
||||
</td>
|
||||
<td class="px-2 py-2 text-sm text-gray-600">
|
||||
{{ $task->target_date ? Jalalian::fromCarbon(Carbon::parse($task->target_date))->format('Y/m/d') : '-' }}
|
||||
</td>
|
||||
{{-- <td class="px-2 py-2 text-sm text-gray-600 font-mono">--}}
|
||||
{{-- {{ $totalDuration }}--}}
|
||||
{{-- </td>--}}
|
||||
{{-- <td class="px-2 py-2">--}}
|
||||
{{-- <button onclick="openTaskModal(--}}
|
||||
{{-- '{{ $task->id }}',--}}
|
||||
{{-- '{{ $p_id }}',--}}
|
||||
{{-- '{{ $started_at }}',--}}
|
||||
{{-- '{{ $ended_at }}',--}}
|
||||
{{-- '{{ $actionType }}'--}}
|
||||
{{-- )" class="text-sm bg-blue-50 text-blue-700 px-3 py-1.5 rounded-lg hover:bg-blue-100 transition flex items-center gap-1">--}}
|
||||
{{-- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">--}}
|
||||
{{-- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>--}}
|
||||
{{-- </svg>--}}
|
||||
{{-- {{ $actionType == 'start' ? 'شروع' : 'پایان' }}--}}
|
||||
{{-- </button>--}}
|
||||
{{-- </td>--}}
|
||||
{{-- ستون جزئیات --}}
|
||||
<td class="px-2 py-2">
|
||||
<button onclick='openTaskDetailsModal(@json($task))'
|
||||
class="text-sm bg-gray-50 text-gray-700 px-3 py-1.5 rounded-lg hover:bg-gray-100 transition flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
<article class="task-card task-row {{ $task->active ? 'is-active' : '' }}"
|
||||
data-task-id="{{ $task->id }}"
|
||||
data-assignee-ids='@json($task->assignee_ids ?? [])'
|
||||
onclick="openTaskDetailsModal({{ $task->id }})"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();openTaskDetailsModal({{ $task->id }})}">
|
||||
<div class="task-card-title-row">
|
||||
<span class="task-card-icon">{!! $stateIcon !!}</span>
|
||||
<h3 class="task-card-title">{{ $task->title }}</h3>
|
||||
</div>
|
||||
|
||||
{{-- ستون زمان (همان دکمه شروع/پایان قبلی) --}}
|
||||
{{-- <td class="px-2 py-2">--}}
|
||||
{{-- <button onclick="openTaskTimeModal(--}}
|
||||
{{-- '{{ $task->id }}',--}}
|
||||
{{-- '{{ $p_id }}',--}}
|
||||
{{-- '{{ $started_at }}',--}}
|
||||
{{-- '{{ $ended_at }}',--}}
|
||||
{{-- '{{ $actionType }}'--}}
|
||||
{{-- )" class="text-sm bg-blue-50 text-blue-700 px-3 py-1.5 rounded-lg hover:bg-blue-100 transition flex items-center gap-1">--}}
|
||||
{{-- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">--}}
|
||||
{{-- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>--}}
|
||||
{{-- </svg>--}}
|
||||
{{-- {{ $actionType == 'start' ? 'شروع' : 'پایان' }}--}}
|
||||
{{-- </button>--}}
|
||||
{{-- </td>--}}
|
||||
</tr>
|
||||
@if($task->description)
|
||||
<p class="task-card-desc">{{ strip_tags($task->description) }}</p>
|
||||
@endif
|
||||
|
||||
<dl class="task-card-fields">
|
||||
<div class="task-card-field">
|
||||
<dt class="task-card-label">اولویت</dt>
|
||||
<dd class="task-card-value">
|
||||
<span class="px-2 py-0.5 text-xs rounded-full {{ $priorityColors[$task->priority] ?? 'bg-gray-100 text-gray-800' }}">
|
||||
{{ $priorityLabels[$task->priority] ?? $task->priority }}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
@if($isAdminLevel1)
|
||||
<div class="task-card-field">
|
||||
<dt class="task-card-label">نام اپ</dt>
|
||||
<dd class="task-card-value">{{ $task->appName->name_fa ?? '-' }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($stateKey === 'done')
|
||||
<div class="task-card-field">
|
||||
<dt class="task-card-label">زمان اجرا</dt>
|
||||
<dd class="task-card-value font-mono">{{ $task->total_duration ?? '00:00:00' }}</dd>
|
||||
</div>
|
||||
@else
|
||||
<div class="task-card-field">
|
||||
<dt class="task-card-label">مهلت</dt>
|
||||
<dd class="task-card-value">{{ $task->target_date ? Jalalian::fromCarbon(Carbon::parse($task->target_date))->format('Y/m/d') : '-' }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($task->assignee_names))
|
||||
<div class="task-card-field">
|
||||
<dt class="task-card-label">مسئولین</dt>
|
||||
<dd class="task-card-value">{{ implode('، ', $task->assignee_names) }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</article>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="task-filter-empty hidden tasks-empty">
|
||||
تسکی برای این مسئول در این وضعیت وجود ندارد.
|
||||
</div>
|
||||
@else
|
||||
<div class="p-8 text-center text-gray-400 bg-white rounded-b-lg">
|
||||
<div class="tasks-empty">
|
||||
هیچ وظیفهای در این وضعیت وجود ندارد.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@else
|
||||
<div class=" mt-[200px]">
|
||||
<h1 class=" text-xl font-bold" style="color: #959595">وظیفه ای ندارید</h1>
|
||||
<h1 class="text-xl font-bold text-gray-500">وظیفه ای ندارید</h1>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -230,12 +497,13 @@ $state = [
|
||||
|
||||
|
||||
{{-- START TASK DETAILS MODAL --}}
|
||||
<div id="taskDetailsModal" class="fixed inset-0 bg-black/40 hidden items-center justify-center z-50 p-4">
|
||||
<div class="bg-white w-full max-w-md rounded-2xl shadow-xl max-h-[90vh] overflow-y-auto">
|
||||
<div id="taskDetailsModal" class="fixed inset-0 bg-slate-900/35 hidden items-center justify-center z-50 p-4">
|
||||
<div class="w-full max-w-md rounded-2xl max-h-[90vh] overflow-y-auto">
|
||||
{{-- هدر مودال --}}
|
||||
<div class="flex justify-between items-center p-4 border-b border-gray-200">
|
||||
<div class="flex justify-between items-center p-4 border-b border-slate-200/70">
|
||||
<h2 class="text-lg font-bold text-gray-800" id="modalTaskTitle">عنوان تسک</h2>
|
||||
<button onclick="closeDetailsModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<button onclick="closeDetailsModal()"
|
||||
class="w-9 h-9 inline-flex items-center justify-center rounded-full text-gray-400 hover:text-gray-600 hover:bg-slate-100 transition">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
@@ -245,7 +513,7 @@ $state = [
|
||||
|
||||
<div class="p-4 space-y-4">
|
||||
{{-- Tabs --}}
|
||||
<div class="flex gap-2 bg-white sticky top-0 z-10">
|
||||
<div class="flex gap-2 sticky top-0 z-10" style="background: var(--modal-bg);">
|
||||
<button id="modalTabBtn-info" type="button"
|
||||
onclick="switchDetailsTab('info')"
|
||||
class="flex-1 px-3 py-2 text-sm rounded-lg bg-gray-100 text-gray-800">
|
||||
@@ -324,6 +592,14 @@ $state = [
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-1">ایجادکننده</h3>
|
||||
<p class="text-gray-800 text-sm" id="modalTaskCreator">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-1">تاریخ ایجاد</h3>
|
||||
<p class="text-gray-800 text-sm" id="modalTaskCreatedAt">-</p>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-1">مسئولین</h3>
|
||||
<p class="text-gray-800 text-sm" id="modalTaskAssignees">-</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-2">
|
||||
@@ -389,7 +665,7 @@ $state = [
|
||||
placeholder="مثلاً: @ali لطفاً این بخش رو بررسی کن" required></textarea>
|
||||
|
||||
<div id="mentionDropdown"
|
||||
class="hidden absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-48 overflow-y-auto">
|
||||
class="hidden absolute z-50 w-full mt-1 ui-panel border border-gray-200 rounded-lg shadow-lg max-h-48 overflow-y-auto">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -409,6 +685,7 @@ $state = [
|
||||
<script>
|
||||
const mentionUsers = @json($mentionUsers);
|
||||
const appsList = @json($appsList);
|
||||
const tasksById = window.tasksById || {};
|
||||
const isAdminLevel1 = @json($isAdminLevel1);
|
||||
const currentUserId = @json($user_id ?? null);
|
||||
let currentTask = {};
|
||||
@@ -493,21 +770,61 @@ $state = [
|
||||
document.getElementById(elementId).classList.toggle('hidden');
|
||||
}
|
||||
|
||||
// تابع سوییچ تبهای جدول
|
||||
function switchTaskTab(tabId) {
|
||||
// مخفی کردن همه جداول
|
||||
document.querySelectorAll('.task-table-container').forEach(el => el.classList.add('hidden'));
|
||||
// نمایش جدول انتخاب شده
|
||||
document.getElementById('tab-' + tabId).classList.remove('hidden');
|
||||
function setTaskDetailsLoading() {
|
||||
const commentsList = document.getElementById('modalCommentsList');
|
||||
const durationsList = document.getElementById('modalDurationsList');
|
||||
const loadingHtml = '<div class="text-sm text-gray-400 text-center">در حال بارگذاری...</div>';
|
||||
if (commentsList) commentsList.innerHTML = loadingHtml;
|
||||
if (durationsList) durationsList.innerHTML = loadingHtml;
|
||||
}
|
||||
|
||||
// تغییر استایل تبهای فعال
|
||||
document.querySelectorAll('.task-tab').forEach(tab => {
|
||||
tab.classList.remove('bg-white', 'text-blue-600', 'border-t', 'border-x', 'border-gray-200');
|
||||
tab.classList.add('text-gray-500', 'hover:text-gray-700', 'hover:bg-gray-100');
|
||||
});
|
||||
const activeTab = document.querySelector(`.task-tab[data-tab="${tabId}"]`);
|
||||
activeTab.classList.add('bg-white', 'text-blue-600', 'border-t', 'border-x', 'border-gray-200');
|
||||
activeTab.classList.remove('text-gray-500', 'hover:text-gray-700', 'hover:bg-gray-100');
|
||||
function fetchTaskDetails(taskId) {
|
||||
setTaskDetailsLoading();
|
||||
|
||||
return fetch('/task/' + taskId + '/details', {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error('HTTP ' + res.status);
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
Object.assign(currentTaskDetails, data);
|
||||
tasksById[taskId] = currentTaskDetails;
|
||||
tasksById[String(taskId)] = currentTaskDetails;
|
||||
window.tasksById = tasksById;
|
||||
|
||||
updateModalInfoFields(currentTaskDetails);
|
||||
renderTaskComments(currentTaskDetails);
|
||||
renderTaskDurations(currentTaskDetails);
|
||||
updateModalTotalDuration(currentTaskDetails);
|
||||
|
||||
const durationBtn = document.getElementById('modalDurationActionBtn');
|
||||
if (durationBtn) {
|
||||
const actionType = currentTaskDetails.active ? 'end' : 'start';
|
||||
durationBtn.textContent = actionType === 'start' ? 'شروع' : 'پایان';
|
||||
durationBtn.className = 'flex-1 px-4 py-2 rounded-lg font-medium text-white '
|
||||
+ (actionType === 'start' ? 'bg-blue-600 hover:bg-blue-700' : 'bg-green-600 hover:bg-green-700')
|
||||
+ ' transition';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
const errorHtml = '<div class="text-sm text-red-500 text-center">خطا در بارگذاری جزئیات</div>';
|
||||
const commentsList = document.getElementById('modalCommentsList');
|
||||
const durationsList = document.getElementById('modalDurationsList');
|
||||
if (commentsList) commentsList.innerHTML = errorHtml;
|
||||
if (durationsList) durationsList.innerHTML = errorHtml;
|
||||
});
|
||||
}
|
||||
|
||||
// مقداردهی اولیه (در صورت نیاز)
|
||||
@@ -541,7 +858,7 @@ $state = [
|
||||
const created = c.created_at ? new Date(c.created_at).toLocaleString('fa-IR') : '';
|
||||
const body = escapeHtml(c.body).replace(/\n/g, '<br/>');
|
||||
return `
|
||||
<div class="bg-white border border-gray-200 rounded-lg p-2">
|
||||
<div class="ui-panel border border-gray-200 rounded-lg p-2">
|
||||
<div class="text-xs text-gray-500 flex justify-between">
|
||||
<span>${escapeHtml(author)}</span>
|
||||
<span>${escapeHtml(created)}</span>
|
||||
@@ -615,7 +932,7 @@ $state = [
|
||||
const end = d.end ? new Date(d.end).toLocaleString('fa-IR') : 'در حال انجام';
|
||||
const dur = d.duration || (d.end ? '-' : '...');
|
||||
return `
|
||||
<div class="bg-white border border-gray-200 rounded-lg p-2">
|
||||
<div class="ui-panel border border-gray-200 rounded-lg p-2">
|
||||
<div class="text-xs text-gray-500 flex justify-between">
|
||||
<span>${escapeHtml(uname)}</span>
|
||||
<span class="font-mono">${escapeHtml(dur)}</span>
|
||||
@@ -636,6 +953,7 @@ $state = [
|
||||
const btn = document.getElementById(`modalTabBtn-${t}`);
|
||||
if (el) el.classList.toggle('hidden', t !== tab);
|
||||
if (btn) {
|
||||
btn.classList.toggle('modal-tab-btn-active', t === tab);
|
||||
btn.classList.toggle('bg-gray-100', t === tab);
|
||||
btn.classList.toggle('text-gray-800', t === tab);
|
||||
btn.classList.toggle('text-gray-500', t !== tab);
|
||||
@@ -738,8 +1056,30 @@ $state = [
|
||||
});
|
||||
}
|
||||
|
||||
function formatAssigneeNames(task) {
|
||||
const names = Array.isArray(task?.assignee_names) ? task.assignee_names.filter(Boolean) : [];
|
||||
return names.length ? names.join('، ') : '-';
|
||||
}
|
||||
|
||||
function updateModalInfoFields(task) {
|
||||
document.getElementById('modalTaskCreator').textContent = task.creator_name || '-';
|
||||
document.getElementById('modalTaskCreatedAt').textContent = task.created_at
|
||||
? formatDateTime(task.created_at)
|
||||
: '-';
|
||||
document.getElementById('modalTaskAssignees').textContent = formatAssigneeNames(task);
|
||||
}
|
||||
|
||||
// باز کردن مودال جزئیات تسک
|
||||
function openTaskDetailsModal(task) {
|
||||
function openTaskDetailsModal(taskOrId) {
|
||||
const task = typeof taskOrId === 'object'
|
||||
? taskOrId
|
||||
: tasksById[taskOrId] ?? tasksById[String(taskOrId)];
|
||||
|
||||
if (!task) {
|
||||
alert('اطلاعات این وظیفه در دسترس نیست.');
|
||||
return;
|
||||
}
|
||||
|
||||
currentTaskDetails = task;
|
||||
|
||||
// پر کردن فیلدهای مودال
|
||||
@@ -747,7 +1087,7 @@ $state = [
|
||||
document.getElementById('modalTaskDescription').innerHTML = task.description || 'بدون توضیحات';
|
||||
document.getElementById('modalTaskStartDateView').textContent = task.start_date ? formatJalaliDate(task.start_date) : '-';
|
||||
document.getElementById('modalTaskTargetDateView').textContent = task.target_date ? formatJalaliDate(task.target_date) : '-';
|
||||
document.getElementById('modalTaskCreator').textContent = task.creator_name || '-';
|
||||
updateModalInfoFields(task);
|
||||
|
||||
// اپ
|
||||
const appNameEl = document.getElementById('modalTaskAppName');
|
||||
@@ -902,11 +1242,10 @@ $state = [
|
||||
|
||||
switchDetailsTab('info');
|
||||
setQuickEditMode(false);
|
||||
renderTaskComments(task);
|
||||
renderTaskDurations(task);
|
||||
updateModalTotalDuration(task);
|
||||
setTaskDetailsLoading();
|
||||
setupCommentForm(task);
|
||||
setupMentionAutocomplete();
|
||||
fetchTaskDetails(task.id);
|
||||
}
|
||||
|
||||
function setQuickEditMode(on) {
|
||||
@@ -1089,9 +1428,9 @@ $state = [
|
||||
|
||||
function closeDetailsModal() {
|
||||
const modal = document.getElementById('taskDetailsModal');
|
||||
if (!modal) return;
|
||||
modal.classList.add('hidden');
|
||||
modal.style.display = 'none';
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// فرمت تاریخ شمسی (ساده)
|
||||
|
||||
Reference in New Issue
Block a user