All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m36s
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
171 lines
7.9 KiB
Vue
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>
|