diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 1132f5d9..f9276a17 100755 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -22,24 +22,154 @@ use App\Models\Pivot_Tasks; class AdminController extends Controller { - private function loadPendingRequests($users): array + private function redisIsAvailable(): bool { - $records = []; - try { Redis::connection()->ping(); + + return true; } catch (\Throwable) { - foreach ($users as $user) { - $records[] = [ - 'user_name' => $user->user_name, - 'name' => $user->name, - 'attendances' => [], - ]; + return false; + } + } + + private function appendDecodedRequest(array &$target, ?string $raw): void + { + if (!$raw) { + return; + } + + $request = json_decode($raw); + if ($request && ($request->function ?? null) !== 'rejected') { + $target[] = $request; + } + } + + private function appendDecodedRequestWithAttendance(array &$target, ?string $raw, ?Attendance $attendance = null): void + { + if (!$raw) { + return; + } + + $request = json_decode($raw); + if ($request && ($request->function ?? null) !== 'rejected') { + $target[] = $this->enrichRequestForView($request, $attendance); + } + } + + private function buildFallbackRequestFromAttendance(Attendance $attendance): object + { + $request = (object) [ + 'function' => 'edit', + 'userid' => $attendance->user_identity, + 'id' => $attendance->id, + 'oldenter' => $attendance->entered_at ? Carbon::parse($attendance->entered_at)->format('H:i') : '', + 'newenter' => '', + 'oldexit' => $attendance->exited_at ? Carbon::parse($attendance->exited_at)->format('H:i') : '', + 'newexit' => '', + 'date' => $attendance->entered_at + ? Jalalian::fromCarbon(Carbon::parse($attendance->entered_at))->format('Y/m/d') + : null, + ]; + + $request->display_date = $this->formatRequestDisplayDate($request, $attendance); + + return $request; + } + + private function formatRequestDisplayDate(object $request, ?Attendance $attendance = null): string + { + $candidates = array_filter([ + $attendance?->entered_at, + (!empty($request->oldenter) && str_contains((string) $request->oldenter, ' ')) + ? $request->oldenter + : null, + $request->date ?? null, + ]); + + foreach ($candidates as $value) { + try { + if (is_string($value) && preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $value)) { + $year = (int) substr($value, 0, 4); + + if ($year >= 1700) { + return Jalalian::fromCarbon(Carbon::createFromFormat('Y/m/d', $value))->format('Y/m/d'); + } + + if ($year >= 1300) { + return Jalalian::fromFormat('Y/m/d', $value)->format('Y/m/d'); + } + } + + $carbon = Carbon::parse($value); + if ($carbon->year >= 1000) { + return Jalalian::fromCarbon($carbon)->format('Y/m/d'); + } + } catch (\Throwable) { + continue; + } + } + + return '-'; + } + + private function enrichRequestForView(object $request, ?Attendance $attendance = null): object + { + if (!isset($request->display_date)) { + $request->display_date = $this->formatRequestDisplayDate($request, $attendance); + } + + return $request; + } + + private function resolveRequestTime(?string $preferred, ?string $fallback, $recordDefault = null): ?string + { + $value = ($preferred !== null && $preferred !== '') ? $preferred : $fallback; + if ($value === null || $value === '') { + if ($recordDefault) { + return Carbon::parse($recordDefault)->format('H:i:s'); } - return $records; + return null; } + return Carbon::parse($value)->format('H:i:s'); + } + + private function buildAttendanceTimestamp(string $date, ?string $time): ?string + { + if (!$time) { + return null; + } + + return $date . ' ' . $time; + } + + private function decrementUserRequestCount(?string $userName): void + { + if (!$userName) { + return; + } + + User::where('user_name', $userName) + ->where('req_counts', '>', 0) + ->decrement('req_counts'); + } + + private function loadPendingRequests($users): array + { + $userNames = $users->pluck('user_name')->filter()->values(); + $redisAvailable = $this->redisIsAvailable(); + + $pendingByUser = Attendance::query() + ->whereIn('user_identity', $userNames) + ->whereNotNull('requested') + ->where('requested', '!=', '') + ->get(['id', 'user_identity', 'entered_at', 'exited_at']) + ->groupBy('user_identity'); + + $records = []; + foreach ($users as $user) { $rec = [ 'user_name' => $user->user_name, @@ -47,28 +177,58 @@ class AdminController extends Controller 'attendances' => [], ]; - foreach (Redis::keys("att-tempreq:{$user->user_name}:*") as $key) { - if (str_contains($key, ':creates:')) { - continue; + $pendingForUser = $pendingByUser->get($user->user_name, collect()); + $hasRequestHint = ($user->req_counts ?? 0) > 0 || $pendingForUser->isNotEmpty(); + + if (!$hasRequestHint) { + $records[] = $rec; + continue; + } + + $loadedIds = []; + + if ($redisAvailable) { + foreach (Redis::lrange("att-tempreq:creates:{$user->user_name}", 0, -1) as $reqJson) { + $this->appendDecodedRequestWithAttendance($rec['attendances'], $reqJson); } - $raw = Redis::get($key); - if (!$raw) { - continue; - } + $this->appendDecodedRequestWithAttendance($rec['attendances'], Redis::get("att-tempreq:{$user->user_name}:new")); - $get = json_decode($raw); - if ($get && ($get->function ?? null) !== 'rejected') { - $rec['attendances'][] = $get; + if ($pendingForUser->isNotEmpty()) { + $redisKeys = $pendingForUser + ->map(fn ($attendance) => "att-tempreq:{$user->user_name}:{$attendance->id}") + ->values() + ->all(); + + $rawValues = Redis::mget($redisKeys); + + foreach ($pendingForUser->values() as $index => $attendance) { + $raw = $rawValues[$index] ?? null; + if (!$raw) { + continue; + } + + $beforeCount = count($rec['attendances']); + $this->appendDecodedRequestWithAttendance($rec['attendances'], $raw, $attendance); + if (count($rec['attendances']) > $beforeCount) { + $loadedIds[(string) $attendance->id] = true; + } + } } } - $createRequests = Redis::lrange("att-tempreq:creates:{$user->user_name}", 0, -1); - foreach ($createRequests as $reqJson) { - $get = json_decode($reqJson); - if ($get && ($get->function ?? null) !== 'rejected') { - $rec['attendances'][] = $get; + foreach ($pendingForUser as $attendance) { + if (isset($loadedIds[(string) $attendance->id])) { + continue; } + + $rec['attendances'][] = $this->buildFallbackRequestFromAttendance($attendance); + } + + $pendingById = $pendingForUser->keyBy('id'); + foreach ($rec['attendances'] as $index => $request) { + $attendance = $pendingById->get($request->id ?? null); + $rec['attendances'][$index] = $this->enrichRequestForView($request, $attendance); } $records[] = $rec; @@ -354,6 +514,7 @@ class AdminController extends Controller // Redis::connection()->lrem($create_requests_key, 1, json_encode($data)); Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}"); + $this->decrementUserRequestCount($data->userid); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', 'text' => 'رکورد با موفقیت حذف شد✔️' @@ -371,43 +532,45 @@ class AdminController extends Controller ]); } } elseif ($data->function == 'edit') { -// dd($data); $record = Attendance::find($data->id); + if (!$record) { + return redirect()->back()->with('message', [ + 'color' => '#ff8800ff', + 'text' => 'رکورد پیدا نشد✖️' + ]); + } + $attendance = new Attendance(); $date = Carbon::parse($record->entered_at)->format('Y-m-d'); + $enterTime = $this->resolveRequestTime($data->newenter ?? null, $data->oldenter ?? null, $record->entered_at); + $exitTime = $this->resolveRequestTime($data->newexit ?? null, $data->oldexit ?? null, $record->exited_at); + $record->ip_address = 'admin-edited'; $record->user_identity = $data->userid; - $record->entered_at = $data->newenter ? $date . ' ' . $data->newenter : null; - if ($data->newexit != null) { - //جریمه -// if($data->newexit > '16:00:00'){ -// $data->newexit = date('H:i:s', strtotime($data->newexit . ' -1 hour')); -// } - - $record->exited_at = $date . ' ' . $data->newexit; - $duration = $attendance->duration($data->newenter, $data->newexit); + $record->entered_at = $this->buildAttendanceTimestamp($date, $enterTime) ?? $record->entered_at; + if ($exitTime) { + $record->exited_at = $this->buildAttendanceTimestamp($date, $exitTime); + $duration = $attendance->duration($enterTime, $exitTime); } else { $record->exited_at = null; - $record->duration = null; + $duration = null; } + $break = AttendanceBreak::where('attendance_id', $data->id)->sum('duration'); - if ($break) { + if ($break && $duration) { $breakSec = strtotime("1970-01-01 " . $break) - strtotime("1970-01-01 00:00:00"); $durationSec = strtotime("1970-01-01 " . $duration) - strtotime("1970-01-01 00:00:00"); $durationSec -= $breakSec; $duration = gmdate("H:i:s", $durationSec); - } + $record->duration = $duration ?? $record->duration; -//dd($record->duration, $duration, $break); $record->requested = ''; $record->save(); -// $create_requests_key = "att-tempreq:creates:{$data->userid}"; -// $create_requests_key = "att-tempreq:{$data->userid}:{$data->id}"; -// $t = Redis::connection()->lrem($create_requests_key, -1, json_encode($data)); Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}"); + $this->decrementUserRequestCount($data->userid); tel_notif($record->user_identity, "تایید"); @@ -416,21 +579,33 @@ class AdminController extends Controller "text" => 'رکورد با موفقیت ذخیره شد.✔️' ]); } elseif ($data->function == 'new') { -// dd($data); - $starttime = $data->oldenter ? Carbon::parse($data->oldenter)->format('H:i:s') : null; - $endtime = $data->oldexit ? Carbon::parse($data->oldexit)->format('H:i:s') : null; + $enteredAt = !empty($data->oldenter) ? Carbon::parse($data->oldenter) : null; + $exitedAt = !empty($data->oldexit) ? Carbon::parse($data->oldexit) : null; + + if (!$enteredAt) { + return redirect()->back()->with('message', [ + 'color' => '#ff8800ff', + 'text' => 'زمان ورود در درخواست مشخص نیست✖️' + ]); + } + + $starttime = $enteredAt->format('H:i:s'); + $endtime = $exitedAt?->format('H:i:s'); + if (is_null($endtime) || $endtime > $starttime) { $attendance = new Attendance(); $attendance->ip_address = 'admin-new'; $attendance->user_identity = $data->userid; - $attendance->entered_at = $data->oldenter ?: null; - $attendance->exited_at = $data->oldexit ?: null; - $attendance->duration = $data->oldexit ? $attendance->duration($data->oldenter, $data->oldexit) : null; + $attendance->entered_at = $enteredAt->format('Y-m-d H:i:s'); + $attendance->exited_at = $exitedAt?->format('Y-m-d H:i:s'); + $attendance->duration = $endtime + ? $attendance->duration($starttime, $endtime) + : null; $attendance->save(); $create_requests_key = "att-tempreq:creates:{$data->userid}"; Redis::connection()->lrem($create_requests_key, 1, json_encode($data)); + $this->decrementUserRequestCount($data->userid); -// Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}"); tel_notif($data->userid, "تایید"); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', @@ -456,6 +631,7 @@ class AdminController extends Controller $att->save(); } tel_notif($data->userid, "رد"); + $this->decrementUserRequestCount($data->userid); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'درخواست با موفقیت رد شد.✔️' diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index af249f27..f96a5f2f 100755 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -122,6 +122,28 @@ class TaskController extends Controller return $totalSeconds; } + private function resolveTaskFinishedAt($durations): ?string + { + $latest = collect($durations) + ->pluck('end') + ->filter() + ->map(fn ($value) => Carbon::parse($value)) + ->sortDesc() + ->first(); + + return $latest?->toDateTimeString(); + } + + private function resolveTaskFinishedAtByTaskId(int $taskId): ?string + { + return $this->resolveTaskFinishedAt( + TaskDuration::query() + ->where('task_id', $taskId) + ->whereNotNull('end') + ->get(['end']) + ); + } + private function resolveAssigneeNames(Tasks $task, array $usersById = []): array { $assignees = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees; @@ -155,6 +177,7 @@ class TaskController extends Controller 'description' => $task->description, 'start_date' => $task->start_date, 'target_date' => $task->target_date, + 'finished_at' => $task->finished_at ?? null, 'priority' => $task->priority, 'state' => $task->state, 'app' => $task->app, @@ -200,6 +223,7 @@ class TaskController extends Controller 'description' => $task->description, 'start_date' => $task->start_date, 'target_date' => $task->target_date, + 'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id), 'priority' => $task->priority, 'state' => $task->state, 'app' => $task->app, @@ -295,7 +319,7 @@ class TaskController extends Controller $activeTaskIds = $activeQuery->pluck('task_id')->flip(); $durationQuery = TaskDuration::query() - ->select(['task_id', 'user_id', 'duration']) + ->select(['task_id', 'user_id', 'duration', 'end']) ->whereIn('task_id', $taskIds) ->whereNotNull('duration'); @@ -305,12 +329,20 @@ class TaskController extends Controller $durationsByTaskId = $durationQuery->get()->groupBy('task_id'); + $finishedAtByTaskId = TaskDuration::query() + ->select(['task_id', 'end']) + ->whereIn('task_id', $taskIds) + ->whereNotNull('end') + ->get() + ->groupBy('task_id'); + foreach ($tasks as $task) { $task->active = $activeTaskIds->has($task->id); $task->creator_name = $task->creator?->name ?? 'نامشخص'; $task->assignee_names = $this->resolveAssigneeNames($task, $usersById); $ids = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees; $task->assignee_ids = is_array($ids) ? array_map('strval', $ids) : []; + $task->finished_at = $this->resolveTaskFinishedAt($finishedAtByTaskId->get($task->id, collect())); $totalSeconds = $this->sumDurationSeconds( $durationsByTaskId->get($task->id, collect()), $isAdmin ? null : $user->id @@ -475,7 +507,14 @@ class TaskController extends Controller } else { unset($data['assignees']); } - unset($data['_token'], $data['id'], $data['start_date_display'], $data['target_date_display']); + unset( + $data['_token'], + $data['id'], + $data['start_date_display'], + $data['target_date_display'], + $data['completed_at'], + $data['completed_at_display'] + ); $task->update($data); $after = $task->toArray(); @@ -626,6 +665,7 @@ class TaskController extends Controller 'duration' => $duration, 'start' => $taskDU->start, 'end' => $taskDU->end, + 'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id), ]); } else return response()->json([ diff --git a/resources/views/admin/admin.blade.php b/resources/views/admin/admin.blade.php index 2b95655d..3a649518 100755 --- a/resources/views/admin/admin.blade.php +++ b/resources/views/admin/admin.blade.php @@ -12,7 +12,7 @@ $usersChunked = $users->chunk(3);
@if(!collect($reqs)->pluck('attendances')->flatten()->isEmpty()) -
+

درخواست ها

@foreach ($reqs as $u) @@ -21,9 +21,10 @@ $usersChunked = $users->chunk(3); class="ui-chip-btn" onclick="togglerequests('attendance-{{ $loop->index }}')"> {{ $u['name'] }} + ({{ count($u['attendances']) }}) - @endif -
+ +
@@ -48,13 +49,24 @@ $usersChunked = $users->chunk(3); 'new' => "جدید", default => "نامشخص", } }} - + @php - $oldEnter = $rec->oldenter ? Carbon::parse($rec->oldenter)->format('H:i') : null; - $newEnter = $rec->newenter ? Carbon::parse($rec->newenter)->format('H:i') : null; - $oldExit = $rec->oldexit ? Carbon::parse($rec->oldexit)->format('H:i') : null; - $newExit = $rec->newexit ? Carbon::parse($rec->newexit)->format('H:i') : null; + $parseTime = function ($value) { + if (!$value) { + return null; + } + try { + return Carbon::parse($value)->format('H:i'); + } catch (\Throwable) { + return is_string($value) && preg_match('/^\d{1,2}:\d{2}/', $value) + ? substr($value, 0, 5) + : null; + } + }; + $oldEnter = $parseTime($rec->oldenter ?? null); + $newEnter = $parseTime($rec->newenter ?? null); + $oldExit = $parseTime($rec->oldexit ?? null); + $newExit = $parseTime($rec->newexit ?? null); @endphp @if( $oldEnter == $newEnter || $newEnter == null) @@ -74,8 +86,8 @@ $usersChunked = $users->chunk(3); @csrf - + @@ -83,8 +95,8 @@ $usersChunked = $users->chunk(3); @csrf - + @@ -101,9 +113,8 @@ $usersChunked = $users->chunk(3);
{{ !empty($rec->date) ? Jalalian::fromCarbon(Carbon::parse($rec->date))->format('Y/m/d') - : Jalalian::fromCarbon(Carbon::parse($rec->oldenter))->format('Y/m/d')}}{{ $rec->display_date ?? '-' }}
+ @endif @endforeach - {{-- @if(collect($reqs)->pluck('attendances')->flatten()->isEmpty())--}} - {{--

درخواستی ثبت نشده

--}}
@endif diff --git a/resources/views/app.blade.php b/resources/views/app.blade.php index 9665f9cf..81f968a6 100755 --- a/resources/views/app.blade.php +++ b/resources/views/app.blade.php @@ -56,11 +56,11 @@ --shadow-soft: 0 8px 32px rgba(15, 23, 42, 0.08); --radius-lg: 1.25rem; --radius-xl: 1.5rem; - --glass-panel-bg: rgba(255, 255, 255, 0.08); - --glass-panel-border: rgba(255, 255, 255, 0.35); - --glass-panel-inset: rgba(255, 255, 255, 0.45); - --glass-tint: 14%; - --glass-fallback: color-mix(in srgb, var(--primary-color) 72%, rgba(255, 255, 255, 0.9)); + --glass-panel-bg: color-mix(in srgb, var(--primary-color) 58%, rgba(255, 255, 255, 0.32)); + --glass-panel-border: color-mix(in srgb, var(--primary-color) 38%, rgba(255, 255, 255, 0.55)); + --glass-panel-inset: rgba(255, 255, 255, 0.38); + --glass-tint: 44%; + --glass-fallback: color-mix(in srgb, var(--primary-color) 82%, rgba(255, 255, 255, 0.78)); --text-primary: #0f172a; --text-secondary: #334155; --text-muted: #64748b; @@ -86,12 +86,12 @@ --modal-border: rgba(255, 255, 255, 0.6); --modal-shadow: 0 24px 48px rgba(15, 23, 42, 0.18); --row-shadow: 0 6px 18px rgba(15, 23, 42, 0.06); - --glass-btn-bg: rgba(255, 255, 255, 0.18); - --glass-btn-border: rgba(255, 255, 255, 0.28); - --glass-btn-hover: rgba(255, 255, 255, 0.28); - --header-label-bg: rgba(255, 255, 255, 0.16); - --header-label-border: rgba(255, 255, 255, 0.24); - --header-label-hover: rgba(255, 255, 255, 0.28); + --glass-btn-bg: rgba(255, 255, 255, 0.3); + --glass-btn-border: rgba(255, 255, 255, 0.46); + --glass-btn-hover: rgba(255, 255, 255, 0.44); + --header-label-bg: rgba(255, 255, 255, 0.28); + --header-label-border: rgba(255, 255, 255, 0.42); + --header-label-hover: rgba(255, 255, 255, 0.44); } [data-theme="dark"] { @@ -193,6 +193,32 @@ inset 0 1px 0 var(--glass-panel-inset); } + :root:not([data-theme="dark"]) .glass-header, + :root:not([data-theme="dark"]) .glass-footer-wrap { + box-shadow: + 0 8px 28px color-mix(in srgb, var(--primary-color) 28%, rgba(15, 23, 42, 0.14)), + inset 0 1px 0 var(--glass-panel-inset); + } + + :root:not([data-theme="dark"]) .glass-footer-wrap { + box-shadow: + 0 -8px 28px color-mix(in srgb, var(--primary-color) 28%, rgba(15, 23, 42, 0.14)), + inset 0 1px 0 var(--glass-panel-inset); + } + + :root:not([data-theme="dark"]) .glass-header .headerItems, + :root:not([data-theme="dark"]) .glass-footer button { + text-shadow: 0 1px 3px rgba(15, 23, 42, 0.28); + } + + :root:not([data-theme="dark"]) .fab-btn { + box-shadow: 0 10px 28px color-mix(in srgb, var(--primary-color) 58%, transparent); + } + + :root:not([data-theme="dark"]) .fab-btn:hover { + box-shadow: 0 14px 32px color-mix(in srgb, var(--primary-color) 68%, transparent); + } + .glass-header::before { content: ''; position: absolute; @@ -698,6 +724,66 @@ font-weight: 800; } + .ui-form-stack { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .ui-form-field { + display: flex; + flex-direction: column; + gap: 0.4rem; + width: 100%; + } + + .ui-form-field label { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-secondary); + text-align: right; + } + + .ui-form-field textarea.ui-input { + min-height: 5.5rem; + resize: vertical; + } + + .ui-form-actions { + display: flex; + justify-content: center; + padding-top: 0.25rem; + } + + .ui-input-wrap { + position: relative; + width: 100%; + } + + .ui-input-wrap .ui-input { + padding-left: 2.25rem; + } + + .ui-input-clear { + position: absolute; + left: 0.5rem; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.75rem; + border: none; + background: transparent; + color: var(--text-muted); + cursor: pointer; + } + + .ui-input-clear:hover { + color: #ef4444; + } + .ui-modal-backdrop { position: fixed; inset: 0; @@ -1291,19 +1377,62 @@ [data-theme="dark"] .datepicker-navigator { background-color: var(--surface-card) !important; color: var(--text-primary) !important; + border-color: var(--border-input) !important; } - [data-theme="dark"] .datepicker-day-view .table-days td span, - [data-theme="dark"] .datepicker-month-view .month-item, - [data-theme="dark"] .datepicker-year-view .year-item { + [data-theme="dark"] .datepicker-plot-area .datepicker-navigator .pwt-btn-next, + [data-theme="dark"] .datepicker-plot-area .datepicker-navigator .pwt-btn-switch, + [data-theme="dark"] .datepicker-plot-area .datepicker-navigator .pwt-btn-prev { + background-color: var(--surface-input) !important; color: var(--text-primary) !important; } - [data-theme="dark"] .datepicker-day-view .table-days td.disabled span, - [data-theme="dark"] .datepicker-day-view .table-days td.off span { + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .month-grid-box .header .header-row-cell { + color: var(--text-secondary) !important; + } + + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td span, + [data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item, + [data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item { + background-color: var(--surface-input) !important; + color: var(--text-primary) !important; + } + + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td.disabled span, + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td span.other-month, + [data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item-disable, + [data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item-disable { + background-color: var(--surface-tabs) !important; color: var(--text-faint) !important; } + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td.today span { + background-color: var(--primary-color) !important; + color: #fff !important; + } + + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td.selected span, + [data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item.selected, + [data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item.selected { + background-color: #2563eb !important; + color: #fff !important; + } + + [data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td span:hover, + [data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item:hover, + [data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item:hover { + background-color: var(--surface-tab-hover) !important; + color: var(--text-primary) !important; + } + + [data-theme="dark"] .datepicker-plot-area .toolbox .pwt-btn-submit, + [data-theme="dark"] .datepicker-plot-area .toolbox .pwt-btn-calendar, + [data-theme="dark"] .datepicker-plot-area .toolbox .pwt-btn-today { + background-color: var(--surface-input) !important; + color: var(--text-primary) !important; + border-color: var(--border-input) !important; + } + diff --git a/resources/views/attendance/index.blade.php b/resources/views/attendance/index.blade.php index b55b6b27..48ecd3c8 100755 --- a/resources/views/attendance/index.blade.php +++ b/resources/views/attendance/index.blade.php @@ -461,6 +461,8 @@ if (!empty($user->tasks)) { const lngInput = document.getElementById("longitude"); const distanceInput = document.getElementById("distance"); const form = document.getElementById("attendanceForm"); + + if (submitBtn && latInput && lngInput && distanceInput && distanceMessage && form) { // naser if (existed) { submitBtn.textContent = "خروج"; @@ -571,10 +573,12 @@ if (!empty($user->tasks)) { }); attendanceForm.submit(); } catch (error) { - output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.'; + if (output) { + output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.'; + } } }); - + } function openCity(evt, Month) { @@ -611,20 +615,28 @@ if (!empty($user->tasks)) { {{-- TASKS MODAL--}} function openTaskModal(task) { const modal = document.getElementById('taskModal'); + if (!modal) { + return; + } + modal.classList.remove('hidden'); modal.classList.add('flex'); - // آپدیت متن دکمه بر اساس وضعیت تسک - updateStatusButtonText(task.started_at, task.ended_at); + if (task) { + // آپدیت متن دکمه بر اساس وضعیت تسک + updateStatusButtonText(task.started_at, task.ended_at); - // تنظیم عملکرد دکمه بر اساس تسک فعلی - const statusBtn = document.getElementById('statusBtn'); - statusBtn.onclick = () => handleTaskAction( - task.id, - task.p_id, - task.started_at, - task.ended_at - ); + // تنظیم عملکرد دکمه بر اساس تسک فعلی + const statusBtn = document.getElementById('statusBtn'); + if (statusBtn) { + statusBtn.onclick = () => handleTaskAction( + task.id, + task.p_id, + task.started_at, + task.ended_at + ); + } + } } // ------------------------------- @@ -648,6 +660,10 @@ if (!empty($user->tasks)) { // ------------------------------- function updateStatusButtonText(started_at, ended_at) { const statusBtn = document.getElementById('statusBtn'); + if (!statusBtn) { + return; + } + let text = "شروع"; if (started_at && !ended_at) { @@ -807,6 +823,10 @@ if (!empty($user->tasks)) { } function startBreakCounter(initialSeconds) { + if (!breakBtn) { + return; + } + // ensure no duplicate timer if (breakTimer) clearInterval(breakTimer); currentSeconds = initialSeconds || 0; diff --git a/resources/views/tasks/create.blade.php b/resources/views/tasks/create.blade.php index 404deab7..f924d249 100755 --- a/resources/views/tasks/create.blade.php +++ b/resources/views/tasks/create.blade.php @@ -22,127 +22,126 @@ if (isset($task)) {
{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}
- + @csrf @if(isset($task)) @endif - - - - +
+ + +
- - +
+ + +
- -
- - + + +
+ +
+ + +
- - - {{--
--}} - - - {{--
--}} +
+ +
+ + +
- - - +
+ + +
- - -