This commit is contained in:
mobin
2026-04-08 07:22:13 +00:00
parent 2adb741dac
commit a6b5f27343
30300 changed files with 134406 additions and 1504 deletions
+64 -50
View File
@@ -27,13 +27,23 @@ foreach ($users as $user) {
}
//dd($users);
$now = now()->format('Y-m-d');
foreach ($users as $u) {
$us = $u->attendances()
->whereNull('exited_at')
->latest('entered_at')
->exists();
$u->working = $us ? 1 : 0;
->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();
@@ -91,59 +101,56 @@ $usersChunked = $users->chunk(3);
{{-- {{dd($u['attendances'])}}--}}
@forelse($u['attendances'] as $rec)
<tr style="background-color: {{$rec->id=='new' ? 'lightblue' : 'white'}}">
{{--{{dd($rec)}}--}}
<td class="px-4 py-2 border">{{ match ($rec->function) {
'edit' => "تغییر ساعت",
'delete' => "حذف",
'new' => "جدید",
default => "نامشخص",
} }}</td>
<td class="px-4 py-2 border">{{ Jalalian::fromCarbon(Carbon::parse($rec->oldenter))->format('Y/m/d') }}</td>
@if(Carbon::parse($rec->oldenter)->format('H:i') == Carbon::parse($rec->newenter)->format('H:i') || $rec->newenter=='')
<td class="px-4 py-2 border">{{ $rec->oldenter ? Carbon::parse($rec->oldenter)->format('H:i') : '-' }}</td>
<td class="px-4 py-2 border">{{ !empty($rec->date) ? Jalalian::fromCarbon(Carbon::parse($rec->date))->format('Y/m/d')
: Jalalian::fromCarbon(Carbon::parse($rec->oldenter))->format('Y/m/d')}}</td>
@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;
@endphp
@if( $oldEnter == $newEnter || $newEnter == null)
<td class="px-4 py-2 border">{{ $oldEnter ?? '-' }}</td>
@else
<td class="px-4 py-2 border">{{ $rec->oldenter ? ($rec->id=='new' || $rec->oldenter==($rec->newenter ?? '') ? Carbon::parse($rec->oldenter)->format('H:i') : Carbon::parse($rec->oldenter)->format('H:i').'👇🏻'.Carbon::parse($rec->newenter)->format('H:i')) : '-' }}</td>
<td class="px-4 py-2 border">{{ $oldEnter || $newEnter ? ($rec->id=='new' || $oldEnter==($newEnter ?? '') ? $oldEnter : $oldEnter.'👇🏻'.$newEnter) : '-' }}</td>
@endif
@if(Carbon::parse($rec->oldexit)->format('H:i') == Carbon::parse($rec->newexit)->format('H:i') || $rec->newenter=='')
<td class="px-4 py-2 border">{{$rec->oldexit ? Carbon::parse($rec->oldexit)->format('H:i') : '-'}}</td>
@if($oldExit == $newExit || $newExit == null)
<td class="px-4 py-2 border">{{$oldExit ? ($oldExit ?? $newExit) : '-'}}</td>
@else
<td class="px-4 py-2 border">{{ $rec->oldexit ? ($rec->id=='new' || $rec->oldexit==($rec->newexit ?? '') ? Carbon::parse($rec->oldexit)->format('H:i') : Carbon::parse($rec->oldexit)->format('H:i').'👇🏻'.Carbon::parse($rec->newexit)->format('H:i')) : '-' }}</td>
<td class="px-4 py-2 border">{{ $oldExit || $newExit ? ($rec->id=='new' || $oldExit==($newExit ?? '') ? $oldExit : $oldExit.'👇🏻'.$newExit) : '-' }}</td>
@endif
{{-- @if(Carbon::parse($rec->oldenter)->format('H:i') == Carbon::parse($rec->newenter)->format('H:i') || $rec->newenter=='')--}}
{{-- {{}}--}}
{{-- <td class="px-4 py-2 border">{{ Carbon::parse($rec->oldenter)->format('H:i') }}</td>--}}
{{-- @else--}}
{{-- <td class="px-4 py-2 border">{{ $rec->oldenter ? ($rec->id=='new' ? Carbon::parse($rec->oldenter)->format('H:i') :Carbon::parse($rec->oldenter)->format('H:i').'👇🏻'.Carbon::parse($rec->newenter)->format('H:i')) : '-' }} </td>--}}
{{-- @endif--}}
{{-- @if(Carbon::parse($rec->oldexit)->format('H:i') == Carbon::parse($rec->newexit)->format('H:i') || $rec->newenter=='')--}}
{{-- <td class="px-4 py-2 border">{{Carbon::parse($rec->oldexit)->format('H:i')}}</td>--}}
{{-- @else--}}
{{-- <td class="px-4 py-2 border">{{ $rec->oldexit ? ($rec->id=='new' ? Carbon::parse($rec->oldexit)->format('H:i') :Carbon::parse($rec->oldexit)->format('H:i').'👇🏻'.$rec->newexit) : '-'}} </td>--}}
{{-- @endif--}}
<td class="px-4 py-2 border">
<form method="post" action="{{route('admin.update')}}">
@csrf
<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>
</form>
<form method="post" action="{{route('admin.update')}}">
@csrf
<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>
</form>
</td>
<td class="px-4 py-2 border">
<form method="post" action="{{route('admin.update')}}">
@csrf
<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>
</form>
<form method="post" action="{{route('admin.update')}}">
@csrf
<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>
</form>
</td>
</tr>
@empty
<tr>
@@ -164,23 +171,30 @@ $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-6 justify-evenly rounded-lg shadow-md"
<div class="form m-3 justify-evenly rounded-lg shadow-md "
style="align-items: center; text-align: center; border: 2px solid
{{ $u->working == 1 ? '#16c8ce' : ($u->working == 2 ? 'orange' : 'white' ) }}; padding: 10px; ">
{{ $u->active_task ? '#00ff15' : ($u->working == 1 ? '#16c8ce' : ($u->working == 2 ? 'orange' : 'white' ))}}; padding: 10px; ">
@php
@endphp
<a href="/admin/{{ $u->user_name }}"
class="bg-cyan-500/15 rounded-xl"
class=" rounded-xl"
style="text-decoration: none; color: inherit;">
<h3 style="font-family:Tahoma; color:#333;">
<p style="font-weight: bold ; color:#333;">
{{ $u->name ?? $u->user_name }}
</h3>
</p>
@if($u->active_task)
<p>وظیفه: {{ $u->active_task }}</p>
@endif
<p>
کارکرد ماه جاری:
{{ $u->totaltime }}
</p>
<p>
تعداد درخواست ها:
{{ $u->req_counts }}
</p>
</a>
<button type="button"
class="rounded-lg bg-cyan-500 hover:bg-cyan-600"
@@ -193,7 +207,7 @@ $usersChunked = $users->chunk(3);
@endforeach
@endforeach
<a class="form m-6 justify-evenly rounded-lg shadow-md " href="{{route('admin.new')}}"
<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>
+981
View File
@@ -0,0 +1,981 @@
<?php
use App\Models\Attendance;
use App\Models\User;
use App\Models\ai;
use Morilog\Jalali\Jalalian;
use Carbon\Carbon;
use Illuminate\Support\Collection;
$Attendance = isset($_COOKIE['user_identity'])
? ($_COOKIE['user_identity'] == 1370
? Attendance::all()
: Attendance::where('user_identity', $_COOKIE['user_identity'])->get())
: collect();
$jalalian_object = Jalalian::now();
$first_day_of_month = $jalalian_object->subDays($jalalian_object->getDay() - 1);
$carbon_gregorian = $first_day_of_month->toCarbon();
$thismonth = $carbon_gregorian->format('Y-m-d 00:00:00');
$ids = json_decode(request()->cookie('seen_angizeh_ids', '[]'), true) ?: [];
$s = ai::where('key', 'angizeh')
->when($ids, fn($q) => $q->whereNotIn('id', $ids))
->orderByDesc('id')
->first()
?? ai::where('key', 'angizeh')->orderByDesc('id')->first();
$ids = array_merge($ids, [$s->id]);
Cookie::queue('seen_angizeh_ids', json_encode($ids), 14400);
//total time
$now = Jalalian::fromCarbon(Carbon::now());
$tsec = 0;
//dd(->user_profile);
if (isset($_COOKIE['token01'])) {
$a = json_decode($_COOKIE['token01']);
$user = User::where('user_name', $a->user_profile)->with('attendances')->first();
} else {
$user = isset($_COOKIE['user_identity']) ? User::where('user_name', $_COOKIE['user_identity'])->with('attendances')->first() : null;
}
//dd($user);
if (!empty($user->attendances)) {
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, $sec] = explode(':', $att->duration);
$tsec += $h * 3600 + $m * 60 + $sec;
}
}
}
$hours = floor($tsec / 3600);
$minutes = floor(($tsec % 3600) / 60);
$totalTime = sprintf('%02d:%02d', $hours, $minutes);
$formaction = (isset($_COOKIE['ad']) && $_COOKIE['ad'] == 1)
? 'admin/'
: 'attendance/';
$now = Carbon::now()->format('Y-m-d 00:00:00');
$existed = $Attendance->whereNull('exited_at')->where('entered_at', '>=', $now)->first();
//$requested = $Attendance->whereNull('exited_at')->where('requested', 'true')->first();
//$forgot = $Attendance->whereNull('exited_at')->where('entered_at', '<', $now)->isNotEmpty();
//$today = $Attendance->whereNull('exited_at')->where('entered_at', '>=', $now)->isNotEmpty();
//dd($existed);
if (isset($_COOKIE['token-0'])) {
$hash = md5(strtolower(trim($user->email)));
$gravatarUrl = "https://www.gravatar.com/avatar/$hash?s=200&d=identicon";
}
if (!empty($user->tasks)) {
$backlog = <<<SVG
<svg height="12px" width="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg"><g>
<g transform="translate(8 8) rotate(-90)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(-66)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(-42)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(-18)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(6)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(30)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(54)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(78)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(102)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(126)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(150)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(174)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(198)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(222)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(246)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g></g>
</svg>
SVG;
$todo = <<<SVG
<svg width="12px" height="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4"><g></g>
<circle cx="8" cy="8" r="6" fill="none" stroke="#60646C" stroke-width="1.5" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke-linecap="round" transform="rotate(-90 8 8)">
</circle></svg>
SVG;
$error = <<<SVG
<svg width="12px" height="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4"><g><g transform="translate(8 8) rotate(102)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(126)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(150)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(174)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(198)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(222)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(246)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g></g><circle cx="6" cy="6" r="3" stroke-width="1.5" stroke-linecap="round" fill="none" transform="rotate(-90 8 6)" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke="#eb144c">
</circle><circle cx="8" cy="8" r="6" fill="none" stroke="#eb144c" stroke-width="1.5" stroke-dasharray="37.69911184307752" stroke-dashoffset="18.84955592153876" stroke-linecap="round" transform="rotate(-90 8 8)"></circle></svg>
SVG;
$in_progress = <<<SVG
<svg width="12px" height="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4"><g></g><circle cx="6" cy="6" r="3" stroke-width="1.5" stroke-linecap="round" fill="none" transform="rotate(-90 8 6)" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke="#F59E0B"></circle><circle cx="8" cy="8" r="6" fill="none" stroke="#F59E0B" stroke-width="1.5" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke-linecap="round" transform="rotate(-90 8 8)"></circle></svg>
SVG;
$done = <<<SVG
<svg height="12px" width="12px" class="flex-shrink-0 flex-shrink-0 size-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" percentage="1"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11.3587 6.18828C11.6007 5.85214 11.5244 5.38343 11.1882 5.14141C10.8521 4.89938 10.3834 4.97568 10.1414 5.31183L7.03706 9.62335L5.25956 7.97751C4.95563 7.69609 4.4811 7.71434 4.19968 8.01828C3.91826 8.32221 3.93651 8.79673 4.24045 9.07815L6.64045 11.3004C6.79816 11.4464 7.01095 11.5178 7.22481 11.4963C7.43868 11.4749 7.63307 11.3627 7.75865 11.1883L11.3587 6.18828Z" fill="#46A758"></path></svg>
SVG;
$cancelled = <<<SVG
<svg height="12px" width="12px" class="flex-shrink-0 flex-shrink-0 size-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" percentage="1"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11.1018 4.89826C11.3947 5.19115 11.3947 5.66603 11.1018 5.95892L9.06068 8.00002L11.1018 10.0411C11.3947 10.334 11.3947 10.8089 11.1018 11.1018C10.8089 11.3947 10.334 11.3947 10.0411 11.1018L8.00002 9.06068L5.95892 11.1018C5.66603 11.3947 5.19115 11.3947 4.89826 11.1018C4.60537 10.8089 4.60537 10.334 4.89826 10.0411L6.93936 8.00002L4.89826 5.95892C4.60537 5.66603 4.60537 5.19115 4.89826 4.89826C5.19115 4.60537 5.66603 4.60537 5.95892 4.89826L8.00002 6.93936L10.0411 4.89826C10.334 4.60537 10.8089 4.60537 11.1018 4.89826Z" fill="#9AA4BC"></path></svg>
SVG;
$state = [
"c73c2044-ea14-42cb-b08d-cecf3d1fdfb5" => "$in_progress",// in progress
"a9ce312f-cfca-491e-8e3d-1975aa6ebc10" => "$todo",//to do
"dea1fb57-a281-4fe7-8f3e-ee5567637e35" => "$error",// error
"770fbe42-99f6-41db-9b9e-773d9cced1a5" => "$backlog",//backlog
"b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0" => "$done",//done
"d61d14a3-72f7-4f44-894b-efe8b2a75be1" => "$cancelled",//canceled
];
$state_order = [
"c73c2044-ea14-42cb-b08d-cecf3d1fdfb5",
"a9ce312f-cfca-491e-8e3d-1975aa6ebc10",
"dea1fb57-a281-4fe7-8f3e-ee5567637e35",
"770fbe42-99f6-41db-9b9e-773d9cced1a5",
"b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0",
"d61d14a3-72f7-4f44-894b-efe8b2a75be1",
];
$user->tasks = $user->tasks->sortBy(function ($task) use ($state_order) {
return array_search($task->state, $state_order);
});
$tasks_with_started = $user->tasks->filter(function ($task) {
return $task->pivot && $task->pivot->started_at != null;
});
}
//dd($tasks_with_started);
//dd($user->tasks);
//dd($user->tasks->where('started_at', !null));
?>
@extends('app')
@section('content')
<div class="container flex-col"
style="letter-spacing: 0;display: flex;justify-content: center;align-items: center; margin : 90px 0 10px 0">
{{-- <button type="button"--}}
{{-- class="fixed max-w-[350px] bottom-24 right-9 z-50--}}
{{-- w-16 h-16 flex items-center justify-center--}}
{{-- text-white rounded-full--}}
{{-- bg-blue-500 hover:bg-blue-600--}}
{{-- shadow-lg cursor-pointer transition-all duration-300"--}}
{{-- onclick="openModal('ثبت روز کاری جدید', 'new')">--}}
{{-- ثبت روز جدید--}}
{{-- </button>--}}
@if (!isset($_COOKIE['token-0']))
<form id="attendanceForm" class="form items-center" action="{{ url('attendance/enter') }}" method="post"
style="text-align: left">
@csrf
<p class="title text-center font-bold text-lg mb-2">سامانه گذارش کار</p>
<p class="message text-right text-sm text-gray-700">{{ $s->response }}</p>
@php
$user_identity = $_COOKIE['user_identity'] ?? '';
@endphp
<input type="hidden" name="latitude" id="latitude">
<input type="hidden" name="longitude" id="longitude">
<input type="hidden" name="distance" id="distance">
<p id="distanceMessage" class="text-center mb-2 font-semibold text-gray-600">در حال بررسی موقعیت...</p>
<button id="submitBtn" disabled type="button"
class="bg-gray-400 text-white rounded cursor-not-allowed rounded-xl py-2 px-4 max-w-60 self-center"
style="width: 100%">
ورود/خروج
</button>
<div class="flex justify-flex max-w-[240px]" style="align-items: center;">
<button type="button"
class="w-1/2 p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600 cursor-pointer"
onclick="openModal('ثبت روز کاری جدید', 'new')">
ثبت روز کاری
</button>
<button type="button" id="breakAttendance"
class="w-1/2 p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 cursor-pointer mx-auto"
href="">
استراحت
</button>
</div>
<button type="button" id="tasksBtn"
class="text-white rounded-xl py-2 px-4 max-w-60 self-center bg-cyan-500 hover:bg-cyan-700 cursor-pointer w-full"
onclick="openTaskModal()">
وظیفه‌ها
</button>
</form>
@if(session('open_task_modal'))
<script>
window.onload = () => {
openTaskModal();
};
</script>
@endif
@else
<div class="bg-white flex justify-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>
{{$user->name}}
</label>
<button type="button"
class="w-auto p-2 text-sm text-white text-center rounded-xl bg-blue-500 hover:bg-blue-600 cursor-pointer"
onclick="openModal('ثبت روز کاری جدید', 'new')">
ثبت روز کاری
</button>
</div>
@endif
<script>
const existed = {{ $existed ? 'true' : 'false' }};
{{--const today = {{$today}};--}}
const distanceMessage = document.getElementById("distanceMessage");
const referenceLat = 32.6504751;
const referenceLng = 51.6936045;
const submitBtn = document.getElementById("submitBtn");
const latInput = document.getElementById("latitude");
const lngInput = document.getElementById("longitude");
const distanceInput = document.getElementById("distance");
const form = document.getElementById("attendanceForm");
// naser
if (existed) {
submitBtn.textContent = "خروج";
} else {
submitBtn.textContent = "ورود";
}
function getDistanceInMeters(lat1, lon1, lat2, lon2) {
const earthRadius = 6371000; // meters
const toRad = deg => deg * Math.PI / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
lat1 = toRad(lat1);
lat2 = toRad(lat2);
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadius * c;
}
navigator.geolocation.getCurrentPosition(
position => {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
latInput.value = lat;
lngInput.value = lng;
const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng));
distanceInput.value = dist;
if (dist < 20) {
distanceMessage.textContent = `مکان: شرکت`;
} else {
distanceMessage.textContent = `مکان: خارج از شرکت`;
}
distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed");
if (dist < 10) {
distanceMessage.classList.add("text-green-500");
} else if (dist < 50) {
distanceMessage.classList.add("text-blue-500");
} else if (dist > 2000) {
distanceMessage.classList.add("text-orange-500");
} else {
distanceMessage.classList.add("text-gray-400");
}
submitBtn.disabled = false;
submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed");
submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer");
},
error => alert("⛔ خطا در دریافت موقعیت مکانی: " + error.message), {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
const attendanceForm = document.getElementById('attendanceForm');
const output = document.getElementById('output');
const challenge = new Uint8Array(32);
window.crypto.getRandomValues(challenge);
const createOptions = {
challenge,
rp: {
name: "مثال سایت شما"
},
user: {
id: Uint8Array.from('user-id', c => c.charCodeAt(0)),
name: "user@example.com",
displayName: "نام کاربر"
},
pubKeyCredParams: [{
type: 'public-key',
alg: -7
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
userVerification: "required"
},
timeout: 60000,
attestation: "direct"
};
submitBtn.addEventListener('click', async () => {
// ابتدا اعتبارسنجی اثر انگشت توسط WebAuthn
if (!window.PublicKeyCredential) {
alert('مرورگر شما از احراز هویت بیومتریک پشتیبانی نمی‌کند.');
return;
}
try {
const credential = await navigator.credentials.create({
publicKey: createOptions
});
attendanceForm.submit();
} catch (error) {
output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
}
});
</script>
<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=" border-b border-gray-300 text-center w-full">
<!-- Your Table Code Here -->
<div class="w-[350px]">
@if(isset($_COOKIE['user_identity']))
<table class="min-w-full bg-white shadow-md rounded-lg 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>
@if($_COOKIE['user_identity'] == 1370)
<th class="py-3 px-6 text-right font-medium text-black uppercase tracking-wider"
style="color: #2c2c2c">نام
</th>
@endif
{{-- {{dd($user)}}--}}
<th class="py-3 px-6 text-right font-medium text-black uppercase tracking-wider"
style="color: #2c2c2c">تاریخ
<th class="py-3 px-6 text-right font-medium text-black uppercase tracking-wider"
style="color: #2c2c2c">ورود
</th>
<th class="py-3 px-6 text-center font-medium text-black uppercase tracking-wider"
style="color: #2c2c2c">
خروج
</th>
{{-- <th class="py-3 px-6 text-center font-medium text-black uppercase tracking-wider"--}}
{{-- style="color: #2c2c2c">--}}
{{-- استراحت--}}
{{-- </th>--}}
<th class="py-3 px-6 text-center font-medium text-black uppercase tracking-wider"
style="color: #2c2c2c">جمع
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
@if(!empty($user->attendances))
@foreach($user->attendances as $item)
{{--{{dd($item)}}--}}
<tr class="hover:bg-red-100 cursor-pointer "
style="background-color: {{$item->requested ? '#bfdbfe' : '#ffffff' }}"
onclick="openModal('{{ $item->id }}', '{{ $item->id }}' , '{{ $item->entered_at }}', '{{ $item->exited_at }} ')">
@if($_COOKIE['user_identity'] == 1370)
<td class="py-4 px-6 text-sm text-gray-900 whitespace-nowrap">
{{$item->user_identity}}
</td>
@endif
<td class="py-4 px-6 text-sm text-gray-900 whitespace-nowrap text-right"
style="background-color: {{$item->requested ? '' :($item->distance < 10 ? '#d1fae5' : ($item->distance < 50 ? '#dbeafe' : ($item->distance > 2000 ? '#ffedd5' : '#f3f4f6'))) }}; color: #111827;">
{{ $item->entered_at ? Jalalian::fromCarbon(Carbon::parse($item->entered_at))->format('m/d') : '-' }}
</td>
<td class="py-4 px-6 text-sm text-gray-900 whitespace-nowrap text-right"
style="background-color: {{ $item->requested ? '' :($item->distance < 10 ? '#d1fae5' : ($item->distance < 50 ? '#dbeafe' : ($item->distance > 2000 ? '#ffedd5' : '#f3f4f6'))) }}; color: #111827;">
{{ $item->entered_at ? Carbon::parse($item->entered_at)->format('H:i') : '-' }}
</td>
<td class="py-4 px-6 text-sm text-gray-900 whitespace-nowrap text-center"
style="background-color: {{ $item->requested ? '' :($item->distance_exit < 10 ? '#d1fae5' : ($item->distance_exit < 50 ? '#dbeafe' : ($item->distance_exit > 2000 ? '#ffedd5' : '#f3f4f6'))) }}; color: #111827;">
{{ $item->exited_at ? Carbon::parse($item->exited_at)->format('H:i') : '-' }}
</td>
{{-- <td class="py-4 px-6 text-sm text-gray-900 whitespace-nowrap text-center"--}}
{{-- style="background-color: {{ $item->requested ? '' :($item->distance_exit < 10 ? '#d1fae5' : ($item->distance_exit < 50 ? '#dbeafe' : ($item->distance_exit > 2000 ? '#ffedd5' : '#f3f4f6'))) }}; color: #111827;">--}}
{{-- میزان استراحت--}}
{{-- </td>--}}
<td class="py-4 px-6 text-sm text-gray-900 whitespace-nowrap">
{{ $item->entered_at && $item->exited_at ? Carbon::parse($item->duration)->format('H:i') : '-' }}
</td>
</tr>
<tr class="spacer">
</tr>
@endforeach
@endif
</tbody>
</table>
@endif
</div>
</div>
</div>
<!-- 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>
<div class="flex flex-col gap-4" style="align-items: center">
<div class="flex justify-center gap-2">
<div id="panel-access-link" href="#" class="" title="Panel Access"></div>
</div>
<form id="edit-form" method="post" action="{{$formaction.'edit'}}"
class="inline-block flex-col gap-2 flex items-center">
@csrf
<p id="new-date-label" style="display: none; text-align: right; ">تاریخ:</p>
<div id="new-date-group" class="input-group" style="margin: 20px; display: none;">
<input type="text" name="display_date" id="display_date"
class=" example1 form-control border p-2 rounded date-picker-input" value=""
readonly>
</div>
<p style=" ">ساعت شروع:</p>
<div class="input-group clockpicker border-2 border-gray-300 rounded-lg"
style="margin: 20px;">
<span class="input-group-addon bg-gray-200 align-middle text-stone-600 rounded-r-md p-2 pt-2.5">
<i class="fa-solid fa-clock"></i>
</span>
<input type="text" name="starttime" id="starttime"
class="form-control cursor-default p-2 w-36 "
placeholder="09:00
value="" readonly>
<button type="button" onclick="clearTime('starttime')"
class="p-2 bg-gray-200 hover:bg-gray-300 text-red-600 rounded-l-md w-9
title=" خالی کردن ساعت
">
&times;
</button>
</div>
<p>ساعت پایان:</p>
<div class="input-group clockpicker border-2 border-gray-300 rounded-lg"
style="margin: 20px;">
<span class="input-group-addon bg-gray-200 align-middle text-stone-600 rounded-r-md p-2 pt-2.5">
<i class="fa-solid fa-clock"></i>
</span>
<input type="text" name="endtime" id="endtime"
class="form-control cursor-default p-2 w-36"
placeholder="17:30"
value="" readonly>
<button type="button" onclick="clearTime('endtime')"
class="p-2 bg-gray-200 hover:bg-gray-300 text-red-600 rounded-l-md w-9
title=" خالی کردن ساعت
">
&times;
</button>
</div>
<input type="hidden" name="uname" id="uname" value="{{$user->user_name ?? null}}">
<input type="hidden" name="date" id="date" value="">
<input type="hidden" name="id" id="edit_id" value="">
<button type="submit" id="submit-edit-button" style="display: none;"
onclick="return confirmSubmit()">
ثبت تغییرات
</button>
</form>
<form id="delete-form" action="{{$formaction.'delete'}}" method="post" style="display: none;">
@csrf
<input type="hidden" name="id" id="delete_id" value="">
<button type="submit" id="submit-delete-button" onclick="return confirmSubmit()">
حذف
</button>
</form>
<div class="flex justify-center gap-2 mt-2 px-4">
<button type="button" class="bg-blue-500 text-white rounded p-2 **w-[70%]**"
onclick="document.getElementById('edit-form').submit(); return confirmSubmit()">
@if(isset($_COOKIE['ad']) && $_COOKIE['ad'] == 1)
ثبت تغییرات
@else
ارسال برای تایید
@endif
</button>
<button type="button" class="bg-red-500 text-white rounded p-2 **w-[30%]**"
onclick="document.getElementById('delete-form').submit(); return confirmSubmit()">
<i class="fas fa-trash text-white text-lg"></i>
</button>
</div>
</div>
</div>
</div>
</div>
{{-- 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">
&times;
</button>
<h2 class="text-lg font-bold mb-3" id="modalTitle">انتخاب وظیفه</h2>
<p class="text-gray-600 mb-4" id="modalDesc">یکی از وظایف زیر را انتخاب کنید:</p>
<div class="grid flex-col gap-2 grid-cols-2 pb-3 items-center justify-items-end">
@if(!empty($user->tasks))
@foreach($user->tasks as $item)
<div class="inline-flex items-center ml-auto">
{!! array_key_exists($item->state,$state) ? $state[$item->state] : "" !!}
{{-- <div class="grid flex-col gap-2 grid-cols-2 pb-3 items-center justify-items-center">--}}
<p class="w-full">{{ $item->name }}</p>
</div>
<button
class="actionBtn px-4 py-2 rounded-lg font-semibold text-white transition w-20
{{ ($item->pivot->started_at && !$item->pivot->ended_at) ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700' }}"
id="statusBtn"
onclick="handleTaskAction(
'{{ $item->task_id }}',
'{{ $item->pivot->id ?? '' }}',
'{{ $item->pivot->started_at ?? '' }}',
'{{ $item->pivot->ended_at ?? '' }}',
'{{ $item->pivot->ended_at ? 'start' : ($item->pivot->started_at ? 'end' : 'start') }}'
)">
شروع/پایان
</button>
{{-- </div>--}}
{{-- @if(!$loop->last)--}}
{{-- <hr class="border-t border-gray-300 border-dashed w-full grid-span-2 col-end-2">--}}
{{-- @endif--}}
@endforeach
@endif
</div>
</div>
</div>
@endsection
@section('script')
<script>
{{-- TASKS MODAL--}}
function openTaskModal(task) {
const modal = document.getElementById('taskModal');
modal.classList.remove('hidden');
modal.classList.add('flex');
// آپدیت متن دکمه بر اساس وضعیت تسک
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
);
}
// -------------------------------
// بستن مودال
// -------------------------------
function closeTaskModal() {
const modal = document.getElementById('taskModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
// -------------------------------
// آپدیت متن دکمه شروع/پایان
// -------------------------------
function updateStatusButtonText(started_at, ended_at) {
const statusBtn = document.getElementById('statusBtn');
let text = "شروع";
if (started_at && !ended_at) {
text = "پایان";
}
statusBtn.textContent = text;
}
// -------------------------------
// هندل عملیات شروع / پایان تسک
// -------------------------------
function handleTaskAction(taskId, p_id, started_at, ended_at) {
// ... (منطق تعیین actionType و زمان‌ها) ...
// ارسال داده به سرور
fetch('{{ url("/task-manager") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({
task_id: taskId,
p_id: p_id,
started_at: started_at,
ended_at: ended_at,
action_type: actionType
})
})
.then(res => {
// ابتدا بررسی وضعیت HTTP
if (!res.ok) {
// اگر کد وضعیت 4xx یا 5xx باشد
throw new Error(`HTTP error! status: ${res.status}`);
}
// در صورت موفقیت (200)، تلاش برای خواندن JSON
return res.json();
})
.then(data => {
// 💡 اصلاح: بررسی دقیق فیلد success
if (data && data.success === true) {
console.log('✅ Task updated successfully:', data);
closeTaskModal();
// 💡 رفع مشکل عدم به‌روزرسانی وضعیت دکمه در بار بعدی:
// برای سادگی، صفحه را رفرش کنید تا داده‌های جدید واکشی شوند.
window.location.reload();
} else {
// سرور با کد 200 جواب داده اما success: false بوده یا ساختار پاسخ مشکل دارد.
console.error('❌ Operation failed based on server data:', data);
alert('❌ خطا در انجام عملیات! پاسخ سرور را در کنسول بررسی کنید.');
}
})
.catch(err => {
// شامل خطای ارتباط، خطای HTTP و خطای JSON نامعتبر
console.error('⚠️ Server or JSON parsing error:', err);
alert(`⚠️ خطا در ارتباط با سرور یا پردازش داده! جزئیات در کنسول.`);
});
}
{{-- BREAK MODAL--}}
let breakStarted = false;
let breakStartTime = null;
let breakTimer = null;
const breakBtn = document.getElementById('breakAttendance');
function formatLocalDatetime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
function formatDuration(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const h = String(hours);
const i = String(minutes).padStart(2, '0');
return `${h}:${i}`;
}
breakBtn.addEventListener('click', () => {
if (!breakStarted) {
// شروع استراحت 🚀
breakStarted = true;
breakStartTime = new Date();
// تغییر ظاهر دکمه و شروع تایمر (همچنان M:S نمایش داده می شود)
breakBtn.classList.remove('bg-gray-500', 'hover:bg-gray-600');
breakBtn.classList.add('bg-red-500', 'hover:bg-red-600');
breakBtn.textContent = '00:00';
let seconds = 0;
breakTimer = setInterval(() => {
seconds++;
// اینجا همچنان به صورت دقیقه:ثانیه نمایش داده می شود
const m = String(Math.floor(seconds / 60)).padStart(2, '0');
const s = String(seconds % 60).padStart(2, '0');
breakBtn.textContent = ` ${m}:${s}`;
}, 1000);
//send start time
let formData = new FormData();
formData.append('start_time', formatLocalDatetime(breakStartTime));
formData.append('_token', '{{ csrf_token() }}');
fetch("{{ url('attendance/break') }}", {
method: "POST",
body: formData
})
.then(res => res.json())
.then(data => console.log('✅ Break Started Logged:', data))
.catch(err => console.error('💥 Error starting break:', err));
} else {
// پایان استراحت 🛑
breakStarted = false;
const breakEndTime = new Date();
clearInterval(breakTimer);
// 1. محاسبه مدت زمان استراحت (بر حسب ثانیه)
const durationSec = Math.floor((breakEndTime - breakStartTime) / 1000);
// 2. تبدیل زمان های شروع و پایان به فرمت رشته ای
const breakStartTime_str = formatLocalDatetime(breakStartTime);
const breakEndTime_str = formatLocalDatetime(breakEndTime);
// 3. فرمت کردن مدت زمان به H:i (فقط برای نمایش در کنسول/هشدار)
const duration_formatted_h_i = formatDuration(durationSec);
// 4. نمایش دکمه به حالت اولیه
breakBtn.classList.remove('bg-red-500', 'hover:bg-red-600');
breakBtn.classList.add('bg-gray-500', 'hover:bg-gray-600');
breakBtn.textContent = 'استراحت';
// 5. ارسال داده‌ها به سرور
let formData = new FormData();
formData.append('start_time', breakStartTime_str);
formData.append('end_time', breakEndTime_str);
formData.append('duration_seconds', durationSec);
formData.append('_token', '{{ csrf_token() }}');
fetch("{{ url('attendance/break') }}", {
method: "POST",
body: formData
})
.then(res => res.json())
.then(data => console.log('✅ Break saved:', data))
.catch(err => console.error('💥 Error:', err));
}
});
{{-- ATTENDANCE MODAL--}}
function clearTime(fieldId) {
document.getElementById(fieldId).value = '';
}
function closeModal() {
document.getElementById('modal-container').classList.add('hidden');
}
document.getElementById('modal-container').addEventListener('click', (e) => {
if (e.target === document.getElementById('modal-container')) {
closeModal();
}
});
function confirmSubmit() {
return confirm("آیا تغییرات اعمال شوند؟");
}
function openModal(title, id, enteredAt, exitedAt) {
const isNewEntry = id === 'new';
const modalTitleElement = document.getElementById('modal-title');
// ✅ بهترین روش: انتخاب مستقیم فیلد مخفی با ID آن
const hiddenIdInput = document.getElementById('edit_id');
// const hiddenIdInput = document.getElementById('delete_id');
// انتخاب فیلد ID برای فرم حذف
const deleteIdInput = document.getElementById('delete_id');
const submitButton = document.querySelector('#modal-content button[type="submit"]');
const hiddenDateInput = document.querySelector('input[name="date"]');
// مقداردهی به فرم حذف
if (deleteIdInput) {
deleteIdInput.value = id;
}
const newDateLabel = document.getElementById('new-date-label');
const newDateGroup = document.getElementById('new-date-group');
const displayDateInput = document.getElementById('display_date');
const panelLink = document.getElementById('panel-access-link');
panelLink.href = `/go_down/${id}`;
document.getElementById('modal-container').classList.remove('hidden');
const startTimeInput = document.querySelector('input[name="starttime"]');
const endTimeInput = document.querySelector('input[name="endtime"]');
// تنظیم ساعت‌ها
if (enteredAt && !isNaN(new Date(enteredAt).getTime())) {
startTimeInput.value = new Date(enteredAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
} else {
startTimeInput.value = '09:00';
}
if (exitedAt && !isNaN(new Date(exitedAt).getTime())) {
endTimeInput.value = new Date(exitedAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
}
// else {
// endTimeInput.value = '17:30';
// }
if (isNewEntry) {
// حالت ثبت جدید
modalTitleElement.innerText = 'ثبت روز کاری جدید';
newDateLabel.style.display = 'block';
newDateGroup.style.display = 'block';
displayDateInput.value = '';
hiddenDateInput.value = '';
// ✅ اصلاح: مقداردهی 'new' به فیلد ID
if (hiddenIdInput) hiddenIdInput.value = 'new';
submitButton.onclick = function () {
if (hiddenDateInput.value === '') {
alert("لطفاً یک تاریخ را از تقویم انتخاب کنید.");
return false;
}
return confirmSubmit();
};
} else {
// حالت ویرایش
modalTitleElement.innerText = 'تغییر ساعت کاری';
newDateLabel.style.display = 'none';
newDateGroup.style.display = 'none';
// ✅ اصلاح: استفاده از متغیر 'id' که از جدول فرستاده شده است
if (hiddenIdInput) hiddenIdInput.value = id;
if (hiddenDateInput) hiddenDateInput.value = enteredAt;
submitButton.onclick = function () {
return confirmSubmit();
};
}
}
$(document).ready(function () {
// 1. فعال‌سازی Clockpicker
$('.clockpicker').clockpicker({
autoclose: true,
placement: 'center',
align: 'left',
donetext: 'تایید',
vibrate: true,
// donetext: true,
});
$(".example1").persianDatepicker({
format: "YYYY/MM/DD",
altField: '#date',
altFormat: 'YYYY-MM-DD',
calendar: {
persian: {
locale: 'fa',
leapYearMode: "astronomical"
}
},
observer: true,
initialValue: false,
initialValueType: 'gregorian',
autoClose: true,
toolbox: {
calendarSwitch: {enabled: false},
todayButton: {enabled: true, text: {fa: "امروز"}}
},
onSelect: function (unix) {
// تبدیل تاریخ انتخابی به میلادی درست با timezone ایران
const d = new persianDate(unix).toCalendar('gregorian').toLocale('en');
console.log("Gregorian (Fixed):", d.format("YYYY-MM-DD"));
}
});
// 2. فعال‌سازی Persian Datepicker
// $('.example1').pDatepicker({
// format: 'YYYY/MM/DD',
// // placement: 'center',
// altField: '#date', // این فیلد مخفی، تاریخ میلادی تبدیل شده را دریافت می‌کند
// altFormat: 'YYYY-MM-DD', // میلادی ذخیره بشه
// // changeMonth: true,
// // changeYear: true,
// // showOtherMonths: true,
// // selectOtherMonths: true,
// autoClose: true,
//
// });
});
</script>
@endsection
+10 -8
View File
@@ -3,7 +3,7 @@
<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-full max-w-sm border-t-8 border-[#16c8ce]">
<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="text-center mb-6">
<h1 class="text-2xl font-extrabold text-gray-800 border-b pb-2">کاربر جدید</h1>
@@ -14,7 +14,7 @@
<div>
<label for="name" class="block text-sm font-medium text-gray-700">اسم:</label>
<input type="text" name="name" id="name" required
<input type="text" name="name" id="name" required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-cyan-500 focus:border-cyan-500 sm:text-sm transition duration-150">
</div>
@@ -41,16 +41,18 @@
<div class="w-full sm:w-1/2">
<label for="number" class="block text-sm font-medium text-gray-700">شماره تماس:</label>
<input type="tel" name="number" id="number" placeholder=" ۰۹۱2xxxxxxx" pattern="[0-9]{11}" required
<input type="tel" name="number" id="number" placeholder=" ۰۹۱2xxxxxxx" pattern="[0-9]{11}"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-cyan-500 focus:border-cyan-500 sm:text-sm transition duration-150">
</div>
</div>
<button type="submit"
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer rounded-xl border border-400 justify-self-center text-white px-2 py-1.5">
ثبت اطلاعات
</button>
<div class="flex justify-center">
<button type="submit"
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer rounded-xl border border-400 justify-self-center text-white px-2 py-1.5">
ثبت اطلاعات
</button>
</div>
</form>
</div>
+189
View File
@@ -0,0 +1,189 @@
<?php
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;
}
}
$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">
@if(empty($data) || count($data) == 0)
<div class="mt-80 text-gray-500 text-xl font-bold italic">
<h1>اطلاعاتی موجود نمیباشد</h1>
</div>
@else
<div class="flex flex-wrap justify-center gap-2 mt-10 mb-6 max-w-3xl">
@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"
onclick="openEmployee(event, 'emp_{{ $employee->id }}', '{{ $employee->id }}')">
{{ $employee->name }}
</button>
@endforeach
</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">
<span>کارکرد ماه قبل ({{ $previousMonthName }}):</span>
<strong class="text-sm font-black">{{ $employeeTotals[$employee->id][$previousMonthName] ?? '00:00' }}</strong>
</span>
</div>
@php
$recordsGrouped = is_array($employee->records) ? $employee->records : (array)$employee->records;
@endphp
@if(empty($recordsGrouped))
<div class="my-10 text-gray-500 text-lg font-bold italic">
رکورد ترددی برای این شخص ثبت نشده است.
</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">
@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"
onclick="openMonth(event, 'month_{{ $employee->id }}_{{ $month }}', '{{ $employee->id }}')">
{{ $month }}
</button>
@endforeach
</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">
<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>
</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>
@endforeach
<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;">
مجموع کارکرد این ماه: {{ $employeeTotals[$employee->id][$month] ?? '00:00' }}
</td>
</tr>
</tbody>
</table>
</div>
@endforeach
@endif
</div>
@endforeach
@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";
}
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);
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";
}
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.getElementById(monthId).style.display = "block";
evt.currentTarget.classList.remove("bg-gray-100", "border-transparent");
evt.currentTarget.classList.add("bg-white", "border-cyan-500");
}
document.addEventListener("DOMContentLoaded", function () {
let firstEmployeeBtn = document.querySelector(".emp-btn");
if (firstEmployeeBtn) {
firstEmployeeBtn.click();
}
});
</script>
@endsection
+69 -33
View File
@@ -17,28 +17,29 @@
<title>سامانه حضور و غیاب</title>
{{--ساعت--}}
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/clockpicker/0.0.7/bootstrap-clockpicker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clockpicker/0.0.7/bootstrap-clockpicker.min.js"></script>
{{--تاریخ--}}
<link rel="stylesheet" href="https://unpkg.com/persian-datepicker@1.2.0/dist/css/persian-datepicker.css">
<link rel="stylesheet" href="{{ asset('/packages/clockpicker/bootstrap-clockpicker.min.css') }}">
<script src="{{ asset('/packages/clockpicker/jquery.min.js')}}"></script>
<script src="{{ asset('/packages/clockpicker/bootstrap-clockpicker.min.js')}}"></script>
<script src="https://unpkg.com/persian-date@1.1.0/dist/persian-date.js"></script>
<script src="https://unpkg.com/persian-datepicker@1.2.0/dist/js/persian-datepicker.js"></script>
{{-- --}}{{--تاریخ--}}
<link rel="stylesheet" href="{{ asset('/packages/datepicker/persian-datepicker.css')}}">
<script src="{{ asset("/packages/datepicker/persian-date.js")}}"></script>
<script src="{{ asset('/packages/datepicker/persian-datepicker.js') }}"></script>
<link rel="manifest" href="manifest.json">
{{-- <link rel="manifest" href="manifest.json">--}}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Baloo+Bhaijaan+2:wght@600&display=swap" rel="stylesheet">
{{-- font awesome--}}
<link href="{{url('fontawesome-pro-6.5.2/css/all.css')}}" rel="stylesheet"/>
{{-- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" rel="stylesheet"/>--}}
{{-- <link href="https://fonts.googleapis.com/css2?family=Baloo+Bhaijaan+2:wght@600&display=swap" rel="stylesheet">--}}
<style>
/* Ignore CSS variable inspection */
:root {
/* @phpstorm-ignore */
--primary-color: #16c8ce;
--primary-color: {{ \App\Models\Setting::where('key','primary_color')->value('value') ?? '#16c8ce'}};
--text-color: #ffffff;
}
@@ -55,6 +56,10 @@
z-index: 99999 !important;
direction: ltr !important;
/* چپ به راست */
background-color: #c8c8c8 !important;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.3);
}
.hidden-row {
@@ -93,7 +98,7 @@
width: 16px;
border-radius: 50%;
right: 0;
background-color: #16c8ce;
background-color: {{ \App\Models\Setting::where('key','primary_color')->value('value')}};
}
.title::before {
@@ -172,7 +177,7 @@
.submit {
border: none;
outline: none;
background-color: #16c8ce;
background-color: {{ \App\Models\Setting::where('key','primary_color')->value('value')}};
padding: 10px;
border-radius: 10px;
color: #fff;
@@ -264,6 +269,21 @@
.btn {
display: flex !important;
justify-content: center !important;
width: 100%;
}
.btn-sm {
display: flex !important;
justify-content: center !important;
}
@@ -272,6 +292,7 @@
</head>
<body>
@if (session('message'))
<center>
<div id="error-box"
@@ -285,15 +306,23 @@
<div style="max-width: 420px; width: 100%; ">
@if(url()->current() == url('/tasks'))
<div class="flex mr-6 object-right-bottom bottom-20 fixed z-10 w-14 ">
<a class="bg-[var(--primary-color)] rounded-full size-14 flex justify-center items-center "
href="/create_task">
<i class=" fa-solid fa-plus " style="color: white"></i>
</a>
</div>
@endif
@if(url()->current() != url('/tasks'))
<div class="flex mr-6 object-right-bottom bottom-20 fixed z-10 ">
<a class="bg-cyan-500 rounded-full size-14 flex justify-center items-center "
<div class="flex mr-6 object-right-bottom bottom-20 fixed z-10 w-14 ">
<a class="bg-[var(--primary-color)] rounded-full size-14 flex justify-center items-center "
href="/tasks">
<i class=" fa-solid fa-list-check " style="color: white"></i>
</a>
</div>
@endif
<div class="header bg-cyan-500"
<div class="header bg-[var(--primary-color)]"
style="position: fixed;padding: 6px; top: 0;max-width: 420px; width: 100%; z-index: 11;
/*background-color: #16c8ce;*/
font-size: 24px;font-weight: 600; color: #FFFFFF">
@@ -330,9 +359,10 @@
{{-- @if(isset($attendance_recorder) and $attendance_recorder == 1 or true)--}}
{{-- @else--}}
{{-- footer--}}
<div class="fixed bottom-0 max-w-[420px] w-full z-20">
<form id="actionForm" method="POST"
class="flex justify-around gap-4 items-center px-4 py-1 bg-cyan-500 text-[25px]"
class="flex justify-around gap-4 items-center px-4 py-1 bg-[var(--primary-color)] text-[25px]"
style="padding: 15px 0 10px 0">
@csrf
@if(isset($_COOKIE['token_0']) || isset($_COOKIE['token_1']) || isset($_COOKIE['token_2']))
@@ -340,30 +370,36 @@
class="fa-solid fa-arrow-up text-white"></i></button>
@endif
{{-- attendances list--}}
@if(isset($_COOKIE['token-0']))
<button type="button" onclick="submitForm('/back')"><i
class="fa-solid fa-backward-step text-white"></i>
</button>
@endif
{{-- BACK BOTTUN--}}
{{-- @if(isset($_COOKIE['token-0']))--}}
{{-- <button type="button" onclick="submitForm('/back')"><i--}}
{{-- class="fa-solid fa-backward-step text-white"></i>--}}
{{-- </button>--}}
{{-- @endif--}}
@if(isset($_COOKIE['user_identity']))
<button type="button" onclick="submitForm('{{route('attendance.show')}}','')"><i
class="fa-solid fa-clipboard-user text-white"></i></button>
<button type="button" onclick="submitForm('{{route('attendance.show')}}','')">
<i class="fa-solid fa-clipboard-user text-white"></i>
</button>
@endif
<button type="button" onclick="submitForm('/', 'GET')"><i
class="fa-solid fa-house text-white"></i></button>
<button type="button" onclick="submitForm('/', 'GET')">
<i class="fa-solid fa-house text-white"></i>
</button>
@if(!request()->is("admin") && (isset($_COOKIE['ad']) && $_COOKIE['ad']==1))
<button type="button" onclick="submitForm('{{route('admin.dashboard')}}', 'GET')"><i
class="fa-solid fa-users text-white"></i></button>
<button type="button" onclick="submitForm('{{route('admin.dashboard')}}', 'GET')">
<i class="fa-solid fa-users text-white"></i>
</button>
@elseif(request()->is("admin") && (isset($_COOKIE['ad']) && $_COOKIE['ad']==1))
<button type="button" onclick="submitForm('/', 'GET')"><i
class="fa-solid fa-users text-white"></i></button>
<button type="button" onclick="submitForm('/', 'GET')">
<i class="fa-solid fa-users text-white"></i>
</button>
@endif
@if(isset($_COOKIE['user_identity']))
<button type="button" onclick="submitForm('{{route('user.profile')}}', 'GET')"><i
class="fa-solid fa-user text-white"></i></button>
<button type="button" onclick="submitForm('{{route('user.profile')}}', 'GET')">
<i class="fa-solid fa-user text-white"></i>
</button>
@endif
</form>
</div>
File diff suppressed because it is too large Load Diff
+103 -58
View File
@@ -2,6 +2,7 @@
<html lang="en" dir="rtl">
<head>
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
@@ -256,61 +257,6 @@
</div>
<div style="margin-bottom: 48px;"></div>
{{-- @if(isset($attendance_recorder) and $attendance_recorder == 1 or true)--}}
{{-- @else--}}
{{-- <div class="fixed bottom-0 max-w-[420px] w-full z-20">--}}
{{-- <form id="actionForm" method="POST"--}}
{{-- class="flex justify-around gap-4 items-center px-4 py-1 bg-cyan-500 text-[25px]"--}}
{{-- style="padding: 15px 0 10px 0">--}}
{{-- @csrf--}}
{{-- @if(isset($_COOKIE['token_0']) || isset($_COOKIE['token_1']) || isset($_COOKIE['token_2']))--}}
{{-- <button type="button" onclick="submitForm('up')"><i--}}
{{-- class="fa-solid fa-arrow-up text-white"></i></button>--}}
{{-- @endif--}}
{{-- --}}{{-- attendances list--}}
{{-- @if(isset($_COOKIE['token-0']))--}}
{{-- <button type="button" onclick="submitForm('/back')"><i--}}
{{-- class="fa-solid fa-backward-step text-white"></i>--}}
{{-- </button>--}}
{{-- @endif--}}
{{-- @if(isset($_COOKIE['user_identity']))--}}
{{-- <button type="button" onclick="submitForm('{{route('attendance.show')}}','')"><i--}}
{{-- class="fa-solid fa-clipboard-user text-white"></i></button>--}}
{{-- @endif--}}
{{-- <button type="button" onclick="submitForm('/', 'GET')"><i--}}
{{-- class="fa-solid fa-house text-white"></i></button>--}}
{{-- @if(!request()->is("admin") && (isset($_COOKIE['ad']) && $_COOKIE['ad']==1))--}}
{{-- <button type="button" onclick="submitForm('{{route('admin.dashboard')}}', 'GET')"><i--}}
{{-- class="fa-solid fa-users text-white"></i></button>--}}
{{-- @elseif(request()->is("admin") && (isset($_COOKIE['ad']) && $_COOKIE['ad']==1))--}}
{{-- <button type="button" onclick="submitForm('/', 'GET')"><i--}}
{{-- class="fa-solid fa-users text-white"></i></button>--}}
{{-- @endif--}}
{{-- @if(isset($_COOKIE['user_identity']))--}}
{{-- <button type="button" onclick="submitForm('{{route('user.profile')}}', 'GET')"><i--}}
{{-- class="fa-solid fa-user text-white"></i></button>--}}
{{-- @endif--}}
{{-- </form>--}}
{{-- </div>--}}
{{-- --}}{{-- @endif--}}
{{-- <script>--}}
{{-- function submitForm(action, method = 'POST') {--}}
{{-- const form = document.getElementById('actionForm');--}}
{{-- form.action = `${action}`;--}}
{{-- form.method = method; // Sets method dynamically--}}
{{-- if (method === 'GET') {--}}
{{-- window.location.href = form.action; // Redirect for GET request--}}
{{-- } else {--}}
{{-- form.submit(); // Submit for POST request--}}
{{-- }--}}
{{-- }--}}
{{-- </script>--}}
<?php
@@ -355,9 +301,9 @@
<span>رمز عبور</span>
</label>
<input type="hidden" name="latitude" id="latitude">
<input type="hidden" name="longitude" id="longitude">
<input type="hidden" name="distance" id="distance">
{{-- <input type="hidden" name="provider" id="provider">--}}
{{-- <input type="hidden" name="longitude" id="longitude">--}}
{{-- <input type="hidden" name="distance" id="distance">--}}
<button type="submit" class=" text-white px-4 py-2 rounded bg-cyan-500">
ورود
</button>
@@ -365,6 +311,105 @@
</div>
<button id="fingerprintLoginBtn" type="button">
ورود با اثر انگشت
</button>
<script>
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const btn = document.getElementById('fingerprintLoginBtn');
btn.addEventListener('click', async () => {
try {
// 1. گرفتن challenge
const optRes = await fetch('{{ route('fingerprint.login.options') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken
},
credentials: 'same-origin'
});
const optJson = await optRes.json();
const publicKey = optJson.publicKey;
// base64url → ArrayBuffer
publicKey.challenge = base64urlToBuffer(publicKey.challenge);
if (publicKey.allowCredentials) {
publicKey.allowCredentials = publicKey.allowCredentials.map(c => ({
...c,
id: base64urlToBuffer(c.id)
}));
}
// 2. درخواست اثر انگشت
const credential = await navigator.credentials.get({
publicKey
});
// 3. ارسال به سرور
const data = {
id: credential.id,
rawId: bufferToBase64url(credential.rawId),
type: credential.type,
response: {
authenticatorData: bufferToBase64url(credential.response.authenticatorData),
clientDataJSON: bufferToBase64url(credential.response.clientDataJSON),
signature: bufferToBase64url(credential.response.signature),
userHandle: credential.response.userHandle
? bufferToBase64url(credential.response.userHandle)
: null
}
};
const verifyRes = await fetch('{{ route('fingerprint.login.verify') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken
},
body: JSON.stringify(data),
credentials: 'same-origin'
});
const result = await verifyRes.json();
if (verifyRes.ok) {
alert('ورود موفق');
location.href = '/dashboard'; // مسیر بعد از لاگین
} else {
alert(result.message || 'خطا در ورود');
}
} catch (err) {
console.error(err);
alert('اثر انگشت در این دستگاه یا مرورگر پشتیبانی نمی‌شود');
}
});
function base64urlToBuffer(base64url) {
const padding = '='.repeat((4 - base64url.length % 4) % 4);
const base64 = (base64url + padding).replace(/-/g, '+').replace(/_/g, '/');
const raw = atob(base64);
const buffer = new ArrayBuffer(raw.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < raw.length; i++) view[i] = raw.charCodeAt(i);
return buffer;
}
function bufferToBase64url(buffer) {
const bytes = new Uint8Array(buffer);
let str = '';
for (const b of bytes) str += String.fromCharCode(b);
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
</script>
<!-- Modals -->
<!-- Modals -->
<div id="modal-container"
+123 -40
View File
@@ -13,57 +13,140 @@
</label>
</div>
@endif
<form action="{{route('edit.profile')}}" method="post" class="grid grid-cols-2 gap-2 justify-items-stretch content-between text-sm indent-2.5 align-middle divide">
{{-- align-center text-sm text-start">--}}
{{-- <div class="grid grid-cols-2 h-64 gap-2 justify-items-stretch content-between text-sm indent-2.5 align-middle divide">--}}
@csrf
<form action="{{route('edit.profile')}}" method="post"
class="grid grid-cols-2 gap-2 justify-items-stretch content-between text-sm indent-2.5 align-middle divide">
{{-- align-center text-sm text-start">--}}
{{-- <div class="grid grid-cols-2 h-64 gap-2 justify-items-stretch content-between text-sm indent-2.5 align-middle divide">--}}
@csrf
<label class="pb-3">نام و نام خانوادگی:</label>
<input type="text" name="name" value="{{ $user->name ?? '' }}"
placeholder="نام و نام خانوادگی"
class="border border-gray-300 rounded-md text-center">
<label class="pb-3">نام و نام خانوادگی:</label>
<input type="text" name="name" value="{{ $user->name ?? '' }}"
placeholder="نام و نام خانوادگی"
class="border border-gray-300 rounded-md text-center">
<label class="pb-3">ایمیل:</label>
<input type="email" name="email" value="{{ $user->email ?? '' }}"
placeholder="ایمیل"
class="border border-gray-300 rounded-md text-center">
<label class="pb-3">ایمیل:</label>
<input type="email" name="email" value="{{ $user->email ?? '' }}"
placeholder="ایمیل"
class="border border-gray-300 rounded-md text-center">
<label class="pb-3">شماره تماس:</label>
<input type="tel" name="number" value="{{ $user->number ?? '' }}"
placeholder="شماره تماس"
pattern="^09\d{9}$"
class="border border-gray-300 rounded-md text-center" required>
<label class="pb-3">شماره تماس:</label>
<input type="tel" name="number" value="{{ $user->number ?? '' }}"
placeholder="شماره تماس"
pattern="^09\d{9}$"
class="border border-gray-300 rounded-md text-center" required>
<label class="pb-3">چت آیدی تلگرام:</label>
<input type="text" pattern="\d+" name="chatid" value="{{ $user->chatid ?? '' }}"
placeholder="چت آیدی تلگرام"
class="border border-gray-300 rounded-md text-center">
<label class="pb-3">چت آیدی تلگرام:</label>
<input type="text" pattern="\d+" name="chatid" value="{{ $user->chatid ?? '' }}"
placeholder="چت آیدی تلگرام"
class="border border-gray-300 rounded-md text-center">
<label class="pb-3">رمز عبور:</label>
<input type="password" name="password" value="{{ $user->password ?? '' }}"
placeholder="رمز عبور"
class="input border border-gray-300 rounded-md text-center">
<input type="password" name="password_confirmation"
placeholder="تکرار رمز عبور"
class="input border border-gray-300 rounded-md text-center col-start-2 min-h-8">
<label class="pb-3">رمز عبور:</label>
<input type="password" name="password" value="{{ $user->password ?? '' }}"
placeholder="رمز عبور"
class="input border border-gray-300 rounded-md text-center">
<input type="password" name="password_confirmation"
placeholder="تکرار رمز عبور"
class="input border border-gray-300 rounded-md text-center col-start-2 min-h-8">
@if(!empty($_COOKIE['ad']) && $_COOKIE['ad'] == 1)
<label class="pb-3">نقش کاربری:</label>
<select name="role" class="border border-gray-300 rounded-md text-center">
<option value="0" {{ $user->is_admin == 0 ? 'selected' : '' }}>کارمند</option>
<option value="1" {{ $user->is_admin == 1 ? 'selected' : '' }}>ادمین</option>
</select>
@endif
<button type="submit"
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer
@if(!empty($_COOKIE['ad']) && $_COOKIE['ad'] == 1)
<label class="pb-3">نقش کاربری:</label>
<select name="role" class="border border-gray-300 rounded-md text-center">
<option value="0" {{ $user->is_admin == 0 ? 'selected' : '' }}>کارمند</option>
<option value="1" {{ $user->is_admin == 1 ? 'selected' : '' }}>ادمین</option>
</select>
@endif
<button type="submit"
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer
rounded-xl border border-400 justify-self-center
p-1.5 text-white">
ثبت تغییرات
</button>
{{-- </div>--}}
ثبت تغییرات
</button>
{{-- </div>--}}
</form>
{{-- اضافه کن درست بعد از فرم یا قبل از دکمه submit --}}
<button type="button" id="registerFingerprint" class="...">ثبت اثر انگشت</button>
<script>
function base64urlToBuffer(base64url) {
const padding = '='.repeat((4 - base64url.length % 4) % 4);
const base64 = (base64url + padding).replace(/-/g, '+').replace(/_/g, '/');
const str = atob(base64);
const buf = new ArrayBuffer(str.length);
const view = new Uint8Array(buf);
for (let i = 0; i < str.length; i++) view[i] = str.charCodeAt(i);
return buf;
}
document.getElementById('registerFingerprint').addEventListener('click', async function() {
try {
// 1. گرفتن options از سرور
const res = await fetch("{{ route('fingerprint.register.options') }}", {
method: "POST",
headers: {
"X-CSRF-TOKEN": "{{ csrf_token() }}",
"Accept": "application/json"
}
});
const options = await res.json();
if (!options.publicKey) {
alert(options.error || "خطا در گرفتن options");
return;
}
// ✅ اینجا: تبدیل user.id و challenge به ArrayBuffer
options.publicKey.user.id = base64urlToBuffer(options.publicKey.user.id);
options.publicKey.challenge = base64urlToBuffer(options.publicKey.challenge);
// 2. ایجاد credential
const credential = await navigator.credentials.create({ publicKey: options.publicKey });
// 3. آماده‌سازی payload برای verifyRegister
const payload = {
id: credential.id,
response: {
clientDataJSON: bufferToBase64url(credential.response.clientDataJSON),
attestationObject: bufferToBase64url(credential.response.attestationObject),
publicKey: "" // کنترلر استخراج می‌کنه
},
name: "دستگاه {{ $user->name }}"
};
// 4. ارسال به verifyRegister
const verifyRes = await fetch("{{ route('fingerprint.register.verify') }}", {
method: "POST",
headers: {
"X-CSRF-TOKEN": "{{ csrf_token() }}",
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify(payload)
});
const verifyData = await verifyRes.json();
alert(verifyData.message || "ثبت اثر انگشت انجام شد");
} catch (e) {
console.error(e);
alert("خطا در ثبت اثر انگشت");
}
});
function bufferToBase64url(buffer) {
const bytes = new Uint8Array(buffer);
let str = '';
for (let i = 0; i < bytes.byteLength; i++) str += String.fromCharCode(bytes[i]);
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
</script>
</div>
</div>
+91
View File
@@ -0,0 +1,91 @@
@extends('app')
@section('content')
<div class="w-full mt-[90px] justify-center container flex ">
<div class="bg-white w-[350px] p-6 rounded-xl ">
<h1 class="w-full bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">ایجاد وظیفه جدید</h1>
<form action="{{Route('create_api')}}" method="POST" class="grid grid-cols-2 gap-4 items-center">
@csrf
<!-- نام Issue (الزامی) -->
<label for="name" class="font-medium text-sm">نام:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="text" id="name" name="name" required>
<!-- توضیحات HTML -->
<label for="description_html" class="font-medium text-sm">توضیحات:</label>
<textarea id="description_html" name="description_html"
class="rounded-lg border border-gray-300 p-2 w-full"></textarea>
<!-- اولویت -->
<label for="priority" class="font-medium text-sm">اولویت:</label>
<select id="priority" name="priority"
class="rounded-lg border border-gray-300 p-2 w-full">
<option value="none">ندارد</option>
<option value="urgent">فوری</option>
<option value="high">بالا</option>
<option value="medium">عادی</option>
<option value="low">پایین</option>
</select>
<!-- تاریخ شروع -->
<label for="start_date" class="font-medium text-sm">تاریخ شروع:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="start_date"
name="start_date">
<!-- تاریخ هدف -->
<label for="target_date" class="font-medium text-sm">تاریخ هدف:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="target_date"
name="target_date">
<!-- وضعیت -->
<label for="state" class="font-medium text-sm">وضعیت:</label>
<select id="state" name="state"
class="rounded-lg border border-gray-300 p-2 w-full">
<option value="c73c2044-ea14-42cb-b08d-cecf3d1fdfb5">درحال انجام</option>
<option value="a9ce312f-cfca-491e-8e3d-1975aa6ebc10">در انتظار</option>
<option value="dea1fb57-a281-4fe7-8f3e-ee5567637e35">دارای خطا</option>
<option value="770fbe42-99f6-41db-9b9e-773d9cced1a5">در صف انجام</option>
<option value="b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0">انجام‌شده</option>
<option value="d61d14a3-72f7-4f44-894b-efe8b2a75be1">لغوشده</option>
</select>
<!-- کاربران مسئول -->
{{-- <label for="assignees" class="font-medium text-sm">انتصاب کاربران:</label>--}}
<label class="font-medium text-sm">انتصاب کاربران:</label>
<div class="rounded-lg border border-gray-300 p-2 w-full max-h-32 overflow-y-auto space-y-2">
@foreach($users as $user)
<label class="flex items-center space-x-2">
<input type="checkbox" name="assignees[]" value="{{ $user->plain_id }}" class="rounded text-cyan-600 focus:ring-cyan-500">
<span>{{ $user->name }}</span>
</label>
@endforeach
</div>
<!-- برچسب‌ها -->
<label for="labels" class="font-medium text-sm">برچسب‌ها:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full"
type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2">
<!-- Issue والد -->
<label for="parent" class="font-medium text-sm">پروژه والد:</label>
<select id="parent" name="parent" class="rounded-lg border border-gray-300 p-2 w-full">
@foreach($tasks as $task)
<option value="{{$task->task_id}}">{{$task->name}}</option>
@endforeach
</select>
<!-- امتیاز تخمین -->
<label for="estimate_point" class="font-medium text-sm">Estimate Point (0-7):</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="number" id="estimate_point"
name="estimate_point" min="0" max="7">
<!-- دکمه -->
<div></div>
<button type="submit"
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition">
ایجاد وظیفه
</button>
</form>
</div>
</div>
@endsection
+314 -42
View File
@@ -2,7 +2,107 @@
use Morilog\Jalali\Jalalian;
use Carbon\Carbon;
use App\Models\Pivot_Tasks;
$backlog = <<<SVG
<svg height="12px" width="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg"><g>
<g transform="translate(8 8) rotate(-90)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(-66)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(-42)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(-18)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(6)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(30)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(54)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(78)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(102)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(126)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(150)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(174)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(198)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(222)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g><g transform="translate(8 8) rotate(246)"><line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#60646C" stroke-width="1.21" stroke-linecap="round">
</line></g></g>
</svg>
SVG;
$todo = <<<SVG
<svg width="12px" height="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4"><g></g>
<circle cx="8" cy="8" r="6" fill="none" stroke="#60646C" stroke-width="1.5" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke-linecap="round" transform="rotate(-90 8 8)">
</circle></svg>
SVG;
$error = <<<SVG
<svg width="12px" height="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4"><g><g transform="translate(8 8) rotate(102)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(126)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(150)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(174)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(198)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(222)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g><g transform="translate(8 8) rotate(246)">
<line x1="5.75" y1="0" x2="6.5" y2="0" stroke="#eb144c" stroke-width="1.21" stroke-linecap="round"></line></g></g><circle cx="6" cy="6" r="3" stroke-width="1.5" stroke-linecap="round" fill="none" transform="rotate(-90 8 6)" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke="#eb144c">
</circle><circle cx="8" cy="8" r="6" fill="none" stroke="#eb144c" stroke-width="1.5" stroke-dasharray="37.69911184307752" stroke-dashoffset="18.84955592153876" stroke-linecap="round" transform="rotate(-90 8 8)"></circle></svg>
SVG;
$in_progress = <<<SVG
<svg width="12px" height="12px" viewBox="0 0 16 16" class="flex-shrink-0 flex-shrink-0 size-4"><g></g><circle cx="6" cy="6" r="3" stroke-width="1.5" stroke-linecap="round" fill="none" transform="rotate(-90 8 6)" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke="#F59E0B"></circle><circle cx="8" cy="8" r="6" fill="none" stroke="#F59E0B" stroke-width="1.5" stroke-dasharray="37.69911184307752" stroke-dashoffset="0" stroke-linecap="round" transform="rotate(-90 8 8)"></circle></svg>
SVG;
$done = <<<SVG
<svg height="12px" width="12px" class="flex-shrink-0 flex-shrink-0 size-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" percentage="1"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11.3587 6.18828C11.6007 5.85214 11.5244 5.38343 11.1882 5.14141C10.8521 4.89938 10.3834 4.97568 10.1414 5.31183L7.03706 9.62335L5.25956 7.97751C4.95563 7.69609 4.4811 7.71434 4.19968 8.01828C3.91826 8.32221 3.93651 8.79673 4.24045 9.07815L6.64045 11.3004C6.79816 11.4464 7.01095 11.5178 7.22481 11.4963C7.43868 11.4749 7.63307 11.3627 7.75865 11.1883L11.3587 6.18828Z" fill="#46A758"></path></svg>
SVG;
$cancelled = <<<SVG
<svg height="12px" width="12px" class="flex-shrink-0 flex-shrink-0 size-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" percentage="1"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11.1018 4.89826C11.3947 5.19115 11.3947 5.66603 11.1018 5.95892L9.06068 8.00002L11.1018 10.0411C11.3947 10.334 11.3947 10.8089 11.1018 11.1018C10.8089 11.3947 10.334 11.3947 10.0411 11.1018L8.00002 9.06068L5.95892 11.1018C5.66603 11.3947 5.19115 11.3947 4.89826 11.1018C4.60537 10.8089 4.60537 10.334 4.89826 10.0411L6.93936 8.00002L4.89826 5.95892C4.60537 5.66603 4.60537 5.19115 4.89826 4.89826C5.19115 4.60537 5.66603 4.60537 5.95892 4.89826L8.00002 6.93936L10.0411 4.89826C10.334 4.60537 10.8089 4.60537 11.1018 4.89826Z" fill="#9AA4BC"></path></svg>
SVG;
$state = [
"c73c2044-ea14-42cb-b08d-cecf3d1fdfb5" => "$in_progress",// in progress
"a9ce312f-cfca-491e-8e3d-1975aa6ebc10" => "$todo",//to do
"dea1fb57-a281-4fe7-8f3e-ee5567637e35" => "$error",// error
"770fbe42-99f6-41db-9b9e-773d9cced1a5" => "$backlog",//backlog
"b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0" => "$done",//done
"d61d14a3-72f7-4f44-894b-efe8b2a75be1" => "$cancelled",//canceled
];
$p_tasks = Pivot_Tasks::get();
foreach ($work_item as $work) {
$duration = '00:00:00';
$totalSeconds = 0;
$items = $sub_work_items->where('parent', $work->task_id);
if (count($items) >= 1) {
foreach ($items as $item) {
$p_t = $p_tasks->where('task_id', $item->task_id)->first();
// dump($item->task_id, $p_t?->duration);
if (!empty($p_t)) {
$item->p_id = $p_t->id;
$item->started_at = $p_t->started_at;
$item->ended_at = $p_t->ended_at;
$item->duration = $p_t->duration;
if ($p_t->duration !== null) {
// dump($p_t->duration);
[$h1, $m1, $s1] = explode(':', $p_t->duration);
$durationSeconds = $h1 * 3600 + $m1 * 60 + $s1;
$totalSeconds += $durationSeconds;
}
}
}
$hours = floor($totalSeconds / 3600);
$minutes = floor(($totalSeconds % 3600) / 60);
$seconds = $totalSeconds % 60;
$duration = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
}
$work->total_duration = $duration ?? '00:00:00';
}
//dd($work_item,'sub',$sub_work_items);
?>
@extends('app')
@section('content')
@@ -10,61 +110,233 @@ use Carbon\Carbon;
<div class="container w-full flex justify-center items-center flex-col ">
{{-- {{dd(count($tasks))}}--}}
@if(count($tasks) > 0)
<div class="bg-white p-4 rounded-lg shadow-md mt-10 w-[350px] text-center">
<h1 class="text-xl font-bold">
وظیفه ها
</h1>
</div>
@foreach($tasks as $t)
{{-- <div class="bg-white p-4 rounded-lg shadow-md mt-10 w-[350px] text-center">--}}
{{-- <h1 class="text-xl font-bold">--}}
{{-- وظیفه ها--}}
{{-- </h1>--}}
{{-- </div>--}}
<div class="mt-10">
@foreach($work_item as $t)
{{-- {{dump(Pivot_Tasks::where('task_id',$t->task_id)->whereNotNull('duration')->first())}}--}}
{{-- {{dump($t->total_duration)}}--}}
<div class="bg-white p-4 rounded-lg shadow-md mt-[10px] w-[350px] text-center flex flex-col items-center">
<button type="button"
onclick="togglerequests('name-{{ $loop->index }}')"
class="relative flex items-center justify-center w-full text-center py-2">
{{-- <div class="bg-white p-4 rounded-lg shadow-md mt-[10px] w-[350px] text-center">--}}
{{-- <button type="button"--}}
{{-- onclick="togglerequests('attendance-{{ $loop->index }}')"--}}
{{-- class="mt-[0px]">--}}
{{-- hi--}}
{{-- </button>--}}
{{-- </div>--}}
<div class="bg-white p-4 rounded-lg shadow-md mt-[10px] w-[350px] text-center flex flex-col items-center">
<button type="button"
onclick="togglerequests('name-{{ $loop->index }}')"
class="">
{{$t->name}}
</button>
{{-- @endforeach--}}
{{-- @foreach($tasks as $t)--}}
<div id="name-{{ $loop->index }}" class="hidden border-gray-300 border-t-2 border-dotted">
<div class=" w-[350px] text-center">
<p class="mt-2 text-gray-600">
توضیحات: {!! $t->description_html !!}
</p>
<!-- آیکون سمت چپ -->
{!! array_key_exists($t->state,$state) ? $state[$t->state] : "" !!}
@if(isset($t->start_date))
<p class="mt-2 text-gray-600">
تاریخ شروع: {{$t->start_date}}
</p>
@endif
@if(isset($t->target_date))
<p class="mt-2 text-gray-600">
تاریخ
پایان: {{$t->target_date ? Jalalian::fromCarbon(Carbon::parse($t->target_date))->format('Y/m/d') : ''}}
</p>
@endif
<!-- متن وسط -->
<span class="mx-auto">{{$t->name}}</span>
</button>
{{-- @endforeach--}}
{{-- @foreach($tasks as $t)--}}
<div id="name-{{ $loop->index }}" class="hidden border-gray-300 border-t-2 border-dotted">
<div class=" w-[350px] text-center flex flex-col items-center">
@if(isset($t->description_html) && strlen(trim(strip_tags($t->description_html))) > 0)
<p class="mt-2 text-gray-600 ">
توضیحات: {!! $t->description_html !!}
</p>
@endif
@if(isset($t->start_date))
<p class="mt-2 ">
شروع وظیفه: {{$t->start_date ? Jalalian::fromCarbon(Carbon::parse($t->start_date))->format('Y/m/d') : ''}}
</p>
@endif
@if(isset($t->target_date))
<p class="mt-2 ">
پایان وظیفه: {{$t->target_date ? Jalalian::fromCarbon(Carbon::parse($t->target_date))->format('Y/m/d') : ''}}
</p>
@endif
<p class="mt-2 text-gray-600 border-gray-300 {{ isset($t->description_html) && strlen(trim(strip_tags($t->description_html))) > 0 ?
'border-t-2 border-dotted w-full pt-2' : ''}}">
ایجاد شده توسط {{$t->created_by}}
</p>
<p class="mt-2 text-gray-600">
تاریخ ایجاد: {{$t->created_at ? Jalalian::fromCarbon(Carbon::parse($t->created_at))->format('Y/m/d') : ''}}
</p>
@if($t->total_duration !== '00:00:00')
<p class="mt-2 text-gray-600">
مجموع زمان صرف شده: {{$t->total_duration}}
</p>
@endif
@php
$task_items = $sub_work_items->where('parent',$t->task_id)
@endphp
@foreach($task_items as $item)
<div class=" form w-[350px] text-center border-gray-300 border-2 border-dotted max-w-80 mt-2"
>
<style>
.form ul li {
display: flex;
align-items: flex-start;
gap: 8px;
text-align: right;
}
.form ul li input[type="checkbox"] {
margin-top: 3px;
accent-color: #3b82f6; /* آبی Tailwind */
}
.form ul li p {
margin: 0;
}
</style>
{{-- {{$t}}--}}
<a onclick="openTaskModal(
'{{ $item->task_id }}',
'{{ $item->p_id ?? '' }}',
'{{ $item->started_at ?? '' }}',
'{{ $item->ended_at ?? '' }}',
'{{ $item->ended_at ? 'start' : ($item->started_at ? 'end' : 'start') }}'
)"
class="relative flex items-center justify-center w-full text-center py-2
font-bold text-base rounded hover:bg-gray-500 hover:text-white transition cursor-pointer">
<span class="">{{ $item->name }}</span>
</a>
@if(isset($t->description_html) && strlen(trim(strip_tags($item->description_html))) > 0)
<p class="mt-2 text-gray-600">
توضیحات: {!! $item->description_html !!}
</p>
@endif
@if(isset($item->start_date))
<lable class="mt-2 text-gray-600">
تاریخ شروع: {{$item->start_date}}
</lable>
@endif
@if(isset($item->target_date))
<p class="mt-2 text-gray-600">
تاریخ
پایان: {{$item->target_date ? Jalalian::fromCarbon(Carbon::parse($item->target_date))->format('Y/m/d') : ''}}
</p>
@endif
</div>
@endforeach
</div>
</div>
</div>
</div>
@endforeach
@endforeach
</div>
@else
<div class=" mt-[200px]">
<h1 class=" text-xl font-bold" style="color: #959595">وظیفه ای ندارید</h1>
</div>
@endif
</div>
{{-- START TASK MODAL --}}
<div id="taskModal" class="fixed inset-0 bg-black/40 hidden items-center justify-center z-50">
<div class="bg-white w-[350px] rounded-2xl shadow-lg p-5 text-center">
<h2 class="text-lg font-bold mb-3" id="modalTitle">وضعیت وظیفه</h2>
<p class="text-gray-600 mb-4" id="modalDesc"></p>
<div class="flex justify-center gap-3">
<button id="actionBtn"
class="px-4 py-2 rounded-lg font-semibold text-white bg-blue-600 hover:bg-blue-700 transition">
شروع
</button>
<button id="cancelBtn"
class="px-4 py-2 rounded-lg font-semibold bg-gray-300 hover:bg-gray-400 transition">
انصراف
</button>
</div>
</div>
</div>
@endsection
@section('script')
<script>
function togglerequests(elementId) {
var contentDiv = document.getElementById(elementId);
contentDiv.classList.toggle('hidden'); // همین یه خط کافیست
let currentTask = {};
function openTaskModal(taskId, p_id, started_at, ended_at, actionType) {
currentTask = {taskId, p_id, started_at, ended_at, actionType};
const modal = document.getElementById('taskModal');
const actionBtn = document.getElementById('actionBtn');
const modalTitle = document.getElementById('modalTitle');
const modalDesc = document.getElementById('modalDesc');
// تعیین متن دکمه بر اساس actionType
actionBtn.textContent = actionType === 'start' ? 'شروع' : 'پایان';
if (actionType === 'start') {
actionBtn.classList.remove('bg-green-600', 'hover:bg-green-700');
actionBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
modalDesc.textContent = 'آیا می‌خواهید این وظیفه را شروع کنید؟';
} else {
actionBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
actionBtn.classList.add('bg-green-600', 'hover:bg-green-700');
modalDesc.textContent = 'آیا می‌خواهید این وظیفه را پایان دهید؟';
}
modal.classList.remove('hidden');
modal.classList.add('flex');
// عملکرد دکمه شروع/پایان
actionBtn.onclick = () => handleTaskAction();
document.getElementById('cancelBtn').onclick = () => closeModal();
}
function closeModal() {
const modal = document.getElementById('taskModal');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
function handleTaskAction() {
// ثبت زمان فعلی با فرمت Y-m-d H:i:s
const now = new Date();
const pad = n => n.toString().padStart(2, '0');
const formattedNow = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
if (currentTask.actionType === 'start') {
currentTask.started_at = formattedNow;
} else {
currentTask.ended_at = formattedNow;
}
// مسیر API لاراول که باید POST بزنی
fetch('{{ url("/task-manager") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({
task_id: currentTask.taskId,
p_id: currentTask.p_id,
started_at: currentTask.started_at,
ended_at: currentTask.ended_at,
action_type: currentTask.actionType
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
console.log('Task updated', data);
closeModal();
} else {
alert('خطا در انجام عملیات!');
}
})
.catch(err => {
console.error(err);
alert('خطا در ارتباط با سرور!');
});
}
function togglerequests(elementId) {
document.getElementById(elementId).classList.toggle('hidden');
}
</script>
@endsection