gabrielb 3fb5cdb6a8
All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m29s
feat(portal): adicionar navegação e mock de dados para certidões, alvarás e pagamentos
2026-05-20 00:13:10 -03:00

161 lines
7.3 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue'
import { portalService } from '@/services/portalService'
definePageMeta({
layout: 'portal',
middleware: 'auth',
})
// ─── MOCKS APRESENTAÇÃO — remover antes do deploy ─────────────────────────
const MOCK_ATIVO = true
const CERTIDOES_MOCK = [
{
id: 1,
tipo: 'Certidão Negativa de Débitos',
numero: '2024/0042',
dataEmissao: '2024-03-10',
dataValidade: '2025-03-10',
status: 'ATIVA',
},
{
id: 2,
tipo: 'Certidão de Regularidade Fiscal',
numero: '2023/0187',
dataEmissao: '2023-06-15',
dataValidade: '2024-06-15',
status: 'VENCIDA',
},
]
// ──────────────────────────────────────────────────────────────────────────
const router = useRouter()
const certidoes = ref([])
const carregando = ref(true)
const carregandoPdf = ref(null)
const mensagemErro = ref('')
onMounted(carregar)
async function carregar() {
carregando.value = true
mensagemErro.value = ''
try {
const res = await portalService.getCertidoes()
certidoes.value = res.data ?? []
if (MOCK_ATIVO && certidoes.value.length === 0) {
certidoes.value = CERTIDOES_MOCK
}
} catch (e) {
mensagemErro.value = e?.data?.description ?? 'Não foi possível carregar as certidões.'
if (MOCK_ATIVO) {
certidoes.value = CERTIDOES_MOCK
mensagemErro.value = ''
}
} finally {
carregando.value = false
}
}
async function reemitir(cert) {
carregandoPdf.value = cert.id
try {
const buf = await portalService.reemitirCertidao(cert.id)
const url = URL.createObjectURL(new Blob([buf], { type: 'application/pdf' }))
const a = document.createElement('a')
a.href = url
a.download = `certidao-${cert.numero}.pdf`
a.click()
URL.revokeObjectURL(url)
} catch {
mensagemErro.value = 'Erro ao reemitir a certidão.'
} finally {
carregandoPdf.value = null
}
}
const statusMap = {
ATIVA: { label: 'Ativa', classe: 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400' },
VENCIDA: { label: 'Vencida', classe: 'bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400' },
CANCELADA: { label: 'Cancelada', classe: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400' },
}
</script>
<template>
<div class="space-y-6">
<div class="flex items-center justify-between gap-4 flex-wrap">
<div>
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Certidões</h1>
<p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5">Suas certidões emitidas e disponíveis para reemissão.</p>
</div>
<Button label="Nova certidão" icon="pi pi-plus" size="small" @click="router.push('/servicos/certidao?from=portal')" />
</div>
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 overflow-hidden">
<div v-if="carregando" class="divide-y divide-slate-100 dark:divide-slate-700">
<div v-for="i in 4" :key="i" class="p-5 flex items-center gap-4">
<div class="flex-1 space-y-2">
<div class="h-3.5 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-2/5" />
<div class="h-3 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-1/4" />
</div>
<div class="h-6 bg-slate-200 dark:bg-slate-700 rounded-full animate-pulse w-14" />
<div class="h-8 bg-slate-200 dark:bg-slate-700 rounded-lg animate-pulse w-24" />
</div>
</div>
<div v-else-if="mensagemErro" class="p-8 text-center">
<p class="text-sm text-slate-600 dark:text-slate-300">{{ mensagemErro }}</p>
<Button label="Tentar novamente" severity="secondary" size="small" class="mt-4" @click="carregar" />
</div>
<div v-else-if="certidoes.length === 0" class="p-12 text-center">
<i class="pi pi-file text-slate-300 dark:text-slate-600 text-4xl mb-3 block" aria-hidden="true" />
<p class="font-semibold text-slate-700 dark:text-slate-200">Nenhuma certidão emitida</p>
<p class="text-sm text-slate-400 dark:text-slate-500 mt-1 mb-4">Emita sua primeira certidão pelo portal público.</p>
<Button label="Emitir certidão" size="small" @click="router.push('/servicos/certidao?from=portal')" />
</div>
<div v-else>
<div class="flex items-center gap-4 px-5 py-3 bg-slate-50 dark:bg-slate-700/50 border-b border-slate-200 dark:border-slate-700 text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide">
<span class="flex-1">Certidão</span>
<span class="hidden sm:block w-28 text-right">Emissão</span>
<span class="hidden sm:block w-28 text-right">Validade</span>
<span class="w-20 text-center">Status</span>
<span class="w-24" />
</div>
<div class="divide-y divide-slate-100 dark:divide-slate-700">
<div
v-for="cert in certidoes"
:key="cert.id"
class="flex items-center gap-4 px-5 py-4 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors"
>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 truncate">{{ cert.tipo }}</p>
<p class="text-xs text-slate-400 dark:text-slate-500 mt-0.5"> {{ cert.numero }}</p>
</div>
<p class="hidden sm:block text-sm text-slate-500 dark:text-slate-400 w-28 text-right">{{ cert.dataEmissao }}</p>
<p class="hidden sm:block text-sm text-slate-500 dark:text-slate-400 w-28 text-right">{{ cert.dataValidade }}</p>
<div class="w-20 flex justify-center">
<span :class="['text-xs font-semibold px-2 py-1 rounded-full', statusMap[cert.status]?.classe ?? 'bg-slate-100 text-slate-500']">
{{ statusMap[cert.status]?.label ?? cert.status }}
</span>
</div>
<div class="w-24 flex justify-end">
<Button
icon="pi pi-download"
label="PDF"
size="small"
outlined
:loading="carregandoPdf === cert.id"
:disabled="cert.status === 'CANCELADA' || !!carregandoPdf"
@click="reemitir(cert)"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</template>