1119 lines
65 KiB
PHP
Executable File
1119 lines
65 KiB
PHP
Executable File
<?php
|
|
|
|
use Morilog\Jalali\Jalalian;
|
|
use Carbon\Carbon;
|
|
use App\Models\User;
|
|
use App\Models\App;
|
|
|
|
|
|
$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 = [
|
|
1 => "$backlog",//backlog
|
|
2 => "$todo",//to do
|
|
3 => "$in_progress",// in progress
|
|
4 => "$done",//done
|
|
];
|
|
|
|
//$p_tasks = Pivot_Tasks::get();
|
|
|
|
|
|
?>
|
|
@extends('app')
|
|
@section('content')
|
|
|
|
<div class="container w-full flex justify-center items-center flex-col ">
|
|
{{-- {{dd(count($tasks))}}--}}
|
|
@if($count > 0)
|
|
<div class="w-full max-w-6xl mx-auto mt-10 px-2">
|
|
<div class="flex flex-nowrap gap-1 border-b border-gray-200">
|
|
@foreach(array_keys($tasks) as $stateKey)
|
|
<button onclick="switchTaskTab('{{ $stateKey }}')"
|
|
class="task-tab px-4 py-2 rounded-t-lg font-medium text-sm transition
|
|
{{ $loop->first ? 'bg-white text-blue-600 border-t border-x border-gray-200' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100' }}"
|
|
data-tab="{{ $stateKey }}">
|
|
{{ $stateKey }}
|
|
<span class="mr-1 text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded-full">
|
|
{{ count($tasks[$stateKey] ?? []) }}
|
|
</span>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
|
|
{{-- جداول هر وضعیت --}}
|
|
@foreach($tasks as $stateKey => $stateTasks)
|
|
<div id="tab-{{ $stateKey }}"
|
|
class="task-table-container {{ $loop->first ? '' : 'hidden' }} overflow-x-auto bg-white rounded-b-lg shadow">
|
|
@if(count($stateTasks) > 0)
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">عنوان
|
|
</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">
|
|
اولویت
|
|
</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">{{ $isAdminLevel1 ? 'نام اپ' : 'تاریخ شروع' }}
|
|
</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">مهلت
|
|
</th>
|
|
{{-- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">جزئیات</th>--}}
|
|
|
|
{{-- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">مدت زمان</th>--}}
|
|
{{-- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">عملیات</th>--}}
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
@foreach($stateTasks as $task)
|
|
@php
|
|
// دریافت نام ایجادکننده (با فرض وجود رابطه 'creator' در مدل Tasks)
|
|
$creatorName = 'نامشخص';
|
|
if ($task->relationLoaded('creator') && $task->creator) {
|
|
$creatorName = $task->creator->name;
|
|
} elseif ($task->created_by) {
|
|
// اگر رابطه لود نشده، یک بار کاربر را پیدا کن (توصیه میشود در کنترلر eager load شود)
|
|
$user = \App\Models\User::find($task->created_by);
|
|
$creatorName = $user ? $user->name : $task->created_by;
|
|
}
|
|
$task->creator_name = $creatorName;
|
|
// آیکون وضعیت
|
|
$stateIcon = '';
|
|
switch($task->state){
|
|
case 1: $stateIcon = $backlog; break;
|
|
case 2: $stateIcon = $todo; break;
|
|
case 3: $stateIcon = $in_progress; break;
|
|
case 4: $stateIcon = $done; break;
|
|
default: $stateIcon = '';
|
|
}
|
|
|
|
// تعیین وضعیت شروع/پایان برای دکمه
|
|
$started_at = $lastDuration?->start ?? null;
|
|
$ended_at = $lastDuration?->end ?? null;
|
|
$actionType = $ended_at ? 'start' : ($started_at ? 'end' : 'start');
|
|
$p_id = $lastDuration?->id ?? '';
|
|
@endphp
|
|
{{--@if($_COOKIE['user_identity'] == 'naser_me') {{dump($task)}} @endif--}}
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-2 py-2 {{ $task->active ? 'border-s-4 border-cyan-400 ' : '' }}">
|
|
<div class="flex items-center gap-2">
|
|
<span class="inline-flex items-center justify-center w-5 h-5">{!! $stateIcon !!}</span>
|
|
<div>
|
|
<div class="font-small text-gray-900 text-sm font-bold">{{ $task->title }}</div>
|
|
@if($task->description)
|
|
<div class="text-xs text-gray-500 mt-1 line-clamp-2">{!! strip_tags($task->description) !!}</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-2 py-2">
|
|
<span class="px-2 py-1 text-xs rounded-full {{ $priorityColors[$task->priority] ?? 'bg-gray-100 text-gray-800' }}">
|
|
{{ $priorityLabels[$task->priority] ?? $task->priority }}
|
|
</span>
|
|
</td>
|
|
<td class="px-2 py-2 text-sm text-gray-600">
|
|
{{ $isAdminLevel1 ? $task->appName->name_fa : Jalalian::fromCarbon(Carbon::parse($task->start_date))->format('Y/m/d') }}
|
|
</td>
|
|
<td class="px-2 py-2 text-sm text-gray-600">
|
|
{{ $task->target_date ? Jalalian::fromCarbon(Carbon::parse($task->target_date))->format('Y/m/d') : '-' }}
|
|
</td>
|
|
{{-- <td class="px-2 py-2 text-sm text-gray-600 font-mono">--}}
|
|
{{-- {{ $totalDuration }}--}}
|
|
{{-- </td>--}}
|
|
{{-- <td class="px-2 py-2">--}}
|
|
{{-- <button onclick="openTaskModal(--}}
|
|
{{-- '{{ $task->id }}',--}}
|
|
{{-- '{{ $p_id }}',--}}
|
|
{{-- '{{ $started_at }}',--}}
|
|
{{-- '{{ $ended_at }}',--}}
|
|
{{-- '{{ $actionType }}'--}}
|
|
{{-- )" class="text-sm bg-blue-50 text-blue-700 px-3 py-1.5 rounded-lg hover:bg-blue-100 transition flex items-center gap-1">--}}
|
|
{{-- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">--}}
|
|
{{-- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>--}}
|
|
{{-- </svg>--}}
|
|
{{-- {{ $actionType == 'start' ? 'شروع' : 'پایان' }}--}}
|
|
{{-- </button>--}}
|
|
{{-- </td>--}}
|
|
{{-- ستون جزئیات --}}
|
|
<td class="px-2 py-2">
|
|
<button onclick='openTaskDetailsModal(@json($task))'
|
|
class="text-sm bg-gray-50 text-gray-700 px-3 py-1.5 rounded-lg hover:bg-gray-100 transition flex items-center gap-1">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor"
|
|
viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
|
</svg>
|
|
</button>
|
|
</td>
|
|
|
|
{{-- ستون زمان (همان دکمه شروع/پایان قبلی) --}}
|
|
{{-- <td class="px-2 py-2">--}}
|
|
{{-- <button onclick="openTaskTimeModal(--}}
|
|
{{-- '{{ $task->id }}',--}}
|
|
{{-- '{{ $p_id }}',--}}
|
|
{{-- '{{ $started_at }}',--}}
|
|
{{-- '{{ $ended_at }}',--}}
|
|
{{-- '{{ $actionType }}'--}}
|
|
{{-- )" class="text-sm bg-blue-50 text-blue-700 px-3 py-1.5 rounded-lg hover:bg-blue-100 transition flex items-center gap-1">--}}
|
|
{{-- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">--}}
|
|
{{-- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>--}}
|
|
{{-- </svg>--}}
|
|
{{-- {{ $actionType == 'start' ? 'شروع' : 'پایان' }}--}}
|
|
{{-- </button>--}}
|
|
{{-- </td>--}}
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
@else
|
|
<div class="p-8 text-center text-gray-400 bg-white rounded-b-lg">
|
|
هیچ وظیفهای در این وضعیت وجود ندارد.
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
@else
|
|
<div class=" mt-[200px]">
|
|
<h1 class=" text-xl font-bold" style="color: #959595">وظیفه ای ندارید</h1>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
|
|
|
|
{{-- START TASK DETAILS MODAL --}}
|
|
<div id="taskDetailsModal" class="fixed inset-0 bg-black/40 hidden items-center justify-center z-50 p-4">
|
|
<div class="bg-white w-full max-w-md rounded-2xl shadow-xl max-h-[90vh] overflow-y-auto">
|
|
{{-- هدر مودال --}}
|
|
<div class="flex justify-between items-center p-4 border-b border-gray-200">
|
|
<h2 class="text-lg font-bold text-gray-800" id="modalTaskTitle">عنوان تسک</h2>
|
|
<button onclick="closeDetailsModal()" class="text-gray-400 hover:text-gray-600">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="p-4 space-y-4">
|
|
{{-- Tabs --}}
|
|
<div class="flex gap-2 bg-white sticky top-0 z-10">
|
|
<button id="modalTabBtn-info" type="button"
|
|
onclick="switchDetailsTab('info')"
|
|
class="flex-1 px-3 py-2 text-sm rounded-lg bg-gray-100 text-gray-800">
|
|
جزئیات
|
|
</button>
|
|
<button id="modalTabBtn-durations" type="button"
|
|
onclick="switchDetailsTab('durations')"
|
|
class="flex-1 px-3 py-2 text-sm rounded-lg text-gray-500 hover:bg-gray-50">
|
|
زمانها
|
|
</button>
|
|
<button id="modalTabBtn-comments" type="button"
|
|
onclick="switchDetailsTab('comments')"
|
|
class="flex-1 px-3 py-2 text-sm rounded-lg text-gray-500 hover:bg-gray-50">
|
|
کامنتها
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Tab: info --}}
|
|
<div id="modalTab-info" class="space-y-4">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">توضیحات</h3>
|
|
<p class="text-gray-800 text-sm leading-relaxed" id="modalTaskDescription">-</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div class="col-span-2">
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">اپ</h3>
|
|
<p class="text-gray-800 text-sm" id="modalTaskAppName">-</p>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">اولویت</h3>
|
|
<span id="modalTaskPriorityView"
|
|
class="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800 inline-block">-</span>
|
|
<select id="modalTaskPriorityEdit"
|
|
class="hidden mt-2 rounded-lg border border-gray-300 p-2 w-full text-sm">
|
|
<option value="none">ندارد</option>
|
|
<option value="low">کم</option>
|
|
<option value="medium">عادی</option>
|
|
<option value="high">زیاد</option>
|
|
<option value="urgent">فوری</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">وضعیت</h3>
|
|
<span id="modalTaskStateView"
|
|
class="px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800">-</span>
|
|
<select id="modalTaskStateEdit"
|
|
class="hidden mt-2 rounded-lg border border-gray-300 p-2 w-full text-sm">
|
|
<option value="1">انباشته</option>
|
|
<option value="2">برای انجام</option>
|
|
<option value="3">در حال انجام</option>
|
|
<option value="4">پایان یافته</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">تاریخ شروع</h3>
|
|
<p class="text-gray-800 text-sm" id="modalTaskStartDateView">-</p>
|
|
<input id="modalTaskStartDateEdit" type="text"
|
|
class="hidden mt-2 rounded-lg border border-gray-300 p-2 w-full text-sm"
|
|
placeholder="تاریخ شروع" autocomplete="off" readonly>
|
|
<input id="modalTaskStartDateGregorian" type="hidden">
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">مهلت</h3>
|
|
<p class="text-gray-800 text-sm" id="modalTaskTargetDateView">-</p>
|
|
<input id="modalTaskTargetDateEdit" type="text"
|
|
class="hidden mt-2 rounded-lg border border-gray-300 p-2 w-full text-sm"
|
|
placeholder="مهلت" autocomplete="off" readonly>
|
|
<input id="modalTaskTargetDateGregorian" type="hidden">
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">زمان صرف شده</h3>
|
|
<p class="text-gray-800 text-sm font-mono" id="modalTaskDuration">00:00:00</p>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-sm font-medium text-gray-500 mb-1">ایجادکننده</h3>
|
|
<p class="text-gray-800 text-sm" id="modalTaskCreator">-</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-2 pt-2">
|
|
<button id="modalQuickEditBtn" type="button"
|
|
class="flex-1 px-4 py-2 rounded-lg font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 transition">
|
|
ویرایش سریع
|
|
</button>
|
|
<button id="modalEditTaskBtn" type="button"
|
|
class="px-4 py-2 rounded-lg font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 transition">
|
|
ویرایش کامل
|
|
</button>
|
|
<button id="modalDeleteTaskBtn" type="button"
|
|
class="px-4 py-2 rounded-lg font-medium text-gray-700 bg-red-100 hover:bg-gray-200 transition">
|
|
حذف
|
|
</button>
|
|
</div>
|
|
|
|
<div id="modalQuickEditActions" class="hidden flex gap-2">
|
|
<button id="modalQuickEditSaveBtn" type="button"
|
|
class="flex-1 px-4 py-2 rounded-lg font-medium text-white bg-cyan-600 hover:bg-cyan-700 transition">
|
|
ذخیره
|
|
</button>
|
|
<button id="modalQuickEditCancelBtn" type="button"
|
|
class="px-4 py-2 rounded-lg font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 transition">
|
|
انصراف
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Tab: durations --}}
|
|
<div id="modalTab-durations" class="hidden space-y-2">
|
|
<div class="text-xs text-gray-500" id="modalDurationsSummary">
|
|
-
|
|
</div>
|
|
<div class="flex gap-2 items-center">
|
|
<button id="modalDurationActionBtn" type="button"
|
|
class="flex-1 px-4 py-2 rounded-lg font-medium text-white bg-blue-600 hover:bg-blue-700 transition">
|
|
شروع
|
|
</button>
|
|
</div>
|
|
<div id="modalDurationsList"
|
|
class="space-y-2 max-h-[420px] overflow-y-auto border border-gray-100 rounded-lg p-2 bg-gray-50">
|
|
<div class="text-sm text-gray-400 text-center">در حال بارگذاری...</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Tab: comments --}}
|
|
<div id="modalTab-comments" class="hidden space-y-3">
|
|
<div id="modalCommentsList"
|
|
class="space-y-2 max-h-[300px] overflow-y-auto border border-gray-100 rounded-lg p-2 bg-gray-50">
|
|
<div class="text-sm text-gray-400 text-center">در حال بارگذاری...</div>
|
|
</div>
|
|
|
|
<form id="modalCommentForm" method="POST">
|
|
@csrf
|
|
<label for="modalCommentBody" class="font-medium text-sm text-gray-700">
|
|
ثبت کامنت (برای منشن از @ استفاده کنید)
|
|
</label>
|
|
|
|
<div class="relative">
|
|
<textarea id="modalCommentBody" name="body" rows="3"
|
|
class="rounded-lg border border-gray-300 p-2 w-full mt-2"
|
|
placeholder="مثلاً: @ali لطفاً این بخش رو بررسی کن" required></textarea>
|
|
|
|
<div id="mentionDropdown"
|
|
class="hidden absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-48 overflow-y-auto">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-center mt-3">
|
|
<button type="submit"
|
|
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition">
|
|
ارسال کامنت
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
@section('script')
|
|
<script>
|
|
const mentionUsers = @json($mentionUsers);
|
|
const appsList = @json($appsList);
|
|
const isAdminLevel1 = @json($isAdminLevel1);
|
|
const currentUserId = @json($user_id ?? null);
|
|
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');
|
|
}
|
|
|
|
// تابع سوییچ تبهای جدول
|
|
function switchTaskTab(tabId) {
|
|
// مخفی کردن همه جداول
|
|
document.querySelectorAll('.task-table-container').forEach(el => el.classList.add('hidden'));
|
|
// نمایش جدول انتخاب شده
|
|
document.getElementById('tab-' + tabId).classList.remove('hidden');
|
|
|
|
// تغییر استایل تبهای فعال
|
|
document.querySelectorAll('.task-tab').forEach(tab => {
|
|
tab.classList.remove('bg-white', 'text-blue-600', 'border-t', 'border-x', 'border-gray-200');
|
|
tab.classList.add('text-gray-500', 'hover:text-gray-700', 'hover:bg-gray-100');
|
|
});
|
|
const activeTab = document.querySelector(`.task-tab[data-tab="${tabId}"]`);
|
|
activeTab.classList.add('bg-white', 'text-blue-600', 'border-t', 'border-x', 'border-gray-200');
|
|
activeTab.classList.remove('text-gray-500', 'hover:text-gray-700', 'hover:bg-gray-100');
|
|
}
|
|
|
|
// مقداردهی اولیه (در صورت نیاز)
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// میتوانید اولین تب را به عنوان پیشفرض فعال نگه دارید
|
|
});
|
|
|
|
let currentTaskDetails = {}; // برای نگهداری اطلاعات تسک فعلی در مودال جزئیات
|
|
|
|
function escapeHtml(str) {
|
|
return String(str ?? '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function renderTaskComments(task) {
|
|
const list = document.getElementById('modalCommentsList');
|
|
if (!list) return;
|
|
|
|
const comments = Array.isArray(task.comments) ? task.comments : [];
|
|
if (comments.length === 0) {
|
|
list.innerHTML = `<div class="text-sm text-gray-400 text-center">هنوز کامنتی ثبت نشده.</div>`;
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = comments.map(c => {
|
|
const author = c.user?.name || 'نامشخص';
|
|
const created = c.created_at ? new Date(c.created_at).toLocaleString('fa-IR') : '';
|
|
const body = escapeHtml(c.body).replace(/\n/g, '<br/>');
|
|
return `
|
|
<div class="bg-white border border-gray-200 rounded-lg p-2">
|
|
<div class="text-xs text-gray-500 flex justify-between">
|
|
<span>${escapeHtml(author)}</span>
|
|
<span>${escapeHtml(created)}</span>
|
|
</div>
|
|
<div class="text-sm text-gray-800 whitespace-pre-wrap mt-1">${body}</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function sumDurationSeconds(durations) {
|
|
let total = 0;
|
|
(durations || []).forEach(d => {
|
|
if (!d.duration) return;
|
|
const parts = String(d.duration).split(':').map(Number);
|
|
if (parts.length !== 3 || parts.some(n => Number.isNaN(n))) return;
|
|
total += (parts[0] * 3600) + (parts[1] * 60) + parts[2];
|
|
});
|
|
return total;
|
|
}
|
|
|
|
function formatHms(totalSeconds) {
|
|
const s = Math.max(0, Number(totalSeconds) || 0);
|
|
const h = Math.floor(s / 3600);
|
|
const m = Math.floor((s % 3600) / 60);
|
|
const sec = Math.floor(s % 60);
|
|
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function updateModalTotalDuration(task) {
|
|
const el = document.getElementById('modalTaskDuration');
|
|
if (!el) return;
|
|
const durs = Array.isArray(task.duration) ? task.duration : [];
|
|
const filtered = isAdminLevel1
|
|
? durs
|
|
: durs.filter(d => String(d.user_id) === String(currentUserId));
|
|
const total = sumDurationSeconds(filtered);
|
|
el.textContent = formatHms(total);
|
|
|
|
const summary = document.getElementById('modalDurationsSummary');
|
|
if (summary) {
|
|
summary.textContent = isAdminLevel1
|
|
? `زمان کل همه کاربران روی این تسک: ${formatHms(total)}`
|
|
: `زمان کل شما روی این تسک: ${formatHms(total)}`;
|
|
}
|
|
}
|
|
|
|
function renderTaskDurations(task) {
|
|
const list = document.getElementById('modalDurationsList');
|
|
if (!list) return;
|
|
|
|
const durs = Array.isArray(task.duration) ? task.duration : [];
|
|
const filtered = isAdminLevel1
|
|
? durs
|
|
: durs.filter(d => String(d.user_id) === String(currentUserId));
|
|
|
|
if (filtered.length === 0) {
|
|
list.innerHTML = `<div class="text-sm text-gray-400 text-center">زمانی ثبت نشده.</div>`;
|
|
return;
|
|
}
|
|
|
|
const usersById = new Map((mentionUsers || []).map(u => [String(u.id), u]));
|
|
|
|
list.innerHTML = filtered
|
|
.slice()
|
|
.sort((a, b) => new Date(b.start || 0) - new Date(a.start || 0))
|
|
.map(d => {
|
|
const u = usersById.get(String(d.user_id));
|
|
const uname = u?.name || d.user_id;
|
|
const start = d.start ? new Date(d.start).toLocaleString('fa-IR') : '-';
|
|
const end = d.end ? new Date(d.end).toLocaleString('fa-IR') : 'در حال انجام';
|
|
const dur = d.duration || (d.end ? '-' : '...');
|
|
return `
|
|
<div class="bg-white border border-gray-200 rounded-lg p-2">
|
|
<div class="text-xs text-gray-500 flex justify-between">
|
|
<span>${escapeHtml(uname)}</span>
|
|
<span class="font-mono">${escapeHtml(dur)}</span>
|
|
</div>
|
|
<div class="text-xs text-gray-600 mt-1">
|
|
شروع: ${escapeHtml(start)}<br/>
|
|
پایان: ${escapeHtml(end)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function switchDetailsTab(tab) {
|
|
const tabs = ['info', 'durations', 'comments'];
|
|
tabs.forEach(t => {
|
|
const el = document.getElementById(`modalTab-${t}`);
|
|
const btn = document.getElementById(`modalTabBtn-${t}`);
|
|
if (el) el.classList.toggle('hidden', t !== tab);
|
|
if (btn) {
|
|
btn.classList.toggle('bg-gray-100', t === tab);
|
|
btn.classList.toggle('text-gray-800', t === tab);
|
|
btn.classList.toggle('text-gray-500', t !== tab);
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupCommentForm(task) {
|
|
const form = document.getElementById('modalCommentForm');
|
|
const textarea = document.getElementById('modalCommentBody');
|
|
if (!form || !textarea) return;
|
|
|
|
form.action = `/task/${task.id}/comments`;
|
|
textarea.value = '';
|
|
}
|
|
|
|
function getMentionQuery(text, caretPos) {
|
|
const before = text.slice(0, caretPos);
|
|
const at = before.lastIndexOf('@');
|
|
if (at === -1) return null;
|
|
if (at > 0 && /\S/.test(before[at - 1])) return null; // باید ابتدای کلمه باشد
|
|
const query = before.slice(at + 1);
|
|
if (/\s/.test(query)) return null;
|
|
return {atIndex: at, query};
|
|
}
|
|
|
|
function insertMention(textarea, atIndex, username) {
|
|
const text = textarea.value;
|
|
const caretPos = textarea.selectionStart;
|
|
const before = text.slice(0, atIndex);
|
|
const after = text.slice(caretPos);
|
|
const mention = `@${username} `;
|
|
textarea.value = before + mention + after;
|
|
const newPos = (before + mention).length;
|
|
textarea.setSelectionRange(newPos, newPos);
|
|
textarea.focus();
|
|
}
|
|
|
|
function setupMentionAutocomplete() {
|
|
const textarea = document.getElementById('modalCommentBody');
|
|
const dropdown = document.getElementById('mentionDropdown');
|
|
if (!textarea || !dropdown) return;
|
|
|
|
let state = {open: false, atIndex: -1, query: ''};
|
|
|
|
function close() {
|
|
dropdown.classList.add('hidden');
|
|
dropdown.innerHTML = '';
|
|
state.open = false;
|
|
}
|
|
|
|
function open(items, atIndex, query) {
|
|
state = {open: true, atIndex, query};
|
|
dropdown.innerHTML = items.map(u => {
|
|
const name = escapeHtml(u.name);
|
|
const uname = escapeHtml(u.user_name);
|
|
return `
|
|
<button type="button"
|
|
class="w-full text-right px-3 py-2 hover:bg-gray-50 text-sm flex justify-between"
|
|
data-username="${uname}">
|
|
<span class="font-medium text-gray-800">${name}</span>
|
|
<span class="text-xs text-gray-500">@${uname}</span>
|
|
</button>
|
|
`;
|
|
}).join('');
|
|
dropdown.classList.remove('hidden');
|
|
}
|
|
|
|
textarea.addEventListener('input', () => {
|
|
const caretPos = textarea.selectionStart;
|
|
const q = getMentionQuery(textarea.value, caretPos);
|
|
if (!q) return close();
|
|
|
|
const query = (q.query || '').toLowerCase();
|
|
const filtered = (mentionUsers || []).filter(u => {
|
|
const n = (u.name || '').toLowerCase();
|
|
const un = (u.user_name || '').toLowerCase();
|
|
return n.includes(query) || un.includes(query);
|
|
}).slice(0, 8);
|
|
|
|
if (filtered.length === 0) return close();
|
|
open(filtered, q.atIndex, q.query);
|
|
});
|
|
|
|
textarea.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') close();
|
|
});
|
|
|
|
dropdown.addEventListener('click', (e) => {
|
|
const btn = e.target.closest('button[data-username]');
|
|
if (!btn) return;
|
|
const uname = btn.getAttribute('data-username');
|
|
insertMention(textarea, state.atIndex, uname);
|
|
close();
|
|
});
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target === textarea || dropdown.contains(e.target)) return;
|
|
close();
|
|
});
|
|
}
|
|
|
|
// باز کردن مودال جزئیات تسک
|
|
function openTaskDetailsModal(task) {
|
|
currentTaskDetails = task;
|
|
|
|
// پر کردن فیلدهای مودال
|
|
document.getElementById('modalTaskTitle').textContent = task.title || 'بدون عنوان';
|
|
document.getElementById('modalTaskDescription').innerHTML = task.description || 'بدون توضیحات';
|
|
document.getElementById('modalTaskStartDateView').textContent = task.start_date ? formatJalaliDate(task.start_date) : '-';
|
|
document.getElementById('modalTaskTargetDateView').textContent = task.target_date ? formatJalaliDate(task.target_date) : '-';
|
|
document.getElementById('modalTaskCreator').textContent = task.creator_name || '-';
|
|
|
|
// اپ
|
|
const appNameEl = document.getElementById('modalTaskAppName');
|
|
const appTypes = {1: 'خانگی', 2: 'همکار'};
|
|
if (appNameEl) {
|
|
const app = (appsList || []).find(a => String(a.id) === String(task.app));
|
|
appNameEl.textContent = app ? `${app.name_fa} (${appTypes[app.type]})` : '-';
|
|
}
|
|
|
|
// اولویت
|
|
const priorityLabels = {'urgent': 'فوری', 'high': 'بالا', 'medium': 'متوسط', 'low': 'پایین'};
|
|
const priorityColors = {
|
|
'urgent': 'bg-red-100 text-red-800',
|
|
'high': 'bg-orange-100 text-orange-800',
|
|
'medium': 'bg-yellow-100 text-yellow-800',
|
|
'low': 'bg-green-100 text-green-800'
|
|
};
|
|
const prioritySpan = document.getElementById('modalTaskPriorityView');
|
|
if (prioritySpan) {
|
|
prioritySpan.textContent = priorityLabels[task.priority] || (task.priority || 'ندارد');
|
|
prioritySpan.className = `px-2 py-1 text-xs rounded-full ${priorityColors[task.priority] || 'bg-gray-100 text-gray-800'} inline-block`;
|
|
}
|
|
|
|
// وضعیت
|
|
const stateLabels = {1: 'انباشته', 2: 'برای انجام', 3: 'در حال انجام', 4: 'پایان یافته'};
|
|
const stateColors = {
|
|
1: 'bg-gray-100 text-gray-800',
|
|
2: 'bg-orange-100 text-orange-800',
|
|
3: 'bg-cyan-100 text-cyan-800',
|
|
4: 'bg-green-100 text-green-800'
|
|
};
|
|
const stateSpan = document.getElementById('modalTaskStateView');
|
|
if (stateSpan) {
|
|
stateSpan.textContent = stateLabels[task.state] || 'نامشخص';
|
|
stateSpan.className = `px-2 py-1 text-xs rounded-full ${stateColors[task.state] || 'bg-gray-100 text-gray-800'}`;
|
|
}
|
|
|
|
// مجموع زمان صرفشده (محاسبه از duration در صورت وجود)
|
|
updateModalTotalDuration(task);
|
|
|
|
// تنظیم دکمه شروع/پایان داخل تب زمانها (بدون رفرش)
|
|
const durationBtn = document.getElementById('modalDurationActionBtn');
|
|
if (durationBtn) {
|
|
const actionType = task.active ? 'end' : 'start';
|
|
durationBtn.textContent = actionType === 'start' ? 'شروع' : 'پایان';
|
|
durationBtn.className = `flex-1 px-4 py-2 rounded-lg font-medium text-white ${actionType === 'start' ? 'bg-blue-600 hover:bg-blue-700' : 'bg-green-600 hover:bg-green-700'} transition`;
|
|
|
|
durationBtn.onclick = () => {
|
|
durationBtn.disabled = true;
|
|
fetch('{{ url("/active_task") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
body: JSON.stringify({task_id: task.id})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (!data.status) {
|
|
alert('خطا در درخواست!');
|
|
return;
|
|
}
|
|
|
|
const durs = Array.isArray(currentTaskDetails.duration) ? currentTaskDetails.duration : [];
|
|
|
|
if (data.action === 'start') {
|
|
currentTaskDetails.active = true;
|
|
durs.push({
|
|
id: data.id || null,
|
|
task_id: currentTaskDetails.id,
|
|
user_id: currentUserId,
|
|
start: data.start,
|
|
end: null,
|
|
duration: null,
|
|
});
|
|
} else if (data.action === 'ended') {
|
|
currentTaskDetails.active = false;
|
|
for (let i = durs.length - 1; i >= 0; i--) {
|
|
if (String(durs[i].user_id) === String(currentUserId) && !durs[i].end) {
|
|
durs[i].end = data.end;
|
|
durs[i].duration = data.duration;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
currentTaskDetails.duration = durs;
|
|
renderTaskDurations(currentTaskDetails);
|
|
updateModalTotalDuration(currentTaskDetails);
|
|
|
|
const nextAction = currentTaskDetails.active ? 'end' : 'start';
|
|
durationBtn.textContent = nextAction === 'start' ? 'شروع' : 'پایان';
|
|
durationBtn.className = `flex-1 px-4 py-2 rounded-lg font-medium text-white ${nextAction === 'start' ? 'bg-blue-600 hover:bg-blue-700' : 'bg-green-600 hover:bg-green-700'} transition`;
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
alert('خطا در ارتباط با سرور!');
|
|
})
|
|
.finally(() => {
|
|
durationBtn.disabled = false;
|
|
});
|
|
};
|
|
}
|
|
|
|
// دکمه ویرایش تسک
|
|
document.getElementById('modalEditTaskBtn').onclick = () => {
|
|
// هدایت به صفحه ویرایش (فرض میکنیم route با نام tasks.edit وجود دارد)
|
|
window.location.href = `/task/${task.id}`; // مسیر را مطابق پروژه تنظیم کنید
|
|
};
|
|
document.getElementById('modalDeleteTaskBtn').onclick = () => {
|
|
fetch(`/task/delete/${task.id}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
})
|
|
.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);
|
|
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(`⚠️ خطا در ارتباط با سرور یا پردازش داده!`);
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// نمایش مودال
|
|
const modal = document.getElementById('taskDetailsModal');
|
|
modal.classList.remove('hidden');
|
|
modal.style.display = 'flex';
|
|
|
|
switchDetailsTab('info');
|
|
setQuickEditMode(false);
|
|
renderTaskComments(task);
|
|
renderTaskDurations(task);
|
|
updateModalTotalDuration(task);
|
|
setupCommentForm(task);
|
|
setupMentionAutocomplete();
|
|
}
|
|
|
|
function setQuickEditMode(on) {
|
|
const btn = document.getElementById('modalQuickEditBtn');
|
|
const actions = document.getElementById('modalQuickEditActions');
|
|
|
|
const prView = document.getElementById('modalTaskPriorityView');
|
|
const prEdit = document.getElementById('modalTaskPriorityEdit');
|
|
|
|
const stView = document.getElementById('modalTaskStateView');
|
|
const stEdit = document.getElementById('modalTaskStateEdit');
|
|
|
|
const sdView = document.getElementById('modalTaskStartDateView');
|
|
const sdEdit = document.getElementById('modalTaskStartDateEdit');
|
|
const sdGreg = document.getElementById('modalTaskStartDateGregorian');
|
|
|
|
const tdView = document.getElementById('modalTaskTargetDateView');
|
|
const tdEdit = document.getElementById('modalTaskTargetDateEdit');
|
|
const tdGreg = document.getElementById('modalTaskTargetDateGregorian');
|
|
|
|
if (!btn || !actions || !prView || !prEdit || !stView || !stEdit || !sdView || !sdEdit || !tdView || !tdEdit) return;
|
|
|
|
actions.classList.toggle('hidden', !on);
|
|
prView.classList.toggle('hidden', on);
|
|
prEdit.classList.toggle('hidden', !on ? true : false);
|
|
|
|
stView.classList.toggle('hidden', on);
|
|
stEdit.classList.toggle('hidden', !on ? true : false);
|
|
|
|
sdView.classList.toggle('hidden', on);
|
|
sdEdit.classList.toggle('hidden', !on ? true : false);
|
|
|
|
tdView.classList.toggle('hidden', on);
|
|
tdEdit.classList.toggle('hidden', !on ? true : false);
|
|
|
|
btn.textContent = on ? 'در حال ویرایش' : 'ویرایش سریع';
|
|
btn.classList.toggle('bg-yellow-50', on);
|
|
|
|
if (on) {
|
|
prEdit.value = currentTaskDetails.priority || 'none';
|
|
stEdit.value = String(currentTaskDetails.state || 1);
|
|
sdEdit.value = currentTaskDetails.start_date ? formatJalaliDate(currentTaskDetails.start_date) : '';
|
|
tdEdit.value = currentTaskDetails.target_date ? formatJalaliDate(currentTaskDetails.target_date) : '';
|
|
|
|
if (sdGreg) sdGreg.value = currentTaskDetails.start_date || '';
|
|
if (tdGreg) tdGreg.value = currentTaskDetails.target_date || '';
|
|
|
|
initModalDatepickers();
|
|
}
|
|
}
|
|
|
|
function initModalDatepickers() {
|
|
const sdEdit = $('#modalTaskStartDateEdit');
|
|
const tdEdit = $('#modalTaskTargetDateEdit');
|
|
|
|
if (sdEdit.length) {
|
|
try {
|
|
sdEdit.persianDatepicker('destroy');
|
|
} catch (e) {
|
|
}
|
|
sdEdit.persianDatepicker({
|
|
format: 'YYYY/MM/DD',
|
|
autoClose: true,
|
|
toolbox: {
|
|
calendarSwitch: {enabled: false},
|
|
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
|
},
|
|
onSelect: function (unix) {
|
|
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
|
let dateStr = gregorian.format('YYYY-MM-DD');
|
|
const sdGreg = document.getElementById('modalTaskStartDateGregorian');
|
|
if (sdGreg) sdGreg.value = dateStr;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (tdEdit.length) {
|
|
try {
|
|
tdEdit.persianDatepicker('destroy');
|
|
} catch (e) {
|
|
}
|
|
tdEdit.persianDatepicker({
|
|
format: 'YYYY/MM/DD',
|
|
autoClose: true,
|
|
toolbox: {
|
|
calendarSwitch: {enabled: false},
|
|
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
|
},
|
|
onSelect: function (unix) {
|
|
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
|
let dateStr = gregorian.format('YYYY-MM-DD');
|
|
const tdGreg = document.getElementById('modalTaskTargetDateGregorian');
|
|
if (tdGreg) tdGreg.value = dateStr;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Quick edit events
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const quickBtn = document.getElementById('modalQuickEditBtn');
|
|
const saveBtn = document.getElementById('modalQuickEditSaveBtn');
|
|
const cancelBtn = document.getElementById('modalQuickEditCancelBtn');
|
|
|
|
if (quickBtn) quickBtn.onclick = () => setQuickEditMode(true);
|
|
if (cancelBtn) cancelBtn.onclick = () => setQuickEditMode(false);
|
|
|
|
if (saveBtn) {
|
|
saveBtn.onclick = () => {
|
|
const prEdit = document.getElementById('modalTaskPriorityEdit');
|
|
const stEdit = document.getElementById('modalTaskStateEdit');
|
|
const sdGreg = document.getElementById('modalTaskStartDateGregorian');
|
|
const tdGreg = document.getElementById('modalTaskTargetDateGregorian');
|
|
|
|
const fd = new FormData();
|
|
fd.append('_token', '{{ csrf_token() }}');
|
|
fd.append('id', currentTaskDetails.id);
|
|
fd.append('priority', prEdit?.value || 'none');
|
|
fd.append('state', stEdit?.value || String(currentTaskDetails.state || 1));
|
|
fd.append('start_date', sdGreg?.value || '');
|
|
fd.append('target_date', tdGreg?.value || '');
|
|
|
|
saveBtn.disabled = true;
|
|
fetch('{{ route('task.edit') }}', {method: 'POST', body: fd})
|
|
.then(() => {
|
|
// backend redirect میدهد، ما فقط UI را آپدیت میکنیم
|
|
currentTaskDetails.priority = prEdit?.value || 'none';
|
|
currentTaskDetails.start_date = sdGreg?.value || null;
|
|
currentTaskDetails.target_date = tdGreg?.value || null;
|
|
|
|
// refresh view bits (بدون بازنشانی مودال)
|
|
const sdView = document.getElementById('modalTaskStartDateView');
|
|
const tdView = document.getElementById('modalTaskTargetDateView');
|
|
if (sdView) sdView.textContent = currentTaskDetails.start_date ? formatJalaliDate(currentTaskDetails.start_date) : '-';
|
|
if (tdView) tdView.textContent = currentTaskDetails.target_date ? formatJalaliDate(currentTaskDetails.target_date) : '-';
|
|
|
|
// priority view
|
|
const priorityLabels = {
|
|
'urgent': 'فوری',
|
|
'high': 'بالا',
|
|
'medium': 'متوسط',
|
|
'low': 'پایین'
|
|
};
|
|
const priorityColors = {
|
|
'urgent': 'bg-red-100 text-red-800',
|
|
'high': 'bg-orange-100 text-orange-800',
|
|
'medium': 'bg-yellow-100 text-yellow-800',
|
|
'low': 'bg-green-100 text-green-800'
|
|
};
|
|
const prView = document.getElementById('modalTaskPriorityView');
|
|
if (prView) {
|
|
prView.textContent = priorityLabels[currentTaskDetails.priority] || (currentTaskDetails.priority || 'ندارد');
|
|
prView.className = `px-2 py-1 text-xs rounded-full ${priorityColors[currentTaskDetails.priority] || 'bg-gray-100 text-gray-800'} inline-block`;
|
|
}
|
|
|
|
// state view
|
|
currentTaskDetails.state = parseInt(stEdit?.value || currentTaskDetails.state || 1, 10);
|
|
const stateLabels = {1: 'انباشته', 2: 'برای انجام', 3: 'در حال انجام', 4: 'پایان یافته'};
|
|
const stateColors = {
|
|
1: 'bg-gray-100 text-gray-800',
|
|
2: 'bg-orange-100 text-orange-800',
|
|
3: 'bg-cyan-100 text-cyan-800',
|
|
4: 'bg-green-100 text-green-800'
|
|
};
|
|
const stView = document.getElementById('modalTaskStateView');
|
|
if (stView) {
|
|
stView.textContent = stateLabels[currentTaskDetails.state] || 'نامشخص';
|
|
stView.className = `px-2 py-1 text-xs rounded-full ${stateColors[currentTaskDetails.state] || 'bg-gray-100 text-gray-800'}`;
|
|
}
|
|
|
|
setQuickEditMode(false);
|
|
})
|
|
.catch(() => alert('خطا در ذخیره تغییرات'))
|
|
.finally(() => {
|
|
saveBtn.disabled = false;
|
|
});
|
|
};
|
|
}
|
|
});
|
|
|
|
function closeDetailsModal() {
|
|
const modal = document.getElementById('taskDetailsModal');
|
|
modal.classList.add('hidden');
|
|
modal.style.display = 'none';
|
|
window.location.reload();
|
|
}
|
|
|
|
// فرمت تاریخ شمسی (ساده)
|
|
function formatJalaliDate(dateStr) {
|
|
if (!dateStr) return '-';
|
|
// فرض میکنیم از کتابخانه Jalalian در blade استفاده میکنید، اما در js نیز میتوان پیادهسازی کرد
|
|
// برای سادگی از Intl یا یک تبدیل ساده استفاده میکنیم
|
|
const d = new Date(dateStr);
|
|
if (isNaN(d)) return dateStr;
|
|
// این قسمت میتواند با کتابخانه moment-jalaali جایگزین شود
|
|
return d.toLocaleDateString('fa-IR'); // نیاز به تنظیم locale مرورگر دارد
|
|
}
|
|
|
|
function formatDateTime(dateStr) {
|
|
if (!dateStr) return '-';
|
|
const d = new Date(dateStr);
|
|
return d.toLocaleString('fa-IR');
|
|
}
|
|
|
|
// NOTE: مدیریت زمان از مودال جزئیات حذف شده و فقط در تب "زمانها" انجام میشود.
|
|
|
|
</script>
|
|
|
|
@endsection
|