All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 3m13s
feat(painel): modificar último pagamento para data e adicionar formatação de data
241 lines
12 KiB
Vue
241 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: '2025-05-15',
|
|
valorUltimoPagamento: 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)
|
|
}
|
|
|
|
function formatarData(data) {
|
|
if (!data) return ''
|
|
return new Date(data + 'T00:00:00').toLocaleDateString('pt-BR')
|
|
}
|
|
|
|
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?.valorUltimoPagamento) }}</template>
|
|
</p>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
|
Último pagamento
|
|
<template v-if="!carregando && resumo?.ultimoPagamento">
|
|
· {{ formatarData(resumo.ultimoPagamento) }}
|
|
</template>
|
|
</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>
|