developer #3

Merged
gabrielb merged 12 commits from developer into main 2026-05-19 13:42:38 +00:00
4 changed files with 576 additions and 17 deletions
Showing only changes of commit f687e650b8 - Show all commits

View File

@ -1,12 +1,143 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'
import { portalService } from '@/services/portalService'
const alvaras = ref([])
const carregando = ref(true)
const mensagemErro = ref('')
const filtroStatus = ref(null)
const statusOptions = [
{ value: 'EM_ANALISE', label: 'Em análise' },
{ value: 'AGUARDANDO_DOCUMENTOS', label: 'Aguardando documentos' },
{ value: 'DEFERIDO', label: 'Deferido' },
{ value: 'INDEFERIDO', label: 'Indeferido' },
{ value: 'CANCELADO', label: 'Cancelado' },
]
const statusMap = {
EM_ANALISE: { label: 'Em análise', classe: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400', icone: 'pi-clock' },
AGUARDANDO_DOCUMENTOS: { label: 'Aguard. docs', classe: 'bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400', icone: 'pi-file-edit' },
DEFERIDO: { label: 'Deferido', classe: 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400', icone: 'pi-check-circle' },
INDEFERIDO: { label: 'Indeferido', classe: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400', icone: 'pi-times-circle' },
CANCELADO: { label: 'Cancelado', classe: 'bg-slate-100 dark:bg-slate-700 text-slate-500 dark:text-slate-400', icone: 'pi-ban' },
}
onMounted(() => carregar())
async function carregar() {
carregando.value = true
mensagemErro.value = ''
try {
const params = filtroStatus.value ? { status: filtroStatus.value } : {}
const { data } = await portalService.getAlvaras(params)
alvaras.value = data.data?.content ?? []
} catch (e) {
mensagemErro.value = e.response?.data?.description ?? 'Não foi possível carregar os alvarás.'
} finally {
carregando.value = false
}
}
</script> </script>
<template> <template>
<div class="space-y-6"> <div class="space-y-6">
<h1 class="text-2xl font-bold text-slate-800">Alvarás</h1> <div>
<div class="bg-white rounded-xl border border-slate-200 p-8 text-center text-slate-400"> <h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Alvarás</h1>
<i class="pi pi-briefcase text-4xl mb-3 block" /> <p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5">Acompanhe o andamento dos seus processos de alvará.</p>
<p>Acompanhamento de alvarás e processos a ser implementado.</p> </div>
<!-- Filtro -->
<div class="flex gap-3 flex-wrap">
<button
:class="['px-4 py-2 rounded-lg text-sm font-semibold border transition-colors', filtroStatus === null ? 'bg-primary text-white border-primary' : 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-700 hover:border-slate-300']"
@click="filtroStatus = null; carregar()"
>
Todos
</button>
<button
v-for="opt in statusOptions"
:key="opt.value"
:class="['px-4 py-2 rounded-lg text-sm font-semibold border transition-colors', filtroStatus === opt.value ? 'bg-primary text-white border-primary' : 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-700 hover:border-slate-300']"
@click="filtroStatus = opt.value; carregar()"
>
{{ opt.label }}
</button>
</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 3" :key="i" class="p-5 space-y-3">
<div class="flex items-center gap-3">
<div class="h-4 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-1/3" />
<div class="h-6 bg-slate-200 dark:bg-slate-700 rounded-full animate-pulse w-24 ml-auto" />
</div>
<div class="h-3 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-2/3" />
<div class="h-2.5 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-1/4" />
</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="alvaras.length === 0" class="p-12 text-center">
<i class="pi pi-briefcase 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">Nenhum alvará encontrado</p>
<p class="text-sm text-slate-400 dark:text-slate-500 mt-1">Solicitações de alvará aparecem aqui após o protocolo.</p>
</div>
<div v-else class="divide-y divide-slate-100 dark:divide-slate-700">
<div
v-for="alv in alvaras"
:key="alv.id"
class="p-5 hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors"
>
<div class="flex items-start justify-between gap-4 mb-2">
<div>
<p class="font-semibold text-slate-800 dark:text-slate-100 text-sm">{{ alv.tipo }}</p>
<p class="text-xs text-slate-400 dark:text-slate-500 mt-0.5">Processo {{ alv.numeroProcesso }}</p>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<i :class="['pi', statusMap[alv.status]?.icone ?? 'pi-circle', 'text-xs', statusMap[alv.status]?.classe?.split(' ').find(c => c.startsWith('text'))]" aria-hidden="true" />
<span :class="['text-xs font-semibold px-2 py-1 rounded-full whitespace-nowrap', statusMap[alv.status]?.classe ?? 'bg-slate-100 text-slate-500']">
{{ statusMap[alv.status]?.label ?? alv.status }}
</span>
</div>
</div>
<!-- Timeline de etapas -->
<div v-if="alv.etapas?.length" class="mt-4 flex items-center gap-1 overflow-x-auto pb-1">
<template v-for="(etapa, idx) in alv.etapas" :key="idx">
<div class="flex flex-col items-center gap-1 min-w-[72px]">
<div
class="w-6 h-6 rounded-full flex items-center justify-center text-xs"
:class="etapa.concluida
? 'bg-primary text-white'
: etapa.atual
? 'bg-primary/20 dark:bg-primary/30 text-primary border-2 border-primary'
: 'bg-slate-100 dark:bg-slate-700 text-slate-400'"
>
<i v-if="etapa.concluida" class="pi pi-check text-xs" aria-hidden="true" />
<span v-else class="text-xs font-bold">{{ idx + 1 }}</span>
</div>
<span class="text-xs text-slate-500 dark:text-slate-400 text-center leading-tight w-16">{{ etapa.nome }}</span>
</div>
<div
v-if="idx < alv.etapas.length - 1"
class="flex-1 h-px min-w-[12px]"
:class="alv.etapas[idx + 1].concluida || alv.etapas[idx + 1].atual ? 'bg-primary/40' : 'bg-slate-200 dark:bg-slate-700'"
/>
</template>
</div>
<p v-if="alv.ultimaAtualizacao" class="text-xs text-slate-400 dark:text-slate-500 mt-3">
Última atualização: {{ alv.ultimaAtualizacao }}
</p>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,12 +1,127 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { portalService } from '@/services/portalService'
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 { data } = await portalService.getCertidoes()
certidoes.value = data.data?.content ?? []
} catch (e) {
mensagemErro.value = e.response?.data?.description ?? 'Não foi possível carregar as certidões.'
} finally {
carregando.value = false
}
}
async function reemitir(cert) {
carregandoPdf.value = cert.id
try {
const { data } = await portalService.reemitirCertidao(cert.id)
const url = URL.createObjectURL(new Blob([data], { 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> </script>
<template> <template>
<div class="space-y-6"> <div class="space-y-6">
<h1 class="text-2xl font-bold text-slate-800">Certidões</h1> <div class="flex items-center justify-between gap-4 flex-wrap">
<div class="bg-white rounded-xl border border-slate-200 p-8 text-center text-slate-400"> <div>
<i class="pi pi-file-edit text-4xl mb-3 block" /> <h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Certidões</h1>
<p>Certidões ativas e reemissão a ser implementado.</p> <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({ name: 'certidao' })" />
</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({ name: 'certidao' })" />
</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'"
@click="reemitir(cert)"
/>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,12 +1,194 @@
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue'
import { portalService } from '@/services/portalService'
const carregando = ref(true)
const salvando = ref(false)
const modoEditar = ref(false)
const mensagemErro = ref('')
const mensagemSucesso = ref('')
const dados = ref(null)
const contato = reactive({ email: '', telefone: '', whatsapp: false })
onMounted(async () => {
try {
const { data } = await portalService.getDadosCadastrais()
dados.value = data.data
contato.email = dados.value?.email ?? ''
contato.telefone = dados.value?.telefone ?? ''
contato.whatsapp = dados.value?.whatsapp ?? false
} catch (e) {
mensagemErro.value = e.response?.data?.description ?? 'Não foi possível carregar os dados cadastrais.'
} finally {
carregando.value = false
}
})
async function salvarContato() {
salvando.value = true
mensagemErro.value = ''
mensagemSucesso.value = ''
try {
await portalService.atualizarContato({
email: contato.email,
telefone: contato.telefone.replace(/\D/g, ''),
whatsapp: contato.whatsapp,
})
dados.value.email = contato.email
dados.value.telefone = contato.telefone
dados.value.whatsapp = contato.whatsapp
mensagemSucesso.value = 'Dados de contato atualizados com sucesso!'
modoEditar.value = false
} catch (e) {
mensagemErro.value = e.response?.data?.description ?? 'Erro ao salvar. Tente novamente.'
} finally {
salvando.value = false
}
}
function cancelarEdicao() {
contato.email = dados.value?.email ?? ''
contato.telefone = dados.value?.telefone ?? ''
contato.whatsapp = dados.value?.whatsapp ?? false
modoEditar.value = false
mensagemErro.value = ''
}
function formatarTelefone(e) {
const d = e.target.value.replace(/\D/g, '').slice(0, 11)
contato.telefone = d
.replace(/(\d{2})(\d)/, '($1) $2')
.replace(/(\d{5})(\d{1,4})$/, '$1-$2')
}
</script> </script>
<template> <template>
<div class="space-y-6"> <div class="space-y-6 max-w-2xl">
<h1 class="text-2xl font-bold text-slate-800">Dados Cadastrais</h1>
<div class="bg-white rounded-xl border border-slate-200 p-8 text-center text-slate-400"> <div>
<i class="pi pi-user text-4xl mb-3 block" /> <h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Dados Cadastrais</h1>
<p>Visualização e atualização de dados cadastrais a ser implementado.</p> <p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5">Visualize seus dados e mantenha o contato atualizado.</p>
</div> </div>
<!-- Loading -->
<div v-if="carregando" class="space-y-4">
<div v-for="i in 2" :key="i" class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-6 space-y-4">
<div class="h-4 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-1/4" />
<div class="space-y-3">
<div class="h-3.5 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-3/4" />
<div class="h-3.5 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-1/2" />
</div>
</div>
</div>
<template v-else-if="dados">
<!-- Dados gerais (somente leitura) -->
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-6">
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-4">Identificação</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p class="text-xs text-slate-400 dark:text-slate-500">{{ dados.tipoPessoa === 'JURIDICA' ? 'CNPJ' : 'CPF' }}</p>
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 mt-0.5 font-mono">{{ dados.documento }}</p>
</div>
<div>
<p class="text-xs text-slate-400 dark:text-slate-500">{{ dados.tipoPessoa === 'JURIDICA' ? 'Razão Social' : 'Nome completo' }}</p>
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 mt-0.5">{{ dados.nomeCompleto }}</p>
</div>
<div v-if="dados.nomeFantasia">
<p class="text-xs text-slate-400 dark:text-slate-500">Nome fantasia</p>
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 mt-0.5">{{ dados.nomeFantasia }}</p>
</div>
<div v-if="dados.dataNascimento">
<p class="text-xs text-slate-400 dark:text-slate-500">Data de nascimento</p>
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 mt-0.5">{{ dados.dataNascimento }}</p>
</div>
</div>
</div>
<!-- Endereço (somente leitura) -->
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-6">
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-4">Endereço</p>
<p class="text-sm text-slate-800 dark:text-slate-100">
{{ dados.endereco?.logradouro }}, {{ dados.endereco?.numero }}
<template v-if="dados.endereco?.complemento"> {{ dados.endereco.complemento }}</template>
</p>
<p class="text-sm text-slate-600 dark:text-slate-300 mt-0.5">
{{ dados.endereco?.bairro }} {{ dados.endereco?.cidade }}/{{ dados.endereco?.uf }}
</p>
<p class="text-xs text-slate-400 dark:text-slate-500 mt-1">CEP: {{ dados.endereco?.cep }}</p>
<p class="text-xs text-slate-400 dark:text-slate-500 mt-3">
Para alterar o endereço, compareça ao setor de atendimento da Prefeitura.
</p>
</div>
<!-- Contato (editável) -->
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-6">
<div class="flex items-center justify-between mb-4">
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wide">Contato</p>
<Button
v-if="!modoEditar"
label="Editar"
icon="pi pi-pencil"
size="small"
text
@click="modoEditar = true"
/>
</div>
<!-- Modo visualização -->
<div v-if="!modoEditar" class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p class="text-xs text-slate-400 dark:text-slate-500">E-mail</p>
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 mt-0.5">{{ dados.email }}</p>
</div>
<div>
<p class="text-xs text-slate-400 dark:text-slate-500">Telefone</p>
<p class="text-sm font-semibold text-slate-800 dark:text-slate-100 mt-0.5">
{{ dados.telefone }}
<span v-if="dados.whatsapp" class="ml-1.5 text-xs text-emerald-600 dark:text-emerald-400 font-normal">(WhatsApp)</span>
</p>
</div>
</div>
<!-- Modo edição -->
<div v-else class="space-y-4">
<div>
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-200 mb-1.5">E-mail</label>
<InputText v-model="contato.email" type="email" class="w-full" size="large" />
</div>
<div>
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-200 mb-1.5">Telefone / Celular</label>
<InputText :value="contato.telefone" class="w-full" size="large" inputmode="numeric" @input="formatarTelefone" />
</div>
<div class="flex items-center gap-3 p-4 bg-slate-50 dark:bg-slate-700/50 rounded-xl">
<Checkbox v-model="contato.whatsapp" :binary="true" input-id="whatsapp-dados" />
<label for="whatsapp-dados" class="text-sm text-slate-700 dark:text-slate-200 cursor-pointer">
Este número também recebe WhatsApp
</label>
</div>
<p v-if="mensagemErro" role="alert" class="text-sm text-red-600 dark:text-red-400 flex items-center gap-1.5">
<i class="pi pi-exclamation-circle" aria-hidden="true" /> {{ mensagemErro }}
</p>
<div class="flex gap-3">
<Button label="Cancelar" severity="secondary" outlined class="flex-1" @click="cancelarEdicao" />
<Button label="Salvar" icon="pi pi-check" class="flex-1" :loading="salvando" @click="salvarContato" />
</div>
</div>
<!-- Sucesso -->
<p v-if="mensagemSucesso && !modoEditar" role="status" class="mt-4 text-sm text-emerald-600 dark:text-emerald-400 flex items-center gap-1.5">
<i class="pi pi-check-circle" aria-hidden="true" /> {{ mensagemSucesso }}
</p>
</div>
</template>
<!-- Erro global -->
<div v-else-if="mensagemErro" class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-8 text-center">
<p class="text-sm text-slate-600 dark:text-slate-300">{{ mensagemErro }}</p>
</div>
</div> </div>
</template> </template>

View File

@ -1,12 +1,143 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'
import { portalService } from '@/services/portalService'
const pagamentos = ref([])
const carregando = ref(true)
const carregandoComprovante = ref(null)
const mensagemErro = ref('')
const filtroAno = ref(new Date().getFullYear())
const anosDisponiveis = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i)
onMounted(() => carregar())
async function carregar() {
carregando.value = true
mensagemErro.value = ''
try {
const { data } = await portalService.getPagamentos({ ano: filtroAno.value })
pagamentos.value = data.data?.content ?? []
} catch (e) {
mensagemErro.value = e.response?.data?.description ?? 'Não foi possível carregar os pagamentos.'
} finally {
carregando.value = false
}
}
async function baixarComprovante(pag) {
carregandoComprovante.value = pag.id
try {
const { data } = await portalService.getComprovante(pag.id)
const url = URL.createObjectURL(new Blob([data], { type: 'application/pdf' }))
const a = document.createElement('a')
a.href = url
a.download = `comprovante-${pag.id}.pdf`
a.click()
URL.revokeObjectURL(url)
} catch {
mensagemErro.value = 'Erro ao baixar o comprovante.'
} finally {
carregandoComprovante.value = null
}
}
function formatarMoeda(valor) {
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(valor ?? 0)
}
const formaPagMap = {
BOLETO: { label: 'Boleto', icone: 'pi-barcode' },
PIX: { label: 'Pix', icone: 'pi-qrcode' },
CARTAO: { label: 'Cartão', icone: 'pi-credit-card' },
TRANSFERENCIA: { label: 'Transferência', icone: 'pi-arrow-right-arrow-left' },
}
</script> </script>
<template> <template>
<div class="space-y-6"> <div class="space-y-6">
<h1 class="text-2xl font-bold text-slate-800">Histórico de Pagamentos</h1> <div>
<div class="bg-white rounded-xl border border-slate-200 p-8 text-center text-slate-400"> <h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Histórico de Pagamentos</h1>
<i class="pi pi-credit-card text-4xl mb-3 block" /> <p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5">Todos os seus pagamentos com comprovantes para download.</p>
<p>Histórico de pagamentos com comprovantes a ser implementado.</p> </div>
<!-- Filtro de ano -->
<div class="flex gap-2 flex-wrap">
<button
v-for="ano in anosDisponiveis"
:key="ano"
:class="['px-4 py-2 rounded-lg text-sm font-semibold border transition-colors', filtroAno === ano ? 'bg-primary text-white border-primary' : 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-700 hover:border-slate-300']"
@click="filtroAno = ano; carregar()"
>
{{ ano }}
</button>
</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 5" :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-4 bg-slate-200 dark:bg-slate-700 rounded animate-pulse w-20" />
<div class="h-8 bg-slate-200 dark:bg-slate-700 rounded-lg animate-pulse w-28" />
</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="pagamentos.length === 0" class="p-12 text-center">
<i class="pi pi-credit-card 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">Nenhum pagamento em {{ filtroAno }}</p>
<p class="text-sm text-slate-400 dark:text-slate-500 mt-1">Pagamentos realizados aparecerão aqui.</p>
</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">Descrição</span>
<span class="hidden sm:block w-28 text-right">Data</span>
<span class="hidden sm:block w-24 text-center">Forma</span>
<span class="w-28 text-right">Valor</span>
<span class="w-28" />
</div>
<div class="divide-y divide-slate-100 dark:divide-slate-700">
<div
v-for="pag in pagamentos"
:key="pag.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">{{ pag.descricao }}</p>
<p class="text-xs text-slate-400 dark:text-slate-500 mt-0.5">Ref: {{ pag.referencia }}</p>
</div>
<p class="hidden sm:block text-sm text-slate-500 dark:text-slate-400 w-28 text-right whitespace-nowrap">{{ pag.dataPagamento }}</p>
<div class="hidden sm:flex w-24 justify-center">
<span class="inline-flex items-center gap-1.5 text-xs text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-700 px-2 py-1 rounded-full">
<i :class="['pi', formaPagMap[pag.formaPagamento]?.icone ?? 'pi-circle', 'text-xs']" aria-hidden="true" />
{{ formaPagMap[pag.formaPagamento]?.label ?? pag.formaPagamento }}
</span>
</div>
<p class="text-sm font-bold text-emerald-600 dark:text-emerald-400 w-28 text-right whitespace-nowrap">
{{ formatarMoeda(pag.valor) }}
</p>
<div class="w-28 flex justify-end">
<Button
icon="pi pi-download"
label="Comprovante"
size="small"
text
:loading="carregandoComprovante === pag.id"
@click="baixarComprovante(pag)"
/>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>