diff --git a/app/Console/Commands/ConvertSpatialFile.php b/app/Console/Commands/ConvertSpatialFile.php
deleted file mode 100644
index 63728b5..0000000
--- a/app/Console/Commands/ConvertSpatialFile.php
+++ /dev/null
@@ -1,44 +0,0 @@
-argument('file');
- $phaseId = $this->argument('phase_id');
- $phase = Phase::findOrFail($phaseId);
- $file = new \Illuminate\Http\UploadedFile($filePath, basename($filePath));
-
- $geojson = SpatialFileConverter::convertToGeoJson($file, $file->getClientOriginalName());
- if ($geojson) {
- $layer = $phase->layers()->create([
- 'project_id' => $phase->project_id,
- 'name' => 'Converted: ' . basename($filePath),
- 'geojson_data' => $geojson,
- 'uploaded_by' => 1, // admin
- 'original_file' => $filePath
- ]);
- $this->info("GeoJSON saved to layer ID {$layer->id}");
- } else {
- $this->error("Conversion failed for file type.");
- }
- }
-}
diff --git a/app/Console/Commands/MigrateGeojsonToFeatures.php b/app/Console/Commands/MigrateGeojsonToFeatures.php
deleted file mode 100644
index b732cc3..0000000
--- a/app/Console/Commands/MigrateGeojsonToFeatures.php
+++ /dev/null
@@ -1,45 +0,0 @@
-get();
- $totalFeatures = 0;
-
- foreach ($layers as $layer) {
- $geojson = $layer->geojson_data;
- if (!isset($geojson['features'])) continue;
-
- foreach ($geojson['features'] as $featureData) {
- $geometry = $featureData['geometry'];
- $props = $featureData['properties'] ?? [];
-
- Feature::create([
- 'layer_id' => $layer->id,
- 'name' => $props['name'] ?? null,
- 'geometry' => $geometry,
- 'properties' => $props,
- 'template_id' => $props['template_id'] ?? null,
- 'progress' => $props['progress'] ?? 0,
- 'responsible' => $props['responsible'] ?? null,
- ]);
- $totalFeatures++;
- }
-
- // Opcional: Marcar la capa como migrada (podrías agregar columna 'migrated_at')
- $this->info("Layer {$layer->id} ({$layer->name}) migrated.");
- }
-
- $this->info("Total features migrated: {$totalFeatures}");
- }
-}
\ No newline at end of file
diff --git a/app/DTO/ReportFilters.php b/app/DTO/ReportFilters.php
index ec19c79..090e5c5 100644
--- a/app/DTO/ReportFilters.php
+++ b/app/DTO/ReportFilters.php
@@ -3,7 +3,6 @@
namespace App\DTO;
use Carbon\Carbon;
-use Illuminate\Support\Collection;
class ReportFilters
{
@@ -21,10 +20,10 @@ class ReportFilters
return new self(
dateFrom: isset($data['date_from']) ? Carbon::parse($data['date_from']) : null,
dateTo: isset($data['date_to']) ? Carbon::parse($data['date_to']) : null,
- entityTypes: $data['entity_types'] ?? ['phases','features','inspections','issues','tasks'],
- includePhotos: (bool)($data['include_photos'] ?? false),
+ entityTypes: $data['entity_types'] ?? ['phases', 'features', 'inspections', 'issues', 'tasks'],
+ includePhotos: (bool) ($data['include_photos'] ?? false),
format: $data['format'] ?? 'html',
- includeCharts: (bool)($data['include_charts'] ?? false),
+ includeCharts: (bool) ($data['include_charts'] ?? false),
);
}
@@ -55,14 +54,15 @@ class ReportFilters
public function getDateRangeLabel(): string
{
if ($this->dateFrom && $this->dateTo) {
- return $this->dateFrom->format('d/m/Y') . ' - ' . $this->dateTo->format('d/m/Y');
+ return $this->dateFrom->format('d/m/Y').' - '.$this->dateTo->format('d/m/Y');
}
if ($this->dateFrom) {
- return 'Desde ' . $this->dateFrom->format('d/m/Y');
+ return 'Desde '.$this->dateFrom->format('d/m/Y');
}
if ($this->dateTo) {
- return 'Hasta ' . $this->dateTo->format('d/m/Y');
+ return 'Hasta '.$this->dateTo->format('d/m/Y');
}
+
return 'Todo el período';
}
-}
\ No newline at end of file
+}
diff --git a/app/Exports/InspectionsExport.php b/app/Exports/InspectionsExport.php
index 06d156a..8df91ca 100644
--- a/app/Exports/InspectionsExport.php
+++ b/app/Exports/InspectionsExport.php
@@ -18,7 +18,7 @@ class InspectionsExport implements FromCollection, WithHeadings
'status',
'notes',
'created_at',
- 'updated_at'
+ 'updated_at',
])->get();
}
@@ -32,7 +32,7 @@ class InspectionsExport implements FromCollection, WithHeadings
'Estado',
'Notas',
'Creado el',
- 'Actualizado el'
+ 'Actualizado el',
];
}
}
diff --git a/app/Exports/PhasesExport.php b/app/Exports/PhasesExport.php
index 3df9db4..4bd5476 100644
--- a/app/Exports/PhasesExport.php
+++ b/app/Exports/PhasesExport.php
@@ -18,7 +18,7 @@ class PhasesExport implements FromCollection, WithHeadings
'start_date',
'end_date',
'created_at',
- 'updated_at'
+ 'updated_at',
])->get();
}
@@ -32,7 +32,7 @@ class PhasesExport implements FromCollection, WithHeadings
'Fecha de inicio',
'Fecha de fin',
'Creado el',
- 'Actualizado el'
+ 'Actualizado el',
];
}
}
diff --git a/app/Exports/ProjectReportExport.php b/app/Exports/ProjectReportExport.php
index 58c3058..6c54aa8 100644
--- a/app/Exports/ProjectReportExport.php
+++ b/app/Exports/ProjectReportExport.php
@@ -5,20 +5,22 @@ namespace App\Exports;
use App\DTO\ReportFilters;
use App\Models\Project;
use Maatwebsite\Excel\Concerns\FromArray;
-use Maatwebsite\Excel\Concerns\WithMultipleSheets;
-use Maatwebsite\Excel\Concerns\WithTitle;
-use Maatwebsite\Excel\Concerns\WithHeadings;
-use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
-use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use Maatwebsite\Excel\Concerns\WithHeadings;
+use Maatwebsite\Excel\Concerns\WithMultipleSheets;
+use Maatwebsite\Excel\Concerns\WithStyles;
+use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ProjectReportExport implements WithMultipleSheets
{
protected Project $project;
+
protected ReportFilters $filters;
+
protected array $data;
public function __construct(Project $project, ReportFilters $filters, array $data)
@@ -34,35 +36,35 @@ class ProjectReportExport implements WithMultipleSheets
new SummarySheet($this->data['summary'] ?? [], $this->project, $this->filters),
];
- if (!empty($this->data['phases'])) {
+ if (! empty($this->data['phases'])) {
$sheets[] = new PhasesSheet($this->data['phases']);
}
- if (!empty($this->data['features'])) {
+ if (! empty($this->data['features'])) {
$sheets[] = new FeaturesSheet($this->data['features']);
}
- if (!empty($this->data['inspections'])) {
+ if (! empty($this->data['inspections'])) {
$sheets[] = new InspectionsSheet($this->data['inspections']);
}
- if (!empty($this->data['issues'])) {
+ if (! empty($this->data['issues'])) {
$sheets[] = new IssuesSheet($this->data['issues']);
}
- if (!empty($this->data['tasks'])) {
+ if (! empty($this->data['tasks'])) {
$sheets[] = new TasksSheet($this->data['tasks']);
}
- if (!empty($this->data['deviations'])) {
+ if (! empty($this->data['deviations'])) {
$sheets[] = new DeviationsSheet($this->data['deviations']);
}
- if (!empty($this->data['media'])) {
+ if (! empty($this->data['media'])) {
$sheets[] = new MediaSheet($this->data['media']);
}
- if (!empty($this->data['progress_curve'])) {
+ if (! empty($this->data['progress_curve'])) {
$sheets[] = new ProgressCurveSheet($this->data['progress_curve']);
}
@@ -77,7 +79,7 @@ class ProjectReportExport implements WithMultipleSheets
// Base Sheet with common styling
// ============================================================
-abstract class BaseSheet implements FromArray, WithHeadings, WithStyles, WithColumnWidths, WithTitle
+abstract class BaseSheet implements FromArray, WithColumnWidths, WithHeadings, WithStyles, WithTitle
{
protected array $rows = [];
@@ -129,7 +131,7 @@ abstract class BaseSheet implements FromArray, WithHeadings, WithStyles, WithCol
// Auto-filter
$sheet->setAutoFilter($sheet->calculateWorksheetDimension());
-
+
// Freeze header row
$sheet->freezePane('A2');
}
@@ -147,13 +149,14 @@ abstract class BaseSheet implements FromArray, WithHeadings, WithStyles, WithCol
class SummarySheet extends BaseSheet
{
protected Project $project;
+
protected ReportFilters $filters;
public function __construct(array $summary, Project $project, ReportFilters $filters)
{
$this->project = $project;
$this->filters = $filters;
-
+
$rows = [
['Informe de Proyecto', $project->name],
['Referencia', $project->reference ?? '—'],
@@ -194,7 +197,7 @@ class SummarySheet extends BaseSheet
['Retrasadas', $summary['phases_delayed'] ?? 0],
['Sin datos', $summary['phases_no_data'] ?? 0],
];
-
+
parent::__construct($rows);
}
@@ -218,10 +221,10 @@ class PhasesSheet extends BaseSheet
public function headings(): array
{
return [
- 'ID', 'Fase', 'Orden', 'Color', 'Inicio Plan', 'Fin Plan',
- 'Inicio Real', 'Fin Real', 'Progreso (%)', 'Progreso Plan (%)',
+ 'ID', 'Fase', 'Orden', 'Color', 'Inicio Plan', 'Fin Plan',
+ 'Inicio Real', 'Fin Real', 'Progreso (%)', 'Progreso Plan (%)',
'Desvío Fin (días)', 'Desvío Inicio (días)', 'SPI', 'En Plazo',
- 'Elementos', 'Completados', 'Capas'
+ 'Elementos', 'Completados', 'Capas',
];
}
@@ -252,9 +255,9 @@ class PhasesSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 8, 'B' => 25, 'C' => 8, 'D' => 10, 'E' => 12, 'F' => 12,
- 'G' => 12, 'H' => 12, 'I' => 12, 'J' => 14, 'K' => 14, 'L' => 14,
- 'M' => 8, 'N' => 10, 'O' => 10, 'P' => 12, 'Q' => 10];
+ return ['A' => 8, 'B' => 25, 'C' => 8, 'D' => 10, 'E' => 12, 'F' => 12,
+ 'G' => 12, 'H' => 12, 'I' => 12, 'J' => 14, 'K' => 14, 'L' => 14,
+ 'M' => 8, 'N' => 10, 'O' => 10, 'P' => 12, 'Q' => 10];
}
}
@@ -270,7 +273,7 @@ class FeaturesSheet extends BaseSheet
'ID', 'Elemento', 'Fase', 'Capa', 'Estado', 'Progreso (%)', 'Progreso Plan (%)',
'Inicio Plan', 'Fin Plan', 'Inicio Real', 'Fin Real', 'Desvío Fin (días)',
'Desvío Inicio (días)', 'SPI', 'En Plazo', 'Responsable', 'Template',
- 'Última Inspección', 'Resultado', 'Inspecciones', 'Issues Abiertos'
+ 'Última Inspección', 'Resultado', 'Inspecciones', 'Issues Abiertos',
];
}
@@ -305,10 +308,10 @@ class FeaturesSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 8, 'B' => 25, 'C' => 20, 'D' => 20, 'E' => 15, 'F' => 12,
- 'G' => 14, 'H' => 12, 'I' => 12, 'J' => 12, 'K' => 12, 'L' => 14,
- 'M' => 14, 'N' => 8, 'O' => 10, 'P' => 20, 'Q' => 20, 'R' => 14,
- 'S' => 12, 'T' => 12, 'U' => 12];
+ return ['A' => 8, 'B' => 25, 'C' => 20, 'D' => 20, 'E' => 15, 'F' => 12,
+ 'G' => 14, 'H' => 12, 'I' => 12, 'J' => 12, 'K' => 12, 'L' => 14,
+ 'M' => 14, 'N' => 8, 'O' => 10, 'P' => 20, 'Q' => 20, 'R' => 14,
+ 'S' => 12, 'T' => 12, 'U' => 12];
}
}
@@ -321,8 +324,8 @@ class InspectionsSheet extends BaseSheet
public function headings(): array
{
return [
- 'ID', 'Elemento', 'Fase', 'Template', 'Inspector', 'Fecha',
- 'Estado', 'Resultado', 'Notas', 'Fotos'
+ 'ID', 'Elemento', 'Fase', 'Template', 'Inspector', 'Fecha',
+ 'Estado', 'Resultado', 'Notas', 'Fotos',
];
}
@@ -346,8 +349,8 @@ class InspectionsSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 8, 'B' => 25, 'C' => 20, 'D' => 25, 'E' => 20, 'F' => 18,
- 'G' => 12, 'H' => 12, 'I' => 40, 'J' => 8];
+ return ['A' => 8, 'B' => 25, 'C' => 20, 'D' => 25, 'E' => 20, 'F' => 18,
+ 'G' => 12, 'H' => 12, 'I' => 40, 'J' => 8];
}
}
@@ -360,9 +363,9 @@ class IssuesSheet extends BaseSheet
public function headings(): array
{
return [
- 'ID', 'Título', 'Elemento', 'Fase', 'Prioridad', 'Estado',
- 'Reportado por', 'Asignado a', 'Creado', 'Cerrado', 'Días abierto',
- 'Tareas total', 'Tareas completadas'
+ 'ID', 'Título', 'Elemento', 'Fase', 'Prioridad', 'Estado',
+ 'Reportado por', 'Asignado a', 'Creado', 'Cerrado', 'Días abierto',
+ 'Tareas total', 'Tareas completadas',
];
}
@@ -389,8 +392,8 @@ class IssuesSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 8, 'B' => 35, 'C' => 20, 'D' => 20, 'E' => 12, 'F' => 15,
- 'G' => 20, 'H' => 20, 'I' => 12, 'J' => 12, 'K' => 12, 'L' => 12, 'M' => 14];
+ return ['A' => 8, 'B' => 35, 'C' => 20, 'D' => 20, 'E' => 12, 'F' => 15,
+ 'G' => 20, 'H' => 20, 'I' => 12, 'J' => 12, 'K' => 12, 'L' => 12, 'M' => 14];
}
}
@@ -404,15 +407,16 @@ class TasksSheet extends BaseSheet
{
return [
'ID', 'Tarea', 'Fase', 'Estado', 'Prioridad', 'Asignado', 'Creador',
- 'Fecha inicio', 'Fecha fin', 'Completada', 'Horas est.', 'Horas real',
- 'Progreso (%)', 'Vencida', 'Subtareas'
+ 'Fecha inicio', 'Fecha fin', 'Completada', 'Horas est.', 'Horas real',
+ 'Progreso (%)', 'Vencida', 'Subtareas',
];
}
public function array(): array
{
return array_map(function ($task) {
- $subtasksStr = implode('; ', array_map(fn($st) => "{$st['title']} ({$st['status']})", $task['subtasks']));
+ $subtasksStr = implode('; ', array_map(fn ($st) => "{$st['title']} ({$st['status']})", $task['subtasks']));
+
return [
$task['id'],
$task['title'],
@@ -435,9 +439,9 @@ class TasksSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 8, 'B' => 30, 'C' => 20, 'D' => 15, 'E' => 12, 'F' => 20,
- 'G' => 20, 'H' => 12, 'I' => 12, 'J' => 12, 'K' => 10, 'L' => 10,
- 'M' => 12, 'N' => 10, 'O' => 40];
+ return ['A' => 8, 'B' => 30, 'C' => 20, 'D' => 15, 'E' => 12, 'F' => 20,
+ 'G' => 20, 'H' => 12, 'I' => 12, 'J' => 12, 'K' => 10, 'L' => 10,
+ 'M' => 12, 'N' => 10, 'O' => 40];
}
}
@@ -453,14 +457,14 @@ class DeviationsSheet extends BaseSheet
{
$this->deviations = $deviations;
$rows = [];
-
+
// Phase deviations
- if (!empty($deviations['phases'])) {
+ if (! empty($deviations['phases'])) {
$rows[] = ['=== DESVÍOS POR FASE ===', '', '', '', '', '', '', '', '', '', ''];
- $rows[] = ['ID', 'Fase', 'Inicio Plan', 'Fin Plan', 'Inicio Real', 'Fin Real',
- 'Desvío Inicio (d)', 'Desvío Fin (d)', 'Prog. Plan (%)', 'Prog. Real (%)',
- 'Desvío Prog.', 'SPI', 'En Plazo'];
-
+ $rows[] = ['ID', 'Fase', 'Inicio Plan', 'Fin Plan', 'Inicio Real', 'Fin Real',
+ 'Desvío Inicio (d)', 'Desvío Fin (d)', 'Prog. Plan (%)', 'Prog. Real (%)',
+ 'Desvío Prog.', 'SPI', 'En Plazo'];
+
foreach ($deviations['phases'] as $phase) {
$rows[] = [
$phase['id'], $phase['name'], $phase['planned_start'], $phase['planned_end'],
@@ -475,12 +479,12 @@ class DeviationsSheet extends BaseSheet
}
// Feature deviations
- if (!empty($deviations['features'])) {
+ if (! empty($deviations['features'])) {
$rows[] = ['=== DESVÍOS POR ELEMENTO ===', '', '', '', '', '', '', '', '', '', '', '', ''];
- $rows[] = ['ID', 'Elemento', 'Fase', 'Inicio Plan', 'Fin Plan', 'Inicio Real', 'Fin Real',
- 'Desvío Inicio (d)', 'Desvío Fin (d)', 'Prog. Plan (%)', 'Prog. Real (%)',
- 'Desvío Prog.', 'SPI', 'En Plazo', 'Responsable'];
-
+ $rows[] = ['ID', 'Elemento', 'Fase', 'Inicio Plan', 'Fin Plan', 'Inicio Real', 'Fin Real',
+ 'Desvío Inicio (d)', 'Desvío Fin (d)', 'Prog. Plan (%)', 'Prog. Real (%)',
+ 'Desvío Prog.', 'SPI', 'En Plazo', 'Responsable'];
+
foreach ($deviations['features'] as $feature) {
$rows[] = [
$feature['id'], $feature['name'], $feature['phase'],
@@ -497,7 +501,7 @@ class DeviationsSheet extends BaseSheet
}
// Summary
- if (!empty($deviations['summary'])) {
+ if (! empty($deviations['summary'])) {
$rows[] = ['=== RESUMEN DESVÍOS ===', ''];
$rows[] = ['Fases retrasadas', $deviations['summary']['phases_delayed'] ?? 0];
$rows[] = ['Fases adelantadas', $deviations['summary']['phases_early'] ?? 0];
@@ -522,9 +526,9 @@ class DeviationsSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 25, 'B' => 25, 'C' => 20, 'D' => 12, 'E' => 12, 'F' => 12,
- 'G' => 12, 'H' => 14, 'I' => 14, 'J' => 14, 'K' => 14, 'L' => 10,
- 'M' => 10, 'N' => 20];
+ return ['A' => 25, 'B' => 25, 'C' => 20, 'D' => 12, 'E' => 12, 'F' => 12,
+ 'G' => 12, 'H' => 14, 'I' => 14, 'J' => 14, 'K' => 14, 'L' => 10,
+ 'M' => 10, 'N' => 20];
}
}
@@ -558,8 +562,8 @@ class MediaSheet extends BaseSheet
public function columnWidths(): array
{
- return ['A' => 8, 'B' => 30, 'C' => 12, 'D' => 15, 'E' => 30, 'F' => 12,
- 'G' => 20, 'H' => 18, 'I' => 50];
+ return ['A' => 8, 'B' => 30, 'C' => 12, 'D' => 15, 'E' => 30, 'F' => 12,
+ 'G' => 20, 'H' => 18, 'I' => 50];
}
}
@@ -574,17 +578,17 @@ class ProgressCurveSheet extends BaseSheet
public function __construct(array $curveData)
{
$this->curveData = $curveData;
-
+
$rows = [['Fecha', 'Progreso Planificado (%)', 'Progreso Real (%)']];
-
+
$labels = $curveData['labels'] ?? [];
$planned = $curveData['planned'] ?? [];
$actual = $curveData['actual'] ?? [];
-
+
for ($i = 0; $i < count($labels); $i++) {
$rows[] = [$labels[$i], $planned[$i] ?? 0, $actual[$i] ?? 0];
}
-
+
$this->rows = $rows;
}
@@ -611,13 +615,14 @@ class ProgressCurveSheet extends BaseSheet
class ParametersSheet extends BaseSheet
{
protected ReportFilters $filters;
+
protected Project $project;
public function __construct(ReportFilters $filters, Project $project)
{
$this->filters = $filters;
$this->project = $project;
-
+
$rows = [
['Parámetro', 'Valor'],
['Proyecto', $project->name],
@@ -625,7 +630,7 @@ class ParametersSheet extends BaseSheet
['Fecha desde', $filters->dateFrom?->format('d/m/Y') ?? '—'],
['Fecha hasta', $filters->dateTo?->format('d/m/Y') ?? '—'],
['Entidades incluidas', implode(', ', array_map(
- fn($e) => $filters->getAvailableEntities()[$e] ?? $e,
+ fn ($e) => $filters->getAvailableEntities()[$e] ?? $e,
$filters->entityTypes
))],
['Incluir fotos', $filters->includePhotos ? 'Sí' : 'No'],
@@ -634,7 +639,7 @@ class ParametersSheet extends BaseSheet
['Generado', now()->format('d/m/Y H:i')],
['Generado por', auth()->guard()->user()?->name ?? 'Sistema'],
];
-
+
parent::__construct($rows);
}
@@ -652,4 +657,4 @@ class ParametersSheet extends BaseSheet
{
return ['A' => 30, 'B' => 50];
}
-}
\ No newline at end of file
+}
diff --git a/app/Exports/ProjectsExport.php b/app/Exports/ProjectsExport.php
index 3ad7ad6..d9e8caa 100644
--- a/app/Exports/ProjectsExport.php
+++ b/app/Exports/ProjectsExport.php
@@ -18,7 +18,7 @@ class ProjectsExport implements FromCollection, WithHeadings
'end_date',
'status',
'created_at',
- 'updated_at'
+ 'updated_at',
])->get();
}
@@ -32,7 +32,7 @@ class ProjectsExport implements FromCollection, WithHeadings
'Fecha de fin',
'Estado',
'Creado el',
- 'Actualizado el'
+ 'Actualizado el',
];
}
}
diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php
index dcf6e5f..039579b 100644
--- a/app/Http/Controllers/Api/V1/AuthController.php
+++ b/app/Http/Controllers/Api/V1/AuthController.php
@@ -17,8 +17,8 @@ class AuthController extends Controller
public function login(Request $request)
{
$data = $request->validate([
- 'email' => ['required', 'email'],
- 'password' => ['required', 'string'],
+ 'email' => ['required', 'email'],
+ 'password' => ['required', 'string'],
'device_name' => ['required', 'string', 'max:255'],
'app_version' => ['nullable', 'string', 'max:50'],
]);
@@ -39,15 +39,15 @@ class AuthController extends Controller
Device::updateOrCreate(
['user_id' => $user->id, 'name' => $data['device_name']],
[
- 'token_id' => $token->accessToken->id,
- 'app_version' => $data['app_version'] ?? null,
+ 'token_id' => $token->accessToken->id,
+ 'app_version' => $data['app_version'] ?? null,
'last_seen_at' => now(),
]
);
return response()->json([
'token' => $token->plainTextToken,
- 'user' => $this->userPayload($user),
+ 'user' => $this->userPayload($user),
]);
}
@@ -70,10 +70,10 @@ class AuthController extends Controller
private function userPayload(User $user): array
{
return [
- 'id' => $user->id,
- 'name' => $user->name,
- 'email' => $user->email,
- 'roles' => $user->getRoleNames(),
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'email' => $user->email,
+ 'roles' => $user->getRoleNames(),
'permissions' => $user->getAllPermissions()->pluck('name')->values(),
];
}
diff --git a/app/Http/Controllers/Api/V1/MediaController.php b/app/Http/Controllers/Api/V1/MediaController.php
index 77ecca5..439dc30 100644
--- a/app/Http/Controllers/Api/V1/MediaController.php
+++ b/app/Http/Controllers/Api/V1/MediaController.php
@@ -13,31 +13,31 @@ use App\Models\Phase;
use App\Models\Project;
use App\Models\User;
use Illuminate\Http\Request;
-use Illuminate\Validation\Rule;
use Illuminate\Support\Str;
+use Illuminate\Validation\Rule;
class MediaController extends Controller
{
private array $map = [
- 'feature' => Feature::class,
- 'issue' => Issue::class,
- 'issue_task' => IssueTask::class,
+ 'feature' => Feature::class,
+ 'issue' => Issue::class,
+ 'issue_task' => IssueTask::class,
'issue_comment' => IssueComment::class,
- 'project' => Project::class,
- 'phase' => Phase::class,
- 'layer' => Layer::class,
+ 'project' => Project::class,
+ 'phase' => Phase::class,
+ 'layer' => Layer::class,
];
/** Upload a file (multipart) and attach it to a parent record. Idempotent by uuid. */
public function upload(Request $request)
{
$data = $request->validate([
- 'uuid' => ['required', 'uuid'],
+ 'uuid' => ['required', 'uuid'],
'parent_entity' => ['required', Rule::in(array_keys($this->map))],
- 'parent_id' => ['required', 'integer'],
- 'file' => ['required', 'file', 'max:20480'], // 20 MB
- 'category' => ['nullable', 'in:image,document,other'],
- 'description' => ['nullable', 'string'],
+ 'parent_id' => ['required', 'integer'],
+ 'file' => ['required', 'file', 'max:20480'], // 20 MB
+ 'category' => ['nullable', 'in:image,document,other'],
+ 'description' => ['nullable', 'string'],
]);
// Idempotency: same uuid already uploaded → return it.
@@ -59,15 +59,15 @@ class MediaController extends Controller
$mime = $file->getClientMimeType();
$media = $parent->media()->create([
- 'uuid' => $data['uuid'],
- 'name' => $file->getClientOriginalName(),
- 'file_path' => $path,
- 'file_type' => $mime,
- 'file_extension' => $file->getClientOriginalExtension(),
- 'file_size' => $file->getSize(),
- 'category' => $data['category'] ?? (Str::startsWith($mime, 'image/') ? 'image' : 'document'),
- 'description' => $data['description'] ?? null,
- 'uploaded_by' => $user->id,
+ 'uuid' => $data['uuid'],
+ 'name' => $file->getClientOriginalName(),
+ 'file_path' => $path,
+ 'file_type' => $mime,
+ 'file_extension' => $file->getClientOriginalExtension(),
+ 'file_size' => $file->getSize(),
+ 'category' => $data['category'] ?? (Str::startsWith($mime, 'image/') ? 'image' : 'document'),
+ 'description' => $data['description'] ?? null,
+ 'uploaded_by' => $user->id,
'client_updated_at' => $request->input('client_updated_at'),
]);
@@ -77,14 +77,14 @@ class MediaController extends Controller
private function projectOf(string $entity, $parent): ?Project
{
return match ($entity) {
- 'project' => $parent,
- 'phase' => $parent->project,
- 'layer' => $parent->phase?->project,
- 'feature' => $parent->layer?->phase?->project,
- 'issue' => $parent->project,
- 'issue_task' => $parent->issue?->project,
+ 'project' => $parent,
+ 'phase' => $parent->project,
+ 'layer' => $parent->phase?->project,
+ 'feature' => $parent->layer?->phase?->project,
+ 'issue' => $parent->project,
+ 'issue_task' => $parent->issue?->project,
'issue_comment' => $parent->issue?->project,
- default => null,
+ default => null,
};
}
@@ -93,6 +93,7 @@ class MediaController extends Controller
if (! $project) {
return false;
}
+
return $user->can('manage all')
|| $project->users()->where('user_id', $user->id)->exists();
}
@@ -100,12 +101,12 @@ class MediaController extends Controller
private function payload(Media $m): array
{
return [
- 'id' => $m->id,
- 'uuid' => $m->uuid,
- 'url' => $m->url,
- 'name' => $m->name,
- 'file_type' => $m->file_type,
- 'category' => $m->category,
+ 'id' => $m->id,
+ 'uuid' => $m->uuid,
+ 'url' => $m->url,
+ 'name' => $m->name,
+ 'file_type' => $m->file_type,
+ 'category' => $m->category,
'updated_at' => $m->updated_at?->toIso8601String(),
];
}
diff --git a/app/Http/Controllers/Api/V1/ProjectApiController.php b/app/Http/Controllers/Api/V1/ProjectApiController.php
index 113757b..ee32c00 100644
--- a/app/Http/Controllers/Api/V1/ProjectApiController.php
+++ b/app/Http/Controllers/Api/V1/ProjectApiController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Feature;
+use App\Models\FeatureType;
use App\Models\Inspection;
use App\Models\InspectionTemplate;
use App\Models\Issue;
@@ -53,35 +54,35 @@ class ProjectApiController extends Controller
$templates = $changed(InspectionTemplate::whereHas('projects', fn ($q) => $q->where('projects.id', $project->id)))->get();
$allIssueIds = Issue::withTrashed()->where('project_id', $project->id)->pluck('id');
- $issueTasks = $changed(IssueTask::whereIn('issue_id', $allIssueIds))->get();
+ $issueTasks = $changed(IssueTask::whereIn('issue_id', $allIssueIds))->get();
$issueComments = $changed(IssueComment::whereIn('issue_id', $allIssueIds))->get();
$featureIds = Feature::whereIn('layer_id', $allLayerIds)->pluck('id');
- $issueIds = Issue::where('project_id', $project->id)->pluck('id');
- $taskIds = IssueTask::whereIn('issue_id', $allIssueIds)->pluck('id');
+ $issueIds = Issue::where('project_id', $project->id)->pluck('id');
+ $taskIds = IssueTask::whereIn('issue_id', $allIssueIds)->pluck('id');
$commentIds = IssueComment::whereIn('issue_id', $allIssueIds)->pluck('id');
$media = $changed(Media::where(function ($q) use ($project, $featureIds, $issueIds, $taskIds, $commentIds) {
$q->where(fn ($w) => $w->where('mediable_type', Project::class)->where('mediable_id', $project->id))
- ->orWhere(fn ($w) => $w->where('mediable_type', Feature::class)->whereIn('mediable_id', $featureIds))
- ->orWhere(fn ($w) => $w->where('mediable_type', Issue::class)->whereIn('mediable_id', $issueIds))
- ->orWhere(fn ($w) => $w->where('mediable_type', IssueTask::class)->whereIn('mediable_id', $taskIds))
- ->orWhere(fn ($w) => $w->where('mediable_type', IssueComment::class)->whereIn('mediable_id', $commentIds));
+ ->orWhere(fn ($w) => $w->where('mediable_type', Feature::class)->whereIn('mediable_id', $featureIds))
+ ->orWhere(fn ($w) => $w->where('mediable_type', Issue::class)->whereIn('mediable_id', $issueIds))
+ ->orWhere(fn ($w) => $w->where('mediable_type', IssueTask::class)->whereIn('mediable_id', $taskIds))
+ ->orWhere(fn ($w) => $w->where('mediable_type', IssueComment::class)->whereIn('mediable_id', $commentIds));
}))->get();
return response()->json([
- 'server_time' => now()->toIso8601String(),
- 'project' => $this->mapProject($project),
- 'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(),
- 'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(),
- 'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(),
- 'feature_types' => \App\Models\FeatureType::orderBy('name')->get(['id', 'name', 'color'])->values(),
- 'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(),
- 'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(),
- 'issue_tasks' => $issueTasks->map(fn ($t) => $this->mapIssueTask($t))->values(),
+ 'server_time' => now()->toIso8601String(),
+ 'project' => $this->mapProject($project),
+ 'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(),
+ 'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(),
+ 'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(),
+ 'feature_types' => FeatureType::orderBy('name')->get(['id', 'name', 'color'])->values(),
+ 'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(),
+ 'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(),
+ 'issue_tasks' => $issueTasks->map(fn ($t) => $this->mapIssueTask($t))->values(),
'issue_comments' => $issueComments->map(fn ($c) => $this->mapIssueComment($c))->values(),
- 'templates' => $templates->map(fn ($t) => $this->mapTemplate($t))->values(),
- 'media' => $media->map(fn ($m) => $this->mapMedia($m))->values(),
- 'deleted' => $since ? $this->tombstones($since, $project, $allPhaseIds, $allLayerIds, $allIssueIds) : (object) [],
+ 'templates' => $templates->map(fn ($t) => $this->mapTemplate($t))->values(),
+ 'media' => $media->map(fn ($m) => $this->mapMedia($m))->values(),
+ 'deleted' => $since ? $this->tombstones($since, $project, $allPhaseIds, $allLayerIds, $allIssueIds) : (object) [],
]);
}
@@ -118,12 +119,12 @@ class ProjectApiController extends Controller
private function tombstones(Carbon $since, Project $project, $allPhaseIds, $allLayerIds, $allIssueIds): array
{
return [
- 'phases' => Phase::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
- 'layers' => Layer::onlyTrashed()->whereIn('phase_id', $allPhaseIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
- 'features' => Feature::onlyTrashed()->whereIn('layer_id', $allLayerIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
- 'inspections' => Inspection::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
- 'issues' => Issue::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
- 'issue_tasks' => IssueTask::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
+ 'phases' => Phase::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
+ 'layers' => Layer::onlyTrashed()->whereIn('phase_id', $allPhaseIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
+ 'features' => Feature::onlyTrashed()->whereIn('layer_id', $allLayerIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
+ 'inspections' => Inspection::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
+ 'issues' => Issue::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
+ 'issue_tasks' => IssueTask::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
'issue_comments' => IssueComment::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
];
}
@@ -209,7 +210,7 @@ class ProjectApiController extends Controller
'id' => $t->id, 'project_id' => $t->project_id, 'phase_id' => $t->phase_id,
'name' => $t->name, 'description' => $t->description, 'fields' => $t->fields,
'version' => $t->updated_at?->timestamp,
- 'hash' => md5(json_encode($t->fields) . $t->name),
+ 'hash' => md5(json_encode($t->fields).$t->name),
'updated_at' => $t->updated_at?->toIso8601String(),
];
}
@@ -217,10 +218,10 @@ class ProjectApiController extends Controller
private function mapMedia(Media $m): array
{
$entity = [
- Project::class => 'project',
- Feature::class => 'feature',
- Issue::class => 'issue',
- IssueTask::class => 'issue_task',
+ Project::class => 'project',
+ Feature::class => 'feature',
+ Issue::class => 'issue',
+ IssueTask::class => 'issue_task',
IssueComment::class => 'issue_comment',
][$m->mediable_type] ?? class_basename($m->mediable_type);
@@ -232,4 +233,3 @@ class ProjectApiController extends Controller
];
}
}
-
diff --git a/app/Http/Controllers/Api/V1/SyncController.php b/app/Http/Controllers/Api/V1/SyncController.php
index ea4a011..0781a2b 100644
--- a/app/Http/Controllers/Api/V1/SyncController.php
+++ b/app/Http/Controllers/Api/V1/SyncController.php
@@ -30,12 +30,12 @@ class SyncController extends Controller
public function sync(Request $request)
{
$data = $request->validate([
- 'operations' => ['required', 'array'],
- 'operations.*.entity' => ['required', 'string'],
- 'operations.*.op' => ['required', 'string'],
- 'operations.*.uuid' => ['required', 'uuid'],
- 'operations.*.data' => ['required', 'array'],
- 'operations.*.client_updated_at' => ['nullable', 'date'],
+ 'operations' => ['required', 'array'],
+ 'operations.*.entity' => ['required', 'string'],
+ 'operations.*.op' => ['required', 'string'],
+ 'operations.*.uuid' => ['required', 'uuid'],
+ 'operations.*.data' => ['required', 'array'],
+ 'operations.*.client_updated_at' => ['nullable', 'date'],
]);
$user = $request->user();
@@ -74,16 +74,16 @@ class SyncController extends Controller
}
try {
- $result = match ($op['entity'] . '.' . $op['op']) {
+ $result = match ($op['entity'].'.'.$op['op']) {
'progress_update.create' => $this->progressUpdateCreate($user, $uuid, $op),
- 'inspection.create' => $this->inspectionCreate($user, $uuid, $op),
- 'issue.create' => $this->issueCreate($user, $uuid, $op),
- 'issue.update' => $this->issueUpdate($user, $uuid, $op),
- 'issue_task.create' => $this->issueTaskCreate($user, $uuid, $op),
- 'issue_task.update' => $this->issueTaskUpdate($user, $uuid, $op),
- 'issue_comment.create' => $this->issueCommentCreate($user, $uuid, $op),
- 'feature.update' => $this->featureUpdate($user, $uuid, $op),
- default => $this->error($uuid, 'unsupported entity/op: ' . $op['entity'] . '.' . $op['op']),
+ 'inspection.create' => $this->inspectionCreate($user, $uuid, $op),
+ 'issue.create' => $this->issueCreate($user, $uuid, $op),
+ 'issue.update' => $this->issueUpdate($user, $uuid, $op),
+ 'issue_task.create' => $this->issueTaskCreate($user, $uuid, $op),
+ 'issue_task.update' => $this->issueTaskUpdate($user, $uuid, $op),
+ 'issue_comment.create' => $this->issueCommentCreate($user, $uuid, $op),
+ 'feature.update' => $this->featureUpdate($user, $uuid, $op),
+ default => $this->error($uuid, 'unsupported entity/op: '.$op['entity'].'.'.$op['op']),
};
} catch (\Throwable $e) {
$result = $this->error($uuid, $e->getMessage());
@@ -92,11 +92,11 @@ class SyncController extends Controller
// Record only terminal successes so conflicts/errors can be safely retried.
if ($result['status'] === 'applied') {
SyncLog::create([
- 'user_id' => $user->id,
- 'op_uuid' => $uuid,
- 'entity' => $op['entity'],
- 'op' => $op['op'],
- 'status' => 'applied',
+ 'user_id' => $user->id,
+ 'op_uuid' => $uuid,
+ 'entity' => $op['entity'],
+ 'op' => $op['op'],
+ 'status' => 'applied',
'server_id' => $result['server_id'] ?? null,
]);
}
@@ -115,11 +115,11 @@ class SyncController extends Controller
$v = Validator::make($op['data'], [
'phase_id' => ['required', 'integer', 'exists:phases,id'],
'progress' => ['required', 'integer', 'min:0', 'max:100'],
- 'comment' => ['nullable', 'string'],
+ 'comment' => ['nullable', 'string'],
'location' => ['nullable', 'array'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -129,12 +129,12 @@ class SyncController extends Controller
}
$pu = ProgressUpdate::create([
- 'uuid' => $uuid,
- 'phase_id' => $phase->id,
- 'user_id' => $user->id,
- 'progress_percent' => $d['progress'],
- 'comment' => $d['comment'] ?? null,
- 'location' => $d['location'] ?? null,
+ 'uuid' => $uuid,
+ 'phase_id' => $phase->id,
+ 'user_id' => $user->id,
+ 'progress_percent' => $d['progress'],
+ 'comment' => $d['comment'] ?? null,
+ 'location' => $d['location'] ?? null,
'client_updated_at' => $op['client_updated_at'] ?? null,
]);
@@ -153,15 +153,15 @@ class SyncController extends Controller
}
$v = Validator::make($op['data'], [
- 'feature_id' => ['required', 'integer', 'exists:features,id'],
+ 'feature_id' => ['required', 'integer', 'exists:features,id'],
'template_id' => ['nullable', 'integer', 'exists:inspection_templates,id'],
- 'data' => ['nullable', 'array'],
- 'status' => ['nullable', 'string'],
- 'result' => ['nullable', 'string'],
- 'notes' => ['nullable', 'string'],
+ 'data' => ['nullable', 'array'],
+ 'status' => ['nullable', 'string'],
+ 'result' => ['nullable', 'string'],
+ 'notes' => ['nullable', 'string'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -172,16 +172,16 @@ class SyncController extends Controller
}
$inspection = Inspection::create([
- 'uuid' => $uuid,
- 'project_id' => $project->id,
- 'layer_id' => $feature->layer_id,
- 'feature_id' => $feature->id,
- 'template_id' => $d['template_id'] ?? null,
- 'user_id' => $user->id,
- 'data' => $d['data'] ?? [],
- 'status' => $d['status'] ?? 'completed',
- 'result' => $d['result'] ?? null,
- 'notes' => $d['notes'] ?? null,
+ 'uuid' => $uuid,
+ 'project_id' => $project->id,
+ 'layer_id' => $feature->layer_id,
+ 'feature_id' => $feature->id,
+ 'template_id' => $d['template_id'] ?? null,
+ 'user_id' => $user->id,
+ 'data' => $d['data'] ?? [],
+ 'status' => $d['status'] ?? 'completed',
+ 'result' => $d['result'] ?? null,
+ 'notes' => $d['notes'] ?? null,
'client_updated_at' => $op['client_updated_at'] ?? null,
]);
@@ -197,16 +197,16 @@ class SyncController extends Controller
}
$v = Validator::make($op['data'], [
- 'project_id' => ['required', 'integer', 'exists:projects,id'],
- 'feature_id' => ['nullable', 'integer', 'exists:features,id'],
- 'title' => ['required', 'string', 'max:255'],
+ 'project_id' => ['required', 'integer', 'exists:projects,id'],
+ 'feature_id' => ['nullable', 'integer', 'exists:features,id'],
+ 'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
- 'priority' => ['nullable', 'in:' . implode(',', Issue::PRIORITIES)],
- 'status' => ['nullable', 'in:' . implode(',', Issue::STATUSES)],
- 'type' => ['nullable', 'in:' . implode(',', Issue::TYPES)],
+ 'priority' => ['nullable', 'in:'.implode(',', Issue::PRIORITIES)],
+ 'status' => ['nullable', 'in:'.implode(',', Issue::STATUSES)],
+ 'type' => ['nullable', 'in:'.implode(',', Issue::TYPES)],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -216,15 +216,15 @@ class SyncController extends Controller
}
$issue = Issue::create([
- 'uuid' => $uuid,
- 'project_id' => $project->id,
- 'feature_id' => $d['feature_id'] ?? null,
- 'title' => $d['title'],
- 'description' => $d['description'] ?? null,
- 'priority' => $d['priority'] ?? 'medium',
- 'status' => $d['status'] ?? 'open',
- 'type' => $d['type'] ?? 'other',
- 'reported_by' => $user->id,
+ 'uuid' => $uuid,
+ 'project_id' => $project->id,
+ 'feature_id' => $d['feature_id'] ?? null,
+ 'title' => $d['title'],
+ 'description' => $d['description'] ?? null,
+ 'priority' => $d['priority'] ?? 'medium',
+ 'status' => $d['status'] ?? 'open',
+ 'type' => $d['type'] ?? 'other',
+ 'reported_by' => $user->id,
'client_updated_at' => $op['client_updated_at'] ?? null,
]);
@@ -234,17 +234,17 @@ class SyncController extends Controller
private function issueUpdate(User $user, string $uuid, array $op): array
{
$v = Validator::make($op['data'], [
- 'id' => ['required', 'integer', 'exists:issues,id'],
- 'title' => ['nullable', 'string', 'max:255'],
- 'description' => ['nullable', 'string'],
- 'priority' => ['nullable', 'in:' . implode(',', Issue::PRIORITIES)],
- 'status' => ['nullable', 'in:' . implode(',', Issue::STATUSES)],
- 'type' => ['nullable', 'in:' . implode(',', Issue::TYPES)],
- 'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
+ 'id' => ['required', 'integer', 'exists:issues,id'],
+ 'title' => ['nullable', 'string', 'max:255'],
+ 'description' => ['nullable', 'string'],
+ 'priority' => ['nullable', 'in:'.implode(',', Issue::PRIORITIES)],
+ 'status' => ['nullable', 'in:'.implode(',', Issue::STATUSES)],
+ 'type' => ['nullable', 'in:'.implode(',', Issue::TYPES)],
+ 'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
'resolution_notes' => ['nullable', 'string'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -276,14 +276,14 @@ class SyncController extends Controller
}
$v = Validator::make($op['data'], [
- 'issue_id' => ['required', 'integer', 'exists:issues,id'],
- 'title' => ['required', 'string', 'max:255'],
+ 'issue_id' => ['required', 'integer', 'exists:issues,id'],
+ 'title' => ['required', 'string', 'max:255'],
'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
- 'due_date' => ['nullable', 'date'],
- 'is_done' => ['nullable', 'boolean'],
+ 'due_date' => ['nullable', 'date'],
+ 'is_done' => ['nullable', 'boolean'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -294,15 +294,15 @@ class SyncController extends Controller
$done = $d['is_done'] ?? false;
$task = IssueTask::create([
- 'uuid' => $uuid,
- 'issue_id' => $issue->id,
- 'title' => $d['title'],
- 'assigned_to' => $d['assigned_to'] ?? null,
- 'due_date' => $d['due_date'] ?? null,
- 'is_done' => $done,
- 'done_at' => $done ? now() : null,
- 'done_by' => $done ? $user->id : null,
- 'order' => ((int) $issue->tasks()->max('order')) + 1,
+ 'uuid' => $uuid,
+ 'issue_id' => $issue->id,
+ 'title' => $d['title'],
+ 'assigned_to' => $d['assigned_to'] ?? null,
+ 'due_date' => $d['due_date'] ?? null,
+ 'is_done' => $done,
+ 'done_at' => $done ? now() : null,
+ 'done_by' => $done ? $user->id : null,
+ 'order' => ((int) $issue->tasks()->max('order')) + 1,
'client_updated_at' => $op['client_updated_at'] ?? null,
]);
@@ -312,14 +312,14 @@ class SyncController extends Controller
private function issueTaskUpdate(User $user, string $uuid, array $op): array
{
$v = Validator::make($op['data'], [
- 'id' => ['required', 'integer', 'exists:issue_tasks,id'],
- 'title' => ['nullable', 'string', 'max:255'],
+ 'id' => ['required', 'integer', 'exists:issue_tasks,id'],
+ 'title' => ['nullable', 'string', 'max:255'],
'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
- 'due_date' => ['nullable', 'date'],
- 'is_done' => ['nullable', 'boolean'],
+ 'due_date' => ['nullable', 'date'],
+ 'is_done' => ['nullable', 'boolean'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -354,10 +354,10 @@ class SyncController extends Controller
$v = Validator::make($op['data'], [
'issue_id' => ['required', 'integer', 'exists:issues,id'],
- 'body' => ['required', 'string', 'max:5000'],
+ 'body' => ['required', 'string', 'max:5000'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -367,10 +367,10 @@ class SyncController extends Controller
}
$comment = IssueComment::create([
- 'uuid' => $uuid,
- 'issue_id' => $issue->id,
- 'user_id' => $user->id,
- 'body' => $d['body'],
+ 'uuid' => $uuid,
+ 'issue_id' => $issue->id,
+ 'user_id' => $user->id,
+ 'body' => $d['body'],
'client_updated_at' => $op['client_updated_at'] ?? null,
]);
@@ -382,15 +382,15 @@ class SyncController extends Controller
private function featureUpdate(User $user, string $uuid, array $op): array
{
$v = Validator::make($op['data'], [
- 'id' => ['required', 'integer', 'exists:features,id'],
- 'status' => ['nullable', 'string'],
- 'progress' => ['nullable', 'integer', 'min:0', 'max:100'],
- 'responsible' => ['nullable', 'string'],
- 'is_active' => ['nullable', 'boolean'],
+ 'id' => ['required', 'integer', 'exists:features,id'],
+ 'status' => ['nullable', 'string'],
+ 'progress' => ['nullable', 'integer', 'min:0', 'max:100'],
+ 'responsible' => ['nullable', 'string'],
+ 'is_active' => ['nullable', 'boolean'],
'feature_type_id' => ['nullable', 'integer', 'exists:feature_types,id'],
]);
if ($v->fails()) {
- return $this->error($uuid, 'validation: ' . $v->errors()->first());
+ return $this->error($uuid, 'validation: '.$v->errors()->first());
}
$d = $v->validated();
@@ -424,6 +424,7 @@ class SyncController extends Controller
if (! $project) {
return false;
}
+
return $user->can('manage all')
|| $project->users()->where('user_id', $user->id)->exists();
}
@@ -440,11 +441,12 @@ class SyncController extends Controller
$clientAt = Carbon::parse($op['client_updated_at']);
if ($model->updated_at && $model->updated_at->gt($clientAt)) {
return [
- 'uuid' => $uuid,
+ 'uuid' => $uuid,
'status' => 'conflict',
'server' => $model->fresh()->toArray(),
];
}
+
return null;
}
diff --git a/app/Http/Controllers/FeaturesController.php b/app/Http/Controllers/FeaturesController.php
deleted file mode 100644
index 3856c59..0000000
--- a/app/Http/Controllers/FeaturesController.php
+++ /dev/null
@@ -1,65 +0,0 @@
-validate([
'name' => 'required|string|max:255',
- 'email' => 'required|email|max:255|unique:users,email,' . $user->id,
+ 'email' => 'required|email|max:255|unique:users,email,'.$user->id,
]);
$user->update($validated);
@@ -52,4 +50,4 @@ class ProfileController extends Controller
return redirect('/');
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php
index 5cb882e..2009d85 100644
--- a/app/Http/Controllers/ProjectController.php
+++ b/app/Http/Controllers/ProjectController.php
@@ -3,7 +3,6 @@
namespace App\Http\Controllers;
use App\Models\Project;
-use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
@@ -16,6 +15,7 @@ class ProjectController extends Controller
public function index()
{
Gate::authorize('view projects');
+
return view('projects.index');
}
@@ -37,6 +37,7 @@ class ProjectController extends Controller
// Assign creator as supervisor in project
$project->users()->attach(Auth::id(), ['role_in_project' => 'supervisor']);
+
return redirect()->route('projects.map', $project)->with('success', 'Proyecto creado');
}
@@ -94,4 +95,4 @@ class ProjectController extends Controller
// (lo validaremos dentro del componente Livewire)
return view('projects.map', compact('project'));
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/ProjectReportController.php b/app/Http/Controllers/ProjectReportController.php
index 5f23411..d8a210d 100644
--- a/app/Http/Controllers/ProjectReportController.php
+++ b/app/Http/Controllers/ProjectReportController.php
@@ -1,8 +1,10 @@
can('manage all') && !$project->users()->where('user_id', $user->id)->exists()) {
+ if (! $user->can('manage all') && ! $project->users()->where('user_id', $user->id)->exists()) {
abort(403);
}
@@ -20,11 +22,11 @@ class ProjectReportController extends Controller
->get();
$stats = [
- 'total_features' => $phases->flatMap(fn($p) => $p->layers)->flatMap(fn($l) => $l->features)->count(),
- 'completed_features' => $phases->flatMap(fn($p) => $p->layers)->flatMap(fn($l) => $l->features)->where('status', 'completed')->count(),
- 'total_inspections' => \App\Models\Inspection::where('project_id', $project->id)->count(),
- 'open_issues' => \App\Models\Issue::where('project_id', $project->id)->where('status', 'open')->count(),
- 'avg_progress' => round($phases->avg('progress_percent') ?? 0),
+ 'total_features' => $phases->flatMap(fn ($p) => $p->layers)->flatMap(fn ($l) => $l->features)->count(),
+ 'completed_features' => $phases->flatMap(fn ($p) => $p->layers)->flatMap(fn ($l) => $l->features)->where('status', 'completed')->count(),
+ 'total_inspections' => Inspection::where('project_id', $project->id)->count(),
+ 'open_issues' => Issue::where('project_id', $project->id)->where('status', 'open')->count(),
+ 'avg_progress' => round($phases->avg('progress_percent') ?? 0),
];
$pdf_data = compact('project', 'phases', 'stats');
diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php
index 95a07a3..c03b03c 100644
--- a/app/Http/Controllers/ReportController.php
+++ b/app/Http/Controllers/ReportController.php
@@ -3,12 +3,12 @@
namespace App\Http\Controllers;
use App\DTO\ReportFilters;
+use App\Exports\ProjectReportExport;
use App\Models\Project;
use App\Services\ReportGenerator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Facades\Excel;
-use App\Exports\ProjectReportExport;
class ReportController extends Controller
{
@@ -19,7 +19,7 @@ class ReportController extends Controller
{
$this->authorizeProjectAccess($project);
- return \Livewire\Livewire::mount(\App\Livewire\Reports\ReportBuilder::class, [
+ return view('reports.builder', [
'project' => $project,
]);
}
@@ -65,8 +65,8 @@ class ReportController extends Controller
protected function downloadExcel(Project $project, ReportFilters $filters, array $data)
{
$export = new ProjectReportExport($project, $filters, $data);
- $filename = 'informe_' . $project->name . '_' . now()->format('Ymd_His') . '.xlsx';
-
+ $filename = 'informe_'.$project->name.'_'.now()->format('Ymd_His').'.xlsx';
+
return Excel::download($export, $filename);
}
@@ -76,13 +76,13 @@ class ReportController extends Controller
protected function authorizeProjectAccess(Project $project): void
{
$user = Auth::user();
-
+
if ($user->can('manage all')) {
return;
}
- if (!$project->users()->where('user_id', $user->id)->exists()) {
+ if (! $project->users()->where('user_id', $user->id)->exists()) {
abort(403, 'No tienes acceso a este proyecto.');
}
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/Reports/ExportController.php b/app/Http/Controllers/Reports/ExportController.php
index 8f66457..db1f97c 100644
--- a/app/Http/Controllers/Reports/ExportController.php
+++ b/app/Http/Controllers/Reports/ExportController.php
@@ -2,15 +2,12 @@
namespace App\Http\Controllers\Reports;
-use App\Http\Controllers\Controller;
-use App\Models\Project;
-use App\Models\Phase;
-use App\Models\Inspection;
-use Maatwebsite\Excel\Facades\Excel;
-use App\Exports\ProjectsExport;
-use App\Exports\PhasesExport;
use App\Exports\InspectionsExport;
+use App\Exports\PhasesExport;
+use App\Exports\ProjectsExport;
+use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
+use Maatwebsite\Excel\Facades\Excel;
class ExportController extends Controller
{
diff --git a/app/Http/Middleware/BindProjectModel.php b/app/Http/Middleware/BindProjectModel.php
index 1efef77..966a7ec 100644
--- a/app/Http/Middleware/BindProjectModel.php
+++ b/app/Http/Middleware/BindProjectModel.php
@@ -2,9 +2,9 @@
namespace App\Http\Middleware;
+use App\Models\Project;
use Closure;
use Illuminate\Http\Request;
-use App\Models\Project;
class BindProjectModel
{
@@ -18,6 +18,7 @@ class BindProjectModel
$route->setParameter('project', $project);
}
}
+
return $next($request);
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php
index 3c4547e..9505fb5 100644
--- a/app/Http/Middleware/SetLocale.php
+++ b/app/Http/Middleware/SetLocale.php
@@ -27,7 +27,7 @@ class SetLocale
}
// 2. From session
- if (!$locale && Session::has('locale')) {
+ if (! $locale && Session::has('locale')) {
$sessionLocale = Session::get('locale');
if (in_array($sessionLocale, $allowedLocales)) {
$locale = $sessionLocale;
@@ -35,7 +35,7 @@ class SetLocale
}
// 3. From browser Accept-Language
- if (!$locale) {
+ if (! $locale) {
$browserLang = substr($request->server('HTTP_ACCEPT_LANGUAGE', 'en'), 0, 2);
if (in_array($browserLang, $allowedLocales)) {
$locale = $browserLang;
@@ -43,7 +43,7 @@ class SetLocale
}
// 4. Default to app locale
- if (!$locale) {
+ if (! $locale) {
$locale = config('app.locale', 'en');
}
@@ -52,4 +52,4 @@ class SetLocale
return $next($request);
}
-}
\ No newline at end of file
+}
diff --git a/app/Jobs/CaptureDailyProgressSnapshot.php b/app/Jobs/CaptureDailyProgressSnapshot.php
index a635f7b..e07e06d 100644
--- a/app/Jobs/CaptureDailyProgressSnapshot.php
+++ b/app/Jobs/CaptureDailyProgressSnapshot.php
@@ -22,7 +22,7 @@ class CaptureDailyProgressSnapshot implements ShouldQueue
public function handle(): void
{
$date = $this->snapshotDate ? Carbon::parse($this->snapshotDate) : Carbon::today();
-
+
Log::info('Starting daily progress snapshot capture', ['date' => $date->toDateString()]);
$captured = 0;
@@ -45,7 +45,7 @@ class CaptureDailyProgressSnapshot implements ShouldQueue
private function capturePhaseSnapshots(Carbon $date): int
{
$count = 0;
-
+
Phase::with(['project'])->chunkById(100, function ($phases) use ($date, &$count) {
foreach ($phases as $phase) {
// Skip if snapshot already exists for this date
@@ -87,7 +87,7 @@ class CaptureDailyProgressSnapshot implements ShouldQueue
private function captureFeatureSnapshots(Carbon $date): int
{
$count = 0;
-
+
Feature::with(['layer.phase.project'])->chunkById(200, function ($features) use ($date, &$count) {
foreach ($features as $feature) {
if (ProgressSnapshot::where('trackable_type', Feature::class)
@@ -131,7 +131,7 @@ class CaptureDailyProgressSnapshot implements ShouldQueue
private function captureTaskSnapshots(Carbon $date): int
{
$count = 0;
-
+
Task::with(['project', 'phase'])->chunkById(200, function ($tasks) use ($date, &$count) {
foreach ($tasks as $task) {
if (ProgressSnapshot::where('trackable_type', Task::class)
@@ -171,4 +171,4 @@ class CaptureDailyProgressSnapshot implements ShouldQueue
return $count;
}
-}
\ No newline at end of file
+}
diff --git a/app/Livewire/Admin/RoleForm.php b/app/Livewire/Admin/RoleForm.php
index 43c5649..e0254a4 100644
--- a/app/Livewire/Admin/RoleForm.php
+++ b/app/Livewire/Admin/RoleForm.php
@@ -2,9 +2,9 @@
namespace App\Livewire\Admin;
-use Livewire\Component;
-use Livewire\Attributes\Layout;
use Illuminate\Support\Facades\Auth;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
@@ -14,9 +14,11 @@ class RoleForm extends Component
public ?Role $role = null;
public string $name = '';
+
public string $description = '';
private const PROTECTED_ROLES = ['Admin'];
+
private const CORE_PERMISSION = 'manage all';
public function mount(?Role $role = null): void
@@ -24,8 +26,8 @@ class RoleForm extends Component
abort_unless(Auth::user()?->can('manage roles'), 403);
if ($role && $role->exists) {
- $this->role = $role;
- $this->name = $role->name;
+ $this->role = $role;
+ $this->name = $role->name;
$this->description = $role->description ?? '';
}
}
@@ -33,7 +35,7 @@ class RoleForm extends Component
public function save()
{
$this->validate([
- 'name' => 'required|string|max:50|unique:roles,name' . ($this->role ? ',' . $this->role->id : ''),
+ 'name' => 'required|string|max:50|unique:roles,name'.($this->role ? ','.$this->role->id : ''),
'description' => 'nullable|string|max:255',
], [], ['name' => 'nombre', 'description' => 'descripción']);
@@ -46,7 +48,7 @@ class RoleForm extends Component
$this->role->save();
} else {
Role::create([
- 'name' => $this->name,
+ 'name' => $this->name,
'description' => $this->description ?: null,
]);
}
diff --git a/app/Livewire/Admin/RolePermissionManager.php b/app/Livewire/Admin/RolePermissionManager.php
index 109432f..055575c 100644
--- a/app/Livewire/Admin/RolePermissionManager.php
+++ b/app/Livewire/Admin/RolePermissionManager.php
@@ -2,21 +2,23 @@
namespace App\Livewire\Admin;
-use Livewire\Component;
-use Livewire\Attributes\Layout;
use Illuminate\Support\Facades\Auth;
-use Spatie\Permission\Models\Role;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
use Spatie\Permission\Models\Permission;
+use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
#[Layout('layouts.app')]
class RolePermissionManager extends Component
{
public string $newRole = '';
+
public string $newPermission = '';
/** Roles that must not be deleted or stripped of core powers. */
private const PROTECTED_ROLES = ['Admin'];
+
private const CORE_PERMISSION = 'manage all';
public function mount(): void
@@ -36,7 +38,8 @@ class RolePermissionManager extends Component
if ($role->hasPermissionTo($permissionName)) {
// Admin must always keep the core permission
if ($role->name === 'Admin' && $permissionName === self::CORE_PERMISSION) {
- $this->dispatch('notify', "El rol Admin no puede perder '" . self::CORE_PERMISSION . "'.");
+ $this->dispatch('notify', "El rol Admin no puede perder '".self::CORE_PERMISSION."'.");
+
return;
}
$role->revokePermissionTo($permissionName);
@@ -66,6 +69,7 @@ class RolePermissionManager extends Component
if (in_array($role->name, self::PROTECTED_ROLES, true)) {
$this->dispatch('notify', "El rol '{$role->name}' está protegido y no se puede borrar.");
+
return;
}
@@ -91,7 +95,8 @@ class RolePermissionManager extends Component
$permission = Permission::findOrFail($permissionId);
if ($permission->name === self::CORE_PERMISSION) {
- $this->dispatch('notify', "El permiso '" . self::CORE_PERMISSION . "' está protegido y no se puede borrar.");
+ $this->dispatch('notify', "El permiso '".self::CORE_PERMISSION."' está protegido y no se puede borrar.");
+
return;
}
@@ -103,7 +108,7 @@ class RolePermissionManager extends Component
public function render()
{
return view('livewire.roles.role-permission-manager', [
- 'roles' => Role::with('permissions')->orderBy('name')->get(),
+ 'roles' => Role::with('permissions')->orderBy('name')->get(),
'permissions' => Permission::orderBy('name')->get(),
]);
}
diff --git a/app/Livewire/Admin/RoleTable.php b/app/Livewire/Admin/RoleTable.php
index 7e1e443..be6da0b 100644
--- a/app/Livewire/Admin/RoleTable.php
+++ b/app/Livewire/Admin/RoleTable.php
@@ -2,9 +2,9 @@
namespace App\Livewire\Admin;
+use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
-use Illuminate\Database\Eloquent\Builder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
@@ -17,8 +17,8 @@ class RoleTable extends DataTableComponent
public function configure(): void
{
$this->setPrimaryKey('id')
- ->setDefaultSort('name', 'asc')
- ->setSortingPillsEnabled(false);
+ ->setDefaultSort('name', 'asc')
+ ->setSortingPillsEnabled(false);
}
public function builder(): Builder
@@ -30,48 +30,48 @@ class RoleTable extends DataTableComponent
{
return [
Column::make(__('Name'), 'name')
- ->sortable()
- ->searchable()
- ->format(fn ($value, $row) =>
- ''.e($value).''
- . (in_array($row->name, self::PROTECTED_ROLES, true) ? ' protegido' : '')
- )
- ->html(),
+ ->sortable()
+ ->searchable()
+ ->format(fn ($value, $row) => ''.e($value).''
+ .(in_array($row->name, self::PROTECTED_ROLES, true) ? ' protegido' : '')
+ )
+ ->html(),
Column::make(__('Description'), 'description')
- ->sortable()
- ->searchable()
- ->format(fn ($value) => $value
- ? ''.e($value).''
- : '—')
- ->html(),
+ ->sortable()
+ ->searchable()
+ ->format(fn ($value) => $value
+ ? ''.e($value).''
+ : '—')
+ ->html(),
Column::make(__('Permissions'))
- ->label(fn ($row) => ''.(int) $row->permissions_count.'')
- ->html(),
+ ->label(fn ($row) => ''.(int) $row->permissions_count.'')
+ ->html(),
Column::make(__('Users'))
- ->label(fn ($row) => ''.(int) $row->users_count.'')
- ->html(),
+ ->label(fn ($row) => ''.(int) $row->users_count.'')
+ ->html(),
Column::make(__('Actions'))
- ->label(function ($row) {
- $show = route('admin.roles.show', $row->id);
- $edit = route('admin.roles.edit', $row->id);
- $eye = '';
- $pencil = '';
- $trash = '';
+ ->label(function ($row) {
+ $show = route('admin.roles.show', $row->id);
+ $edit = route('admin.roles.edit', $row->id);
+ $eye = '';
+ $pencil = '';
+ $trash = '';
- $html = '
';
- $html .= '
'.$eye.'';
- $html .= '
'.$pencil.'';
- if (! in_array($row->name, self::PROTECTED_ROLES, true)) {
- $html .= '
';
- }
- $html .= '
';
- return $html;
- })
- ->html(),
+ $html = '';
+ $html .= '
'.$eye.'';
+ $html .= '
'.$pencil.'';
+ if (! in_array($row->name, self::PROTECTED_ROLES, true)) {
+ $html .= '
';
+ }
+ $html .= '
';
+
+ return $html;
+ })
+ ->html(),
];
}
@@ -84,7 +84,9 @@ class RoleTable extends DataTableComponent
{
$roles = Role::whereIn('id', $this->selected)->get();
foreach ($roles as $role) {
- if (in_array($role->name, self::PROTECTED_ROLES, true)) continue;
+ if (in_array($role->name, self::PROTECTED_ROLES, true)) {
+ continue;
+ }
$role->delete();
}
$this->clearSelected();
diff --git a/app/Livewire/Admin/RoleView.php b/app/Livewire/Admin/RoleView.php
index 5010288..871fdc3 100644
--- a/app/Livewire/Admin/RoleView.php
+++ b/app/Livewire/Admin/RoleView.php
@@ -2,23 +2,26 @@
namespace App\Livewire\Admin;
-use Livewire\Component;
-use Livewire\Attributes\Layout;
+use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
-use App\Models\User;
-use Spatie\Permission\Models\Role;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
use Spatie\Permission\Models\Permission;
+use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
#[Layout('layouts.app')]
class RoleView extends Component
{
public Role $role;
+
public string $tab = 'ficha'; // ficha | permisos
+
public $newUserId = '';
private const PROTECTED_ROLES = ['Admin'];
+
private const CORE_PERMISSION = 'manage all';
public function mount(Role $role): void
@@ -38,7 +41,8 @@ class RoleView extends Component
if ($this->role->name === 'Admin'
&& $permissionName === self::CORE_PERMISSION
&& $this->role->hasPermissionTo($permissionName)) {
- $this->dispatch('notify', "El rol Admin no puede perder '" . self::CORE_PERMISSION . "'.");
+ $this->dispatch('notify', "El rol Admin no puede perder '".self::CORE_PERMISSION."'.");
+
return;
}
@@ -91,6 +95,7 @@ class RoleView extends Component
{
if (in_array($this->role->name, self::PROTECTED_ROLES, true)) {
$this->dispatch('notify', "El rol '{$this->role->name}' está protegido y no se puede borrar.");
+
return;
}
$this->role->delete();
@@ -107,6 +112,7 @@ class RoleView extends Component
return 'General';
}
$resource = Str::afterLast($name, ' ');
+
return Str::headline($resource ?: 'General');
}
@@ -126,6 +132,7 @@ class RoleView extends Component
->groupBy(fn ($perm) => $perm->group ?: $this->sectionFor($perm->name))
->sortBy(function ($perms, $section) use ($order) {
$i = array_search($section, $order, true);
+
return $i === false ? 999 : $i;
});
@@ -133,11 +140,11 @@ class RoleView extends Component
->orderBy('first_name')->orderBy('name')->get();
return view('livewire.roles.role-view', [
- 'users' => $users,
+ 'users' => $users,
'availableUsers' => $availableUsers,
- 'grouped' => $grouped,
- 'rolePerms' => $this->role->permissions->pluck('name')->toArray(),
- 'isProtected' => in_array($this->role->name, self::PROTECTED_ROLES, true),
+ 'grouped' => $grouped,
+ 'rolePerms' => $this->role->permissions->pluck('name')->toArray(),
+ 'isProtected' => in_array($this->role->name, self::PROTECTED_ROLES, true),
]);
}
}
diff --git a/app/Livewire/Client/ClientProjects.php b/app/Livewire/Client/ClientProjects.php
index 1f24371..8789b93 100644
--- a/app/Livewire/Client/ClientProjects.php
+++ b/app/Livewire/Client/ClientProjects.php
@@ -2,20 +2,20 @@
namespace App\Livewire\Client;
-use Livewire\Component;
-use App\Models\Project;
-use App\Models\Phase;
-use App\Models\Inspection;
-use App\Models\Feature;
use App\Models\ChangeOrder;
-use Carbon\Carbon;
+use App\Models\Project;
+use Livewire\Component;
class ClientProjects extends Component
{
public $projects = [];
+
public $selectedProject = null;
+
public $projectDetails = [];
+
public $galleryImages = [];
+
public $changeOrders = [];
public function mount()
@@ -29,7 +29,7 @@ class ClientProjects extends Component
$user = auth()->user();
$this->projects = $user->projects()
->wherePivot('role_in_project', 'client')
- ->with(['phases' => function($query) {
+ ->with(['phases' => function ($query) {
$query->select('id', 'project_id', 'name', 'progress_percent');
}])
->get()
@@ -44,17 +44,17 @@ class ClientProjects extends Component
public function loadProjectDetails()
{
- if (!$this->selectedProject) {
+ if (! $this->selectedProject) {
return;
}
$project = Project::with([
'phases.features',
'inspections.template',
- 'changeOrders' // Load change orders for this project
+ 'changeOrders', // Load change orders for this project
])->find($this->selectedProject);
- if (!$project) {
+ if (! $project) {
return;
}
@@ -75,11 +75,11 @@ class ClientProjects extends Component
->latest()
->take(3)
->get()
- ->map(function($media) {
+ ->map(function ($media) {
return [
'url' => $media->url,
'title' => $media->name,
- 'date' => $media->created_at->format('d/m/Y')
+ 'date' => $media->created_at->format('d/m/Y'),
];
})
->toArray();
@@ -93,18 +93,18 @@ class ClientProjects extends Component
[
'url' => 'https://via.placeholder.com/400x300?text=Avance+1',
'title' => 'Avance inicial',
- 'date' => now()->subDays(30)->format('d/m/Y')
+ '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')
+ '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')
- ]
+ 'date' => now()->subDays(5)->format('d/m/Y'),
+ ],
];
}
@@ -112,14 +112,14 @@ class ClientProjects extends Component
$this->changeOrders = $project->changeOrders
->orderBy('requested_at', 'desc')
->get()
- ->map(function($order) {
+ ->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
+ 'amount' => $order->amount,
];
})
->toArray();
diff --git a/app/Livewire/Common/LanguageSwitcher.php b/app/Livewire/Common/LanguageSwitcher.php
index 8e26307..72924b7 100644
--- a/app/Livewire/Common/LanguageSwitcher.php
+++ b/app/Livewire/Common/LanguageSwitcher.php
@@ -2,10 +2,10 @@
namespace App\Livewire\Common;
-use Livewire\Component;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
+use Livewire\Component;
class LanguageSwitcher extends Component
{
@@ -26,7 +26,7 @@ class LanguageSwitcher extends Component
public function updatedCurrentLocale(string $locale): void
{
- if (!in_array($locale, ['en', 'es', 'fr', 'ru'])) {
+ if (! in_array($locale, ['en', 'es', 'fr', 'ru'])) {
return;
}
@@ -51,4 +51,4 @@ class LanguageSwitcher extends Component
{
return view('livewire.common.language-switcher');
}
-}
\ No newline at end of file
+}
diff --git a/app/Livewire/Common/NotificationBell.php b/app/Livewire/Common/NotificationBell.php
index a4dd1af..bd9a87e 100644
--- a/app/Livewire/Common/NotificationBell.php
+++ b/app/Livewire/Common/NotificationBell.php
@@ -2,13 +2,15 @@
namespace App\Livewire\Common;
-use Livewire\Component;
use Illuminate\Support\Facades\Auth;
+use Livewire\Component;
class NotificationBell extends Component
{
public $notifications = [];
+
public $unreadCount = 0;
+
public $showDropdown = false;
public function mount()
diff --git a/app/Livewire/Companies/CompanyForm.php b/app/Livewire/Companies/CompanyForm.php
index d9c3970..25ef4c3 100644
--- a/app/Livewire/Companies/CompanyForm.php
+++ b/app/Livewire/Companies/CompanyForm.php
@@ -2,11 +2,11 @@
namespace App\Livewire\Companies;
-use Livewire\Component;
-use Livewire\WithFileUploads;
-use Livewire\Attributes\Layout;
use App\Models\Company;
use Illuminate\Support\Facades\Storage;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
+use Livewire\WithFileUploads;
#[Layout('layouts.app')]
class CompanyForm extends Component
@@ -16,50 +16,61 @@ class CompanyForm extends Component
public ?Company $company = null;
// Form fields
- public string $name = '';
- public string $apodo = '';
- public string $tax_id = '';
- public string $estado = 'activo';
- public string $type = 'other';
+ public string $name = '';
+
+ public string $apodo = '';
+
+ public string $tax_id = '';
+
+ public string $estado = 'activo';
+
+ public string $type = 'other';
+
public string $address = '';
- public string $phone = '';
- public string $email = '';
+
+ public string $phone = '';
+
+ public string $email = '';
+
public string $website = '';
- public string $notes = '';
+
+ public string $notes = '';
+
public $logo = null;
public function mount(?Company $company = null): void
{
if ($company && $company->exists) {
$this->company = $company;
- $this->name = $company->name;
- $this->apodo = $company->apodo ?? '';
- $this->tax_id = $company->tax_id ?? '';
- $this->estado = $company->estado ?? 'activo';
- $this->type = $company->type ?? 'other';
+ $this->name = $company->name;
+ $this->apodo = $company->apodo ?? '';
+ $this->tax_id = $company->tax_id ?? '';
+ $this->estado = $company->estado ?? 'activo';
+ $this->type = $company->type ?? 'other';
$this->address = $company->address ?? '';
- $this->phone = $company->phone ?? '';
- $this->email = $company->email ?? '';
+ $this->phone = $company->phone ?? '';
+ $this->email = $company->email ?? '';
$this->website = $company->website ?? '';
- $this->notes = $company->notes ?? '';
+ $this->notes = $company->notes ?? '';
}
}
protected function rules(): array
{
$id = $this->company?->id ?? 'NULL';
+
return [
- 'name' => 'required|string|max:255',
- 'apodo' => 'nullable|string|max:100',
- 'tax_id' => "nullable|string|max:50|unique:companies,tax_id,{$id}",
- 'estado' => 'required|in:activo,inactivo,suspendido',
- 'type' => 'required|in:owner,constructor,subcontractor,consultant,supplier,other',
+ 'name' => 'required|string|max:255',
+ 'apodo' => 'nullable|string|max:100',
+ 'tax_id' => "nullable|string|max:50|unique:companies,tax_id,{$id}",
+ 'estado' => 'required|in:activo,inactivo,suspendido',
+ 'type' => 'required|in:owner,constructor,subcontractor,consultant,supplier,other',
'address' => 'nullable|string',
- 'phone' => 'nullable|string|max:30',
- 'email' => 'nullable|email|max:255',
+ 'phone' => 'nullable|string|max:30',
+ 'email' => 'nullable|email|max:255',
'website' => 'nullable|url|max:255',
- 'notes' => 'nullable|string',
- 'logo' => 'nullable|image|max:2048',
+ 'notes' => 'nullable|string',
+ 'logo' => 'nullable|image|max:2048',
];
}
@@ -68,16 +79,16 @@ class CompanyForm extends Component
$this->validate();
$data = [
- 'name' => $this->name,
- 'apodo' => $this->apodo ?: null,
- 'tax_id' => $this->tax_id ?: null,
- 'estado' => $this->estado,
- 'type' => $this->type,
+ 'name' => $this->name,
+ 'apodo' => $this->apodo ?: null,
+ 'tax_id' => $this->tax_id ?: null,
+ 'estado' => $this->estado,
+ 'type' => $this->type,
'address' => $this->address ?: null,
- 'phone' => $this->phone ?: null,
- 'email' => $this->email ?: null,
+ 'phone' => $this->phone ?: null,
+ 'email' => $this->email ?: null,
'website' => $this->website ?: null,
- 'notes' => $this->notes ?: null,
+ 'notes' => $this->notes ?: null,
];
if ($this->logo) {
diff --git a/app/Livewire/Companies/CompanyManagement.php b/app/Livewire/Companies/CompanyManagement.php
index ec44bc7..eecbd49 100644
--- a/app/Livewire/Companies/CompanyManagement.php
+++ b/app/Livewire/Companies/CompanyManagement.php
@@ -2,29 +2,31 @@
namespace App\Livewire\Companies;
-use Livewire\Component;
-use Livewire\Attributes\Layout;
use App\Models\Company;
use Illuminate\Support\Facades\Storage;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
#[Layout('layouts.app')]
class CompanyManagement extends Component
{
- public string $search = '';
- public string $filterType = '';
+ public string $search = '';
+
+ public string $filterType = '';
+
public string $filterEstado = '';
public function getCompaniesProperty()
{
return Company::when($this->search, function ($q) {
- $s = '%' . $this->search . '%';
- $q->where(fn($q2) => $q2
- ->where('name', 'like', $s)
- ->orWhere('apodo', 'like', $s)
- ->orWhere('tax_id', 'like', $s));
- })
- ->when($this->filterType, fn($q) => $q->where('type', $this->filterType))
- ->when($this->filterEstado, fn($q) => $q->where('estado', $this->filterEstado))
+ $s = '%'.$this->search.'%';
+ $q->where(fn ($q2) => $q2
+ ->where('name', 'like', $s)
+ ->orWhere('apodo', 'like', $s)
+ ->orWhere('tax_id', 'like', $s));
+ })
+ ->when($this->filterType, fn ($q) => $q->where('type', $this->filterType))
+ ->when($this->filterEstado, fn ($q) => $q->where('estado', $this->filterEstado))
->withCount('projects')
->orderBy('name')
->get();
@@ -45,7 +47,7 @@ class CompanyManagement extends Component
return response()->streamDownload(function () use ($companies) {
$handle = fopen('php://output', 'w');
- fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
+ fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($handle, ['Nombre', 'Apodo', 'NIF/Tax ID', 'Tipo', 'Estado', 'Dirección', 'Teléfono', 'Email', 'Website', 'Proyectos', 'Creación']);
foreach ($companies as $c) {
fputcsv($handle, [
diff --git a/app/Livewire/Companies/CompanyTable.php b/app/Livewire/Companies/CompanyTable.php
index 695583f..0447530 100644
--- a/app/Livewire/Companies/CompanyTable.php
+++ b/app/Livewire/Companies/CompanyTable.php
@@ -2,13 +2,12 @@
namespace App\Livewire\Companies;
+use App\Models\Company;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Facades\Storage;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;
-use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Storage;
-use App\Models\Company;
class CompanyTable extends DataTableComponent
{
@@ -17,17 +16,17 @@ class CompanyTable extends DataTableComponent
public function configure(): void
{
$this->setPrimaryKey('id')
- ->setDefaultSort('name', 'asc')
- ->setSortingPillsEnabled(false)
- ->setAdditionalSelects([
- 'companies.id as id',
- 'companies.apodo as apodo',
- 'companies.tax_id as tax_id',
- 'companies.phone as phone',
- 'companies.email as email',
- 'companies.logo_path as logo_path',
- 'companies.created_at as created_at',
- ]);
+ ->setDefaultSort('name', 'asc')
+ ->setSortingPillsEnabled(false)
+ ->setAdditionalSelects([
+ 'companies.id as id',
+ 'companies.apodo as apodo',
+ 'companies.tax_id as tax_id',
+ 'companies.phone as phone',
+ 'companies.email as email',
+ 'companies.logo_path as logo_path',
+ 'companies.created_at as created_at',
+ ]);
}
public function builder(): Builder
@@ -39,100 +38,108 @@ class CompanyTable extends DataTableComponent
{
return [
Column::make('Empresa', 'name')
- ->sortable()
- ->searchable()
- ->format(function ($value, $row) {
- $logoHtml = '';
- if ($row->logo_path && Storage::disk('public')->exists($row->logo_path)) {
- $url = Storage::disk('public')->url($row->logo_path);
- $logoHtml = '
';
- } else {
- $logoHtml = '
+ ->sortable()
+ ->searchable()
+ ->format(function ($value, $row) {
+ $logoHtml = '';
+ if ($row->logo_path && Storage::disk('public')->exists($row->logo_path)) {
+ $url = Storage::disk('public')->url($row->logo_path);
+ $logoHtml = '
.')
';
+ } else {
+ $logoHtml = '
';
- }
- $html = '
'.$logoHtml.'
';
- $html .= '
'.e($value).'
';
- if ($row->apodo) $html .= '
'.e($row->apodo).'
';
- if ($row->tax_id) $html .= '
NIF: '.e($row->tax_id).'
';
- $html .= '
';
- return $html;
- })
- ->html(),
+ }
+ $html = '
'.$logoHtml.'
';
+ $html .= '
'.e($value).'
';
+ if ($row->apodo) {
+ $html .= '
'.e($row->apodo).'
';
+ }
+ if ($row->tax_id) {
+ $html .= '
NIF: '.e($row->tax_id).'
';
+ }
+ $html .= '
';
+
+ return $html;
+ })
+ ->html(),
Column::make('Tipo', 'type')
- ->sortable()
- ->format(function ($value) {
- $map = [
- 'owner' => ['badge-success', 'Promotor'],
- 'constructor' => ['badge-primary', 'Constructor'],
- 'subcontractor' => ['badge-secondary', 'Subcontratista'],
- 'consultant' => ['badge-info', 'Consultor'],
- 'supplier' => ['badge-warning', 'Proveedor'],
- ];
- [$cls, $label] = $map[$value] ?? ['badge-ghost', 'Otro'];
- return '
'.$label.'';
- })
- ->html(),
+ ->sortable()
+ ->format(function ($value) {
+ $map = [
+ 'owner' => ['badge-success', 'Promotor'],
+ 'constructor' => ['badge-primary', 'Constructor'],
+ 'subcontractor' => ['badge-secondary', 'Subcontratista'],
+ 'consultant' => ['badge-info', 'Consultor'],
+ 'supplier' => ['badge-warning', 'Proveedor'],
+ ];
+ [$cls, $label] = $map[$value] ?? ['badge-ghost', 'Otro'];
+
+ return '
'.$label.'';
+ })
+ ->html(),
Column::make('Contacto', 'phone')
- ->format(function ($value, $row) {
- $html = '';
- if ($row->phone) {
- $html .= '
+ ->format(function ($value, $row) {
+ $html = '';
+ if ($row->phone) {
+ $html .= '
';
- }
- if ($row->email) {
- $html .= '
+ }
+ if ($row->email) {
+ $html .= '
';
- }
- return $html ?: '
—';
- })
- ->html(),
+ }
+
+ return $html ?: '
—';
+ })
+ ->html(),
Column::make('Estado', 'estado')
- ->sortable()
- ->format(function ($value) {
- $map = [
- 'activo' => ['badge-success', 'Activo'],
- 'inactivo' => ['badge-ghost', 'Inactivo'],
- 'suspendido' => ['badge-error', 'Suspendido'],
- ];
- [$cls, $label] = $map[$value ?? 'activo'] ?? ['badge-ghost', ucfirst($value ?? 'activo')];
- return '
'.$label.'';
- })
- ->html(),
+ ->sortable()
+ ->format(function ($value) {
+ $map = [
+ 'activo' => ['badge-success', 'Activo'],
+ 'inactivo' => ['badge-ghost', 'Inactivo'],
+ 'suspendido' => ['badge-error', 'Suspendido'],
+ ];
+ [$cls, $label] = $map[$value ?? 'activo'] ?? ['badge-ghost', ucfirst($value ?? 'activo')];
+
+ return '
'.$label.'';
+ })
+ ->html(),
Column::make('Proyectos')
- ->label(fn ($row) =>
- '
'.(int)($row->projects_count ?? 0).''
- )
- ->html(),
+ ->label(fn ($row) => '
'.(int) ($row->projects_count ?? 0).''
+ )
+ ->html(),
Column::make('Acciones')
- ->label(function ($row) {
- $ver = route('companies.show', $row->id);
- $editar = route('companies.edit', $row->id);
- $name = addslashes($row->name);
+ ->label(function ($row) {
+ $ver = route('companies.show', $row->id);
+ $editar = route('companies.edit', $row->id);
+ $name = addslashes($row->name);
- $html = '
';
+
+ return $html;
+ })
+ ->html(),
];
}
@@ -141,21 +148,21 @@ class CompanyTable extends DataTableComponent
return [
SelectFilter::make('Tipo', 'type')
->options([
- '' => 'Tipo: todos',
- 'owner' => 'Promotor',
- 'constructor' => 'Constructor',
+ '' => 'Tipo: todos',
+ 'owner' => 'Promotor',
+ 'constructor' => 'Constructor',
'subcontractor' => 'Subcontratista',
- 'consultant' => 'Consultor',
- 'supplier' => 'Proveedor',
- 'other' => 'Otro',
+ 'consultant' => 'Consultor',
+ 'supplier' => 'Proveedor',
+ 'other' => 'Otro',
])
->filter(fn (Builder $query, string $value) => $query->where('type', $value)),
SelectFilter::make('Estado', 'estado')
->options([
- '' => 'Estado: todos',
- 'activo' => 'Activo',
- 'inactivo' => 'Inactivo',
+ '' => 'Estado: todos',
+ 'activo' => 'Activo',
+ 'inactivo' => 'Inactivo',
'suspendido' => 'Suspendido',
])
->filter(fn (Builder $query, string $value) => $query->where('estado', $value)),
diff --git a/app/Livewire/Companies/CompanyView.php b/app/Livewire/Companies/CompanyView.php
index 51ba887..38d62a4 100644
--- a/app/Livewire/Companies/CompanyView.php
+++ b/app/Livewire/Companies/CompanyView.php
@@ -2,45 +2,53 @@
namespace App\Livewire\Companies;
-use Livewire\Component;
-use Livewire\Attributes\Layout;
use App\Models\Company;
+use App\Models\Issue;
use App\Models\Project;
use App\Models\User;
-use App\Models\Issue;
use Illuminate\Support\Facades\Auth;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
#[Layout('layouts.app')]
class CompanyView extends Component
{
public Company $company;
- public string $activeTab = 'summary';
+
+ public string $activeTab = 'summary';
// Projects tab
- public ?int $addProjectId = null;
- public string $addProjectRole = '';
+ public ?int $addProjectId = null;
+
+ public string $addProjectRole = '';
+
public $availableProjects;
// People tab
- public ?int $assignUserId = null;
+ public ?int $assignUserId = null;
+
public $assignableUsers;
// Notes tab
- public string $notes = '';
- public bool $editingNotes = false;
+ public string $notes = '';
+
+ public bool $editingNotes = false;
// Stats (computed once in mount, refreshed on mutations)
- public int $usersCount = 0;
- public int $projectsCount = 0;
- public float $avgProgress = 0.0;
- public int $openIssues = 0;
+ public int $usersCount = 0;
+
+ public int $projectsCount = 0;
+
+ public float $avgProgress = 0.0;
+
+ public int $openIssues = 0;
public function mount(Company $company): void
{
abort_unless(Auth::user()->can('view companies'), 403);
$this->company = $company->load(['users.roles', 'projects.phases']);
- $this->notes = $company->notes ?? '';
+ $this->notes = $company->notes ?? '';
$this->loadAvailableProjects();
$this->loadAssignableUsers();
@@ -60,16 +68,16 @@ class CompanyView extends Component
{
$this->assignableUsers = User::where(function ($q) {
$q->where('company_id', '!=', $this->company->id)
- ->orWhereNull('company_id');
+ ->orWhereNull('company_id');
})->orderBy('name')->get();
}
private function computeStats(): void
{
- $this->usersCount = $this->company->users->count();
+ $this->usersCount = $this->company->users->count();
$this->projectsCount = $this->company->projects->count();
- $this->avgProgress = round(
- $this->company->projects->flatMap(fn($p) => $p->phases)->avg('progress_percent') ?? 0
+ $this->avgProgress = round(
+ $this->company->projects->flatMap(fn ($p) => $p->phases)->avg('progress_percent') ?? 0
);
$userIds = $this->company->users->pluck('id');
$this->openIssues = $userIds->isNotEmpty()
@@ -89,7 +97,7 @@ class CompanyView extends Component
public function assignProject(): void
{
$this->validate([
- 'addProjectId' => 'required|exists:projects,id',
+ 'addProjectId' => 'required|exists:projects,id',
'addProjectRole' => 'required|string|max:150',
], [], ['addProjectId' => 'proyecto', 'addProjectRole' => 'rol en proyecto']);
@@ -98,7 +106,7 @@ class CompanyView extends Component
]);
$this->company->load('projects.phases');
- $this->addProjectId = null;
+ $this->addProjectId = null;
$this->addProjectRole = '';
$this->loadAvailableProjects();
$this->computeStats();
diff --git a/app/Livewire/Inspections/GlobalTemplateManager.php b/app/Livewire/Inspections/GlobalTemplateManager.php
index f6db27e..653b631 100644
--- a/app/Livewire/Inspections/GlobalTemplateManager.php
+++ b/app/Livewire/Inspections/GlobalTemplateManager.php
@@ -18,29 +18,35 @@ class GlobalTemplateManager extends Component
public $templates;
public $editingTemplate = null;
+
public $showForm = false;
+
public $form = [
- 'name' => '',
+ 'name' => '',
'description' => '',
- 'fields' => [],
+ 'fields' => [],
];
// ── Importar desde CSV/Excel ───────────────────────────────────────────
- public $showImportFileModal = false;
- public $importFile = null;
- public $importPreviewFields = [];
- public $importTemplateName = '';
- public $importError = '';
+ public $showImportFileModal = false;
+
+ public $importFile = null;
+
+ public $importPreviewFields = [];
+
+ public $importTemplateName = '';
+
+ public $importError = '';
public $fieldTypes = [
- 'text' => 'Texto corto',
- 'textarea' => 'Texto largo',
- 'integer' => 'Número entero',
- 'decimal' => 'Número decimal',
+ 'text' => 'Texto corto',
+ 'textarea' => 'Texto largo',
+ 'integer' => 'Número entero',
+ 'decimal' => 'Número decimal',
'percentage' => 'Porcentaje (0-100)',
- 'boolean' => 'Sí/No (checkbox)',
- 'date' => 'Fecha',
- 'select' => 'Lista desplegable',
+ 'boolean' => 'Sí/No (checkbox)',
+ 'date' => 'Fecha',
+ 'select' => 'Lista desplegable',
];
public function mount()
@@ -66,9 +72,9 @@ class GlobalTemplateManager extends Component
{
$template = InspectionTemplate::findOrFail($id);
$this->form = [
- 'name' => $template->name,
+ 'name' => $template->name,
'description' => $template->description ?? '',
- 'fields' => $template->fields ?? [],
+ 'fields' => $template->fields ?? [],
];
$this->editingTemplate = $id;
$this->showForm = true;
@@ -83,9 +89,9 @@ class GlobalTemplateManager extends Component
public function resetForm()
{
$this->form = [
- 'name' => '',
+ 'name' => '',
'description' => '',
- 'fields' => [],
+ 'fields' => [],
];
$this->editingTemplate = null;
}
@@ -93,17 +99,17 @@ class GlobalTemplateManager extends Component
public function addField()
{
$this->form['fields'][] = [
- 'group' => '',
- 'name' => '',
- 'label' => '',
+ 'group' => '',
+ 'name' => '',
+ 'label' => '',
'question' => '',
- 'type' => 'text',
- 'options' => '',
+ 'type' => 'text',
+ 'options' => '',
'required' => false,
- 'min' => null,
- 'max' => null,
- 'step' => null,
- 'help' => '',
+ 'min' => null,
+ 'max' => null,
+ 'step' => null,
+ 'help' => '',
];
}
@@ -116,15 +122,15 @@ class GlobalTemplateManager extends Component
public function saveTemplate()
{
$this->validate([
- 'form.name' => 'required|string|max:255',
+ 'form.name' => 'required|string|max:255',
'form.fields' => 'array',
]);
$data = [
- 'name' => $this->form['name'],
+ 'name' => $this->form['name'],
'description' => $this->form['description'],
- 'project_id' => null,
- 'fields' => array_values($this->form['fields']),
+ 'project_id' => null,
+ 'fields' => array_values($this->form['fields']),
];
if ($this->editingTemplate) {
@@ -153,10 +159,10 @@ class GlobalTemplateManager extends Component
public function openImportFileModal()
{
- $this->importFile = null;
+ $this->importFile = null;
$this->importPreviewFields = [];
- $this->importTemplateName = '';
- $this->importError = '';
+ $this->importTemplateName = '';
+ $this->importError = '';
$this->showImportFileModal = true;
}
@@ -164,31 +170,34 @@ class GlobalTemplateManager extends Component
{
$headers = ['Content-Type' => 'text/csv'];
$csv = "\xEF\xBB\xBF" // BOM UTF-8 (para que Excel respete los acentos)
- . "group,name,label,question,type,required,options,min,max,step,help\n"
- . "Dimensiones,altura,Altura (m),¿Cumple la altura de proyecto?,decimal,1,,0,100,0.1,Medir con flexómetro\n"
- . "Dimensiones,material,Material,,select,1,Hormigón|Acero|Madera,,,,\n"
- . "Acabados,ok,¿Acabado correcto?,,boolean,1,,,,,\n";
- return response()->streamDownload(fn () => print($csv), 'plantilla_ejemplo.csv', $headers);
+ ."group,name,label,question,type,required,options,min,max,step,help\n"
+ ."Dimensiones,altura,Altura (m),¿Cumple la altura de proyecto?,decimal,1,,0,100,0.1,Medir con flexómetro\n"
+ ."Dimensiones,material,Material,,select,1,Hormigón|Acero|Madera,,,,\n"
+ ."Acabados,ok,¿Acabado correcto?,,boolean,1,,,,,\n";
+
+ return response()->streamDownload(fn () => print ($csv), 'plantilla_ejemplo.csv', $headers);
}
public function parseImportFile()
{
$this->importError = '';
$this->validate([
- 'importFile' => 'required|file|mimes:csv,txt,xlsx,xls|max:5120',
+ 'importFile' => 'required|file|mimes:csv,txt,xlsx,xls|max:5120',
'importTemplateName' => 'required|string|max:255',
]);
try {
$rows = $this->readFileRows();
} catch (\Throwable $e) {
- $this->importError = 'No se pudo leer el archivo: ' . $e->getMessage();
+ $this->importError = 'No se pudo leer el archivo: '.$e->getMessage();
+
return;
}
$fields = $this->parseRows($rows);
if (empty($fields)) {
$this->importError = 'No se encontraron filas válidas.';
+
return;
}
$this->importPreviewFields = $fields;
@@ -196,19 +205,21 @@ class GlobalTemplateManager extends Component
public function confirmImportFile()
{
- if (empty($this->importPreviewFields) || empty($this->importTemplateName)) return;
+ if (empty($this->importPreviewFields) || empty($this->importTemplateName)) {
+ return;
+ }
InspectionTemplate::create([
- 'name' => $this->importTemplateName,
+ 'name' => $this->importTemplateName,
'description' => 'Importado desde archivo',
- 'project_id' => null,
- 'fields' => array_values($this->importPreviewFields),
+ 'project_id' => null,
+ 'fields' => array_values($this->importPreviewFields),
]);
$this->showImportFileModal = false;
$this->importPreviewFields = [];
- $this->importTemplateName = '';
- $this->importFile = null;
+ $this->importTemplateName = '';
+ $this->importFile = null;
$this->loadTemplates();
$this->dispatch('templates-changed');
$this->dispatch('notify', 'Plantilla importada');
@@ -216,7 +227,7 @@ class GlobalTemplateManager extends Component
private function readFileRows(): array
{
- $ext = strtolower($this->importFile->getClientOriginalExtension());
+ $ext = strtolower($this->importFile->getClientOriginalExtension());
$path = $this->importFile->getRealPath();
// Fila no vacía = tiene al menos una celda con contenido (no filtramos por
@@ -226,21 +237,27 @@ class GlobalTemplateManager extends Component
if ($ext === 'xlsx' || $ext === 'xls') {
$spreadsheet = IOFactory::load($path);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray(null, true, true, false);
+ $sheet = $spreadsheet->getActiveSheet();
+ $rows = $sheet->toArray(null, true, true, false);
array_shift($rows);
+
return array_values(array_filter($rows, $notEmpty));
}
- $rows = [];
+ $rows = [];
$handle = fopen($path, 'r');
$bom = fread($handle, 3);
- if ($bom !== "\xEF\xBB\xBF") rewind($handle);
+ if ($bom !== "\xEF\xBB\xBF") {
+ rewind($handle);
+ }
fgetcsv($handle);
while (($row = fgetcsv($handle)) !== false) {
- if ($notEmpty($row)) $rows[] = $row;
+ if ($notEmpty($row)) {
+ $rows[] = $row;
+ }
}
fclose($handle);
+
return $rows;
}
@@ -250,24 +267,27 @@ class GlobalTemplateManager extends Component
// group, name, label, question, type, required, options, min, max, step, help
$fields = [];
foreach ($rows as $row) {
- $row = array_values((array) $row);
+ $row = array_values((array) $row);
$rawName = trim($row[1] ?? '');
- if ($rawName === '') continue;
+ if ($rawName === '') {
+ continue;
+ }
$fields[] = [
- 'group' => trim($row[0] ?? ''),
- 'name' => $this->slugify($rawName),
- 'label' => trim($row[2] ?? '') ?: $rawName,
+ 'group' => trim($row[0] ?? ''),
+ 'name' => $this->slugify($rawName),
+ 'label' => trim($row[2] ?? '') ?: $rawName,
'question' => trim($row[3] ?? ''),
- 'type' => $this->normalizeType($row[4] ?? 'text'),
+ 'type' => $this->normalizeType($row[4] ?? 'text'),
'required' => in_array(strtolower(trim($row[5] ?? '0')), ['1', 'si', 'sí', 'yes', 'true']),
- 'options' => trim($row[6] ?? ''),
- 'min' => ($row[7] ?? '') !== '' ? $row[7] : null,
- 'max' => ($row[8] ?? '') !== '' ? $row[8] : null,
- 'step' => ($row[9] ?? '') !== '' ? $row[9] : null,
- 'help' => trim($row[10] ?? ''),
+ 'options' => trim($row[6] ?? ''),
+ 'min' => ($row[7] ?? '') !== '' ? $row[7] : null,
+ 'max' => ($row[8] ?? '') !== '' ? $row[8] : null,
+ 'step' => ($row[9] ?? '') !== '' ? $row[9] : null,
+ 'help' => trim($row[10] ?? ''),
];
}
+
return $fields;
}
@@ -276,6 +296,7 @@ class GlobalTemplateManager extends Component
$str = mb_strtolower(trim($str));
$str = preg_replace('/\s+/', '_', $str);
$str = preg_replace('/[^a-z0-9_]/i', '', $str);
+
return trim($str, '_') ?: 'campo';
}
@@ -291,6 +312,7 @@ class GlobalTemplateManager extends Component
'date' => 'date', 'fecha' => 'date',
'select' => 'select', 'lista' => 'select', 'dropdown' => 'select', 'opciones' => 'select',
];
+
return $map[strtolower(trim($type))] ?? 'text';
}
diff --git a/app/Livewire/Inspections/InspectionTemplatesTable.php b/app/Livewire/Inspections/InspectionTemplatesTable.php
index 6dadec3..e0f387f 100644
--- a/app/Livewire/Inspections/InspectionTemplatesTable.php
+++ b/app/Livewire/Inspections/InspectionTemplatesTable.php
@@ -18,13 +18,13 @@ class InspectionTemplatesTable extends DataTableComponent
public function configure(): void
{
$this->setPrimaryKey('id')
- ->setDefaultSort('inspection_templates.name', 'asc')
- ->setSortingPillsEnabled(false)
- ->setSecondaryHeaderEnabled()
- ->setAdditionalSelects([
- 'inspection_templates.id as id',
- 'inspection_templates.fields as fields',
- ]);
+ ->setDefaultSort('inspection_templates.name', 'asc')
+ ->setSortingPillsEnabled(false)
+ ->setSecondaryHeaderEnabled()
+ ->setAdditionalSelects([
+ 'inspection_templates.id as id',
+ 'inspection_templates.fields as fields',
+ ]);
}
/** Refrescar cuando el manager crea/edita/borra. */
@@ -46,47 +46,46 @@ class InspectionTemplatesTable extends DataTableComponent
{
return [
Column::make('Plantilla', 'name')
- ->sortable()->searchable()
- ->secondaryHeaderFilter('name')
- ->format(fn ($value) => '
' . e($value) . '')
- ->html(),
+ ->sortable()->searchable()
+ ->secondaryHeaderFilter('name')
+ ->format(fn ($value) => '
'.e($value).'')
+ ->html(),
Column::make('Descripción', 'description')
- ->searchable()
- ->secondaryHeaderFilter('description')
- ->format(fn ($value) => $value
- ? '
' . e($value) . ''
- : '
—')
- ->html(),
+ ->searchable()
+ ->secondaryHeaderFilter('description')
+ ->format(fn ($value) => $value
+ ? '
'.e($value).''
+ : '
—')
+ ->html(),
Column::make('Campos')
- ->label(fn ($row) =>
- '
' . count($row->fields ?? []) . '')
- ->html(),
+ ->label(fn ($row) => '
'.count($row->fields ?? []).'')
+ ->html(),
Column::make('Proyectos')
- ->secondaryHeaderFilter('usage')
- ->label(function ($row) {
- $n = (int) ($row->projects_count ?? 0);
- $cls = $n > 0 ? 'badge-info' : 'badge-ghost';
- return '
' . $n . '';
- })
- ->html(),
+ ->secondaryHeaderFilter('usage')
+ ->label(function ($row) {
+ $n = (int) ($row->projects_count ?? 0);
+ $cls = $n > 0 ? 'badge-info' : 'badge-ghost';
+
+ return '
'.$n.'';
+ })
+ ->html(),
Column::make('Acciones')
- ->label(fn ($row) =>
- '
-