File manager - Edit - /var/www/html/portalHomolog/resources/views/admin/cronogramas/exports/pdf.blade.php
Back
@php use App\Enums\Accounts\Cronogramas\CronogramaActivityStatus; use App\Enums\Accounts\Cronogramas\CronogramaProjectStatus; /** Rótulo da aba exportada (para o subtítulo institucional). */ $tipoLabel = [ 'table' => __('Tabela de Atividades'), 'gantt' => __('Gráfico de Gantt'), 'timeline' => __('Linha do Tempo'), ][$exportType] ?? __('Tabela de Atividades'); $brasaoPath = public_path('pmar/assets/img/brasao-2026.png'); /** Cor da barra/etiqueta conforme estado da atividade. */ $statusColor = static function (string $status, bool $overdue = false): string { if ($overdue) { return '#c0392b'; } return match ($status) { 'completed' => '#1e7e34', 'in_progress' => '#1245A8', 'validation' => '#b8860b', 'planning' => '#0c7c8c', 'suspended' => '#6c757d', 'cancelled' => '#343a40', default => '#8a939b', }; }; @endphp <!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <title>{{ __('Cronograma') }} - {{ $project->name }}</title> <style> @page { margin: 18mm 12mm 22mm 12mm; } body { font-family: DejaVu Sans, sans-serif; font-size: 9pt; color: #222; margin: 0; padding: 0; } .pdf-header table { width: 100%; border-collapse: collapse; } .pdf-header td { border: none; vertical-align: middle; } .pdf-header img { width: 64px; height: auto; } .pdf-header h1 { font-size: 13pt; margin: 0; color: #1245A8; text-transform: uppercase; } .pdf-header h2 { font-size: 10pt; margin: 2px 0 0; color: #444; font-weight: normal; } .doc-title { margin: 10px 0 6px; font-size: 11pt; font-weight: bold; text-align: center; text-transform: uppercase; border-top: 2px solid #1245A8; border-bottom: 1px solid #bbb; padding: 6px 0; } .meta { margin-bottom: 12px; font-size: 9pt; color: #555; } .meta strong { color: #222; } .meta .meta-line { margin-bottom: 2px; } h3.section { font-size: 10pt; margin: 14px 0 6px; padding: 4px 8px; background: #f2f2f2; border-left: 4px solid #1245A8; text-transform: uppercase; page-break-after: avoid; } table.data { width: 100%; border-collapse: collapse; } table.data th, table.data td { border: 1px solid #ccc; padding: 4px 6px; text-align: left; vertical-align: middle; } table.data th { background: #1245A8; color: #fff; font-weight: bold; font-size: 8.5pt; } table.data tr.overdue td { background: #fde8e8; } .badge { display: inline-block; padding: 1px 6px; border-radius: 3px; color: #fff; font-size: 8pt; white-space: nowrap; } .progress-cell { white-space: nowrap; } /* ---- Gantt ---- */ table.gantt { width: 100%; border-collapse: collapse; table-layout: fixed; } table.gantt th, table.gantt td { border: 1px solid #ddd; font-size: 7.5pt; padding: 2px 3px; vertical-align: middle; } table.gantt th { background: #f2f2f2; text-align: center; } table.gantt th.act, table.gantt td.act { text-align: left; width: 28%; } table.gantt .cell-on { background: #1245A8; } table.gantt .month-grid { text-align: center; } /* ---- Timeline ---- */ .tl-month { margin-top: 10px; page-break-inside: avoid; } .tl-month-label { background: #1245A8; color: #fff; font-weight: bold; font-size: 9pt; padding: 3px 8px; text-transform: capitalize; } .tl-item { border: 1px solid #ddd; border-left: 4px solid #1245A8; padding: 4px 8px; margin: 4px 0; page-break-inside: avoid; } .tl-item .tl-title { font-weight: bold; } .tl-item .tl-meta { color: #666; font-size: 8pt; margin-top: 2px; } .empty-note { font-style: italic; color: #777; margin: 6px 0; } .footer-wrapper { position: fixed; bottom: -12mm; left: 0; width: 100%; font-size: 8pt; color: #555; padding-top: 4px; border-top: 1px solid #ccc; } .footer-wrapper table { width: 100%; } .footer-wrapper td { border: none; padding: 0 4px; } .page:after { content: counter(page); } </style> </head> <body> <div class="pdf-header"> <table> <tr> <td style="width: 72px;"> <img src="{{ $brasaoPath }}" alt="{{ __('Prefeitura Municipal de Angra dos Reis') }}"> </td> <td> <h1>{{ __('Prefeitura Municipal de Angra dos Reis') }}</h1> <h2>{{ __('Cronogramas') }} - {{ $tipoLabel }}</h2> </td> </tr> </table> <div class="doc-title">{{ $project->name }}</div> </div> <div class="meta"> <div class="meta-line"> <strong>{{ __('Status') }}:</strong> {{ CronogramaProjectStatus::tryFrom($project->status)?->label() }} | <strong>{{ __('Progresso') }}:</strong> {{ (int) $project->completion_percent }}% | <strong>{{ __('Atividades') }}:</strong> {{ $projectStats['total_count'] ?? 0 }} ({{ __('Concluídas') }}: {{ $projectStats['completed_count'] ?? 0 }}, {{ __('Atrasadas') }}: {{ $projectStats['overdue_count'] ?? 0 }}) </div> <div class="meta-line"> <strong>{{ __('Início') }}:</strong> {{ $project->start_date?->format('d/m/Y') ?? '-' }} | <strong>{{ __('Término previsto') }}:</strong> {{ $project->planned_end_date?->format('d/m/Y') ?? '-' }} | <strong>{{ __('Gerado em') }}:</strong> {{ now()->format('d/m/Y H:i') }} </div> @if($project->description) <div class="meta-line"><strong>{{ __('Descrição') }}:</strong> {{ $project->description }}</div> @endif @if(($filtersActive ?? false) && ! empty($filtersSummary)) <div class="meta-line"><strong>{{ __('Filtros aplicados') }}:</strong> {{ implode(' | ', $filtersSummary) }}</div> @endif </div> {{-- ============================ TABELA ============================ --}} @if($exportType === 'table') @if(empty($tableRows)) <p class="empty-note">{{ __('Nenhuma atividade cadastrada.') }}</p> @else <table class="data"> <thead> <tr> <th>{{ __('Item') }}</th> <th>{{ __('Atividades') }}</th> <th>{{ __('Predecessora') }}</th> <th>{{ __('%') }}</th> <th>{{ __('Status') }}</th> <th>{{ __('Responsável') }}</th> <th>{{ __('Início') }}</th> <th>{{ __('Término') }}</th> <th>{{ __('Descrição') }}</th> <th>{{ __('Observação') }}</th> </tr> </thead> <tbody> @foreach($tableRows as $row) <tr @if($row['is_overdue']) class="overdue" @endif> <td>{{ $row['code'] }}</td> <td>{{ ! empty($row['parent_id']) ? '↳ ' : '' }}{{ $row['title'] }}</td> <td>{{ $row['predecessors'] ?: '-' }}</td> <td class="progress-cell">{{ (int) $row['completion_percent'] }}%</td> <td> <span class="badge" style="background: {{ $statusColor($row['status'], $row['is_overdue']) }};"> {{ $row['is_overdue'] ? __('Atrasado') : $row['status_label'] }} </span> </td> <td>{{ $row['responsible'] }}</td> <td>{{ $row['planned_start_date'] ? \Carbon\Carbon::parse($row['planned_start_date'])->format('d/m/Y') : '-' }}</td> <td>{{ $row['planned_end_date'] ? \Carbon\Carbon::parse($row['planned_end_date'])->format('d/m/Y') : '-' }}</td> <td>{{ $row['description'] }}</td> <td>{{ $row['notes'] }}</td> </tr> @endforeach </tbody> </table> @endif @endif {{-- ============================ GANTT ============================ --}} @if($exportType === 'gantt') @php $withDates = collect($ganttData)->filter(fn ($g) => ! empty($g['start']) && ! empty($g['end']))->values(); $withoutDates = collect($ganttData)->filter(fn ($g) => empty($g['start']) || empty($g['end']))->values(); $months = []; if ($withDates->isNotEmpty()) { $min = $withDates->min(fn ($g) => $g['start']); $max = $withDates->max(fn ($g) => $g['end']); $cursor = \Carbon\Carbon::parse($min)->startOfMonth(); $limit = \Carbon\Carbon::parse($max)->startOfMonth(); // Limita a 36 meses para manter o PDF legível. $guard = 0; while ($cursor->lte($limit) && $guard < 36) { $months[] = $cursor->format('Y-m'); $cursor->addMonth(); $guard++; } } @endphp @if($withDates->isEmpty()) <p class="empty-note">{{ __('Nenhuma atividade com datas definidas para o gráfico de Gantt.') }}</p> @else <table class="gantt"> <thead> <tr> <th class="act">{{ __('Atividade') }}</th> @foreach($months as $m) <th>{{ \Carbon\Carbon::createFromFormat('Y-m', $m)->translatedFormat('M/y') }}</th> @endforeach </tr> </thead> <tbody> @foreach($withDates as $g) @php $startM = \Carbon\Carbon::parse($g['start'])->format('Y-m'); $endM = \Carbon\Carbon::parse($g['end'])->format('Y-m'); $color = $statusColor($g['status'], $g['overdue'] ?? false); @endphp <tr> <td class="act"> {{ ! empty($g['parent_id']) ? '↳ ' : '' }}<strong>{{ $g['code'] }}</strong> {{ \Illuminate\Support\Str::limit($g['title'], 45) }} <span style="color:#888;">({{ (int) $g['progress'] }}%)</span> </td> @foreach($months as $m) @php $on = ($m >= $startM && $m <= $endM); @endphp <td class="month-grid" @if($on) style="background: {{ $color }};" @endif> </td> @endforeach </tr> @endforeach </tbody> </table> @if($withoutDates->isNotEmpty()) <h3 class="section">{{ __('Atividades sem datas definidas') }}</h3> <table class="data"> <thead> <tr> <th>{{ __('Código') }}</th> <th>{{ __('Atividade') }}</th> <th>{{ __('Responsável') }}</th> <th>{{ __('Status') }}</th> <th>{{ __('Progresso') }}</th> </tr> </thead> <tbody> @foreach($withoutDates as $g) <tr> <td>{{ $g['code'] }}</td> <td>{{ $g['title'] }}</td> <td>{{ $g['responsible'] ?: '-' }}</td> <td> <span class="badge" style="background: {{ $statusColor($g['status'], $g['overdue'] ?? false) }};"> {{ CronogramaActivityStatus::tryFrom($g['status'])?->label() ?? $g['status'] }} </span> </td> <td>{{ (int) $g['progress'] }}%</td> </tr> @endforeach </tbody> </table> @endif @endif @endif {{-- ========================= LINHA DO TEMPO ========================= --}} @if($exportType === 'timeline') @php $statusBadge = [ 'not_started' => '#8a939b', 'planning' => '#0c7c8c', 'in_progress' => '#1245A8', 'validation' => '#b8860b', 'completed' => '#1e7e34', 'suspended' => '#6c757d', 'cancelled' => '#343a40', ]; $tlRows = collect($tableRows)->sortBy([ fn ($a, $b) => (empty($a['planned_start_date']) ? 1 : 0) <=> (empty($b['planned_start_date']) ? 1 : 0), fn ($a, $b) => ($a['planned_start_date'] ?? '9999') <=> ($b['planned_start_date'] ?? '9999'), fn ($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0), ]); $grouped = $tlRows->groupBy(function ($row) { if (empty($row['planned_start_date'])) { return 'sem_data'; } return \Carbon\Carbon::parse($row['planned_start_date'])->format('Y-m'); }); @endphp @if($tlRows->isEmpty()) <p class="empty-note">{{ __('Nenhuma atividade cadastrada.') }}</p> @else @foreach($grouped as $monthKey => $rows) <div class="tl-month"> <div class="tl-month-label"> @if($monthKey === 'sem_data') {{ __('Sem data') }} @else {{ \Carbon\Carbon::createFromFormat('Y-m', $monthKey)->translatedFormat('F \d\e Y') }} @endif </div> @foreach($rows as $row) @php $dotStatus = $row['is_overdue'] ? 'overdue' : $row['status']; $color = $statusColor($row['status'], $row['is_overdue']); $startFmt = $row['planned_start_date'] ? \Carbon\Carbon::parse($row['planned_start_date'])->format('d/m/Y') : null; $endFmt = $row['planned_end_date'] ? \Carbon\Carbon::parse($row['planned_end_date'])->format('d/m/Y') : null; @endphp <div class="tl-item" style="border-left-color: {{ $color }};"> <span class="tl-title"> {{ ! empty($row['parent_id']) ? '↳ ' : '' }}<span style="color:{{ $color }};">{{ $row['code'] }}</span> {{ $row['title'] }} </span> <span class="badge" style="background: {{ $color }};"> {{ $row['is_overdue'] ? __('Atrasado') : $row['status_label'] }} </span> <div class="tl-meta"> @if($startFmt || $endFmt) {{ __('Período') }}: {{ $startFmt ?? '-' }} - {{ $endFmt ?? '-' }} @endif @if($row['responsible']) | {{ __('Responsável') }}: {{ $row['responsible'] }} @endif | {{ __('Progresso') }}: {{ (int) $row['completion_percent'] }}% </div> </div> @endforeach </div> @endforeach @endif @endif <div class="footer-wrapper"> <table> <tr> <td style="text-align: left;">{{ __('Prefeitura Municipal de Angra dos Reis') }} - {{ __('Cronogramas') }}</td> <td style="text-align: right;">{{ __('Página') }} <span class="page"></span></td> </tr> </table> </div> </body> </html>
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings