File manager - Edit - /var/www/html/portalHomolog/resources/views/admin/demandas/dashboard.blade.php
Back
@extends('layouts.admin.theme') @section('title', env('APP_NAME') . ' :: ' . __('Dashboard - Gerenciador de Demandas')) <link rel="stylesheet" href="{{ asset('pmar/css/demandas.css') }}"> @php $prioConfig = [ 'critica' => ['label' => 'Crítica', 'color' => 'danger'], 'alta' => ['label' => 'Alta', 'color' => 'warning'], 'media' => ['label' => 'Média', 'color' => 'primary'], 'baixa' => ['label' => 'Baixa', 'color' => 'success'], ]; $totalAbertas = ($resumo['pendente'] ?? 0) + ($resumo['em_andamento'] ?? 0); // Pré-computa dados para Chart.js (só para o secretário) $tendenciaLabs = $tendenciaVals = $canalLabels = $canalValues = $canalColors = $unidadeLabels = $unidadeValues = []; if ($isSecretario) { $nomesMeses = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez']; foreach ($tendencia['meses'] as $mes => $total) { [$ano, $num] = explode('-', $mes); $tendenciaLabs[] = $nomesMeses[(int)$num - 1] . '/' . substr($ano, 2); $tendenciaVals[] = $total; } $canalCores = [ 'portal' => '#2C499F', 'telefone' => '#198754', 'email' => '#fd7e14', 'presencial' => '#6f42c1', 'sei' => '#20c997', 'interno' => '#6c757d', ]; $canalLabels = $porCanal->pluck('canal')->toArray(); $canalValues = $porCanal->pluck('total')->toArray(); $canalColors = $porCanal->map(fn($c) => $canalCores[$c['chave']] ?? '#adb5bd')->values()->toArray(); $unidadeLabels = $porUnidade->pluck('nome')->toArray(); $unidadeValues = $porUnidade->pluck('total')->toArray(); } @endphp @section('content') <div class="card shadow-sm border-0"> @include('components.buttons._headLineButtonsBackAdd', [ 'headLine' => "<i class='fa-solid fa-chart-line'></i> " . __('Dashboard - Gerenciador de Demandas'), 'routeBack' => 'admin.demandas.index', 'routeBackParams' => [], 'nameBack' => __('Quadro'), 'permission' => 'GERENCIADOR_DEMANDAS', 'tabs' => [ [ 'route' => 'admin.demandas.relatorio.index', 'label' => 'Relatório', 'class' => 'fa-file-chart-column', ], ], ]) <div class="card-body"> @include('errors.messageFlash') {{-- ── STAT CARDS (sempre visíveis) ───────────────────────────────── --}} @php $statCards = [ ['label' => 'Total', 'value' => $resumo['total'], 'color' => 'primary', 'icon' => 'fa-clipboard-list'], ['label' => 'Em Andamento', 'value' => $resumo['em_andamento'], 'color' => 'info', 'icon' => 'fa-spinner'], ['label' => 'Pendentes', 'value' => $resumo['pendente'], 'color' => 'warning', 'icon' => 'fa-clock'], ['label' => 'Atendidas', 'value' => $resumo['atendido'], 'color' => 'success', 'icon' => 'fa-circle-check'], ['label' => 'Canceladas', 'value' => $resumo['cancelado'], 'color' => 'secondary', 'icon' => 'fa-ban'], ]; @endphp <div class="row row-cols-2 row-cols-md-3 row-cols-xl-5 g-3 mb-4"> @foreach ($statCards as $card) <div class="col"> <div class="card border-0 shadow-sm text-center h-100"> <div class="card-body py-3"> <div class="text-{{ $card['color'] }} mb-1"> <i class="fa-solid {{ $card['icon'] }} fa-2x"></i> </div> <h3 class="fw-bold mb-0 text-{{ $card['color'] }}">{{ $card['value'] }}</h3> <small class="text-muted">{{ __($card['label']) }}</small> </div> </div> </div> @endforeach </div> @if ($isSecretario) {{-- ════════════════════════════════════════════════════════════════ --}} {{-- VISÃO SECRETÁRIO --}} {{-- ════════════════════════════════════════════════════════════════ --}} {{-- Linha 1: Tendência | Por Responsável | Por Canal ──────────── --}} <div class="row g-3 mb-3"> {{-- Tendência Mensal --}} <div class="col-12 col-lg-4"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <h6 class="fw-semibold mb-0"> <i class="fa-solid fa-chart-column text-primary me-1"></i>{{ __('Tendência Mensal') }} </h6> <small class="text-muted d-block mb-3">{{ __('Demandas abertas nos últimos 6 meses') }}</small> <canvas id="chartTendencia" height="160"></canvas> <div class="d-flex gap-4 mt-3 pt-2 border-top"> <div> <span class="fw-bold fs-6 text-primary">{{ $tendencia['qtd_atual'] }}</span> <small class="text-muted d-block">{{ __('este mês') }}</small> </div> <div> @if ($tendencia['variacao'] !== null) <span class="fw-bold fs-6 {{ $tendencia['variacao'] >= 0 ? 'text-danger' : 'text-success' }}"> {{ $tendencia['variacao'] >= 0 ? '+' : '' }}{{ $tendencia['variacao'] }}% </span> <small class="text-muted d-block">{{ __('vs. mês anterior') }}</small> @else <span class="text-muted small">-</span> <small class="text-muted d-block">{{ __('vs. mês anterior') }}</small> @endif </div> </div> </div> </div> </div> {{-- Por Responsável --}} <div class="col-12 col-lg-4"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <h6 class="fw-semibold mb-0"> <i class="fa-solid fa-users text-primary me-1"></i>{{ __('Por Responsável') }} </h6> <small class="text-muted d-block mb-3">{{ __('Quem está atendendo mais demandas') }}</small> @if ($porResponsavel->isEmpty()) <div class="text-center py-4 text-muted small"> <i class="fa-solid fa-users fa-2x d-block mb-2 opacity-25"></i> {{ __('Nenhum responsável encontrado.') }} </div> @else @php $maxResp = $porResponsavel->max('total') ?: 1; @endphp @foreach ($porResponsavel as $i => $resp) @php $cores = ['primary','success','warning','info','danger']; $cor = $cores[$i % 5]; $ini = collect(explode(' ', $resp['nome']))->map(fn($p) => strtoupper($p[0] ?? ''))->take(2)->join(''); $pct = round(($resp['total'] / $maxResp) * 100); @endphp <div class="d-flex align-items-center gap-2 mb-2"> <div class="rounded-circle bg-{{ $cor }} text-white d-flex align-items-center justify-content-center flex-shrink-0" style="width:32px;height:32px;font-size:11px;font-weight:700">{{ $ini }}</div> <div class="flex-grow-1 min-w-0"> <div class="d-flex justify-content-between small mb-1"> <span class="fw-semibold text-truncate">{{ $resp['nome'] }}</span> <span class="fw-bold ms-2 flex-shrink-0">{{ $resp['total'] }}</span> </div> <div class="progress" style="height:4px"> <div class="progress-bar bg-{{ $cor }}" style="width:{{ $pct }}%"></div> </div> </div> </div> @endforeach <div class="mt-2 pt-2 border-top small text-muted"> {{ __('Mais ativo:') }} <strong class="text-body">{{ $porResponsavel->first()['nome'] }}</strong> </div> @endif </div> </div> </div> {{-- Por Canal --}} <div class="col-12 col-lg-4"> <div class="card border-0 shadow-sm h-100"> <div class="card-body d-flex flex-column"> <h6 class="fw-semibold mb-0"> <i class="fa-solid fa-signs-post text-primary me-1"></i>{{ __('Por Canal de Entrada') }} </h6> <small class="text-muted d-block mb-3">{{ __('De onde vêm as demandas') }}</small> @if ($porCanal->isEmpty()) <div class="text-center py-4 text-muted small flex-grow-1 d-flex flex-column align-items-center justify-content-center"> <i class="fa-solid fa-signs-post fa-2x d-block mb-2 opacity-25"></i> {{ __('Nenhum canal registrado.') }} </div> @else <div class="d-flex justify-content-center align-items-center flex-grow-1"> <div style="position:relative;width:150px;height:150px"> <canvas id="chartCanal"></canvas> </div> </div> <div class="mt-3 pt-2 border-top"> @foreach ($porCanal as $c) @php $cor = $canalCores[$c['chave']] ?? '#adb5bd'; @endphp <div class="d-flex justify-content-between align-items-center small mb-1"> <span class="d-flex align-items-center gap-2"> <span style="width:10px;height:10px;border-radius:2px;background:{{ $cor }};display:inline-block;flex-shrink:0"></span> {{ $c['canal'] }} </span> <span class="fw-bold">{{ $c['total'] }}</span> </div> @endforeach </div> @endif </div> </div> </div> </div> {{-- Linha 2: Por Unidade | Tempo Médio ───────────────────────── --}} <div class="row g-3"> {{-- Por Unidade --}} <div class="col-12 col-lg-8"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <h6 class="fw-semibold mb-0"> <i class="fa-solid fa-building text-primary me-1"></i>{{ __('Abertas por Unidade') }} </h6> <small class="text-muted d-block mb-3">{{ __('Demandas pendentes e em andamento por setor') }}</small> @if ($porUnidade->isEmpty()) <div class="text-center py-4 text-muted small"> <i class="fa-solid fa-building fa-2x d-block mb-2 opacity-25"></i> {{ __('Nenhuma demanda aberta no momento.') }} </div> @else <canvas id="chartUnidades" height="180"></canvas> @endif </div> </div> </div> {{-- Tempo Médio --}} <div class="col-12 col-lg-4"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <h6 class="fw-semibold mb-0"> <i class="fa-regular fa-clock text-primary me-1"></i>{{ __('Tempo Médio de Atendimento') }} </h6> <small class="text-muted d-block mb-3">{{ __('Dias para resolver, por prioridade') }}</small> @php $maxTempo = collect($tempoMedio)->except('geral')->filter()->max() ?: 1; @endphp @foreach ($prioConfig as $key => $cfg) @php $val = $tempoMedio[$key]; @endphp <div class="d-flex align-items-center gap-2 mb-3"> <div class="d-flex align-items-center gap-1 flex-shrink-0" style="width:66px"> <span class="rounded-circle bg-{{ $cfg['color'] }}" style="width:9px;height:9px;display:inline-block"></span> <small class="text-muted">{{ __($cfg['label']) }}</small> </div> <div class="flex-grow-1 progress" style="height:6px"> @if ($val !== null) <div class="progress-bar bg-{{ $cfg['color'] }}" style="width:{{ round(($val / $maxTempo) * 100) }}%"></div> @endif </div> <small class="text-end flex-shrink-0 {{ $key === 'critica' && $val !== null ? 'text-danger fw-semibold' : 'text-muted' }}" style="width:50px"> {{ $val !== null ? $val . 'd' : '-' }} </small> </div> @endforeach <div class="mt-2 pt-2 border-top small"> @if ($tempoMedio['geral'] !== null) <span class="text-muted">{{ __('Média geral:') }}</span> <strong class="ms-1">{{ $tempoMedio['geral'] }} {{ __('dias') }}</strong> @else <span class="text-muted">{{ __('Sem demandas atendidas ainda.') }}</span> @endif </div> </div> </div> </div> </div> @else {{-- ════════════════════════════════════════════════════════════════ --}} {{-- VISÃO USUÁRIO COMUM --}} {{-- ════════════════════════════════════════════════════════════════ --}} {{-- Linha 1: Ação Requerida | Abertas por Prioridade ───────────── --}} <div class="row g-3 mb-3"> {{-- Ação Requerida --}} <div class="col-12 col-lg-5"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <h6 class="fw-semibold mb-0"> <i class="fa-solid fa-bolt text-warning me-1"></i>{{ __('Ação Requerida') }} </h6> <small class="text-muted d-block mb-3">{{ __('Itens que dependem de você agora') }}</small> <div class="row g-3"> {{-- Aguardando aceite --}} <div class="col-6"> <a href="{{ route('admin.demandas.index', ['aba' => 'unidade']) }}" class="card border-0 text-decoration-none h-100 {{ $acaoRequerida['sem_responsavel'] > 0 ? 'bg-warning bg-opacity-10 border-warning border-opacity-25' : 'bg-light' }} rounded-3 d-block"> <div class="card-body text-center py-4"> <div class="display-5 fw-bold {{ $acaoRequerida['sem_responsavel'] > 0 ? 'text-warning' : 'text-muted' }}"> {{ $acaoRequerida['sem_responsavel'] }} </div> <div class="small {{ $acaoRequerida['sem_responsavel'] > 0 ? 'text-warning' : 'text-muted' }} fw-semibold mt-1"> <i class="fa-solid fa-hand-point-up fa-xs me-1"></i>{{ __('Aguardando aceite') }} </div> <div class="text-muted" style="font-size:11px;margin-top:4px">{{ __('da sua unidade') }}</div> </div> </a> </div> {{-- Encaminhadas para mim --}} <div class="col-6"> <a href="{{ route('admin.demandas.index', ['aba' => 'recebidas']) }}" class="card border-0 text-decoration-none h-100 {{ $acaoRequerida['minhas_pendentes'] > 0 ? 'bg-danger bg-opacity-10 border-danger border-opacity-25' : 'bg-light' }} rounded-3 d-block"> <div class="card-body text-center py-4"> <div class="display-5 fw-bold {{ $acaoRequerida['minhas_pendentes'] > 0 ? 'text-danger' : 'text-muted' }}"> {{ $acaoRequerida['minhas_pendentes'] }} </div> <div class="small {{ $acaoRequerida['minhas_pendentes'] > 0 ? 'text-danger' : 'text-muted' }} fw-semibold mt-1"> <i class="fa-solid fa-inbox fa-xs me-1"></i>{{ __('Para mim - pendentes') }} </div> <div class="text-muted" style="font-size:11px;margin-top:4px">{{ __('encaminhadas para você') }}</div> </div> </a> </div> </div> @if ($acaoRequerida['total'] === 0) <div class="text-center mt-3 pt-2 border-top small text-muted"> <i class="fa-solid fa-circle-check text-success me-1"></i>{{ __('Tudo em dia! Nenhum item aguardando sua ação.') }} </div> @else <div class="text-center mt-3 pt-2 border-top small"> <span class="badge bg-danger rounded-pill me-1">{{ $acaoRequerida['total'] }}</span> <span class="text-muted">{{ __('item(ns) aguardando sua ação') }}</span> </div> @endif </div> </div> </div> {{-- Abertas por Prioridade --}} <div class="col-12 col-lg-7"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <h6 class="fw-semibold mb-0"> <i class="fa-solid fa-flag text-primary me-1"></i>{{ __('Abertas por Prioridade') }} </h6> <small class="text-muted d-block mb-3"> {{ __('Demandas pendentes e em andamento') }} - <strong>{{ $totalAbertas }}</strong> {{ __('no total') }} </small> @if ($totalAbertas === 0) <div class="text-center py-4 text-muted small"> <i class="fa-solid fa-inbox fa-2x d-block mb-2 opacity-25"></i> {{ __('Nenhuma demanda aberta no momento.') }} </div> @else @foreach ($prioConfig as $key => $cfg) @php $qtd = (int) ($abertasPorPrioridade[$key] ?? 0); $pct = $totalAbertas > 0 ? round(($qtd / $totalAbertas) * 100) : 0; @endphp <div class="mb-3"> <div class="d-flex justify-content-between align-items-center mb-1"> <span class="d-flex align-items-center gap-2 small"> <span class="badge bg-{{ $cfg['color'] }}">{{ __($cfg['label']) }}</span> </span> <span class="small fw-bold"> {{ $qtd }} @if ($qtd > 0) <span class="fw-normal text-muted">({{ $pct }}%)</span> @endif </span> </div> <div class="progress" style="height:8px;border-radius:4px"> <div class="progress-bar bg-{{ $cfg['color'] }}" style="width:{{ $pct }}%"></div> </div> </div> @endforeach @endif </div> </div> </div> </div> {{-- Linha 2: Próximas com Prazo | Recentes ─────────────────────── --}} <div class="row g-3"> {{-- Próximas com Prazo --}} <div class="col-12 col-lg-5"> <div class="card border-0 shadow-sm h-100 d-flex flex-column"> <div class="card-header bg-transparent d-flex align-items-center justify-content-between py-3"> <div> <h6 class="fw-semibold mb-0"> <i class="fa-regular fa-calendar-xmark text-primary me-1"></i>{{ __('Prazos') }} </h6> <small class="text-muted">{{ __('Vencidas e nos próximos 7 dias') }}</small> </div> @if ($proximasPrazo['vencidas']->isNotEmpty()) <span class="badge bg-danger rounded-pill"> {{ $proximasPrazo['vencidas']->count() }} {{ __('vencida(s)') }} </span> @endif </div> <div class="card-body p-0 flex-grow-1" style="overflow-y:auto;max-height:280px"> @if ($proximasPrazo['vencidas']->isEmpty() && $proximasPrazo['proximas']->isEmpty()) <div class="text-center py-5 text-muted"> <i class="fa-solid fa-calendar-check fa-2x d-block mb-2 opacity-25 text-success"></i> <small>{{ __('Nenhum prazo vencido ou próximo.') }}</small> @if ($totalAbertas > 0) <div class="mt-1" style="font-size:11px">{{ __('Defina prazos ao criar demandas para receber alertas aqui.') }}</div> @endif </div> @endif {{-- Vencidas --}} @foreach ($proximasPrazo['vencidas'] as $dem) @php $diasAtraso = (int) \Carbon\Carbon::parse($dem->prazo)->diffInDays(now()); @endphp <div class="px-3 py-2 border-bottom" style="background:rgba(220,53,69,.04)"> <div class="d-flex justify-content-between align-items-start gap-2"> <div class="min-w-0"> <a href="{{ route('admin.demandas.show', $dem->id) }}" class="small fw-semibold text-body text-decoration-none d-block text-truncate"> {{ $dem->numero }} - {{ $dem->titulo }} </a> <small class="text-danger"> <i class="fa-solid fa-triangle-exclamation fa-xs me-1"></i>{{ __('Venceu há') }} {{ $diasAtraso }}d </small> </div> <small class="text-danger text-nowrap flex-shrink-0 fw-semibold">{{ $dem->prazo->format('d/m') }}</small> </div> </div> @endforeach {{-- Próximas --}} @foreach ($proximasPrazo['proximas'] as $dem) @php $diasRestantes = (int) now()->diffInDays(\Carbon\Carbon::parse($dem->prazo)); @endphp <div class="px-3 py-2 border-bottom"> <div class="d-flex justify-content-between align-items-start gap-2"> <div class="min-w-0"> <a href="{{ route('admin.demandas.show', $dem->id) }}" class="small fw-semibold text-body text-decoration-none d-block text-truncate"> {{ $dem->numero }} - {{ $dem->titulo }} </a> <small class="text-warning"> <i class="fa-solid fa-clock fa-xs me-1"></i>{{ __('Vence em') }} {{ $diasRestantes }}d </small> </div> <small class="text-muted text-nowrap flex-shrink-0">{{ $dem->prazo->format('d/m') }}</small> </div> </div> @endforeach </div> </div> </div> {{-- Recentes --}} <div class="col-12 col-lg-7"> <div class="card border-0 shadow-sm h-100"> <div class="card-body p-0"> <div class="d-flex align-items-center gap-2 px-3 pt-3 pb-2 border-bottom"> <i class="fa-solid fa-clock-rotate-left text-primary"></i> <h6 class="fw-semibold mb-0">{{ __('Recentes') }}</h6> </div> @if ($recentes->isEmpty()) <div class="text-center py-5 text-muted small"> <i class="fa-solid fa-inbox fa-2x d-block mb-2 opacity-25"></i> {{ __('Nenhuma demanda encontrada.') }} </div> @else <div class="table-responsive"> <table class="table table-hover align-middle mb-0 small"> <thead class="table-light"> <tr class="text-muted text-uppercase" style="font-size:11px;letter-spacing:.4px"> <th class="fw-semibold ps-3">{{ __('Nº') }}</th> <th class="fw-semibold">{{ __('Título') }}</th> <th class="fw-semibold">{{ __('Status') }}</th> <th class="fw-semibold text-end pe-3">{{ __('Abertura') }}</th> </tr> </thead> <tbody> @foreach ($recentes as $d) @php $sColor = match ($d->status) { 'em_andamento' => 'primary', 'pendente' => 'warning', 'atendido' => 'success', default => 'secondary', }; $pColor = match ($d->prioridade) { 'critica' => 'danger', 'alta' => 'warning', 'media' => 'info', default => 'success', }; @endphp <tr> <td class="ps-3 text-muted fw-semibold" style="font-size:11px">{{ $d->numero }}</td> <td> <a href="{{ route('admin.demandas.show', $d->id) }}" class="fw-semibold text-decoration-none text-truncate d-block" style="max-width:200px"> {{ $d->titulo }} </a> @if ($d->prazo && $d->prazo->isPast() && !in_array($d->status, ['atendido','cancelado'])) <small class="text-danger"> <i class="fa-solid fa-triangle-exclamation fa-xs me-1"></i>{{ __('Prazo vencido') }} </small> @endif </td> <td> <span class="badge bg-{{ $sColor }}"> {{ __($statusLabels[$d->status] ?? $d->status) }} </span> </td> <td class="text-muted text-end pe-3"> {{ $d->data_abertura?->format('d/m/Y') ?? '-' }} </td> </tr> @endforeach </tbody> </table> </div> <div class="text-end px-3 py-2 border-top"> <a href="{{ route('admin.demandas.index') }}" class="btn btn-sm btn-outline-primary"> {{ __('Ver todas') }} <i class="fa-solid fa-arrow-right"></i> </a> </div> @endif </div> </div> </div> </div> @endif {{-- fim condicional secretário/usuário --}} </div> </div> @endsection @if ($isSecretario) @push('after-scripts') <script> (function () { 'use strict'; // ── Tendência Mensal (Bar) ──────────────────────────────────────── const tLabels = @json($tendenciaLabs); const tData = @json($tendenciaVals); new Chart(document.getElementById('chartTendencia'), { type: 'bar', data: { labels: tLabels, datasets: [{ data: tData, backgroundColor: tData.map((_, i) => i === tData.length - 1 ? '#2C499F' : '#93aade'), borderRadius: 5, borderSkipped: false, }] }, options: { responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => ctx.parsed.y + ' demanda(s)' } } }, scales: { x: { grid: { display: false }, ticks: { font: { size: 11 } } }, y: { beginAtZero: true, ticks: { precision: 0, font: { size: 11 } }, grid: { color: '#f3f3f3' } } } } }); // ── Por Canal (Donut) ───────────────────────────────────────────── const canalEl = document.getElementById('chartCanal'); if (canalEl) { new Chart(canalEl, { type: 'doughnut', data: { labels: @json($canalLabels), datasets: [{ data: @json($canalValues), backgroundColor: @json($canalColors), borderWidth: 0, hoverOffset: 5, }] }, options: { cutout: '65%', responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => ctx.label + ': ' + ctx.parsed + ' demanda(s)' } } } } }); } // ── Por Unidade (Bar horizontal) ────────────────────────────────── const unidEl = document.getElementById('chartUnidades'); if (unidEl) { new Chart(unidEl, { type: 'bar', data: { labels: @json($unidadeLabels), datasets: [{ data: @json($unidadeValues), backgroundColor: '#2C499F', borderRadius: 4, }] }, options: { indexAxis: 'y', responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => ctx.parsed.x + ' demanda(s) abertas' } } }, scales: { x: { beginAtZero: true, ticks: { precision: 0, font: { size: 11 } }, grid: { color: '#f3f3f3' } }, y: { grid: { display: false }, ticks: { font: { size: 11 } } } } } }); } })(); </script> @endpush @endif
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings