diff --git a/app/Livewire/Client/ClientProjects.php b/app/Livewire/Client/ClientProjects.php index cf2ea96..1f24371 100644 --- a/app/Livewire/Client/ClientProjects.php +++ b/app/Livewire/Client/ClientProjects.php @@ -7,6 +7,7 @@ use App\Models\Project; use App\Models\Phase; use App\Models\Inspection; use App\Models\Feature; +use App\Models\ChangeOrder; use Carbon\Carbon; class ClientProjects extends Component @@ -49,7 +50,8 @@ class ClientProjects extends Component $project = Project::with([ 'phases.features', - 'inspections.template' + 'inspections.template', + 'changeOrders' // Load change orders for this project ])->find($this->selectedProject); if (!$project) { @@ -66,68 +68,103 @@ class ClientProjects extends Component 'progress' => $project->phases->avg('progress_percent') ?? 0, ]; - // Get recent images (simulated for now) - $this->galleryImages = [ - [ - 'url' => 'https://via.placeholder.com/400x300?text=Avance+1', - 'title' => 'Avance inicial', - 'date' => now()->subDays(30)->format('d/m/Y') - ], - [ - 'url' => 'https://via.placeholder.com/400x300?text=Avance+2', - 'title' => 'Estructura levantada', - 'date' => now()->subDays(15)->format('d/m/Y') - ], - [ - 'url' => 'https://via.placeholder.com/400x300?text=Avance+3', - 'title' => 'Instalaciones', - 'date' => now()->subDays(5)->format('d/m/Y') - ] - ]; + // Get recent images (we can fetch from media table if needed, but for now we'll keep simulated or link to real) + // For simplicity, we'll try to get some media images for the project + $mediaImages = $project->media() + ->where('category', 'image') + ->latest() + ->take(3) + ->get() + ->map(function($media) { + return [ + 'url' => $media->url, + 'title' => $media->name, + 'date' => $media->created_at->format('d/m/Y') + ]; + }) + ->toArray(); - // Get change orders (simulated for now) - $this->changeOrders = [ - [ - 'id' => 124, - 'title' => 'Ampliación de zona de almacenamiento', - 'description' => 'Solicitud de ampliación de zona de almacenamiento debido a cambios logísticos.', - 'status' => 'pending', - 'requested_at' => now()->subDays(10)->format('d/m/Y'), - 'amount' => 1500.00 - ], - [ - 'id' => 125, - 'title' => 'Cambio de material en acabados interiores', - 'description' => 'Cambio de cerámica estándar a porcelanato en baños y cocinas.', - 'status' => 'pending', - 'requested_at' => now()->subDays(5)->format('d/m/Y'), - 'amount' => 3200.00 - ] - ]; + // If we don't have 3 images, we can fallback to placeholders or just use what we have + if (count($mediaImages) > 0) { + $this->galleryImages = $mediaImages; + } else { + // Fallback to placeholders + $this->galleryImages = [ + [ + 'url' => 'https://via.placeholder.com/400x300?text=Avance+1', + 'title' => 'Avance inicial', + 'date' => now()->subDays(30)->format('d/m/Y') + ], + [ + 'url' => 'https://via.placeholder.com/400x300?text=Avance+2', + 'title' => 'Estructura levantada', + 'date' => now()->subDays(15)->format('d/m/Y') + ], + [ + 'url' => 'https://via.placeholder.com/400x300?text=Avance+3', + 'title' => 'Instalaciones', + 'date' => now()->subDays(5)->format('d/m/Y') + ] + ]; + } + + // Get change orders for this project + $this->changeOrders = $project->changeOrders + ->orderBy('requested_at', 'desc') + ->get() + ->map(function($order) { + return [ + 'id' => $order->id, + 'title' => $order->title, + 'description' => $order->description, + 'status' => $order->status, + 'requested_at' => $order->requested_at->format('d/m/Y'), + 'amount' => $order->amount + ]; + }) + ->toArray(); } public function approveChangeOrder($orderId) { - // In a real app, this would update the database - foreach ($this->changeOrders as &$order) { - if ($order['id'] == $orderId) { - $order['status'] = 'approved'; - break; + // Update the change order in the database + $changeOrder = ChangeOrder::find($orderId); + if ($changeOrder) { + // Check that the change order belongs to the selected project (security) + if ($changeOrder->project_id == $this->selectedProject) { + $changeOrder->status = 'approved'; + $changeOrder->responded_at = now()->toDateString(); + $changeOrder->responded_by = auth()->id(); + $changeOrder->save(); + + // Refresh the change orders list + $this->loadProjectDetails(); + + // Notify any listeners (optional) + $this->dispatch('changeOrderUpdated', ['id' => $orderId, 'status' => 'approved']); } } - $this->dispatch('changeOrderUpdated', ['id' => $orderId, 'status' => 'approved']); } public function rejectChangeOrder($orderId) { - // In a real app, this would update the database - foreach ($this->changeOrders as &$order) { - if ($order['id'] == $orderId) { - $order['status'] = 'rejected'; - break; + // Update the change order in the database + $changeOrder = ChangeOrder::find($orderId); + if ($changeOrder) { + // Check that the change order belongs to the selected project (security) + if ($changeOrder->project_id == $this->selectedProject) { + $changeOrder->status = 'rejected'; + $changeOrder->responded_at = now()->toDateString(); + $changeOrder->responded_by = auth()->id(); + $changeOrder->save(); + + // Refresh the change orders list + $this->loadProjectDetails(); + + // Notify any listeners (optional) + $this->dispatch('changeOrderUpdated', ['id' => $orderId, 'status' => 'rejected']); } } - $this->dispatch('changeOrderUpdated', ['id' => $orderId, 'status' => 'rejected']); } public function render() diff --git a/app/Livewire/ProjectTable.php.bak b/app/Livewire/ProjectTable.php.bak deleted file mode 100644 index a800e1a..0000000 --- a/app/Livewire/ProjectTable.php.bak +++ /dev/null @@ -1,111 +0,0 @@ -setPrimaryKey('id') - ->setDefaultSort('created_at', 'desc') - ->setTableAttributes(['class' => 'table-auto w-full']) - ->setThAttributes(['class' => 'px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider']) - ->setTdAttributes(['class' => 'px-4 py-2 whitespace-nowrap text-sm text-gray-900']); - - $this->addColumn('name', __('Project Name')) - ->setSortable() - ->setSearchable(); - - $this->addColumn('address', __('Address')) - ->setSortable() - ->setSearchable(); - - $this->addColumn('status', __('Status')) - ->setSortable() - ->setFilterable([ - 'planning' => __('Planning'), - 'in_progress' => __('In progress'), - 'paused' => __('Paused'), - 'completed' => __('Completed'), - ]) - ->setLabel(fn ($value, $row, $column, $component) => - match ($value) { - 'planning' => ''.__('Planning').'', - 'in_progress' => ''.__('In progress').'', - 'paused' => ''.__('Paused').'', - 'completed' => ''.__('Completed').'', - default => $value - } - ); - - $this->addColumn('start_date', __('Start Date')) - ->setSortable() - ->setFormat(fn ($value, $row, $column) => $value ? $value->format('Y-m-d') : ''); - - $this->addColumn('end_date_estimated', __('Estimated End Date')) - ->setSortable() - ->setFormat(fn ($value, $row, $column) => $value ? $value->format('Y-m-d') : ''); - - $this->addColumn('actions', __('Actions')) - ->setLabel(fn ($row) => '
- '.__('Edit').' -
- '.csrf_field().' - - -
-
') - ->setHtmlAttribute(['class' => 'text-right']); - } - - public function columns(): array - { - return [ - Column::make(__('Project Name'), 'name') - ->sortable() - ->searchable(), - Column::make(__('Address'), 'address') - ->sortable() - ->searchable(), - Column::make(__('Status'), 'status') - ->sortable() - ->filterable([ - 'planning' => __('Planning'), - 'in_progress' => __('In progress'), - 'paused' => __('Paused'), - 'completed' => __('Completed'), - ]) - ->label(fn ($value, $row, $column) => - match ($value) { - 'planning' => ''.__('Planning').'', - 'in_progress' => ''.__('In progress').'', - 'paused' => ''.__('Paused').'', - 'completed' => ''.__('Completed').'', - default => $value - } - ), - Column::make(__('Start Date'), 'start_date') - ->sortable() - ->format(fn ($value, $row, $column) => $value ? $value->format('Y-m-d') : ''), - Column::make(__('Estimated End Date'), 'end_date_estimated') - ->sortable() - ->format(fn ($value, $row, $column) => $value ? $value->format('Y-m-d') : ''), - Column::make(__('Actions')) - ->label(fn ($row) => '
- '.__('Edit').' -
- '.csrf_field().' - - -
-
') - ->htmlAttribute(['class' => 'text-right']), - ]; - } -} diff --git a/app/Models/ChangeOrder.php b/app/Models/ChangeOrder.php new file mode 100644 index 0000000..61ec3f2 --- /dev/null +++ b/app/Models/ChangeOrder.php @@ -0,0 +1,36 @@ + 'date', + 'responded_at' => 'date', + 'amount' => 'decimal:2', + ]; + + public function project(): BelongsTo + { + return $this->belongsTo(Project::class); + } + + public function responder(): BelongsTo + { + return $this->belongsTo(User::class, 'responded_by'); + } +} diff --git a/app/Models/Project.php b/app/Models/Project.php index 892c229..42e8904 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -8,16 +8,22 @@ use Illuminate\Database\Eloquent\Model; class Project extends Model { use HasFactory; + use HasFactory; protected $fillable = [ 'name', 'address', 'lat', 'lng', 'start_date', 'end_date_estimated', 'status', 'created_by' ]; protected $casts = [ - 'start_date' => 'date', - 'end_date_estimated' => 'date', + "start_date" => "date", + "end_date_estimated" => "date", ]; + public function changeOrders() + { + return $this->hasMany(ChangeOrder::class); + } + // Relationships public function phases() { diff --git a/database/migrations/2026_05_25_164538_create_change_orders_table.php b/database/migrations/2026_05_25_164538_create_change_orders_table.php new file mode 100644 index 0000000..3cfbadb --- /dev/null +++ b/database/migrations/2026_05_25_164538_create_change_orders_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('project_id')->constrained()->onDelete('cascade'); + $table->string('title'); + $table->text('description'); + $table->decimal('amount', 10, 2)->default(0.00); + $table->enum('status', ['pending', 'approved', 'rejected'])->default('pending'); + $table->date('requested_at'); + $table->date('responded_at')->nullable(); + $table->foreignId('responded_by')->nullable()->constrained('users')->onDelete('set null'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('change_orders'); + } +}; diff --git a/resources/views/livewire/template-manager.blade.php.bak b/resources/views/livewire/template-manager.blade.php.bak deleted file mode 100644 index 16f9fb1..0000000 --- a/resources/views/livewire/template-manager.blade.php.bak +++ /dev/null @@ -1,160 +0,0 @@ -
-
-
-

📋 Templates de inspección

-
- -
-
- - @if(session()->has('message')) -
{{ session('message') }}
- @endif - - {{-- Formulario de creación/edición con diseño de dos columnas --}} - @if($showForm) -
- - - {{-- Nombre del template --}} - - - - - - {{-- Descripción --}} - - - - - - {{-- Fase asociada (opcional) --}} - - - - - -
- {{__('Nombre del template')}} - - -
- {{__('Descripción')}} - - -
- {{__('Fase asociada (opcional)')}} - - -
- - {{-- Campos dinámicos --}} -
-

Campos del formulario

- @foreach($form['fields'] as $index => $field) -
- {{-- Fila: nombre interno --}} -
-
Nombre interno
-
-
- {{-- Fila: etiqueta --}} -
-
Etiqueta visible
-
-
- {{-- Fila: tipo --}} -
-
Tipo de campo
-
- -
-
- {{-- Fila: requerido y botón eliminar --}} -
-
Requerido
-
- - -
-
- - {{-- Campos adicionales según tipo --}} - @if(in_array($field['type'], ['integer', 'decimal', 'percentage'])) -
-
Mínimo / Máximo / Paso
-
- - - -
-
- @elseif($field['type'] === 'select') -
-
Opciones (separadas por coma)
-
-
- @endif -
- @endforeach - -
- -
- - -
-
- @endif - - {{-- Tabla de templates existentes --}} -
- - - - - - - - - - - - @forelse($templates as $template) - - - - - - - - @empty - - - - @endforelse - -
NombreDescripciónFaseCamposAcciones
{{ $template->name }}{{ $template->description ?? '-' }}{{ $template->phase ? $template->phase->name : 'Global' }}{{ count($template->fields) }} - - -
No hay templates creados. Presiona "Nuevo template" para comenzar.
-
-
-
\ No newline at end of file