NASER add taskززطزSQLSTATE[42000]: Syntax error or access violation: 1305 FUNCTION montask.ST_SetSRID does not exist (Connection: mysql, SQL: update ttendances set ip_exit = 51.684726144224, distance_exit = 850, exited_at = 2026-04-23 13:59:46, exit_location = ST_SetSRID(ST_Point(51.684726144224, 32.652053279154), 4326), duration = 04:59:46, ttendances.updated_at = 2026-04-23 13:59:46 where id = 937)

This commit is contained in:
mobinesaghi
2026-04-23 14:00:39 +03:30
parent 2419808175
commit 74e0026529
Notes: mobinesaghi 2026-04-23 14:42:21 +03:30
NASER add task functions undepended
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)
{
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;
}
}
// 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;
// }
// }
}
+50 -127
View File
@@ -82,8 +82,7 @@ class AttendanceController extends Controller
? 'admin/'
: 'attendance/';
$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();
$until_now = null;
@@ -321,7 +320,7 @@ class AttendanceController extends Controller
$attendance->duration = $duration;
$attendance->save();
send_tel($user->name, $attendance, 'attendance');
$this->send($user->name, $attendance, 'attendance');
return redirect()->back()->with('message', [
'color' => '#3b82f6',
@@ -340,7 +339,7 @@ class AttendanceController extends Controller
$attendance->office_location = ['lat' => $officeLat, 'lng' => $officeLng];
$attendance->save();
send_tel($user->name, $attendance, 'attendance');
$this->send($user->name, $attendance, 'attendance');
return redirect()->back()->with('message', [
'color' => '#22c55e',
@@ -354,15 +353,15 @@ class AttendanceController extends Controller
if (!$user) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$attendance = Attendance::where('user_identity', $user->user_name)
->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();
$today = Carbon::now()->format('Y-m-d');
$day = Carbon::parse($attendance->entered_at)->format('Y-m-d');
if ($today !== $day || !$attendance) {
if (!$attendance) {
return response()->json([
'status' => 'error',
'color' => '#C30004',
@@ -386,7 +385,7 @@ class AttendanceController extends Controller
'duration' => $durationSeconds
]);
dd(send_tel($user->name, $break, 'break'));
// dd($this->send($user->name, $break, 'break'));
return response()->json([
"break_status" => 'ended',
'duration' => $durationSeconds
@@ -398,7 +397,7 @@ class AttendanceController extends Controller
'break_start' => $now,
]);
dd(send_tel($user->name, $break, 'break'));
// dd($this->send($user->name, $break, 'break'));
$user->update([
'break' => 1
@@ -486,7 +485,7 @@ class AttendanceController extends Controller
$status = 'request';
$d = "ایجاد روز کاری";
send_tel($user->name, $d, $status);
$this->send($user->name, $d, $status);
$user->req_counts += 1;
$user->save();
return redirect('/')->with('message', [
@@ -517,7 +516,7 @@ class AttendanceController extends Controller
$att->save();
$status = 'request';
$d = "تغییر ساعت کاری";
send_tel($user->name, $d, $status);
$this->send($user->name, $d, $status);
$user->req_counts += 1;
$user->save();
return redirect('/')->with('message', [
@@ -564,7 +563,7 @@ class AttendanceController extends Controller
$attendance->save();
$status = 'request';
$d = "حذف روز کاری";
send_tel($user->name, $d, $status);
$this->send($user->name, $d, $status);
$user->req_counts += 1;
$user->save();
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') {
$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;
}
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;
}
//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
namespace App\Http\Controllers;
use App\Models\Log;
use App\Models\Setting;
abstract class Controller
use App\Models\User;
use Illuminate\Support\Facades\Http;
abstract class Controller
{
public static function Config($name)
{
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;
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\Http\Request;
use App\Models\Pivot_Tasks;
@@ -10,262 +15,530 @@ use GuzzleHttp\Client;
use App\Models\Tasks;
use App\Models\User;
use Carbon\Carbon;
use Morilog\Jalali\Jalalian;
use Carbon\CarbonInterval;
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()
{
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
$tasks = $user->tasks;
$users = User::get();
if ($user) {
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 = [
"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",
];
$task->totalDuration = CarbonInterval::seconds($totalSeconds)->cascade()->format('%H:%I:%S');
$active_task = TaskDuration::where('task_id', $task->id)->orderBy('start', 'desc')->first();
$task->active = !empty($active_task) && $active_task->end == null;
}
} else {
$tasks = Tasks::with(['duration', 'comments.user'])->where('created_by', $user->id)
->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));
$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;
$task->totalDuration = CarbonInterval::seconds($totalSeconds)->cascade()->format('%H:%I:%S');
$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;
}
}
}
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();
$p_task->ended_at = null;
$user->active_task = $task->name;
$p_task->save();
$user->save();
return back()->with('message', [
'color' => 'green',
'text' => 'وظیفه شما شروع شد، موفق باشید.'
]);
} else {
// dd('in elseif');
$task = Tasks::with(['comments.user'])->find($id ?? null);
return view('tasks.create', ['task' => $task]);
$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);
$diffSeconds = $h2 * 3600 + $m2 * 60 + $s2;
public function editTask(Request $request)
{
// 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);
$minutes = floor(($totalSeconds % 3600) / 60);
$seconds = $totalSeconds % 60;
$before = $task->toArray();
$duration = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
} else {
$duration = $diff;
}
$p_task->ended_at = $now;
$p_task->duration = $duration;
$user->active_task = null;
$p_task->save();
$user->save();
return back()->with('message', [
'color' => 'green',
'text' => 'وظیفه شما با موفقیت پایان یافت.'
]);
$data['updated_by'] = $actor?->id;
if (array_key_exists('assignees', $data)) {
$data['assignees'] = !empty($data['assignees']) ? json_encode($data['assignees']) : null;
} else {
unset($data['assignees']);
}
unset($data['_token'], $data['id'], $data['start_date_display'], $data['target_date_display']);
$task->update($data);
$after = $task->toArray();
$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', [
'color' => 'red',
'text' => 'وظیفه یافت نشد✖️'
'color' => 'green',
'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()
{
$users = User::whereNotNull('plain_id')->get();
$tasks = Tasks::all();
return view('tasks.create');
}
return view('tasks.create', [
'users' => $users,
'tasks' => $tasks,
public function deleteTask($id)
{
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)
{
$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([
"name" => $request->name,
"desc" => $request->description_html,
"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,
$data = $request->all();
$data['created_by'] = $user->id;
$data['assignees'] = !empty($data['assignees']) ? json_encode($data['assignees']) : null;
]);
$task = Tasks::create($data);
// $headers = [
// 'x-api-key' => 'plane_api_ec378e977ceb4bd1bbd2332855370645',
// 'Content-Type' => 'application/json'
// ];
//
// $body = json_encode([
// "name" => $name,
// "description_html" => $desc ?: '',
// "priority" => $priority ?: 'none',
// "start_date" => $start_date ?: 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);
$message = "تسک جدیدی با عنوان {$data['title']} برای شما ایجاد شد.";
$deadline = !empty($data['target_date']) ? Jalalian::fromCarbon(Carbon::parse($data['target_date']))->format('Y-m-d') : null;
$app = App::find($data['app']);
$message_admin = "ایجاد تسک: «{$data['title']}»";
$message_admin .= !empty($app) ? "\nاپ {$app->name_fa}" : null;
$message_admin .= !empty($deadline) ? "\nددلاین {$deadline}" : null;
$message_admin .= !empty($data['start_date']) ? "\nتاریخ شروع {$data['start_date']}" : null;
$message_admin .= !empty($data['state_fa']) ? "\nوضعیت: {$data['state_fa']}" : null;
$message_admin .= !empty($data['description']) ? "\nبا توضیحات:\n" . $data['description'] : null;
$message_admin .= !empty($data['assignees']) ? "\nتوسط " . $user->name . " ساخته شد و به" : null;
// 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', [
'color' => 'green',
'text' => 'وظیفه با موفقیت ایجاد شد.'
'text' => $message
]);
}
// 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);
// }
public function activeTask(Request $request)
{
// dd($request);
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
$task = Tasks::find($request->task_id);
if (!$task) {
return response()->json([
'action' => 'not_found',
'status' => false
]);
}
$taskDU = TaskDuration::where('user_id', $user->id)
->where('task_id', $request->task_id)
->orderBy('id', 'desc')
->first();
// اگر رکورد باز داریم => پایان
if (!empty($taskDU) && ($taskDU->start !== null && $taskDU->end == null)) {
// dd('in if');
$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\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\SoftDeletes;
class Tasks extends Model
{
use HasFactory;
use HasFactory,SoftDeletes;
protected $table = 'tasks';
protected $fillable = [
'task_id',
'title',
@@ -65,6 +66,21 @@ class Tasks extends Model
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){
$backlog = [];
$todo = [];
@@ -86,7 +102,7 @@ class Tasks extends Model
'todo' => $todo,
'inprogress' => $inprogress,
'done' => $done,
'count' => count($tasks),
// 'count' => count($tasks),
];
return $tasks;
}
+2 -1
View File
@@ -6,6 +6,7 @@ namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB;
@@ -15,7 +16,7 @@ use Morilog\Jalali\Jalalian;
class User extends Authenticatable
{
/** @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/tinker": "^2.10.1",
"morilog/jalali": "^3.4",
"predis/predis": "^3.2",
"tymon/jwt-auth": "^1.0"
"predis/predis": "^3.2"
},
"require-dev": {
"fakerphp/faker": "^1.23",
Generated
-85
View File
@@ -5840,91 +5840,6 @@
],
"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",
"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"
},
"devDependencies": {
"@37signals/lexxy": "^0.8.5-beta",
"@tailwindcss/vite": "^4.0.0",
"axios": "^1.8.2",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0",
"tailwindcss": "^4.0.0",
"trix": "^2.1.16",
"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()
}
})
document.addEventListener('DOMContentLoaded', function () {
function autoDismissFlashMessage() {
const errorBox = document.getElementById('error-box')
if (errorBox) {
// Fade out the error box after 3 seconds
setTimeout(() => {
errorBox.classList.add('opacity-0') // Add fade-out class
// Remove the error box after the fade-out transition ends
errorBox.addEventListener('transitionend', () => {
errorBox.remove()
})
}, 3000) // Duration before starting fade out
}
})
if (!errorBox) return
setTimeout(() => {
errorBox.classList.add('opacity-0')
errorBox.addEventListener('transitionend', () => {
errorBox.remove()
})
}, 5000)
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', autoDismissFlashMessage)
} else {
autoDismissFlashMessage()
}
</script>
@yield('script')
+364 -333
View File
@@ -38,7 +38,7 @@ if (!empty($user->tasks)) {
});
}
//dd(count($break = $user->attendanceBreaks));
if (count($user->attendanceBreaks) > 0){
if (count($user->attendanceBreaks) > 0) {
$break = $user->attendanceBreaks[0];
if ($break->break_end == null) {
$now = Carbon::now()->format('Y-m-d H:i:s');
@@ -68,7 +68,7 @@ if (count($user->attendanceBreaks) > 0){
{{-- </button>--}}
@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">
@csrf
<p class="title text-center font-bold text-lg mb-2">سامانه گذارش کار</p>
@@ -89,6 +89,12 @@ if (count($user->attendanceBreaks) > 0){
style="width: 100%">
ورود/خروج
</button>
sghl<button id="submitBtn" disabled type="button"
class="bg-gray-400 text-white rounded cursor-not-allowed rounded-xl py-2 px-4 max-w-60 self-center"
style="width: 100%">
ورود/خروج
</button>
<div id="output" class="text-red-500 text-sm mt-2 text-center"></div>
<div class="flex justify-flex max-w-[240px]" style="align-items: center;">
<button type="button"
@@ -111,13 +117,13 @@ if (count($user->attendanceBreaks) > 0){
</button>
{{--<p>{{Carbon::now()}}</p>--}}
</form>
{{-- @if(session('open_task_modal'))--}}
{{-- <script>--}}
{{-- window.onload = () => {--}}
{{-- openTaskModal();--}}
{{-- };--}}
{{-- </script>--}}
{{-- @endif--}}
{{-- @if(session('open_task_modal'))--}}
{{-- <script>--}}
{{-- window.onload = () => {--}}
{{-- openTaskModal();--}}
{{-- };--}}
{{-- </script>--}}
{{-- @endif--}}
@else
@@ -253,19 +259,44 @@ if (count($user->attendanceBreaks) > 0){
attestation: "direct"
};
submitBtn.addEventListener('click', async () => {
// ابتدا اعتبارسنجی اثر انگشت توسط WebAuthn
if (!window.PublicKeyCredential) {
alert('مرورگر شما از احراز هویت بیومتریک پشتیبانی نمی‌کند.');
return;
}
// یک چالش تصادفی بسازید
const challenge = new Uint8Array(32);
window.crypto.getRandomValues(challenge);
// گزینه‌های احراز هویت (Authentication)
const getOptions = {
publicKey: {
challenge: challenge,
timeout: 60000,
rpId: window.location.hostname, // اختیاری؛ برای لوکال‌هاست خالی بگذارید
userVerification: "required",
allowCredentials: [] // خالی بگذارید تا هر کلید موجود نمایش داده شود
}
};
try {
const credential = await navigator.credentials.create({
publicKey: createOptions
});
// به جای create از get استفاده کنید
const assertion = await navigator.credentials.get(getOptions);
console.log('احراز هویت موفق:', assertion);
// اینجا فرم را سابمیت کنید یا هر عملیات دیگر
attendanceForm.submit();
} 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>
@@ -415,7 +446,6 @@ if (count($user->attendanceBreaks) > 0){
});
</script>
@endif
</div>
@@ -447,7 +477,7 @@ if (count($user->attendanceBreaks) > 0){
class=" example1 form-control border p-2 rounded date-picker-input" value=""
placeholder="تاریخ"
readonly
>
>
</div>
@@ -885,329 +915,330 @@ if (count($user->attendanceBreaks) > 0){
method: "POST",
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'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();
'Accept': 'application/json'
}
});
function confirmSubmit() {
return confirm("آیا تغییرات اعمال شوند؟");
}
function openModal(title, id, enteredAt, exitedAt) {
const isNewEntry = id === 'new';
const modalTitleElement = document.getElementById('modal-title');
// ✅ بهترین روش: انتخاب مستقیم فیلد مخفی با ID آن
const hiddenIdInput = document.getElementById('edit_id');
// const hiddenIdInput = document.getElementById('delete_id');
// انتخاب فیلد ID برای فرم حذف
const deleteIdInput = document.getElementById('delete_id');
const submitButton = document.querySelector('#modal-content button[type="submit"]');
const hiddenDateInput = document.querySelector('input[name="date"]');
// مقداردهی به فرم حذف
if (deleteIdInput) {
deleteIdInput.value = id;
}
const newDateLabel = document.getElementById('new-date-label');
const newDateGroup = document.getElementById('new-date-group');
const displayDateInput = document.getElementById('display_date');
const panelLink = document.getElementById('panel-access-link');
panelLink.href = `/go_down/${id}`;
document.getElementById('modal-container').classList.remove('hidden');
const startTimeInput = document.querySelector('input[name="starttime"]');
const endTimeInput = document.querySelector('input[name="endtime"]');
// تنظیم ساعت‌ها
if (enteredAt && !isNaN(new Date(enteredAt).getTime())) {
startTimeInput.value = new Date(enteredAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
} else {
startTimeInput.value = '09:00';
}
if (exitedAt && !isNaN(new Date(exitedAt).getTime())) {
endTimeInput.value = new Date(exitedAt).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
}
// else {
// endTimeInput.value = '17:30';
// }
if (isNewEntry) {
// حالت ثبت جدید
modalTitleElement.innerText = 'ثبت روز کاری جدید';
newDateLabel.style.display = 'block';
newDateGroup.style.display = 'block';
displayDateInput.value = '';
hiddenDateInput.value = '';
// ✅ اصلاح: مقداردهی 'new' به فیلد ID
if (hiddenIdInput) hiddenIdInput.value = 'new';
submitButton.onclick = function () {
if (hiddenDateInput.value === '') {
alert("لطفاً یک تاریخ را از تقویم انتخاب کنید.");
return false;
}
return confirmSubmit();
};
} else {
// حالت ویرایش
modalTitleElement.innerText = 'تغییر ساعت کاری';
newDateLabel.style.display = 'none';
newDateGroup.style.display = 'none';
// ✅ اصلاح: استفاده از متغیر 'id' که از جدول فرستاده شده است
if (hiddenIdInput) hiddenIdInput.value = id;
if (hiddenDateInput) hiddenDateInput.value = enteredAt;
submitButton.onclick = function () {
return confirmSubmit();
};
}
//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"));
})
.then(res => {
if (res.status == 'error') {
console.log(res);
window.location.reload();
} else {
console.log(res);
breakBtn.textContent = 'خطا';
breakBtn.disabled = false;
console.error('Server error on break action:', res.statusText);
window.location.reload();
}
})
.catch(err => {
console.log(err);
window.location.reload();
// breakBtn.textContent = 'خطای شبکه';
// breakBtn.disabled = false;
// console.error('Network error:', err);
});
// 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>
+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')
@section('content')
<div class="w-full mt-[90px] justify-center container flex ">
<div class="bg-white w-[350px] p-6 rounded-xl ">
<h1 class="w-full bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">ایجاد وظیفه جدید</h1>
<form action="{{Route('create_api')}}" method="POST" class="grid grid-cols-2 gap-4 items-center">
<div class="bg-white w-[350px] rounded-xl ">
<h1 class="w-[350px] bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}
</h1>
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class=" p-6 grid-cols-2 gap-4 items-center">
@csrf
<!-- نام Issue (الزامی) -->
<label for="name" class="font-medium text-sm">نام:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="text" id="name" name="name" required>
@if(isset($task))
<input type="hidden" id="id" name="id" value="{{$task->id}}">
@endif
<label for="title" class="font-medium text-sm"><span class="text-red-500">*</span>عنوان:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" value="{{$task->title ?? null}}" type="text"
id="title" name="title" required>
<!-- توضیحات HTML -->
<label for="description_html" class="font-medium text-sm">توضیحات:</label>
<textarea id="description_html" name="description_html"
class="rounded-lg border border-gray-300 p-2 w-full"></textarea>
<label for="description" class="font-medium text-sm">توضیحات:</label>
<textarea id="description" name="description"
class="rounded-lg border border-gray-300 p-2 w-full">{{$task->description ?? null}}</textarea>
<!-- اولویت -->
<label for="priority" class="font-medium text-sm">اولویت:</label>
<select id="priority" name="priority"
class="rounded-lg border border-gray-300 p-2 w-full">
<option value="none">ندارد</option>
<option value="urgent">فوری</option>
<option value="high">بالا</option>
<option value="medium">عادی</option>
<option value="low">پایین</option>
<select id="priority" name="priority" class="rounded-lg border border-gray-300 p-2 w-full">
<option value="none" {{ old('priority', $task->priority ?? '') == 'none' ? 'selected' : '' }}>ندارد</option>
<option value="low" {{ old('priority', $task->priority ?? '') == 'low' ? 'selected' : '' }}>کم</option>
<option value="medium" {{ old('priority', $task->priority ?? '') == 'medium' ? 'selected' : '' }}>عادی</option>
<option value="urgent" {{ old('priority', $task->priority ?? '') == 'urgent' ? 'selected' : '' }}>فوری</option>
<option value="high" {{ old('priority', $task->priority ?? '') == 'high' ? 'selected' : '' }}>زیاد</option>
</select>
<!-- تاریخ شروع -->
<label for="start_date" class="font-medium text-sm">تاریخ شروع:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="start_date"
name="start_date">
<div class="p-2">
<label for="start_date" class="font-medium text-sm">تاریخ شروع <span
class="text-red-500">*</span></label>
<input type="text" id="start_date" name="start_date_display"
class="rounded-lg border border-gray-300 p-2 " value="{{$task->start_date ?? null}}"
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>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="target_date"
name="target_date">
<!-- تاریخ هدف -->
<label for="target_date" class="font-medium text-sm">تاریخ هدف</label>
{{-- <div class="relative">--}}
<input type="text" id="target_date" name="target_date_display"
class="rounded-lg border border-gray-300 p-2 pwt-datepicker-input-element mt-2"
value="{{$task->target_date ?? null}}"
placeholder="تاریخ هدف" autocomplete="off" readonly>
<button type="button" id="clear_target_date"
class="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-red-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
{{-- </div>--}}
<input type="hidden" name="target_date" id="target_date_gregorian">
</div>
<!-- وضعیت -->
<label for="state" class="font-medium text-sm">وضعیت:</label>
<select id="state" name="state"
class="rounded-lg border border-gray-300 p-2 w-full">
<option value="c73c2044-ea14-42cb-b08d-cecf3d1fdfb5">درحال انجام</option>
<option value="a9ce312f-cfca-491e-8e3d-1975aa6ebc10">در انتظار</option>
<option value="dea1fb57-a281-4fe7-8f3e-ee5567637e35">دارای خطا</option>
<option value="770fbe42-99f6-41db-9b9e-773d9cced1a5">در صف انجام</option>
<option value="b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0">انجام‌شده</option>
<option value="d61d14a3-72f7-4f44-894b-efe8b2a75be1">لغوشده</option>
<label for="state" class="font-medium text-sm"><span class="text-red-500">*</span>وضعیت:</label>
<select id="state" name="state" class="rounded-lg border border-gray-300 p-2 w-full" required>
<option value="1" {{ old('state', $task->state ?? '') == 1 ? 'selected' : '' }}>در انتظار</option>
<option value="2" {{ old('state', $task->state ?? '') == 2 ? 'selected' : '' }}>در صف انجام</option>
<option value="3" {{ old('state', $task->state ?? '') == 3 ? 'selected' : '' }}>درحال انجام</option>
<option value="4" {{ old('state', $task->state ?? '') == 4 ? 'selected' : '' }}>انجام‌شده</option>
</select>
<!-- کاربران مسئول -->
{{-- <label for="assignees" class="font-medium text-sm">انتصاب کاربران:</label>--}}
<label class="font-medium text-sm">انتصاب کاربران:</label>
<div class="rounded-lg border border-gray-300 p-2 w-full max-h-32 overflow-y-auto space-y-2">
@foreach($users as $user)
<label class="flex items-center space-x-2">
<input type="checkbox" name="assignees[]" value="{{ $user->plain_id }}" class="rounded text-cyan-600 focus:ring-cyan-500">
<span>{{ $user->name }}</span>
</label>
@endforeach
<label for="assignees" class="font-medium text-sm">انتصاب
کاربران:</label>
<div class="relative" id="dropdown-container">
{{-- دکمه --}}
<button type="button" id="dropdown-btn"
class="w-full bg-white border border-gray-300 rounded-lg px-4 py-2 text-right flex justify-between items-center">
<span id="selected-count">انتخاب کاربران</span>
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</button>
<div id="dropdown-list"
class="hidden absolute z-20 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>
<!-- برچسب‌ها -->
<label for="labels" class="font-medium text-sm">برچسب‌ها:</label>
<input class="rounded-lg border border-gray-300 p-2 w-full"
type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2">
{{-- <!-- برچسب‌ها -->--}}
{{-- <label for="labels" class="font-medium text-sm">برچسب‌ها:</label>--}}
{{-- <input class="rounded-lg border border-gray-300 p-2 w-full"--}}
{{-- type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2">--}}
<!-- Issue والد -->
<label for="parent" class="font-medium text-sm">پروژه والد:</label>
<select id="parent" name="parent" class="rounded-lg border border-gray-300 p-2 w-full">
@foreach($tasks as $task)
<option value="{{$task->task_id}}">{{$task->name}}</option>
@endforeach
<!-- اپ -->
<label for="app" class="font-medium text-sm">نام اپ:</label>
<select id="app" name="app" class="rounded-lg z-20 border border-gray-300 p-2 w-full">
<option value="">-- انتخاب اپ --</option>
@if(!empty($apps) && count($apps) > 0)
@foreach($apps as $app)
<option value="{{ $app->id }}" {{ old('app', $task->app ?? '') == $app->id ? 'selected' : '' }}>
{{ $app->name_fa }}
</option>
@endforeach
@endif
</select>
<!-- امتیاز تخمین -->
<label for="estimate_point" class="font-medium text-sm">Estimate Point (0-7):</label>
<input class="rounded-lg border border-gray-300 p-2 w-full" type="number" id="estimate_point"
name="estimate_point" min="0" max="7">
<!-- دکمه -->
<div></div>
<div class="inline-block flex pt-3 justify-center">
<button type="submit"
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition">
ایجاد وظیفه
</button>
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition <!--mr-24 mt-2.5-->">
{{isset($task) ? "ویرایش" : "ایجاد وظیفه جدید"}}</button>
</div>
</form>
</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
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']);
//attendance Routes
Route::get('attendance/login', [AttendanceController::class, 'login']);
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::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::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/verify', [FingerprintController::class, 'verifyLogin'])->name('fingerprint.login.verify');