feat: add empresa and contatos

- 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.
This commit is contained in:
2025-11-17 15:45:48 -03:00
parent 33a9c9e81d
commit 2b94b56f6e
9 changed files with 936 additions and 97 deletions

View File

@@ -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<any, "public">
>;
/**
* 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<any, "internal">
>;

View File

@@ -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<DataModel>
| GenericMutationCtx<DataModel>
| GenericQueryCtx<DataModel>;
/**
* Define a query in this Convex app's public API.
*
@@ -85,12 +92,11 @@ export declare const internalAction: ActionBuilder<DataModel, "internal">;
/**
* 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;

View File

@@ -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;

View File

@@ -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;
},
});

View File

@@ -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(),