- Refactored ErrorModal to utilize a dialog element for better accessibility and user experience, including a close button with an icon. - Updated chat components to improve participant display and message read status, enhancing user engagement and clarity. - Introduced loading indicators for user and conversation data in SalaReuniaoManager to improve responsiveness during data fetching. - Enhanced message handling in MessageList to indicate whether messages have been read, providing users with better feedback on message status. - Improved overall structure and styling across various components for consistency and maintainability.
796 lines
27 KiB
TypeScript
796 lines
27 KiB
TypeScript
import { defineSchema, defineTable } from "convex/server";
|
|
import { Infer, v } from "convex/values";
|
|
|
|
export const simboloTipo = v.union(
|
|
v.literal("cargo_comissionado"),
|
|
v.literal("funcao_gratificada")
|
|
);
|
|
export type SimboloTipo = Infer<typeof simboloTipo>;
|
|
|
|
export default defineSchema({
|
|
todos: defineTable({
|
|
text: v.string(),
|
|
completed: v.boolean(),
|
|
}),
|
|
funcionarios: defineTable({
|
|
// Campos obrigatórios existentes
|
|
nome: v.string(),
|
|
nascimento: v.string(),
|
|
rg: v.string(),
|
|
cpf: v.string(),
|
|
endereco: v.string(),
|
|
cep: v.string(),
|
|
cidade: v.string(),
|
|
uf: v.string(),
|
|
telefone: v.string(),
|
|
email: v.string(),
|
|
matricula: v.optional(v.string()),
|
|
admissaoData: v.optional(v.string()),
|
|
desligamentoData: v.optional(v.string()),
|
|
simboloId: v.id("simbolos"),
|
|
simboloTipo: simboloTipo,
|
|
gestorId: v.optional(v.id("usuarios")),
|
|
statusFerias: v.optional(
|
|
v.union(v.literal("ativo"), v.literal("em_ferias"))
|
|
),
|
|
|
|
// Regime de trabalho (para cálculo correto de férias)
|
|
regimeTrabalho: v.optional(
|
|
v.union(
|
|
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
|
|
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
|
|
v.literal("estatutario_federal"), // Servidor Público Federal
|
|
v.literal("estatutario_municipal") // Servidor Público Municipal
|
|
)
|
|
),
|
|
|
|
// Dados Pessoais Adicionais (opcionais)
|
|
nomePai: v.optional(v.string()),
|
|
nomeMae: v.optional(v.string()),
|
|
naturalidade: v.optional(v.string()),
|
|
naturalidadeUF: v.optional(v.string()),
|
|
sexo: v.optional(
|
|
v.union(v.literal("masculino"), v.literal("feminino"), v.literal("outro"))
|
|
),
|
|
estadoCivil: v.optional(
|
|
v.union(
|
|
v.literal("solteiro"),
|
|
v.literal("casado"),
|
|
v.literal("divorciado"),
|
|
v.literal("viuvo"),
|
|
v.literal("uniao_estavel")
|
|
)
|
|
),
|
|
nacionalidade: v.optional(v.string()),
|
|
|
|
// Documentos Pessoais
|
|
rgOrgaoExpedidor: v.optional(v.string()),
|
|
rgDataEmissao: v.optional(v.string()),
|
|
carteiraProfissionalNumero: v.optional(v.string()),
|
|
carteiraProfissionalSerie: v.optional(v.string()),
|
|
carteiraProfissionalDataEmissao: v.optional(v.string()),
|
|
reservistaNumero: v.optional(v.string()),
|
|
reservistaSerie: v.optional(v.string()),
|
|
tituloEleitorNumero: v.optional(v.string()),
|
|
tituloEleitorZona: v.optional(v.string()),
|
|
tituloEleitorSecao: v.optional(v.string()),
|
|
pisNumero: v.optional(v.string()),
|
|
|
|
// Formação e Saúde
|
|
grauInstrucao: v.optional(
|
|
v.union(
|
|
v.literal("fundamental"),
|
|
v.literal("medio"),
|
|
v.literal("superior"),
|
|
v.literal("pos_graduacao"),
|
|
v.literal("mestrado"),
|
|
v.literal("doutorado")
|
|
)
|
|
),
|
|
formacao: v.optional(v.string()),
|
|
formacaoRegistro: v.optional(v.string()),
|
|
grupoSanguineo: v.optional(
|
|
v.union(v.literal("A"), v.literal("B"), v.literal("AB"), v.literal("O"))
|
|
),
|
|
fatorRH: v.optional(v.union(v.literal("positivo"), v.literal("negativo"))),
|
|
|
|
// Cargo e Vínculo
|
|
descricaoCargo: v.optional(v.string()),
|
|
nomeacaoPortaria: v.optional(v.string()),
|
|
nomeacaoData: v.optional(v.string()),
|
|
nomeacaoDOE: v.optional(v.string()),
|
|
pertenceOrgaoPublico: v.optional(v.boolean()),
|
|
orgaoOrigem: v.optional(v.string()),
|
|
aposentado: v.optional(
|
|
v.union(v.literal("nao"), v.literal("funape_ipsep"), v.literal("inss"))
|
|
),
|
|
|
|
// Dados Bancários
|
|
contaBradescoNumero: v.optional(v.string()),
|
|
contaBradescoDV: v.optional(v.string()),
|
|
contaBradescoAgencia: v.optional(v.string()),
|
|
|
|
// Documentos Anexos (Storage IDs)
|
|
certidaoAntecedentesPF: v.optional(v.id("_storage")),
|
|
certidaoAntecedentesJFPE: v.optional(v.id("_storage")),
|
|
certidaoAntecedentesSDS: v.optional(v.id("_storage")),
|
|
certidaoAntecedentesTJPE: v.optional(v.id("_storage")),
|
|
certidaoImprobidade: v.optional(v.id("_storage")),
|
|
rgFrente: v.optional(v.id("_storage")),
|
|
rgVerso: v.optional(v.id("_storage")),
|
|
cpfFrente: v.optional(v.id("_storage")),
|
|
cpfVerso: v.optional(v.id("_storage")),
|
|
situacaoCadastralCPF: v.optional(v.id("_storage")),
|
|
tituloEleitorFrente: v.optional(v.id("_storage")),
|
|
tituloEleitorVerso: v.optional(v.id("_storage")),
|
|
comprovanteVotacao: v.optional(v.id("_storage")),
|
|
carteiraProfissionalFrente: v.optional(v.id("_storage")),
|
|
carteiraProfissionalVerso: v.optional(v.id("_storage")),
|
|
comprovantePIS: v.optional(v.id("_storage")),
|
|
certidaoRegistroCivil: v.optional(v.id("_storage")),
|
|
certidaoNascimentoDependentes: v.optional(v.id("_storage")),
|
|
cpfDependentes: v.optional(v.id("_storage")),
|
|
reservistaDoc: v.optional(v.id("_storage")),
|
|
comprovanteEscolaridade: v.optional(v.id("_storage")),
|
|
comprovanteResidencia: v.optional(v.id("_storage")),
|
|
comprovanteContaBradesco: v.optional(v.id("_storage")),
|
|
|
|
// Dependentes do funcionário (uploads opcionais)
|
|
dependentes: v.optional(
|
|
v.array(
|
|
v.object({
|
|
parentesco: v.union(
|
|
v.literal("filho"),
|
|
v.literal("filha"),
|
|
v.literal("conjuge"),
|
|
v.literal("outro")
|
|
),
|
|
nome: v.string(),
|
|
cpf: v.string(),
|
|
nascimento: v.string(),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
// Benefícios/declarações por dependente
|
|
salarioFamilia: v.optional(v.boolean()),
|
|
impostoRenda: v.optional(v.boolean()),
|
|
})
|
|
)
|
|
),
|
|
|
|
// Declarações (Storage IDs)
|
|
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
|
|
declaracaoDependentesIR: v.optional(v.id("_storage")),
|
|
declaracaoIdoneidade: v.optional(v.id("_storage")),
|
|
termoNepotismo: v.optional(v.id("_storage")),
|
|
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
|
|
})
|
|
.index("by_matricula", ["matricula"])
|
|
.index("by_nome", ["nome"])
|
|
.index("by_simboloId", ["simboloId"])
|
|
.index("by_simboloTipo", ["simboloTipo"])
|
|
.index("by_cpf", ["cpf"])
|
|
.index("by_rg", ["rg"])
|
|
.index("by_gestor", ["gestorId"]),
|
|
|
|
atestados: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
tipo: v.union(
|
|
v.literal("atestado_medico"),
|
|
v.literal("declaracao_comparecimento")
|
|
),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
cid: v.optional(v.string()), // Apenas para atestado médico
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_data_inicio", ["dataInicio"])
|
|
.index("by_funcionario_and_tipo", ["funcionarioId", "tipo"]),
|
|
|
|
licencas: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
tipo: v.union(v.literal("maternidade"), v.literal("paternidade")),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
observacoes: v.optional(v.string()),
|
|
licencaOriginalId: v.optional(v.id("licencas")), // Para prorrogações
|
|
ehProrrogacao: v.boolean(),
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_data_inicio", ["dataInicio"])
|
|
.index("by_licenca_original", ["licencaOriginalId"])
|
|
.index("by_funcionario_and_tipo", ["funcionarioId", "tipo"]),
|
|
|
|
solicitacoesFerias: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
anoReferencia: v.number(),
|
|
status: v.union(
|
|
v.literal("aguardando_aprovacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado"),
|
|
v.literal("data_ajustada_aprovada")
|
|
),
|
|
periodos: v.array(
|
|
v.object({
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
diasCorridos: v.number(),
|
|
})
|
|
),
|
|
observacao: v.optional(v.string()),
|
|
motivoReprovacao: v.optional(v.string()),
|
|
gestorId: v.optional(v.id("usuarios")),
|
|
dataAprovacao: v.optional(v.number()),
|
|
dataReprovacao: v.optional(v.number()),
|
|
historicoAlteracoes: v.optional(
|
|
v.array(
|
|
v.object({
|
|
data: v.number(),
|
|
usuarioId: v.id("usuarios"),
|
|
acao: v.string(),
|
|
periodosAnteriores: v.optional(
|
|
v.array(
|
|
v.object({
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
diasCorridos: v.number(),
|
|
})
|
|
)
|
|
),
|
|
})
|
|
)
|
|
),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_status", ["status"])
|
|
.index("by_funcionario_and_status", ["funcionarioId", "status"])
|
|
.index("by_ano", ["anoReferencia"]),
|
|
|
|
notificacoesFerias: defineTable({
|
|
destinatarioId: v.id("usuarios"),
|
|
solicitacaoFeriasId: v.id("solicitacoesFerias"),
|
|
tipo: v.union(
|
|
v.literal("nova_solicitacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado"),
|
|
v.literal("data_ajustada")
|
|
),
|
|
lida: v.boolean(),
|
|
mensagem: v.string(),
|
|
})
|
|
.index("by_destinatario", ["destinatarioId"])
|
|
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
|
|
|
|
// Solicitações de Ausências
|
|
solicitacoesAusencias: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
motivo: v.string(),
|
|
status: v.union(
|
|
v.literal("aguardando_aprovacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado")
|
|
),
|
|
gestorId: v.optional(v.id("usuarios")),
|
|
dataAprovacao: v.optional(v.number()),
|
|
dataReprovacao: v.optional(v.number()),
|
|
motivoReprovacao: v.optional(v.string()),
|
|
observacao: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_status", ["status"])
|
|
.index("by_funcionario_and_status", ["funcionarioId", "status"]),
|
|
|
|
notificacoesAusencias: defineTable({
|
|
destinatarioId: v.id("usuarios"),
|
|
solicitacaoAusenciaId: v.id("solicitacoesAusencias"),
|
|
tipo: v.union(
|
|
v.literal("nova_solicitacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado")
|
|
),
|
|
lida: v.boolean(),
|
|
mensagem: v.string(),
|
|
})
|
|
.index("by_destinatario", ["destinatarioId"])
|
|
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
|
|
|
|
// Períodos aquisitivos e saldos de férias
|
|
periodosAquisitivos: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
anoReferencia: v.number(), // Ano do período aquisitivo (ex: 2024)
|
|
dataInicio: v.string(), // Data de início do período aquisitivo
|
|
dataFim: v.string(), // Data de fim do período aquisitivo
|
|
diasDireito: v.number(), // Dias de férias que tem direito (30 ou proporcional)
|
|
diasUsados: v.number(), // Dias já usados
|
|
diasPendentes: v.number(), // Dias em solicitações aguardando aprovação
|
|
diasDisponiveis: v.number(), // Dias disponíveis = direito - usados - pendentes
|
|
abonoPermitido: v.boolean(), // Se pode vender 1/3 das férias
|
|
diasAbono: v.number(), // Dias vendidos como abono pecuniário
|
|
status: v.union(
|
|
v.literal("ativo"), // Período vigente
|
|
v.literal("vencido"), // Período vencido (não tirou férias)
|
|
v.literal("concluido") // Período totalmente utilizado
|
|
),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_funcionario_and_ano", ["funcionarioId", "anoReferencia"])
|
|
.index("by_funcionario_and_status", ["funcionarioId", "status"]),
|
|
|
|
times: defineTable({
|
|
nome: v.string(),
|
|
descricao: v.optional(v.string()),
|
|
gestorId: v.id("usuarios"),
|
|
ativo: v.boolean(),
|
|
cor: v.optional(v.string()), // Cor para identificação visual
|
|
}).index("by_gestor", ["gestorId"]),
|
|
|
|
timesMembros: defineTable({
|
|
timeId: v.id("times"),
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataEntrada: v.number(),
|
|
dataSaida: v.optional(v.number()),
|
|
ativo: v.boolean(),
|
|
})
|
|
.index("by_time", ["timeId"])
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_time_and_ativo", ["timeId", "ativo"]),
|
|
|
|
cursos: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
descricao: v.string(),
|
|
data: v.string(),
|
|
certificadoId: v.optional(v.id("_storage")),
|
|
}).index("by_funcionario", ["funcionarioId"]),
|
|
|
|
simbolos: defineTable({
|
|
nome: v.string(),
|
|
tipo: simboloTipo,
|
|
descricao: v.string(),
|
|
vencValor: v.string(),
|
|
repValor: v.string(),
|
|
valor: v.string(),
|
|
}),
|
|
|
|
solicitacoesAcesso: defineTable({
|
|
nome: v.string(),
|
|
matricula: v.string(),
|
|
email: v.string(),
|
|
telefone: v.string(),
|
|
status: v.union(
|
|
v.literal("pendente"),
|
|
v.literal("aprovado"),
|
|
v.literal("rejeitado")
|
|
),
|
|
dataSolicitacao: v.number(),
|
|
dataResposta: v.optional(v.number()),
|
|
observacoes: v.optional(v.string()),
|
|
})
|
|
.index("by_status", ["status"])
|
|
.index("by_matricula", ["matricula"])
|
|
.index("by_email", ["email"]),
|
|
|
|
// Sistema de Autenticação e Controle de Acesso
|
|
usuarios: defineTable({
|
|
senhaHash: v.string(), // Senha criptografada com bcrypt
|
|
nome: v.string(),
|
|
email: v.string(),
|
|
funcionarioId: v.optional(v.id("funcionarios")),
|
|
roleId: v.id("roles"),
|
|
ativo: v.boolean(),
|
|
primeiroAcesso: v.boolean(),
|
|
ultimoAcesso: v.optional(v.number()),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
|
|
// Controle de Bloqueio e Segurança
|
|
bloqueado: v.optional(v.boolean()),
|
|
motivoBloqueio: v.optional(v.string()),
|
|
dataBloqueio: v.optional(v.number()),
|
|
tentativasLogin: v.optional(v.number()), // contador de tentativas falhas
|
|
ultimaTentativaLogin: v.optional(v.number()), // timestamp da última tentativa
|
|
|
|
// Campos de Chat e Perfil
|
|
avatar: v.optional(v.string()), // "avatar-1" até "avatar-15" ou storageId
|
|
fotoPerfil: v.optional(v.id("_storage")),
|
|
setor: v.optional(v.string()),
|
|
statusMensagem: v.optional(v.string()), // max 100 chars
|
|
statusPresenca: v.optional(
|
|
v.union(
|
|
v.literal("online"),
|
|
v.literal("offline"),
|
|
v.literal("ausente"),
|
|
v.literal("externo"),
|
|
v.literal("em_reuniao")
|
|
)
|
|
),
|
|
ultimaAtividade: v.optional(v.number()), // timestamp
|
|
notificacoesAtivadas: v.optional(v.boolean()),
|
|
somNotificacao: v.optional(v.boolean()),
|
|
})
|
|
.index("by_email", ["email"])
|
|
.index("by_role", ["roleId"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_status_presenca", ["statusPresenca"])
|
|
.index("by_bloqueado", ["bloqueado"])
|
|
.index("by_funcionarioId", ["funcionarioId"]),
|
|
|
|
roles: defineTable({
|
|
nome: v.string(), // "admin", "ti_master", "ti_usuario", "usuario_avancado", "usuario"
|
|
descricao: v.string(),
|
|
nivel: v.number(), // 0 = admin, 1 = ti_master, 2 = ti_usuario, 3+ = customizado
|
|
setor: v.optional(v.string()), // "ti", "rh", "financeiro", etc.
|
|
customizado: v.optional(v.boolean()), // se é um perfil customizado criado por TI_MASTER
|
|
criadoPor: v.optional(v.id("usuarios")), // usuário TI_MASTER que criou este perfil
|
|
editavel: v.optional(v.boolean()), // se pode ser editado (false para roles fixas)
|
|
})
|
|
.index("by_nome", ["nome"])
|
|
.index("by_nivel", ["nivel"])
|
|
.index("by_setor", ["setor"])
|
|
.index("by_customizado", ["customizado"]),
|
|
|
|
permissoes: defineTable({
|
|
nome: v.string(), // "funcionarios.criar", "simbolos.editar", etc.
|
|
descricao: v.string(),
|
|
recurso: v.string(), // "funcionarios", "simbolos", "usuarios", etc.
|
|
acao: v.string(), // "criar", "ler", "editar", "excluir"
|
|
})
|
|
.index("by_recurso", ["recurso"])
|
|
.index("by_recurso_e_acao", ["recurso", "acao"])
|
|
.index("by_nome", ["nome"]),
|
|
|
|
rolePermissoes: defineTable({
|
|
roleId: v.id("roles"),
|
|
permissaoId: v.id("permissoes"),
|
|
})
|
|
.index("by_role", ["roleId"])
|
|
.index("by_permissao", ["permissaoId"]),
|
|
|
|
sessoes: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
token: v.string(),
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
expiraEm: v.number(),
|
|
ativo: v.boolean(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_token", ["token"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_expiracao", ["expiraEm"]),
|
|
|
|
logsAcesso: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
tipo: v.union(
|
|
v.literal("login"),
|
|
v.literal("logout"),
|
|
v.literal("acesso_negado"),
|
|
v.literal("senha_alterada"),
|
|
v.literal("sessao_expirada")
|
|
),
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string()),
|
|
detalhes: v.optional(v.string()),
|
|
timestamp: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_timestamp", ["timestamp"]),
|
|
|
|
// Logs de Login Detalhados
|
|
logsLogin: defineTable({
|
|
usuarioId: v.optional(v.id("usuarios")), // pode ser null se falha antes de identificar usuário
|
|
matriculaOuEmail: v.string(), // tentativa de login
|
|
sucesso: v.boolean(),
|
|
motivoFalha: v.optional(v.string()), // "senha_incorreta", "usuario_bloqueado", "usuario_inexistente"
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string()),
|
|
device: v.optional(v.string()),
|
|
browser: v.optional(v.string()),
|
|
sistema: v.optional(v.string()),
|
|
timestamp: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_sucesso", ["sucesso"])
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_ip", ["ipAddress"]),
|
|
|
|
// Logs de Atividades
|
|
logsAtividades: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
acao: v.string(), // "criar", "editar", "excluir", "bloquear", "desbloquear", etc.
|
|
recurso: v.string(), // "funcionarios", "simbolos", "usuarios", "perfis", etc.
|
|
recursoId: v.optional(v.string()), // ID do recurso afetado
|
|
detalhes: v.optional(v.string()), // JSON com detalhes da ação
|
|
timestamp: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_acao", ["acao"])
|
|
.index("by_recurso", ["recurso"])
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_recurso_id", ["recurso", "recursoId"]),
|
|
|
|
// Histórico de Bloqueios
|
|
bloqueiosUsuarios: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
motivo: v.string(),
|
|
bloqueadoPor: v.id("usuarios"), // ID do TI_MASTER que bloqueou
|
|
dataInicio: v.number(),
|
|
dataFim: v.optional(v.number()), // quando foi desbloqueado
|
|
desbloqueadoPor: v.optional(v.id("usuarios")),
|
|
ativo: v.boolean(), // se é o bloqueio atual ativo
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_bloqueado_por", ["bloqueadoPor"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_data_inicio", ["dataInicio"]),
|
|
|
|
// Perfis Customizados
|
|
|
|
// Templates de Mensagens
|
|
templatesMensagens: defineTable({
|
|
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
|
|
nome: v.string(),
|
|
tipo: v.union(
|
|
v.literal("sistema"), // predefinido, não editável
|
|
v.literal("customizado") // criado por TI_MASTER
|
|
),
|
|
titulo: v.string(),
|
|
corpo: v.string(), // pode ter variáveis {{variavel}}
|
|
variaveis: v.optional(v.array(v.string())), // ["motivo", "senha", etc.]
|
|
criadoPor: v.optional(v.id("usuarios")),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_codigo", ["codigo"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_criado_por", ["criadoPor"]),
|
|
|
|
// Configuração de Email/SMTP
|
|
configuracaoEmail: defineTable({
|
|
servidor: v.string(), // smtp.gmail.com
|
|
porta: v.number(), // 587, 465, etc.
|
|
usuario: v.string(),
|
|
senhaHash: v.string(), // senha criptografada reversível (AES-GCM) - necessário para descriptografar e usar no SMTP
|
|
emailRemetente: v.string(),
|
|
nomeRemetente: v.string(),
|
|
usarSSL: v.boolean(),
|
|
usarTLS: v.boolean(),
|
|
ativo: v.boolean(),
|
|
testadoEm: v.optional(v.number()),
|
|
configuradoPor: v.id("usuarios"),
|
|
atualizadoEm: v.number(),
|
|
}).index("by_ativo", ["ativo"]),
|
|
|
|
// Fila de Emails
|
|
notificacoesEmail: defineTable({
|
|
destinatario: v.string(), // email
|
|
destinatarioId: v.optional(v.id("usuarios")),
|
|
assunto: v.string(),
|
|
corpo: v.string(), // HTML ou texto
|
|
templateId: v.optional(v.id("templatesMensagens")),
|
|
status: v.union(
|
|
v.literal("pendente"),
|
|
v.literal("enviando"),
|
|
v.literal("enviado"),
|
|
v.literal("falha")
|
|
),
|
|
tentativas: v.number(),
|
|
ultimaTentativa: v.optional(v.number()),
|
|
erroDetalhes: v.optional(v.string()),
|
|
enviadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
enviadoEm: v.optional(v.number()),
|
|
agendadaPara: v.optional(v.number()), // timestamp para agendamento
|
|
})
|
|
.index("by_status", ["status"])
|
|
.index("by_destinatario", ["destinatarioId"])
|
|
.index("by_enviado_por", ["enviadoPor"])
|
|
.index("by_criado_em", ["criadoEm"])
|
|
.index("by_agendamento", ["agendadaPara"]),
|
|
|
|
configuracaoAcesso: defineTable({
|
|
chave: v.string(), // "sessao_duracao", "max_tentativas_login", etc.
|
|
valor: v.string(),
|
|
descricao: v.string(),
|
|
}).index("by_chave", ["chave"]),
|
|
|
|
// Rate Limiting de Emails
|
|
rateLimitEmails: defineTable({
|
|
remetenteId: v.id("usuarios"),
|
|
timestamp: v.number(),
|
|
contador: v.number(), // quantidade de emails enviados neste período
|
|
periodo: v.union(
|
|
v.literal("minuto"), // último minuto
|
|
v.literal("hora") // última hora
|
|
),
|
|
})
|
|
.index("by_remetente_periodo", ["remetenteId", "periodo", "timestamp"])
|
|
.index("by_timestamp", ["timestamp"]),
|
|
|
|
// Sistema de Chat
|
|
conversas: defineTable({
|
|
tipo: v.union(v.literal("individual"), v.literal("grupo"), v.literal("sala_reuniao")),
|
|
nome: v.optional(v.string()), // nome do grupo/sala
|
|
avatar: v.optional(v.string()), // avatar do grupo/sala
|
|
participantes: v.array(v.id("usuarios")), // IDs dos participantes
|
|
administradores: v.optional(v.array(v.id("usuarios"))), // IDs dos administradores (apenas para sala_reuniao)
|
|
ultimaMensagem: v.optional(v.string()),
|
|
ultimaMensagemTimestamp: v.optional(v.number()),
|
|
ultimaMensagemRemetenteId: v.optional(v.id("usuarios")), // ID do remetente da última mensagem
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_criado_por", ["criadoPor"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_ultima_mensagem", ["ultimaMensagemTimestamp"]),
|
|
|
|
mensagens: defineTable({
|
|
conversaId: v.id("conversas"),
|
|
remetenteId: v.id("usuarios"),
|
|
tipo: v.union(
|
|
v.literal("texto"),
|
|
v.literal("arquivo"),
|
|
v.literal("imagem")
|
|
),
|
|
conteudo: v.string(), // texto ou nome do arquivo
|
|
conteudoBusca: v.optional(v.string()), // versão normalizada para busca
|
|
arquivoId: v.optional(v.id("_storage")),
|
|
arquivoNome: v.optional(v.string()),
|
|
arquivoTamanho: v.optional(v.number()),
|
|
arquivoTipo: v.optional(v.string()),
|
|
linkPreview: v.optional(
|
|
v.object({
|
|
url: v.string(),
|
|
titulo: v.optional(v.string()),
|
|
descricao: v.optional(v.string()),
|
|
imagem: v.optional(v.string()),
|
|
site: v.optional(v.string()),
|
|
})
|
|
),
|
|
reagiuPor: v.optional(
|
|
v.array(
|
|
v.object({
|
|
usuarioId: v.id("usuarios"),
|
|
emoji: v.string(),
|
|
})
|
|
)
|
|
),
|
|
mencoes: v.optional(v.array(v.id("usuarios"))),
|
|
respostaPara: v.optional(v.id("mensagens")), // ID da mensagem que está respondendo
|
|
agendadaPara: v.optional(v.number()), // timestamp
|
|
enviadaEm: v.number(),
|
|
editadaEm: v.optional(v.number()),
|
|
deletada: v.optional(v.boolean()),
|
|
lidaPor: v.optional(v.array(v.id("usuarios"))), // IDs dos usuários que leram a mensagem
|
|
})
|
|
.index("by_conversa", ["conversaId", "enviadaEm"])
|
|
.index("by_remetente", ["remetenteId"])
|
|
.index("by_agendamento", ["agendadaPara"])
|
|
.index("by_resposta", ["respostaPara"]),
|
|
|
|
leituras: defineTable({
|
|
conversaId: v.id("conversas"),
|
|
usuarioId: v.id("usuarios"),
|
|
ultimaMensagemLida: v.id("mensagens"),
|
|
lidaEm: v.number(),
|
|
})
|
|
.index("by_conversa_usuario", ["conversaId", "usuarioId"])
|
|
.index("by_usuario", ["usuarioId"]),
|
|
|
|
notificacoes: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
tipo: v.union(
|
|
v.literal("nova_mensagem"),
|
|
v.literal("mencao"),
|
|
v.literal("grupo_criado"),
|
|
v.literal("adicionado_grupo")
|
|
),
|
|
conversaId: v.optional(v.id("conversas")),
|
|
mensagemId: v.optional(v.id("mensagens")),
|
|
remetenteId: v.optional(v.id("usuarios")),
|
|
titulo: v.string(),
|
|
descricao: v.string(),
|
|
lida: v.boolean(),
|
|
criadaEm: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId", "lida", "criadaEm"])
|
|
.index("by_usuario_lida", ["usuarioId", "lida"]),
|
|
|
|
digitando: defineTable({
|
|
conversaId: v.id("conversas"),
|
|
usuarioId: v.id("usuarios"),
|
|
iniciouEm: v.number(),
|
|
})
|
|
.index("by_conversa", ["conversaId", "iniciouEm"])
|
|
.index("by_usuario", ["usuarioId"]),
|
|
|
|
// Push Notifications
|
|
pushSubscriptions: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
endpoint: v.string(), // URL do serviço de push
|
|
keys: v.object({
|
|
p256dh: v.string(), // Chave pública
|
|
auth: v.string(), // Chave de autenticação
|
|
}),
|
|
userAgent: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
ultimaAtividade: v.number(),
|
|
ativo: v.boolean(),
|
|
})
|
|
.index("by_usuario", ["usuarioId", "ativo"])
|
|
.index("by_endpoint", ["endpoint"]),
|
|
|
|
// Preferências de Notificação por Conversa
|
|
preferenciasNotificacaoConversa: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
conversaId: v.id("conversas"),
|
|
pushAtivado: v.boolean(), // Receber push notifications
|
|
emailAtivado: v.boolean(), // Receber emails quando offline
|
|
somAtivado: v.boolean(), // Tocar som
|
|
silenciado: v.boolean(), // Silenciar completamente
|
|
apenasMencoes: v.boolean(), // Notificar apenas quando mencionado
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_usuario_conversa", ["usuarioId", "conversaId"])
|
|
.index("by_conversa", ["conversaId"]),
|
|
|
|
// Tabelas de Monitoramento do Sistema
|
|
systemMetrics: defineTable({
|
|
timestamp: v.number(),
|
|
// Métricas de Sistema
|
|
cpuUsage: v.optional(v.number()),
|
|
memoryUsage: v.optional(v.number()),
|
|
networkLatency: v.optional(v.number()),
|
|
storageUsed: v.optional(v.number()),
|
|
// Métricas de Aplicação
|
|
usuariosOnline: v.optional(v.number()),
|
|
mensagensPorMinuto: v.optional(v.number()),
|
|
tempoRespostaMedio: v.optional(v.number()),
|
|
errosCount: v.optional(v.number()),
|
|
}).index("by_timestamp", ["timestamp"]),
|
|
|
|
alertConfigurations: defineTable({
|
|
metricName: v.string(),
|
|
threshold: v.number(),
|
|
operator: v.union(
|
|
v.literal(">"),
|
|
v.literal("<"),
|
|
v.literal(">="),
|
|
v.literal("<="),
|
|
v.literal("==")
|
|
),
|
|
enabled: v.boolean(),
|
|
notifyByEmail: v.boolean(),
|
|
notifyByChat: v.boolean(),
|
|
createdBy: v.id("usuarios"),
|
|
lastModified: v.number(),
|
|
}).index("by_enabled", ["enabled"]),
|
|
|
|
alertHistory: defineTable({
|
|
configId: v.id("alertConfigurations"),
|
|
metricName: v.string(),
|
|
metricValue: v.number(),
|
|
threshold: v.number(),
|
|
timestamp: v.number(),
|
|
status: v.union(v.literal("triggered"), v.literal("resolved")),
|
|
notificationsSent: v.object({
|
|
email: v.boolean(),
|
|
chat: v.boolean(),
|
|
}),
|
|
})
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_status", ["status"])
|
|
.index("by_config", ["configId", "timestamp"]),
|
|
});
|