hasRole('Admin') && !$project->users()->where('user_id', $user->id)->exists()) { abort(403); } $this->project = $project; $this->loadGanttData(); } public function loadGanttData() { $phases = $this->project->phases()->with(['layers.features'])->orderBy('order')->get(); $projectStart = $this->project->start_date ?? now()->startOfMonth(); $projectEnd = $this->project->end_date_estimated ?? now()->addMonths(6); $this->ganttData = $phases->map(function($phase) use ($projectStart, $projectEnd) { $planned_start = $phase->planned_start ?? $projectStart; $planned_end = $phase->planned_end ?? $projectEnd; $actual_start = $phase->actual_start; $actual_end = $phase->actual_end; $totalDays = max(1, $projectStart->diffInDays($projectEnd)); $pStartOffset = max(0, $projectStart->diffInDays($planned_start)); $pDuration = max(1, $planned_start->diffInDays($planned_end)); $pStartPct = round(($pStartOffset / $totalDays) * 100, 2); $pWidthPct = round(($pDuration / $totalDays) * 100, 2); $aStartPct = null; $aWidthPct = null; if ($actual_start) { $aStart = max(0, $projectStart->diffInDays($actual_start)); $aEnd = $actual_end ?? now(); $aDuration = max(1, $actual_start->diffInDays($aEnd)); $aStartPct = round(($aStart / $totalDays) * 100, 2); $aWidthPct = round(($aDuration / $totalDays) * 100, 2); } $isDelayed = $phase->planned_end && $phase->planned_end->isPast() && $phase->progress_percent < 100; return [ 'id' => $phase->id, 'name' => $phase->name, 'color' => $phase->color ?? '#3b82f6', 'progress' => $phase->progress_percent, 'planned_start' => $planned_start->format('d/m/Y'), 'planned_end' => $planned_end->format('d/m/Y'), 'actual_start' => $actual_start?->format('d/m/Y'), 'actual_end' => $actual_end?->format('d/m/Y'), 'p_start_pct' => $pStartPct, 'p_width_pct' => min($pWidthPct, 100 - $pStartPct), 'a_start_pct' => $aStartPct, 'a_width_pct' => $aWidthPct ? min($aWidthPct, 100 - $aStartPct) : null, 'is_delayed' => $isDelayed, 'features_count' => $phase->layers->sum(fn($l) => $l->features->count()), ]; })->toArray(); } public function updatePhaseDates($phaseId, $plannedStart, $plannedEnd, $actualStart = null, $actualEnd = null) { $phase = $this->project->phases()->findOrFail($phaseId); $phase->update([ 'planned_start' => $plannedStart ?: null, 'planned_end' => $plannedEnd ?: null, 'actual_start' => $actualStart ?: null, 'actual_end' => $actualEnd ?: null, ]); $this->loadGanttData(); $this->dispatch('notify', 'Fechas actualizadas'); } public function render() { return view('livewire.projects.phase-gantt', [ 'project' => $this->project, 'phases' => $this->project->phases()->orderBy('order')->get(), ]); } }