Files
a.montask/resources/views/attendance/index.blade.php
T

1245 lines
64 KiB
PHP
Executable File

<?php
use App\Models\Attendance;
use App\Models\User;
use App\Models\ai;
use Morilog\Jalali\Jalalian;
use Carbon\Carbon;
use Illuminate\Support\Collection;
if (!empty($user->tasks)) {
$in_progress = '#fca000';
$todo = '#7d7d7d';
$error = '#fc0000';
$backlog = '#bababa';
$done = '#40de02';
$cancelled = 'black';
$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);
});
}
//dd(count($break = $user->attendanceBreaks));
if (count($user->attendanceBreaks) > 0) {
$break = $user->attendanceBreaks[0];
if ($break->break_end == null) {
$now = Carbon::now()->format('Y-m-d H:i:s');
$attendances = new Attendance();
$duration = $attendances->duration($break->break_start, $now);
}
}
//dd($now, $break->break_start, $duration);
?>
@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>
sghl<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 id="output" class="text-red-500 text-sm mt-2 text-center"></div>
<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>
{{--<p>{{Carbon::now()}}</p>--}}
</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">
<label class="text-xl font-bold pb-2">
{{$user->name}}
</label>
<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>
</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 => {
const lat = 32.6520532791536; // از مختصات ثابت استفاده کنید
const lng = 51.68472614422439; // از مختصات ثابت استفاده کنید
const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng));
distanceInput.value = dist;
distanceMessage.textContent = `مکان: خارج از شرکت`;
distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed");
distanceMessage.classList.add("text-orange-500");
submitBtn.disabled = false;
submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed");
submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer");
// 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;
}
// یک چالش تصادفی بسازید
const challenge = new Uint8Array(32);
window.crypto.getRandomValues(challenge);
// گزینه‌های احراز هویت (Authentication)
const getOptions = {
publicKey: {
challenge: challenge,
timeout: 60000,
rpId: window.location.hostname, // اختیاری؛ برای لوکال‌هاست خالی بگذارید
userVerification: "required",
allowCredentials: [] // خالی بگذارید تا هر کلید موجود نمایش داده شود
}
};
try {
// به جای create از get استفاده کنید
const assertion = await navigator.credentials.get(getOptions);
console.log('احراز هویت موفق:', assertion);
// اینجا فرم را سابمیت کنید یا هر عملیات دیگر
attendanceForm.submit();
} catch (error) {
const message = error.name === 'NotAllowedError'
? 'احراز هویت لغو شد یا دسترسی مجاز نبود.'
: 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
const output = document.getElementById('output');
if (output) {
output.textContent = message;
} else {
alert(message);
}
console.error(error);
}
});
</script>
<div class="w-full flex justify-center mt-4 pb-20" style="">
<div class="overflow-hidden rounded-[23px] 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']))
{{-- قسمت پردازش داده‌ها: گروه‌بندی لیست حضور و غیاب بر اساس نام ماه شمسی --}}
@php
// بررسی وجود داده
$attendances = $user->attendances ?? collect();
// گروه‌بندی داده‌ها بر اساس نام ماه (مثلا: مهر، آبان)
$groupedData = $attendances->groupBy(function($item) {
return \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($item->entered_at))->format('%B');
});
@endphp
<div class="container inline-flex flex-col items-center justify-center">
@if($groupedData->isEmpty())
<div class="mt-80 text-gray-500 text-xl font-bold italic">
<h1>اطلاعاتی موجود نمیباشد</h1>
</div>
@else
{{-- دکمه‌های تب (نام ماه‌ها) --}}
<div class="tab rounded-t-2xl max-w-[350px] mx-auto flex overflow-x-auto"
style="margin-top: 0px; max-width: 350px">
@foreach($groupedData->keys() as $month)
<button class="tablinks rounded-t-2xl cursor-pointer px-4 py-2 hover:bg-gray-200 transition flex-shrink-0"
onclick="openCity(event, '{{$month}}')">
{{$month}}
</button>
@endforeach
</div>
{{-- محتوای تب‌ها (جدول‌ها) --}}
@foreach($groupedData as $month => $records)
<div id="{{$month}}"
class="tabcontent w-[350px] bg-white rounded-b-xl rounded-tl-2xl cursor-default leading-7 shadow-md">
<table class="table-auto"
style="width:100%; border-collapse: collapse; text-align: center">
<thead>
<tr style="border-bottom: 1px solid #ddd; background: rgba(244,244,244,0.82);"
class="border-1 border-slate-300">
@if($_COOKIE['user_identity'] == 1370)
<th style="border-right: 1px solid #ddd; padding: 8px;">نام</th>
@endif
<th style="border-right: 1px solid #ddd; padding: 4px;">تاریخ</th>
<th style="border-right: 1px solid #ddd; padding: 4px;">ورود</th>
<th style="border-right: 1px solid #ddd; padding: 4px;">خروج</th>
<th style="border-right: 1px solid #ddd; padding: 4px;">مدت</th>
</tr>
</thead>
<tbody>
@foreach($records as $item)
<tr class="hover:bg-cyan-500 cursor-pointer transition duration-150"
style="border-bottom: 1px solid #ddd; background-color: {{$item->requested ? '#bfdbfe' : '#ffffff' }}"
onclick="openModal('{{ $item->id }}', '{{ $item->id }}' , '{{ $item->entered_at }}', '{{ $item->exited_at }} ')">
{{-- شرط نمایش نام --}}
@if($_COOKIE['user_identity'] == 1370)
<td style="border-right: 1px solid #ddd; padding: 8px;">
{{$item->user_identity}}
</td>
@endif
{{-- تاریخ --}}
<td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{"#e6e6e6" }};">
{{ $item->entered_at ? \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($item->entered_at))->format('m/d') : '-' }}
<span class="text-xs text-gray-500 block">
{{ $item->entered_at ? \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($item->entered_at))->format('%A') : '' }}
</span>
</td>
{{-- زمان ورود --}}
<td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{$item->entered_at && $item->distance ? ($item->requested !== null ? '#fad1d1' :($item->distance < 50 ? '#d1fae5' : '#edae80')) : '' }};">
{{ $item->entered_at ? \Carbon\Carbon::parse($item->entered_at)->format('H:i') : '-' }}
</td>
{{-- زمان خروج --}}
<td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{$item->exited_at && $item->distance_exit ? ($item->requested !== null ? '#fad1d1' :($item->distance_exit < 50 ? '#d1fae5' : '#edae80')) : '' }};">
{{ $item->exited_at ? \Carbon\Carbon::parse($item->exited_at)->format('H:i') : '-' }}
</td>
{{-- مدت زمان --}}
<td style="border-right: 1px solid #ddd; padding: 4px;">
{{ $item->entered_at && $item->exited_at ? \Carbon\Carbon::parse($item->duration)->format('H:i') : '-' }}
</td>
</tr>
@endforeach
{{-- ردیف جمع کل (اختیاری - چون متغیر آن موجود نبود کامنت کردم که خطا ندهد) --}}
<tr>
<td colspan="5" class="rounded-b-xl bg-gray-50 font-bold"
style="border-top: 1px solid #ddd; text-align: center; padding: 10px;">
تعداد رکورد: {{ count($records) }}
</td>
</tr>
</tbody>
</table>
</div>
@endforeach
@endif
</div>
<script>
function openCity(evt, Month) {
var i, tabcontent, tablinks;
// مخفی کردن تمام محتواها
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// غیرفعال کردن تمام تب‌ها
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active bg-gray-300 font-bold", "");
// اضافه کردن استایل دیفالت اگر کلاس اکتیو حذف شد
if (!tablinks[i].className.includes("active")) {
tablinks[i].style.backgroundColor = "";
}
}
// نمایش تب انتخاب شده
document.getElementById(Month).style.display = "block";
evt.currentTarget.className += " active bg-gray-300 font-bold";
}
// باز کردن اولین تب به صورت خودکار هنگام لود صفحه
document.addEventListener("DOMContentLoaded", function () {
let firstTab = document.querySelector(".tablinks");
if (firstTab) {
firstTab.click();
}
});
</script>
@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=""
placeholder="تاریخ"
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"
onclick="closeOnOutsideClick(event)">
<div class="relative bg-white w-[350px] rounded-2xl shadow-lg p-5 text-center">
<button onclick="closeTaskModal()"
class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 transition">
<i class="fa-solid fa-xmark"></i>
</button>
<h2 class="text-lg font-bold mb-3" id="modalTitle">انتخاب وظیفه</h2>
<p class="text-gray-600 mb-4" id="modalDesc">یکی از وظایف زیر را انتخاب کنید:</p>
<input class="h-12 w-full bg-transparent pl-11 pr-4 text-sm text-custom-text-100
placeholder:text-custom-text-400 focus:ring-0 rounded-3xl border-2 border-gray-300 py-2"
placeholder="جست و جو..." tabindex="1" id="headlessui-combobox-input-:r13m:" role="combobox"
aria-expanded="false" aria-autocomplete="list" data-headlessui-state="" type="text" value=""
aria-controls="headlessui-combobox-options-:r13n:"
aria-activedescendant="headlessui-combobox-option-:r13s:" data-sharkid="__4">
{{-- <div class="grid flex-col gap-2 grid-cols-2 pb-3 items-center justify-items-end">--}}
@if(isset($user->tasks) && count($user->tasks) >= 1)
<form action="{{url("active_task")}}" method="POST" class=" ">@csrf
{{-- <input type="hidden" name="pid" value="{{ $user->tasks->pivot->id }}" class="">--}}
<input type="hidden" name="uid" value="{{ $user->id }}" class="">
<div class=" overflow-y-scroll"
style="max-height: calc(100vh - 300px);">
@foreach($user->tasks as $item)
<div class="inline-flex items-center bg-gray-300 hover:bg-cyan-600 rounded-lg m-2 p-2 justify-between"
style="display: flex">
@if($user->active_task)
@if( $user->active_task == $item->name )
<input type="radio" name="task_id" checked="true"
value="{{ $item->task_id }}"
class="">
@else
<input type="radio" name="task_id" value="{{ $item->task_id }}" class=""
disabled>
@endif
@else
<input type="radio" name="task_id" value="{{ $item->task_id }}" class="">
{{-- <input type="hidden" name="task_name" value="{{$item->name}}">--}}
@endif
<div class="px-2.5" style="direction: rtl">{{ $item->name }}</div>
<span class="block h-1.5 w-1.5 flex-shrink-0 rounded-full" style="background-color:
{!! array_key_exists($item->state,$state) ? $state[$item->state] : "" !!};"></span>
</div>
@endforeach
</div>
@if( $user->active_task )
{{-- <div class="absolute bottom-2 left-0 w-full flex justify-center">--}}
<button class="submit mt-2">
پایان
</button>
{{-- </div>--}}
@else
{{-- <div class="absolute bottom-2 left-0 w-full flex justify-center">--}}
<button class="submit mt-2">
شروع
</button>
{{-- </div>--}}
@endif
</form>
@else
<div class="inline-flex justify-center items-center"
style="height: calc(100vh - 400px);">
<p class="font-bold text-gray-400">
وظیفه ای وجود ندارد
</p>
</div>
@endif
{{-- افزودن تسک--}}
<form>
<input class="h-12 w-[200px] bg-transparent pl-11 pr-4 text-sm text-custom-text-100
placeholder:text-custom-text-400 focus:ring-0 rounded-3xl border-2 border-gray-300 py-2"
placeholder="عنوان وظیفه" tabindex="1" id="taskTitle" role="combobox"
aria-expanded="false" aria-autocomplete="list" data-headlessui-state="" type="text" value=""
aria-controls="headlessui-combobox-options-:r13n:"
aria-activedescendant="headlessui-combobox-option-:r13s:" data-sharkid="__4">
<button id="createTask"
class="mt-4 inline-block bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-2.5 fa-notdog fa-solid fa-plus rounded-full">
</button>
</form>
</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 closeOnOutsideClick(event) {
// وقتی کاربر بیرون از محتوای مودال کلیک کنه، ببندش
if (event.target.id === 'taskModal') {
closeTaskModal();
}
}
// -------------------------------
// آپدیت متن دکمه شروع/پایان
// -------------------------------
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(`⚠️ خطا در ارتباط با سرور یا پردازش داده!`);
});
}
//SEARCH BAR IN TASK MODAL
const searchInput = document.getElementById("headlessui-combobox-input-:r13m:");
const listItems = document.querySelectorAll('#taskModal .inline-flex.items-center');
searchInput.addEventListener("input", function () {
const filter = searchInput.value.toLowerCase();
listItems.forEach(item => {
const nameDiv = item.querySelector('div');
if (nameDiv) {
const name = nameDiv.textContent.toLowerCase();
if (name.includes(filter)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
}
});
});
const taskTitle = document.getElementById('taskTitle');
function createTask(taskTitle) {
// const createTask = document.getElementById('createTask');
if (createTask) {
createTask.addEventListener('click', () => {
createTask.disabled = true;
fetch("{{ url('task/create') }}", {
method: "POST",
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json'
},
body: JSON.stringify({taskTitle})
})
.then(res => {
if (res.ok) {
window.location.reload();
} else {
createTask.textContent = 'خطا';
createTask.disabled = false;
console.error('Server error on create task action:', res.statusText);
}
})
.catch(err => {
createTask.textContent = 'خطای شبکه';
createTask.disabled = false;
console.error('Network error:', err);
});
})
}
}
{{--BREAK--}}
const breakBtn = document.getElementById('breakAttendance');
let breakTimer = null;
let currentSeconds = 0;
// server-provided initial duration (string or null)
const serverDuration = {!! json_encode($duration ?? null) !!};
function parseDurationString(str) {
if (!str) return 0;
const parts = String(str).split(':').map(s => parseInt(s, 10) || 0);
if (parts.length === 3) {
return parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// treat as MM:SS
return parts[0] * 60 + parts[1];
} else if (parts.length === 1) {
return parts[0];
}
return 0;
}
function formatDuration(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
const mm = String(m).padStart(2, '0');
const ss = String(s).padStart(2, '0');
if (h > 0) {
return `${h}:${mm}:${ss}`;
}
return `${mm}:${ss}`;
}
function startBreakCounter(initialSeconds) {
// ensure no duplicate timer
if (breakTimer) clearInterval(breakTimer);
currentSeconds = initialSeconds || 0;
// update immediately
breakBtn.textContent = formatDuration(currentSeconds);
breakBtn.classList.add('bg-red-500');
breakBtn.classList.remove('bg-gray-500');
breakTimer = setInterval(() => {
currentSeconds++;
breakBtn.textContent = formatDuration(currentSeconds);
}, 1000);
}
function stopBreakCounter() {
if (breakTimer) {
clearInterval(breakTimer);
breakTimer = null;
}
currentSeconds = 0;
if (breakBtn) {
breakBtn.textContent = 'استراحت';
breakBtn.classList.remove('bg-red-500');
breakBtn.classList.add('bg-gray-500');
}
}
document.addEventListener('DOMContentLoaded', () => {
if (!breakBtn) return;
if (serverDuration !== null && serverDuration !== '') {
const secs = parseDurationString(serverDuration);
startBreakCounter(secs);
console.log(`استراحت فعال است. زمان شروع از سرور: ${serverDuration} (${secs} ثانیه).`);
} else {
stopBreakCounter();
}
});
if (breakBtn) {
breakBtn.addEventListener('click', () => {
// breakBtn.textContent = '...در حال ارسال';
// breakBtn.disabled = true;
fetch("{{ url('attendance/break') }}", {
method: "POST",
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json'
}
})
.then(res => {
if (res.status == 'error') {
console.log(res);
window.location.reload();
} else {
console.log(res);
breakBtn.textContent = 'خطا';
breakBtn.disabled = false;
console.error('Server error on break action:', res.statusText);
window.location.reload();
}
})
.catch(err => {
console.log(err);
window.location.reload();
// breakBtn.textContent = 'خطای شبکه';
// breakBtn.disabled = false;
// console.error('Network error:', err);
});
});
}
{{--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');
const hiddenIdInput = document.getElementById('edit_id');
// const hiddenIdInput = document.getElementById('delete_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();
};
}
//date and clock picker
$('.clockpicker').clockpicker({
autoclose: false,
placement: 'center',
donetext: 'ثبت',
vibrate: 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: "امروز"}}
},
});
}
$(document).ready(function () {
// 1. فعال‌سازی Clockpicker
$('.clockpicker').clockpicker({
autoclose: false,
placement: 'center',
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