diff --git a/app/Livewire/PhaseList.php b/app/Livewire/PhaseList.php index 4bbd6ec..f284cf7 100644 --- a/app/Livewire/PhaseList.php +++ b/app/Livewire/PhaseList.php @@ -2,40 +2,141 @@ namespace App\Livewire; -use Livewire\Component; -use App\Models\Project; use App\Models\Phase; +use App\Models\Project; +use Illuminate\Support\Facades\Auth; +use Livewire\Attributes\On; +use Livewire\Component; class PhaseList extends Component { public Project $project; - public $phases; + + // Modal state + public bool $showForm = false; + public $editingId = null; + + // Form fields + public string $name = ''; + public string $description = ''; + public string $color = '#3b82f6'; + public int $order = 1; + public int $progressPercent = 0; + public string $plannedStart = ''; + public string $plannedEnd = ''; + public string $actualStart = ''; + public string $actualEnd = ''; public function mount(Project $project) { $this->project = $project; - $this->phases = $project->phases; } - public function addPhase() + protected function rules(): array { - $this->project->phases()->create([ - 'name' => 'Nueva fase', - 'order' => $this->phases->count() + 1, - 'color' => '#'.substr(md5(rand()), 0, 6) + return [ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'color' => 'required|string|max:7', + 'order' => 'required|integer|min:0', + 'progressPercent' => 'required|integer|min:0|max:100', + 'plannedStart' => 'nullable|date', + 'plannedEnd' => 'nullable|date|after_or_equal:plannedStart', + 'actualStart' => 'nullable|date', + 'actualEnd' => 'nullable|date|after_or_equal:actualStart', + ]; + } + + protected $validationAttributes = [ + 'name' => 'nombre', + 'color' => 'color', + 'order' => 'orden', + 'progressPercent' => 'progreso', + 'plannedStart' => 'inicio previsto', + 'plannedEnd' => 'fin previsto', + 'actualStart' => 'inicio real', + 'actualEnd' => 'fin real', + ]; + + public function openForm($phaseId = null): void + { + abort_unless(Auth::user()->can('manage phases'), 403); + $this->resetForm(); + + if ($phaseId) { + $phase = $this->project->phases()->findOrFail($phaseId); + $this->editingId = $phase->id; + $this->name = $phase->name; + $this->description = $phase->description ?? ''; + $this->color = $phase->color ?? '#3b82f6'; + $this->order = (int) $phase->order; + $this->progressPercent = (int) $phase->progress_percent; + $this->plannedStart = $phase->planned_start?->format('Y-m-d') ?? ''; + $this->plannedEnd = $phase->planned_end?->format('Y-m-d') ?? ''; + $this->actualStart = $phase->actual_start?->format('Y-m-d') ?? ''; + $this->actualEnd = $phase->actual_end?->format('Y-m-d') ?? ''; + } else { + $this->order = (int) $this->project->phases()->max('order') + 1; + $this->color = '#' . substr(md5((string) rand()), 0, 6); + } + + $this->showForm = true; + } + + /** Opened from the table's edit button. */ + #[On('phase-edit')] + public function editPhase($id): void + { + $this->openForm($id); + } + + public function closeForm(): void + { + $this->showForm = false; + $this->resetForm(); + } + + private function resetForm(): void + { + $this->reset([ + 'editingId', 'name', 'description', 'plannedStart', 'plannedEnd', 'actualStart', 'actualEnd', ]); - $this->phases = $this->project->phases()->get(); - session()->flash('message', 'Fase agregada'); + $this->color = '#3b82f6'; + $this->order = 1; + $this->progressPercent = 0; + $this->resetErrorBag(); } - public function deletePhase($phaseId) + public function save(): void { - Phase::find($phaseId)->delete(); - $this->phases = $this->project->phases()->get(); + abort_unless(Auth::user()->can('manage phases'), 403); + $this->validate(); + + $data = [ + 'name' => $this->name, + 'description' => $this->description ?: null, + 'color' => $this->color, + 'order' => $this->order, + 'progress_percent' => $this->progressPercent, + 'planned_start' => $this->plannedStart ?: null, + 'planned_end' => $this->plannedEnd ?: null, + 'actual_start' => $this->actualStart ?: null, + 'actual_end' => $this->actualEnd ?: null, + ]; + + if ($this->editingId) { + $this->project->phases()->findOrFail($this->editingId)->update($data); + } else { + $this->project->phases()->create($data); + } + + $this->closeForm(); + $this->dispatch('phases-changed'); + $this->dispatch('notify', 'Fase guardada correctamente'); } public function render() { return view('livewire.phase-list'); } -} \ No newline at end of file +} diff --git a/app/Livewire/PhaseTable.php b/app/Livewire/PhaseTable.php new file mode 100644 index 0000000..c794fac --- /dev/null +++ b/app/Livewire/PhaseTable.php @@ -0,0 +1,120 @@ +setPrimaryKey('id') + ->setDefaultSort('order', 'asc') + ->setSortingPillsEnabled(false) + ->setAdditionalSelects(['phases.id as id', 'phases.order as order']); + } + + /** Re-render when the parent (PhaseList) creates/edits a phase. */ + #[On('phases-changed')] + public function refreshRows(): void + { + // no-op: triggers a re-render so the builder re-runs with fresh data. + } + + public function builder(): Builder + { + $user = Auth::user(); + abort_unless( + $user->can('manage all') || $user->can('edit projects') || + Project::whereKey($this->projectId)->whereHas('users', fn ($q) => $q->where('user_id', $user->id))->exists(), + 403 + ); + + return Phase::where('phases.project_id', $this->projectId); + } + + public function columns(): array + { + return [ + Column::make('Orden', 'order')->sortable(), + + Column::make('Nombre', 'name') + ->sortable() + ->searchable() + ->format(function ($value, $row) { + $html = ''.e($value).''; + if ($row->description) { + $html .= '
| {{ __('Name') }} | {{ __('Progress') }} | {{ __('Color') }} | {{ __('Actions') }} |
|---|---|---|---|
| {{ $phase->name }} | -
-
-
+
+
+
+ {{-- Tabla Rappasoft de fases --}}
+
+
+ @can('manage phases')
+
+ @endcan
+ {{ __('Phases') }}+Fases del proyecto y su progreso +
+
+
+
+
+ {{ $editingId ? 'Editar fase' : 'Nueva fase' }}+ + |
- - | - {{ __('Update') }} - - | -