add endereco e edita tabela empresas
This commit is contained in:
@@ -4,12 +4,13 @@
|
||||
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
import { Building2, Phone, Mail, Plus, Users, Pencil, X } from "lucide-svelte";
|
||||
import { resolve } from "$app/paths";
|
||||
import { maskCNPJ, maskPhone } from "$lib/utils/masks";
|
||||
import { maskCNPJ, maskCEP, maskPhone, maskUF, onlyDigits } from "$lib/utils/masks";
|
||||
import "$lib/svelte-compat";
|
||||
|
||||
const client = useConvexClient();
|
||||
const empresasQuery = useQuery(api.empresas.list, {});
|
||||
|
||||
let modalAberto = false;
|
||||
let modalAberto = $state(false);
|
||||
|
||||
type ContatoForm = {
|
||||
_id?: Id<"contatosEmpresa">;
|
||||
@@ -21,40 +22,137 @@
|
||||
_deleted?: boolean;
|
||||
};
|
||||
|
||||
type EnderecoForm = {
|
||||
cep: string;
|
||||
logradouro: string;
|
||||
numero: string;
|
||||
complemento: string;
|
||||
bairro: string;
|
||||
cidade: string;
|
||||
uf: string;
|
||||
};
|
||||
|
||||
type EmpresaForm = {
|
||||
id?: string;
|
||||
nome: string;
|
||||
id?: Id<"empresas">;
|
||||
razao_social: string;
|
||||
nome_fantasia?: string;
|
||||
cnpj: string;
|
||||
telefone: string;
|
||||
email: string;
|
||||
descricao?: string;
|
||||
endereco: EnderecoForm;
|
||||
contatos: ContatoForm[];
|
||||
};
|
||||
|
||||
let empresaForm: EmpresaForm = {
|
||||
nome: "",
|
||||
let empresaForm = $state<EmpresaForm>({
|
||||
razao_social: "",
|
||||
nome_fantasia: "",
|
||||
cnpj: "",
|
||||
telefone: "",
|
||||
email: "",
|
||||
descricao: "",
|
||||
endereco: {
|
||||
cep: "",
|
||||
logradouro: "",
|
||||
numero: "",
|
||||
complemento: "",
|
||||
bairro: "",
|
||||
cidade: "",
|
||||
uf: "",
|
||||
},
|
||||
contatos: [],
|
||||
});
|
||||
|
||||
let contatoEmEdicao = $state<ContatoForm | null>(null);
|
||||
let contatoIndiceEdicao = $state<number | null>(null);
|
||||
let erroFormulario = $state("");
|
||||
let salvando = $state(false);
|
||||
|
||||
let contatosModalAberto = $state(false);
|
||||
let contatosDaEmpresa = $state<ContatoForm[]>([]);
|
||||
let empresaContatosNome = $state("");
|
||||
let carregandoCep = $state(false);
|
||||
let erroCep = $state("");
|
||||
let carregandoCnpj = $state(false);
|
||||
let erroCnpj = $state("");
|
||||
|
||||
type ReceitaWsResponse = {
|
||||
status?: string;
|
||||
message?: string;
|
||||
nome?: string;
|
||||
fantasia?: string;
|
||||
telefone?: string;
|
||||
email?: string;
|
||||
cep?: string;
|
||||
logradouro?: string;
|
||||
numero?: string;
|
||||
complemento?: string;
|
||||
bairro?: string;
|
||||
municipio?: string;
|
||||
uf?: string;
|
||||
};
|
||||
|
||||
let contatoEmEdicao: ContatoForm | null = null;
|
||||
let contatoIndiceEdicao: number | null = null;
|
||||
let erroFormulario = "";
|
||||
let salvando = false;
|
||||
|
||||
let contatosModalAberto = false;
|
||||
let contatosDaEmpresa: ContatoForm[] = [];
|
||||
let empresaContatosNome = "";
|
||||
|
||||
function handleEmpresaCnpjInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
empresaForm.cnpj = maskCNPJ(target.value);
|
||||
target.value = empresaForm.cnpj;
|
||||
}
|
||||
|
||||
async function handleEmpresaCnpjBlur() {
|
||||
const digits = onlyDigits(empresaForm.cnpj);
|
||||
if (digits.length !== 14) return;
|
||||
|
||||
carregandoCnpj = true;
|
||||
erroCnpj = "";
|
||||
try {
|
||||
const response = await fetch(`https://www.receitaws.com.br/v1/cnpj/${digits}`);
|
||||
const data: ReceitaWsResponse = await response.json();
|
||||
|
||||
if (data.status === "ERROR") {
|
||||
throw new Error(data.message || "CNPJ não encontrado.");
|
||||
}
|
||||
|
||||
if (data.nome && !empresaForm.razao_social) {
|
||||
empresaForm.razao_social = data.nome;
|
||||
}
|
||||
if (data.fantasia && !empresaForm.nome_fantasia) {
|
||||
empresaForm.nome_fantasia = data.fantasia;
|
||||
}
|
||||
if (data.telefone && !empresaForm.telefone) {
|
||||
empresaForm.telefone = maskPhone(data.telefone);
|
||||
}
|
||||
if (data.email && !empresaForm.email) {
|
||||
empresaForm.email = data.email;
|
||||
}
|
||||
|
||||
if (
|
||||
data.cep ||
|
||||
data.logradouro ||
|
||||
data.bairro ||
|
||||
data.municipio ||
|
||||
data.uf
|
||||
) {
|
||||
empresaForm.endereco = {
|
||||
...empresaForm.endereco,
|
||||
cep: data.cep ? maskCEP(data.cep) : empresaForm.endereco.cep,
|
||||
logradouro: data.logradouro ?? empresaForm.endereco.logradouro,
|
||||
numero: data.numero ?? empresaForm.endereco.numero,
|
||||
complemento: data.complemento ?? empresaForm.endereco.complemento,
|
||||
bairro: data.bairro ?? empresaForm.endereco.bairro,
|
||||
cidade: data.municipio ?? empresaForm.endereco.cidade,
|
||||
uf: data.uf ? maskUF(data.uf) : empresaForm.endereco.uf,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
erroCnpj =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Não foi possível buscar os dados do CNPJ.";
|
||||
} finally {
|
||||
carregandoCnpj = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEmpresaTelefoneInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
empresaForm.telefone = maskPhone(target.value);
|
||||
@@ -68,13 +166,87 @@
|
||||
target.value = contatoEmEdicao.telefone;
|
||||
}
|
||||
|
||||
function handleCepInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
empresaForm.endereco.cep = maskCEP(target.value);
|
||||
target.value = empresaForm.endereco.cep;
|
||||
|
||||
const digits = onlyDigits(empresaForm.endereco.cep);
|
||||
if (digits.length === 8) {
|
||||
void buscarCep(digits);
|
||||
}
|
||||
}
|
||||
|
||||
async function buscarCep(cepDigits: string) {
|
||||
carregandoCep = true;
|
||||
erroCep = "";
|
||||
try {
|
||||
const response = await fetch(`https://viacep.com.br/ws/${cepDigits}/json/`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.erro) {
|
||||
throw new Error("CEP não encontrado.");
|
||||
}
|
||||
|
||||
empresaForm.endereco = {
|
||||
...empresaForm.endereco,
|
||||
cep: maskCEP(cepDigits),
|
||||
logradouro: data.logradouro ?? empresaForm.endereco.logradouro,
|
||||
bairro: data.bairro ?? empresaForm.endereco.bairro,
|
||||
cidade: data.localidade ?? empresaForm.endereco.cidade,
|
||||
uf: data.uf ? maskUF(data.uf) : empresaForm.endereco.uf,
|
||||
};
|
||||
} catch (error) {
|
||||
erroCep =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Não foi possível buscar o endereço pelo CEP.";
|
||||
} finally {
|
||||
carregandoCep = false;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeEnderecoForSave(endereco: EnderecoForm) {
|
||||
const hasData =
|
||||
endereco.cep ||
|
||||
endereco.logradouro ||
|
||||
endereco.numero ||
|
||||
endereco.bairro ||
|
||||
endereco.cidade ||
|
||||
endereco.uf;
|
||||
|
||||
if (!hasData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
cep: endereco.cep,
|
||||
logradouro: endereco.logradouro,
|
||||
numero: endereco.numero,
|
||||
complemento: endereco.complemento || undefined,
|
||||
bairro: endereco.bairro,
|
||||
cidade: endereco.cidade,
|
||||
uf: endereco.uf,
|
||||
};
|
||||
}
|
||||
|
||||
function abrirNovaEmpresa() {
|
||||
empresaForm = {
|
||||
nome: "",
|
||||
razao_social: "",
|
||||
nome_fantasia: "",
|
||||
cnpj: "",
|
||||
telefone: "",
|
||||
email: "",
|
||||
descricao: "",
|
||||
endereco: {
|
||||
cep: "",
|
||||
logradouro: "",
|
||||
numero: "",
|
||||
complemento: "",
|
||||
bairro: "",
|
||||
cidade: "",
|
||||
uf: "",
|
||||
},
|
||||
contatos: [],
|
||||
};
|
||||
modalAberto = true;
|
||||
@@ -86,11 +258,21 @@
|
||||
|
||||
empresaForm = {
|
||||
id: detalhes._id,
|
||||
nome: detalhes.nome,
|
||||
razao_social: detalhes.razao_social,
|
||||
nome_fantasia: detalhes.nome_fantasia,
|
||||
cnpj: detalhes.cnpj,
|
||||
telefone: detalhes.telefone,
|
||||
email: detalhes.email,
|
||||
descricao: detalhes.descricao ?? "",
|
||||
endereco: {
|
||||
cep: detalhes.endereco?.cep ?? "",
|
||||
logradouro: detalhes.endereco?.logradouro ?? "",
|
||||
numero: detalhes.endereco?.numero ?? "",
|
||||
complemento: detalhes.endereco?.complemento ?? "",
|
||||
bairro: detalhes.endereco?.bairro ?? "",
|
||||
cidade: detalhes.endereco?.cidade ?? "",
|
||||
uf: detalhes.endereco?.uf ?? "",
|
||||
},
|
||||
contatos:
|
||||
detalhes.contatos?.map((c) => ({
|
||||
_id: c._id,
|
||||
@@ -104,10 +286,10 @@
|
||||
modalAberto = true;
|
||||
}
|
||||
|
||||
async function verContatos(empresaId: Id<"empresas">, nome: string) {
|
||||
async function verContatos(empresaId: Id<"empresas">, razaoSocial: string) {
|
||||
const detalhes = await client.query(api.empresas.getById, { id: empresaId });
|
||||
contatosDaEmpresa = detalhes?.contatos ?? [];
|
||||
empresaContatosNome = nome;
|
||||
empresaContatosNome = razaoSocial;
|
||||
contatosModalAberto = true;
|
||||
}
|
||||
|
||||
@@ -174,36 +356,53 @@
|
||||
}
|
||||
|
||||
async function salvarEmpresa() {
|
||||
if (!empresaForm.nome || !empresaForm.cnpj || !empresaForm.telefone || !empresaForm.email) {
|
||||
if (
|
||||
!empresaForm.razao_social ||
|
||||
!empresaForm.cnpj ||
|
||||
!empresaForm.telefone ||
|
||||
!empresaForm.email
|
||||
) {
|
||||
erroFormulario = "Preencha todos os campos obrigatórios da empresa.";
|
||||
return;
|
||||
}
|
||||
salvando = true;
|
||||
erroFormulario = "";
|
||||
try {
|
||||
const enderecoPayload = normalizeEnderecoForSave(empresaForm.endereco);
|
||||
|
||||
if (empresaForm.id) {
|
||||
await client.mutation(
|
||||
api.empresas.update as typeof api.empresas.update,
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
id: empresaForm.id as any,
|
||||
nome: empresaForm.nome,
|
||||
cnpj: empresaForm.cnpj,
|
||||
telefone: empresaForm.telefone,
|
||||
email: empresaForm.email,
|
||||
descricao: empresaForm.descricao || undefined,
|
||||
contatos: empresaForm.contatos,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await client.mutation(api.empresas.create, {
|
||||
nome: empresaForm.nome,
|
||||
const baseArgs = {
|
||||
id: empresaForm.id,
|
||||
razao_social: empresaForm.razao_social,
|
||||
nome_fantasia: empresaForm.nome_fantasia,
|
||||
cnpj: empresaForm.cnpj,
|
||||
telefone: empresaForm.telefone,
|
||||
email: empresaForm.email,
|
||||
descricao: empresaForm.descricao || undefined,
|
||||
contatos: empresaForm.contatos,
|
||||
});
|
||||
};
|
||||
|
||||
const args = enderecoPayload
|
||||
? { ...baseArgs, endereco: enderecoPayload }
|
||||
: baseArgs;
|
||||
|
||||
await client.mutation(api.empresas.update, args);
|
||||
} else {
|
||||
const baseArgs = {
|
||||
razao_social: empresaForm.razao_social,
|
||||
nome_fantasia: empresaForm.nome_fantasia,
|
||||
cnpj: empresaForm.cnpj,
|
||||
telefone: empresaForm.telefone,
|
||||
email: empresaForm.email,
|
||||
descricao: empresaForm.descricao || undefined,
|
||||
contatos: empresaForm.contatos,
|
||||
};
|
||||
|
||||
const args = enderecoPayload
|
||||
? { ...baseArgs, endereco: enderecoPayload }
|
||||
: baseArgs;
|
||||
|
||||
await client.mutation(api.empresas.create, args);
|
||||
}
|
||||
fecharModal();
|
||||
} catch (error) {
|
||||
@@ -264,34 +463,39 @@
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Razão social / Nome fantasia</th>
|
||||
<th>CNPJ</th>
|
||||
<th>Telefone</th>
|
||||
<th>E-mail</th>
|
||||
<th></th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each empresasQuery.data as empresa (empresa._id)}
|
||||
<tr>
|
||||
<td>{empresa.nome}</td>
|
||||
<td>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold">{empresa.razao_social}</span>
|
||||
{#if empresa.nome_fantasia}
|
||||
<span class="text-xs text-base-content/70">{empresa.nome_fantasia}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td>{empresa.cnpj}</td>
|
||||
<td class="flex items-center gap-2">
|
||||
<Phone class="h-4 w-4 text-base-content/60" strokeWidth={2} />
|
||||
<span>{empresa.telefone}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<Mail class="h-4 w-4 text-base-content/60" strokeWidth={2} />
|
||||
<span>{empresa.email}</span>
|
||||
</div>
|
||||
<td class="flex items-center gap-2">
|
||||
<Mail class="h-4 w-4 text-base-content/60" strokeWidth={2} />
|
||||
<span>{empresa.email}</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
class="btn btn-outline btn-sm gap-2"
|
||||
type="button"
|
||||
onclick={() => verContatos(empresa._id, empresa.nome)}
|
||||
onclick={() => verContatos(empresa._id, empresa.razao_social)}
|
||||
>
|
||||
<Users class="h-4 w-4" strokeWidth={2} />
|
||||
Contatos
|
||||
@@ -339,16 +543,29 @@
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<div class="form-control">
|
||||
<label class="label" for="nomeEmpresa"><span class="label-text">Nome da empresa *</span></label>
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label" for="razaoSocialEmpresa">
|
||||
<span class="label-text">Razão social *</span>
|
||||
</label>
|
||||
<input
|
||||
id="nomeEmpresa"
|
||||
id="razaoSocialEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.nome}
|
||||
placeholder="Nome"
|
||||
bind:value={empresaForm.razao_social}
|
||||
placeholder="Razão social"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="nomeFantasiaEmpresa">
|
||||
<span class="label-text">Nome fantasia</span>
|
||||
</label>
|
||||
<input
|
||||
id="nomeFantasiaEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.nome_fantasia}
|
||||
placeholder="Nome fantasia"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="cnpjEmpresa"><span class="label-text">CNPJ *</span></label>
|
||||
<input
|
||||
@@ -357,9 +574,15 @@
|
||||
value={empresaForm.cnpj}
|
||||
inputmode="numeric"
|
||||
oninput={handleEmpresaCnpjInput}
|
||||
onblur={handleEmpresaCnpjBlur}
|
||||
placeholder="00.000.000/0000-00"
|
||||
required
|
||||
/>
|
||||
{#if carregandoCnpj}
|
||||
<span class="text-xs text-primary mt-1">Buscando dados do CNPJ...</span>
|
||||
{:else if erroCnpj}
|
||||
<span class="text-xs text-error mt-1">{erroCnpj}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="telefoneEmpresa"><span class="label-text">Telefone *</span></label>
|
||||
@@ -398,6 +621,87 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider my-4">Endereço da empresa</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="form-control">
|
||||
<label class="label" for="cepEmpresa"><span class="label-text">CEP</span></label>
|
||||
<input
|
||||
id="cepEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
value={empresaForm.endereco.cep}
|
||||
inputmode="numeric"
|
||||
oninput={handleCepInput}
|
||||
placeholder="00000-000"
|
||||
/>
|
||||
{#if carregandoCep}
|
||||
<span class="text-xs text-primary mt-1">Buscando endereço pelo CEP...</span>
|
||||
{:else if erroCep}
|
||||
<span class="text-xs text-error mt-1">{erroCep}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label" for="logradouroEmpresa"><span class="label-text">Logradouro</span></label>
|
||||
<input
|
||||
id="logradouroEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.endereco.logradouro}
|
||||
placeholder="Rua, avenida, etc."
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="numeroEmpresa"><span class="label-text">Número</span></label>
|
||||
<input
|
||||
id="numeroEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.endereco.numero}
|
||||
placeholder="Número"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="complementoEmpresa"><span class="label-text">Complemento</span></label>
|
||||
<input
|
||||
id="complementoEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.endereco.complemento}
|
||||
placeholder="Sala, bloco, etc."
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="bairroEmpresa"><span class="label-text">Bairro</span></label>
|
||||
<input
|
||||
id="bairroEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.endereco.bairro}
|
||||
placeholder="Bairro"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="cidadeEmpresa"><span class="label-text">Cidade</span></label>
|
||||
<input
|
||||
id="cidadeEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={empresaForm.endereco.cidade}
|
||||
placeholder="Cidade"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="ufEmpresa"><span class="label-text">UF</span></label>
|
||||
<input
|
||||
id="ufEmpresa"
|
||||
class="input input-bordered w-full"
|
||||
maxlength="2"
|
||||
bind:value={empresaForm.endereco.uf}
|
||||
oninput={(event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
empresaForm.endereco.uf = maskUF(target.value);
|
||||
target.value = empresaForm.endereco.uf;
|
||||
}}
|
||||
placeholder="UF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider my-4">Contatos da empresa</div>
|
||||
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
|
||||
Reference in New Issue
Block a user