From 2b94b56f6e8f2f6f0d5e3670918f54d000bfc69d Mon Sep 17 00:00:00 2001 From: killer-cf Date: Mon, 17 Nov 2025 15:45:48 -0300 Subject: [PATCH 01/12] feat: add empresa and contatos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced a new utility function `maskCNPJ` for formatting CNPJ values. - Updated the dashboard pages to replace icons and improve layout, including the addition of a link to manage companies. - Enhanced the display of upcoming features for both the Licitações and Programas Esportivos modules, indicating their development status. --- apps/web/src/lib/utils/masks.ts | 10 + .../(dashboard)/licitacoes/+page.svelte | 86 +-- .../licitacoes/empresas/+page.svelte | 626 ++++++++++++++++++ .../programas-esportivos/+page.svelte | 40 +- packages/backend/convex/_generated/api.d.ts | 36 +- .../backend/convex/_generated/server.d.ts | 16 +- packages/backend/convex/_generated/server.js | 13 +- packages/backend/convex/empresas.ts | 185 ++++++ packages/backend/convex/schema.ts | 21 + 9 files changed, 936 insertions(+), 97 deletions(-) create mode 100644 apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte create mode 100644 packages/backend/convex/empresas.ts diff --git a/apps/web/src/lib/utils/masks.ts b/apps/web/src/lib/utils/masks.ts index 2aab943..d418b61 100644 --- a/apps/web/src/lib/utils/masks.ts +++ b/apps/web/src/lib/utils/masks.ts @@ -43,6 +43,16 @@ export const maskCEP = (value: string): string => { return digits.replace(/(\d{5})(\d{1,3})$/, "$1-$2"); }; +/** Format CNPJ: 00.000.000/0000-00 */ +export const maskCNPJ = (value: string): string => { + const digits = onlyDigits(value).slice(0, 14); + return digits + .replace(/(\d{2})(\d)/, "$1.$2") + .replace(/(\d{3})(\d)/, "$1.$2") + .replace(/(\d{3})(\d)/, "$1/$2") + .replace(/(\d{4})(\d{1,2})$/, "$1-$2"); +}; + /** Format phone: (00) 0000-0000 or (00) 00000-0000 */ export const maskPhone = (value: string): string => { const digits = onlyDigits(value).slice(0, 11); diff --git a/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte index 65b2844..806d0cb 100644 --- a/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte @@ -1,5 +1,5 @@ @@ -25,63 +25,49 @@ - -
-
-
-
- +
+ + - -
-

Funcionalidades Previstas

-
-
-
-
-
- +
+
+
+
+
-

Processos Licitatórios

-
-

Cadastro e acompanhamento de licitações

-
-
- -
-
-
-
- -
-

Fornecedores

-
-

Cadastro e gestão de fornecedores

-
-
- -
-
-
-
- -
-

Documentação

-
-

Gestão de documentos e editais

+

Documentação

+

+ Em breve: gestão de documentos e editais. +

diff --git a/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte new file mode 100644 index 0000000..6ed1c60 --- /dev/null +++ b/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte @@ -0,0 +1,626 @@ + + +
+ + +
+
+
+ +
+
+

Empresas

+

+ Cadastro, listagem e contatos de empresas fornecedoras. +

+
+
+ +
+ +
+
+ {#if empresasQuery.isLoading} +
+ +
+ {:else if empresasQuery.error} +
+ Erro ao carregar empresas. +
+ {:else if empresasQuery.data && empresasQuery.data.length === 0} +
+

Nenhuma empresa cadastrada ainda.

+ +
+ {:else if empresasQuery.data} +
+ + + + + + + + + + + + {#each empresasQuery.data as empresa (empresa._id)} + + + + + + + + {/each} + +
NomeCNPJTelefoneE-mail
{empresa.nome}{empresa.cnpj} + + {empresa.telefone} + +
+ + {empresa.email} +
+
+
+ + +
+
+
+ {/if} +
+
+ + {#if modalAberto} + + {/if} + + {#if contatosModalAberto} + + {/if} +
+ diff --git a/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte b/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte index 60340ef..3a502c4 100644 --- a/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte +++ b/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte @@ -1,5 +1,5 @@ @@ -23,22 +23,36 @@
-
-
-
-
- +
+ + - diff --git a/packages/backend/convex/_generated/api.d.ts b/packages/backend/convex/_generated/api.d.ts index 87b4b87..cd2d43a 100644 --- a/packages/backend/convex/_generated/api.d.ts +++ b/packages/backend/convex/_generated/api.d.ts @@ -15,8 +15,8 @@ import type * as actions_smtp from "../actions/smtp.js"; import type * as actions_utils_nodeCrypto from "../actions/utils/nodeCrypto.js"; import type * as atestadosLicencas from "../atestadosLicencas.js"; import type * as ausencias from "../ausencias.js"; -import type * as auth from "../auth.js"; import type * as auth_utils from "../auth/utils.js"; +import type * as auth from "../auth.js"; import type * as chat from "../chat.js"; import type * as configuracaoEmail from "../configuracaoEmail.js"; import type * as crons from "../crons.js"; @@ -24,6 +24,7 @@ import type * as cursos from "../cursos.js"; import type * as dashboard from "../dashboard.js"; import type * as documentos from "../documentos.js"; import type * as email from "../email.js"; +import type * as empresas from "../empresas.js"; import type * as ferias from "../ferias.js"; import type * as funcionarios from "../funcionarios.js"; import type * as healthCheck from "../healthCheck.js"; @@ -54,6 +55,14 @@ import type { FunctionReference, } from "convex/server"; +/** + * A utility for referencing Convex functions in your app's API. + * + * Usage: + * ```js + * const myFunctionReference = api.myModule.myFunction; + * ``` + */ declare const fullApi: ApiFromModules<{ "actions/email": typeof actions_email; "actions/linkPreview": typeof actions_linkPreview; @@ -62,8 +71,8 @@ declare const fullApi: ApiFromModules<{ "actions/utils/nodeCrypto": typeof actions_utils_nodeCrypto; atestadosLicencas: typeof atestadosLicencas; ausencias: typeof ausencias; - auth: typeof auth; "auth/utils": typeof auth_utils; + auth: typeof auth; chat: typeof chat; configuracaoEmail: typeof configuracaoEmail; crons: typeof crons; @@ -71,6 +80,7 @@ declare const fullApi: ApiFromModules<{ dashboard: typeof dashboard; documentos: typeof documentos; email: typeof email; + empresas: typeof empresas; ferias: typeof ferias; funcionarios: typeof funcionarios; healthCheck: typeof healthCheck; @@ -95,30 +105,14 @@ declare const fullApi: ApiFromModules<{ "utils/getClientIP": typeof utils_getClientIP; verificarMatriculas: typeof verificarMatriculas; }>; +declare const fullApiWithMounts: typeof fullApi; -/** - * A utility for referencing Convex functions in your app's public API. - * - * Usage: - * ```js - * const myFunctionReference = api.myModule.myFunction; - * ``` - */ export declare const api: FilterApi< - typeof fullApi, + typeof fullApiWithMounts, FunctionReference >; - -/** - * A utility for referencing Convex functions in your app's internal API. - * - * Usage: - * ```js - * const myFunctionReference = internal.myModule.myFunction; - * ``` - */ export declare const internal: FilterApi< - typeof fullApi, + typeof fullApiWithMounts, FunctionReference >; diff --git a/packages/backend/convex/_generated/server.d.ts b/packages/backend/convex/_generated/server.d.ts index bec05e6..b5c6828 100644 --- a/packages/backend/convex/_generated/server.d.ts +++ b/packages/backend/convex/_generated/server.d.ts @@ -10,6 +10,7 @@ import { ActionBuilder, + AnyComponents, HttpActionBuilder, MutationBuilder, QueryBuilder, @@ -18,9 +19,15 @@ import { GenericQueryCtx, GenericDatabaseReader, GenericDatabaseWriter, + FunctionReference, } from "convex/server"; import type { DataModel } from "./dataModel.js"; +type GenericCtx = + | GenericActionCtx + | GenericMutationCtx + | GenericQueryCtx; + /** * Define a query in this Convex app's public API. * @@ -85,12 +92,11 @@ export declare const internalAction: ActionBuilder; /** * Define an HTTP action. * - * The wrapped function will be used to respond to HTTP requests received - * by a Convex deployment if the requests matches the path and method where - * this action is routed. Be sure to route your httpAction in `convex/http.js`. + * This function will be used to respond to HTTP requests received by a Convex + * deployment if the requests matches the path and method where this action + * is routed. Be sure to route your action in `convex/http.js`. * - * @param func - The function. It receives an {@link ActionCtx} as its first argument - * and a Fetch API `Request` object as its second. + * @param func - The function. It receives an {@link ActionCtx} as its first argument. * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. */ export declare const httpAction: HttpActionBuilder; diff --git a/packages/backend/convex/_generated/server.js b/packages/backend/convex/_generated/server.js index bf3d25a..4a21df4 100644 --- a/packages/backend/convex/_generated/server.js +++ b/packages/backend/convex/_generated/server.js @@ -16,6 +16,7 @@ import { internalActionGeneric, internalMutationGeneric, internalQueryGeneric, + componentsGeneric, } from "convex/server"; /** @@ -80,14 +81,10 @@ export const action = actionGeneric; export const internalAction = internalActionGeneric; /** - * Define an HTTP action. + * Define a Convex HTTP action. * - * The wrapped function will be used to respond to HTTP requests received - * by a Convex deployment if the requests matches the path and method where - * this action is routed. Be sure to route your httpAction in `convex/http.js`. - * - * @param func - The function. It receives an {@link ActionCtx} as its first argument - * and a Fetch API `Request` object as its second. - * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. + * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object + * as its second. + * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. */ export const httpAction = httpActionGeneric; diff --git a/packages/backend/convex/empresas.ts b/packages/backend/convex/empresas.ts new file mode 100644 index 0000000..04fbbce --- /dev/null +++ b/packages/backend/convex/empresas.ts @@ -0,0 +1,185 @@ +import { v } from "convex/values"; +import { mutation, query } from "./_generated/server"; +import { internal } from "./_generated/api"; +import { getCurrentUserFunction } from "./auth"; + +export const list = query({ + args: {}, + handler: async (ctx) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: "empresas", + acao: "listar", + }); + + const empresas = await ctx.db.query("empresas").collect(); + return empresas; + }, +}); + +export const getById = query({ + args: { id: v.id("empresas") }, + handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: "empresas", + acao: "ver", + }); + + const empresa = await ctx.db.get(args.id); + if (!empresa) { + return null; + } + + const contatos = await ctx.db + .query("contatosEmpresa") + .withIndex("by_empresa", (q) => q.eq("empresaId", args.id)) + .collect(); + + return { ...empresa, contatos }; + }, +}); + +const contatoInput = v.object({ + _id: v.optional(v.id("contatosEmpresa")), + empresaId: v.optional(v.id("empresas")), + nome: v.string(), + funcao: v.string(), + email: v.string(), + telefone: v.string(), + adicionadoPor: v.optional(v.id("usuarios")), + descricao: v.optional(v.string()), + _deleted: v.optional(v.boolean()), +}); + +export const create = mutation({ + args: { + nome: v.string(), + cnpj: v.string(), + telefone: v.string(), + email: v.string(), + descricao: v.optional(v.string()), + contatos: v.optional(v.array(contatoInput)), + }, + returns: v.id("empresas"), + handler: async (ctx, args) => { + const usuarioAtual = await getCurrentUserFunction(ctx); + + if (!usuarioAtual) { + throw new Error("Usuário não autenticado."); + } + + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: "empresas", + acao: "criar", + }); + + const cnpjExistente = await ctx.db + .query("empresas") + .withIndex("by_cnpj", (q) => q.eq("cnpj", args.cnpj)) + .unique(); + + if (cnpjExistente) { + throw new Error("Já existe uma empresa cadastrada com este CNPJ."); + } + + + + const empresaId = await ctx.db.insert("empresas", { + nome: args.nome, + cnpj: args.cnpj, + telefone: args.telefone, + email: args.email, + descricao: args.descricao, + criadoPor: usuarioAtual._id, + }); + + if (args.contatos && args.contatos.length > 0) { + for (const contato of args.contatos) { + await ctx.db.insert("contatosEmpresa", { + empresaId, + nome: contato.nome, + funcao: contato.funcao, + email: contato.email, + telefone: contato.telefone, + adicionadoPor: usuarioAtual._id, + descricao: contato.descricao, + }); + } + } + + return empresaId; + }, +}); + +export const update = mutation({ + args: { + id: v.id("empresas"), + nome: v.string(), + cnpj: v.string(), + telefone: v.string(), + email: v.string(), + descricao: v.optional(v.string()), + contatos: v.optional(v.array(contatoInput)), + }, + returns: v.null(), + handler: async (ctx, args) => { + await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { + recurso: "empresas", + acao: "editar", + }); + + const cnpjExistente = await ctx.db + .query("empresas") + .withIndex("by_cnpj", (q) => q.eq("cnpj", args.cnpj)) + .unique(); + + if (cnpjExistente && cnpjExistente._id !== args.id) { + throw new Error("Já existe uma empresa cadastrada com este CNPJ."); + } + + await ctx.db.patch(args.id, { + nome: args.nome, + cnpj: args.cnpj, + telefone: args.telefone, + email: args.email, + descricao: args.descricao, + }); + + if (!args.contatos) { + return null; + } + + for (const contato of args.contatos) { + if (contato._id && contato._deleted) { + await ctx.db.delete(contato._id); + } else if (contato._id) { + await ctx.db.patch(contato._id, { + nome: contato.nome, + funcao: contato.funcao, + email: contato.email, + telefone: contato.telefone, + descricao: contato.descricao, + }); + } else if (!contato._deleted) { + const usuarioAtual = await getCurrentUserFunction(ctx); + + if (!usuarioAtual) { + throw new Error("Usuário não autenticado."); + } + + await ctx.db.insert("contatosEmpresa", { + empresaId: args.id, + nome: contato.nome, + funcao: contato.funcao, + email: contato.email, + telefone: contato.telefone, + adicionadoPor: usuarioAtual._id, + descricao: contato.descricao, + }); + } + } + + return null; + }, +}); + + diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 602221b..a39ea4b 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -12,6 +12,27 @@ export default defineSchema({ text: v.string(), completed: v.boolean(), }), + empresas: defineTable({ + nome: v.string(), + cnpj: v.string(), + telefone: v.string(), + email: v.string(), + descricao: v.optional(v.string()), + criadoPor: v.optional(v.id("usuarios")), + }) + .index("by_nome", ["nome"]) + .index("by_cnpj", ["cnpj"]), + contatosEmpresa: defineTable({ + empresaId: v.id("empresas"), + nome: v.string(), + funcao: v.string(), + email: v.string(), + telefone: v.string(), + adicionadoPor: v.optional(v.id("usuarios")), + descricao: v.optional(v.string()), + }) + .index("by_empresa", ["empresaId"]) + .index("by_email", ["email"]), funcionarios: defineTable({ // Campos obrigatórios existentes nome: v.string(), From 029cd9c637e75dcdbaf470df1ecfd0ff178c346c Mon Sep 17 00:00:00 2001 From: killer-cf Date: Tue, 18 Nov 2025 11:15:44 -0300 Subject: [PATCH 02/12] add endereco e edita tabela empresas --- .../licitacoes/empresas/+page.svelte | 406 +++++++++++++++--- bun.lock | 1 + packages/backend/convex/_generated/api.d.ts | 34 +- .../backend/convex/_generated/server.d.ts | 16 +- packages/backend/convex/_generated/server.js | 13 +- packages/backend/convex/empresas.ts | 133 +++++- packages/backend/convex/schema.ts | 17 +- 7 files changed, 525 insertions(+), 95 deletions(-) diff --git a/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte index 6ed1c60..189872f 100644 --- a/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte +++ b/apps/web/src/routes/(dashboard)/licitacoes/empresas/+page.svelte @@ -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({ + razao_social: "", + nome_fantasia: "", cnpj: "", telefone: "", email: "", descricao: "", + endereco: { + cep: "", + logradouro: "", + numero: "", + complemento: "", + bairro: "", + cidade: "", + uf: "", + }, contatos: [], + }); + + let contatoEmEdicao = $state(null); + let contatoIndiceEdicao = $state(null); + let erroFormulario = $state(""); + let salvando = $state(false); + + let contatosModalAberto = $state(false); + let contatosDaEmpresa = $state([]); + 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 @@ - + - + {#each empresasQuery.data as empresa (empresa._id)} - + -
NomeRazão social / Nome fantasia CNPJ Telefone E-mailAções
{empresa.nome} +
+ {empresa.razao_social} + {#if empresa.nome_fantasia} + {empresa.nome_fantasia} + {/if} +
+
{empresa.cnpj} {empresa.telefone} -
- - {empresa.email} -
+
+ + {empresa.email}