NASER add task functions

This commit is contained in:
mobinesaghi
2026-04-23 14:49:46 +03:30
parent 2419808175
commit 2bcf9f1b7e
47 changed files with 5793 additions and 1029 deletions
+12 -12
View File
@@ -358,18 +358,18 @@ class AdminController extends Controller
} }
private function fa2en_numbers($input) // private function fa2en_numbers($input)
{ // {
if (is_string($input)) { // if (is_string($input)) {
$fa_numbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; // $fa_numbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
$en_numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; // $en_numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
return str_replace($fa_numbers, $en_numbers, $input); // return str_replace($fa_numbers, $en_numbers, $input);
} elseif (is_array($input)) { // } elseif (is_array($input)) {
return array_map([$this, __FUNCTION__], $input); // return array_map([$this, __FUNCTION__], $input);
} else { // } else {
return $input; // return $input;
} // }
} // }
} }
+50 -127
View File
@@ -82,8 +82,7 @@ class AttendanceController extends Controller
? 'admin/' ? 'admin/'
: 'attendance/'; : 'attendance/';
$now = Carbon::now()->format('Y-m-d 00:00:00'); $now = Carbon::now()->format('Y-m-d 00:00:00');
$existed = $Attendance->whereNull('exited_at')->where('entered_at', '>=', $now)->first(); $existed = $Attendance->whereNull('exited_at')->where('entered_at', '>=', now()->format('Y-m-d 00:00:00'))->where('entered_at', '<=', now()->format('Y-m-d 23:59:59'))->first();
$user = User::where('user_name', $_COOKIE['user_identity'])->first(); $user = User::where('user_name', $_COOKIE['user_identity'])->first();
$until_now = null; $until_now = null;
@@ -321,7 +320,7 @@ class AttendanceController extends Controller
$attendance->duration = $duration; $attendance->duration = $duration;
$attendance->save(); $attendance->save();
send_tel($user->name, $attendance, 'attendance'); $this->send($user->name, $attendance, 'attendance');
return redirect()->back()->with('message', [ return redirect()->back()->with('message', [
'color' => '#3b82f6', 'color' => '#3b82f6',
@@ -340,7 +339,7 @@ class AttendanceController extends Controller
$attendance->office_location = ['lat' => $officeLat, 'lng' => $officeLng]; $attendance->office_location = ['lat' => $officeLat, 'lng' => $officeLng];
$attendance->save(); $attendance->save();
send_tel($user->name, $attendance, 'attendance'); $this->send($user->name, $attendance, 'attendance');
return redirect()->back()->with('message', [ return redirect()->back()->with('message', [
'color' => '#22c55e', 'color' => '#22c55e',
@@ -354,15 +353,15 @@ class AttendanceController extends Controller
if (!$user) { if (!$user) {
return response()->json(['error' => 'Unauthorized'], 401); return response()->json(['error' => 'Unauthorized'], 401);
} }
$attendance = Attendance::where('user_identity', $user->user_name) $attendance = Attendance::where('user_identity', $user->user_name)
->whereNull('exited_at') ->whereNull('exited_at')
->latest('entered_at') ->where('entered_at', '>=', now()->format('Y-m-d 00:00:00'))->where('entered_at', '<=', now()->format('Y-m-d 23:59:59'))
->first(); ->first();
$today = Carbon::now()->format('Y-m-d'); $today = Carbon::now()->format('Y-m-d');
$day = Carbon::parse($attendance->entered_at)->format('Y-m-d'); $day = Carbon::parse($attendance->entered_at)->format('Y-m-d');
if ($today !== $day || !$attendance) { if (!$attendance) {
return response()->json([ return response()->json([
'status' => 'error', 'status' => 'error',
'color' => '#C30004', 'color' => '#C30004',
@@ -386,7 +385,7 @@ class AttendanceController extends Controller
'duration' => $durationSeconds 'duration' => $durationSeconds
]); ]);
dd(send_tel($user->name, $break, 'break')); // dd($this->send($user->name, $break, 'break'));
return response()->json([ return response()->json([
"break_status" => 'ended', "break_status" => 'ended',
'duration' => $durationSeconds 'duration' => $durationSeconds
@@ -398,7 +397,7 @@ class AttendanceController extends Controller
'break_start' => $now, 'break_start' => $now,
]); ]);
dd(send_tel($user->name, $break, 'break')); // dd($this->send($user->name, $break, 'break'));
$user->update([ $user->update([
'break' => 1 'break' => 1
@@ -486,7 +485,7 @@ class AttendanceController extends Controller
$status = 'request'; $status = 'request';
$d = "ایجاد روز کاری"; $d = "ایجاد روز کاری";
send_tel($user->name, $d, $status); $this->send($user->name, $d, $status);
$user->req_counts += 1; $user->req_counts += 1;
$user->save(); $user->save();
return redirect('/')->with('message', [ return redirect('/')->with('message', [
@@ -517,7 +516,7 @@ class AttendanceController extends Controller
$att->save(); $att->save();
$status = 'request'; $status = 'request';
$d = "تغییر ساعت کاری"; $d = "تغییر ساعت کاری";
send_tel($user->name, $d, $status); $this->send($user->name, $d, $status);
$user->req_counts += 1; $user->req_counts += 1;
$user->save(); $user->save();
return redirect('/')->with('message', [ return redirect('/')->with('message', [
@@ -564,7 +563,7 @@ class AttendanceController extends Controller
$attendance->save(); $attendance->save();
$status = 'request'; $status = 'request';
$d = "حذف روز کاری"; $d = "حذف روز کاری";
send_tel($user->name, $d, $status); $this->send($user->name, $d, $status);
$user->req_counts += 1; $user->req_counts += 1;
$user->save(); $user->save();
return redirect()->back()->with('message', [ return redirect()->back()->with('message', [
@@ -643,122 +642,46 @@ class AttendanceController extends Controller
]); ]);
} }
private function fa2en_numbers($input)
{
if (is_string($input)) {
$fa_numbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
$en_numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
return str_replace($fa_numbers, $en_numbers, $input);
} elseif (is_array($input)) {
return array_map([$this, __FUNCTION__], $input);
} else {
return $input;
}
}
} }
function send_tel($user, $data, $status)
{
if ($status == 'attendance') {
// dd('hi');
$enter = Carbon::parse($data->entered_at)->format('H:i');
$date = Jalalian::fromCarbon(Carbon::parse($data->entered_at))->format('Y/m/d');
$exit = $data->exited_at ? Carbon::parse($data->exited_at)->format('H:i') : null;
// $user = json_decode($user);
$message = "تاریخ: {$date}\n {$user} در ساعت ";
if (!$exit) {
$message .= "{$enter} وارد شد.";
} else {
$message .= "{$exit} خارج شد.";
}
}
if ($status == 'break') { //function auto_break($user_id, $force_method = null)
$date = Jalalian::fromCarbon(Carbon::parse($data->break_start))->format('Y/m/d'); //{
$start = Carbon::parse($data->break_start)->format('H:i'); // $user = User::where('user_name', $user_id)->first();
$end = Carbon::parse($data->break_end)->format('H:i'); // if (!$attendances = Attendance::whereNull('exited_at')->latest('entered_at')->where('user_identity', $user_id)->first()) {
if (!$data->break_end) // throw new UnprocessableEntityHttpException('attendance not found');
$message = "تاریخ: {$date}\n {$user} در ساعت {$start} شروع به استراحت کرد."; // }
else // $break = "start";
$message = "تاریخ: {$date}\n {$user} در ساعت {$end} به استراحت پایان داد."; // if ($AttendanceBreak = AttendanceBreak::where('attendance_id', $attendances->id)->whereNull('break_end')->first()) {
} // $break = "end";
//
if ($status == 'request') { // }
$message = "{$user} درخواست {$data} ثبت کرد."; // if ($force_method) {
} // $break = $force_method;
// }
$admins = User::whereIn('is_admin', [1,2])->whereNotNull('chatid')->select('chatid', 'bale', 'user_name')->get(); //
if ($_COOKIE['user_identity'] !== 'test') { // if ($break == "end") {
foreach ($admins as $admin) { // $start = Carbon::parse($break->break_start)->format('H:i:s');
if (!$admin->bale) {continue;} // $end = Carbon::parse($break->break_end)->format('H:i:s');
// dd($admin->chatid); // $status = 'ended';
// Http::get("https://api.wwwapp.ir/telegran_send.php", [ // $att = new Attendance();
// 'botToken' => "7467099619:AAF2V6yYcbAb8oSIRSCyH5i2GKfdWVO3a7k", // $break->break_end = now();
// 'chatId' => $admin->chatid, // $break->duration = $att->duration($start, $end);
// 'message' => $message, // $break->save();
// ]); // $time = Carbon::parse($break->break_end)->format('H:i:s');
if ($admin->bale) { // sendbot($user->chatid, "پایان استراحت: $time");
try { //
$response = Http::post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [ // } elseif ($break == "start" and empty($AttendanceBreak)) {
"chat_id" => $admin->bale, // $status = 'started';
'text' => $message, // $break = new AttendanceBreak();
]); // $break->attendance_id = $attendances->id;
}catch (\Exception $e){} // $break->break_start = now();
} // $break->save();
Log::create([ // $time = Carbon::parse($break->break_start)->format('H:i:s');
'error' => $response->body(), //
'user' => "کاربر: $user // sendbot($user->chatid, "شروع استراحت: $time");
ادمین: {$admin->user_name}", //
]); // }
} // return $status ?? $break;
}else{ //}
try {
$response = Http::post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
"chat_id" => '985309723',
'text' => $message,
]);
}catch (\Exception $e){}
}
// return $message;
}
function auto_break($user_id, $force_method = null)
{
$user = User::where('user_name', $user_id)->first();
if (!$attendances = Attendance::whereNull('exited_at')->latest('entered_at')->where('user_identity', $user_id)->first()) {
throw new UnprocessableEntityHttpException('attendance not found');
}
$break = "start";
if ($AttendanceBreak = AttendanceBreak::where('attendance_id', $attendances->id)->whereNull('break_end')->first()) {
$break = "end";
}
if ($force_method) {
$break = $force_method;
}
if ($break == "end") {
$start = Carbon::parse($break->break_start)->format('H:i:s');
$end = Carbon::parse($break->break_end)->format('H:i:s');
$status = 'ended';
$att = new Attendance();
$break->break_end = now();
$break->duration = $att->duration($start, $end);
$break->save();
$time = Carbon::parse($break->break_end)->format('H:i:s');
sendbot($user->chatid, "پایان استراحت: $time");
} elseif ($break == "start" and empty($AttendanceBreak)) {
$status = 'started';
$break = new AttendanceBreak();
$break->attendance_id = $attendances->id;
$break->break_start = now();
$break->save();
$time = Carbon::parse($break->break_start)->format('H:i:s');
sendbot($user->chatid, "شروع استراحت: $time");
}
return $status ?? $break;
}
+118 -2
View File
@@ -1,11 +1,127 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Log;
use App\Models\Setting; use App\Models\Setting;
abstract class Controller use App\Models\User;
use Illuminate\Support\Facades\Http;
abstract class Controller
{ {
public static function Config($name) public static function Config($name)
{ {
return Setting::where('key',$name)->first()->value ?? false; return Setting::where('key',$name)->first()->value ?? false;
} }
}
public function fa2en_numbers($input)
{
if (is_string($input)) {
$fa_numbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
$en_numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
return str_replace($fa_numbers, $en_numbers, $input);
} elseif (is_array($input)) {
return array_map([$this, __FUNCTION__], $input);
} else {
return $input;
}
}
public function send($user, $data, $status)
{
if ($status == 'attendance') {
// dd('hi');
$enter = Carbon::parse($data->entered_at)->format('H:i');
$date = Jalalian::fromCarbon(Carbon::parse($data->entered_at))->format('Y/m/d');
$exit = $data->exited_at ? Carbon::parse($data->exited_at)->format('H:i') : null;
// $user = json_decode($user);
$message = "تاریخ: {$date}\n {$user} در ساعت ";
if (!$exit) {
$message .= "{$enter} وارد شد.";
} else {
$message .= "{$exit} خارج شد.";
}
}
if ($status == 'break') {
$date = Jalalian::fromCarbon(Carbon::parse($data->break_start))->format('Y/m/d');
$start = Carbon::parse($data->break_start)->format('H:i');
$end = Carbon::parse($data->break_end)->format('H:i');
if (!$data->break_end)
$message = "تاریخ: {$date}\n {$user} در ساعت {$start} شروع به استراحت کرد.";
else
$message = "تاریخ: {$date}\n {$user} در ساعت {$end} به استراحت پایان داد.";
}
if ($status == 'request') {
$message = "{$user} درخواست {$data} ثبت کرد.";
}
$admins = User::whereIn('is_admin', [1,2])->whereNotNull('chatid')->select('chatid', 'bale', 'user_name')->get();
if ($_COOKIE['user_identity'] !== 'test') {
foreach ($admins as $admin) {
if (!$admin->bale) {continue;}
// dd($admin->chatid);
// Http::get("https://api.wwwapp.ir/telegran_send.php", [
// 'botToken' => "7467099619:AAF2V6yYcbAb8oSIRSCyH5i2GKfdWVO3a7k",
// 'chatId' => $admin->chatid,
// 'message' => $message,
// ]);
if ($admin->bale) {
try {
$response = Http::post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
"chat_id" => $admin->bale,
'text' => $message,
]);
}catch (\Exception $e){}
}
Log::create([
'error' => $response->body(),
'user' => "کاربر: $user
ادمین: {$admin->user_name}",
]);
}
}else{
try {
$response = Http::post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
"chat_id" => '985309723',
'text' => $message,
]);
}catch (\Exception $e){}
}
// return $message;
}
public function send_admins($message)
{
$admins = User::whereNotNull('is_admin')->whereNotNull('bale')->select('bale')->get();
foreach ($admins as $admin) {
try{
Http::/*withOptions([
'curl' => [
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
CURLOPT_TCP_FASTOPEN => 1,
]
])->*/withoutVerifying()->post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
"chat_id" => $admin->bale,
'text' => $message,
]);
}catch (\Exception $e){}
}
}
public function send_level1_admins($message)
{
$admins = User::where('is_admin', 1)->whereNotNull('bale')->select('bale')->get();
foreach ($admins as $admin) {
try {
Http::withoutVerifying()->post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
"chat_id" => $admin->bale,
'text' => $message,
]);
} catch (\Exception $e) {
}
}
}
}
+480 -207
View File
@@ -2,6 +2,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\App;
use App\Models\TaskComment;
use App\Models\TaskDuration;
use http\Env\Response;
use http\Message;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Pivot_Tasks; use App\Models\Pivot_Tasks;
@@ -10,262 +15,530 @@ use GuzzleHttp\Client;
use App\Models\Tasks; use App\Models\Tasks;
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Morilog\Jalali\Jalalian;
use Carbon\CarbonInterval;
class TaskController extends Controller class TaskController extends Controller
{ {
private function taskChangeMessage(Tasks $task, array $before, array $after, ?User $actor): string
{
$lines = [];
$actorName = $actor?->name ?? 'نامشخص';
$titleBefore = (string)($before['title'] ?? '');
$titleAfter = (string)($after['title'] ?? $task->title ?? '');
$lines[] = "ویرایش تسک: «{$titleAfter}»";
$lines[] = "توسط: {$actorName}";
$appBefore = $before['app'] ?? null;
$appAfter = $after['app'] ?? $task->app ?? null;
$a1 = App::find($appBefore)?->name_fa ?? ($appBefore ?? '-');
$a2 = App::find($appAfter)?->name_fa ?? ($appAfter ?? '-');
$lines[] = ($appBefore != $appAfter) ? "اپ: {$a1} ← {$a2}" : "اپ: {$a2}";
$lines[] = (($before['title'] ?? null) !== ($after['title'] ?? null))
? "عنوان: {$titleBefore} ← {$titleAfter}"
: "عنوان: {$titleAfter}";
$priorityFa = [
'none' => 'ندارد',
'low' => 'کم',
'medium' => 'عادی',
'high' => 'زیاد',
'urgent' => 'فوری',
];
$p1 = $before['priority'] ?? null;
$p2 = $after['priority'] ?? null;
$lines[] = ($p1 !== $p2)
? "اولویت: " . ($priorityFa[$p1] ?? ($p1 ?? '-')) . " ← " . ($priorityFa[$p2] ?? ($p2 ?? '-'))
: "اولویت: " . ($priorityFa[$p2] ?? ($p2 ?? '-'));
$stateFa = [
1 => 'انباشته',
2 => 'برای انجام',
3 => 'درحال انجام',
4 => 'پایان یافته',
];
$s1 = (int)($before['state'] ?? 0);
$s2 = (int)($after['state'] ?? 0);
$lines[] = ($s1 !== $s2 && $s2)
? "وضعیت: " . ($stateFa[$s1] ?? ($before['state'] ?? '-')) . " ← " . ($stateFa[$s2] ?? ($after['state'] ?? '-'))
: "وضعیت: " . ($stateFa[$s2] ?? ($after['state'] ?? '-'));
$d1 = $before['start_date'] ?? null;
$d2 = $after['start_date'] ?? null;
$lines[] = ($d1 !== $d2)
? "شروع: " . ($d1 ?: '-') . " ← " . ($d2 ?: '-')
: "شروع: " . ($d2 ?: '-');
$t1 = $before['target_date'] ?? null;
$t2 = $after['target_date'] ?? null;
$lines[] = ($t1 !== $t2)
? "ددلاین: " . ($t1 ?: '-') . " ← " . ($t2 ?: '-')
: "ددلاین: " . ($t2 ?: '-');
$desc1 = trim((string)($before['description'] ?? ''));
$desc2 = trim((string)($after['description'] ?? ''));
$lines[] = ($desc1 !== $desc2)
? "توضیحات: " . (!empty($desc2) ? 'تغییر کرد' : 'پاک شد')
: "توضیحات: بدون تغییر";
$aBefore = $before['assignees'] ?? null;
$aAfter = $after['assignees'] ?? null;
$arr1 = is_string($aBefore) ? json_decode($aBefore, true) : $aBefore;
$arr2 = is_string($aAfter) ? json_decode($aAfter, true) : $aAfter;
$arr1 = is_array($arr1) ? array_map('strval', $arr1) : [];
$arr2 = is_array($arr2) ? array_map('strval', $arr2) : [];
sort($arr1);
sort($arr2);
$names1 = User::whereIn('id', $arr1)->pluck('name')->all();
$names2 = User::whereIn('id', $arr2)->pluck('name')->all();
$lines[] = ($arr1 !== $arr2)
? "مسئولین: " . (!empty($names1) ? implode('، ', $names1) : '-') . " ← " . (!empty($names2) ? implode('، ', $names2) : '-')
: "مسئولین: " . (!empty($names2) ? implode('، ', $names2) : '-');
return implode("\n", $lines);
}
// public function tasks()
// {
// $user = User::where('user_name', $_COOKIE['user_identity'])->first();
// $tasks = $user->tasks;
// $users = User::get();
//
// $state_order = [
// "c73c2044-ea14-42cb-b08d-cecf3d1fdfb5",
// "a9ce312f-cfca-491e-8e3d-1975aa6ebc10",
// "dea1fb57-a281-4fe7-8f3e-ee5567637e35",
// "770fbe42-99f6-41db-9b9e-773d9cced1a5",
// "b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0",
// "d61d14a3-72f7-4f44-894b-efe8b2a75be1",
// ];
//
// $work_item = $tasks->whereNull('parent')->sortBy(fn($t) => array_search($t->state, $state_order));
// $sub_work_items = $tasks->whereNotNull('parent')->sortBy(fn($t) => array_search($t->state, $state_order));
//
// foreach ($work_item as $w) {
// foreach ($sub_work_items as $t) {
// foreach ($users as $u) {
// if ($t->created_by == $u->plain_id)
// $t->created_by = $u->name;
// if ($w->created_by == $u->plain_id)
// $w->created_by = $u->name;
// }
// }
// }
//
// return view('tasks.tasks', compact('tasks', 'work_item', 'sub_work_items'));
// }
public function tasks() public function tasks()
{ {
$user = User::where('user_name', $_COOKIE['user_identity'])->first(); $user = User::where('user_name', $_COOKIE['user_identity'])->first();
$tasks = $user->tasks; if ($user) {
$users = User::get(); if ($user->is_admin == 1) {
$tasks = Tasks::with(['duration', 'comments.user'])
->orderBy('target_date', 'desc')
->get();
// dd($tasks);
foreach ($tasks as $task) {
$totalSeconds = 0;
foreach ($task->duration as $item) {
if ($item->duration !== null) {
$totalSeconds += CarbonInterval::createFromFormat('H:i:s', $item->duration)->totalSeconds;
}
}
$state_order = [ $task->totalDuration = CarbonInterval::seconds($totalSeconds)->cascade()->format('%H:%I:%S');
"c73c2044-ea14-42cb-b08d-cecf3d1fdfb5", $active_task = TaskDuration::where('task_id', $task->id)->orderBy('start', 'desc')->first();
"a9ce312f-cfca-491e-8e3d-1975aa6ebc10", $task->active = !empty($active_task) && $active_task->end == null;
"dea1fb57-a281-4fe7-8f3e-ee5567637e35", }
"770fbe42-99f6-41db-9b9e-773d9cced1a5", } else {
"b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0", $tasks = Tasks::with(['duration', 'comments.user'])->where('created_by', $user->id)
"d61d14a3-72f7-4f44-894b-efe8b2a75be1", ->orWhereJsonContains('assignees', (string)$user->id)
]; ->orderBy('target_date', 'desc')
->get();
// dd($tasks);
foreach ($tasks as $task) {
$totalSeconds = 0;
foreach ($task->duration as $item) {
if ($user->id == $item->user_id && $item->duration !== null) {
$totalSeconds += CarbonInterval::createFromFormat('H:i:s', $item->duration)->totalSeconds;
}
}
$work_item = $tasks->whereNull('parent')->sortBy(fn($t) => array_search($t->state, $state_order)); $task->totalDuration = CarbonInterval::seconds($totalSeconds)->cascade()->format('%H:%I:%S');
$sub_work_items = $tasks->whereNotNull('parent')->sortBy(fn($t) => array_search($t->state, $state_order)); $active_task = TaskDuration::where('task_id', $task->id)->where('user_id', $user->id)->orderBy('start', 'desc')->first();
$task->active = !empty($active_task) && $active_task->end == null;
foreach ($work_item as $w) {
foreach ($sub_work_items as $t) {
foreach ($users as $u) {
if ($t->created_by == $u->plain_id)
$t->created_by = $u->name;
if ($w->created_by == $u->plain_id)
$w->created_by = $u->name;
} }
} }
}
return view('tasks.tasks', compact('tasks', 'work_item', 'sub_work_items')); $count = count($tasks);
// dd((new Tasks())->filterByState($tasks)['backlog'][0], 'count ='.$count);
return view('tasks.tasks', [
'tasks' => (new Tasks())->filterByState($tasks),
'count' => $count,
'user_id' => $user->id,
]);
} else {
return back()->with('message', [
'color' => 'red',
'text' => 'کاربر یافت نشد!'
]);
}
} }
public function activeTask(Request $request) public function showTask($id)
{ {
// dd($request->all());
// dd($request->uid);
$user = User::where('id', $request->uid)->first();
$p_task = Pivot_Tasks::where('task_id', $request->task_id)
->where('user_id', $user->plain_id)->first();
$task = Tasks::where('task_id', $request->task_id)->first();
if ($p_task) {
// dd('task is set in pivot');
if ($p_task->started_at == null || ($p_task->started_at !== null && $p_task->ended_at !== null)) {
// dd('in if');
$p_task->started_at = now(); $task = Tasks::with(['comments.user'])->find($id ?? null);
$p_task->ended_at = null; return view('tasks.create', ['task' => $task]);
$user->active_task = $task->name;
$p_task->save();
$user->save();
return back()->with('message', [
'color' => 'green',
'text' => 'وظیفه شما شروع شد، موفق باشید.'
]);
} else {
// dd('in elseif');
$now = now(); }
$attendance = new Attendance();
$s = Carbon::parse($p_task->started_at)->format('H:i:s');
$e = Carbon::parse($now)->format('H:i:s');
$duration = $p_task->duration;
if ($e > $s) {
$diff = $attendance->duration($s, $e);
if ($duration !== null) {
[$h1, $m1, $s1] = explode(':', $duration);
$durationSeconds = $h1 * 3600 + $m1 * 60 + $s1;
[$h2, $m2, $s2] = explode(':', $diff); public function editTask(Request $request)
$diffSeconds = $h2 * 3600 + $m2 * 60 + $s2; {
// dd($request->all());
if (!isset($_COOKIE['user_identity'])) {
return back()->with('message', [
'color' => 'red',
'text' => 'ابتدا باید وارد شوید!'
]);
}
$actor = User::where('user_name', $_COOKIE['user_identity'])->first();
$totalSeconds = $durationSeconds + $diffSeconds; $data = $request->all();
$task = Tasks::find($data['id'] ?? null);
if (!$task) {
return back()->with('message', [
'color' => 'red',
'text' => 'وظیفه یافت نشد!'
]);
}
$hours = floor($totalSeconds / 3600); $before = $task->toArray();
$minutes = floor(($totalSeconds % 3600) / 60);
$seconds = $totalSeconds % 60;
$duration = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); $data['updated_by'] = $actor?->id;
} else { if (array_key_exists('assignees', $data)) {
$duration = $diff; $data['assignees'] = !empty($data['assignees']) ? json_encode($data['assignees']) : null;
} } else {
$p_task->ended_at = $now; unset($data['assignees']);
$p_task->duration = $duration; }
$user->active_task = null; unset($data['_token'], $data['id'], $data['start_date_display'], $data['target_date_display']);
$p_task->save();
$user->save(); $task->update($data);
return back()->with('message', [ $after = $task->toArray();
'color' => 'green',
'text' => 'وظیفه شما با موفقیت پایان یافت.' $title = $task->title;
]); $message = "تسک «{$title}» برای شما به‌روزرسانی شد.";
$message_admin = $this->taskChangeMessage($task, $before, $after, $actor);
if (!empty($request->assignees)) {
foreach ($request->assignees as $assignee) {
$assigneeUser = User::where('id', $assignee)->first();
if ($assigneeUser && $assigneeUser->bale !== null) {
$this->send_bale($assigneeUser->bale, $message);
} }
} }
} }
// dd($data, $message, $message_admin);
$this->send_level1_admins($message_admin);
return back()->with('message', [ return back()->with('message', [
'color' => 'red', 'color' => 'green',
'text' => 'وظیفه یافت نشد✖️' 'text' => $message
]); ]);
// $action = $request->input('action_type');
// $start = $request->input('started_at');
// $end = $request->input('ended_at');
// $duration = $request->input('duration');
//
// if ($action == 'end') {
// $attendance = new Attendance();
// $s = Carbon::parse($start)->format('H:i:s');
// $e = Carbon::parse($end)->format('H:i:s');
// if ($e > $s) {
// $diff = $attendance->duration($s, $e);
// if ($duration !== null) {
// [$h1, $m1, $s1] = explode(':', $duration);
// $durationSeconds = $h1 * 3600 + $m1 * 60 + $s1;
//
// [$h2, $m2, $s2] = explode(':', $diff);
// $diffSeconds = $h2 * 3600 + $m2 * 60 + $s2;
//
// $totalSeconds = $durationSeconds + $diffSeconds;
//
// $hours = floor($totalSeconds / 3600);
// $minutes = floor(($totalSeconds % 3600) / 60);
// $seconds = $totalSeconds % 60;
//
// $duration = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
// } else {
// $duration = $diff;
// }
// }
// $data = [
// 'started_at' => $start,
// 'ended_at' => $end,
// 'duration' => $duration,
// ];
// } else {
// $data = [
// 'started_at' => $start,
// 'ended_at' => null,
//
// ];
// }
// try {
//
// DB::table('pivot_tasks')->where('id', $request->p_id)->update($data);
//
//
// return "record saved";
// } catch (\Exception $e) {
// return $e->getMessage();
// }
} }
public function createTask() public function createTask()
{ {
$users = User::whereNotNull('plain_id')->get(); return view('tasks.create');
$tasks = Tasks::all(); }
return view('tasks.create', [ public function deleteTask($id)
'users' => $users, {
'tasks' => $tasks, Tasks::destroy($id);
return response()->json([
"success" => true,
"message" => "Task deleted successfully.",
"task_id" => 45
]); ]);
} }
public function addTask(Request $request){
// dd($request);
$task = new Tasks();
$task->name = $request->input('taskTitle');
$task->created_at = Carbon::now()->format('Y-m-d H:i:s');
$task->save();
return response()->with('message', "تسک اضافه شد");
}
public function storeTask(Request $request) public function storeTask(Request $request)
{ {
$created_by = User::where('user_name', $_COOKIE['user_identity'])->first()->plain_id; // dd($request->all());
if (!isset($_COOKIE['user_identity'])) {
return back()->with('message', [
'color' => 'red',
'text' => 'ابتدا باید وارد شوید!'
]);
}
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
Tasks::create([ $data = $request->all();
"name" => $request->name, $data['created_by'] = $user->id;
"desc" => $request->description_html, $data['assignees'] = !empty($data['assignees']) ? json_encode($data['assignees']) : null;
"priority" => $request->priority,
"start_date" => $request->start_date,
"target_date" => $request->target_date,
"state" => $request->state,
"assignees" => $request->assignees,
"parent" => $request->parent,
"estimate_point" => $request->estimate_point,
]); $task = Tasks::create($data);
// $headers = [ $message = "تسک جدیدی با عنوان {$data['title']} برای شما ایجاد شد.";
// 'x-api-key' => 'plane_api_ec378e977ceb4bd1bbd2332855370645', $deadline = !empty($data['target_date']) ? Jalalian::fromCarbon(Carbon::parse($data['target_date']))->format('Y-m-d') : null;
// 'Content-Type' => 'application/json' $app = App::find($data['app']);
// ]; $message_admin = "ایجاد تسک: «{$data['title']}»";
// $message_admin .= !empty($app) ? "\nاپ {$app->name_fa}" : null;
// $body = json_encode([ $message_admin .= !empty($deadline) ? "\nددلاین {$deadline}" : null;
// "name" => $name, $message_admin .= !empty($data['start_date']) ? "\nتاریخ شروع {$data['start_date']}" : null;
// "description_html" => $desc ?: '', $message_admin .= !empty($data['state_fa']) ? "\nوضعیت: {$data['state_fa']}" : null;
// "priority" => $priority ?: 'none', $message_admin .= !empty($data['description']) ? "\nبا توضیحات:\n" . $data['description'] : null;
// "start_date" => $start_date ?: null, $message_admin .= !empty($data['assignees']) ? "\nتوسط " . $user->name . " ساخته شد و به" : null;
// "target_date" => $target_date ?: null,
// "estimate_point" => $estimate_point ?: '',
// "state" => $state ?: '',
// "parent" => $parent ?: '',
// "assignees" => $assignees ?: '',
// // "labels" => $labels ?: null,
// // "created_by" => $created_by
// ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
//
// $client = new Client();
//
// $response = $client->post(
// 'https://api.plane.so/api/v1/workspaces/montzar/projects/c1df4085-582f-4b57-b3c6-95b7d4d19007/issues/',
// [
// 'headers' => $headers,
// 'body' => $body,
// ]
// );
//
// $data = json_decode($response->getBody(), true);
// update_tasks($data); if (!empty($data['assignees'])) {
foreach ($request->assignees as $assignee) {
$user = User::where('id', $assignee)->first();
$message_admin .= "\n$user->name";
if ($user && $user->bale !== null) {
$this->send_bale($user->bale, $message);
}
}
$message_admin .= "\nاختصاص یافت.";
} else {
$message_admin .= "\nتوسط " . $user->name . " ساخته شد";
}
// dd($data, $message, $message_admin);
$this->send_level1_admins($message_admin);
return back()->with('message', [ return back()->with('message', [
'color' => 'green', 'color' => 'green',
'text' => 'وظیفه با موفقیت ایجاد شد.' 'text' => $message
]); ]);
} }
// public function webhookPlane(Request $request) public function activeTask(Request $request)
// { {
// $d = $request->all(); // dd($request);
// $user = User::where('user_name', $_COOKIE['user_identity'])->first();
// // فقط وقتی تغییر state هست $task = Tasks::find($request->task_id);
// if (isset($d['event'], $d['action'], $d['data']['name'], $d['data']['state']['name'])) { if (!$task) {
// $msg = "Event: {$d['event']}\nAction: {$d['action']}\nName: {$d['data']['name']}\nNew Status: {$d['data']['state']['name']}"; return response()->json([
// 'action' => 'not_found',
// // ارسال به تلگرام 'status' => false
// Http::get("https://api.wwwapp.ir/telegran_send.php", [ ]);
// 'botToken' => "7467099619:AAF2V6yYcbAb8oSIRSCyH5i2GKfdWVO3a7k", }
// 'chatId' => "-4742642674",
// 'message' => $msg, $taskDU = TaskDuration::where('user_id', $user->id)
// ]); ->where('task_id', $request->task_id)
// } ->orderBy('id', 'desc')
// ->first();
// // اگر خواستی ثبت در دیتابیس هم اضافه کنی
// // DB::table('tst')->insert([ // اگر رکورد باز داریم => پایان
// // 'data' => json_encode($d, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), if (!empty($taskDU) && ($taskDU->start !== null && $taskDU->end == null)) {
// // ]); // dd('in if');
//
// return response()->json(['status' => 'ok'], 200); $attendance = new Attendance();
// } $s = Carbon::parse($taskDU->start)->format('H:i:s');
$e = Carbon::parse(now())->format('H:i:s');
$duration = $taskDU->duration;
if ($e > $s) {
$diff = $attendance->duration($s, $e);
if ($duration !== null) {
[$h1, $m1, $s1] = explode(':', $duration);
$durationSeconds = $h1 * 3600 + $m1 * 60 + $s1;
[$h2, $m2, $s2] = explode(':', $diff);
$diffSeconds = $h2 * 3600 + $m2 * 60 + $s2;
$totalSeconds = $durationSeconds + $diffSeconds;
$hours = floor($totalSeconds / 3600);
$minutes = floor(($totalSeconds % 3600) / 60);
$seconds = $totalSeconds % 60;
$duration = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
} else {
$duration = $diff;
}
$taskDU->end = now();
$taskDU->duration = $duration;
$user->active_task = null;
$taskDU->save();
$user->save();
$this->send_level1_admins("پایان تسک: «{$task->title}»\nتوسط: {$user->name}\nمدت این نوبت: {$duration}");
return response()->json([
'action' => 'ended',
'status' => true,
'duration' => $duration,
'start' => $taskDU->start,
'end' => $taskDU->end,
]);
} else
return response()->json([
'action' => 'end',
'status' => false
]);
} else {
// شروع => همیشه یک رکورد جدید بساز
$new_task_duration = TaskDuration::create([
'task_id' => $task->id,
'user_id' => $user->id,
'start' => now(),
'end' => null,
]);
$user->active_task = $task->title;
$user->save();
$this->send_level1_admins("شروع تسک: «{$task->title}»\nتوسط: {$user->name}");
return response()->json([
'action' => 'start',
'status' => true,
'start' => $new_task_duration->start,
'id' => $new_task_duration->id,
]);
}
return back()->with('message', ['color' => 'red',
'text' => 'وظیفه یافت نشد✖️']);
}
public function addTask(Request $request)
{
if (isset($_COOKIE['user_identity'])) {
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
} else {
return back()->with('message', [
'color' => 'red',
'text' => 'auth failed!'
]);
}
$request->validate([
'title' => 'required',
'description' => 'required',
'assignees' => 'required',
'start_date' => 'required',
'target_date' => 'required',
'priority' => 'required',
'description_html' => 'required',
'completed_at' => 'required',
'state' => 'required',
'created_at' => 'required',
'updated_at' => 'required',
'app' => 'required',
'task_type' => 'required',
]);
$data = $request->all();
$data['user_id'] = $user->id;
Tasks::create([$data]);
return back()->with('message', [
'color' => 'green',
'text' => 'وظیفه ایجاد شد.'
]);
}
public function storeComment(Request $request, $id)
{
if (!isset($_COOKIE['user_identity'])) {
return back()->with('message', [
'color' => 'red',
'text' => 'ابتدا باید وارد شوید!'
]);
}
$actor = User::where('user_name', $_COOKIE['user_identity'])->first();
$task = Tasks::find($id ?? null);
if (!$task) {
return back()->with('message', [
'color' => 'red',
'text' => 'وظیفه یافت نشد!'
]);
}
$request->validate([
'body' => 'required|string',
]);
$body = trim((string)$request->input('body'));
preg_match_all('/@([\p{L}\p{N}_.-]+)/u', $body, $matches);
$usernames = array_values(array_unique($matches[1] ?? []));
$mentionedUsers = !empty($usernames)
? User::whereIn('user_name', $usernames)->get()
: collect();
$mentionedIds = $mentionedUsers->pluck('id')->values()->all();
TaskComment::create([
'task_id' => $task->id,
'user_id' => $actor->id,
'body' => $body,
'mentioned_user_ids' => !empty($mentionedIds) ? $mentionedIds : null,
]);
$text = "کامنت جدید روی تسک «{$task->title}»\n"
. "توسط: {$actor->name}\n\n"
. $body;
foreach ($mentionedUsers as $mu) {
if ($mu->id === $actor->id) {
continue;
}
if ($mu->bale) {
$this->send_bale($mu->bale, $text);
}
}
$this->send_level1_admins($text);
return back()->with('message', [
'color' => 'green',
'text' => 'کامنت ثبت شد.'
]);
}
public function webhookPlane(Request $request)
{
$d = $request->all();
// فقط وقتی تغییر state هست
if (isset($d['event'], $d['action'], $d['data']['name'], $d['data']['state']['name'])) {
$msg = "Event: {$d['event']}\nAction: {$d['action']}\nName: {$d['data']['name']}\nNew Status: {$d['data']['state']['name']}";
// ارسال به تلگرام
Http::get("https://api.wwwapp.ir/telegran_send.php", [
'botToken' => "7467099619:AAF2V6yYcbAb8oSIRSCyH5i2GKfdWVO3a7k",
'chatId' => "-4742642674",
'message' => $msg,
]);
}
// اگر خواستی ثبت در دیتابیس هم اضافه کنی
// DB::table('tst')->insert([
// 'data' => json_encode($d, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
// ]);
return response()->json(['status' => 'ok'], 200);
}
private function send_bale($chat_id, $message)
{
try {
Http::withOptions([
'curl' => [
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
CURLOPT_TCP_FASTOPEN => 1,
]
])->withoutVerifying()->post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [
"chat_id" => $chat_id,
'text' => $message,
]);
} catch (\Exception $e) {
}
}
} }
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Console\View\Components\Task;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class App extends Model
{
use HasFactory;
protected $table = 'apps';
protected $fillable = [
'name',
'created_at',
'updated_at',
'deleted_at',
'name_fa',
'type',
];
protected $TYPE = [
1=>'خانگی',
2=>'همکار'
];
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TaskComment extends Model
{
use HasFactory;
protected $table = 'task_comments';
protected $fillable = [
'task_id',
'user_id',
'body',
'mentioned_user_ids',
];
protected $casts = [
'mentioned_user_ids' => 'array',
];
public function task()
{
return $this->belongsTo(Tasks::class, 'task_id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Console\View\Components\Task;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class TaskDuration extends Model
{
use HasFactory;
protected $table = 'tasks_duration';
public $timestamps = false;
protected $fillable = [
'task_id',
'duration',
'start',
'end',
'user_id',
];
public function task(){
return $this->belongsTo(Tasks::class, 'task_id', 'id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
+19 -3
View File
@@ -6,15 +6,16 @@ use Illuminate\Console\View\Components\Task;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\SoftDeletes;
class Tasks extends Model class Tasks extends Model
{ {
use HasFactory; use HasFactory,SoftDeletes;
protected $table = 'tasks'; protected $table = 'tasks';
protected $fillable = [ protected $fillable = [
'task_id', 'task_id',
'title', 'title',
@@ -65,6 +66,21 @@ class Tasks extends Model
return $this->belogsTo(App::class); return $this->belogsTo(App::class);
} }
public function duration(){
return $this->hasMany(TaskDuration::class, 'task_id', 'id');
}
public function comments()
{
return $this->hasMany(TaskComment::class, 'task_id', 'id')->orderBy('id', 'desc');
}
public function userDurations($userId = null)
{
$userId = $userId ?: User::where('user_name', $_COOKIE['user_identity'])->first()->id; // پیش‌فرض: کاربر جاری
return $this->taskDurations()->where('user_id', $userId);
}
public function filterByState($tasks){ public function filterByState($tasks){
$backlog = []; $backlog = [];
$todo = []; $todo = [];
@@ -86,7 +102,7 @@ class Tasks extends Model
'todo' => $todo, 'todo' => $todo,
'inprogress' => $inprogress, 'inprogress' => $inprogress,
'done' => $done, 'done' => $done,
'count' => count($tasks), // 'count' => count($tasks),
]; ];
return $tasks; return $tasks;
} }
+2 -1
View File
@@ -6,6 +6,7 @@ namespace App\Models;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@@ -15,7 +16,7 @@ use Morilog\Jalali\Jalalian;
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<\Database\Factories\UserFactory> */ /** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable; use HasFactory, Notifiable, softDeletes;
/** /**
+1 -3
View File
@@ -10,9 +10,7 @@
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1", "laravel/tinker": "^2.10.1",
"morilog/jalali": "^3.4", "morilog/jalali": "^3.4",
"predis/predis": "^3.2", "predis/predis": "^3.2"
"tymon/jwt-auth": "^1.0"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
Generated
-85
View File
@@ -5840,91 +5840,6 @@
], ],
"time": "2025-04-30T23:37:27+00:00" "time": "2025-04-30T23:37:27+00:00"
}, },
{
"name": "tymon/jwt-auth",
"version": "dev-develop",
"source": {
"type": "git",
"url": "https://github.com/tymondesigns/jwt-auth.git",
"reference": "014be8d493d228d14bbc291b24e835d330c092a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tymondesigns/jwt-auth/zipball/014be8d493d228d14bbc291b24e835d330c092a0",
"reference": "014be8d493d228d14bbc291b24e835d330c092a0",
"shasum": ""
},
"require": {
"illuminate/auth": "^5.2|^6|^7|^8|^9",
"illuminate/contracts": "^5.2|^6|^7|^8|^9",
"illuminate/http": "^5.2|^6|^7|^8|^9",
"illuminate/support": "^5.2|^6|^7|^8|^9",
"lcobucci/jwt": "^3.4|^4.0",
"namshi/jose": "^7.0",
"nesbot/carbon": "^1.0|^2.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"illuminate/console": "^5.2|^6|^7|^8|^9",
"illuminate/database": "^5.2|^6|^7|^8|^9",
"illuminate/routing": "^5.2|^6|^7|^8|^9",
"mockery/mockery": ">=0.9.9",
"phpunit/phpunit": "^8.5|^9.4",
"yoast/phpunit-polyfills": "^0.2.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"JWTAuth": "Tymon\\JWTAuth\\Facades\\JWTAuth",
"JWTFactory": "Tymon\\JWTAuth\\Facades\\JWTFactory"
},
"providers": [
"Tymon\\JWTAuth\\Providers\\LaravelServiceProvider"
]
},
"branch-alias": {
"dev-develop": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Tymon\\JWTAuth\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sean Tymon",
"email": "tymon148@gmail.com",
"homepage": "https://tymon.xyz",
"role": "Developer"
}
],
"description": "JSON Web Token Authentication for Laravel and Lumen",
"homepage": "https://github.com/tymondesigns/jwt-auth",
"keywords": [
"Authentication",
"JSON Web Token",
"auth",
"jwt",
"laravel"
],
"support": {
"issues": "https://github.com/tymondesigns/jwt-auth/issues",
"source": "https://github.com/tymondesigns/jwt-auth"
},
"funding": [
{
"url": "https://www.patreon.com/seantymon",
"type": "patreon"
}
],
"time": "2022-04-27T08:53:50+00:00"
},
{ {
"name": "voku/portable-ascii", "name": "voku/portable-ascii",
"version": "2.0.3", "version": "2.0.3",
+78
View File
@@ -0,0 +1,78 @@
<?php
use Tonysm\RichTextLaravel\Editor\LexxyEditor;
use Tonysm\RichTextLaravel\Editor\TrixEditor;
use Tonysm\RichTextLaravel\Models\EncryptedRichText;
use Tonysm\RichTextLaravel\Models\RichText;
return [
/*
|--------------------------------------------------------------------------
| Rich Text Model
|--------------------------------------------------------------------------
|
| When using the suggested database structure, all your Rich Text content will be
| stored in the same Database table. All interactions with that table happens
| using this Eloquent Model. You can override this if you really need to.
|
*/
'model' => RichText::class,
/*
|--------------------------------------------------------------------------
| Encrypted Rich Text Model
|--------------------------------------------------------------------------
|
| When setting the `encrypted` option to `true` on the attribute, the package
| will use this model instead of the base RichText model.
|
*/
'encrypted_model' => EncryptedRichText::class,
/*
|--------------------------------------------------------------------------
| Supported Files Content-Types
|--------------------------------------------------------------------------
|
| When attaching non-image files to Trix, you can control here which files
| you want to handle and render in the default "remote file" template
| by explicitly adding your supported Content-Types to this list.
|
*/
'supported_files_content_types' => [
'application/pdf',
'text/csv',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
],
/*
|--------------------------------------------------------------------------
| Default Editor
|--------------------------------------------------------------------------
|
| Here you may specify the default rich text editor. Use the editor name
| (e.g., 'trix') which will be used to resolve the editor class and
| install the corresponding frontend assets during installation.
|
*/
'editor' => env('RICH_TEXT_EDITOR', 'lexxy'),
/*
|--------------------------------------------------------------------------
| Registered Editors
|--------------------------------------------------------------------------
|
| Here you may register the available editors for your application.
| The key is the editor name used above, and the value is the
| fully qualified class name of the editor implementation.
|
*/
'editors' => [
'trix' => TrixEditor::class,
'lexxy' => LexxyEditor::class,
],
];
@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up()
{
Schema::create('rich_texts', function (Blueprint $table) {
$table->id();
$table->morphs('record');
$table->string('field');
$table->longText('body')->nullable();
$table->timestamps();
$table->unique(['field', 'record_type', 'record_id']);
});
}
public function down()
{
Schema::dropIfExists('rich_texts');
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('task_comments', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('task_id');
$table->unsignedBigInteger('user_id');
$table->text('body');
$table->json('mentioned_user_ids')->nullable();
$table->timestamps();
$table->index(['task_id', 'created_at']);
$table->index(['user_id', 'created_at']);
});
}
public function down(): void
{
Schema::dropIfExists('task_comments');
}
};
+2718
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -7,11 +7,13 @@
"dev": "vite" "dev": "vite"
}, },
"devDependencies": { "devDependencies": {
"@37signals/lexxy": "^0.8.5-beta",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"axios": "^1.8.2", "axios": "^1.8.2",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0", "laravel-vite-plugin": "^1.2.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"trix": "^2.1.16",
"vite": "^6.2.4" "vite": "^6.2.4"
} }
} }
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
:where(lexxy-editor){rich-text-attachment {cursor: pointer;}}:where(rich-text-attachment){--lexxy-attachment-bg-color: transparent;--lexxy-attachment-image-size: 1em;--lexxy-attachment-text-color: currentColor;align-items:center;background:var(--lexxy-attachment-bg-color);border-radius:var(--lexxy-radius);box-shadow:-.25ch 0 0 var(--lexxy-attachment-bg-color),.5ch 0 0 var(--lexxy-attachment-bg-color);color:var(--lexxy-attachment-text-color);display:inline-flex;gap:.25ch;margin:0;padding:0;position:relative;white-space:normal;img {block-size: var(--lexxy-attachment-image-size); border-radius: 50%; inline-size: var(--lexxy-attachment-image-size);} &.node--selected { --lexxy-attachment-bg-color: var(--lexxy-color-accent-dark); --lexxy-attachment-text-color: var(--lexxy-color-ink-inverted); }}
@@ -0,0 +1 @@
:root{--lexxy-color-canvas: var(--color-base-100);--lexxy-color-ink: var(--color-base-content);--lexxy-color-ink-medium: color-mix(in oklch, var(--color-base-content) 60%, transparent);--lexxy-color-ink-light: color-mix(in oklch, var(--color-base-content) 40%, transparent);--lexxy-color-ink-lighter: color-mix(in oklch, var(--color-base-content) 20%, transparent);--lexxy-color-ink-lightest: var(--color-base-200);--lexxy-color-ink-inverted: var(--color-base-100);--lexxy-color-accent-dark: var(--color-primary);--lexxy-color-accent-medium: color-mix(in oklch, var(--color-primary) 75%, var(--color-base-100));--lexxy-color-accent-light: color-mix(in oklch, var(--color-primary) 15%, var(--color-base-100));--lexxy-color-accent-lightest: color-mix(in oklch, var(--color-primary) 8%, var(--color-base-100));--lexxy-color-red: var(--color-error);--lexxy-color-green: var(--color-success);--lexxy-color-blue: var(--color-info);--lexxy-color-purple: var(--color-secondary);--lexxy-color-text: var(--color-base-content);--lexxy-color-text-subtle: color-mix(in oklch, var(--color-base-content) 60%, transparent);--lexxy-color-link: var(--color-primary);--lexxy-color-selected: color-mix(in oklch, var(--color-primary) 10%, var(--color-base-100));--lexxy-color-selected-hover: color-mix(in oklch, var(--color-primary) 18%, var(--color-base-100));--lexxy-color-selected-dark: var(--color-primary);--lexxy-color-code-bg: var(--color-base-200);--lexxy-color-table-header-bg: var(--color-base-200);--lexxy-color-table-cell-border: var(--color-base-300);--lexxy-focus-ring-color: var(--color-primary);--lexxy-radius: var(--radius-field);--lexxy-shadow: 0 2px 8px color-mix(in oklch, var(--color-base-content) 10%, transparent)}lexxy-editor{border-width:var(--border);font-size:.875rem;&:focus-within{outline:2px solid var(--color-base-content);outline-offset:2px}:is(code,pre):is(pre,[data-language]) {padding: 2ch;} lexxy-code-language-picker select { --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000); appearance: none; background-color: var(--color-base-100); border: var(--border) solid var(--input-color); border-radius: var(--radius-field); color: var(--color-base-content); font-family: inherit; font-size: var(--lexxy-text-small); height: calc(var(--size-field, .25rem) * 8); padding-inline-start: .75rem; padding-inline-end: 1.75rem; background-image: linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%); background-position: calc(100% - 14px) calc(1px + 50%), calc(100% - 10.1px) calc(1px + 50%); background-size: 4px 4px, 4px 4px; background-repeat: no-repeat; margin: .5ch; &:focus { --input-color: var(--color-base-content); outline: 2px solid var(--input-color); outline-offset: 2px; } }}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
{
"/lexxy.css": "/vendor/rich-text-laravel/lexxy-64053482.css",
"/trix.css": "/vendor/rich-text-laravel/trix-4dba4d46.css",
"/lexxy-rich-text-laravel-attachments.css": "/vendor/rich-text-laravel/lexxy-rich-text-laravel-attachments-4974baec.css",
"/lexxy-rich-text-laravel-daisyui.css": "/vendor/rich-text-laravel/lexxy-rich-text-laravel-daisyui-9dcf6ff2.css",
"/lexxy-rich-text-laravel-flux.css": "/vendor/rich-text-laravel/lexxy-rich-text-laravel-flux-4459f325.css",
"/trix-rich-text-laravel.css": "/vendor/rich-text-laravel/trix-rich-text-laravel-d74ec4d1.css",
"/trix-rich-text-laravel-attachments.css": "/vendor/rich-text-laravel/trix-rich-text-laravel-attachments-5258f25c.css",
"/trix-rich-text-laravel-daisyui.css": "/vendor/rich-text-laravel/trix-rich-text-laravel-daisyui-33127e56.css",
"/trix-rich-text-laravel-flux.css": "/vendor/rich-text-laravel/trix-rich-text-laravel-flux-cad7da99.css",
"/lexxy.esm.js": "/vendor/rich-text-laravel/lexxy.esm.js",
"/trix.esm.js": "/vendor/rich-text-laravel/trix.esm.js"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.trix-content .attachment-gallery>rich-text-attachment,.trix-content .attachment-gallery>.attachment{flex:1 0 33%;padding:0 .5em;max-width:33%}.trix-content .attachment-gallery.attachment-gallery--2>rich-text-attachment,.trix-content .attachment-gallery.attachment-gallery--2>.attachment,.trix-content .attachment-gallery.attachment-gallery--4>rich-text-attachment,.trix-content .attachment-gallery.attachment-gallery--4>.attachment{flex-basis:50%;max-width:50%}.trix-content rich-text-attachment .attachment{padding:0!important;max-width:100%!important}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
import * as Lexxy from "@37signals/lexxy"
// Rich Text Laravel uses a custom attachment tag name, so we must configure it here...
Lexxy.configure({ global: { attachmentTagName: "rich-text-attachment" } })
export default Lexxy;
+5
View File
@@ -0,0 +1,5 @@
import Trix from 'trix';
window.Trix = Trix;
export default Trix;
+16 -12
View File
@@ -464,19 +464,23 @@
closeModal() closeModal()
} }
}) })
document.addEventListener('DOMContentLoaded', function () { function autoDismissFlashMessage() {
const errorBox = document.getElementById('error-box') const errorBox = document.getElementById('error-box')
if (errorBox) { if (!errorBox) return
// Fade out the error box after 3 seconds
setTimeout(() => { setTimeout(() => {
errorBox.classList.add('opacity-0') // Add fade-out class errorBox.classList.add('opacity-0')
// Remove the error box after the fade-out transition ends errorBox.addEventListener('transitionend', () => {
errorBox.addEventListener('transitionend', () => { errorBox.remove()
errorBox.remove() })
}) }, 5000)
}, 3000) // Duration before starting fade out }
}
}) if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', autoDismissFlashMessage)
} else {
autoDismissFlashMessage()
}
</script> </script>
@yield('script') @yield('script')
+364 -333
View File
@@ -38,7 +38,7 @@ if (!empty($user->tasks)) {
}); });
} }
//dd(count($break = $user->attendanceBreaks)); //dd(count($break = $user->attendanceBreaks));
if (count($user->attendanceBreaks) > 0){ if (count($user->attendanceBreaks) > 0) {
$break = $user->attendanceBreaks[0]; $break = $user->attendanceBreaks[0];
if ($break->break_end == null) { if ($break->break_end == null) {
$now = Carbon::now()->format('Y-m-d H:i:s'); $now = Carbon::now()->format('Y-m-d H:i:s');
@@ -68,7 +68,7 @@ if (count($user->attendanceBreaks) > 0){
{{-- </button>--}} {{-- </button>--}}
@if (!isset($_COOKIE['token-0'])) @if (!isset($_COOKIE['token-0']))
<form id="attendanceForm" class="form items-center" action="{{ url('attendance/enter') }}" method="post" <form id="attendanceForm" class="form items-center" {{--action="{{ url('attendance/enter') }}" method="post"--}}
style="text-align: left"> style="text-align: left">
@csrf @csrf
<p class="title text-center font-bold text-lg mb-2">سامانه گذارش کار</p> <p class="title text-center font-bold text-lg mb-2">سامانه گذارش کار</p>
@@ -89,6 +89,12 @@ if (count($user->attendanceBreaks) > 0){
style="width: 100%"> style="width: 100%">
ورود/خروج ورود/خروج
</button> </button>
sghl<button id="submitBtn" disabled type="button"
class="bg-gray-400 text-white rounded cursor-not-allowed rounded-xl py-2 px-4 max-w-60 self-center"
style="width: 100%">
ورود/خروج
</button>
<div id="output" class="text-red-500 text-sm mt-2 text-center"></div>
<div class="flex justify-flex max-w-[240px]" style="align-items: center;"> <div class="flex justify-flex max-w-[240px]" style="align-items: center;">
<button type="button" <button type="button"
@@ -111,13 +117,13 @@ if (count($user->attendanceBreaks) > 0){
</button> </button>
{{--<p>{{Carbon::now()}}</p>--}} {{--<p>{{Carbon::now()}}</p>--}}
</form> </form>
{{-- @if(session('open_task_modal'))--}} {{-- @if(session('open_task_modal'))--}}
{{-- <script>--}} {{-- <script>--}}
{{-- window.onload = () => {--}} {{-- window.onload = () => {--}}
{{-- openTaskModal();--}} {{-- openTaskModal();--}}
{{-- };--}} {{-- };--}}
{{-- </script>--}} {{-- </script>--}}
{{-- @endif--}} {{-- @endif--}}
@else @else
@@ -253,19 +259,44 @@ if (count($user->attendanceBreaks) > 0){
attestation: "direct" attestation: "direct"
}; };
submitBtn.addEventListener('click', async () => { submitBtn.addEventListener('click', async () => {
// ابتدا اعتبارسنجی اثر انگشت توسط WebAuthn // ابتدا اعتبارسنجی اثر انگشت توسط WebAuthn
if (!window.PublicKeyCredential) { if (!window.PublicKeyCredential) {
alert('مرورگر شما از احراز هویت بیومتریک پشتیبانی نمی‌کند.'); alert('مرورگر شما از احراز هویت بیومتریک پشتیبانی نمی‌کند.');
return; return;
} }
// یک چالش تصادفی بسازید
const challenge = new Uint8Array(32);
window.crypto.getRandomValues(challenge);
// گزینه‌های احراز هویت (Authentication)
const getOptions = {
publicKey: {
challenge: challenge,
timeout: 60000,
rpId: window.location.hostname, // اختیاری؛ برای لوکال‌هاست خالی بگذارید
userVerification: "required",
allowCredentials: [] // خالی بگذارید تا هر کلید موجود نمایش داده شود
}
};
try { try {
const credential = await navigator.credentials.create({ // به جای create از get استفاده کنید
publicKey: createOptions const assertion = await navigator.credentials.get(getOptions);
}); console.log('احراز هویت موفق:', assertion);
// اینجا فرم را سابمیت کنید یا هر عملیات دیگر
attendanceForm.submit(); attendanceForm.submit();
} catch (error) { } catch (error) {
output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.'; const message = error.name === 'NotAllowedError'
? 'احراز هویت لغو شد یا دسترسی مجاز نبود.'
: 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
const output = document.getElementById('output');
if (output) {
output.textContent = message;
} else {
alert(message);
}
console.error(error);
} }
}); });
</script> </script>
@@ -415,7 +446,6 @@ if (count($user->attendanceBreaks) > 0){
}); });
</script> </script>
@endif @endif
</div> </div>
@@ -447,7 +477,7 @@ if (count($user->attendanceBreaks) > 0){
class=" example1 form-control border p-2 rounded date-picker-input" value="" class=" example1 form-control border p-2 rounded date-picker-input" value=""
placeholder="تاریخ" placeholder="تاریخ"
readonly readonly
> >
</div> </div>
@@ -885,329 +915,330 @@ if (count($user->attendanceBreaks) > 0){
method: "POST", method: "POST",
headers: { headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}', 'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json' 'Accept': 'application/json'
}
})
.then(res => {
if (res.ok) {
window.location.reload();
} else {
window.location.reload();
// breakBtn.textContent = 'خطا';
// breakBtn.disabled = false;
// console.error('Server error on break action:', res.statusText);
}
})
.catch(err => {
window.location.reload();
// breakBtn.textContent = 'خطای شبکه';
// breakBtn.disabled = false;
// console.error('Network error:', err);
});
});
}
{{--let breakStarted = false;--}}
{{--let breakStartTime = null;--}}
{{--let breakTimer = null;--}}
{{--const breakBtn = document.getElementById('breakAttendance');--}}
{{--function formatLocalDatetime(date) {--}}
{{-- const year = date.getFullYear();--}}
{{-- const month = String(date.getMonth() + 1).padStart(2, '0');--}}
{{-- const day = String(date.getDate()).padStart(2, '0');--}}
{{-- const hours = String(date.getHours()).padStart(2, '0');--}}
{{-- const minutes = String(date.getMinutes()).padStart(2, '0');--}}
{{-- const seconds = String(date.getSeconds()).padStart(2, '0');--}}
{{-- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;--}}
{{--}--}}
{{--function formatDuration(totalSeconds) {--}}
{{-- const hours = Math.floor(totalSeconds / 3600);--}}
{{-- const minutes = Math.floor((totalSeconds % 3600) / 60);--}}
{{-- const h = String(hours);--}}
{{-- const i = String(minutes).padStart(2, '0');--}}
{{-- return `${h}:${i}`;--}}
{{--}--}}
{{--breakBtn.addEventListener('click', () => {--}}
{{-- if (!breakStarted) {--}}
{{-- // شروع استراحت 🚀--}}
{{-- breakStarted = true;--}}
{{-- breakStartTime = new Date();--}}
{{-- // تغییر ظاهر دکمه و شروع تایمر (همچنان M:S نمایش داده می شود)--}}
{{-- breakBtn.classList.remove('bg-gray-500', 'hover:bg-gray-600');--}}
{{-- breakBtn.classList.add('bg-red-500', 'hover:bg-red-600');--}}
{{-- breakBtn.textContent = '00:00';--}}
{{-- let seconds = 0;--}}
{{-- breakTimer = setInterval(() => {--}}
{{-- seconds++;--}}
{{-- // اینجا همچنان به صورت دقیقه:ثانیه نمایش داده می شود--}}
{{-- const m = String(Math.floor(seconds / 60)).padStart(2, '0');--}}
{{-- const s = String(seconds % 60).padStart(2, '0');--}}
{{-- breakBtn.textContent = ` ${m}:${s}`;--}}
{{-- }, 1000);--}}
{{-- //send start time--}}
{{-- let formData = new FormData();--}}
{{-- formData.append('start_time', formatLocalDatetime(breakStartTime));--}}
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
{{-- fetch("{{ url('attendance/break') }}", {--}}
{{-- method: "POST",--}}
{{-- body: formData--}}
{{-- })--}}
{{-- .then(res => res.json())--}}
{{-- .then(data => console.log('✅ Break Started Logged:', data))--}}
{{-- .catch(err => console.error('💥 Error starting break:', err));--}}
{{-- } else {--}}
{{-- // پایان استراحت 🛑--}}
{{-- breakStarted = false;--}}
{{-- const breakEndTime = new Date();--}}
{{-- clearInterval(breakTimer);--}}
{{-- // 1. محاسبه مدت زمان استراحت (بر حسب ثانیه)--}}
{{-- const durationSec = Math.floor((breakEndTime - breakStartTime) / 1000);--}}
{{-- // 2. تبدیل زمان های شروع و پایان به فرمت رشته ای--}}
{{-- const breakStartTime_str = formatLocalDatetime(breakStartTime);--}}
{{-- const breakEndTime_str = formatLocalDatetime(breakEndTime);--}}
{{-- // 3. فرمت کردن مدت زمان به H:i (فقط برای نمایش در کنسول/هشدار)--}}
{{-- const duration_formatted_h_i = formatDuration(durationSec);--}}
{{-- // 4. نمایش دکمه به حالت اولیه--}}
{{-- breakBtn.classList.remove('bg-red-500', 'hover:bg-red-600');--}}
{{-- // breakBtn.classList.add('bg-gray-500', 'hover:bg-gray-600');--}}
{{-- breakBtn.textContent = 'استراحت';--}}
{{-- // 5. ارسال داده‌ها به سرور--}}
{{-- let formData = new FormData();--}}
{{-- formData.append('start_time', breakStartTime_str);--}}
{{-- formData.append('end_time', breakEndTime_str);--}}
{{-- formData.append('duration_seconds', durationSec);--}}
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
{{-- fetch("{{ url('attendance/break') }}", {--}}
{{-- method: "POST",--}}
{{-- body: formData--}}
{{-- })--}}
{{-- .then(res => res.json())--}}
{{-- .then(data => console.log('✅ Break saved:', data))--}}
{{-- .catch(err => console.error('💥 Error:', err));--}}
{{-- }--}}
{{--});--}}
{{-- ATTENDANCE MODAL--}}
function clearTime(fieldId) {
document.getElementById(fieldId).value = '';
}
function closeModal() {
document.getElementById('modal-container').classList.add('hidden');
}
document.getElementById('modal-container').addEventListener('click', (e) => {
if (e.target === document.getElementById('modal-container')) {
closeModal();
} }
}); })
.then(res => {
function confirmSubmit() { if (res.status == 'error') {
return confirm("آیا تغییرات اعمال شوند؟"); console.log(res);
} window.location.reload();
} else {
function openModal(title, id, enteredAt, exitedAt) { console.log(res);
const isNewEntry = id === 'new'; breakBtn.textContent = 'خطا';
const modalTitleElement = document.getElementById('modal-title'); breakBtn.disabled = false;
console.error('Server error on break action:', res.statusText);
// ✅ بهترین روش: انتخاب مستقیم فیلد مخفی با ID آن window.location.reload();
const hiddenIdInput = document.getElementById('edit_id');
// const hiddenIdInput = document.getElementById('delete_id');
// انتخاب فیلد ID برای فرم حذف
const deleteIdInput = document.getElementById('delete_id');
const submitButton = document.querySelector('#modal-content button[type="submit"]');
const hiddenDateInput = document.querySelector('input[name="date"]');
// مقداردهی به فرم حذف
if (deleteIdInput) {
deleteIdInput.value = id;
}
const newDateLabel = document.getElementById('new-date-label');
const newDateGroup = document.getElementById('new-date-group');
const displayDateInput = document.getElementById('display_date');
const panelLink = document.getElementById('panel-access-link');
panelLink.href = `/go_down/${id}`;
document.getElementById('modal-container').classList.remove('hidden');
const startTimeInput = document.querySelector('input[name="starttime"]');
const endTimeInput = document.querySelector('input[name="endtime"]');
// تنظیم ساعت‌ها
if (enteredAt && !isNaN(new Date(enteredAt).getTime())) {
startTimeInput.value = new Date(enteredAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
} else {
startTimeInput.value = '09:00';
}
if (exitedAt && !isNaN(new Date(exitedAt).getTime())) {
endTimeInput.value = new Date(exitedAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
}
// else {
// endTimeInput.value = '17:30';
// }
if (isNewEntry) {
// حالت ثبت جدید
modalTitleElement.innerText = 'ثبت روز کاری جدید';
newDateLabel.style.display = 'block';
newDateGroup.style.display = 'block';
displayDateInput.value = '';
hiddenDateInput.value = '';
// ✅ اصلاح: مقداردهی 'new' به فیلد ID
if (hiddenIdInput) hiddenIdInput.value = 'new';
submitButton.onclick = function () {
if (hiddenDateInput.value === '') {
alert("لطفاً یک تاریخ را از تقویم انتخاب کنید.");
return false;
}
return confirmSubmit();
};
} else {
// حالت ویرایش
modalTitleElement.innerText = 'تغییر ساعت کاری';
newDateLabel.style.display = 'none';
newDateGroup.style.display = 'none';
// ✅ اصلاح: استفاده از متغیر 'id' که از جدول فرستاده شده است
if (hiddenIdInput) hiddenIdInput.value = id;
if (hiddenDateInput) hiddenDateInput.value = enteredAt;
submitButton.onclick = function () {
return confirmSubmit();
};
}
//date and clock picker
$('.clockpicker').clockpicker({
autoclose: false,
placement: 'center',
donetext: 'ثبت',
vibrate: true,
});
// --- فعال‌سازی تقویم ---
$(".example1").persianDatepicker({
format: "YYYY/MM/DD",
altField: '#date',
altFormat: 'YYYY-MM-DD',
calendar: {
persian: {
locale: 'fa',
leapYearMode: "astronomical"
}
},
observer: true,
initialValue: false,
initialValueType: 'gregorian',
autoClose: true,
toolbox: {
calendarSwitch: {enabled: false},
todayButton: {enabled: true, text: {fa: "امروز"}}
},
});
}
$(document).ready(function () {
// 1. فعال‌سازی Clockpicker
$('.clockpicker').clockpicker({
autoclose: false,
placement: 'center',
donetext: 'ثبت',
vibrate: true,
// donetext: true,
});
$(".example1").persianDatepicker({
format: "YYYY/MM/DD",
altField: '#date',
altFormat: 'YYYY-MM-DD',
calendar: {
persian: {
locale: 'fa',
leapYearMode: "astronomical"
}
},
observer: true,
initialValue: false,
initialValueType: 'gregorian',
autoClose: true,
toolbox: {
calendarSwitch: {enabled: false},
todayButton: {enabled: true, text: {fa: "امروز"}}
},
onSelect: function (unix) {
// تبدیل تاریخ انتخابی به میلادی درست با timezone ایران
const d = new persianDate(unix).toCalendar('gregorian').toLocale('en');
console.log("Gregorian (Fixed):", d.format("YYYY-MM-DD"));
} }
})
.catch(err => {
console.log(err);
window.location.reload();
// breakBtn.textContent = 'خطای شبکه';
// breakBtn.disabled = false;
// console.error('Network error:', err);
}); });
});
// 2. فعال‌سازی Persian Datepicker }
// $('.example1').pDatepicker({
// format: 'YYYY/MM/DD',
// // placement: 'center',
// altField: '#date', // این فیلد مخفی، تاریخ میلادی تبدیل شده را دریافت می‌کند
// altFormat: 'YYYY-MM-DD', // میلادی ذخیره بشه
// // changeMonth: true,
// // changeYear: true,
// // showOtherMonths: true,
// // selectOtherMonths: true,
// autoClose: true,
//
// });
{{--let breakStarted = false;--}}
{{--let breakStartTime = null;--}}
{{--let breakTimer = null;--}}
{{--const breakBtn = document.getElementById('breakAttendance');--}}
{{--function formatLocalDatetime(date) {--}}
{{-- const year = date.getFullYear();--}}
{{-- const month = String(date.getMonth() + 1).padStart(2, '0');--}}
{{-- const day = String(date.getDate()).padStart(2, '0');--}}
{{-- const hours = String(date.getHours()).padStart(2, '0');--}}
{{-- const minutes = String(date.getMinutes()).padStart(2, '0');--}}
{{-- const seconds = String(date.getSeconds()).padStart(2, '0');--}}
{{-- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;--}}
{{--}--}}
{{--function formatDuration(totalSeconds) {--}}
{{-- const hours = Math.floor(totalSeconds / 3600);--}}
{{-- const minutes = Math.floor((totalSeconds % 3600) / 60);--}}
{{-- const h = String(hours);--}}
{{-- const i = String(minutes).padStart(2, '0');--}}
{{-- return `${h}:${i}`;--}}
{{--}--}}
{{--breakBtn.addEventListener('click', () => {--}}
{{-- if (!breakStarted) {--}}
{{-- // شروع استراحت 🚀--}}
{{-- breakStarted = true;--}}
{{-- breakStartTime = new Date();--}}
{{-- // تغییر ظاهر دکمه و شروع تایمر (همچنان M:S نمایش داده می شود)--}}
{{-- breakBtn.classList.remove('bg-gray-500', 'hover:bg-gray-600');--}}
{{-- breakBtn.classList.add('bg-red-500', 'hover:bg-red-600');--}}
{{-- breakBtn.textContent = '00:00';--}}
{{-- let seconds = 0;--}}
{{-- breakTimer = setInterval(() => {--}}
{{-- seconds++;--}}
{{-- // اینجا همچنان به صورت دقیقه:ثانیه نمایش داده می شود--}}
{{-- const m = String(Math.floor(seconds / 60)).padStart(2, '0');--}}
{{-- const s = String(seconds % 60).padStart(2, '0');--}}
{{-- breakBtn.textContent = ` ${m}:${s}`;--}}
{{-- }, 1000);--}}
{{-- //send start time--}}
{{-- let formData = new FormData();--}}
{{-- formData.append('start_time', formatLocalDatetime(breakStartTime));--}}
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
{{-- fetch("{{ url('attendance/break') }}", {--}}
{{-- method: "POST",--}}
{{-- body: formData--}}
{{-- })--}}
{{-- .then(res => res.json())--}}
{{-- .then(data => console.log('✅ Break Started Logged:', data))--}}
{{-- .catch(err => console.error('💥 Error starting break:', err));--}}
{{-- } else {--}}
{{-- // پایان استراحت 🛑--}}
{{-- breakStarted = false;--}}
{{-- const breakEndTime = new Date();--}}
{{-- clearInterval(breakTimer);--}}
{{-- // 1. محاسبه مدت زمان استراحت (بر حسب ثانیه)--}}
{{-- const durationSec = Math.floor((breakEndTime - breakStartTime) / 1000);--}}
{{-- // 2. تبدیل زمان های شروع و پایان به فرمت رشته ای--}}
{{-- const breakStartTime_str = formatLocalDatetime(breakStartTime);--}}
{{-- const breakEndTime_str = formatLocalDatetime(breakEndTime);--}}
{{-- // 3. فرمت کردن مدت زمان به H:i (فقط برای نمایش در کنسول/هشدار)--}}
{{-- const duration_formatted_h_i = formatDuration(durationSec);--}}
{{-- // 4. نمایش دکمه به حالت اولیه--}}
{{-- breakBtn.classList.remove('bg-red-500', 'hover:bg-red-600');--}}
{{-- // breakBtn.classList.add('bg-gray-500', 'hover:bg-gray-600');--}}
{{-- breakBtn.textContent = 'استراحت';--}}
{{-- // 5. ارسال داده‌ها به سرور--}}
{{-- let formData = new FormData();--}}
{{-- formData.append('start_time', breakStartTime_str);--}}
{{-- formData.append('end_time', breakEndTime_str);--}}
{{-- formData.append('duration_seconds', durationSec);--}}
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
{{-- fetch("{{ url('attendance/break') }}", {--}}
{{-- method: "POST",--}}
{{-- body: formData--}}
{{-- })--}}
{{-- .then(res => res.json())--}}
{{-- .then(data => console.log('✅ Break saved:', data))--}}
{{-- .catch(err => console.error('💥 Error:', err));--}}
{{-- }--}}
{{--});--}}
{{-- ATTENDANCE MODAL--}}
function clearTime(fieldId) {
document.getElementById(fieldId).value = '';
}
function closeModal() {
document.getElementById('modal-container').classList.add('hidden');
}
document.getElementById('modal-container').addEventListener('click', (e) => {
if (e.target === document.getElementById('modal-container')) {
closeModal();
}
});
function confirmSubmit() {
return confirm("آیا تغییرات اعمال شوند؟");
}
function openModal(title, id, enteredAt, exitedAt) {
const isNewEntry = id === 'new';
const modalTitleElement = document.getElementById('modal-title');
const hiddenIdInput = document.getElementById('edit_id');
// const hiddenIdInput = document.getElementById('delete_id');
const deleteIdInput = document.getElementById('delete_id');
const submitButton = document.querySelector('#modal-content button[type="submit"]');
const hiddenDateInput = document.querySelector('input[name="date"]');
// مقداردهی به فرم حذف
if (deleteIdInput) {
deleteIdInput.value = id;
}
const newDateLabel = document.getElementById('new-date-label');
const newDateGroup = document.getElementById('new-date-group');
const displayDateInput = document.getElementById('display_date');
const panelLink = document.getElementById('panel-access-link');
panelLink.href = `/go_down/${id}`;
document.getElementById('modal-container').classList.remove('hidden');
const startTimeInput = document.querySelector('input[name="starttime"]');
const endTimeInput = document.querySelector('input[name="endtime"]');
// تنظیم ساعت‌ها
if (enteredAt && !isNaN(new Date(enteredAt).getTime())) {
startTimeInput.value = new Date(enteredAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
}); });
} else {
startTimeInput.value = '09:00';
}
if (exitedAt && !isNaN(new Date(exitedAt).getTime())) {
endTimeInput.value = new Date(exitedAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
}
// else {
// endTimeInput.value = '17:30';
// }
if (isNewEntry) {
// حالت ثبت جدید
modalTitleElement.innerText = 'ثبت روز کاری جدید';
newDateLabel.style.display = 'block';
newDateGroup.style.display = 'block';
displayDateInput.value = '';
hiddenDateInput.value = '';
// ✅ اصلاح: مقداردهی 'new' به فیلد ID
if (hiddenIdInput) hiddenIdInput.value = 'new';
submitButton.onclick = function () {
if (hiddenDateInput.value === '') {
alert("لطفاً یک تاریخ را از تقویم انتخاب کنید.");
return false;
}
return confirmSubmit();
};
} else {
// حالت ویرایش
modalTitleElement.innerText = 'تغییر ساعت کاری';
newDateLabel.style.display = 'none';
newDateGroup.style.display = 'none';
// ✅ اصلاح: استفاده از متغیر 'id' که از جدول فرستاده شده است
if (hiddenIdInput) hiddenIdInput.value = id;
if (hiddenDateInput) hiddenDateInput.value = enteredAt;
submitButton.onclick = function () {
return confirmSubmit();
};
}
//date and clock picker
$('.clockpicker').clockpicker({
autoclose: false,
placement: 'center',
donetext: 'ثبت',
vibrate: true,
});
// --- فعال‌سازی تقویم ---
$(".example1").persianDatepicker({
format: "YYYY/MM/DD",
altField: '#date',
altFormat: 'YYYY-MM-DD',
calendar: {
persian: {
locale: 'fa',
leapYearMode: "astronomical"
}
},
observer: true,
initialValue: false,
initialValueType: 'gregorian',
autoClose: true,
toolbox: {
calendarSwitch: {enabled: false},
todayButton: {enabled: true, text: {fa: "امروز"}}
},
});
}
$(document).ready(function () {
// 1. فعال‌سازی Clockpicker
$('.clockpicker').clockpicker({
autoclose: false,
placement: 'center',
donetext: 'ثبت',
vibrate: true,
// donetext: true,
});
$(".example1").persianDatepicker({
format: "YYYY/MM/DD",
altField: '#date',
altFormat: 'YYYY-MM-DD',
calendar: {
persian: {
locale: 'fa',
leapYearMode: "astronomical"
}
},
observer: true,
initialValue: false,
initialValueType: 'gregorian',
autoClose: true,
toolbox: {
calendarSwitch: {enabled: false},
todayButton: {enabled: true, text: {fa: "امروز"}}
},
onSelect: function (unix) {
// تبدیل تاریخ انتخابی به میلادی درست با timezone ایران
const d = new persianDate(unix).toCalendar('gregorian').toLocale('en');
console.log("Gregorian (Fixed):", d.format("YYYY-MM-DD"));
}
});
// 2. فعال‌سازی Persian Datepicker
// $('.example1').pDatepicker({
// format: 'YYYY/MM/DD',
// // placement: 'center',
// altField: '#date', // این فیلد مخفی، تاریخ میلادی تبدیل شده را دریافت می‌کند
// altFormat: 'YYYY-MM-DD', // میلادی ذخیره بشه
// // changeMonth: true,
// // changeYear: true,
// // showOtherMonths: true,
// // selectOtherMonths: true,
// autoClose: true,
//
// });
});
</script> </script>
+219 -63
View File
@@ -1,91 +1,247 @@
<?php
use App\Models\App;
use App\Models\User;
use App\Models\Tasks;
$users = User::get();
$apps = App::get();
if (isset($task)) {
$selectedAssignees = [];
if ($task && $task->assignees) {
$decoded = json_decode($task->assignees, true);
$selectedAssignees = is_array($decoded) ? $decoded : [];
}
}
?>
@extends('app') @extends('app')
@section('content') @section('content')
<div class="w-full mt-[90px] justify-center container flex "> <div class="w-full mt-[90px] justify-center container flex ">
<div class="bg-white w-[350px] p-6 rounded-xl "> <div class="bg-white w-[350px] rounded-xl ">
<h1 class="w-full bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">ایجاد وظیفه جدید</h1> <h1 class="w-[350px] bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}
<form action="{{Route('create_api')}}" method="POST" class="grid grid-cols-2 gap-4 items-center"> </h1>
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class=" p-6 grid-cols-2 gap-4 items-center">
@csrf @csrf
<!-- نام Issue (الزامی) --> @if(isset($task))
<label for="name" class="font-medium text-sm">نام:</label> <input type="hidden" id="id" name="id" value="{{$task->id}}">
<input class="rounded-lg border border-gray-300 p-2 w-full" type="text" id="name" name="name" required> @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>
<!-- توضیحات HTML --> <label for="description" class="font-medium text-sm">توضیحات:</label>
<label for="description_html" class="font-medium text-sm">توضیحات:</label> <textarea id="description" name="description"
<textarea id="description_html" name="description_html" class="rounded-lg border border-gray-300 p-2 w-full">{{$task->description ?? null}}</textarea>
class="rounded-lg border border-gray-300 p-2 w-full"></textarea>
<!-- اولویت -->
<label for="priority" class="font-medium text-sm">اولویت:</label> <label for="priority" class="font-medium text-sm">اولویت:</label>
<select id="priority" name="priority" <select id="priority" name="priority" class="rounded-lg border border-gray-300 p-2 w-full">
class="rounded-lg border border-gray-300 p-2 w-full"> <option value="none" {{ old('priority', $task->priority ?? '') == 'none' ? 'selected' : '' }}>ندارد</option>
<option value="none">ندارد</option> <option value="low" {{ old('priority', $task->priority ?? '') == 'low' ? 'selected' : '' }}>کم</option>
<option value="urgent">فوری</option> <option value="medium" {{ old('priority', $task->priority ?? '') == 'medium' ? 'selected' : '' }}>عادی</option>
<option value="high">بالا</option> <option value="urgent" {{ old('priority', $task->priority ?? '') == 'urgent' ? 'selected' : '' }}>فوری</option>
<option value="medium">عادی</option> <option value="high" {{ old('priority', $task->priority ?? '') == 'high' ? 'selected' : '' }}>زیاد</option>
<option value="low">پایین</option>
</select> </select>
<!-- تاریخ شروع --> <!-- تاریخ شروع -->
<label for="start_date" class="font-medium text-sm">تاریخ شروع:</label> <div class="p-2">
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="start_date" <label for="start_date" class="font-medium text-sm">تاریخ شروع <span
name="start_date"> 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}}"
placeholder="تاریخ شروع" autocomplete="off" readonly required>
<input type="hidden" name="start_date" id="start_date_gregorian">
<!-- تاریخ هدف --> <!-- تاریخ هدف -->
<label for="target_date" class="font-medium text-sm">تاریخ هدف:</label> <label for="target_date" class="font-medium text-sm">تاریخ هدف</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="target_date" {{-- <div class="relative">--}}
name="target_date"> <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>--}}
<input type="hidden" name="target_date" id="target_date_gregorian">
</div>
<!-- وضعیت --> <!-- وضعیت -->
<label for="state" class="font-medium text-sm">وضعیت:</label> <label for="state" class="font-medium text-sm"><span class="text-red-500">*</span>وضعیت:</label>
<select id="state" name="state" <select id="state" name="state" class="rounded-lg border border-gray-300 p-2 w-full" required>
class="rounded-lg border border-gray-300 p-2 w-full"> <option value="1" {{ old('state', $task->state ?? '') == 1 ? 'selected' : '' }}>در انتظار</option>
<option value="c73c2044-ea14-42cb-b08d-cecf3d1fdfb5">درحال انجام</option> <option value="2" {{ old('state', $task->state ?? '') == 2 ? 'selected' : '' }}>در صف انجام</option>
<option value="a9ce312f-cfca-491e-8e3d-1975aa6ebc10">در انتظار</option> <option value="3" {{ old('state', $task->state ?? '') == 3 ? 'selected' : '' }}>درحال انجام</option>
<option value="dea1fb57-a281-4fe7-8f3e-ee5567637e35">دارای خطا</option> <option value="4" {{ old('state', $task->state ?? '') == 4 ? 'selected' : '' }}>انجام‌شده</option>
<option value="770fbe42-99f6-41db-9b9e-773d9cced1a5">در صف انجام</option>
<option value="b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0">انجام‌شده</option>
<option value="d61d14a3-72f7-4f44-894b-efe8b2a75be1">لغوشده</option>
</select> </select>
<!-- کاربران مسئول --> <!-- کاربران مسئول -->
{{-- <label for="assignees" class="font-medium text-sm">انتصاب کاربران:</label>--}} <label for="assignees" class="font-medium text-sm">انتصاب
<label class="font-medium text-sm">انتصاب کاربران:</label> کاربران:</label>
<div class="rounded-lg border border-gray-300 p-2 w-full max-h-32 overflow-y-auto space-y-2"> <div class="relative" id="dropdown-container">
@foreach($users as $user) {{-- دکمه --}}
<label class="flex items-center space-x-2"> <button type="button" id="dropdown-btn"
<input type="checkbox" name="assignees[]" value="{{ $user->plain_id }}" class="rounded text-cyan-600 focus:ring-cyan-500"> class="w-full bg-white border border-gray-300 rounded-lg px-4 py-2 text-right flex justify-between items-center">
<span>{{ $user->name }}</span> <span id="selected-count">انتخاب کاربران</span>
</label> <svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@endforeach <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 mt-1 w-full bg-white border border-gray-300 rounded-lg shadow-lg">
<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' : '' }}>
<span>{{ $user->name }}</span>
</label>
@endforeach
</div>
</div>
</div> </div>
<!-- برچسب‌ها --> {{-- <!-- برچسب‌ها -->--}}
<label for="labels" class="font-medium text-sm">برچسب‌ها:</label> {{-- <label for="labels" class="font-medium text-sm">برچسب‌ها:</label>--}}
<input class="rounded-lg border border-gray-300 p-2 w-full" {{-- <input class="rounded-lg border border-gray-300 p-2 w-full"--}}
type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2"> {{-- type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2">--}}
<!-- Issue والد --> <!-- اپ -->
<label for="parent" class="font-medium text-sm">پروژه والد:</label> <label for="app" class="font-medium text-sm">نام اپ:</label>
<select id="parent" name="parent" class="rounded-lg border border-gray-300 p-2 w-full"> <select id="app" name="app" class="rounded-lg z-20 border border-gray-300 p-2 w-full">
@foreach($tasks as $task) <option value="">-- انتخاب اپ --</option>
<option value="{{$task->task_id}}">{{$task->name}}</option> @if(!empty($apps) && count($apps) > 0)
@endforeach @foreach($apps as $app)
<option value="{{ $app->id }}" {{ old('app', $task->app ?? '') == $app->id ? 'selected' : '' }}>
{{ $app->name_fa }}
</option>
@endforeach
@endif
</select> </select>
<!-- امتیاز تخمین --> <div class="inline-block flex pt-3 justify-center">
<label for="estimate_point" class="font-medium text-sm">Estimate Point (0-7):</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="number" id="estimate_point"
name="estimate_point" min="0" max="7">
<!-- دکمه -->
<div></div>
<button type="submit" <button type="submit"
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition"> 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>
</button> </div>
</form> </form>
</div> </div>
</div> </div>
<script>
const dropdownBtn = document.getElementById('dropdown-btn');
const dropdownList = document.getElementById('dropdown-list');
const searchInput = document.getElementById('search-input');
const usersList = document.getElementById('users-list');
const selectedCountSpan = document.getElementById('selected-count');
// باز و بسته کردن
dropdownBtn.addEventListener('click', (e) => {
e.stopPropagation();
dropdownList.classList.toggle('hidden');
if (!dropdownList.classList.contains('hidden')) {
searchInput.focus();
}
});
// بستن با کلیک بیرون
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';
}
});
});
// آپدیت تعداد
function updateSelectedCount() {
const selected = document.querySelectorAll('.user-checkbox:checked');
const count = selected.length;
selectedCountSpan.textContent = count === 0 ? 'انتخاب کاربران' : count + ' کاربر انتخاب شده';
}
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', updateSelectedCount);
});
updateSelectedCount();
$(document).ready(function () {
// تاریخ شروع با ساعت جاری
$('#start_date').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');
$('#start_date_gregorian').val(dateStr);
console.log('Start Gregorian:', dateStr);
}
});
// تاریخ هدف
$('#target_date').persianDatepicker({
format: 'YYYY/MM/DD',
autoClose: true,
toolbox: {
calendarSwitch: {enabled: false},
todayButton: {enabled: true, text: {fa: 'امروز'}}
},
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('');
});
});
</script>
@endsection @endsection
File diff suppressed because it is too large Load Diff
View File
@@ -0,0 +1,3 @@
<figure class="attachment attachment--content">
{!! trim($content->renderTrixContentAttachment($options)) !!}
</figure>
@@ -0,0 +1,8 @@
<div class="not-prose attachment-gallery attachment-gallery--{{ count($attachables) }}">
@foreach ($attachables as $attachable)
@include('rich-text-laravel::attachables._remote_image', [
'remoteImage' => $attachable,
'gallery' => true
])
@endforeach
</div>
@@ -0,0 +1 @@
<span>&#8864;</span>
@@ -0,0 +1,11 @@
<figure class="not-prose attachment attachment--file attachment--{{ $remoteFile->extension() }}">
<figcaption class="attachment__caption">
@if ($remoteFile->caption)
{{ $remoteFile->caption }}
@else
<span class="attachment__name">{{ $remoteFile->filename }}</span>
<span class="attachment__size">{{ $remoteFile->filesizeForHumans() }}</span>
<span class="attachment__download"><a class="!underline" href="{{ $remoteFile->url }}">{{ __('Download') }}</a></span>
@endif
</figcaption>
</figure>
@@ -0,0 +1,8 @@
<figure class="not-prose attachment attachment--preview attachment--{{ $remoteImage->extension() }}" @if ($gallery ?? false) data-trix-attributes='{"presentation":"gallery"}' @endif>
<img src="{{ $remoteImage->url }}" width="{{ $remoteImage->width }}" height="{{ $remoteImage->height }}" />
@if ($remoteImage->caption)
<figcaption class="attachment__caption">
{{ $remoteImage->caption }}
</figcaption>
@endif
</figure>
@@ -0,0 +1,5 @@
<div class="attachment-gallery attachment-gallery--{{ $attachmentGallery->count() }}">
@foreach ($attachmentGallery->attachments() as $attachment)
{!! $attachment->richTextRender() !!}
@endforeach
</div>
@@ -0,0 +1,13 @@
@props(['id', 'name' => null, 'value' => ''])
@php
$editor = \Tonysm\RichTextLaravel\RichTextLaravel::editorName();
@endphp
<x-dynamic-component
:component="'rich-text::' . $editor"
:id="$id"
:name="$name"
:value="$value"
{{ $attributes }}
>{{ $slot }}</x-dynamic-component>
@@ -0,0 +1,23 @@
@props(['id', 'name' => null, 'value' => ''])
<div
{{ $attributes->whereDoesntStartWith('wire:') }}
@if ($attributes->has('wire:model'))
x-data="{ content: $wire.entangle('{{ $attributes->wire('model')->value() }}') }"
x-on:lexxy:initialize="$nextTick(() => $refs.input.value = content)"
x-on:lexxy:change="content = $refs.input.value"
@endif
>
<lexxy-editor
id="{{ $id }}"
class="lexxy-content"
value="{{ $value }}"
@if ($name ?? false)
name="{{ $name }}"
@endif
@if ($attributes->has('wire:model'))
wire:ignore
x-ref="input"
@endif
>{{ $slot }}</lexxy-editor>
</div>
@@ -0,0 +1,24 @@
@props(['theme' => 'default'])
@inject('assets', \Tonysm\RichTextLaravel\AssetsManager::class)
@if (config('rich-text-laravel.editor') === 'lexxy')
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy.css') }}" />
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy-rich-text-laravel-attachments.css') }}" />
@if ($theme === 'daisyui')
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy-rich-text-laravel-daisyui.css') }}" />
@elseif ($theme === 'flux')
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy-rich-text-laravel-flux.css') }}" />
@endif
@elseif ($theme === 'richtextlaravel')
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel.css') }}" />
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-attachments.css') }}" />
@else
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix.css') }}" />
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-attachments.css') }}" />
@if ($theme === 'daisyui')
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-daisyui.css') }}" />
@elseif ($theme === 'flux')
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-flux.css') }}" />
@endif
@endif
@@ -0,0 +1,41 @@
@props(['id', 'name' => null, 'value' => ''])
<div
{{ $attributes->whereDoesntStartWith('wire:') }}
@if ($attributes->has('wire:model'))
x-data="{ content: $wire.entangle('{{ $attributes->wire('model')->value() }}') }"
x-on:trix-initialize="$refs.input.value = content"
x-on:trix-change="content = $refs.input.value"
@endif
>
<input
type="hidden"
@if ($name ?? false)
name="{{ $name }}"
@endif
id="{{ $id }}_input"
value="{{ $value }}"
@if ($attributes->has('wire:model'))
x-on:change="$refs.input.value = $event.target.value"
{{ $attributes->whereStartsWith('wire:') }}
@endif
/>
<trix-toolbar
id="{{ $id }}_toolbar"
@if ($attributes->has('wire:model'))
wire:ignore
@endif
></trix-toolbar>
<trix-editor
id="{{ $id }}"
toolbar="{{ $id }}_toolbar"
input="{{ $id }}_input"
class="trix-content"
@if ($attributes->has('wire:model'))
x-ref="input"
wire:ignore
@endif
></trix-editor>
</div>
@@ -0,0 +1,5 @@
<div class="{{ e(\Tonysm\RichTextLaravel\RichTextLaravel::editorName()) . '-content' }}">
@if (trim($content = $content->renderWithAttachments()))
{!! $content !!}
@endif
</div>
+24 -10
View File
@@ -34,6 +34,8 @@ Route::post('/back', [AttendanceController::class, 'back']);
Route::get('/ai', [AttendanceController::class, 'ai']); Route::get('/ai', [AttendanceController::class, 'ai']);
//attendance Routes
Route::get('attendance/login', [AttendanceController::class, 'login']); Route::get('attendance/login', [AttendanceController::class, 'login']);
Route::get('attendance/logMeOut', [AttendanceController::class, 'logMeOut']); Route::get('attendance/logMeOut', [AttendanceController::class, 'logMeOut']);
@@ -52,20 +54,32 @@ Route::get('/attendance/profile', [AttendanceController::class, 'profile'])->nam
Route::post('/attendance/profile', [AttendanceController::class, 'profile']); Route::post('/attendance/profile', [AttendanceController::class, 'profile']);
Route::get('/tasks', [TaskController::class, 'Tasks']);
Route::post('/active_task', [TaskController::class, 'activeTask']);
Route::get('/create_task', [TaskController::class, 'createTask'])->name('create_task');
Route::post('/create_task', [TaskController::class, 'storeTask'])->name('create_api');
Route::any('/pplain', [TaskController::class, 'webhookPlane']);
Route::get('/attendance/profile', [AttendanceController::class, 'profile'])->name('user.profile'); Route::get('/attendance/profile', [AttendanceController::class, 'profile'])->name('user.profile');
Route::post('/attendance/profile', [AttendanceController::class, 'updateProfile'])->name('edit.profile'); Route::post('/attendance/profile', [AttendanceController::class, 'updateProfile'])->name('edit.profile');
//Task routes
Route::get('/tasks', [TaskController::class, 'Tasks']);
Route::post('/active_task', [TaskController::class, 'activeTask'])->name('task.active');
Route::get('/create_task', [TaskController::class, 'createTask'])->name('task.create');
Route::get('/task/{id}', [TaskController::class, 'showTask'])->name('task.show');
Route::get('/task/delete/{id}', [TaskController::class, 'deleteTask'])->name('task.show');
Route::post('/task/edit', [TaskController::class, 'editTask'])->name('task.edit');
Route::post('/create_task', [TaskController::class, 'storeTask'])->name('task.create');
Route::post('/task/{id}/comments', [TaskController::class, 'storeComment'])->name('task.comments.store');
Route::any('/pplain', [TaskController::class, 'webhookPlane']);
Route::post('/fingerprint/login/options', [FingerprintController::class, 'generateLoginOptions'])->name('fingerprint.login.options'); Route::post('/fingerprint/login/options', [FingerprintController::class, 'generateLoginOptions'])->name('fingerprint.login.options');
Route::post('/fingerprint/login/verify', [FingerprintController::class, 'verifyLogin'])->name('fingerprint.login.verify'); Route::post('/fingerprint/login/verify', [FingerprintController::class, 'verifyLogin'])->name('fingerprint.login.verify');