gabrielb 5e528f234b
All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m36s
feat(auth): adicionar tratamento de erro ao salvar estado PKCE
feat(proxy): melhorar tratamento de erro para backend inacessível
feat(certidao): adicionar validação de CPF e CNPJ
feat(iptu): ajustar manipulação de dados de imóveis retornados
feat(error): criar componente de erro para exibição de mensagens
2026-05-25 18:59:41 -03:00

171 lines
7.9 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue'
import { useGuiasEmitidasPortal } from '@/composables/useGuiasEmitidasPortal'
import { formatCurrency, formatDate } from '@/utils/formatador'
import ModalDetalhesGuia from '@/components/extrato/ModalDetalhesGuia.vue'
definePageMeta({
layout: 'portal',
middleware: 'auth',
})
const modalVisible = ref(false)
const guiaSelecionadaId = ref(null)
const guiaSelecionadaStatus = ref(null)
const {
guias,
isLoading,
mensagemErro,
pagina,
totalPaginas,
totalElementos,
filtro,
statusOptions,
consultar,
limparFiltros,
mudarPagina,
visualizarPdf,
formatarNumeroGuia,
getSeverityStatus,
} = useGuiasEmitidasPortal()
onMounted(() => consultar())
function detalhar(guia) {
guiaSelecionadaId.value = guia.id
guiaSelecionadaStatus.value = guia.statusCodigo
modalVisible.value = true
}
function paginaAnterior() {
if (pagina.value > 0) mudarPagina(pagina.value - 1)
}
function proximaPagina() {
if (pagina.value < totalPaginas.value - 1) mudarPagina(pagina.value + 1)
}
function aplicarFiltros() {
pagina.value = 0
consultar()
}
</script>
<template>
<div class="space-y-6">
<div>
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Guias Emitidas</h1>
<p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5">Consulte e baixe as guias de pagamento emitidas em seu nome.</p>
</div>
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4 space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="lg:col-span-2">
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Nº Guia</label>
<InputText v-model="filtro.numeroGuia" class="w-full" size="small" />
</div>
<div class="lg:col-span-2">
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Status</label>
<Select
v-model="filtro.status"
:options="statusOptions"
option-label="label"
option-value="value"
placeholder="Todos"
show-clear
class="w-full"
size="small"
/>
</div>
<div>
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Emissão início</label>
<DatePicker v-model="filtro.dataEmissaoInicio" show-icon class="w-full" size="small" date-format="dd/mm/yy" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Emissão fim</label>
<DatePicker v-model="filtro.dataEmissaoFim" show-icon class="w-full" size="small" date-format="dd/mm/yy" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Vencimento início</label>
<DatePicker v-model="filtro.dataVencimentoInicio" show-icon class="w-full" size="small" date-format="dd/mm/yy" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Vencimento fim</label>
<DatePicker v-model="filtro.dataVencimentoFim" show-icon class="w-full" size="small" date-format="dd/mm/yy" />
</div>
<div class="lg:col-span-2">
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Valor mínimo</label>
<InputNumber v-model="filtro.valorMinimo" mode="currency" currency="BRL" locale="pt-BR" class="w-full" size="small" />
</div>
<div class="lg:col-span-2">
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1.5">Valor máximo</label>
<InputNumber v-model="filtro.valorMaximo" mode="currency" currency="BRL" locale="pt-BR" class="w-full" size="small" />
</div>
</div>
<div class="flex gap-2">
<Button label="Consultar" icon="pi pi-search" size="small" :loading="isLoading" @click="aplicarFiltros" />
<Button label="Limpar" icon="pi pi-eraser" size="small" severity="secondary" outlined @click="limparFiltros" />
</div>
</div>
<Message v-if="mensagemErro" severity="warn" :closable="false">{{ mensagemErro }}</Message>
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 overflow-hidden">
<div v-if="isLoading" class="p-8 flex justify-center">
<ProgressSpinner />
</div>
<div v-else-if="guias.length === 0" class="p-12 text-center">
<p class="font-semibold text-slate-700 dark:text-slate-200">Nenhuma guia encontrada</p>
</div>
<div v-else>
<div class="hidden sm:grid grid-cols-[1fr_1fr_100px_100px_120px_140px] gap-3 px-5 py-3 bg-slate-50 dark:bg-slate-700/50 border-b text-xs font-semibold text-slate-500 uppercase">
<span>Nº Guia</span>
<span>Contribuinte</span>
<span>Emissão</span>
<span>Vencimento</span>
<span class="text-right">Valor</span>
<span class="text-right">Ações</span>
</div>
<div class="divide-y divide-slate-100 dark:divide-slate-700">
<div
v-for="guia in guias"
:key="guia.id"
class="px-5 py-4 flex flex-col sm:grid sm:grid-cols-[1fr_1fr_100px_100px_120px_140px] sm:items-center gap-2 sm:gap-3"
>
<p class="font-semibold text-sm">{{ formatarNumeroGuia(guia.numeroGuia) }}</p>
<div class="min-w-0">
<p class="text-sm truncate">{{ guia.nomeContribuinte }}</p>
<Tag :value="guia.statusDescricao" :severity="getSeverityStatus(guia.statusCodigo)" class="mt-1" />
</div>
<p class="text-sm text-slate-500">{{ formatDate(guia.dataEmissao) }}</p>
<p class="text-sm text-slate-500">{{ formatDate(guia.dataVencimento) }}</p>
<p class="text-sm font-bold text-right">{{ formatCurrency(guia.valorTotal) }}</p>
<div class="flex gap-1 justify-end">
<Button icon="pi pi-info-circle" text rounded size="small" @click="detalhar(guia)" />
<Button icon="pi pi-file-pdf" text rounded size="small" @click="visualizarPdf(guia.id)" />
</div>
</div>
</div>
<div v-if="totalPaginas > 1" class="flex items-center justify-between px-5 py-3 border-t text-sm">
<span class="text-slate-500">{{ totalElementos }} guia(s)</span>
<div class="flex gap-2">
<Button icon="pi pi-chevron-left" size="small" text :disabled="pagina === 0" @click="paginaAnterior" />
<span class="px-2 py-1">{{ pagina + 1 }} / {{ totalPaginas }}</span>
<Button icon="pi pi-chevron-right" size="small" text :disabled="pagina >= totalPaginas - 1" @click="proximaPagina" />
</div>
</div>
</div>
</div>
<ModalDetalhesGuia
v-model:visible="modalVisible"
:guia-id="guiaSelecionadaId"
:status-codigo="guiaSelecionadaStatus"
/>
</div>
</template>