feat: enhance user management with matricula retrieval and validation
- Updated user-related queries and mutations to retrieve the matricula from associated funcionario records, improving data accuracy. - Refactored user creation and listing functionalities to ensure matricula is correctly handled and displayed. - Enhanced error handling and validation for user operations, ensuring a more robust user management experience. - Improved the overall structure of user-related code for better maintainability and clarity.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Estados do formulário
|
// Estados do formulário
|
||||||
let matricula = $state("");
|
|
||||||
let nome = $state("");
|
let nome = $state("");
|
||||||
let email = $state("");
|
let email = $state("");
|
||||||
let roleId = $state("");
|
let roleId = $state("");
|
||||||
@@ -30,7 +29,9 @@
|
|||||||
let senhaInicial = $state("");
|
let senhaInicial = $state("");
|
||||||
let confirmarSenha = $state("");
|
let confirmarSenha = $state("");
|
||||||
let processando = $state(false);
|
let processando = $state(false);
|
||||||
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null);
|
let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
function mostrarMensagem(tipo: "success" | "error", texto: string) {
|
function mostrarMensagem(tipo: "success" | "error", texto: string) {
|
||||||
mensagem = { tipo, texto };
|
mensagem = { tipo, texto };
|
||||||
@@ -43,8 +44,7 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Validações
|
// Validações
|
||||||
const matriculaStr = String(matricula).trim();
|
if (!nome.trim() || !email.trim() || !roleId || !senhaInicial) {
|
||||||
if (!matriculaStr || !nome.trim() || !email.trim() || !roleId || !senhaInicial) {
|
|
||||||
mostrarMensagem("error", "Preencha todos os campos obrigatórios");
|
mostrarMensagem("error", "Preencha todos os campos obrigatórios");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -63,11 +63,12 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const resultado = await client.mutation(api.usuarios.criar, {
|
const resultado = await client.mutation(api.usuarios.criar, {
|
||||||
matricula: matriculaStr,
|
|
||||||
nome: nome.trim(),
|
nome: nome.trim(),
|
||||||
email: email.trim(),
|
email: email.trim(),
|
||||||
roleId: roleId as Id<"roles">,
|
roleId: roleId as Id<"roles">,
|
||||||
funcionarioId: funcionarioId ? (funcionarioId as Id<"funcionarios">) : undefined,
|
funcionarioId: funcionarioId
|
||||||
|
? (funcionarioId as Id<"funcionarios">)
|
||||||
|
: undefined,
|
||||||
senhaInicial: senhaInicial,
|
senhaInicial: senhaInicial,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
if (senhaGerada) {
|
if (senhaGerada) {
|
||||||
mostrarMensagem(
|
mostrarMensagem(
|
||||||
"success",
|
"success",
|
||||||
`Usuário criado! SENHA TEMPORÁRIA: ${senhaGerada} - Anote esta senha, ela não será exibida novamente!`
|
`Usuário criado! SENHA TEMPORÁRIA: ${senhaGerada} - Anote esta senha, ela não será exibida novamente!`,
|
||||||
);
|
);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
goto("/ti/usuarios");
|
goto("/ti/usuarios");
|
||||||
@@ -102,17 +103,19 @@
|
|||||||
// Auto-completar ao selecionar funcionário
|
// Auto-completar ao selecionar funcionário
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (funcionarioId && funcionarios?.data) {
|
if (funcionarioId && funcionarios?.data) {
|
||||||
const funcSelecionado = funcionarios.data.find((f: any) => f._id === funcionarioId);
|
const funcSelecionado = funcionarios.data.find(
|
||||||
|
(f: any) => f._id === funcionarioId,
|
||||||
|
);
|
||||||
if (funcSelecionado) {
|
if (funcSelecionado) {
|
||||||
email = funcSelecionado.email || email;
|
email = funcSelecionado.email || email;
|
||||||
nome = funcSelecionado.nome || nome;
|
nome = funcSelecionado.nome || nome;
|
||||||
matricula = funcSelecionado.matricula || matricula;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function gerarSenhaAleatoria() {
|
function gerarSenhaAleatoria() {
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$!";
|
const chars =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$!";
|
||||||
let senha = "";
|
let senha = "";
|
||||||
for (let i = 0; i < 12; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
senha += chars.charAt(Math.floor(Math.random() * chars.length));
|
senha += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
@@ -154,8 +157,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-base-content">Criar Novo Usuário</h1>
|
<h1 class="text-3xl font-bold text-base-content">
|
||||||
<p class="text-base-content/60 mt-1">Cadastre um novo usuário no sistema</p>
|
Criar Novo Usuário
|
||||||
|
</h1>
|
||||||
|
<p class="text-base-content/60 mt-1">
|
||||||
|
Cadastre um novo usuário no sistema
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/ti/usuarios" class="btn btn-outline btn-primary gap-2">
|
<a href="/ti/usuarios" class="btn btn-outline btn-primary gap-2">
|
||||||
@@ -248,7 +255,9 @@
|
|||||||
<!-- Funcionário (primeiro) -->
|
<!-- Funcionário (primeiro) -->
|
||||||
<div class="form-control md:col-span-2">
|
<div class="form-control md:col-span-2">
|
||||||
<label class="label" for="funcionario">
|
<label class="label" for="funcionario">
|
||||||
<span class="label-text font-semibold">Vincular Funcionário (Opcional)</span>
|
<span class="label-text font-semibold"
|
||||||
|
>Vincular Funcionário (Opcional)</span
|
||||||
|
>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="funcionario"
|
id="funcionario"
|
||||||
@@ -256,34 +265,24 @@
|
|||||||
bind:value={funcionarioId}
|
bind:value={funcionarioId}
|
||||||
disabled={processando || !funcionarios?.data}
|
disabled={processando || !funcionarios?.data}
|
||||||
>
|
>
|
||||||
<option value="">Selecione um funcionário para auto-completar dados</option>
|
<option value=""
|
||||||
|
>Selecione um funcionário para auto-completar dados</option
|
||||||
|
>
|
||||||
{#if funcionarios?.data}
|
{#if funcionarios?.data}
|
||||||
{#each funcionarios.data as func}
|
{#each funcionarios.data as func}
|
||||||
<option value={func._id}>{func.nome} - Mat: {func.matricula}</option>
|
<option value={func._id}
|
||||||
|
>{func.nome} - Mat: {func.matricula}</option
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</select>
|
</select>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text-alt">Ao selecionar, os campos serão preenchidos automaticamente</span>
|
<span class="label-text-alt"
|
||||||
|
>Ao selecionar, os campos serão preenchidos automaticamente</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Matrícula -->
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label" for="matricula">
|
|
||||||
<span class="label-text font-semibold">Matrícula *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="matricula"
|
|
||||||
type="number"
|
|
||||||
placeholder="Ex: 12345"
|
|
||||||
class="input input-bordered"
|
|
||||||
bind:value={matricula}
|
|
||||||
required
|
|
||||||
disabled={processando}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nome -->
|
<!-- Nome -->
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label" for="nome">
|
<label class="label" for="nome">
|
||||||
@@ -301,7 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Email -->
|
<!-- Email -->
|
||||||
<div class="form-control md:col-span-2">
|
<div class="form-control">
|
||||||
<label class="label" for="email">
|
<label class="label" for="email">
|
||||||
<span class="label-text font-semibold">E-mail *</span>
|
<span class="label-text font-semibold">E-mail *</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -341,7 +340,9 @@
|
|||||||
</select>
|
</select>
|
||||||
{#if !roles?.data || !Array.isArray(roles.data)}
|
{#if !roles?.data || !Array.isArray(roles.data)}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class="label-text-alt text-warning">Carregando perfis disponíveis...</span>
|
<span class="label-text-alt text-warning"
|
||||||
|
>Carregando perfis disponíveis...</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -446,7 +447,9 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3 class="font-bold">Senha Gerada:</h3>
|
<h3 class="font-bold">Senha Gerada:</h3>
|
||||||
<div class="flex items-center gap-2 mt-2">
|
<div class="flex items-center gap-2 mt-2">
|
||||||
<code class="bg-base-300 px-3 py-2 rounded text-lg font-mono select-all">
|
<code
|
||||||
|
class="bg-base-300 px-3 py-2 rounded text-lg font-mono select-all"
|
||||||
|
>
|
||||||
{senhaGerada}
|
{senhaGerada}
|
||||||
</code>
|
</code>
|
||||||
<button
|
<button
|
||||||
@@ -473,8 +476,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm mt-2">
|
<p class="text-sm mt-2">
|
||||||
⚠️ <strong>IMPORTANTE:</strong> Anote esta senha! Você precisará repassá-la
|
⚠️ <strong>IMPORTANTE:</strong> Anote esta senha! Você precisará
|
||||||
manualmente ao usuário até que o SMTP seja configurado.
|
repassá-la manualmente ao usuário até que o SMTP seja configurado.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -500,18 +503,27 @@
|
|||||||
<h3 class="font-bold">Informações Importantes</h3>
|
<h3 class="font-bold">Informações Importantes</h3>
|
||||||
<ul class="text-sm list-disc list-inside mt-2 space-y-1">
|
<ul class="text-sm list-disc list-inside mt-2 space-y-1">
|
||||||
<li>O usuário deverá alterar a senha no primeiro acesso</li>
|
<li>O usuário deverá alterar a senha no primeiro acesso</li>
|
||||||
<li>As credenciais devem ser repassadas manualmente (por enquanto)</li>
|
|
||||||
<li>
|
<li>
|
||||||
Configure o SMTP em <a href="/ti/configuracoes-email" class="link"
|
As credenciais devem ser repassadas manualmente (por enquanto)
|
||||||
>Configurações de Email</a
|
</li>
|
||||||
|
<li>
|
||||||
|
Configure o SMTP em <a
|
||||||
|
href="/ti/configuracoes-email"
|
||||||
|
class="link">Configurações de Email</a
|
||||||
> para envio automático
|
> para envio automático
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-actions justify-end mt-8 pt-6 border-t border-base-300">
|
<div
|
||||||
<a href="/ti/usuarios" class="btn btn-ghost gap-2" class:btn-disabled={processando}>
|
class="card-actions justify-end mt-8 pt-6 border-t border-base-300"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/ti/usuarios"
|
||||||
|
class="btn btn-ghost gap-2"
|
||||||
|
class:btn-disabled={processando}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-5 w-5"
|
class="h-5 w-5"
|
||||||
@@ -528,7 +540,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Cancelar
|
Cancelar
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-primary gap-2" disabled={processando}>
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary gap-2"
|
||||||
|
disabled={processando}
|
||||||
|
>
|
||||||
{#if processando}
|
{#if processando}
|
||||||
<span class="loading loading-spinner loading-sm"></span>
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
Criando Usuário...
|
Criando Usuário...
|
||||||
@@ -556,4 +572,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
|
|
||||||
|
|||||||
@@ -294,12 +294,19 @@ export const login = mutation({
|
|||||||
timestamp: agora,
|
timestamp: agora,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Obter matrícula do funcionário se houver
|
||||||
|
let matricula: string | undefined = undefined;
|
||||||
|
if (usuario.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sucesso: true as const,
|
sucesso: true as const,
|
||||||
token,
|
token,
|
||||||
usuario: {
|
usuario: {
|
||||||
_id: usuario._id,
|
_id: usuario._id,
|
||||||
matricula: usuario.matricula,
|
matricula: matricula || "",
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
email: usuario.email,
|
email: usuario.email,
|
||||||
funcionarioId: usuario.funcionarioId,
|
funcionarioId: usuario.funcionarioId,
|
||||||
@@ -568,12 +575,19 @@ export const loginComIP = internalMutation({
|
|||||||
timestamp: agora,
|
timestamp: agora,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Obter matrícula do funcionário se houver
|
||||||
|
let matricula: string | undefined = undefined;
|
||||||
|
if (usuario.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sucesso: true as const,
|
sucesso: true as const,
|
||||||
token,
|
token,
|
||||||
usuario: {
|
usuario: {
|
||||||
_id: usuario._id,
|
_id: usuario._id,
|
||||||
matricula: usuario.matricula,
|
matricula: matricula || "",
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
email: usuario.email,
|
email: usuario.email,
|
||||||
funcionarioId: usuario.funcionarioId,
|
funcionarioId: usuario.funcionarioId,
|
||||||
@@ -688,11 +702,18 @@ export const verificarSessao = query({
|
|||||||
return { valido: false as const, motivo: "Role não encontrada" };
|
return { valido: false as const, motivo: "Role não encontrada" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obter matrícula do funcionário se houver
|
||||||
|
let matricula: string | undefined = undefined;
|
||||||
|
if (usuario.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valido: true as const,
|
valido: true as const,
|
||||||
usuario: {
|
usuario: {
|
||||||
_id: usuario._id,
|
_id: usuario._id,
|
||||||
matricula: usuario.matricula,
|
matricula: matricula || "",
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
email: usuario.email,
|
email: usuario.email,
|
||||||
funcionarioId: usuario.funcionarioId,
|
funcionarioId: usuario.funcionarioId,
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ export const criarConversa = mutation({
|
|||||||
conversaId,
|
conversaId,
|
||||||
remetenteId: usuarioAtual._id,
|
remetenteId: usuarioAtual._id,
|
||||||
titulo: "Adicionado a grupo",
|
titulo: "Adicionado a grupo",
|
||||||
descricao: `Você foi adicionado ao grupo "${args.nome || "Sem nome"}" por ${usuarioAtual.nome}`,
|
descricao: `Você foi adicionado ao grupo "${
|
||||||
|
args.nome || "Sem nome"
|
||||||
|
}" por ${usuarioAtual.nome}`,
|
||||||
lida: false,
|
lida: false,
|
||||||
criadaEm: Date.now(),
|
criadaEm: Date.now(),
|
||||||
});
|
});
|
||||||
@@ -226,8 +228,9 @@ export const enviarMensagem = mutation({
|
|||||||
for (const participanteId of conversa.participantes) {
|
for (const participanteId of conversa.participantes) {
|
||||||
// ✅ MODIFICADO: Permite notificação para si mesmo se flag estiver ativa
|
// ✅ MODIFICADO: Permite notificação para si mesmo se flag estiver ativa
|
||||||
const ehOMesmoUsuario = participanteId === usuarioAtual._id;
|
const ehOMesmoUsuario = participanteId === usuarioAtual._id;
|
||||||
const deveCriarNotificacao = !ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo;
|
const deveCriarNotificacao =
|
||||||
|
!ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo;
|
||||||
|
|
||||||
if (deveCriarNotificacao) {
|
if (deveCriarNotificacao) {
|
||||||
const tipoNotificacao = args.mencoes?.includes(participanteId)
|
const tipoNotificacao = args.mencoes?.includes(participanteId)
|
||||||
? "mencao"
|
? "mencao"
|
||||||
@@ -318,7 +321,10 @@ export const cancelarMensagemAgendada = mutation({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mensagem.remetenteId !== usuarioAtual._id) {
|
if (mensagem.remetenteId !== usuarioAtual._id) {
|
||||||
return { sucesso: false, erro: "Você só pode cancelar suas próprias mensagens" };
|
return {
|
||||||
|
sucesso: false,
|
||||||
|
erro: "Você só pode cancelar suas próprias mensagens",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mensagem.agendadaPara) {
|
if (!mensagem.agendadaPara) {
|
||||||
@@ -611,16 +617,20 @@ export const listarConversas = query({
|
|||||||
// Para conversas individuais, pegar o outro usuário
|
// Para conversas individuais, pegar o outro usuário
|
||||||
let outroUsuario = null;
|
let outroUsuario = null;
|
||||||
if (conversa.tipo === "individual") {
|
if (conversa.tipo === "individual") {
|
||||||
const outroUsuarioRaw = participantes.find((p) => p?._id !== usuarioAtual._id);
|
const outroUsuarioRaw = participantes.find(
|
||||||
|
(p) => p?._id !== usuarioAtual._id
|
||||||
|
);
|
||||||
if (outroUsuarioRaw) {
|
if (outroUsuarioRaw) {
|
||||||
// 🔄 BUSCAR DADOS ATUALIZADOS DO USUÁRIO (não usar snapshot)
|
// 🔄 BUSCAR DADOS ATUALIZADOS DO USUÁRIO (não usar snapshot)
|
||||||
const usuarioAtualizado = await ctx.db.get(outroUsuarioRaw._id);
|
const usuarioAtualizado = await ctx.db.get(outroUsuarioRaw._id);
|
||||||
|
|
||||||
if (usuarioAtualizado) {
|
if (usuarioAtualizado) {
|
||||||
// Adicionar URL da foto de perfil
|
// Adicionar URL da foto de perfil
|
||||||
let fotoPerfilUrl = null;
|
let fotoPerfilUrl = null;
|
||||||
if (usuarioAtualizado.fotoPerfil) {
|
if (usuarioAtualizado.fotoPerfil) {
|
||||||
fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtualizado.fotoPerfil);
|
fotoPerfilUrl = await ctx.storage.getUrl(
|
||||||
|
usuarioAtualizado.fotoPerfil
|
||||||
|
);
|
||||||
}
|
}
|
||||||
outroUsuario = {
|
outroUsuario = {
|
||||||
...usuarioAtualizado,
|
...usuarioAtualizado,
|
||||||
@@ -643,7 +653,7 @@ export const listarConversas = query({
|
|||||||
.query("mensagens")
|
.query("mensagens")
|
||||||
.withIndex("by_conversa", (q) => q.eq("conversaId", conversa._id))
|
.withIndex("by_conversa", (q) => q.eq("conversaId", conversa._id))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
const mensagens = todasMensagens.filter((m) => !m.agendadaPara);
|
const mensagens = todasMensagens.filter((m) => !m.agendadaPara);
|
||||||
|
|
||||||
let naoLidas = 0;
|
let naoLidas = 0;
|
||||||
@@ -756,7 +766,16 @@ export const obterMensagensAgendadas = query({
|
|||||||
*/
|
*/
|
||||||
export const listarAgendamentosChat = query({
|
export const listarAgendamentosChat = query({
|
||||||
args: {},
|
args: {},
|
||||||
handler: async (ctx): Promise<Array<Doc<"mensagens"> & { conversaInfo: Doc<"conversas"> | null; destinatarioInfo: Doc<"usuarios"> | null }>> => {
|
handler: async (
|
||||||
|
ctx
|
||||||
|
): Promise<
|
||||||
|
Array<
|
||||||
|
Doc<"mensagens"> & {
|
||||||
|
conversaInfo: Doc<"conversas"> | null;
|
||||||
|
destinatarioInfo: Doc<"usuarios"> | null;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
> => {
|
||||||
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
||||||
if (!usuarioAtual) {
|
if (!usuarioAtual) {
|
||||||
return [];
|
return [];
|
||||||
@@ -912,20 +931,31 @@ export const listarTodosUsuarios = query({
|
|||||||
.withIndex("by_ativo", (q) => q.eq("ativo", true))
|
.withIndex("by_ativo", (q) => q.eq("ativo", true))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Excluir o usuário atual
|
// Excluir o usuário atual e buscar matrículas
|
||||||
return usuarios
|
const usuariosComMatricula = await Promise.all(
|
||||||
.filter((u) => u._id !== usuarioAtual._id)
|
usuarios
|
||||||
.map((u) => ({
|
.filter((u) => u._id !== usuarioAtual._id)
|
||||||
_id: u._id,
|
.map(async (u) => {
|
||||||
nome: u.nome,
|
let matricula: string | undefined = undefined;
|
||||||
email: u.email,
|
if (u.funcionarioId) {
|
||||||
matricula: u.matricula,
|
const funcionario = await ctx.db.get(u.funcionarioId);
|
||||||
avatar: u.avatar,
|
matricula = funcionario?.matricula;
|
||||||
fotoPerfil: u.fotoPerfil,
|
}
|
||||||
statusPresenca: u.statusPresenca,
|
return {
|
||||||
statusMensagem: u.statusMensagem,
|
_id: u._id,
|
||||||
setor: u.setor,
|
nome: u.nome,
|
||||||
}));
|
email: u.email,
|
||||||
|
matricula,
|
||||||
|
avatar: u.avatar,
|
||||||
|
fotoPerfil: u.fotoPerfil,
|
||||||
|
statusPresenca: u.statusPresenca,
|
||||||
|
statusMensagem: u.statusMensagem,
|
||||||
|
setor: u.setor,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return usuariosComMatricula;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -88,9 +88,14 @@ export const listar = query({
|
|||||||
if (log.usuarioId) {
|
if (log.usuarioId) {
|
||||||
const user = await ctx.db.get(log.usuarioId);
|
const user = await ctx.db.get(log.usuarioId);
|
||||||
if (user) {
|
if (user) {
|
||||||
|
let matricula: string | undefined = undefined;
|
||||||
|
if (user.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(user.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula;
|
||||||
|
}
|
||||||
usuario = {
|
usuario = {
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
matricula: user.matricula,
|
matricula: matricula || "",
|
||||||
nome: user.nome,
|
nome: user.nome,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,10 +78,15 @@ export const listarAtividades = query({
|
|||||||
const atividadesComUsuarios = await Promise.all(
|
const atividadesComUsuarios = await Promise.all(
|
||||||
atividades.map(async (atividade) => {
|
atividades.map(async (atividade) => {
|
||||||
const usuario = await ctx.db.get(atividade.usuarioId);
|
const usuario = await ctx.db.get(atividade.usuarioId);
|
||||||
|
let matricula = "N/A";
|
||||||
|
if (usuario?.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula || "N/A";
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...atividade,
|
...atividade,
|
||||||
usuarioNome: usuario?.nome || "Usuário Desconhecido",
|
usuarioNome: usuario?.nome || "Usuário Desconhecido",
|
||||||
usuarioMatricula: usuario?.matricula || "N/A",
|
usuarioMatricula: matricula,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -157,10 +162,15 @@ export const obterHistoricoRecurso = query({
|
|||||||
const atividadesComUsuarios = await Promise.all(
|
const atividadesComUsuarios = await Promise.all(
|
||||||
atividades.map(async (atividade) => {
|
atividades.map(async (atividade) => {
|
||||||
const usuario = await ctx.db.get(atividade.usuarioId);
|
const usuario = await ctx.db.get(atividade.usuarioId);
|
||||||
|
let matricula = "N/A";
|
||||||
|
if (usuario?.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula || "N/A";
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...atividade,
|
...atividade,
|
||||||
usuarioNome: usuario?.nome || "Usuário Desconhecido",
|
usuarioNome: usuario?.nome || "Usuário Desconhecido",
|
||||||
usuarioMatricula: usuario?.matricula || "N/A",
|
usuarioMatricula: matricula,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,18 +30,19 @@ export default defineSchema({
|
|||||||
simboloId: v.id("simbolos"),
|
simboloId: v.id("simbolos"),
|
||||||
simboloTipo: simboloTipo,
|
simboloTipo: simboloTipo,
|
||||||
gestorId: v.optional(v.id("usuarios")),
|
gestorId: v.optional(v.id("usuarios")),
|
||||||
statusFerias: v.optional(v.union(
|
statusFerias: v.optional(
|
||||||
v.literal("ativo"),
|
v.union(v.literal("ativo"), v.literal("em_ferias"))
|
||||||
v.literal("em_ferias")
|
),
|
||||||
)),
|
|
||||||
|
|
||||||
// Regime de trabalho (para cálculo correto de férias)
|
// Regime de trabalho (para cálculo correto de férias)
|
||||||
regimeTrabalho: v.optional(v.union(
|
regimeTrabalho: v.optional(
|
||||||
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
|
v.union(
|
||||||
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
|
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
|
||||||
v.literal("estatutario_federal"), // Servidor Público Federal
|
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
|
||||||
v.literal("estatutario_municipal") // Servidor Público Municipal
|
v.literal("estatutario_federal"), // Servidor Público Federal
|
||||||
)),
|
v.literal("estatutario_municipal") // Servidor Público Municipal
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
// Dados Pessoais Adicionais (opcionais)
|
// Dados Pessoais Adicionais (opcionais)
|
||||||
nomePai: v.optional(v.string()),
|
nomePai: v.optional(v.string()),
|
||||||
@@ -191,10 +192,7 @@ export default defineSchema({
|
|||||||
|
|
||||||
licencas: defineTable({
|
licencas: defineTable({
|
||||||
funcionarioId: v.id("funcionarios"),
|
funcionarioId: v.id("funcionarios"),
|
||||||
tipo: v.union(
|
tipo: v.union(v.literal("maternidade"), v.literal("paternidade")),
|
||||||
v.literal("maternidade"),
|
|
||||||
v.literal("paternidade")
|
|
||||||
),
|
|
||||||
dataInicio: v.string(),
|
dataInicio: v.string(),
|
||||||
dataFim: v.string(),
|
dataFim: v.string(),
|
||||||
documentoId: v.optional(v.id("_storage")),
|
documentoId: v.optional(v.id("_storage")),
|
||||||
@@ -237,11 +235,15 @@ export default defineSchema({
|
|||||||
data: v.number(),
|
data: v.number(),
|
||||||
usuarioId: v.id("usuarios"),
|
usuarioId: v.id("usuarios"),
|
||||||
acao: v.string(),
|
acao: v.string(),
|
||||||
periodosAnteriores: v.optional(v.array(v.object({
|
periodosAnteriores: v.optional(
|
||||||
dataInicio: v.string(),
|
v.array(
|
||||||
dataFim: v.string(),
|
v.object({
|
||||||
diasCorridos: v.number(),
|
dataInicio: v.string(),
|
||||||
}))),
|
dataFim: v.string(),
|
||||||
|
diasCorridos: v.number(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -343,7 +345,6 @@ export default defineSchema({
|
|||||||
|
|
||||||
// Sistema de Autenticação e Controle de Acesso
|
// Sistema de Autenticação e Controle de Acesso
|
||||||
usuarios: defineTable({
|
usuarios: defineTable({
|
||||||
matricula: v.string(),
|
|
||||||
senhaHash: v.string(), // Senha criptografada com bcrypt
|
senhaHash: v.string(), // Senha criptografada com bcrypt
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
email: v.string(),
|
email: v.string(),
|
||||||
@@ -380,7 +381,6 @@ export default defineSchema({
|
|||||||
notificacoesAtivadas: v.optional(v.boolean()),
|
notificacoesAtivadas: v.optional(v.boolean()),
|
||||||
somNotificacao: v.optional(v.boolean()),
|
somNotificacao: v.optional(v.boolean()),
|
||||||
})
|
})
|
||||||
.index("by_matricula", ["matricula"])
|
|
||||||
.index("by_email", ["email"])
|
.index("by_email", ["email"])
|
||||||
.index("by_role", ["roleId"])
|
.index("by_role", ["roleId"])
|
||||||
.index("by_ativo", ["ativo"])
|
.index("by_ativo", ["ativo"])
|
||||||
@@ -500,7 +500,7 @@ export default defineSchema({
|
|||||||
.index("by_data_inicio", ["dataInicio"]),
|
.index("by_data_inicio", ["dataInicio"]),
|
||||||
|
|
||||||
// Perfis Customizados
|
// Perfis Customizados
|
||||||
|
|
||||||
// Templates de Mensagens
|
// Templates de Mensagens
|
||||||
templatesMensagens: defineTable({
|
templatesMensagens: defineTable({
|
||||||
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
|
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
|
||||||
@@ -663,8 +663,7 @@ export default defineSchema({
|
|||||||
mensagensPorMinuto: v.optional(v.number()),
|
mensagensPorMinuto: v.optional(v.number()),
|
||||||
tempoRespostaMedio: v.optional(v.number()),
|
tempoRespostaMedio: v.optional(v.number()),
|
||||||
errosCount: v.optional(v.number()),
|
errosCount: v.optional(v.number()),
|
||||||
})
|
}).index("by_timestamp", ["timestamp"]),
|
||||||
.index("by_timestamp", ["timestamp"]),
|
|
||||||
|
|
||||||
alertConfigurations: defineTable({
|
alertConfigurations: defineTable({
|
||||||
metricName: v.string(),
|
metricName: v.string(),
|
||||||
@@ -681,8 +680,7 @@ export default defineSchema({
|
|||||||
notifyByChat: v.boolean(),
|
notifyByChat: v.boolean(),
|
||||||
createdBy: v.id("usuarios"),
|
createdBy: v.id("usuarios"),
|
||||||
lastModified: v.number(),
|
lastModified: v.number(),
|
||||||
})
|
}).index("by_enabled", ["enabled"]),
|
||||||
.index("by_enabled", ["enabled"]),
|
|
||||||
|
|
||||||
alertHistory: defineTable({
|
alertHistory: defineTable({
|
||||||
configId: v.id("alertConfigurations"),
|
configId: v.id("alertConfigurations"),
|
||||||
|
|||||||
@@ -316,7 +316,6 @@ export const seedDatabase = internalMutation({
|
|||||||
|
|
||||||
const senhaInicial = await hashPassword("Mudar@123");
|
const senhaInicial = await hashPassword("Mudar@123");
|
||||||
await ctx.db.insert("usuarios", {
|
await ctx.db.insert("usuarios", {
|
||||||
matricula: funcionario.matricula,
|
|
||||||
senhaHash: senhaInicial,
|
senhaHash: senhaInicial,
|
||||||
nome: funcionario.nome,
|
nome: funcionario.nome,
|
||||||
email: funcionario.email,
|
email: funcionario.email,
|
||||||
|
|||||||
@@ -4,6 +4,21 @@ import { hashPassword, generateToken } from "./auth/utils";
|
|||||||
import { registrarAtividade } from "./logsAtividades";
|
import { registrarAtividade } from "./logsAtividades";
|
||||||
import { Id, Doc } from "./_generated/dataModel";
|
import { Id, Doc } from "./_generated/dataModel";
|
||||||
import { api } from "./_generated/api";
|
import { api } from "./_generated/api";
|
||||||
|
import type { QueryCtx } from "./_generated/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper para obter a matrícula do usuário (do funcionário se houver)
|
||||||
|
*/
|
||||||
|
async function obterMatriculaUsuario(
|
||||||
|
ctx: QueryCtx,
|
||||||
|
usuario: Doc<"usuarios">
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
if (usuario.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
return funcionario?.matricula;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associar funcionário a um usuário
|
* Associar funcionário a um usuário
|
||||||
@@ -30,8 +45,11 @@ export const associarFuncionario = mutation({
|
|||||||
.first();
|
.first();
|
||||||
|
|
||||||
if (usuarioExistente && usuarioExistente._id !== args.usuarioId) {
|
if (usuarioExistente && usuarioExistente._id !== args.usuarioId) {
|
||||||
|
const matricula = await obterMatriculaUsuario(ctx, usuarioExistente);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Este funcionário já está associado ao usuário: ${usuarioExistente.nome} (${usuarioExistente.matricula})`
|
`Este funcionário já está associado ao usuário: ${
|
||||||
|
usuarioExistente.nome
|
||||||
|
}${matricula ? ` (${matricula})` : ""}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +84,6 @@ export const desassociarFuncionario = mutation({
|
|||||||
*/
|
*/
|
||||||
export const criar = mutation({
|
export const criar = mutation({
|
||||||
args: {
|
args: {
|
||||||
matricula: v.string(),
|
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
email: v.string(),
|
email: v.string(),
|
||||||
roleId: v.id("roles"),
|
roleId: v.id("roles"),
|
||||||
@@ -78,16 +95,6 @@ export const criar = mutation({
|
|||||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
// Verificar se matrícula já existe
|
|
||||||
const existente = await ctx.db
|
|
||||||
.query("usuarios")
|
|
||||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (existente) {
|
|
||||||
return { sucesso: false as const, erro: "Matrícula já cadastrada" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verificar se email já existe
|
// Verificar se email já existe
|
||||||
const emailExistente = await ctx.db
|
const emailExistente = await ctx.db
|
||||||
.query("usuarios")
|
.query("usuarios")
|
||||||
@@ -103,7 +110,6 @@ export const criar = mutation({
|
|||||||
|
|
||||||
// Criar usuário
|
// Criar usuário
|
||||||
const usuarioId = await ctx.db.insert("usuarios", {
|
const usuarioId = await ctx.db.insert("usuarios", {
|
||||||
matricula: args.matricula,
|
|
||||||
senhaHash,
|
senhaHash,
|
||||||
nome: args.nome,
|
nome: args.nome,
|
||||||
email: args.email,
|
email: args.email,
|
||||||
@@ -194,9 +200,17 @@ export const listar = query({
|
|||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
let usuarios = await ctx.db.query("usuarios").collect();
|
let usuarios = await ctx.db.query("usuarios").collect();
|
||||||
|
|
||||||
// Filtrar por matrícula
|
// Filtrar por matrícula (buscar no funcionário)
|
||||||
if (args.matricula) {
|
if (args.matricula) {
|
||||||
usuarios = usuarios.filter((u) => u.matricula.includes(args.matricula!));
|
const usuariosComMatricula = await Promise.all(
|
||||||
|
usuarios.map(async (u) => {
|
||||||
|
const matricula = await obterMatriculaUsuario(ctx, u);
|
||||||
|
return { usuario: u, matricula };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
usuarios = usuariosComMatricula
|
||||||
|
.filter(({ matricula }) => matricula?.includes(args.matricula!))
|
||||||
|
.map(({ usuario }) => usuario);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrar por ativo
|
// Filtrar por ativo
|
||||||
@@ -206,20 +220,25 @@ export const listar = query({
|
|||||||
|
|
||||||
// Buscar roles e funcionários
|
// Buscar roles e funcionários
|
||||||
const resultado = [];
|
const resultado = [];
|
||||||
const usuariosSemRole: Array<{ nome: string; matricula: string; roleId: Id<"roles"> }> = [];
|
const usuariosSemRole: Array<{
|
||||||
|
nome: string;
|
||||||
|
matricula: string;
|
||||||
|
roleId: Id<"roles">;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
for (const usuario of usuarios) {
|
for (const usuario of usuarios) {
|
||||||
try {
|
try {
|
||||||
const role = await ctx.db.get(usuario.roleId);
|
const role = await ctx.db.get(usuario.roleId);
|
||||||
|
|
||||||
// Se a role não existe, criar uma role de erro mas ainda incluir o usuário
|
// Se a role não existe, criar uma role de erro mas ainda incluir o usuário
|
||||||
if (!role) {
|
if (!role) {
|
||||||
|
const matricula = await obterMatriculaUsuario(ctx, usuario);
|
||||||
usuariosSemRole.push({
|
usuariosSemRole.push({
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
matricula: usuario.matricula,
|
matricula: matricula || "N/A",
|
||||||
roleId: usuario.roleId,
|
roleId: usuario.roleId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filtrar por setor - se filtro está ativo e role não existe, pular
|
// Filtrar por setor - se filtro está ativo e role não existe, pular
|
||||||
if (args.setor) {
|
if (args.setor) {
|
||||||
continue;
|
continue;
|
||||||
@@ -240,14 +259,19 @@ export const listar = query({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error);
|
console.error(
|
||||||
|
`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const matriculaUsuario = await obterMatriculaUsuario(ctx, usuario);
|
||||||
|
|
||||||
// Criar role de erro (sem _creationTime pois a role não existe)
|
// Criar role de erro (sem _creationTime pois a role não existe)
|
||||||
resultado.push({
|
resultado.push({
|
||||||
_id: usuario._id,
|
_id: usuario._id,
|
||||||
matricula: usuario.matricula,
|
matricula: matriculaUsuario,
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
email: usuario.email,
|
email: usuario.email,
|
||||||
ativo: usuario.ativo,
|
ativo: usuario.ativo,
|
||||||
@@ -294,7 +318,10 @@ export const listar = query({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error);
|
console.error(
|
||||||
|
`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,14 +332,18 @@ export const listar = query({
|
|||||||
nome: role.nome,
|
nome: role.nome,
|
||||||
nivel: role.nivel,
|
nivel: role.nivel,
|
||||||
...(role.criadoPor !== undefined && { criadoPor: role.criadoPor }),
|
...(role.criadoPor !== undefined && { criadoPor: role.criadoPor }),
|
||||||
...(role.customizado !== undefined && { customizado: role.customizado }),
|
...(role.customizado !== undefined && {
|
||||||
|
customizado: role.customizado,
|
||||||
|
}),
|
||||||
...(role.editavel !== undefined && { editavel: role.editavel }),
|
...(role.editavel !== undefined && { editavel: role.editavel }),
|
||||||
...(role.setor !== undefined && { setor: role.setor }),
|
...(role.setor !== undefined && { setor: role.setor }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const matriculaUsuario = await obterMatriculaUsuario(ctx, usuario);
|
||||||
|
|
||||||
resultado.push({
|
resultado.push({
|
||||||
_id: usuario._id,
|
_id: usuario._id,
|
||||||
matricula: usuario.matricula,
|
matricula: matriculaUsuario,
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
email: usuario.email,
|
email: usuario.email,
|
||||||
ativo: usuario.ativo,
|
ativo: usuario.ativo,
|
||||||
@@ -334,7 +365,12 @@ export const listar = query({
|
|||||||
if (usuariosSemRole.length > 0) {
|
if (usuariosSemRole.length > 0) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`⚠️ Encontrados ${usuariosSemRole.length} usuário(s) com perfil ausente:`,
|
`⚠️ Encontrados ${usuariosSemRole.length} usuário(s) com perfil ausente:`,
|
||||||
usuariosSemRole.map((u) => `${u.nome} (${u.matricula}) - RoleID: ${u.roleId}`)
|
usuariosSemRole.map(
|
||||||
|
(u) =>
|
||||||
|
`${u.nome}${
|
||||||
|
u.matricula !== "N/A" ? ` (${u.matricula})` : ""
|
||||||
|
} - RoleID: ${u.roleId}`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +595,9 @@ export const atualizarPerfil = mutation({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atualizar apenas os campos fornecidos
|
// Atualizar apenas os campos fornecidos
|
||||||
const updates: Partial<Doc<"usuarios">> & { atualizadoEm: number } = { atualizadoEm: Date.now() };
|
const updates: Partial<Doc<"usuarios">> & { atualizadoEm: number } = {
|
||||||
|
atualizadoEm: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
if (args.avatar !== undefined) updates.avatar = args.avatar;
|
if (args.avatar !== undefined) updates.avatar = args.avatar;
|
||||||
if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil;
|
if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil;
|
||||||
@@ -591,7 +629,7 @@ export const obterPerfil = query({
|
|||||||
_id: v.id("usuarios"),
|
_id: v.id("usuarios"),
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
email: v.string(),
|
email: v.string(),
|
||||||
matricula: v.string(),
|
matricula: v.optional(v.string()),
|
||||||
funcionarioId: v.optional(v.id("funcionarios")),
|
funcionarioId: v.optional(v.id("funcionarios")),
|
||||||
avatar: v.optional(v.string()),
|
avatar: v.optional(v.string()),
|
||||||
fotoPerfil: v.optional(v.id("_storage")),
|
fotoPerfil: v.optional(v.id("_storage")),
|
||||||
@@ -675,11 +713,13 @@ export const obterPerfil = query({
|
|||||||
fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtual.fotoPerfil);
|
fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtual.fotoPerfil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const matricula = await obterMatriculaUsuario(ctx, usuarioAtual);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: usuarioAtual._id,
|
_id: usuarioAtual._id,
|
||||||
nome: usuarioAtual.nome,
|
nome: usuarioAtual.nome,
|
||||||
email: usuarioAtual.email,
|
email: usuarioAtual.email,
|
||||||
matricula: usuarioAtual.matricula,
|
matricula: matricula || undefined,
|
||||||
funcionarioId: usuarioAtual.funcionarioId,
|
funcionarioId: usuarioAtual.funcionarioId,
|
||||||
avatar: usuarioAtual.avatar,
|
avatar: usuarioAtual.avatar,
|
||||||
fotoPerfil: usuarioAtual.fotoPerfil,
|
fotoPerfil: usuarioAtual.fotoPerfil,
|
||||||
@@ -735,11 +775,13 @@ export const listarParaChat = query({
|
|||||||
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
|
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const matricula = await obterMatriculaUsuario(ctx, usuario);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: usuario._id,
|
_id: usuario._id,
|
||||||
nome: usuario.nome,
|
nome: usuario.nome,
|
||||||
email: usuario.email,
|
email: usuario.email,
|
||||||
matricula: usuario.matricula || undefined,
|
matricula: matricula || undefined,
|
||||||
avatar: usuario.avatar,
|
avatar: usuario.avatar,
|
||||||
fotoPerfil: usuario.fotoPerfil,
|
fotoPerfil: usuario.fotoPerfil,
|
||||||
fotoPerfilUrl,
|
fotoPerfilUrl,
|
||||||
@@ -1035,7 +1077,6 @@ export const editarUsuario = mutation({
|
|||||||
*/
|
*/
|
||||||
export const criarAdminMaster = mutation({
|
export const criarAdminMaster = mutation({
|
||||||
args: {
|
args: {
|
||||||
matricula: v.string(),
|
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
email: v.string(),
|
email: v.string(),
|
||||||
senha: v.optional(v.string()),
|
senha: v.optional(v.string()),
|
||||||
@@ -1074,32 +1115,9 @@ export const criarAdminMaster = mutation({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se já existir usuário por matrícula, promove/atualiza
|
|
||||||
const existentePorMatricula = await ctx.db
|
|
||||||
.query("usuarios")
|
|
||||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const senhaTemporaria = args.senha || gerarSenhaTemporaria();
|
const senhaTemporaria = args.senha || gerarSenhaTemporaria();
|
||||||
const senhaHash = await hashPassword(senhaTemporaria);
|
const senhaHash = await hashPassword(senhaTemporaria);
|
||||||
|
|
||||||
if (existentePorMatricula) {
|
|
||||||
await ctx.db.patch(existentePorMatricula._id, {
|
|
||||||
nome: args.nome,
|
|
||||||
email: args.email,
|
|
||||||
senhaHash,
|
|
||||||
roleId: roleTIMaster._id,
|
|
||||||
ativo: true,
|
|
||||||
primeiroAcesso: true,
|
|
||||||
atualizadoEm: Date.now(),
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
sucesso: true as const,
|
|
||||||
usuarioId: existentePorMatricula._id,
|
|
||||||
senhaTemporaria,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verificar se email já existe
|
// Verificar se email já existe
|
||||||
const existentePorEmail = await ctx.db
|
const existentePorEmail = await ctx.db
|
||||||
.query("usuarios")
|
.query("usuarios")
|
||||||
@@ -1108,7 +1126,6 @@ export const criarAdminMaster = mutation({
|
|||||||
if (existentePorEmail) {
|
if (existentePorEmail) {
|
||||||
// Promove usuário existente por email
|
// Promove usuário existente por email
|
||||||
await ctx.db.patch(existentePorEmail._id, {
|
await ctx.db.patch(existentePorEmail._id, {
|
||||||
matricula: args.matricula,
|
|
||||||
nome: args.nome,
|
nome: args.nome,
|
||||||
senhaHash,
|
senhaHash,
|
||||||
roleId: roleTIMaster._id,
|
roleId: roleTIMaster._id,
|
||||||
@@ -1125,7 +1142,6 @@ export const criarAdminMaster = mutation({
|
|||||||
|
|
||||||
// Criar novo usuário TI Master
|
// Criar novo usuário TI Master
|
||||||
const usuarioId = await ctx.db.insert("usuarios", {
|
const usuarioId = await ctx.db.insert("usuarios", {
|
||||||
matricula: args.matricula,
|
|
||||||
senhaHash,
|
senhaHash,
|
||||||
nome: args.nome,
|
nome: args.nome,
|
||||||
email: args.email,
|
email: args.email,
|
||||||
@@ -1194,7 +1210,6 @@ export const excluirUsuarioLogico = mutation({
|
|||||||
*/
|
*/
|
||||||
export const criarUsuarioCompleto = mutation({
|
export const criarUsuarioCompleto = mutation({
|
||||||
args: {
|
args: {
|
||||||
matricula: v.string(),
|
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
email: v.string(),
|
email: v.string(),
|
||||||
roleId: v.id("roles"),
|
roleId: v.id("roles"),
|
||||||
@@ -1212,16 +1227,6 @@ export const criarUsuarioCompleto = mutation({
|
|||||||
v.object({ sucesso: v.literal(false), erro: v.string() })
|
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
// Verificar se matrícula já existe
|
|
||||||
const existente = await ctx.db
|
|
||||||
.query("usuarios")
|
|
||||||
.withIndex("by_matricula", (q) => q.eq("matricula", args.matricula))
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (existente) {
|
|
||||||
return { sucesso: false as const, erro: "Matrícula já cadastrada" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verificar se email já existe
|
// Verificar se email já existe
|
||||||
const emailExistente = await ctx.db
|
const emailExistente = await ctx.db
|
||||||
.query("usuarios")
|
.query("usuarios")
|
||||||
@@ -1238,7 +1243,6 @@ export const criarUsuarioCompleto = mutation({
|
|||||||
|
|
||||||
// Criar usuário
|
// Criar usuário
|
||||||
const usuarioId = await ctx.db.insert("usuarios", {
|
const usuarioId = await ctx.db.insert("usuarios", {
|
||||||
matricula: args.matricula,
|
|
||||||
senhaHash,
|
senhaHash,
|
||||||
nome: args.nome,
|
nome: args.nome,
|
||||||
email: args.email,
|
email: args.email,
|
||||||
@@ -1256,7 +1260,7 @@ export const criarUsuarioCompleto = mutation({
|
|||||||
args.criadoPorId,
|
args.criadoPorId,
|
||||||
"criar",
|
"criar",
|
||||||
"usuarios",
|
"usuarios",
|
||||||
JSON.stringify({ usuarioId, matricula: args.matricula, nome: args.nome }),
|
JSON.stringify({ usuarioId, nome: args.nome }),
|
||||||
usuarioId
|
usuarioId
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1272,7 +1276,6 @@ export const criarUsuarioCompleto = mutation({
|
|||||||
*/
|
*/
|
||||||
export const criarAdminPadrao = mutation({
|
export const criarAdminPadrao = mutation({
|
||||||
args: {
|
args: {
|
||||||
matricula: v.optional(v.string()),
|
|
||||||
nome: v.optional(v.string()),
|
nome: v.optional(v.string()),
|
||||||
email: v.optional(v.string()),
|
email: v.optional(v.string()),
|
||||||
senha: v.optional(v.string()),
|
senha: v.optional(v.string()),
|
||||||
@@ -1282,7 +1285,6 @@ export const criarAdminPadrao = mutation({
|
|||||||
usuarioId: v.optional(v.id("usuarios")),
|
usuarioId: v.optional(v.id("usuarios")),
|
||||||
}),
|
}),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const matricula = args.matricula ?? "0000";
|
|
||||||
const nome = args.nome ?? "Administrador Geral";
|
const nome = args.nome ?? "Administrador Geral";
|
||||||
const email = args.email ?? "admin@sgse.pe.gov.br";
|
const email = args.email ?? "admin@sgse.pe.gov.br";
|
||||||
const senha = args.senha ?? "Admin@123";
|
const senha = args.senha ?? "Admin@123";
|
||||||
@@ -1306,12 +1308,7 @@ export const criarAdminPadrao = mutation({
|
|||||||
|
|
||||||
if (!roleAdmin) return { sucesso: false };
|
if (!roleAdmin) return { sucesso: false };
|
||||||
|
|
||||||
// Verificar se já existe por matrícula ou email
|
// Verificar se já existe por email
|
||||||
const existentePorMatricula = await ctx.db
|
|
||||||
.query("usuarios")
|
|
||||||
.withIndex("by_matricula", (q) => q.eq("matricula", matricula))
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const existentePorEmail = await ctx.db
|
const existentePorEmail = await ctx.db
|
||||||
.query("usuarios")
|
.query("usuarios")
|
||||||
.withIndex("by_email", (q) => q.eq("email", email))
|
.withIndex("by_email", (q) => q.eq("email", email))
|
||||||
@@ -1319,10 +1316,8 @@ export const criarAdminPadrao = mutation({
|
|||||||
|
|
||||||
const senhaHash = await hashPassword(senha);
|
const senhaHash = await hashPassword(senha);
|
||||||
|
|
||||||
if (existentePorMatricula || existentePorEmail) {
|
if (existentePorEmail) {
|
||||||
const alvo = existentePorMatricula ?? existentePorEmail!;
|
await ctx.db.patch(existentePorEmail._id, {
|
||||||
await ctx.db.patch(alvo._id, {
|
|
||||||
matricula,
|
|
||||||
nome,
|
nome,
|
||||||
email,
|
email,
|
||||||
senhaHash,
|
senhaHash,
|
||||||
@@ -1331,11 +1326,10 @@ export const criarAdminPadrao = mutation({
|
|||||||
primeiroAcesso: false,
|
primeiroAcesso: false,
|
||||||
atualizadoEm: Date.now(),
|
atualizadoEm: Date.now(),
|
||||||
});
|
});
|
||||||
return { sucesso: true, usuarioId: alvo._id };
|
return { sucesso: true, usuarioId: existentePorEmail._id };
|
||||||
}
|
}
|
||||||
|
|
||||||
const usuarioId = await ctx.db.insert("usuarios", {
|
const usuarioId = await ctx.db.insert("usuarios", {
|
||||||
matricula,
|
|
||||||
senhaHash,
|
senhaHash,
|
||||||
nome,
|
nome,
|
||||||
email,
|
email,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { v } from "convex/values";
|
|||||||
import { Id, Doc } from "./_generated/dataModel";
|
import { Id, Doc } from "./_generated/dataModel";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verificar duplicatas de matrícula
|
* Verificar duplicatas de matrícula (agora busca do funcionário associado)
|
||||||
*/
|
*/
|
||||||
export const verificarDuplicatas = query({
|
export const verificarDuplicatas = query({
|
||||||
args: {},
|
args: {},
|
||||||
@@ -23,18 +23,27 @@ export const verificarDuplicatas = query({
|
|||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
const usuarios = await ctx.db.query("usuarios").collect();
|
const usuarios = await ctx.db.query("usuarios").collect();
|
||||||
|
|
||||||
// Agrupar por matrícula
|
// Agrupar por matrícula do funcionário associado
|
||||||
const gruposPorMatricula = usuarios.reduce((acc, usuario) => {
|
const gruposPorMatricula: Record<string, Array<{ _id: Id<"usuarios">; nome: string; email: string }>> = {};
|
||||||
if (!acc[usuario.matricula]) {
|
|
||||||
acc[usuario.matricula] = [];
|
for (const usuario of usuarios) {
|
||||||
|
let matricula: string | undefined = undefined;
|
||||||
|
if (usuario.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula;
|
||||||
}
|
}
|
||||||
acc[usuario.matricula].push({
|
|
||||||
_id: usuario._id,
|
if (matricula) {
|
||||||
nome: usuario.nome,
|
if (!gruposPorMatricula[matricula]) {
|
||||||
email: usuario.email || "",
|
gruposPorMatricula[matricula] = [];
|
||||||
});
|
}
|
||||||
return acc;
|
gruposPorMatricula[matricula].push({
|
||||||
}, {} as Record<string, Array<{ _id: Id<"usuarios">; nome: string; email: string }>>);
|
_id: usuario._id,
|
||||||
|
nome: usuario.nome,
|
||||||
|
email: usuario.email || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filtrar apenas duplicatas
|
// Filtrar apenas duplicatas
|
||||||
const duplicatas = Object.entries(gruposPorMatricula)
|
const duplicatas = Object.entries(gruposPorMatricula)
|
||||||
@@ -50,7 +59,7 @@ export const verificarDuplicatas = query({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remover duplicatas mantendo apenas o mais recente
|
* Remover duplicatas mantendo apenas o mais recente (agora busca do funcionário associado)
|
||||||
*/
|
*/
|
||||||
export const removerDuplicatas = internalMutation({
|
export const removerDuplicatas = internalMutation({
|
||||||
args: {},
|
args: {},
|
||||||
@@ -61,14 +70,23 @@ export const removerDuplicatas = internalMutation({
|
|||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
const usuarios = await ctx.db.query("usuarios").collect();
|
const usuarios = await ctx.db.query("usuarios").collect();
|
||||||
|
|
||||||
// Agrupar por matrícula
|
// Agrupar por matrícula do funcionário associado
|
||||||
const gruposPorMatricula = usuarios.reduce((acc, usuario) => {
|
const gruposPorMatricula: Record<string, Doc<"usuarios">[]> = {};
|
||||||
if (!acc[usuario.matricula]) {
|
|
||||||
acc[usuario.matricula] = [];
|
for (const usuario of usuarios) {
|
||||||
|
let matricula: string | undefined = undefined;
|
||||||
|
if (usuario.funcionarioId) {
|
||||||
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
||||||
|
matricula = funcionario?.matricula;
|
||||||
}
|
}
|
||||||
acc[usuario.matricula].push(usuario);
|
|
||||||
return acc;
|
if (matricula) {
|
||||||
}, {} as Record<string, Doc<"usuarios">[]>);
|
if (!gruposPorMatricula[matricula]) {
|
||||||
|
gruposPorMatricula[matricula] = [];
|
||||||
|
}
|
||||||
|
gruposPorMatricula[matricula].push(usuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let removidos = 0;
|
let removidos = 0;
|
||||||
const matriculasDuplicadas: string[] = [];
|
const matriculasDuplicadas: string[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user