whereHas('timeLines', function ($q) use ($user_id, $date) { $q->where('user_id', $user_id) ->whereDate('starts_at', $date); }) ->with(['timeLines' => fn ($q) => $q->where('user_id', $user_id)]) ->orderByDesc('id') ->get(); } public function tasksForUserInRange(int $business_id, int $user_id, Carbon $start, Carbon $end): Collection { return MtUserTask::forBusiness($business_id) ->whereHas('timeLines', function ($q) use ($user_id, $start, $end) { $q->where('user_id', $user_id) ->whereDate('starts_at', '>=', $start->toDateString()) ->whereDate('starts_at', '<=', $end->toDateString()); }) ->with(['timeLines' => fn ($q) => $q->where('user_id', $user_id)]) ->orderByDesc('id') ->get(); } public function calendarTasks(int $business_id, int $user_id, Carbon $start, Carbon $end): Collection { return MtUserTask::forBusiness($business_id) ->where('user_id', $user_id) ->where(function ($q) use ($start, $end) { $q->whereBetween('created_at', [$start, $end]) ->orWhereBetween('due_date', [$start->toDateString(), $end->toDateString()]); }) ->orderBy('title') ->get(); } public function taskWorkMinutes(MtUserTask $task, int $user_id, ?string $date = null): int { $lines = $task->relationLoaded('timeLines') ? $task->timeLines->where('user_id', $user_id) : $task->timeLines()->where('user_id', $user_id)->get(); if ($date) { $lines = $lines->filter(fn ($line) => $line->starts_at && $line->starts_at->toDateString() === $date); } return (int) $lines->sum(fn ($line) => $line->durationMinutes() ?? 0); } public function sumTasksWorkMinutes(Collection $tasks, int $user_id, ?string $date = null): int { return (int) $tasks->sum(fn ($task) => $this->taskWorkMinutes($task, $user_id, $date)); } public function userDayWorkMinutes(int $business_id, int $user_id, string $date): int { $lines = MtUserTimeLine::forBusiness($business_id) ->where('user_id', $user_id) ->whereDate('starts_at', $date) ->get(); return (int) $lines->sum(fn ($line) => $line->durationMinutes() ?? 0); } public function formatDuration(int $minutes): string { if ($minutes <= 0) { return '۰:۰۰'; } $formatted = sprintf('%d:%02d', intdiv($minutes, 60), $minutes % 60); return function_exists('persian_number') ? persian_number($formatted) : $formatted; } public function formatDurationHtml(int $minutes): string { $label = $this->formatDuration($minutes); if ($minutes <= 0) { return ''.$label.''; } return ''.$label.''; } public function latestTimelineForTask(int $business_id, int $user_id, int $task_id, ?string $date = null): ?MtUserTimeLine { $query = MtUserTimeLine::forBusiness($business_id) ->where('user_id', $user_id) ->where('user_task_id', $task_id); if ($date) { $query->whereDate('starts_at', $date); } return $query->latest('starts_at')->first(); } public function dayTabDates(): array { $future = collect(); for ($i = 0; $i < 7; $i++) { $future->push(now()->addDays($i)->format('Y-m-d')); } $past = collect(); for ($i = 1; $i <= 6; $i++) { $past->push(now()->subDays($i)->format('Y-m-d')); } return [ 'future' => $future, 'past' => $past->reverse()->values(), ]; } public function groupedTasksByTimelineDate(Collection $tasks, int $user_id): Collection { $grouped = collect(); foreach ($tasks as $task) { $dates = $task->timeLines ->where('user_id', $user_id) ->pluck('starts_at') ->filter() ->map(fn ($dt) => $dt->toDateString()) ->unique(); foreach ($dates as $date) { if (! $grouped->has($date)) { $grouped->put($date, collect()); } if (! $grouped->get($date)->contains('id', $task->id)) { $grouped->get($date)->push($task); } } } return $grouped->sortKeysDesc(); } }