File manager - Edit - /var/www/html/portal/app/Http/Controllers/StrategicDashboardController.php
Back
<?php namespace App\Http\Controllers; use App\Enums\CarteirinhaPaciente; use App\Models\CadastroDeAmbulantes\Ambulante; use App\Models\Comunicacao\Boletim; use App\Models\Comunicacao\Noticia; use App\Models\Escola; use App\Models\Saude\Fibromialgia\Fibromialgia; use App\Models\OcorrenciasPCA\Occurrence; use App\Models\OcorrenciasPCA\OccurrenceHistoric; use App\Models\PontoFocal; use App\Models\Accounts\Chamados\Chamado; use App\Models\Accounts\Chamados\ChamadoPesquisaSatisfacao; use App\Http\Controllers\ProjetosProgramas\ProjetosProgramasController; use App\Models\ProjetosProgramas\Objetivo; use App\Models\ProjetosProgramas\Programa as ProgramaAuxiliar; use App\Models\ProjetosProgramas\ProjetoPrograma; use App\Models\ProjetosProgramas\Tipo; use App\Models\Parque; use App\Models\Siati\Candidate; use App\Models\Structure; use App\Models\Unidade; use App\Models\User; use App\Models\TurisAngra\VisiteAngra\Atrativo; use App\Http\Resources\OcorrenciasPcaResource; use App\Models\TurisAngra\VisiteAngra\Corredor; use App\Models\TurisAngra\VisiteAngra\Ponto as PontoTuristico; use App\Models\TurisAngra\EmpresasDeTurismo\EmpresaTurismo; use App\Models\TurisAngra\Guias\Guia; use App\Models\TurisAngra\FluxoDeOnibus\OnibusTurismo; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use App\Models\Ppp; use App\Models\Convenio; class StrategicDashboardController extends Controller { public function index(Request $request) { $occurrenceSummary = $this->buildOccurrenceSummary(); $pppSummary = $this->buildPppSummary(); /** @var OcorrenciasPcaResource $ocorrenciasPcaResource */ $ocorrenciasPcaResource = app(OcorrenciasPcaResource::class); $pcaDashboard = $ocorrenciasPcaResource->prepareDashboardData($request); $sections = [ [ 'title' => 'Serviços e Chamados', 'subtitle' => 'Monitoramento do PCA', 'icon' => 'fa-solid fa-headset', 'metrics' => [ ['label' => 'Chamados abertos', 'value' => $occurrenceSummary['totals']['abertos']], ['label' => 'Pendentes', 'value' => $occurrenceSummary['totals']['pendentes']], ['label' => 'Concluídos', 'value' => $occurrenceSummary['totals']['concluidos']], ['label' => 'Base monitorada', 'value' => $occurrenceSummary['totalChamados']], ], 'highlights' => $occurrenceSummary['latest'], 'links' => [ [ 'label' => 'Ver painel de ocorrências', 'url' => route('admin.ocorrencias-pca.index'), 'permission' => 'GERENCIADOR_DE_OCORRENCIAS_PCA', ], ], ], [ 'title' => 'Pesquisa de Satisfação', 'subtitle' => 'Avaliação (Sistema Conta Angra)', 'icon' => 'fa-solid fa-star', 'metrics' => $this->buildSatisfacaoMetrics(), 'highlights' => $this->buildSatisfacaoHighlights(), 'links' => [ [ 'label' => 'Ver chamados no Conta Angra', 'url' => route('admin.contaAngra.chamados.index'), 'permission' => 'GERENCIADOR_DE_CONTA_ANGRA', ], ], ], [ 'title' => 'Unidades de Saúde', 'subtitle' => 'Rede assistencial municipal', 'icon' => 'fa-solid fa-hospital', 'metrics' => [ ['label' => 'Unidades de saúde', 'value' => $this->safe(fn() => Unidade::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Unidade::where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get(['nome', 'tipoUnidadeSaude']) ->map(fn($unidade) => [ 'title' => $unidade->nome, 'meta' => $unidade->tipoUnidadeSaude ?? 'Tipo não informado', 'status' => 'Unidade ativa', 'status_class' => 'primary', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar unidades', 'url' => route('admin.unidadesSaude.index'), 'permission' => 'GERENCIADOR_DE_UNIDADES_SAUDE', ], ], ], [ 'title' => 'Programa Fibromialgia', 'subtitle' => 'Atendimento especializado', 'icon' => 'fa-solid fa-notes-medical', 'metrics' => [ ['label' => 'Pacientes cadastrados', 'value' => $this->safe(fn() => Fibromialgia::count(), 0)], [ 'label' => 'Carteirinhas ativas', 'value' => $this->safe(fn() => Fibromialgia::whereIn('carteirinha_paciente', [ 'ativa', 'renovada', 'impressa', ])->count(), 0) ], ], 'highlights' => $this->safe(function () { return Fibromialgia::with('user') ->latest('created_at') ->take(4) ->get() ->map(fn($item) => [ 'title' => ($item->user->firstName ?? 'Paciente') . ' ' . ($item->user->lastName ?? ''), 'meta' => optional($item->created_at)->format('d/m/Y'), 'status' => 'Cadastro Ativo', 'status_class' => 'success', ])->all(); }, []), 'links' => [ [ 'label' => 'Programa Fibromialgia', 'url' => route('admin.saude.fibromialgia.index'), 'permission' => 'GERENCIADOR_DO_CADASTRO_DE_PACIENTES_COM_FIBROMIALGIA', ], ], ], [ 'title' => 'Educação', 'subtitle' => 'Unidades Educacionais', 'icon' => 'fa-solid fa-school', 'metrics' => [ ['label' => 'Escolas ativas', 'value' => $this->safe(fn() => Escola::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Escola::where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get(['nome', 'tipoUnidadeEscolar']) ->map(fn($escola) => [ 'title' => $escola->nome, 'meta' => $escola->tipoUnidadeEscolar ?? 'Tipo não informado', 'status' => 'Ativa', 'status_class' => 'success', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar escolas', 'url' => route('admin.escolas.index'), 'permission' => 'GERENCIADOR_DE_ESCOLAS', ], ], ], [ 'title' => 'Oportunidades', 'subtitle' => 'Banco de currículos', 'icon' => 'fa-solid fa-briefcase', 'metrics' => [ ['label' => 'Currículos ativos', 'value' => $this->safe(fn() => Candidate::count(), 0)], ], 'highlights' => $this->safe(function () { return Candidate::with('occupation') ->latest('created_at') ->take(4) ->get(['name', 'occupation_id']) ->map(fn($candidate) => [ 'title' => $candidate->name, 'meta' => $candidate->occupation?->name ?? 'Ocupação não informada', 'status' => 'Cadastrado', 'status_class' => 'info', ])->all(); }, []), 'links' => [ [ 'label' => 'Buscar currículos', 'url' => route('admin.curriculos.index'), 'permission' => 'GERENCIADOR_DE_CURRICULOS', ], ], ], [ 'title' => 'Comércio', 'subtitle' => 'Cadastro de Ambulantes', 'icon' => 'fa-solid fa-store', 'metrics' => [ ['label' => 'Ambulantes cadastrados', 'value' => $this->safe(fn() => Ambulante::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Ambulante::where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get(['numeroAutorizacao', 'tipoComercio']) ->map(fn($ambulante) => [ 'title' => 'Autorização ' . ($ambulante->numeroAutorizacao ?? 'N/D'), 'meta' => $ambulante->tipoComercio ?? 'Tipo não informado', 'status' => 'Licença ativa', 'status_class' => 'success', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar ambulantes', 'url' => route('admin.cadastroDeAmbulantes.ambulantes.index'), 'permission' => 'GERENCIADOR_CADASTRO_DE_AMBULANTES', ], ], ], [ 'title' => 'Visite Angra', 'subtitle' => 'Pontos e Atrativos', 'icon' => 'fa-solid fa-umbrella-beach', 'metrics' => [ ['label' => 'Corredores', 'value' => $this->safe(fn() => Corredor::where('status', 'ATIVADO')->count(), 0)], ['label' => 'Pontos turísticos', 'value' => $this->safe(fn() => PontoTuristico::where('status', 'ATIVADO')->count(), 0)], ['label' => 'Atrativos', 'value' => $this->safe(fn() => Atrativo::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Corredor::where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get(['corredor', 'subtitulo']) ->map(fn($corredor) => [ 'title' => $corredor->corredor, 'meta' => $corredor->subtitulo ?? 'Sem descrição', 'status' => 'Corredor ativo', 'status_class' => 'primary', ])->all(); }, []), 'links' => [ [ 'label' => 'Abrir Visite Angra', 'url' => route('admin.visiteAngra.index'), 'permission' => 'GERENCIADOR_VISITE_ANGRA', ], ], ], [ 'title' => 'Empresas de Turismo', 'subtitle' => 'Cadastro Municipal', 'icon' => 'fa-solid fa-hotel', 'metrics' => [ ['label' => 'Empresas ativas', 'value' => $this->safe(fn() => EmpresaTurismo::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return EmpresaTurismo::with('companhia') ->where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get() ->map(fn($item) => [ 'title' => $item->companhia->name ?? 'Empresa sem nome', 'meta' => 'Cadastur: ' . ($item->cadastur ?? 'N/D'), 'status' => 'Ativa', 'status_class' => 'success', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar empresas', 'url' => route('admin.empresasDeTurismo.index'), 'permission' => 'GERENCIADOR_DE_EMPRESAS_DE_TURISMO', ], ], ], [ 'title' => 'Guias Turísticos', 'subtitle' => 'Credenciamento', 'icon' => 'fa-solid fa-id-card', 'metrics' => [ ['label' => 'Guias cadastrados', 'value' => $this->safe(fn() => Guia::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Guia::with('user') ->where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get() ->map(fn($item) => [ 'title' => ($item->user->firstName ?? '') . ' ' . ($item->user->lastName ?? ''), 'meta' => 'CPF: ' . ($item->user->cpf ?? 'N/D'), 'status' => 'Guia Ativo', 'status_class' => 'info', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar guias', 'url' => route('admin.guias.index'), 'permission' => 'GERENCIADOR_DE_GUIAS_TURISTICOS', ], ], ], [ 'title' => 'Fluxo de Ônibus', 'subtitle' => 'Monitoramento de Entrada', 'icon' => 'fa-solid fa-bus', 'metrics' => [ ['label' => 'Registros (total)', 'value' => $this->safe(fn() => OnibusTurismo::count(), 0)], ['label' => 'Passageiros (total)', 'value' => $this->safe(fn() => OnibusTurismo::sum('numeroPassageiros'), 0)], ], 'highlights' => $this->safe(function () { return OnibusTurismo::latest('created_at') ->take(4) ->get() ->map(fn($item) => [ 'title' => $item->veiculo ?? 'Veículo', 'meta' => ($item->numeroPassageiros ?? 0) . ' passageiros', 'status' => optional($item->created_at)->format('d/m/Y'), 'status_class' => 'secondary', ])->all(); }, []), 'links' => [ [ 'label' => 'Ver fluxo de ônibus', 'url' => route('admin.turisAngra.fluxoDeOnibus.onibusTurismo.index'), 'permission' => 'GERENCIADOR_DE_FLUXO_DE_ONIBUS', ], ], ], [ 'title' => 'Urbanização', 'subtitle' => 'Parques e jardins', 'icon' => 'fa-solid fa-tree', 'metrics' => [ ['label' => 'Parques cadastrados', 'value' => $this->safe(fn() => Parque::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Parque::where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get(['nome', 'categorias']) ->map(fn($parque) => [ 'title' => $parque->nome, 'meta' => $parque->categorias ?? 'Sem categoria', 'status' => 'Parque ativo', 'status_class' => 'success', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar parques', 'url' => route('admin.parquesJardins.index'), 'permission' => 'GERENCIADOR_DE_PARQUES_E_JARDINS', ], ], ], [ 'title' => 'Projetos e Programas', 'subtitle' => 'Portfólio estratégico', 'icon' => 'fa-solid fa-diagram-project', 'metrics' => $this->buildProjetosProgramasMetrics(), 'highlights' => $this->safe(function () { return ProjetoPrograma::latest('created_at') ->take(4) ->get(['denominacao', 'status_registro']) ->map(fn($item) => [ 'title' => $item->denominacao, 'meta' => 'Status: ' . ($item->status_registro ?? 'em edição'), 'status' => 'Projeto/Programa', 'status_class' => 'warning', ])->all(); }, []), 'links' => [ [ 'label' => 'Painel Projetos e Programas (Prefeitura)', 'url' => route('admin.dashboardEstrategico.projetosProgramas'), 'permission' => 'GERENCIADOR_DO_DASHBOARD_ESTRATEGICO', ], [ 'label' => 'Abrir Projetos e Programas (SUPOP)', 'url' => route('admin.projetosProgramas.index'), 'permission' => 'GERENCIADOR_DE_PROJETOS_E_PROGRAMAS', ], ], ], [ 'title' => 'Organograma', 'subtitle' => 'Pessoas e estruturas', 'icon' => 'fa-solid fa-sitemap', 'metrics' => [ ['label' => 'Secretarias ativas', 'value' => $this->safe(fn() => Structure::where('status', 'ATIVADO')->where('type', 'SECRETARIA')->count(), 0)], ['label' => 'Usuários do portal', 'value' => $this->safe(fn() => User::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Structure::where('status', 'ATIVADO') ->where('type', 'SECRETARIA') ->orderBy('name') ->take(4) ->get(['name', 'manager']) ->map(fn($structure) => [ 'title' => $structure->name, 'meta' => $structure->manager ?? 'Gestor não informado', 'status' => 'Estrutura ativa', 'status_class' => 'secondary', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar secretarias', 'url' => route('admin.secretarias.index'), 'permission' => 'ADMIN', ], [ 'label' => 'Gerenciar usuários', 'url' => route('admin.contas.index'), 'permission' => 'ADMIN', ], ], ], [ 'title' => 'Comunicação', 'subtitle' => 'Conteúdos publicados', 'icon' => 'fa-solid fa-bullhorn', 'metrics' => [ ['label' => 'Notícias ativas', 'value' => $this->safe(fn() => Noticia::where('status', 'ATIVADO')->count(), 0)], ['label' => 'Boletins publicados', 'value' => $this->safe(fn() => Boletim::where('status', 'ATIVADO')->count(), 0)], ], 'highlights' => $this->safe(function () { return Noticia::where('status', 'ATIVADO') ->latest('created_at') ->take(4) ->get(['title', 'created_at']) ->map(fn($news) => [ 'title' => $news->title, 'meta' => optional($news->created_at)->format('d/m/Y'), 'status' => 'Notícia', 'status_class' => 'info', ])->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar notícias', 'url' => route('admin.noticias.index'), 'permission' => 'GERENCIADOR_DE_NOTICIAS', ], [ 'label' => 'Gerenciar boletins', 'url' => route('admin.boletins.index'), 'permission' => 'GERENCIADOR_DE_BOLETINS', ], ], ], [ 'title' => 'SEI! - Pontos Focais', 'subtitle' => 'Times ligados ao SEI', 'icon' => 'fa-solid fa-network-wired', 'metrics' => [ ['label' => 'Pontos focais ativos', 'value' => $this->safe(fn() => PontoFocal::count(), 0)], ], 'highlights' => $this->safe(function () { return PontoFocal::with('instituicao') ->latest('created_at') ->take(4) ->get() ->map(fn($ponto) => [ 'title' => $ponto->nome, 'meta' => $ponto->instituicao->name ?? 'Sem secretaria', 'status' => $ponto->telefone_ramal ?? $ponto->email ?? 'Contato não informado', 'status_class' => 'secondary', ])->all(); }, []), 'links' => [ [ 'label' => 'Abrir módulo Pontos Focais', 'url' => route('admin.pontosFocais.index'), 'permission' => 'GERENCIADOR_DE_PONTOS_FOCAIS', ], ], ], ]; $comingSoonSections = [ [ 'title' => 'Finanças', 'notes' => 'API passiva com SIARM (TIPLAN).', 'items' => [ ['label' => 'Receita prevista x arrecadada', 'value' => 'Em Planejamento'], ['label' => 'Despesas por secretaria', 'value' => 'Em Planejamento'], ['label' => 'Indicadores de repasses', 'value' => 'Em Planejamento'], ], ], [ 'title' => 'Obras e Projetos', 'notes' => 'Integração com GOP.', 'items' => [ ['label' => 'Obras ativas', 'value' => 'Em Planejamento'], ['label' => 'Obras concluídas', 'value' => 'Em Planejamento'], ['label' => 'Obras atrasadas', 'value' => 'Em Planejamento'], ], ], [ 'title' => 'Mobilidade e Transporte', 'notes' => 'Aguardando consolidação dos indicadores da SMTr.', 'items' => [ ['label' => 'Reclamações registradas', 'value' => 'Em Planejamento'], ['label' => 'Linhas críticas', 'value' => 'Em Planejamento'], ['label' => 'Ocorrências operacionais', 'value' => 'Em Planejamento'], ], ], ]; $chartPayload = [ 'occurrenceTrend' => $this->buildOccurrenceTrendChart(), 'communicationTrend' => $this->buildCommunicationChart(), 'fibroStatus' => $this->buildFibroStatusChart(), 'satisfacaoGeral' => $this->buildSatisfacaoGeralChart(), 'satisfacaoPorServico' => $this->buildSatisfacaoPorServicoChart(), 'taxaResolucao' => $this->buildTaxaResolucaoChart(), 'evolucaoSatisfacao' => $this->buildEvolucaoSatisfacaoChart(), 'satisfacaoPorServicoDetalhado' => $this->buildSatisfacaoPorServicoDetalhado(), 'pppDistribution' => $this->buildPppDistributionChart(), ]; return view('admin.dashboard-estrategico.index', [ 'sections' => $sections, 'comingSoonSections' => $comingSoonSections, 'generatedAt' => now(), 'chartPayload' => $chartPayload, 'pppSummary' => $pppSummary, 'pcaDashboard' => $pcaDashboard, 'projetosProgramasDashboardEmbed' => app(ProjetosProgramasController::class) ->buildProjetosProgramasDashboardEmbedPayload($request), ]); } public function exportPpp(string $id, string $type) { $ppp = Ppp::findOrFail($id); $filename = 'relatorio_ppp_' . Str::slug($ppp->projeto) . '_' . date('YmdHis'); if ($type === 'pdf') { $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('admin.ppps.export_pdf', compact('ppp')); $pdf->setPaper('a4', 'portrait'); return $pdf->download($filename . '.pdf'); } elseif ($type === 'xlsx') { return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\PppExport($id), $filename . '.xlsx'); } elseif ($type === 'csv') { return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\PppExport($id), $filename . '.csv', \Maatwebsite\Excel\Excel::CSV); } abort(404); } /** * Retorna o mesmo payload de PPPs e Convênios usado no dashboard, para uso na página pública. */ public function getPppSummaryForPublic(): array { $summary = $this->buildPppSummary(); $summary['chartDistribution'] = $this->buildPppDistributionChart(); return $summary; } private function buildPppSummary(): array { return [ 'title' => 'PPPs e Convênios', 'subtitle' => 'Investimentos e Parcerias', 'icon' => 'fa-solid fa-handshake', 'metrics' => [ [ 'label' => 'PPPs ativas', 'value' => $this->safe(fn() => Ppp::where('status', 'ATIVADO')->count(), 0) ], [ 'label' => 'Convênios ativos', 'value' => $this->safe(fn() => Convenio::where('status', 'ATIVADO')->count(), 0) ], [ 'label' => 'Total Investido (PPPs)', 'value' => 'R$ ' . $this->safe( fn() => number_format( Ppp::where('status', 'ATIVADO') ->get() ->sum(function ($p) { return (float) str_replace( ['.', ','], ['', '.'], str_replace(['R$', ' '], '', $p->investimento_total) ); }), 2, ',', '.' ), '0,00' ) ], [ 'label' => 'Total Investido (Convênios)', 'value' => 'R$ ' . $this->safe( fn() => number_format( Convenio::where('status', 'ATIVADO') ->get() ->sum(function ($c) { return (float) str_replace( ['.', ','], ['', '.'], str_replace(['R$', ' '], '', $c->valor_global) ); }), 2, ',', '.' ), '0,00' ) ], ], 'highlights' => $this->safe(function () { $ppps = Ppp::with('historicos')->where('status', 'ATIVADO') ->latest('created_at') ->get() ->map(fn($item) => [ 'id' => $item->id, 'type' => 'ppp', 'title' => $item->projeto, 'meta' => 'PPP - ' . ($item->andamento ?? 'Em andamento'), 'status' => 'Ativo', 'status_class' => 'primary', 'created_at' => $item->created_at, 'historicos' => $item->historicos->map(fn($h) => [ 'orgao' => $h->orgao, 'observacao' => $h->observacao, 'data' => $h->data ? \Carbon\Carbon::parse($h->data)->format('d/m/Y') : $h->created_at->format('d/m/Y \à\s H:i'), 'data_fim' => $h->data_fim ? \Carbon\Carbon::parse($h->data_fim)->format('d/m/Y') : null, ])->toArray(), 'details' => array_filter([ // 🔹 IDENTIFICAÇÃO 'Projeto' => $item->show_projeto ? $item->projeto : null, 'Empresa' => $item->show_empresa ? $item->empresa : null, 'Secretaria Responsável' => $item->show_secretaria_responsavel ? $item->secretaria_responsavel : null, 'Critério de Julgamento' => $item->show_criterio_julgamento ? $item->criterio_julgamento : null, 'Andamento' => $item->show_andamento ? $item->andamento : null, 'Próxima Etapa' => $item->show_proxima_etapa ? $item->proxima_etapa : null, // 🔹 PRAZOS 'Prazo de Concessão' => $item->show_prazo_concessao ? $item->prazo_concessao : null, 'Prazo de Implantação' => $item->show_prazo_implantacao ? $item->prazo_implantacao : null, // 🔹 VALORES 'Valor Estimado do Contrato' => $item->show_valor_estimado_contrato ? $item->valor_estimado_contrato : null, 'Investimento Total' => $item->show_investimento_total ? $item->investimento_total : null, 'Outorga Fixa' => $item->show_outorga_fixa ? $item->outorga_fixa : null, 'Outorga Variável' => $item->show_outorga_variavel ? $item->outorga_variavel : null, 'Aporte do Município' => $item->show_aporte_municipio ? $item->aporte_municipio : null, 'Contraprestação Mensal' => $item->show_contraprestacao_mensal ? $item->contraprestacao_mensal : null, 'Payback' => $item->show_payback ? $item->payback : null, 'TIR' => $item->show_tir ? $item->tir : null, 'CAPEX' => $item->show_capex ? $item->capex : null, 'OPEX' => $item->show_opex ? $item->opex : null, // 🔹 COMPLEMENTARES 'Conclusão (%)' => $item->porcentagem_conclusao, 'Obrigações' => $item->show_obrigacao ? $item->obrigacao : null, 'Observações' => $item->show_observacoes ? $item->observacoes : null, ]), ]); $convenios = Convenio::where('status', 'ATIVADO') ->latest('created_at') ->get() ->map(fn($item) => [ 'id' => $item->id, 'type' => 'convenio', 'title' => Str::limit($item->objeto, 60), 'meta' => 'Convênio - ' . ($item->valor_global ?? 'R$ 0,00'), 'status' => 'Ativo', 'status_class' => 'success', 'created_at' => $item->created_at, // ⚠ necessário para sortByDesc 'details' => array_filter([ 'Objeto' => $item->show_objeto ? $item->objeto : null, 'Executor / Ordenador' => $item->show_executor_ordenador ? $item->executor_ordenador : null, 'Concedente' => $item->show_concedente ? $item->concedente : null, 'Local' => $item->show_local ? $item->local : null, 'Status / Plataforma' => $item->show_status_plataforma ? $item->status_plataforma : null, 'Programa' => $item->show_programa ? $item->programa : null, 'Programa Nº PAC' => $item->show_programa_pac ? $item->programa_pac : null, 'Tipo de Programa' => $item->show_tipo_programa ? $item->tipo_programa : null, 'Proposta ID Nº' => $item->show_proposta_id ? $item->proposta_id : null, 'Número do instrumento' => $item->show_numero_instrumento ? $item->numero_instrumento : null, 'Número do processo SEI' => $item->show_numero_processo_sei ? $item->numero_processo_sei : null, 'Data do recebimento do recurso' => $item->show_data_recebimento_recurso ? $this->formatDateForDashboard($item->data_recebimento_recurso) : null, 'Data de assinatura do instrumento' => $item->show_data_assinatura_instrumento ? $this->formatDateForDashboard($item->data_assinatura_instrumento) : null, 'Andamento' => $item->show_andamento ? $item->andamento : null, 'Vigência da cláusula suspensiva' => $item->show_vigencia_clausula_suspensiva ? $this->formatDateForDashboard($item->vigencia_clausula_suspensiva) : null, 'Vigência contratual' => $item->show_vigencia_prazo_contratual ? $this->formatDateForDashboard($item->vigencia_prazo_contratual) : null, 'Porcentagem' => $item->show_porcentagem_conclusao ? $item->porcentagem_conclusao : null, 'Prazo de Cadastro' => $item->show_prazo_cadastro ? $this->formatDateForDashboard($item->prazo_cadastro) : null, 'Data' => $this->resolveConvenioReferenceDate($item), 'Prazo de Execução (Meses)' => $item->show_prazo_execucao_meses ? $item->prazo_execucao_meses : null, 'Contrapartida' => $item->show_contrapartida ? $item->contrapartida : null, 'Repasse' => $item->show_repasse ? $item->repasse : null, 'Valor Global' => $item->show_valor_global ? $item->valor_global : null, ]), ]); return $ppps->merge($convenios)->sortByDesc('created_at')->values()->all(); }, []), 'links' => [ [ 'label' => 'Gerenciar PPPs e Convênios', 'url' => route('admin.ppps.index'), 'permission' => 'GERENCIADOR_DE_PPPS', ], ], ]; } private function buildPppDistributionChart(): array { return $this->safe(function () { $pppCount = Ppp::where('status', 'ATIVADO')->count(); $convenioCount = Convenio::where('status', 'ATIVADO')->count(); return [ 'labels' => ['PPPs', 'Convênios'], 'values' => [$pppCount, $convenioCount], ]; }, ['labels' => [], 'values' => []]); } private function buildOccurrenceSummary(): array { $default = [ 'totals' => [ 'abertos' => 0, 'pendentes' => 0, 'concluidos' => 0, ], 'totalChamados' => 0, 'latest' => [], ]; return $this->safe(function () { $latestStatusSub = OccurrenceHistoric::select('occurrence_id', DB::raw('MAX(created_at) as max_created_at')) ->groupBy('occurrence_id'); $statusCounts = OccurrenceHistoric::from('historics') ->joinSub($latestStatusSub, 'latest', function ($join) { $join->on('historics.occurrence_id', '=', 'latest.occurrence_id') ->on('historics.created_at', '=', 'latest.max_created_at'); }) ->select('historics.status', DB::raw('COUNT(*) as total')) ->groupBy('historics.status') ->get(); $totals = [ 'abertos' => 0, 'pendentes' => 0, 'concluidos' => 0, ]; foreach ($statusCounts as $row) { $bucket = $this->bucketizeStatus($row->status); $totals[$bucket] += (int) $row->total; } $totalChamados = array_sum($totals); $recent = Occurrence::select('id', 'code', 'name', 'created_at') ->latest('created_at') ->with([ 'historics' => function ($query) { $query->latest('created_at')->limit(1); } ]) ->take(5) ->get() ->map(function ($occurrence) { $status = optional($occurrence->historics->first())->status ?? 'Sem status'; return [ 'title' => $occurrence->code ?? ('Ocorrência ' . substr($occurrence->id, -6)), 'meta' => sprintf( '%s · %s', optional($occurrence->created_at)->format('d/m/Y'), Str::limit((string) $occurrence->name, 40) ), 'status' => $status, 'status_class' => $this->statusBadgeClass($status), ]; }) ->all(); return [ 'totals' => $totals, 'totalChamados' => $totalChamados, 'latest' => $recent, ]; }, $default); } private function buildProjetosProgramasMetrics(): array { return $this->safe(function () { $tipoIds = Tipo::whereIn('nome', ['Projeto'])->pluck('id', 'nome'); $projetos = $tipoIds->has('Projeto') ? ProjetoPrograma::where('tipo_id', $tipoIds['Projeto'])->count() : 0; $programas = $this->safe(fn() => ProgramaAuxiliar::count(), 0); $objetivos = $this->safe(fn() => Objetivo::count(), 0); $prioridades = $this->safe(fn() => ProjetoPrograma::where('prioritario', true)->count(), 0); return [ ['label' => 'Projetos', 'value' => $projetos], ['label' => 'Programas (cadastro auxiliar)', 'value' => $programas], ['label' => 'Objetivos', 'value' => $objetivos], ['label' => 'Prioridades ativas', 'value' => $prioridades], ]; }, [ ['label' => 'Projetos', 'value' => 0], ['label' => 'Programas (cadastro auxiliar)', 'value' => 0], ['label' => 'Objetivos', 'value' => 0], ['label' => 'Prioridades ativas', 'value' => 0], ]); } private function bucketizeStatus(?string $status): string { $normalized = Str::lower($status ?? ''); if (Str::contains($normalized, ['final', 'atendid', 'conclu', 'encerr'])) { return 'concluidos'; } if (Str::contains($normalized, ['aguard', 'pend', 'anal', 'andament'])) { return 'pendentes'; } return 'abertos'; } private function statusBadgeClass(?string $status): string { $normalized = Str::lower($status ?? ''); if (Str::contains($normalized, ['final', 'conclu', 'encerr'])) { return 'success'; } if (Str::contains($normalized, ['aguard', 'pend', 'anal'])) { return 'warning'; } if (Str::contains($normalized, ['nao atendido', 'cancel'])) { return 'danger'; } return 'secondary'; } private function safe(callable $callback, $fallback) { try { return $callback(); } catch (\Throwable $th) { report($th); return $fallback; } } private function buildOccurrenceTrendChart(int $months = 6): array { $monthSequence = $this->monthSequence($months); $occurrencePerMonth = $this->safe(function () use ($monthSequence) { return Occurrence::select( DB::raw("DATE_FORMAT(created_at, '%Y-%m') as ym"), DB::raw('COUNT(*) as total') ) ->where('created_at', '>=', $monthSequence->first()->copy()->startOfMonth()) ->groupBy('ym') ->pluck('total', 'ym'); }, collect()); return [ 'labels' => $monthSequence->map(fn($date) => $date->translatedFormat('M/Y'))->values(), 'values' => $monthSequence->map(fn($date) => (int) ($occurrencePerMonth[$date->format('Y-m')] ?? 0))->values(), ]; } private function buildCommunicationChart(int $months = 6): array { $monthSequence = $this->monthSequence($months); $newsCounts = $this->safe(function () use ($monthSequence) { return Noticia::select( DB::raw("DATE_FORMAT(created_at, '%Y-%m') as ym"), DB::raw('COUNT(*) as total') ) ->where('status', 'ATIVADO') ->where('created_at', '>=', $monthSequence->first()->copy()->startOfMonth()) ->groupBy('ym') ->pluck('total', 'ym'); }, collect()); $bulletinCounts = $this->safe(function () use ($monthSequence) { return Boletim::select( DB::raw("DATE_FORMAT(created_at, '%Y-%m') as ym"), DB::raw('COUNT(*) as total') ) ->where('status', 'ATIVADO') ->where('created_at', '>=', $monthSequence->first()->copy()->startOfMonth()) ->groupBy('ym') ->pluck('total', 'ym'); }, collect()); return [ 'labels' => $monthSequence->map(fn($date) => $date->translatedFormat('M/Y'))->values(), 'news' => $monthSequence->map(fn($date) => (int) ($newsCounts[$date->format('Y-m')] ?? 0))->values(), 'bulletins' => $monthSequence->map(fn($date) => (int) ($bulletinCounts[$date->format('Y-m')] ?? 0))->values(), ]; } private function buildFibroStatusChart(int $limit = 6): array { $raw = $this->safe(function () { return Fibromialgia::select('carteirinha_paciente', DB::raw('COUNT(*) as total')) ->groupBy('carteirinha_paciente') ->get(); }, collect()); $grouped = [ 'Ativas' => 0, 'Emitidas' => 0, 'Pendentes' => 0, 'Inválidas' => 0, 'Outros' => 0, ]; foreach ($raw as $row) { $status = $row->carteirinha_paciente; if (in_array($status, ['ativa', 'renovada', 'impressa'])) { $grouped['Ativas'] += (int) $row->total; } elseif ($status == 'emitida') { $grouped['Emitidas'] += (int) $row->total; } elseif ($status == 'pendente_validacao') { $grouped['Pendentes'] += (int) $row->total; } elseif ($status == 'invalida') { $grouped['Inválidas'] += (int) $row->total; } else { $grouped['Outros'] += (int) $row->total; } } $labels = []; $values = []; foreach ($grouped as $label => $total) { if ($total > 0) { $labels[] = $label; $values[] = $total; } } return [ 'labels' => $labels, 'values' => $values, ]; } private function monthSequence(int $months = 6): \Illuminate\Support\Collection { $months = max(1, $months); return collect(range($months - 1, 0))->map(function ($index) { return Carbon::now()->subMonths($index)->locale('pt_BR'); }); } /** * Constrói métricas de satisfação */ private function buildSatisfacaoMetrics(): array { return $this->safe(function () { $totalPesquisas = ChamadoPesquisaSatisfacao::count(); $chamadosResolvidos = ChamadoPesquisaSatisfacao::where('chamado_resolvido', true)->count(); $problemasResolvidos = ChamadoPesquisaSatisfacao::where('problema_resolvido', true)->count(); $satisfacaoAlta = ChamadoPesquisaSatisfacao::whereIn('satisfacao', ['Bom', 'Muito Bom'])->count(); return [ ['label' => 'Total de pesquisas', 'value' => $totalPesquisas], ['label' => 'Chamados resolvidos', 'value' => $chamadosResolvidos], ['label' => 'Problemas resolvidos', 'value' => $problemasResolvidos], ['label' => 'Satisfação alta', 'value' => $satisfacaoAlta], ]; }, [ ['label' => 'Total de pesquisas', 'value' => 0], ['label' => 'Chamados resolvidos', 'value' => 0], ['label' => 'Problemas resolvidos', 'value' => 0], ['label' => 'Satisfação alta', 'value' => 0], ]); } /** * Constrói highlights de satisfação */ private function buildSatisfacaoHighlights(): array { return $this->safe(function () { return ChamadoPesquisaSatisfacao::with(['chamado']) ->latest('created_at') ->take(5) ->get() ->map(function ($pesquisa) { $servico = $pesquisa->chamado->convenioAtual ?? $pesquisa->chamado->convenio ?? 'Serviço não informado'; $satisfacao = $pesquisa->satisfacao ?? 'Não avaliado'; return [ 'title' => $servico, 'meta' => sprintf( '%s · %s', optional($pesquisa->created_at)->format('d/m/Y'), Str::limit($pesquisa->chamado->assunto ?? 'Sem assunto', 30) ), 'status' => $satisfacao, 'status_class' => $this->satisfacaoBadgeClass($satisfacao), ]; }) ->all(); }, []); } /** * Retorna classe CSS para badge de satisfação */ private function satisfacaoBadgeClass(?string $satisfacao): string { $normalized = Str::lower($satisfacao ?? ''); if (Str::contains($normalized, ['muito bom'])) { return 'success'; } if (Str::contains($normalized, ['bom'])) { return 'info'; } if (Str::contains($normalized, ['regular'])) { return 'warning'; } if (Str::contains($normalized, ['ruim', 'muito ruim'])) { return 'danger'; } return 'secondary'; } /** * Constrói gráfico de distribuição geral de satisfação */ private function buildSatisfacaoGeralChart(): array { return $this->safe(function () { $satisfacoes = ChamadoPesquisaSatisfacao::select('satisfacao', DB::raw('COUNT(*) as total')) ->whereNotNull('satisfacao') ->groupBy('satisfacao') ->orderByRaw("FIELD(satisfacao, 'Muito Ruim', 'Ruim', 'Regular', 'Bom', 'Muito Bom')") ->get(); $labels = $satisfacoes->pluck('satisfacao')->map(fn($s) => __($s))->values(); $values = $satisfacoes->pluck('total')->map(fn($v) => (int) $v)->values(); return [ 'labels' => $labels->all(), 'values' => $values->all(), ]; }, [ 'labels' => [], 'values' => [], ]); } /** * Constrói gráfico de satisfação por serviço */ private function buildSatisfacaoPorServicoChart(): array { return $this->safe(function () { $dados = ChamadoPesquisaSatisfacao::with('chamado') ->whereNotNull('satisfacao') ->get() ->groupBy(function ($pesquisa) { return $pesquisa->chamado->convenioAtual ?? $pesquisa->chamado->convenio ?? 'Outros'; }) ->map(function ($pesquisas) { $total = $pesquisas->count(); $satisfacaoAlta = $pesquisas->whereIn('satisfacao', ['Bom', 'Muito Bom'])->count(); return [ 'total' => $total, 'satisfacao_alta' => $satisfacaoAlta, 'percentual' => $total > 0 ? round(($satisfacaoAlta / $total) * 100, 1) : 0, ]; }) ->sortByDesc('total') ->take(10); return [ 'servicos' => $dados->keys()->map(fn($s) => Str::limit($s, 30))->values()->all(), 'totais' => $dados->pluck('total')->values()->all(), 'satisfacao_alta' => $dados->pluck('satisfacao_alta')->values()->all(), 'percentuais' => $dados->pluck('percentual')->values()->all(), ]; }, [ 'servicos' => [], 'totais' => [], 'satisfacao_alta' => [], 'percentuais' => [], ]); } /** * Constrói gráfico de taxa de resolução */ private function buildTaxaResolucaoChart(): array { return $this->safe(function () { $total = ChamadoPesquisaSatisfacao::count(); if ($total === 0) { return [ 'labels' => [], 'chamado_resolvido' => [], 'problema_resolvido' => [], ]; } $chamadoResolvido = ChamadoPesquisaSatisfacao::where('chamado_resolvido', true)->count(); $problemaResolvido = ChamadoPesquisaSatisfacao::where('problema_resolvido', true)->count(); $chamadoNaoResolvido = $total - $chamadoResolvido; $problemaNaoResolvido = $total - $problemaResolvido; return [ 'labels' => [__('Resolvido'), __('Não Resolvido')], 'chamado_resolvido' => [$chamadoResolvido, $chamadoNaoResolvido], 'problema_resolvido' => [$problemaResolvido, $problemaNaoResolvido], ]; }, [ 'labels' => [], 'chamado_resolvido' => [], 'problema_resolvido' => [], ]); } /** * Constrói gráfico de evolução temporal da satisfação */ private function buildEvolucaoSatisfacaoChart(int $months = 6): array { $monthSequence = $this->monthSequence($months); return $this->safe(function () use ($monthSequence) { $satisfacoes = ['Muito Ruim', 'Ruim', 'Regular', 'Bom', 'Muito Bom']; $dados = []; foreach ($satisfacoes as $satisfacao) { $porMes = ChamadoPesquisaSatisfacao::select( DB::raw("DATE_FORMAT(created_at, '%Y-%m') as ym"), DB::raw('COUNT(*) as total') ) ->where('satisfacao', $satisfacao) ->where('created_at', '>=', $monthSequence->first()->copy()->startOfMonth()) ->groupBy('ym') ->pluck('total', 'ym'); $dados[$satisfacao] = $monthSequence->map(fn($date) => (int) ($porMes[$date->format('Y-m')] ?? 0))->values(); } return [ 'labels' => $monthSequence->map(fn($date) => $date->translatedFormat('M/Y'))->values()->all(), 'muito_ruim' => $dados['Muito Ruim']->all(), 'ruim' => $dados['Ruim']->all(), 'regular' => $dados['Regular']->all(), 'bom' => $dados['Bom']->all(), 'muito_bom' => $dados['Muito Bom']->all(), ]; }, [ 'labels' => [], 'muito_ruim' => [], 'ruim' => [], 'regular' => [], 'bom' => [], 'muito_bom' => [], ]); } /** * Constrói dados detalhados de satisfação por serviço (gráfico horizontal) * Retorna um array com dados para cada serviço que possui pesquisas */ private function buildSatisfacaoPorServicoDetalhado(): array { return $this->safe(function () { // Busca todos os serviços únicos que têm pesquisas de satisfação $servicos = ChamadoPesquisaSatisfacao::with('chamado') ->whereNotNull('satisfacao') ->get() ->groupBy(function ($pesquisa) { return $pesquisa->chamado->convenioAtual ?? $pesquisa->chamado->convenio ?? 'Outros'; }); $resultado = []; foreach ($servicos as $nomeServico => $pesquisas) { $total = $pesquisas->count(); if ($total === 0) { continue; } // Conta cada nível de satisfação $muitoBom = $pesquisas->where('satisfacao', 'Muito Bom')->count(); $bom = $pesquisas->where('satisfacao', 'Bom')->count(); $regular = $pesquisas->where('satisfacao', 'Regular')->count(); $ruim = $pesquisas->where('satisfacao', 'Ruim')->count(); $muitoRuim = $pesquisas->where('satisfacao', 'Muito Ruim')->count(); // Calcula porcentagens $percentMuitoBom = $total > 0 ? round(($muitoBom / $total) * 100, 2) : 0; $percentBom = $total > 0 ? round(($bom / $total) * 100, 2) : 0; $percentRegular = $total > 0 ? round(($regular / $total) * 100, 2) : 0; $percentRuim = $total > 0 ? round(($ruim / $total) * 100, 2) : 0; $percentMuitoRuim = $total > 0 ? round(($muitoRuim / $total) * 100, 2) : 0; // Cria um slug único para o serviço (para usar como ID do canvas) $servicoSlug = Str::slug($nomeServico); $resultado[$servicoSlug] = [ 'nome' => $nomeServico, 'total' => $total, 'dados' => [ [ 'categoria' => 'Muito Bom', 'votos' => $muitoBom, 'percentual' => $percentMuitoBom, ], [ 'categoria' => 'Bom', 'votos' => $bom, 'percentual' => $percentBom, ], [ 'categoria' => 'Regular', 'votos' => $regular, 'percentual' => $percentRegular, ], [ 'categoria' => 'Ruim', 'votos' => $ruim, 'percentual' => $percentRuim, ], [ 'categoria' => 'Muito Ruim', 'votos' => $muitoRuim, 'percentual' => $percentMuitoRuim, ], ], ]; } return $resultado; }, []); } /** * Formata datas de convênio aceitando Carbon, DateTime e string. */ private function formatDateForDashboard($value): ?string { if (empty($value)) { return null; } try { if ($value instanceof \DateTimeInterface) { return Carbon::instance($value)->format('d/m/Y'); } return Carbon::parse((string) $value)->format('d/m/Y'); } catch (\Throwable) { return null; } } /** * Resolve a melhor data de referência para o card de convênios. */ private function resolveConvenioReferenceDate(Convenio $item): ?string { $candidates = [ ['visible' => (bool) $item->show_data, 'value' => $item->data], ['visible' => (bool) $item->show_data_assinatura_instrumento, 'value' => $item->data_assinatura_instrumento], ['visible' => (bool) $item->show_data_recebimento_recurso, 'value' => $item->data_recebimento_recurso], ['visible' => (bool) $item->show_vigencia_prazo_contratual, 'value' => $item->vigencia_prazo_contratual], ['visible' => (bool) $item->show_prazo_cadastro, 'value' => $item->prazo_cadastro], ]; foreach ($candidates as $candidate) { if (!$candidate['visible']) { continue; } $formattedDate = $this->formatDateForDashboard($candidate['value']); if ($formattedDate !== null) { return $formattedDate; } } // Fallback para bases legadas onde flags de visibilidade podem não estar preenchidas. foreach ($candidates as $candidate) { $formattedDate = $this->formatDateForDashboard($candidate['value']); if ($formattedDate !== null) { return $formattedDate; } } return null; } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.13 |
proxy
|
phpinfo
|
Settings