feat: Add change orders system with client approval/rejection and integrate with client portal
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\Project;
|
|||||||
use App\Models\Phase;
|
use App\Models\Phase;
|
||||||
use App\Models\Inspection;
|
use App\Models\Inspection;
|
||||||
use App\Models\Feature;
|
use App\Models\Feature;
|
||||||
|
use App\Models\ChangeOrder;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class ClientProjects extends Component
|
class ClientProjects extends Component
|
||||||
@@ -49,7 +50,8 @@ class ClientProjects extends Component
|
|||||||
|
|
||||||
$project = Project::with([
|
$project = Project::with([
|
||||||
'phases.features',
|
'phases.features',
|
||||||
'inspections.template'
|
'inspections.template',
|
||||||
|
'changeOrders' // Load change orders for this project
|
||||||
])->find($this->selectedProject);
|
])->find($this->selectedProject);
|
||||||
|
|
||||||
if (!$project) {
|
if (!$project) {
|
||||||
@@ -66,7 +68,27 @@ class ClientProjects extends Component
|
|||||||
'progress' => $project->phases->avg('progress_percent') ?? 0,
|
'progress' => $project->phases->avg('progress_percent') ?? 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Get recent images (simulated for now)
|
// 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();
|
||||||
|
|
||||||
|
// 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 = [
|
$this->galleryImages = [
|
||||||
[
|
[
|
||||||
'url' => 'https://via.placeholder.com/400x300?text=Avance+1',
|
'url' => 'https://via.placeholder.com/400x300?text=Avance+1',
|
||||||
@@ -84,51 +106,66 @@ class ClientProjects extends Component
|
|||||||
'date' => now()->subDays(5)->format('d/m/Y')
|
'date' => now()->subDays(5)->format('d/m/Y')
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Get change orders (simulated for now)
|
// Get change orders for this project
|
||||||
$this->changeOrders = [
|
$this->changeOrders = $project->changeOrders
|
||||||
[
|
->orderBy('requested_at', 'desc')
|
||||||
'id' => 124,
|
->get()
|
||||||
'title' => 'Ampliación de zona de almacenamiento',
|
->map(function($order) {
|
||||||
'description' => 'Solicitud de ampliación de zona de almacenamiento debido a cambios logísticos.',
|
return [
|
||||||
'status' => 'pending',
|
'id' => $order->id,
|
||||||
'requested_at' => now()->subDays(10)->format('d/m/Y'),
|
'title' => $order->title,
|
||||||
'amount' => 1500.00
|
'description' => $order->description,
|
||||||
],
|
'status' => $order->status,
|
||||||
[
|
'requested_at' => $order->requested_at->format('d/m/Y'),
|
||||||
'id' => 125,
|
'amount' => $order->amount
|
||||||
'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
|
|
||||||
]
|
|
||||||
];
|
];
|
||||||
|
})
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function approveChangeOrder($orderId)
|
public function approveChangeOrder($orderId)
|
||||||
{
|
{
|
||||||
// In a real app, this would update the database
|
// Update the change order in the database
|
||||||
foreach ($this->changeOrders as &$order) {
|
$changeOrder = ChangeOrder::find($orderId);
|
||||||
if ($order['id'] == $orderId) {
|
if ($changeOrder) {
|
||||||
$order['status'] = 'approved';
|
// Check that the change order belongs to the selected project (security)
|
||||||
break;
|
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)
|
public function rejectChangeOrder($orderId)
|
||||||
{
|
{
|
||||||
// In a real app, this would update the database
|
// Update the change order in the database
|
||||||
foreach ($this->changeOrders as &$order) {
|
$changeOrder = ChangeOrder::find($orderId);
|
||||||
if ($order['id'] == $orderId) {
|
if ($changeOrder) {
|
||||||
$order['status'] = 'rejected';
|
// Check that the change order belongs to the selected project (security)
|
||||||
break;
|
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()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use Rappasoft\LaravelLivewireTables\DataTableComponent;
|
|
||||||
use Rappasoft\LaravelLivewireTables\Views\Column;
|
|
||||||
use App\Models\Project;
|
|
||||||
|
|
||||||
class ProjectTable extends DataTableComponent
|
|
||||||
{
|
|
||||||
protected $model = Project::class;
|
|
||||||
|
|
||||||
public function configure(): void
|
|
||||||
{
|
|
||||||
$this->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' => '<span class="badge badge-primary">'.__('Planning').'</span>',
|
|
||||||
'in_progress' => '<span class="badge badge-success">'.__('In progress').'</span>',
|
|
||||||
'paused' => '<span class="badge badge-warning">'.__('Paused').'</span>',
|
|
||||||
'completed' => '<span class="badge badge-secondary">'.__('Completed').'</span>',
|
|
||||||
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) => '<div class="flex space-x-2">
|
|
||||||
<a href="'.route('projects.edit', $row->id).'" class="btn btn-sm btn-primary">'.__('Edit').'</a>
|
|
||||||
<form action="'.route('projects.destroy', $row->id).'" method="POST" onsubmit="return confirm(''.__('Are you sure you want to delete this project?').'');">
|
|
||||||
'.csrf_field().'
|
|
||||||
<input type="hidden" name="_method" value="DELETE">
|
|
||||||
<button type="submit" class="btn btn-sm btn-error">'.__('Delete').'</button>
|
|
||||||
</form>
|
|
||||||
</div>')
|
|
||||||
->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' => '<span class="badge badge-primary">'.__('Planning').'</span>',
|
|
||||||
'in_progress' => '<span class="badge badge-success">'.__('In progress').'</span>',
|
|
||||||
'paused' => '<span class="badge badge-warning">'.__('Paused').'</span>',
|
|
||||||
'completed' => '<span class="badge badge-secondary">'.__('Completed').'</span>',
|
|
||||||
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) => '<div class="flex space-x-2">
|
|
||||||
<a href="'.route('projects.edit', $row->id).'" class="btn btn-sm btn-primary">'.__('Edit').'</a>
|
|
||||||
<form action="'.route('projects.destroy', $row->id).'" method="POST" onsubmit="return confirm(''.__('Are you sure you want to delete this project?').'');">
|
|
||||||
'.csrf_field().'
|
|
||||||
<input type="hidden" name="_method" value="DELETE">
|
|
||||||
<button type="submit" class="btn btn-sm btn-error">'.__('Delete').'</button>
|
|
||||||
</form>
|
|
||||||
</div>')
|
|
||||||
->htmlAttribute(['class' => 'text-right']),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class ChangeOrder extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'amount',
|
||||||
|
'status',
|
||||||
|
'requested_at',
|
||||||
|
'responded_at',
|
||||||
|
'responded_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'requested_at' => '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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,16 +8,22 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
class Project extends Model
|
class Project extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name', 'address', 'lat', 'lng', 'start_date', 'end_date_estimated', 'status', 'created_by'
|
'name', 'address', 'lat', 'lng', 'start_date', 'end_date_estimated', 'status', 'created_by'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'start_date' => 'date',
|
"start_date" => "date",
|
||||||
'end_date_estimated' => 'date',
|
"end_date_estimated" => "date",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function changeOrders()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ChangeOrder::class);
|
||||||
|
}
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
public function phases()
|
public function phases()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('change_orders', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
<div>
|
|
||||||
<div class="bg-base-100 p-4 rounded shadow">
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<h2 class="text-xl font-bold">📋 Templates de inspección</h2>
|
|
||||||
<div>
|
|
||||||
<button wire:click="newTemplate" class="btn btn-primary btn-sm">
|
|
||||||
Nuevo template
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if(session()->has('message'))
|
|
||||||
<div class="alert alert-success mb-4">{{ session('message') }}</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- Formulario de creación/edición con diseño de dos columnas --}}
|
|
||||||
@if($showForm)
|
|
||||||
<form wire:submit.prevent="saveTemplate" class="border p-4 rounded mb-6 bg-base-200">
|
|
||||||
<table class="w-full mb-8">
|
|
||||||
<tbody>
|
|
||||||
{{-- Nombre del template --}}
|
|
||||||
<tr>
|
|
||||||
<td class="w-1/4 py-3 pr-4 align-top">
|
|
||||||
{{__('Nombre del template')}}
|
|
||||||
</td>
|
|
||||||
<td class="py-3">
|
|
||||||
<input type="text" wire:model="form.name"
|
|
||||||
class="input w-full"
|
|
||||||
required>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{{-- Descripción --}}
|
|
||||||
<tr>
|
|
||||||
<td class="w-1/4 py-3 pr-4 align-top">
|
|
||||||
{{__('Descripción')}}
|
|
||||||
</td>
|
|
||||||
<td class="py-3">
|
|
||||||
<textarea wire:model="form.description" class="textarea textarea-bordered w-full" rows="2"></textarea>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{{-- Fase asociada (opcional) --}}
|
|
||||||
<tr>
|
|
||||||
<td class="w-1/4 py-3 pr-4 align-top">
|
|
||||||
{{__('Fase asociada (opcional)')}}
|
|
||||||
</td>
|
|
||||||
<td class="py-3">
|
|
||||||
<select wire:model="form.phase_id" class="select select-bordered w-full">
|
|
||||||
<option value="">Ninguna (global para el proyecto)</option>
|
|
||||||
@foreach($phases as $phase)
|
|
||||||
<option value="{{ $phase->id }}" {{ old('form.phase_id') == $phase->id ? 'selected' : '' }}>
|
|
||||||
{{ $phase->name }}
|
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{{-- Campos dinámicos --}}
|
|
||||||
<div class="border-t pt-4 mt-2">
|
|
||||||
<h3 class="font-bold mb-3">Campos del formulario</h3>
|
|
||||||
@foreach($form['fields'] as $index => $field)
|
|
||||||
<div class="border p-3 rounded mb-3 bg-base-100">
|
|
||||||
{{-- Fila: nombre interno --}}
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
|
||||||
<div class="font-medium">Nombre interno</div>
|
|
||||||
<div><input type="text" wire:model="form.fields.{{ $index }}.name" placeholder="ej: altura_medida" class="input input-sm w-full"></div>
|
|
||||||
</div>
|
|
||||||
{{-- Fila: etiqueta --}}
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
|
||||||
<div class="font-medium">Etiqueta visible</div>
|
|
||||||
<div><input type="text" wire:model="form.fields.{{ $index }}.label" placeholder="ej: Altura medida (m)" class="input input-sm w-full"></div>
|
|
||||||
</div>
|
|
||||||
{{-- Fila: tipo --}}
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
|
||||||
<div class="font-medium">Tipo de campo</div>
|
|
||||||
<div>
|
|
||||||
<select wire:model="form.fields.{{ $index }}.type" class="select select-sm w-full">
|
|
||||||
@foreach($fieldTypes as $typeValue => $typeLabel)
|
|
||||||
<option value="{{ $typeValue }}">{{ $typeLabel }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{-- Fila: requerido y botón eliminar --}}
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
|
||||||
<div class="font-medium">Requerido</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<input type="checkbox" wire:model="form.fields.{{ $index }}.required" class="checkbox checkbox-sm">
|
|
||||||
<button type="button" wire:click="removeField({{ $index }})" class="btn btn-xs btn-error">Eliminar campo</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Campos adicionales según tipo --}}
|
|
||||||
@if(in_array($field['type'], ['integer', 'decimal', 'percentage']))
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
|
||||||
<div class="font-medium">Mínimo / Máximo / Paso</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input type="number" wire:model="form.fields.{{ $index }}.min" placeholder="Mín" class="input input-xs w-20">
|
|
||||||
<input type="number" wire:model="form.fields.{{ $index }}.max" placeholder="Máx" class="input input-xs w-20">
|
|
||||||
<input type="number" step="any" wire:model="form.fields.{{ $index }}.step" placeholder="Paso" class="input input-xs w-20">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@elseif($field['type'] === 'select')
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
|
||||||
<div class="font-medium">Opciones (separadas por coma)</div>
|
|
||||||
<div><input type="text" wire:model="form.fields.{{ $index }}.options" placeholder="ej: Bueno,Regular,Malo" class="input input-sm w-full"></div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
<button type="button" wire:click="addField" class="btn btn-sm btn-secondary mt-2">+ Agregar campo</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-2 mt-4">
|
|
||||||
<button type="submit" class="btn btn-primary">Guardar template</button>
|
|
||||||
<button type="button" wire:click="cancelForm" class="btn">Cancelar</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- Tabla de templates existentes --}}
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="table table-zebra">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Nombre</th>
|
|
||||||
<th>Descripción</th>
|
|
||||||
<th>Fase</th>
|
|
||||||
<th>Campos</th>
|
|
||||||
<th>Acciones</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@forelse($templates as $template)
|
|
||||||
<tr>
|
|
||||||
<td>{{ $template->name }}</td>
|
|
||||||
<td>{{ $template->description ?? '-' }}</td>
|
|
||||||
<td>{{ $template->phase ? $template->phase->name : 'Global' }}</td>
|
|
||||||
<td>{{ count($template->fields) }}</td>
|
|
||||||
<td>
|
|
||||||
<button wire:click="editTemplate({{ $template->id }})" class="btn btn-xs btn-warning">
|
|
||||||
Editar
|
|
||||||
</button>
|
|
||||||
<button wire:click="deleteTemplate({{ $template->id }})" class="btn btn-xs btn-error" onclick="return confirm('¿Eliminar template?')">Eliminar</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@empty
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="text-center">No hay templates creados. Presiona "Nuevo template" para comenzar.</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
Reference in New Issue
Block a user