File manager - Edit - /var/www/html/transparencia/resources/views/pages/obras.blade.php
Back
@extends('theme') @section("title", "Portal da Transparência - Angra dos Reis :: Relatório de Obras") @section('content') <section> <div class="container-full p-5 mx-5 emendas-parlamentares"> @php $breadcrumbs = [ ['route' => route('index'), 'title' => __("Página Inicial")], ['currentPage' => "page", 'title' => __("Relatório de Obras")], ]; @endphp @include('components._breadcrumb', $breadcrumbs) @include('components._titleMain', [ "title" => __("Relatório de Obras"), "summary" => __("Acompanhe o andamento das obras públicas, prazos, empresas executoras e valores aplicados."), ]) {{-- ===== CARD ÚNICO ===== --}} <div class="ep-card"> {{-- Cabeçalho do card --}} <div class="ep-card__header"> <div> <p class="ep-card__eyebrow">Portal da Transparência</p> <h2 class="ep-card__title">Relatório de Obras</h2> </div> <div class="ep-badge-count"> <span class="ep-badge-count__num">{{ $obras->total() }}</span> <span class="ep-badge-count__label">obra(s) encontrada(s)</span> </div> </div> {{-- Filtros --}} <div class="ep-card__section ep-card__section--filters"> <div class="ep-section-label"> <svg width="14" height="14" viewBox="0 0 16 16" fill="none"> <path d="M2 4h12M4 8h8M6 12h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Filtrar obras </div> <form method="GET" action="{{ route('pages.obras') }}"> <div class="ep-filter-grid"> <div class="ep-filter-field ep-filter-field--wide-2"> <label for="q" class="ep-label">Busca geral</label> <input type="text" id="q" name="q" class="ep-input" placeholder="Busca por processo, objeto, empresa, bairro ou situação…" value="{{ request('q') }}"> </div> <div class="ep-filter-field"> <label for="processo" class="ep-label">Nº Processo</label> <input type="text" id="processo" name="processo" class="ep-input" placeholder="Ex: 025/2021" value="{{ request('processo') }}"> </div> <div class="ep-filter-field"> <label for="objeto_empresa" class="ep-label">Objeto ou Empresa</label> <input type="text" id="objeto_empresa" name="objeto_empresa" class="ep-input" placeholder="Digite o objeto ou empresa" value="{{ request('objeto_empresa') }}"> </div> <div class="ep-filter-field"> <label for="situacao" class="ep-label">Situação</label> <select id="situacao" name="situacao" class="ep-input"> <option value="">Todas</option> @foreach($situacoes as $sit) <option value="{{ $sit }}" @selected(request('situacao') === $sit)> {{ $sit }} </option> @endforeach </select> </div> <div class="ep-filter-field"> <label for="bairro" class="ep-label">Bairro</label> <select id="bairro" name="bairro" class="ep-input"> <option value="">Todos</option> @foreach($bairros as $bai) <option value="{{ $bai }}" @selected(request('bairro') === $bai)> {{ $bai }} </option> @endforeach </select> </div> <div class="ep-filter-field"> <label for="ano_inicio" class="ep-label">Ano Início</label> <select id="ano_inicio" name="ano_inicio" class="ep-input"> <option value="">Todos</option> @foreach($anosInicio as $ano) <option value="{{ $ano }}" @selected((string) request('ano_inicio') === (string) $ano)> {{ $ano }} </option> @endforeach </select> </div> <div class="ep-filter-field"> <label for="ano_termino" class="ep-label">Ano Término</label> <select id="ano_termino" name="ano_termino" class="ep-input"> <option value="">Todos</option> @foreach($anosTermino as $ano) <option value="{{ $ano }}" @selected((string) request('ano_termino') === (string) $ano)> {{ $ano }} </option> @endforeach </select> </div> </div> <div class="ep-filter-actions"> <span class="ep-download-label">Baixar:</span> <a href="{{ route('pages.obras.exportar.csv', request()->query()) }}" class="ep-btn ep-btn--download-solid"> <svg width="11" height="11" viewBox="0 0 16 16" fill="none"> <path d="M8 2v8M5 7l3 3 3-3M3 13h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> CSV </a> <div class="ep-filter-actions__spacer"></div> <button type="submit" class="ep-btn ep-btn--primary"> <svg width="12" height="12" viewBox="0 0 16 16" fill="none"> <circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.5"/> <path d="M10.5 10.5L14 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Filtrar </button> <a href="{{ route('pages.obras') }}" class="ep-btn ep-btn--ghost"> <svg width="12" height="12" viewBox="0 0 16 16" fill="none"> <path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> </svg> Limpar filtros </a> </div> </form> </div> {{-- Tabela --}} <div class="ep-table-wrap"> <table class="ep-table"> <colgroup> <col style="width:6%"> <col style="width:22%"> <col style="width:8%"> <col style="width:8%"> <col style="width:6%"> <col style="width:7%"> <col style="width:7%"> <col style="width:10%"> <col style="width:8%"> <col style="width:8%"> <col style="width:10%"> </colgroup> <thead> <tr> <th>Processo</th> <th>Objeto</th> <th>Bairro</th> <th>Situação</th> <th>Início</th> <th>Previsão Término</th> <th>Conclusão</th> <th>Empresa</th> <th>Valor Contratado</th> <th>Valor Pago</th> <th>Contrato</th> </tr> </thead> <tbody> @forelse($obras as $obra) @php $statusBadge = match ($obra->situacao) { 'Concluída' => 'success', 'Em andamento' => 'warning', 'Paralisada' => 'danger', default => 'ghost', }; @endphp <tr> <td class="ep-td--sm" style="white-space: nowrap;">{{ $obra->processo ?: '-' }}</td> <td class="ep-td--truncate ep-td--sm text-start" style="text-align: left !important;">{{ $obra->objeto ?: '-' }}</td> <td class="ep-td--sm">{{ $obra->bairro ?: '-' }}</td> <td> <span class="ep-status ep-status--{{ $statusBadge }}"> <span class="ep-status__dot"></span> {{ $obra->situacao ?: '-' }} </span> </td> <td>{{ $obra->data_inicio?->format('d/m/Y') ?: '-' }}</td> <td>{{ $obra->data_prevista_termino?->format('d/m/Y') ?: '-' }}</td> <td>{{ $obra->data_conclusao?->format('d/m/Y') ?: '-' }}</td> <td class="ep-td--truncate ep-td--sm">{{ $obra->empresa ?: '-' }}</td> <td> {{ $obra->valor_contratado ? 'R$ ' . number_format($obra->valor_contratado, 2, ',', '.') : '-' }} </td> <td> {{ $obra->valor_pago ? 'R$ ' . number_format($obra->valor_pago, 2, ',', '.') : '-' }} </td> <td class="ep-td--sm text-start" style="text-align: left !important;">{!! $obra->contrato ? nl2br(e($obra->contrato)) : '-' !!}</td> </tr> @empty <tr> <td colspan="11" class="ep-empty">Nenhuma obra encontrada com os filtros selecionados.</td> </tr> @endforelse </tbody> </table> </div> {{-- Cards mobile (ocultos no desktop) --}} <div class="ep-mobile-list"> @forelse($obras as $obra) @php $statusBadge = match ($obra->situacao) { 'Concluída' => 'success', 'Em andamento' => 'warning', 'Paralisada' => 'danger', default => 'ghost', }; @endphp <div class="ep-mobile-card"> <div class="ep-mobile-card__top"> <div class="ep-mobile-card__autor">{{ $obra->processo ?: 'Processo não inf.' }}</div> <div class="ep-mobile-card__badge"> <span class="ep-status ep-status--{{ $statusBadge }}"> <span class="ep-status__dot"></span> {{ $obra->situacao }} </span> </div> </div> <div class="ep-mobile-card__objeto">{{ $obra->objeto }}</div> <div class="ep-mobile-card__grid"> <div class="ep-mobile-card__item"> <span class="ep-mobile-card__key">Bairro</span> <span class="ep-mobile-card__val">{{ $obra->bairro ?: '-' }}</span> </div> <div class="ep-mobile-card__item"> <span class="ep-mobile-card__key">Empresa</span> <span class="ep-mobile-card__val">{{ $obra->empresa ?: '-' }}</span> </div> <div class="ep-mobile-card__item"> <span class="ep-mobile-card__key">Início</span> <span class="ep-mobile-card__val">{{ $obra->data_inicio?->format('d/m/Y') ?: '-' }}</span> </div> <div class="ep-mobile-card__item"> <span class="ep-mobile-card__key">Previsão Término</span> <span class="ep-mobile-card__val">{{ $obra->data_prevista_termino?->format('d/m/Y') ?: '-' }}</span> </div> <div class="ep-mobile-card__item"> <span class="ep-mobile-card__key">Conclusão</span> <span class="ep-mobile-card__val">{{ $obra->data_conclusao?->format('d/m/Y') ?: '-' }}</span> </div> </div> @if($obra->contrato) <div class="ep-mobile-card__contrato-box" style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px dashed #dee2e6; margin-bottom: 0.75rem;"> <span class="ep-mobile-card__key">Contrato e Aditivos</span> <div style="font-size: 0.8rem; color: #212529; white-space: pre-line; line-height: 1.4;">{{ $obra->contrato }}</div> </div> @endif <div class="ep-mobile-card__valores"> <div class="ep-mobile-card__valor-item"> <span class="ep-mobile-card__key">Vl. Contratado</span> <span class="ep-mobile-card__valor-num">R$ {{ number_format($obra->valor_contratado, 2, ',', '.') }}</span> </div> <div class="ep-mobile-card__valor-item"> <span class="ep-mobile-card__key">Vl. Pago</span> <span class="ep-mobile-card__valor-num">R$ {{ number_format($obra->valor_pago, 2, ',', '.') }}</span> </div> </div> </div> @empty <div class="ep-empty">Nenhuma obra cadastrada.</div> @endforelse </div> {{-- Rodapé / Paginação --}} <div class="ep-card__footer"> <span class="ep-pagination-info"> Mostrando {{ $obras->firstItem() }}–{{ $obras->lastItem() }} de {{ $obras->total() }} registros </span> <div class="ep-pagination"> {{ $obras->links() }} </div> </div> </div> </div> </section> <style> /* ════════════════════════════════════════════════════ ESTILOS BASE — desktop idêntico ao original Adaptados para o Relatório de Obras ════════════════════════════════════════════════════ */ /* ── Card principal ────────────────────────────────── */ .ep-card { background: #fff; border: 1px solid #dee2e6; border-radius: .625rem; overflow: hidden; } /* ── Cabeçalho ─────────────────────────────────────── */ .ep-card__header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding: 1.25rem 1.5rem; border-bottom: 1px solid #dee2e6; background: #f8f9fa; } .ep-card__eyebrow { font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: #6c757d; margin: 0 0 2px; } .ep-card__title { font-size: 1.15rem; font-weight: 600; color: #212529; margin: 0; } .ep-badge-count { display: flex; align-items: center; gap: 6px; background: #eaf3de; border-radius: 20px; padding: 5px 14px; } .ep-badge-count__num { font-size: 1rem; font-weight: 600; color: #27500a; line-height: 1; } .ep-badge-count__label { font-size: .7rem; color: #3b6d11; } /* ── Seção genérica ────────────────────────────────── */ .ep-card__section { border-bottom: 1px solid #dee2e6; } .ep-card__section--filters { padding: 1.25rem 1.5rem; } .ep-section-label { display: flex; align-items: center; gap: 7px; font-size: .8rem; font-weight: 600; color: #495057; margin-bottom: 1rem; } /* ── Grid de filtros ───────────────────────────────── */ .ep-filter-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 1rem; } .ep-filter-field--wide-2 { grid-column: span 2; } .ep-label { display: block; font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: #6c757d; margin-bottom: 4px; } .ep-input { width: 100%; padding: 6px 10px; font-size: .8rem; border: 1px solid #ced4da; border-radius: .375rem; background: #fff; color: #212529; box-sizing: border-box; transition: border-color .15s ease, box-shadow .15s ease; } .ep-input:focus { outline: none; border-color: #198754; box-shadow: 0 0 0 3px rgba(25,135,84,.15); } /* ── Ações de filtro ───────────────────────────────── */ .ep-filter-actions { display: flex; align-items: center; gap: 8px; padding-top: .875rem; border-top: 1px solid #dee2e6; flex-wrap: wrap; } .ep-filter-actions__spacer { flex: 1; } .ep-download-label { font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: #6c757d; } /* ── Botões ────────────────────────────────────────── */ .ep-btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; font-size: .8rem; font-weight: 500; border-radius: .375rem; cursor: pointer; text-decoration: none; transition: background .15s ease, color .15s ease, border-color .15s ease; white-space: nowrap; } .ep-btn--primary { background: #198754; color: #fff; border: 1px solid #198754; } .ep-btn--primary:hover { background: #157347; border-color: #157347; } .ep-btn--ghost { background: transparent; color: #6c757d; border: 1px solid #ced4da; } .ep-btn--ghost:hover { color: #343a40; border-color: #adb5bd; } .ep-btn--download-solid { background: #198754; color: #fff; border: 1px solid #198754; } .ep-btn--download-solid:hover { background: #157347; color: #fff; } .ep-btn--download-outline { background: transparent; color: #198754; border: 1px solid #198754; } .ep-btn--download-outline:hover { background: #eaf3de; } /* ── Tabela ────────────────────────────────────────── */ .ep-table-wrap { overflow-x: auto; } .ep-table { width: 100%; border-collapse: collapse; font-size: .8rem; table-layout: fixed; min-width: 1600px; } .ep-table thead tr { background: #f8f9fa; border-bottom: 1px solid #dee2e6; } .ep-table thead th { padding: .65rem 1rem; font-size: .68rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: #6c757d; white-space: normal; text-align: center; vertical-align: middle; } .ep-table tbody tr { border-bottom: 1px solid #f0f0f0; transition: background .1s ease; } .ep-table tbody tr:nth-child(even) { background: #fafafa; } .ep-table tbody tr:hover { background: #f0f9f4; } .ep-table tbody td { padding: .6rem 1rem; vertical-align: middle; color: #212529; text-align: center; white-space: normal; word-break: break-word; } .ep-td--truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ep-td--sm { font-size: .75rem; color: #495057; } .ep-td--mono { font-variant-numeric: tabular-nums; font-family: monospace, Courier, monospace; } .ep-empty { text-align: center; padding: 2rem; color: #6c757d; } .text-end { text-align: right !important; } .text-start { text-align: left !important; } /* ── Badges de situação ────────────────────────────── */ .ep-status { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 20px; font-size: .72rem; font-weight: 500; white-space: nowrap; } .ep-status__dot { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; } .ep-status--warning { background: #fff3cd; color: #664d03; } .ep-status--warning .ep-status__dot { background: #ffc107; } .ep-status--success { background: #d1e7dd; color: #0a3622; } .ep-status--success .ep-status__dot { background: #198754; } .ep-status--danger { background: #f8d7da; color: #58151c; } .ep-status--danger .ep-status__dot { background: #dc3545; } .ep-status--ghost { background: #e9ecef; color: #495057; } .ep-status--ghost .ep-status__dot { background: #6c757d; } /* ── Rodapé / Paginação ────────────────────────────── */ .ep-card__footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: .75rem 1.5rem; background: #f8f9fa; border-top: 1px solid #dee2e6; } .ep-pagination-info { font-size: .75rem; color: #6c757d; } .ep-pagination { display: flex; align-items: center; } /* ── Cards mobile: ocultos no desktop ──────────────── */ .ep-mobile-list { display: none; } /* ── Estilos dos cards mobile ──────────────────────── */ .ep-mobile-card { padding: 1rem 1.25rem; border-bottom: 1px solid #f0f0f0; background: #fff; } .ep-mobile-card:nth-child(even) { background: #fafafa; } .ep-mobile-card:last-child { border-bottom: none; } .ep-mobile-card__top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: .4rem; } .ep-mobile-card__autor { font-size: .85rem; font-weight: 600; color: #212529; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ep-mobile-card__objeto { font-size: .78rem; color: #495057; margin-bottom: .75rem; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .ep-mobile-card__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 16px; margin-bottom: .75rem; } .ep-mobile-card__item { display: flex; flex-direction: column; gap: 1px; } .ep-mobile-card__key { font-size: .65rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: #6c757d; } .ep-mobile-card__val { font-size: .8rem; color: #212529; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ep-mobile-card__valores { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding-top: .65rem; border-top: 1px dashed #dee2e6; } .ep-mobile-card__valor-item { display: flex; flex-direction: column; gap: 2px; } .ep-mobile-card__valor-num { font-size: .82rem; font-weight: 600; font-variant-numeric: tabular-nums; color: #212529; } .ep-mobile-card__valor-num--realizado { color: #198754; } /* ════════════════════════════════════════════════════ BREAKPOINTS — somente mobile/tablet (> 992px = intocado) ════════════════════════════════════════════════════ */ /* ── Tablet (≤ 992px) ──────────────────────────────── */ @media (max-width: 992px) { .emendas-parlamentares { padding: 1rem !important; margin: 0 !important; } /* Filtros: 2 colunas */ .ep-filter-grid { grid-template-columns: 1fr 1fr; } .ep-filter-field--wide-2 { grid-column: 1 / -1; } /* Troca tabela por cards */ .ep-table-wrap { display: none; } .ep-mobile-list { display: block; } /* Rodapé empilhado */ .ep-card__footer { flex-direction: column; align-items: flex-start; padding: .75rem 1.25rem; } .ep-pagination { width: 100%; justify-content: center; } } /* ── Mobile (≤ 576px) ──────────────────────────────── */ @media (max-width: 576px) { /* Filtros: coluna única */ .ep-filter-grid { grid-template-columns: 1fr; gap: 8px; } .ep-filter-field--wide-2 { grid-column: 1; } /* Ações empilhadas */ .ep-filter-actions { flex-direction: column; align-items: stretch; gap: 8px; } .ep-filter-actions__spacer { display: none; } .ep-btn--primary, .ep-btn--ghost { justify-content: center; width: 100%; } /* Botão de download de largura total, label oculto */ .ep-download-label { display: none; } .ep-btn--download-solid { flex: 1; justify-content: center; } /* Cabeçalho empilhado */ .ep-card__header { flex-direction: column; align-items: flex-start; padding: .875rem 1rem; } .ep-card__section--filters { padding: .875rem 1rem; } .ep-card__footer { padding: .75rem 1rem; } .ep-mobile-card { padding: .875rem 1rem; } } /* ── Mobile pequeno (≤ 380px) ──────────────────────── */ @media (max-width: 380px) { .ep-mobile-card__grid { grid-template-columns: 1fr; } .ep-mobile-card__valores { grid-template-columns: 1fr; } .ep-card__eyebrow { display: none; } } </style> @endsection
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings