feat: add filtering functionality for empresas in dashboard, allowing users to search by name or CNPJ, and enhance UI with clear feedback for no results

This commit is contained in:
2025-12-15 14:34:09 -03:00
parent a5ad843b3e
commit c7b4ea15bd
2 changed files with 76 additions and 9 deletions

View File

@@ -7,7 +7,16 @@
import { maskCEP, maskCNPJ, maskPhone, maskUF, onlyDigits } from '$lib/utils/masks'; import { maskCEP, maskCNPJ, maskPhone, maskUF, onlyDigits } from '$lib/utils/masks';
const client = useConvexClient(); const client = useConvexClient();
const empresasQuery = useQuery(api.empresas.list, {}); let filtroEmpresa = $state('');
const empresasTotalQuery = useQuery(api.empresas.list, {});
const empresasQuery = useQuery(api.empresas.list, () => ({
query: filtroEmpresa.trim() || undefined
}));
function limparFiltroEmpresa() {
filtroEmpresa = '';
}
let modalAberto = $state(false); let modalAberto = $state(false);
@@ -425,6 +434,32 @@
</button> </button>
</div> </div>
<div class="card bg-base-100/90 border-base-300 mb-6 border shadow-xl backdrop-blur-sm">
<div class="card-body">
<div class="grid gap-4 sm:grid-cols-[1fr_auto] sm:items-end">
<div class="form-control w-full">
<label class="label" for="filtro_empresa">
<span class="label-text font-semibold">Buscar empresa</span>
</label>
<input
id="filtro_empresa"
class="input input-bordered focus:input-primary w-full"
type="text"
placeholder="Nome fantasia, razão social ou CNPJ"
bind:value={filtroEmpresa}
/>
</div>
<div class="flex items-center justify-between gap-3 sm:min-w-max sm:justify-end">
<div class="text-base-content/70 text-sm">
{empresasQuery.data?.length ?? 0} de {empresasTotalQuery.data?.length ?? 0}
</div>
<button type="button" class="btn btn-ghost" onclick={limparFiltroEmpresa}>Limpar</button>
</div>
</div>
</div>
</div>
<div class="card bg-base-100 shadow-xl"> <div class="card bg-base-100 shadow-xl">
<div class="card-body"> <div class="card-body">
{#if empresasQuery.isLoading} {#if empresasQuery.isLoading}
@@ -437,11 +472,21 @@
</div> </div>
{:else if empresasQuery.data && empresasQuery.data.length === 0} {:else if empresasQuery.data && empresasQuery.data.length === 0}
<div class="py-10 text-center"> <div class="py-10 text-center">
<p class="text-base-content/70 mb-4">Nenhuma empresa cadastrada ainda.</p> {#if (empresasTotalQuery.data?.length ?? 0) === 0}
<button class="btn btn-primary gap-2" type="button" onclick={abrirNovaEmpresa}> <p class="text-base-content/70 mb-4">Nenhuma empresa cadastrada ainda.</p>
<Plus class="h-4 w-4" strokeWidth={2} /> <button class="btn btn-primary gap-2" type="button" onclick={abrirNovaEmpresa}>
Cadastrar primeira empresa <Plus class="h-4 w-4" strokeWidth={2} />
</button> Cadastrar primeira empresa
</button>
{:else}
<p class="text-base-content/70 mb-2">Nenhum resultado encontrado.</p>
<p class="text-base-content/60 mb-4 text-sm">
Ajuste ou limpe o filtro para ver empresas.
</p>
<button type="button" class="btn btn-ghost" onclick={limparFiltroEmpresa}>
Limpar filtro
</button>
{/if}
</div> </div>
{:else if empresasQuery.data} {:else if empresasQuery.data}
<div class="overflow-x-auto"> <div class="overflow-x-auto">

View File

@@ -5,15 +5,37 @@ import { getCurrentUserFunction } from './auth';
import type { Id } from './_generated/dataModel'; import type { Id } from './_generated/dataModel';
export const list = query({ export const list = query({
args: {}, args: {
handler: async (ctx) => { query: v.optional(v.string())
},
handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: 'empresas', recurso: 'empresas',
acao: 'listar' acao: 'listar'
}); });
const empresas = await ctx.db.query('empresas').collect(); const empresas = await ctx.db.query('empresas').collect();
return empresas; const term = args.query?.trim();
if (!term) return empresas;
const termLower = term.toLowerCase();
const termDigits = term.replace(/\D/g, '');
return empresas.filter((empresa) => {
const razao = (empresa.razao_social || '').toLowerCase();
const fantasia = (empresa.nome_fantasia || '').toLowerCase();
const cnpjRaw = empresa.cnpj || '';
const cnpjLower = cnpjRaw.toLowerCase();
const cnpjDigits = cnpjRaw.replace(/\D/g, '');
const matchNome = razao.includes(termLower) || fantasia.includes(termLower);
const matchCnpj = termDigits
? cnpjDigits.includes(termDigits)
: cnpjLower.includes(termLower);
return matchNome || matchCnpj;
});
} }
}); });