gabrielb 0c9930d8f7
All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m35s
fix(certidao): corrigir reinicialização do documento com base na autenticação do usuário
2026-05-20 12:51:09 -03:00

230 lines
12 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue'
import { useAuth } from '@/composables/useAuth'
import { portalService } from '@/services/portalService'
import { usePrefeituraStore } from '@/stores/prefeituraStore'
definePageMeta({
layout: 'portal',
middleware: 'auth',
})
const { nomeUsuario } = useAuth()
const prefeitura = usePrefeituraStore()
const router = useRouter()
// ─── MOCKS APRESENTAÇÃO — remover antes do deploy ─────────────────────────
const MOCK_ATIVO = true
const RESUMO_MOCK = {
totalDebitos: 1250.90,
certidoesAtivas: 2,
alvarasAndamento: 2,
ultimoPagamento: 430.00,
debitosVencidos: 1,
}
const ATIVIDADES_MOCK = [
{ tipo: 'PAGAMENTO', descricao: 'IPTU 2025 — Parcela 3/10 paga', data: '15/05/2025' },
{ tipo: 'CERTIDAO', descricao: 'Certidão Negativa emitida', data: '10/05/2025' },
{ tipo: 'DEBITO', descricao: 'Guia IPTU 2025 — Cota 4 emitida', data: '02/05/2025' },
{ tipo: 'ALVARA', descricao: 'Alvará de funcionamento em análise', data: '28/04/2025' },
{ tipo: 'CADASTRO', descricao: 'E-mail cadastral atualizado', data: '20/04/2025' },
]
// ──────────────────────────────────────────────────────────────────────────
const resumo = ref(null)
const atividades = ref([])
const carregando = ref(true)
onMounted(carregar)
async function carregar() {
carregando.value = true
try {
const [resResumo, resAtividades] = await Promise.all([
portalService.getPainelResumo(),
portalService.getAtividades(),
])
resumo.value = resResumo.data
atividades.value = resAtividades.data?.content ?? []
if (MOCK_ATIVO) {
if (!resumo.value) resumo.value = RESUMO_MOCK
if (atividades.value.length === 0) atividades.value = ATIVIDADES_MOCK
}
} catch {
if (MOCK_ATIVO) {
resumo.value = RESUMO_MOCK
atividades.value = ATIVIDADES_MOCK
}
} finally {
carregando.value = false
}
}
const acesRapidos = [
{ icon: 'pi-receipt', label: 'Emitir Guia', to: '/portal/debitos', cor: 'text-primary' },
{ icon: 'pi-file-check', label: 'Nova Certidão', to: '/portal/certidoes', cor: 'text-emerald-600 dark:text-emerald-400' },
{ icon: 'pi-briefcase', label: 'Acompanhar Alvará', to: '/portal/alvaras', cor: 'text-amber-600 dark:text-amber-400' },
{ icon: 'pi-user', label: 'Meus Dados', to: '/portal/dados', cor: 'text-violet-600 dark:text-violet-400' },
]
function formatarMoeda(valor) {
const n = Number(valor ?? 0)
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number.isNaN(n) ? 0 : n)
}
const iconeAtividade = {
DEBITO: 'pi-receipt',
CERTIDAO: 'pi-file-check',
ALVARA: 'pi-briefcase',
PAGAMENTO: 'pi-credit-card',
CADASTRO: 'pi-user',
}
</script>
<template>
<div class="space-y-8">
<div class="flex items-center justify-between gap-4 flex-wrap">
<div>
<p v-if="prefeitura.nomePrefeitura" class="text-xs font-semibold text-primary/70 dark:text-primary/50 uppercase tracking-widest mb-1">
{{ prefeitura.nomePrefeitura }}
</p>
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">
Olá, {{ nomeUsuario || 'Contribuinte' }} 👋
</h1>
<p class="text-slate-500 dark:text-slate-400 mt-1">Bem-vindo ao seu painel de gestão fiscal.</p>
</div>
<img
v-if="prefeitura.pathLogo"
:src="prefeitura.pathLogo"
:alt="prefeitura.nomePrefeitura ?? 'Logo do município'"
class="h-14 w-auto object-contain opacity-80 dark:opacity-60"
/>
</div>
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-3">
<div class="w-10 h-10 bg-red-50 dark:bg-red-900/20 rounded-lg flex items-center justify-center">
<i class="pi pi-receipt text-red-600 dark:text-red-400 text-sm" aria-hidden="true" />
</div>
<div>
<p class="text-2xl font-bold text-slate-800 dark:text-slate-100">
<span v-if="carregando" class="inline-block w-12 h-7 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
<template v-else>{{ formatarMoeda(resumo?.totalDebitos) }}</template>
</p>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Débitos em aberto</p>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-3">
<div class="w-10 h-10 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg flex items-center justify-center">
<i class="pi pi-file-check text-emerald-600 dark:text-emerald-400 text-sm" aria-hidden="true" />
</div>
<div>
<p class="text-2xl font-bold text-slate-800 dark:text-slate-100">
<span v-if="carregando" class="inline-block w-8 h-7 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
<template v-else>{{ resumo?.certidoesAtivas ?? 0 }}</template>
</p>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Certidões ativas</p>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-3">
<div class="w-10 h-10 bg-amber-50 dark:bg-amber-900/20 rounded-lg flex items-center justify-center">
<i class="pi pi-briefcase text-amber-600 dark:text-amber-400 text-sm" aria-hidden="true" />
</div>
<div>
<p class="text-2xl font-bold text-slate-800 dark:text-slate-100">
<span v-if="carregando" class="inline-block w-8 h-7 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
<template v-else>{{ resumo?.alvarasAndamento ?? 0 }}</template>
</p>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Alvarás em andamento</p>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-3">
<div class="w-10 h-10 bg-primary/10 dark:bg-primary/20 rounded-lg flex items-center justify-center">
<i class="pi pi-credit-card text-primary text-sm" aria-hidden="true" />
</div>
<div>
<p class="text-2xl font-bold text-slate-800 dark:text-slate-100">
<span v-if="carregando" class="inline-block w-16 h-7 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
<template v-else>{{ formatarMoeda(resumo?.ultimoPagamento) }}</template>
</p>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Último pagamento</p>
</div>
</div>
</div>
<div
v-if="!carregando && resumo?.debitosVencidos > 0"
class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700/50 rounded-xl p-4 flex items-center gap-4"
>
<i class="pi pi-exclamation-triangle text-red-600 dark:text-red-400 text-xl flex-shrink-0" aria-hidden="true" />
<div class="flex-1">
<p class="font-semibold text-red-800 dark:text-red-300 text-sm">
{{ resumo.debitosVencidos }} débito{{ resumo.debitosVencidos > 1 ? 's' : '' }} vencido{{ resumo.debitosVencidos > 1 ? 's' : '' }}
</p>
<p class="text-xs text-red-600 dark:text-red-400 mt-0.5">Regularize para evitar juros e negativação.</p>
</div>
<Button label="Ver débitos" size="small" severity="danger" @click="router.push('/portal/debitos')" />
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-6">
<p class="text-sm font-bold text-slate-700 dark:text-slate-200 mb-4">Acesso rápido</p>
<div class="grid grid-cols-2 gap-3">
<button
v-for="a in acesRapidos"
:key="a.label"
class="flex flex-col items-center gap-2 p-4 rounded-xl border border-slate-100 dark:border-slate-700 hover:border-primary/40 hover:bg-primary/5 dark:hover:bg-primary/10 transition-colors group"
@click="router.push(a.to)"
>
<i :class="['pi', a.icon, a.cor, 'text-xl']" aria-hidden="true" />
<span class="text-xs font-semibold text-slate-600 dark:text-slate-300 text-center leading-tight group-hover:text-primary transition-colors">{{ a.label }}</span>
</button>
</div>
</div>
<div class="lg:col-span-2 bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-6">
<p class="text-sm font-bold text-slate-700 dark:text-slate-200 mb-4">Atividade recente</p>
<div v-if="carregando" class="space-y-3">
<div v-for="i in 4" :key="i" class="flex gap-3 items-center">
<div class="w-8 h-8 bg-slate-200 dark:bg-slate-700 rounded-lg animate-pulse flex-shrink-0" />
<div class="flex-1 space-y-1.5">
<div class="h-3 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-3/4" />
<div class="h-2.5 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-1/2" />
</div>
</div>
</div>
<div v-else-if="atividades.length === 0" class="text-center py-6">
<i class="pi pi-inbox text-slate-300 dark:text-slate-600 text-3xl mb-2 block" aria-hidden="true" />
<p class="text-sm text-slate-400 dark:text-slate-500">Nenhuma atividade registrada.</p>
</div>
<div v-else class="space-y-1">
<div
v-for="(ativ, i) in atividades"
:key="i"
class="flex items-center gap-3 py-3 border-b border-slate-100 dark:border-slate-700 last:border-0"
>
<div class="w-8 h-8 bg-primary/8 dark:bg-primary/15 rounded-lg flex items-center justify-center flex-shrink-0">
<i :class="['pi', iconeAtividade[ativ.tipo] ?? 'pi-circle', 'text-primary text-sm']" aria-hidden="true" />
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-slate-800 dark:text-slate-100 truncate">{{ ativ.descricao }}</p>
<p class="text-xs text-slate-400 dark:text-slate-500 mt-0.5">{{ ativ.data }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>