where('is_archived', false)->count(); $completedProjects = Project::where('status', 'done')->count(); $today = Carbon::today(); $delayedProjects = Project::whereDate('end_date', '<', $today)->where('status', '!=', 'done')->where('is_archived', false)->count(); $openTasks = Task::whereNotIn('status', ['done', 'canceled'])->count(); $completedTasks = Task::where('status', 'done')->count(); $delayedTasks = Task::whereDate('due_date', '<', $today)->where('status', '!=', 'done')->count(); $teamPerformance = []; $workload = []; $riskyProjects = Project::whereIn('risk_level', ['high', 'critical'])->where('is_archived', false)->limit(5)->get(); $upcomingDeadlines = Task::with('project') ->whereDate('due_date', '>=', $today) ->whereDate('due_date', '<=', $today->copy()->addDays(7)) ->where('status', '!=', 'done') ->orderBy('due_date') ->limit(10) ->get() ->map(fn ($t) => [ 'id' => $t->id, 'title' => $t->title, 'project' => $t->project?->title, 'due_date' => $t->due_date?->format('Y-m-d'), 'days_left' => $today->diffInDays($t->due_date, false), ]); $recentActivities = ActivityLog::with('user') ->orderBy('created_at', 'desc') ->limit(10) ->get() ->map(fn ($log) => [ 'id' => $log->id, 'description' => $log->description, 'user' => $log->user?->name ?? '?', 'created_at' => $log->created_at?->toDateTimeString(), 'time_ago' => $log->created_at?->diffForHumans(), ]); $myTasks = collect(); if ($user) { $myTasks = Task::where('assignee_id', $user->id) ->where('status', '!=', 'done') ->orderBy('due_date') ->limit(10) ->get(); } $activeSprint = null; if ($user) { $sprint = Sprint::with(['project', 'tasks.assignee']) ->where('status', 'active') ->whereHas('members', fn ($query) => $query->where('users.id', $user->id)) ->orderBy('end_date') ->first(); if ($sprint) { $totalTasks = $sprint->tasks->count(); $completedTasks = $sprint->tasks->where('status', 'done')->count(); $activeSprint = [ 'id' => $sprint->id, 'title' => $sprint->title, 'project' => $sprint->project?->title, 'end_date' => $sprint->end_date?->format('Y-m-d'), 'remaining_days' => max(Carbon::today()->diffInDays($sprint->end_date, false), 0), 'progress' => $totalTasks > 0 ? round(($completedTasks / $totalTasks) * 100, 2) : 0, 'my_tasks' => $sprint->tasks ->where('assignee_id', $user->id) ->values() ->map(fn ($task) => [ 'id' => $task->id, 'title' => $task->title, 'status' => $task->status, ]), ]; } } $todayMeetings = Meeting::whereDate('date', Carbon::today())->count(); $teamMembers = User::count(); $projectProgresses = Project::where('is_archived', false) ->select(['id', 'title', 'progress', 'status']) ->limit(10) ->get(); return [ 'activeProjects' => $activeProjects, 'completedProjects' => $completedProjects, 'delayedProjects' => $delayedProjects, 'openTasks' => $openTasks, 'completedTasks' => $completedTasks, 'delayedTasks' => $delayedTasks, 'teamPerformance' => $teamPerformance, 'workload' => $workload, 'riskyProjects' => $riskyProjects, 'upcomingDeadlines' => $upcomingDeadlines, 'recentActivities' => $recentActivities, 'myTasks' => $myTasks, 'todayMeetings' => $todayMeetings, 'teamMembers' => $teamMembers, 'projectProgresses' => $projectProgresses, 'activeSprint' => $activeSprint, ]; } public function getChartData() { $months = collect(); $taskCreationData = []; $taskCompletionData = []; for ($i = 5; $i >= 0; $i--) { $month = Carbon::now()->subMonths($i); $monthName = $month->format('Y-m'); $months->push($monthName); $taskCreationData[] = Task::whereYear('created_at', $month->year) ->whereMonth('created_at', $month->month) ->count(); $taskCompletionData[] = Task::where('status', 'done') ->whereYear('updated_at', $month->year) ->whereMonth('updated_at', $month->month) ->count(); } $monthlyTasks = []; foreach ($months as $i => $month) { $monthlyTasks[] = [ 'month' => $month, 'created' => $taskCreationData[$i], 'completed' => $taskCompletionData[$i], ]; } return [ 'labels' => $months->values(), 'taskCreation' => $taskCreationData, 'taskCompletion' => $taskCompletionData, 'monthlyTasks' => $monthlyTasks, 'monthly_tasks' => $monthlyTasks, ]; } public function getMonitoring($user = null): array { $today = Carbon::today(); $weekEnd = $today->copy()->addDays(7); $projects = Project::query() ->where('is_archived', false) ->withCount([ 'tasks as total_tasks_count', 'tasks as completed_tasks_count' => fn ($query) => $query->where('status', 'done'), 'tasks as overdue_tasks_count' => fn ($query) => $query ->whereDate('due_date', '<', $today) ->whereNotIn('status', ['done', 'canceled']), 'tasks as blocked_tasks_count' => fn ($query) => $query->blocked(), ]) ->orderByRaw("CASE WHEN risk_level = 'critical' THEN 0 WHEN risk_level = 'high' THEN 1 ELSE 2 END") ->limit(8) ->get() ->map(function (Project $project) { $health = 'on_track'; if ($project->risk_level === 'critical' || $project->overdue_tasks_count >= 5) { $health = 'off_track'; } elseif ($project->risk_level === 'high' || $project->overdue_tasks_count > 0 || $project->blocked_tasks_count > 0) { $health = 'at_risk'; } return [ 'id' => $project->id, 'title' => $project->title, 'status' => $project->status, 'risk_level' => $project->risk_level, 'progress' => $project->progress, 'health' => $health, 'total_tasks' => $project->total_tasks_count, 'completed_tasks' => $project->completed_tasks_count, 'overdue_tasks' => $project->overdue_tasks_count, 'blocked_tasks' => $project->blocked_tasks_count, ]; }); $sprints = Sprint::with(['project:id,title']) ->withCount([ 'tasks as total_tasks_count', 'tasks as completed_tasks_count' => fn ($query) => $query->where('status', 'done'), 'tasks as blocked_tasks_count' => fn ($query) => $query->blocked(), ]) ->where('status', 'active') ->orderBy('end_date') ->limit(6) ->get() ->map(function (Sprint $sprint) use ($today) { $progress = $sprint->total_tasks_count > 0 ? round(($sprint->completed_tasks_count / $sprint->total_tasks_count) * 100) : 0; $elapsed = max($sprint->start_date?->diffInDays($today, false) ?? 0, 0); $duration = max($sprint->start_date?->diffInDays($sprint->end_date) ?? 1, 1); $expected = min(100, round(($elapsed / $duration) * 100)); $health = $sprint->blocked_tasks_count > 1 || $progress + 20 < $expected ? 'at_risk' : 'on_track'; if ($sprint->end_date?->isPast() && $progress < 100) { $health = 'off_track'; } return [ 'id' => $sprint->id, 'title' => $sprint->title, 'project' => $sprint->project?->title, 'progress' => $progress, 'expected_progress' => $expected, 'remaining_days' => max($today->diffInDays($sprint->end_date, false), 0), 'health' => $health, 'blocked_tasks' => $sprint->blocked_tasks_count, ]; }); $workload = User::query() ->where('status', 'active') ->withCount([ 'tasks as open_tasks_count' => fn ($query) => $query->whereNotIn('status', ['done', 'canceled']), 'tasks as overdue_tasks_count' => fn ($query) => $query ->whereDate('due_date', '<', $today) ->whereNotIn('status', ['done', 'canceled']), ]) ->orderByDesc('open_tasks_count') ->limit(8) ->get(['id', 'name']) ->map(fn (User $member) => [ 'id' => $member->id, 'name' => $member->name, 'open_tasks' => $member->open_tasks_count, 'overdue_tasks' => $member->overdue_tasks_count, 'load' => $member->open_tasks_count >= 10 ? 'overloaded' : ($member->open_tasks_count >= 6 ? 'high' : 'balanced'), ]); $attention = collect(); Task::with('project:id,title') ->whereNotIn('status', ['done', 'canceled']) ->where(function ($query) use ($today, $weekEnd) { $query->blocked() ->orWhereDate('due_date', '<', $today) ->orWhereBetween('due_date', [$today, $weekEnd]); }) ->orderBy('due_date') ->limit(10) ->get() ->each(fn (Task $task) => $attention->push([ 'id' => "task-{$task->id}", 'type' => $task->is_blocked ? 'blocker' : ($task->due_date?->isPast() ? 'overdue' : 'deadline'), 'title' => $task->title, 'context' => $task->project?->title, 'date' => $task->due_date?->format('Y-m-d'), 'target_url' => "/tasks?preview={$task->id}", ])); $upcomingMeetings = Meeting::with(['project:id,title', 'sprint:id,title']) ->where(function ($query) use ($today) { $query->whereDate('date', '>', $today) ->orWhere(function ($sameDay) use ($today) { $sameDay->whereDate('date', $today)->whereTime('start_time', '>=', now()->format('H:i:s')); }); }) ->whereNotIn('status', ['cancelled', 'completed']) ->orderBy('date') ->orderBy('start_time') ->limit(5) ->get() ->map(fn (Meeting $meeting) => [ 'id' => $meeting->id, 'title' => $meeting->title, 'date' => $meeting->date?->format('Y-m-d'), 'time' => $meeting->start_time ? substr($meeting->start_time, 0, 5) : null, 'project' => $meeting->project?->title, 'sprint' => $meeting->sprint?->title, 'target_url' => "/meetings/{$meeting->id}/workspace", ]); return [ 'generated_at' => now()->toIso8601String(), 'summary' => [ 'active_projects' => Project::where('is_archived', false)->where('status', '!=', 'done')->count(), 'projects_at_risk' => $projects->whereIn('health', ['at_risk', 'off_track'])->count(), 'open_tasks' => Task::whereNotIn('status', ['done', 'canceled'])->count(), 'overdue_tasks' => Task::whereDate('due_date', '<', $today)->whereNotIn('status', ['done', 'canceled'])->count(), 'blocked_tasks' => Task::blocked()->whereNotIn('status', ['done', 'canceled'])->count(), 'active_sprints' => Sprint::where('status', 'active')->count(), 'meetings_today' => Meeting::whereDate('date', $today)->where('status', '!=', 'cancelled')->count(), 'overdue_action_items' => ActionItem::whereDate('due_date', '<', $today)->whereNotIn('status', ['completed', 'cancelled'])->count(), 'open_blockers' => Blocker::whereNotIn('status', ['resolved', 'closed'])->count(), ], 'projects' => $projects, 'sprints' => $sprints, 'workload' => $workload, 'attention' => $attention->values(), 'upcoming_meetings' => $upcomingMeetings, 'trends' => $this->getChartData()['monthlyTasks'], ]; } }