edit filter time
This commit is contained in:
@@ -22,24 +22,154 @@ use App\Models\Pivot_Tasks;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
private function loadPendingRequests($users): array
|
||||
private function redisIsAvailable(): bool
|
||||
{
|
||||
$records = [];
|
||||
|
||||
try {
|
||||
Redis::connection()->ping();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable) {
|
||||
foreach ($users as $user) {
|
||||
$records[] = [
|
||||
'user_name' => $user->user_name,
|
||||
'name' => $user->name,
|
||||
'attendances' => [],
|
||||
];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function appendDecodedRequest(array &$target, ?string $raw): void
|
||||
{
|
||||
if (!$raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = json_decode($raw);
|
||||
if ($request && ($request->function ?? null) !== 'rejected') {
|
||||
$target[] = $request;
|
||||
}
|
||||
}
|
||||
|
||||
private function appendDecodedRequestWithAttendance(array &$target, ?string $raw, ?Attendance $attendance = null): void
|
||||
{
|
||||
if (!$raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = json_decode($raw);
|
||||
if ($request && ($request->function ?? null) !== 'rejected') {
|
||||
$target[] = $this->enrichRequestForView($request, $attendance);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildFallbackRequestFromAttendance(Attendance $attendance): object
|
||||
{
|
||||
$request = (object) [
|
||||
'function' => 'edit',
|
||||
'userid' => $attendance->user_identity,
|
||||
'id' => $attendance->id,
|
||||
'oldenter' => $attendance->entered_at ? Carbon::parse($attendance->entered_at)->format('H:i') : '',
|
||||
'newenter' => '',
|
||||
'oldexit' => $attendance->exited_at ? Carbon::parse($attendance->exited_at)->format('H:i') : '',
|
||||
'newexit' => '',
|
||||
'date' => $attendance->entered_at
|
||||
? Jalalian::fromCarbon(Carbon::parse($attendance->entered_at))->format('Y/m/d')
|
||||
: null,
|
||||
];
|
||||
|
||||
$request->display_date = $this->formatRequestDisplayDate($request, $attendance);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function formatRequestDisplayDate(object $request, ?Attendance $attendance = null): string
|
||||
{
|
||||
$candidates = array_filter([
|
||||
$attendance?->entered_at,
|
||||
(!empty($request->oldenter) && str_contains((string) $request->oldenter, ' '))
|
||||
? $request->oldenter
|
||||
: null,
|
||||
$request->date ?? null,
|
||||
]);
|
||||
|
||||
foreach ($candidates as $value) {
|
||||
try {
|
||||
if (is_string($value) && preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $value)) {
|
||||
$year = (int) substr($value, 0, 4);
|
||||
|
||||
if ($year >= 1700) {
|
||||
return Jalalian::fromCarbon(Carbon::createFromFormat('Y/m/d', $value))->format('Y/m/d');
|
||||
}
|
||||
|
||||
if ($year >= 1300) {
|
||||
return Jalalian::fromFormat('Y/m/d', $value)->format('Y/m/d');
|
||||
}
|
||||
}
|
||||
|
||||
$carbon = Carbon::parse($value);
|
||||
if ($carbon->year >= 1000) {
|
||||
return Jalalian::fromCarbon($carbon)->format('Y/m/d');
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
|
||||
private function enrichRequestForView(object $request, ?Attendance $attendance = null): object
|
||||
{
|
||||
if (!isset($request->display_date)) {
|
||||
$request->display_date = $this->formatRequestDisplayDate($request, $attendance);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function resolveRequestTime(?string $preferred, ?string $fallback, $recordDefault = null): ?string
|
||||
{
|
||||
$value = ($preferred !== null && $preferred !== '') ? $preferred : $fallback;
|
||||
if ($value === null || $value === '') {
|
||||
if ($recordDefault) {
|
||||
return Carbon::parse($recordDefault)->format('H:i:s');
|
||||
}
|
||||
|
||||
return $records;
|
||||
return null;
|
||||
}
|
||||
|
||||
return Carbon::parse($value)->format('H:i:s');
|
||||
}
|
||||
|
||||
private function buildAttendanceTimestamp(string $date, ?string $time): ?string
|
||||
{
|
||||
if (!$time) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $date . ' ' . $time;
|
||||
}
|
||||
|
||||
private function decrementUserRequestCount(?string $userName): void
|
||||
{
|
||||
if (!$userName) {
|
||||
return;
|
||||
}
|
||||
|
||||
User::where('user_name', $userName)
|
||||
->where('req_counts', '>', 0)
|
||||
->decrement('req_counts');
|
||||
}
|
||||
|
||||
private function loadPendingRequests($users): array
|
||||
{
|
||||
$userNames = $users->pluck('user_name')->filter()->values();
|
||||
$redisAvailable = $this->redisIsAvailable();
|
||||
|
||||
$pendingByUser = Attendance::query()
|
||||
->whereIn('user_identity', $userNames)
|
||||
->whereNotNull('requested')
|
||||
->where('requested', '!=', '')
|
||||
->get(['id', 'user_identity', 'entered_at', 'exited_at'])
|
||||
->groupBy('user_identity');
|
||||
|
||||
$records = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$rec = [
|
||||
'user_name' => $user->user_name,
|
||||
@@ -47,28 +177,58 @@ class AdminController extends Controller
|
||||
'attendances' => [],
|
||||
];
|
||||
|
||||
foreach (Redis::keys("att-tempreq:{$user->user_name}:*") as $key) {
|
||||
if (str_contains($key, ':creates:')) {
|
||||
continue;
|
||||
$pendingForUser = $pendingByUser->get($user->user_name, collect());
|
||||
$hasRequestHint = ($user->req_counts ?? 0) > 0 || $pendingForUser->isNotEmpty();
|
||||
|
||||
if (!$hasRequestHint) {
|
||||
$records[] = $rec;
|
||||
continue;
|
||||
}
|
||||
|
||||
$loadedIds = [];
|
||||
|
||||
if ($redisAvailable) {
|
||||
foreach (Redis::lrange("att-tempreq:creates:{$user->user_name}", 0, -1) as $reqJson) {
|
||||
$this->appendDecodedRequestWithAttendance($rec['attendances'], $reqJson);
|
||||
}
|
||||
|
||||
$raw = Redis::get($key);
|
||||
if (!$raw) {
|
||||
continue;
|
||||
}
|
||||
$this->appendDecodedRequestWithAttendance($rec['attendances'], Redis::get("att-tempreq:{$user->user_name}:new"));
|
||||
|
||||
$get = json_decode($raw);
|
||||
if ($get && ($get->function ?? null) !== 'rejected') {
|
||||
$rec['attendances'][] = $get;
|
||||
if ($pendingForUser->isNotEmpty()) {
|
||||
$redisKeys = $pendingForUser
|
||||
->map(fn ($attendance) => "att-tempreq:{$user->user_name}:{$attendance->id}")
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$rawValues = Redis::mget($redisKeys);
|
||||
|
||||
foreach ($pendingForUser->values() as $index => $attendance) {
|
||||
$raw = $rawValues[$index] ?? null;
|
||||
if (!$raw) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$beforeCount = count($rec['attendances']);
|
||||
$this->appendDecodedRequestWithAttendance($rec['attendances'], $raw, $attendance);
|
||||
if (count($rec['attendances']) > $beforeCount) {
|
||||
$loadedIds[(string) $attendance->id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$createRequests = Redis::lrange("att-tempreq:creates:{$user->user_name}", 0, -1);
|
||||
foreach ($createRequests as $reqJson) {
|
||||
$get = json_decode($reqJson);
|
||||
if ($get && ($get->function ?? null) !== 'rejected') {
|
||||
$rec['attendances'][] = $get;
|
||||
foreach ($pendingForUser as $attendance) {
|
||||
if (isset($loadedIds[(string) $attendance->id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rec['attendances'][] = $this->buildFallbackRequestFromAttendance($attendance);
|
||||
}
|
||||
|
||||
$pendingById = $pendingForUser->keyBy('id');
|
||||
foreach ($rec['attendances'] as $index => $request) {
|
||||
$attendance = $pendingById->get($request->id ?? null);
|
||||
$rec['attendances'][$index] = $this->enrichRequestForView($request, $attendance);
|
||||
}
|
||||
|
||||
$records[] = $rec;
|
||||
@@ -354,6 +514,7 @@ class AdminController extends Controller
|
||||
// Redis::connection()->lrem($create_requests_key, 1, json_encode($data));
|
||||
|
||||
Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}");
|
||||
$this->decrementUserRequestCount($data->userid);
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#6aff00ff',
|
||||
'text' => 'رکورد با موفقیت حذف شد✔️'
|
||||
@@ -371,43 +532,45 @@ class AdminController extends Controller
|
||||
]);
|
||||
}
|
||||
} elseif ($data->function == 'edit') {
|
||||
// dd($data);
|
||||
$record = Attendance::find($data->id);
|
||||
if (!$record) {
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'رکورد پیدا نشد✖️'
|
||||
]);
|
||||
}
|
||||
|
||||
$attendance = new Attendance();
|
||||
$date = Carbon::parse($record->entered_at)->format('Y-m-d');
|
||||
$enterTime = $this->resolveRequestTime($data->newenter ?? null, $data->oldenter ?? null, $record->entered_at);
|
||||
$exitTime = $this->resolveRequestTime($data->newexit ?? null, $data->oldexit ?? null, $record->exited_at);
|
||||
|
||||
$record->ip_address = 'admin-edited';
|
||||
$record->user_identity = $data->userid;
|
||||
$record->entered_at = $data->newenter ? $date . ' ' . $data->newenter : null;
|
||||
if ($data->newexit != null) {
|
||||
//جریمه
|
||||
// if($data->newexit > '16:00:00'){
|
||||
// $data->newexit = date('H:i:s', strtotime($data->newexit . ' -1 hour'));
|
||||
// }
|
||||
|
||||
$record->exited_at = $date . ' ' . $data->newexit;
|
||||
$duration = $attendance->duration($data->newenter, $data->newexit);
|
||||
$record->entered_at = $this->buildAttendanceTimestamp($date, $enterTime) ?? $record->entered_at;
|
||||
|
||||
if ($exitTime) {
|
||||
$record->exited_at = $this->buildAttendanceTimestamp($date, $exitTime);
|
||||
$duration = $attendance->duration($enterTime, $exitTime);
|
||||
} else {
|
||||
$record->exited_at = null;
|
||||
$record->duration = null;
|
||||
$duration = null;
|
||||
}
|
||||
|
||||
$break = AttendanceBreak::where('attendance_id', $data->id)->sum('duration');
|
||||
if ($break) {
|
||||
if ($break && $duration) {
|
||||
$breakSec = strtotime("1970-01-01 " . $break) - strtotime("1970-01-01 00:00:00");
|
||||
$durationSec = strtotime("1970-01-01 " . $duration) - strtotime("1970-01-01 00:00:00");
|
||||
$durationSec -= $breakSec;
|
||||
$duration = gmdate("H:i:s", $durationSec);
|
||||
|
||||
}
|
||||
|
||||
$record->duration = $duration ?? $record->duration;
|
||||
//dd($record->duration, $duration, $break);
|
||||
$record->requested = '';
|
||||
$record->save();
|
||||
// $create_requests_key = "att-tempreq:creates:{$data->userid}";
|
||||
// $create_requests_key = "att-tempreq:{$data->userid}:{$data->id}";
|
||||
// $t = Redis::connection()->lrem($create_requests_key, -1, json_encode($data));
|
||||
|
||||
Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}");
|
||||
$this->decrementUserRequestCount($data->userid);
|
||||
|
||||
tel_notif($record->user_identity, "تایید");
|
||||
|
||||
@@ -416,21 +579,33 @@ class AdminController extends Controller
|
||||
"text" => 'رکورد با موفقیت ذخیره شد.✔️'
|
||||
]);
|
||||
} elseif ($data->function == 'new') {
|
||||
// dd($data);
|
||||
$starttime = $data->oldenter ? Carbon::parse($data->oldenter)->format('H:i:s') : null;
|
||||
$endtime = $data->oldexit ? Carbon::parse($data->oldexit)->format('H:i:s') : null;
|
||||
$enteredAt = !empty($data->oldenter) ? Carbon::parse($data->oldenter) : null;
|
||||
$exitedAt = !empty($data->oldexit) ? Carbon::parse($data->oldexit) : null;
|
||||
|
||||
if (!$enteredAt) {
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'زمان ورود در درخواست مشخص نیست✖️'
|
||||
]);
|
||||
}
|
||||
|
||||
$starttime = $enteredAt->format('H:i:s');
|
||||
$endtime = $exitedAt?->format('H:i:s');
|
||||
|
||||
if (is_null($endtime) || $endtime > $starttime) {
|
||||
$attendance = new Attendance();
|
||||
$attendance->ip_address = 'admin-new';
|
||||
$attendance->user_identity = $data->userid;
|
||||
$attendance->entered_at = $data->oldenter ?: null;
|
||||
$attendance->exited_at = $data->oldexit ?: null;
|
||||
$attendance->duration = $data->oldexit ? $attendance->duration($data->oldenter, $data->oldexit) : null;
|
||||
$attendance->entered_at = $enteredAt->format('Y-m-d H:i:s');
|
||||
$attendance->exited_at = $exitedAt?->format('Y-m-d H:i:s');
|
||||
$attendance->duration = $endtime
|
||||
? $attendance->duration($starttime, $endtime)
|
||||
: null;
|
||||
$attendance->save();
|
||||
$create_requests_key = "att-tempreq:creates:{$data->userid}";
|
||||
Redis::connection()->lrem($create_requests_key, 1, json_encode($data));
|
||||
$this->decrementUserRequestCount($data->userid);
|
||||
|
||||
// Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}");
|
||||
tel_notif($data->userid, "تایید");
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#6aff00ff',
|
||||
@@ -456,6 +631,7 @@ class AdminController extends Controller
|
||||
$att->save();
|
||||
}
|
||||
tel_notif($data->userid, "رد");
|
||||
$this->decrementUserRequestCount($data->userid);
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#6aff00ff',
|
||||
"text" => 'درخواست با موفقیت رد شد.✔️'
|
||||
|
||||
@@ -122,6 +122,28 @@ class TaskController extends Controller
|
||||
return $totalSeconds;
|
||||
}
|
||||
|
||||
private function resolveTaskFinishedAt($durations): ?string
|
||||
{
|
||||
$latest = collect($durations)
|
||||
->pluck('end')
|
||||
->filter()
|
||||
->map(fn ($value) => Carbon::parse($value))
|
||||
->sortDesc()
|
||||
->first();
|
||||
|
||||
return $latest?->toDateTimeString();
|
||||
}
|
||||
|
||||
private function resolveTaskFinishedAtByTaskId(int $taskId): ?string
|
||||
{
|
||||
return $this->resolveTaskFinishedAt(
|
||||
TaskDuration::query()
|
||||
->where('task_id', $taskId)
|
||||
->whereNotNull('end')
|
||||
->get(['end'])
|
||||
);
|
||||
}
|
||||
|
||||
private function resolveAssigneeNames(Tasks $task, array $usersById = []): array
|
||||
{
|
||||
$assignees = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
||||
@@ -155,6 +177,7 @@ class TaskController extends Controller
|
||||
'description' => $task->description,
|
||||
'start_date' => $task->start_date,
|
||||
'target_date' => $task->target_date,
|
||||
'finished_at' => $task->finished_at ?? null,
|
||||
'priority' => $task->priority,
|
||||
'state' => $task->state,
|
||||
'app' => $task->app,
|
||||
@@ -200,6 +223,7 @@ class TaskController extends Controller
|
||||
'description' => $task->description,
|
||||
'start_date' => $task->start_date,
|
||||
'target_date' => $task->target_date,
|
||||
'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id),
|
||||
'priority' => $task->priority,
|
||||
'state' => $task->state,
|
||||
'app' => $task->app,
|
||||
@@ -295,7 +319,7 @@ class TaskController extends Controller
|
||||
$activeTaskIds = $activeQuery->pluck('task_id')->flip();
|
||||
|
||||
$durationQuery = TaskDuration::query()
|
||||
->select(['task_id', 'user_id', 'duration'])
|
||||
->select(['task_id', 'user_id', 'duration', 'end'])
|
||||
->whereIn('task_id', $taskIds)
|
||||
->whereNotNull('duration');
|
||||
|
||||
@@ -305,12 +329,20 @@ class TaskController extends Controller
|
||||
|
||||
$durationsByTaskId = $durationQuery->get()->groupBy('task_id');
|
||||
|
||||
$finishedAtByTaskId = TaskDuration::query()
|
||||
->select(['task_id', 'end'])
|
||||
->whereIn('task_id', $taskIds)
|
||||
->whereNotNull('end')
|
||||
->get()
|
||||
->groupBy('task_id');
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$task->active = $activeTaskIds->has($task->id);
|
||||
$task->creator_name = $task->creator?->name ?? 'نامشخص';
|
||||
$task->assignee_names = $this->resolveAssigneeNames($task, $usersById);
|
||||
$ids = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
||||
$task->assignee_ids = is_array($ids) ? array_map('strval', $ids) : [];
|
||||
$task->finished_at = $this->resolveTaskFinishedAt($finishedAtByTaskId->get($task->id, collect()));
|
||||
$totalSeconds = $this->sumDurationSeconds(
|
||||
$durationsByTaskId->get($task->id, collect()),
|
||||
$isAdmin ? null : $user->id
|
||||
@@ -475,7 +507,14 @@ class TaskController extends Controller
|
||||
} else {
|
||||
unset($data['assignees']);
|
||||
}
|
||||
unset($data['_token'], $data['id'], $data['start_date_display'], $data['target_date_display']);
|
||||
unset(
|
||||
$data['_token'],
|
||||
$data['id'],
|
||||
$data['start_date_display'],
|
||||
$data['target_date_display'],
|
||||
$data['completed_at'],
|
||||
$data['completed_at_display']
|
||||
);
|
||||
|
||||
$task->update($data);
|
||||
$after = $task->toArray();
|
||||
@@ -626,6 +665,7 @@ class TaskController extends Controller
|
||||
'duration' => $duration,
|
||||
'start' => $taskDU->start,
|
||||
'end' => $taskDU->end,
|
||||
'finished_at' => $this->resolveTaskFinishedAtByTaskId($task->id),
|
||||
]);
|
||||
} else
|
||||
return response()->json([
|
||||
|
||||
@@ -12,7 +12,7 @@ $usersChunked = $users->chunk(3);
|
||||
<div class="container w-full flex flex-col items-center">
|
||||
|
||||
@if(!collect($reqs)->pluck('attendances')->flatten()->isEmpty())
|
||||
<div class="ui-requests-card flex flex-col items-center">
|
||||
<div class="ui-requests-card flex flex-col items-center w-full max-w-[350px]">
|
||||
<p>درخواست ها</p>
|
||||
|
||||
@foreach ($reqs as $u)
|
||||
@@ -21,9 +21,10 @@ $usersChunked = $users->chunk(3);
|
||||
class="ui-chip-btn"
|
||||
onclick="togglerequests('attendance-{{ $loop->index }}')">
|
||||
{{ $u['name'] }}
|
||||
<span class="mr-1 text-xs opacity-90">({{ count($u['attendances']) }})</span>
|
||||
</button>
|
||||
@endif
|
||||
<div id="attendance-{{ $loop->index }}" class="hidden-content pt-4 w-full">
|
||||
|
||||
<div id="attendance-{{ $loop->index }}" class="hidden-content pt-4 w-full">
|
||||
<div class="ui-table-wrap">
|
||||
<table class="w-full table-auto">
|
||||
|
||||
@@ -48,13 +49,24 @@ $usersChunked = $users->chunk(3);
|
||||
'new' => "جدید",
|
||||
default => "نامشخص",
|
||||
} }}</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>
|
||||
<td class="px-4 py-2 border">{{ $rec->display_date ?? '-' }}</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;
|
||||
$parseTime = function ($value) {
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Carbon::parse($value)->format('H:i');
|
||||
} catch (\Throwable) {
|
||||
return is_string($value) && preg_match('/^\d{1,2}:\d{2}/', $value)
|
||||
? substr($value, 0, 5)
|
||||
: null;
|
||||
}
|
||||
};
|
||||
$oldEnter = $parseTime($rec->oldenter ?? null);
|
||||
$newEnter = $parseTime($rec->newenter ?? null);
|
||||
$oldExit = $parseTime($rec->oldexit ?? null);
|
||||
$newExit = $parseTime($rec->newexit ?? null);
|
||||
@endphp
|
||||
|
||||
@if( $oldEnter == $newEnter || $newEnter == null)
|
||||
@@ -74,8 +86,8 @@ $usersChunked = $users->chunk(3);
|
||||
<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)}}">
|
||||
<input type="hidden" name="data"
|
||||
value='{{ json_encode($rec, JSON_UNESCAPED_UNICODE) }}'>
|
||||
<button type="submit" class="ui-btn-approve">
|
||||
تایید
|
||||
</button>
|
||||
@@ -83,8 +95,8 @@ $usersChunked = $users->chunk(3);
|
||||
<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)}}">
|
||||
<input type="hidden" name="data"
|
||||
value='{{ json_encode($rec, JSON_UNESCAPED_UNICODE) }}'>
|
||||
<button type="submit" class="ui-btn-reject">
|
||||
رد
|
||||
</button>
|
||||
@@ -101,9 +113,8 @@ $usersChunked = $users->chunk(3);
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
{{-- @if(collect($reqs)->pluck('attendances')->flatten()->isEmpty())--}}
|
||||
{{-- <p class="text-stone-400" style="text-align: center">درخواستی ثبت نشده</p>--}}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
+145
-16
@@ -56,11 +56,11 @@
|
||||
--shadow-soft: 0 8px 32px rgba(15, 23, 42, 0.08);
|
||||
--radius-lg: 1.25rem;
|
||||
--radius-xl: 1.5rem;
|
||||
--glass-panel-bg: rgba(255, 255, 255, 0.08);
|
||||
--glass-panel-border: rgba(255, 255, 255, 0.35);
|
||||
--glass-panel-inset: rgba(255, 255, 255, 0.45);
|
||||
--glass-tint: 14%;
|
||||
--glass-fallback: color-mix(in srgb, var(--primary-color) 72%, rgba(255, 255, 255, 0.9));
|
||||
--glass-panel-bg: color-mix(in srgb, var(--primary-color) 58%, rgba(255, 255, 255, 0.32));
|
||||
--glass-panel-border: color-mix(in srgb, var(--primary-color) 38%, rgba(255, 255, 255, 0.55));
|
||||
--glass-panel-inset: rgba(255, 255, 255, 0.38);
|
||||
--glass-tint: 44%;
|
||||
--glass-fallback: color-mix(in srgb, var(--primary-color) 82%, rgba(255, 255, 255, 0.78));
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #334155;
|
||||
--text-muted: #64748b;
|
||||
@@ -86,12 +86,12 @@
|
||||
--modal-border: rgba(255, 255, 255, 0.6);
|
||||
--modal-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);
|
||||
--row-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
|
||||
--glass-btn-bg: rgba(255, 255, 255, 0.18);
|
||||
--glass-btn-border: rgba(255, 255, 255, 0.28);
|
||||
--glass-btn-hover: rgba(255, 255, 255, 0.28);
|
||||
--header-label-bg: rgba(255, 255, 255, 0.16);
|
||||
--header-label-border: rgba(255, 255, 255, 0.24);
|
||||
--header-label-hover: rgba(255, 255, 255, 0.28);
|
||||
--glass-btn-bg: rgba(255, 255, 255, 0.3);
|
||||
--glass-btn-border: rgba(255, 255, 255, 0.46);
|
||||
--glass-btn-hover: rgba(255, 255, 255, 0.44);
|
||||
--header-label-bg: rgba(255, 255, 255, 0.28);
|
||||
--header-label-border: rgba(255, 255, 255, 0.42);
|
||||
--header-label-hover: rgba(255, 255, 255, 0.44);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
@@ -193,6 +193,32 @@
|
||||
inset 0 1px 0 var(--glass-panel-inset);
|
||||
}
|
||||
|
||||
:root:not([data-theme="dark"]) .glass-header,
|
||||
:root:not([data-theme="dark"]) .glass-footer-wrap {
|
||||
box-shadow:
|
||||
0 8px 28px color-mix(in srgb, var(--primary-color) 28%, rgba(15, 23, 42, 0.14)),
|
||||
inset 0 1px 0 var(--glass-panel-inset);
|
||||
}
|
||||
|
||||
:root:not([data-theme="dark"]) .glass-footer-wrap {
|
||||
box-shadow:
|
||||
0 -8px 28px color-mix(in srgb, var(--primary-color) 28%, rgba(15, 23, 42, 0.14)),
|
||||
inset 0 1px 0 var(--glass-panel-inset);
|
||||
}
|
||||
|
||||
:root:not([data-theme="dark"]) .glass-header .headerItems,
|
||||
:root:not([data-theme="dark"]) .glass-footer button {
|
||||
text-shadow: 0 1px 3px rgba(15, 23, 42, 0.28);
|
||||
}
|
||||
|
||||
:root:not([data-theme="dark"]) .fab-btn {
|
||||
box-shadow: 0 10px 28px color-mix(in srgb, var(--primary-color) 58%, transparent);
|
||||
}
|
||||
|
||||
:root:not([data-theme="dark"]) .fab-btn:hover {
|
||||
box-shadow: 0 14px 32px color-mix(in srgb, var(--primary-color) 68%, transparent);
|
||||
}
|
||||
|
||||
.glass-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -698,6 +724,66 @@
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ui-form-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.ui-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-form-field label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ui-form-field textarea.ui-input {
|
||||
min-height: 5.5rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.ui-form-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.ui-input-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-input-wrap .ui-input {
|
||||
padding-left: 2.25rem;
|
||||
}
|
||||
|
||||
.ui-input-clear {
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-input-clear:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.ui-modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -1291,19 +1377,62 @@
|
||||
[data-theme="dark"] .datepicker-navigator {
|
||||
background-color: var(--surface-card) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-color: var(--border-input) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-day-view .table-days td span,
|
||||
[data-theme="dark"] .datepicker-month-view .month-item,
|
||||
[data-theme="dark"] .datepicker-year-view .year-item {
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-navigator .pwt-btn-next,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-navigator .pwt-btn-switch,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-navigator .pwt-btn-prev {
|
||||
background-color: var(--surface-input) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-day-view .table-days td.disabled span,
|
||||
[data-theme="dark"] .datepicker-day-view .table-days td.off span {
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .month-grid-box .header .header-row-cell {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td span,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item {
|
||||
background-color: var(--surface-input) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td.disabled span,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td span.other-month,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item-disable,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item-disable {
|
||||
background-color: var(--surface-tabs) !important;
|
||||
color: var(--text-faint) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td.today span {
|
||||
background-color: var(--primary-color) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td.selected span,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item.selected,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item.selected {
|
||||
background-color: #2563eb !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-day-view .table-days td span:hover,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-month-view .month-item:hover,
|
||||
[data-theme="dark"] .datepicker-plot-area .datepicker-year-view .year-item:hover {
|
||||
background-color: var(--surface-tab-hover) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .datepicker-plot-area .toolbox .pwt-btn-submit,
|
||||
[data-theme="dark"] .datepicker-plot-area .toolbox .pwt-btn-calendar,
|
||||
[data-theme="dark"] .datepicker-plot-area .toolbox .pwt-btn-today {
|
||||
background-color: var(--surface-input) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-color: var(--border-input) !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -461,6 +461,8 @@ if (!empty($user->tasks)) {
|
||||
const lngInput = document.getElementById("longitude");
|
||||
const distanceInput = document.getElementById("distance");
|
||||
const form = document.getElementById("attendanceForm");
|
||||
|
||||
if (submitBtn && latInput && lngInput && distanceInput && distanceMessage && form) {
|
||||
// naser
|
||||
if (existed) {
|
||||
submitBtn.textContent = "خروج";
|
||||
@@ -571,10 +573,12 @@ if (!empty($user->tasks)) {
|
||||
});
|
||||
attendanceForm.submit();
|
||||
} catch (error) {
|
||||
output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
|
||||
if (output) {
|
||||
output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function openCity(evt, Month) {
|
||||
@@ -611,20 +615,28 @@ if (!empty($user->tasks)) {
|
||||
{{-- TASKS MODAL--}}
|
||||
function openTaskModal(task) {
|
||||
const modal = document.getElementById('taskModal');
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
|
||||
// آپدیت متن دکمه بر اساس وضعیت تسک
|
||||
updateStatusButtonText(task.started_at, task.ended_at);
|
||||
if (task) {
|
||||
// آپدیت متن دکمه بر اساس وضعیت تسک
|
||||
updateStatusButtonText(task.started_at, task.ended_at);
|
||||
|
||||
// تنظیم عملکرد دکمه بر اساس تسک فعلی
|
||||
const statusBtn = document.getElementById('statusBtn');
|
||||
statusBtn.onclick = () => handleTaskAction(
|
||||
task.id,
|
||||
task.p_id,
|
||||
task.started_at,
|
||||
task.ended_at
|
||||
);
|
||||
// تنظیم عملکرد دکمه بر اساس تسک فعلی
|
||||
const statusBtn = document.getElementById('statusBtn');
|
||||
if (statusBtn) {
|
||||
statusBtn.onclick = () => handleTaskAction(
|
||||
task.id,
|
||||
task.p_id,
|
||||
task.started_at,
|
||||
task.ended_at
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
@@ -648,6 +660,10 @@ if (!empty($user->tasks)) {
|
||||
// -------------------------------
|
||||
function updateStatusButtonText(started_at, ended_at) {
|
||||
const statusBtn = document.getElementById('statusBtn');
|
||||
if (!statusBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let text = "شروع";
|
||||
|
||||
if (started_at && !ended_at) {
|
||||
@@ -807,6 +823,10 @@ if (!empty($user->tasks)) {
|
||||
}
|
||||
|
||||
function startBreakCounter(initialSeconds) {
|
||||
if (!breakBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure no duplicate timer
|
||||
if (breakTimer) clearInterval(breakTimer);
|
||||
currentSeconds = initialSeconds || 0;
|
||||
|
||||
@@ -22,127 +22,126 @@ if (isset($task)) {
|
||||
<div class="w-full mt-[70px] justify-center container flex">
|
||||
<div class="ui-card">
|
||||
<div class="ui-card-header">{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}</div>
|
||||
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class="ui-card-body grid grid-cols-2 gap-4 items-center">
|
||||
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class="ui-card-body ui-form-stack">
|
||||
@csrf
|
||||
@if(isset($task))
|
||||
<input type="hidden" id="id" name="id" value="{{$task->id}}">
|
||||
@endif
|
||||
<label for="title" class="font-medium text-sm"><span class="text-red-500">*</span>عنوان:</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full" value="{{$task->title ?? null}}" type="text"
|
||||
id="title" name="title" required>
|
||||
|
||||
<label for="description" class="font-medium text-sm">توضیحات:</label>
|
||||
<textarea id="description" name="description"
|
||||
class="rounded-lg border border-gray-300 p-2 w-full">{{$task->description ?? null}}</textarea>
|
||||
<div class="ui-form-field">
|
||||
<label for="title"><span class="text-red-500">*</span> عنوان</label>
|
||||
<input class="ui-input" value="{{$task->title ?? null}}" type="text"
|
||||
id="title" name="title" required>
|
||||
</div>
|
||||
|
||||
<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" {{ old('priority', $task->priority ?? '') == 'none' ? 'selected' : '' }}>ندارد</option>
|
||||
<option value="low" {{ old('priority', $task->priority ?? '') == 'low' ? 'selected' : '' }}>کم</option>
|
||||
<option value="medium" {{ old('priority', $task->priority ?? '') == 'medium' ? 'selected' : '' }}>عادی</option>
|
||||
<option value="urgent" {{ old('priority', $task->priority ?? '') == 'urgent' ? 'selected' : '' }}>فوری</option>
|
||||
<option value="high" {{ old('priority', $task->priority ?? '') == 'high' ? 'selected' : '' }}>زیاد</option>
|
||||
</select>
|
||||
<div class="ui-form-field">
|
||||
<label for="description">توضیحات</label>
|
||||
<textarea id="description" name="description" class="ui-input">{{$task->description ?? null}}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- تاریخ شروع -->
|
||||
<div class="p-2">
|
||||
<label for="start_date" class="font-medium text-sm">تاریخ شروع <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input type="text" id="start_date" name="start_date_display"
|
||||
class="rounded-lg border border-gray-300 p-2 " value="{{$task->start_date ?? null}}"
|
||||
<div class="ui-form-field">
|
||||
<label for="priority">اولویت</label>
|
||||
<select id="priority" name="priority" class="ui-input">
|
||||
<option value="none" {{ old('priority', $task->priority ?? '') == 'none' ? 'selected' : '' }}>ندارد</option>
|
||||
<option value="low" {{ old('priority', $task->priority ?? '') == 'low' ? 'selected' : '' }}>کم</option>
|
||||
<option value="medium" {{ old('priority', $task->priority ?? '') == 'medium' ? 'selected' : '' }}>عادی</option>
|
||||
<option value="urgent" {{ old('priority', $task->priority ?? '') == 'urgent' ? 'selected' : '' }}>فوری</option>
|
||||
<option value="high" {{ old('priority', $task->priority ?? '') == 'high' ? 'selected' : '' }}>زیاد</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui-form-field">
|
||||
<label for="start_date">تاریخ شروع <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="start_date" name="start_date_display" class="ui-input"
|
||||
value="{{$task->start_date ?? null}}"
|
||||
placeholder="تاریخ شروع" autocomplete="off" readonly required>
|
||||
<input type="hidden" name="start_date" id="start_date_gregorian">
|
||||
</div>
|
||||
|
||||
<!-- تاریخ هدف -->
|
||||
<label for="target_date" class="font-medium text-sm">تاریخ هدف</label>
|
||||
{{-- <div class="relative">--}}
|
||||
<input type="text" id="target_date" name="target_date_display"
|
||||
class="rounded-lg border border-gray-300 p-2 pwt-datepicker-input-element mt-2"
|
||||
value="{{$task->target_date ?? null}}"
|
||||
placeholder="تاریخ هدف" autocomplete="off" readonly>
|
||||
<button type="button" id="clear_target_date"
|
||||
class="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-red-500">
|
||||
<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="ui-form-field">
|
||||
<label for="target_date">تاریخ هدف</label>
|
||||
<div class="ui-input-wrap">
|
||||
<input type="text" id="target_date" name="target_date_display" class="ui-input"
|
||||
value="{{$task->target_date ?? null}}"
|
||||
placeholder="تاریخ هدف" autocomplete="off" readonly>
|
||||
<button type="button" id="clear_target_date" class="ui-input-clear" aria-label="پاک کردن تاریخ هدف">
|
||||
<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>
|
||||
<input type="hidden" name="target_date" id="target_date_gregorian">
|
||||
</div>
|
||||
|
||||
<!-- وضعیت -->
|
||||
<label for="state" class="font-medium text-sm"><span class="text-red-500">*</span>وضعیت:</label>
|
||||
<select id="state" name="state" class="rounded-lg border border-gray-300 p-2 w-full" required>
|
||||
<option value="1" {{ old('state', $task->state ?? '') == 1 ? 'selected' : '' }}>در انتظار</option>
|
||||
<option value="2" {{ old('state', $task->state ?? '') == 2 ? 'selected' : '' }}>در صف انجام</option>
|
||||
<option value="3" {{ old('state', $task->state ?? '') == 3 ? 'selected' : '' }}>درحال انجام</option>
|
||||
<option value="4" {{ old('state', $task->state ?? '') == 4 ? 'selected' : '' }}>انجامشده</option>
|
||||
</select>
|
||||
<div class="ui-form-field">
|
||||
<label for="state"><span class="text-red-500">*</span> وضعیت</label>
|
||||
<select id="state" name="state" class="ui-input" required>
|
||||
<option value="1" {{ old('state', $task->state ?? '') == 1 ? 'selected' : '' }}>در انتظار</option>
|
||||
<option value="2" {{ old('state', $task->state ?? '') == 2 ? 'selected' : '' }}>در صف انجام</option>
|
||||
<option value="3" {{ old('state', $task->state ?? '') == 3 ? 'selected' : '' }}>درحال انجام</option>
|
||||
<option value="4" {{ old('state', $task->state ?? '') == 4 ? 'selected' : '' }}>انجامشده</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- کاربران مسئول -->
|
||||
<label for="assignees" class="font-medium text-sm">انتصاب
|
||||
کاربران:</label>
|
||||
<div class="relative" id="dropdown-container">
|
||||
{{-- دکمه --}}
|
||||
<button type="button" id="dropdown-btn"
|
||||
class="ui-input text-right flex justify-between items-center">
|
||||
<span id="selected-count">انتخاب کاربران</span>
|
||||
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="ui-form-field">
|
||||
<label for="dropdown-btn">انتصاب کاربران</label>
|
||||
<div class="relative" id="dropdown-container">
|
||||
<button type="button" id="dropdown-btn"
|
||||
class="ui-input text-right flex justify-between items-center">
|
||||
<span id="selected-count">انتخاب کاربران</span>
|
||||
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div id="dropdown-list"
|
||||
class="hidden absolute z-20 w-full ui-panel border border-gray-300 rounded-lg shadow-lg">
|
||||
<div id="dropdown-list"
|
||||
class="hidden absolute z-20 w-full ui-panel border border-gray-300 rounded-lg shadow-lg mt-1">
|
||||
|
||||
<div class="p-2 border-b">
|
||||
<input type="text" id="search-input"
|
||||
class="w-full px-3 py-1 border border-gray-200 rounded-lg text-sm"
|
||||
placeholder="جستجوی کاربر...">
|
||||
</div>
|
||||
<div class="p-2 border-b">
|
||||
<input type="text" id="search-input"
|
||||
class="w-full px-3 py-1 border border-gray-200 rounded-lg text-sm"
|
||||
placeholder="جستجوی کاربر...">
|
||||
</div>
|
||||
|
||||
{{-- لیست کاربران --}}
|
||||
<div id="users-list" class="max-h-48 overflow-y-auto space-y-1 ">
|
||||
@foreach($users as $user)
|
||||
<label class="user-item flex items-center gap-2 p-1 hover:bg-gray-50 rounded cursor-pointer"
|
||||
data-name="{{ $user->name }}">
|
||||
<input type="checkbox"
|
||||
name="assignees[]"
|
||||
value="{{ $user->id }}"
|
||||
class="user-checkbox rounded text-cyan-600"
|
||||
{{ isset($task) && (in_array($user->id, $selectedAssignees)) ? 'checked' : '' }}>
|
||||
<div id="users-list" class="max-h-48 overflow-y-auto space-y-1 p-1">
|
||||
@foreach($users as $user)
|
||||
<label class="user-item flex items-center gap-2 p-1 hover:bg-gray-50 rounded cursor-pointer"
|
||||
data-name="{{ $user->name }}">
|
||||
<input type="checkbox"
|
||||
name="assignees[]"
|
||||
value="{{ $user->id }}"
|
||||
class="user-checkbox rounded text-cyan-600"
|
||||
{{ isset($task) && (in_array($user->id, $selectedAssignees)) ? 'checked' : '' }}>
|
||||
|
||||
<span>{{ $user->name }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
<span>{{ $user->name }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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">--}}
|
||||
<div class="ui-form-field">
|
||||
<label for="app">نام اپ</label>
|
||||
<select id="app" name="app" class="ui-input">
|
||||
<option value="">-- انتخاب اپ --</option>
|
||||
@if(!empty($apps) && count($apps) > 0)
|
||||
@foreach($apps as $app)
|
||||
<option value="{{ $app->id }}" {{ old('app', $task->app ?? '') == $app->id ? 'selected' : '' }}>
|
||||
{{ $app->name_fa }} {{ $app->type_fa }}
|
||||
</option>
|
||||
@endforeach
|
||||
@endif
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- اپ -->
|
||||
<label for="app" class="font-medium text-sm">نام اپ:</label>
|
||||
<select id="app" name="app" class="rounded-lg z-20 border border-gray-300 p-2 w-full">
|
||||
<option value="">-- انتخاب اپ --</option>
|
||||
@if(!empty($apps) && count($apps) > 0)
|
||||
@foreach($apps as $app)
|
||||
<option value="{{ $app->id }}" {{ old('app', $task->app ?? '') == $app->id ? 'selected' : '' }}>
|
||||
{{ $app->name_fa }} {{ $app->type_fa }}
|
||||
</option>
|
||||
@endforeach
|
||||
@endif
|
||||
</select>
|
||||
|
||||
<div class="inline-block flex pt-3 justify-center">
|
||||
<button type="submit"
|
||||
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition <!--mr-24 mt-2.5-->">
|
||||
{{isset($task) ? "ویرایش" : "ایجاد وظیفه جدید"}}</button>
|
||||
<div class="ui-form-actions">
|
||||
<button type="submit"
|
||||
class="bg-cyan-600 text-white py-2 px-6 rounded-lg hover:bg-cyan-700 transition">
|
||||
{{isset($task) ? "ویرایش" : "ایجاد وظیفه جدید"}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -156,7 +155,6 @@ if (isset($task)) {
|
||||
const usersList = document.getElementById('users-list');
|
||||
const selectedCountSpan = document.getElementById('selected-count');
|
||||
|
||||
// باز و بسته کردن
|
||||
dropdownBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownList.classList.toggle('hidden');
|
||||
@@ -165,29 +163,22 @@ if (isset($task)) {
|
||||
}
|
||||
});
|
||||
|
||||
// بستن با کلیک بیرون
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdownBtn.contains(e.target) && !dropdownList.contains(e.target)) {
|
||||
dropdownList.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// قابلیت جستجو
|
||||
searchInput.addEventListener('input', function () {
|
||||
const searchText = this.value.toLowerCase();
|
||||
const items = usersList.querySelectorAll('.user-item');
|
||||
|
||||
items.forEach(item => {
|
||||
const name = item.getAttribute('data-name').toLowerCase();
|
||||
if (name.includes(searchText)) {
|
||||
item.style.display = 'flex';
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
item.style.display = name.includes(searchText) ? 'flex' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// آپدیت تعداد
|
||||
function updateSelectedCount() {
|
||||
const selected = document.querySelectorAll('.user-checkbox:checked');
|
||||
const count = selected.length;
|
||||
@@ -201,7 +192,6 @@ if (isset($task)) {
|
||||
updateSelectedCount();
|
||||
|
||||
$(document).ready(function () {
|
||||
// تاریخ شروع با ساعت جاری
|
||||
$('#start_date').persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
@@ -210,15 +200,12 @@ if (isset($task)) {
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
},
|
||||
onSelect: function (unix) {
|
||||
// تبدیل به میلادی
|
||||
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStr = gregorian.format('YYYY-MM-DD');
|
||||
$('#start_date_gregorian').val(dateStr);
|
||||
console.log('Start Gregorian:', dateStr);
|
||||
}
|
||||
});
|
||||
|
||||
// تاریخ هدف
|
||||
$('#target_date').persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
@@ -229,13 +216,10 @@ if (isset($task)) {
|
||||
onSelect: function (unix) {
|
||||
let gregorianDate = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStrTarget = gregorianDate.format('YYYY-MM-DD');
|
||||
// let dateTimeStr = `${dateStr} 23:59:59`;
|
||||
$('#target_date_gregorian').val(dateStrTarget);
|
||||
console.log('Target DateTime (Gregorian):', dateStrTarget);
|
||||
}
|
||||
});
|
||||
|
||||
// دکمه پاک کردن تاریخ هدف
|
||||
$('#clear_target_date').on('click', function () {
|
||||
$('#target_date').val('');
|
||||
$('#target_date_gregorian').val('');
|
||||
@@ -243,4 +227,4 @@ if (isset($task)) {
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
@endsection
|
||||
|
||||
@@ -93,7 +93,54 @@ $state = [
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.tasks-filter select {
|
||||
.tasks-filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.tasks-filters .tasks-filter {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tasks-filter-date {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-head select {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.tasks-filter-date-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tasks-filter select,
|
||||
.tasks-filter input[type="text"] {
|
||||
border-radius: 0.875rem;
|
||||
border: 1px solid var(--border-input);
|
||||
background: var(--surface-input);
|
||||
@@ -101,7 +148,38 @@ $state = [
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.tasks-filter select:focus {
|
||||
.tasks-filter-date-field {
|
||||
flex: 1 1 7rem;
|
||||
min-width: 7rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tasks-filter-date-type {
|
||||
min-width: 140px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tasks-filter-clear {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 0.875rem;
|
||||
border: 1px solid var(--border-input);
|
||||
background: var(--surface-tabs);
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.tasks-filter-clear:hover {
|
||||
background: var(--surface-tab-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tasks-filter select:focus,
|
||||
.tasks-filter input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--primary-color) 55%, white);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
|
||||
@@ -344,19 +422,45 @@ $state = [
|
||||
});
|
||||
}
|
||||
|
||||
function filterTasksByAssignee(assigneeId) {
|
||||
function applyTaskFilters() {
|
||||
const assigneeId = document.getElementById('assigneeFilter')?.value || '';
|
||||
const dateField = document.getElementById('dateFilterField')?.value || 'target_date';
|
||||
const fromDate = document.getElementById('dateFilterFromGregorian')?.value || '';
|
||||
const toDate = document.getElementById('dateFilterToGregorian')?.value || '';
|
||||
const hasDateFilter = Boolean(fromDate || toDate);
|
||||
const dateAttrMap = {
|
||||
target_date: 'targetDate',
|
||||
start_date: 'startDate',
|
||||
finished_at: 'finishedDate',
|
||||
created_at: 'createdDate',
|
||||
};
|
||||
const dateAttr = dateAttrMap[dateField] || 'targetDate';
|
||||
const hasAnyFilter = Boolean(assigneeId || hasDateFilter);
|
||||
|
||||
document.querySelectorAll('.task-row').forEach(row => {
|
||||
if (!assigneeId) {
|
||||
row.classList.remove('hidden');
|
||||
return;
|
||||
let visible = true;
|
||||
|
||||
if (assigneeId) {
|
||||
let ids = [];
|
||||
try {
|
||||
ids = JSON.parse(row.dataset.assigneeIds || '[]');
|
||||
} catch (e) {
|
||||
ids = [];
|
||||
}
|
||||
visible = ids.includes(String(assigneeId));
|
||||
}
|
||||
let ids = [];
|
||||
try {
|
||||
ids = JSON.parse(row.dataset.assigneeIds || '[]');
|
||||
} catch (e) {
|
||||
ids = [];
|
||||
|
||||
if (visible && hasDateFilter) {
|
||||
const taskDate = row.dataset[dateAttr] || '';
|
||||
if (!taskDate) {
|
||||
visible = false;
|
||||
} else {
|
||||
if (fromDate && taskDate < fromDate) visible = false;
|
||||
if (toDate && taskDate > toDate) visible = false;
|
||||
}
|
||||
}
|
||||
row.classList.toggle('hidden', !ids.includes(String(assigneeId)));
|
||||
|
||||
row.classList.toggle('hidden', !visible);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.task-table-container').forEach(panel => {
|
||||
@@ -364,32 +468,90 @@ $state = [
|
||||
const visibleRows = panel.querySelectorAll('.task-row:not(.hidden)').length;
|
||||
const hasRows = panel.querySelectorAll('.task-row').length > 0;
|
||||
if (emptyMsg) {
|
||||
emptyMsg.classList.toggle('hidden', !assigneeId || !hasRows || visibleRows > 0);
|
||||
emptyMsg.classList.toggle('hidden', !hasAnyFilter || !hasRows || visibleRows > 0);
|
||||
}
|
||||
});
|
||||
|
||||
updateTaskTabCounts();
|
||||
}
|
||||
|
||||
function filterTasksByAssignee() {
|
||||
applyTaskFilters();
|
||||
}
|
||||
|
||||
function clearTaskDateFilter() {
|
||||
const fromInput = document.getElementById('dateFilterFrom');
|
||||
const toInput = document.getElementById('dateFilterTo');
|
||||
const fromHidden = document.getElementById('dateFilterFromGregorian');
|
||||
const toHidden = document.getElementById('dateFilterToGregorian');
|
||||
|
||||
if (fromInput) fromInput.value = '';
|
||||
if (toInput) toInput.value = '';
|
||||
if (fromHidden) fromHidden.value = '';
|
||||
if (toHidden) toHidden.value = '';
|
||||
|
||||
applyTaskFilters();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container tasks-page w-full flex justify-center items-center flex-col">
|
||||
@if($count > 0)
|
||||
<div class="w-full max-w-6xl mx-auto mt-4">
|
||||
@if(!empty($assigneeFilterOptions))
|
||||
<div class="tasks-filter flex flex-wrap items-center gap-3">
|
||||
<label for="assigneeFilter" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس مسئول:
|
||||
</label>
|
||||
<select id="assigneeFilter"
|
||||
onchange="filterTasksByAssignee(this.value)"
|
||||
class="flex-1 min-w-[180px] px-3 py-2 text-sm text-gray-800">
|
||||
<option value="">همه مسئولین</option>
|
||||
@foreach($assigneeFilterOptions as $option)
|
||||
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="tasks-filters">
|
||||
@if(!empty($assigneeFilterOptions))
|
||||
<div class="tasks-filter flex flex-wrap items-center gap-3">
|
||||
<label for="assigneeFilter" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس مسئول:
|
||||
</label>
|
||||
<select id="assigneeFilter"
|
||||
onchange="applyTaskFilters()"
|
||||
class="flex-1 min-w-[180px] px-3 py-2 text-sm text-gray-800">
|
||||
<option value="">همه مسئولین</option>
|
||||
@foreach($assigneeFilterOptions as $option)
|
||||
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="tasks-filter tasks-filter-date">
|
||||
<div class="tasks-filter-date-head">
|
||||
<label for="dateFilterField" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
فیلتر بر اساس تاریخ:
|
||||
</label>
|
||||
<select id="dateFilterField"
|
||||
onchange="applyTaskFilters()"
|
||||
class="tasks-filter-date-type">
|
||||
<option value="target_date">مهلت</option>
|
||||
<option value="start_date">تاریخ شروع</option>
|
||||
<option value="finished_at">تاریخ اتمام</option>
|
||||
<option value="created_at">تاریخ ایجاد</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="tasks-filter-date-row">
|
||||
<input type="text"
|
||||
id="dateFilterFrom"
|
||||
class="tasks-filter-date-field"
|
||||
placeholder="از تاریخ"
|
||||
autocomplete="off"
|
||||
readonly>
|
||||
<input type="text"
|
||||
id="dateFilterTo"
|
||||
class="tasks-filter-date-field"
|
||||
placeholder="تا تاریخ"
|
||||
autocomplete="off"
|
||||
readonly>
|
||||
<button type="button"
|
||||
id="dateFilterClear"
|
||||
class="tasks-filter-clear"
|
||||
onclick="clearTaskDateFilter()">
|
||||
پاک کردن
|
||||
</button>
|
||||
<input type="hidden" id="dateFilterFromGregorian">
|
||||
<input type="hidden" id="dateFilterToGregorian">
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="tasks-card mt-3">
|
||||
<div class="tasks-tabs">
|
||||
@@ -422,6 +584,10 @@ $state = [
|
||||
<article class="task-card task-row {{ $task->active ? 'is-active' : '' }}"
|
||||
data-task-id="{{ $task->id }}"
|
||||
data-assignee-ids='@json($task->assignee_ids ?? [])'
|
||||
data-start-date="{{ $task->start_date ? \Carbon\Carbon::parse($task->start_date)->format('Y-m-d') : '' }}"
|
||||
data-target-date="{{ $task->target_date ? \Carbon\Carbon::parse($task->target_date)->format('Y-m-d') : '' }}"
|
||||
data-finished-date="{{ $task->finished_at ? \Carbon\Carbon::parse($task->finished_at)->format('Y-m-d') : '' }}"
|
||||
data-created-date="{{ $task->created_at ? \Carbon\Carbon::parse($task->created_at)->format('Y-m-d') : '' }}"
|
||||
onclick="openTaskDetailsModal({{ $task->id }})"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -475,7 +641,7 @@ $state = [
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="task-filter-empty hidden tasks-empty">
|
||||
تسکی برای این مسئول در این وضعیت وجود ندارد.
|
||||
وظیفهای با این فیلترها در این وضعیت وجود ندارد.
|
||||
</div>
|
||||
@else
|
||||
<div class="tasks-empty">
|
||||
@@ -584,6 +750,10 @@ $state = [
|
||||
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" id="modalTaskFinishedAtView">-</p>
|
||||
</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>
|
||||
@@ -804,6 +974,7 @@ $state = [
|
||||
window.tasksById = tasksById;
|
||||
|
||||
updateModalInfoFields(currentTaskDetails);
|
||||
updateTaskFinishedAtView(currentTaskDetails);
|
||||
renderTaskComments(currentTaskDetails);
|
||||
renderTaskDurations(currentTaskDetails);
|
||||
updateModalTotalDuration(currentTaskDetails);
|
||||
@@ -1056,6 +1227,30 @@ $state = [
|
||||
});
|
||||
}
|
||||
|
||||
function resolveFinishedAtFromTask(task) {
|
||||
if (task?.finished_at) {
|
||||
return task.finished_at;
|
||||
}
|
||||
|
||||
const durs = Array.isArray(task?.duration) ? task.duration : [];
|
||||
const ends = durs
|
||||
.map(d => d.end)
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => new Date(b) - new Date(a));
|
||||
|
||||
return ends[0] || null;
|
||||
}
|
||||
|
||||
function updateTaskFinishedAtView(task) {
|
||||
const finishedView = document.getElementById('modalTaskFinishedAtView');
|
||||
if (!finishedView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const finishedAt = resolveFinishedAtFromTask(task);
|
||||
finishedView.textContent = finishedAt ? formatDateTime(finishedAt) : '-';
|
||||
}
|
||||
|
||||
function formatAssigneeNames(task) {
|
||||
const names = Array.isArray(task?.assignee_names) ? task.assignee_names.filter(Boolean) : [];
|
||||
return names.length ? names.join('، ') : '-';
|
||||
@@ -1087,6 +1282,7 @@ $state = [
|
||||
document.getElementById('modalTaskDescription').innerHTML = task.description || 'بدون توضیحات';
|
||||
document.getElementById('modalTaskStartDateView').textContent = task.start_date ? formatJalaliDate(task.start_date) : '-';
|
||||
document.getElementById('modalTaskTargetDateView').textContent = task.target_date ? formatJalaliDate(task.target_date) : '-';
|
||||
updateTaskFinishedAtView(task);
|
||||
updateModalInfoFields(task);
|
||||
|
||||
// اپ
|
||||
@@ -1173,11 +1369,22 @@ $state = [
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentTaskDetails.finished_at = data.finished_at || data.end || currentTaskDetails.finished_at;
|
||||
}
|
||||
|
||||
currentTaskDetails.duration = durs;
|
||||
renderTaskDurations(currentTaskDetails);
|
||||
updateModalTotalDuration(currentTaskDetails);
|
||||
updateTaskFinishedAtView(currentTaskDetails);
|
||||
|
||||
const row = document.querySelector(`.task-row[data-task-id="${currentTaskDetails.id}"]`);
|
||||
if (row && currentTaskDetails.finished_at) {
|
||||
row.dataset.finishedDate = String(currentTaskDetails.finished_at).slice(0, 10);
|
||||
}
|
||||
|
||||
tasksById[currentTaskDetails.id] = currentTaskDetails;
|
||||
tasksById[String(currentTaskDetails.id)] = currentTaskDetails;
|
||||
window.tasksById = tasksById;
|
||||
|
||||
const nextAction = currentTaskDetails.active ? 'end' : 'start';
|
||||
durationBtn.textContent = nextAction === 'start' ? 'شروع' : 'پایان';
|
||||
@@ -1301,18 +1508,22 @@ $state = [
|
||||
const sdEdit = $('#modalTaskStartDateEdit');
|
||||
const tdEdit = $('#modalTaskTargetDateEdit');
|
||||
|
||||
const pickerOptions = {
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
}
|
||||
};
|
||||
|
||||
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: 'امروز'}}
|
||||
},
|
||||
...pickerOptions,
|
||||
onSelect: function (unix) {
|
||||
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStr = gregorian.format('YYYY-MM-DD');
|
||||
@@ -1328,12 +1539,7 @@ $state = [
|
||||
} catch (e) {
|
||||
}
|
||||
tdEdit.persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
},
|
||||
...pickerOptions,
|
||||
onSelect: function (unix) {
|
||||
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStr = gregorian.format('YYYY-MM-DD');
|
||||
@@ -1376,7 +1582,6 @@ $state = [
|
||||
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) : '-';
|
||||
@@ -1452,6 +1657,35 @@ $state = [
|
||||
|
||||
// NOTE: مدیریت زمان از مودال جزئیات حذف شده و فقط در تب "زمانها" انجام میشود.
|
||||
|
||||
$(document).ready(function () {
|
||||
const datePickerOptions = {
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
}
|
||||
};
|
||||
|
||||
$('#dateFilterFrom').persianDatepicker({
|
||||
...datePickerOptions,
|
||||
onSelect: function (unix) {
|
||||
const gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
$('#dateFilterFromGregorian').val(gregorian.format('YYYY-MM-DD'));
|
||||
applyTaskFilters();
|
||||
}
|
||||
});
|
||||
|
||||
$('#dateFilterTo').persianDatepicker({
|
||||
...datePickerOptions,
|
||||
onSelect: function (unix) {
|
||||
const gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
$('#dateFilterToGregorian').val(gregorian.format('YYYY-MM-DD'));
|
||||
applyTaskFilters();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user