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:
@@ -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">
|
||||||
|
{#if (empresasTotalQuery.data?.length ?? 0) === 0}
|
||||||
<p class="text-base-content/70 mb-4">Nenhuma empresa cadastrada ainda.</p>
|
<p class="text-base-content/70 mb-4">Nenhuma empresa cadastrada ainda.</p>
|
||||||
<button class="btn btn-primary gap-2" type="button" onclick={abrirNovaEmpresa}>
|
<button class="btn btn-primary gap-2" type="button" onclick={abrirNovaEmpresa}>
|
||||||
<Plus class="h-4 w-4" strokeWidth={2} />
|
<Plus class="h-4 w-4" strokeWidth={2} />
|
||||||
Cadastrar primeira empresa
|
Cadastrar primeira empresa
|
||||||
</button>
|
</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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user