Improve tasks UI, dark theme, performance fixes, and admin pages polish
This commit is contained in:
@@ -101,6 +101,236 @@ class TaskController extends Controller
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
private function sumDurationSeconds($durations, ?int $userId = null): int
|
||||
{
|
||||
$totalSeconds = 0;
|
||||
foreach ($durations as $item) {
|
||||
if ($userId !== null && (int)$item->user_id !== $userId) {
|
||||
continue;
|
||||
}
|
||||
if ($item->duration === null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$totalSeconds += CarbonInterval::createFromFormat('H:i:s', $item->duration)->totalSeconds;
|
||||
} catch (\Throwable) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $totalSeconds;
|
||||
}
|
||||
|
||||
private function resolveAssigneeNames(Tasks $task, array $usersById = []): array
|
||||
{
|
||||
$assignees = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
||||
$assignees = is_array($assignees) ? array_map('strval', $assignees) : [];
|
||||
|
||||
if (empty($assignees)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!empty($usersById)) {
|
||||
$names = [];
|
||||
foreach ($assignees as $id) {
|
||||
if (!empty($usersById[$id])) {
|
||||
$names[] = $usersById[$id];
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
return User::whereIn('id', $assignees)->pluck('name')->all();
|
||||
}
|
||||
|
||||
private function buildTasksPayload($tasks, array $usersById = []): array
|
||||
{
|
||||
$payload = [];
|
||||
foreach ($tasks as $task) {
|
||||
$payload[$task->id] = [
|
||||
'id' => $task->id,
|
||||
'title' => $task->title,
|
||||
'description' => $task->description,
|
||||
'start_date' => $task->start_date,
|
||||
'target_date' => $task->target_date,
|
||||
'priority' => $task->priority,
|
||||
'state' => $task->state,
|
||||
'app' => $task->app,
|
||||
'active' => (bool)$task->active,
|
||||
'creator_name' => $task->creator_name,
|
||||
'created_at' => $task->created_at,
|
||||
'assignee_names' => $task->assignee_names ?? $this->resolveAssigneeNames($task, $usersById),
|
||||
'total_duration' => $task->total_duration ?? '00:00:00',
|
||||
'comments' => [],
|
||||
'duration' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function userCanAccessTask(Tasks $task, User $user): bool
|
||||
{
|
||||
if ($user->is_admin == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$assignees = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
||||
$assignees = is_array($assignees) ? array_map('strval', $assignees) : [];
|
||||
|
||||
return (int)$task->created_by === (int)$user->id
|
||||
|| in_array((string)$user->id, $assignees, true);
|
||||
}
|
||||
|
||||
private function buildTaskDetailPayload(Tasks $task, bool $isAdmin, User $user): array
|
||||
{
|
||||
$activeQuery = TaskDuration::query()
|
||||
->where('task_id', $task->id)
|
||||
->whereNull('end');
|
||||
|
||||
if (!$isAdmin) {
|
||||
$activeQuery->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $task->id,
|
||||
'title' => $task->title,
|
||||
'description' => $task->description,
|
||||
'start_date' => $task->start_date,
|
||||
'target_date' => $task->target_date,
|
||||
'priority' => $task->priority,
|
||||
'state' => $task->state,
|
||||
'app' => $task->app,
|
||||
'active' => $activeQuery->exists(),
|
||||
'creator_name' => $task->creator?->name ?? 'نامشخص',
|
||||
'created_at' => $task->created_at,
|
||||
'assignee_names' => $this->resolveAssigneeNames($task),
|
||||
'duration' => $task->duration->map(fn ($d) => [
|
||||
'id' => $d->id,
|
||||
'task_id' => $d->task_id,
|
||||
'user_id' => $d->user_id,
|
||||
'start' => $d->start,
|
||||
'end' => $d->end,
|
||||
'duration' => $d->duration,
|
||||
])->values()->all(),
|
||||
'comments' => $task->comments->map(fn ($c) => [
|
||||
'id' => $c->id,
|
||||
'body' => $c->body,
|
||||
'created_at' => $c->created_at,
|
||||
'user' => $c->user ? ['name' => $c->user->name] : null,
|
||||
])->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function loadTasksPageData(User $user): array
|
||||
{
|
||||
$isAdmin = $user->is_admin == 1;
|
||||
|
||||
$query = Tasks::query()
|
||||
->select([
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'start_date',
|
||||
'target_date',
|
||||
'priority',
|
||||
'state',
|
||||
'app',
|
||||
'created_by',
|
||||
'assignees',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])
|
||||
->with([
|
||||
'appName:id,name_fa,type',
|
||||
'creator:id,name',
|
||||
])
|
||||
->orderByRaw('target_date IS NULL')
|
||||
->orderBy('target_date', 'asc')
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
if (!$isAdmin) {
|
||||
$query->where(function ($q) use ($user) {
|
||||
$q->where('created_by', $user->id)
|
||||
->orWhereJsonContains('assignees', (string)$user->id);
|
||||
});
|
||||
}
|
||||
|
||||
$tasks = $query->get();
|
||||
$taskIds = $tasks->pluck('id');
|
||||
|
||||
$assigneeIds = [];
|
||||
foreach ($tasks as $task) {
|
||||
$ids = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
||||
if (is_array($ids)) {
|
||||
$assigneeIds = array_merge($assigneeIds, array_map('strval', $ids));
|
||||
}
|
||||
}
|
||||
$usersById = !empty($assigneeIds)
|
||||
? User::whereIn('id', array_unique($assigneeIds))
|
||||
->get()
|
||||
->mapWithKeys(fn ($user) => [(string)$user->id => $user->name])
|
||||
->all()
|
||||
: [];
|
||||
|
||||
if ($taskIds->isEmpty()) {
|
||||
return [
|
||||
'tasks' => $tasks,
|
||||
'tasksPayload' => [],
|
||||
'assigneeFilterOptions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$activeQuery = TaskDuration::query()
|
||||
->select(['task_id'])
|
||||
->whereIn('task_id', $taskIds)
|
||||
->whereNull('end');
|
||||
|
||||
if (!$isAdmin) {
|
||||
$activeQuery->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
$activeTaskIds = $activeQuery->pluck('task_id')->flip();
|
||||
|
||||
$durationQuery = TaskDuration::query()
|
||||
->select(['task_id', 'user_id', 'duration'])
|
||||
->whereIn('task_id', $taskIds)
|
||||
->whereNotNull('duration');
|
||||
|
||||
if (!$isAdmin) {
|
||||
$durationQuery->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
$durationsByTaskId = $durationQuery->get()->groupBy('task_id');
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$task->active = $activeTaskIds->has($task->id);
|
||||
$task->creator_name = $task->creator?->name ?? 'نامشخص';
|
||||
$task->assignee_names = $this->resolveAssigneeNames($task, $usersById);
|
||||
$ids = is_string($task->assignees) ? json_decode($task->assignees, true) : $task->assignees;
|
||||
$task->assignee_ids = is_array($ids) ? array_map('strval', $ids) : [];
|
||||
$totalSeconds = $this->sumDurationSeconds(
|
||||
$durationsByTaskId->get($task->id, collect()),
|
||||
$isAdmin ? null : $user->id
|
||||
);
|
||||
$task->total_duration = CarbonInterval::seconds($totalSeconds)->cascade()->format('%H:%I:%S');
|
||||
}
|
||||
|
||||
$assigneeFilterOptions = collect($usersById)
|
||||
->map(fn ($name, $id) => ['id' => (string)$id, 'name' => $name])
|
||||
->sortBy('name', SORT_NATURAL)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return [
|
||||
'tasks' => $tasks,
|
||||
'tasksPayload' => $this->buildTasksPayload($tasks, $usersById),
|
||||
'assigneeFilterOptions' => $assigneeFilterOptions,
|
||||
];
|
||||
}
|
||||
|
||||
// public function tasks()
|
||||
// {
|
||||
// $user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
@@ -135,61 +365,27 @@ class TaskController extends Controller
|
||||
|
||||
public function tasks()
|
||||
{
|
||||
if (!isset($_COOKIE['user_identity'])) {
|
||||
return redirect('/')->with('message', [
|
||||
'color' => 'red',
|
||||
'text' => 'ابتدا باید وارد شوید!'
|
||||
]);
|
||||
}
|
||||
|
||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
if ($user) {
|
||||
if ($user->is_admin == 1) {
|
||||
$tasks = Tasks::with(['duration', 'comments.user', 'appName'])
|
||||
->orderByRaw('target_date IS NULL')
|
||||
->orderBy('target_date', 'asc')
|
||||
->orderBy('updated_at', '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;
|
||||
}
|
||||
}
|
||||
|
||||
$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)
|
||||
// ->orderByRaw('target_date IS NULL')
|
||||
// ->orderBy('target_date', 'asc')
|
||||
->orderBy('updated_at', '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;
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($tasks);
|
||||
|
||||
// dd((new Tasks())->filterByState($tasks)['backlog'][0], 'count ='.$count);
|
||||
['tasks' => $tasks, 'tasksPayload' => $tasksPayload, 'assigneeFilterOptions' => $assigneeFilterOptions] = $this->loadTasksPageData($user);
|
||||
$count = $tasks->count();
|
||||
|
||||
return view('tasks.tasks', [
|
||||
'tasks' => (new Tasks())->filterByState($tasks),
|
||||
'tasksPayload' => $tasksPayload,
|
||||
'assigneeFilterOptions' => $assigneeFilterOptions,
|
||||
'count' => $count,
|
||||
'user_id' => $user->id,
|
||||
'isAdminLevel1' => $user->is_admin == 1,
|
||||
'mentionUsers' => User::select('id', 'name', 'user_name')->get(),
|
||||
'appsList' => App::select('id', 'name_fa')->get(),
|
||||
'appsList' => App::select('id', 'name_fa', 'type')->get(),
|
||||
'priorityLabels' => [
|
||||
'urgent' => 'فوری',
|
||||
'high' => 'بالا',
|
||||
@@ -220,6 +416,37 @@ class TaskController extends Controller
|
||||
|
||||
}
|
||||
|
||||
public function taskDetailsJson($id)
|
||||
{
|
||||
if (!isset($_COOKIE['user_identity'])) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'User not found'], 404);
|
||||
}
|
||||
|
||||
$isAdmin = $user->is_admin == 1;
|
||||
$task = Tasks::query()
|
||||
->with([
|
||||
'creator:id,name',
|
||||
'comments.user:id,name',
|
||||
'duration' => function ($q) use ($isAdmin, $user) {
|
||||
if (!$isAdmin) {
|
||||
$q->where('user_id', $user->id);
|
||||
}
|
||||
},
|
||||
])
|
||||
->find($id);
|
||||
|
||||
if (!$task || !$this->userCanAccessTask($task, $user)) {
|
||||
return response()->json(['error' => 'Not found'], 404);
|
||||
}
|
||||
|
||||
return response()->json($this->buildTaskDetailPayload($task, $isAdmin, $user));
|
||||
}
|
||||
|
||||
public function editTask(Request $request)
|
||||
{
|
||||
// dd($request->all());
|
||||
@@ -342,6 +569,10 @@ class TaskController extends Controller
|
||||
public function activeTask(Request $request)
|
||||
{
|
||||
// dd($request);
|
||||
if (!isset($_COOKIE['user_identity'])) {
|
||||
return response()->json(['action' => 'unauthorized', 'status' => false], 401);
|
||||
}
|
||||
|
||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
$task = Tasks::find($request->task_id);
|
||||
if (!$task) {
|
||||
|
||||
Reference in New Issue
Block a user