Files
construprogress/resources/views/reports/project-report.blade.php
T
javier 7d854ffb0a feat: i18n, language switcher fix, DataTable improvements, blade translations
- 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>
2026-06-16 18:05:53 +02:00

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 &mdash; {{ $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()">
&#128438; {{ __('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)
&nbsp;&bull;&nbsp; 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') }}
&mdash;
{{ $phase->planned_end?->format('d/m/Y') ?? 'Sin fecha fin' }}
@else
Sin fechas planificadas
@endif
&nbsp;&bull;&nbsp; {{ $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 &mdash; Sistema de Gestión de Obras</span>
<span>{{ now()->format('d/m/Y H:i') }}</span>
</div>
</div>
</body>
</html>