7d854ffb0a
- Translation system: lang/es/ PHP files (auth, validation, pagination, passwords)
- Rappasoft vendor translations published (lang/vendor/livewire-tables/es/)
- JSON files synced to 391 keys (EN + ES, full parity)
- APP_LOCALE changed to 'es', users.locale column default changed to 'es'
- Language switcher fixed: JS event + window.location.reload() avoids /livewire/update redirect
- SetLocale middleware fallback uses config('app.locale') instead of hardcoded 'en'
- setSortingPillsEnabled(false) on ProjectTable, CompanyTable, UserTable
- Translated 17 blade views: project-map, template-manager, layer-manager,
company-management, phase-list, media-manager, reports-dashboard,
client-projects, layer-upload, project-form, project-map-editor-tab,
admin/users, projects/media, projects/templates, layouts/client
- Navigation 'Empresas' link uses __('Companies')
- Fixed typo key 'Fases and layers' -> 'Phases and layers'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
432 lines
15 KiB
PHP
432 lines
15 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Informe de Proyecto — {{ $project->name }}</title>
|
|
<style>
|
|
/* --------------------------------------------------------
|
|
Base styles
|
|
-------------------------------------------------------- */
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
|
|
font-size: 13px;
|
|
color: #1f2937;
|
|
background: #fff;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
a { color: #2563eb; text-decoration: none; }
|
|
|
|
/* --------------------------------------------------------
|
|
Layout
|
|
-------------------------------------------------------- */
|
|
.page-wrapper {
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
padding: 32px 24px;
|
|
}
|
|
|
|
/* --------------------------------------------------------
|
|
Header
|
|
-------------------------------------------------------- */
|
|
.report-header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
border-bottom: 2px solid #2563eb;
|
|
padding-bottom: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.logo-placeholder {
|
|
width: 64px;
|
|
height: 64px;
|
|
background: #dbeafe;
|
|
border: 2px solid #93c5fd;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 10px;
|
|
color: #2563eb;
|
|
text-align: center;
|
|
font-weight: 600;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.report-header-info {
|
|
flex: 1;
|
|
margin-left: 16px;
|
|
}
|
|
|
|
.report-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: #1e3a8a;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.report-subtitle {
|
|
font-size: 13px;
|
|
color: #6b7280;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.report-meta {
|
|
text-align: right;
|
|
font-size: 12px;
|
|
color: #6b7280;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.report-meta strong { display: block; color: #1f2937; font-size: 13px; }
|
|
|
|
/* --------------------------------------------------------
|
|
Section titles
|
|
-------------------------------------------------------- */
|
|
.section-title {
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: #1e3a8a;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
padding-bottom: 6px;
|
|
margin-bottom: 14px;
|
|
margin-top: 28px;
|
|
}
|
|
|
|
/* --------------------------------------------------------
|
|
Stats summary
|
|
-------------------------------------------------------- */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: #f8fafc;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 12px 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
color: #2563eb;
|
|
line-height: 1;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 11px;
|
|
color: #6b7280;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* --------------------------------------------------------
|
|
Tables
|
|
-------------------------------------------------------- */
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 12px;
|
|
}
|
|
|
|
th {
|
|
background: #f1f5f9;
|
|
text-align: left;
|
|
padding: 7px 10px;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
border-bottom: 1px solid #cbd5e1;
|
|
}
|
|
|
|
td {
|
|
padding: 6px 10px;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
tr:last-child td { border-bottom: none; }
|
|
|
|
tr:hover td { background: #f9fafb; }
|
|
|
|
/* --------------------------------------------------------
|
|
Phase section
|
|
-------------------------------------------------------- */
|
|
.phase-block {
|
|
margin-bottom: 24px;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.phase-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 14px;
|
|
background: #f8fafc;
|
|
border-left: 5px solid #3b82f6;
|
|
}
|
|
|
|
.phase-name {
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
color: #1e3a8a;
|
|
}
|
|
|
|
.phase-progress-bar-wrap {
|
|
background: #e5e7eb;
|
|
border-radius: 6px;
|
|
height: 8px;
|
|
width: 160px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.phase-progress-bar {
|
|
height: 8px;
|
|
border-radius: 6px;
|
|
background: #22c55e;
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.phase-meta {
|
|
font-size: 11px;
|
|
color: #6b7280;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* --------------------------------------------------------
|
|
Status badges
|
|
-------------------------------------------------------- */
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 2px 7px;
|
|
border-radius: 9999px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
line-height: 1.6;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.03em;
|
|
}
|
|
|
|
.badge-planned { background: #f3f4f6; color: #6b7280; }
|
|
.badge-started { background: #dbeafe; color: #1d4ed8; }
|
|
.badge-in_progress { background: #fef3c7; color: #92400e; }
|
|
.badge-completed { background: #d1fae5; color: #065f46; }
|
|
.badge-verified { background: #ede9fe; color: #5b21b6; }
|
|
.badge-default { background: #f3f4f6; color: #6b7280; }
|
|
|
|
/* --------------------------------------------------------
|
|
Print button (hidden on print)
|
|
-------------------------------------------------------- */
|
|
.print-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: #2563eb;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 6px;
|
|
padding: 8px 18px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.print-btn:hover { background: #1d4ed8; }
|
|
|
|
/* --------------------------------------------------------
|
|
Print media rules
|
|
-------------------------------------------------------- */
|
|
@media print {
|
|
.print-btn { display: none !important; }
|
|
|
|
body { font-size: 11px; }
|
|
|
|
.page-wrapper { max-width: 100%; padding: 16px; }
|
|
|
|
.report-header { page-break-inside: avoid; }
|
|
|
|
.phase-block { page-break-inside: avoid; }
|
|
|
|
a { color: inherit; }
|
|
|
|
.stats-grid { grid-template-columns: repeat(5, 1fr); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page-wrapper">
|
|
|
|
{{-- Print button (hidden on print) --}}
|
|
<button class="print-btn" onclick="window.print()">
|
|
🖶 {{ __('Imprimir / Guardar PDF') }}
|
|
</button>
|
|
|
|
{{-- ====================================================
|
|
HEADER
|
|
===================================================== --}}
|
|
<div class="report-header">
|
|
<div class="logo-placeholder">LOGO<br>EMPRESA</div>
|
|
<div class="report-header-info">
|
|
<div class="report-title">{{ $project->name }}</div>
|
|
@if($project->address)
|
|
<div class="report-subtitle">{{ $project->address }}</div>
|
|
@endif
|
|
<div class="report-subtitle" style="margin-top:8px;">
|
|
@if($project->start_date)
|
|
Inicio: <strong style="color:#1f2937">{{ $project->start_date->format('d/m/Y') }}</strong>
|
|
@endif
|
|
@if($project->end_date_estimated)
|
|
• Fin estimado: <strong style="color:#1f2937">{{ $project->end_date_estimated->format('d/m/Y') }}</strong>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="report-meta">
|
|
<strong>Informe de Proyecto</strong>
|
|
Generado el {{ now()->format('d/m/Y H:i') }}<br>
|
|
Estado:
|
|
<span class="badge {{ $project->status === 'completed' ? 'badge-completed' : ($project->status === 'in_progress' ? 'badge-in_progress' : 'badge-planned') }}">
|
|
{{ ucfirst(str_replace('_', ' ', $project->status ?? 'N/A')) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ====================================================
|
|
SUMMARY STATS
|
|
===================================================== --}}
|
|
<div class="section-title">Resumen General</div>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ $stats['total_features'] }}</div>
|
|
<div class="stat-label">Total elementos</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" style="color:#22c55e;">{{ $stats['completed_features'] }}</div>
|
|
<div class="stat-label">Completados</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" style="color:#f59e0b;">{{ $stats['avg_progress'] }}%</div>
|
|
<div class="stat-label">Progreso medio</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" style="color:#6366f1;">{{ $stats['total_inspections'] }}</div>
|
|
<div class="stat-label">Inspecciones</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" style="color:{{ $stats['open_issues'] > 0 ? '#ef4444' : '#22c55e' }};">{{ $stats['open_issues'] }}</div>
|
|
<div class="stat-label">Issues abiertos</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ====================================================
|
|
PHASES
|
|
===================================================== --}}
|
|
<div class="section-title">Detalle por Fase</div>
|
|
|
|
@forelse($phases as $phase)
|
|
@php
|
|
$phaseFeatures = $phase->layers->flatMap(fn($l) => $l->features);
|
|
$phaseColor = $phase->color ?? '#3b82f6';
|
|
@endphp
|
|
<div class="phase-block" style="border-left-color:{{ $phaseColor }};">
|
|
<div class="phase-header" style="border-left-color:{{ $phaseColor }};">
|
|
<div>
|
|
<div class="phase-name">{{ $phase->name }}</div>
|
|
<div class="phase-meta">
|
|
@if($phase->planned_start)
|
|
{{ $phase->planned_start->format('d/m/Y') }}
|
|
—
|
|
{{ $phase->planned_end?->format('d/m/Y') ?? 'Sin fecha fin' }}
|
|
@else
|
|
Sin fechas planificadas
|
|
@endif
|
|
• {{ $phaseFeatures->count() }} elementos
|
|
</div>
|
|
</div>
|
|
<div style="text-align:right;">
|
|
<div style="font-size:16px;font-weight:700;color:{{ $phaseColor }};">{{ $phase->progress_percent ?? 0 }}%</div>
|
|
<div class="phase-progress-bar-wrap" style="margin-top:4px;">
|
|
<div class="phase-progress-bar"
|
|
style="width:{{ min(100, $phase->progress_percent ?? 0) }}%;background:{{ $phaseColor }};"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if($phaseFeatures->count() > 0)
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Elemento</th>
|
|
<th>Estado</th>
|
|
<th>Progreso</th>
|
|
<th>Responsable</th>
|
|
<th>Última inspección</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($phaseFeatures as $feature)
|
|
@php
|
|
$lastInspection = $feature->inspections->sortByDesc('created_at')->first();
|
|
@endphp
|
|
<tr>
|
|
<td>{{ $feature->name ?? 'Sin nombre' }}</td>
|
|
<td>
|
|
<span class="badge badge-{{ $feature->status ?? 'default' }}">
|
|
{{ match($feature->status) {
|
|
'planned' => 'Planificado',
|
|
'started' => 'Iniciado',
|
|
'in_progress' => 'En progreso',
|
|
'completed' => 'Completado',
|
|
'verified' => 'Verificado',
|
|
default => ($feature->status ?? 'N/A'),
|
|
} }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div style="display:flex;align-items:center;gap:6px;">
|
|
<div style="flex:1;background:#e5e7eb;border-radius:4px;height:6px;min-width:60px;">
|
|
<div style="height:6px;border-radius:4px;background:{{ $phaseColor }};width:{{ min(100, $feature->progress ?? 0) }}%;"></div>
|
|
</div>
|
|
<span style="font-size:11px;color:#6b7280;white-space:nowrap;">{{ $feature->progress ?? 0 }}%</span>
|
|
</div>
|
|
</td>
|
|
<td>{{ $feature->responsible ?? ($feature->responsibleUser?->name ?? '—') }}</td>
|
|
<td>{{ $lastInspection?->created_at?->format('d/m/Y') ?? '—' }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
@else
|
|
<div style="padding:12px 14px;color:#9ca3af;font-style:italic;font-size:12px;">
|
|
Sin elementos registrados en esta fase.
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@empty
|
|
<div style="padding:16px;color:#9ca3af;text-align:center;font-style:italic;">
|
|
No hay fases registradas en este proyecto.
|
|
</div>
|
|
@endforelse
|
|
|
|
{{-- ====================================================
|
|
Footer
|
|
===================================================== --}}
|
|
<div style="margin-top:32px;padding-top:12px;border-top:1px solid #e5e7eb;display:flex;justify-content:space-between;font-size:11px;color:#9ca3af;">
|
|
<span>ConstProgress — Sistema de Gestión de Obras</span>
|
|
<span>{{ now()->format('d/m/Y H:i') }}</span>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|