feat: Refactor order flow initiation logic and enhance UI button styles for better user experience

This commit is contained in:
2026-01-08 09:08:25 -03:00
parent e97bcfbd6a
commit 5a6d9069c9
5 changed files with 95 additions and 59 deletions

View File

@@ -386,14 +386,14 @@
<div class="flex flex-none items-center gap-1"> <div class="flex flex-none items-center gap-1">
<button <button
class="btn btn-circle btn-ghost btn-sm hover:bg-primary/10 hover:text-primary transition-colors" class="btn btn-ghost btn-sm hover:bg-primary/10 hover:text-primary transition-colors"
onclick={() => openEditEtapa(etapa)} onclick={() => openEditEtapa(etapa)}
title="Editar etapa" title="Editar etapa"
> >
<Edit size={16} /> <Edit size={16} />
</button> </button>
<button <button
class="btn btn-circle btn-ghost btn-sm text-base-content/30 hover:bg-error/10 hover:text-error transition-colors" class="btn btn-error btn-sm text-base-content/30 hover:bg-error/10 hover:text-error transition-colors"
onclick={() => handleDeleteEtapa(etapa)} onclick={() => handleDeleteEtapa(etapa)}
title="Excluir etapa" title="Excluir etapa"
> >
@@ -495,7 +495,7 @@
</div> </div>
{:else} {:else}
<button <button
class="btn btn-ghost btn-circle btn-sm text-base-content/30 hover:text-accent hover:bg-accent/10 opacity-0 transition-all group-hover:opacity-100" class="btn btn-ghost btn-sm text-base-content/30 hover:text-accent hover:bg-accent/10 opacity-0 transition-all group-hover:opacity-100"
onclick={() => handleSetPadrao(transicao._id)} onclick={() => handleSetPadrao(transicao._id)}
title="Marcar como avanço padrão" title="Marcar como avanço padrão"
> >
@@ -506,7 +506,7 @@
<div class="divider divider-horizontal mx-0 h-6 opacity-30"></div> <div class="divider divider-horizontal mx-0 h-6 opacity-30"></div>
<button <button
class="btn btn-ghost btn-circle btn-sm text-base-content/30 hover:text-error hover:bg-error/10 transition-all" class="btn btn-sm btn-error text-base-content/30 hover:text-error hover:bg-error/10 transition-all"
onclick={() => handleDeleteTransicao(transicao)} onclick={() => handleDeleteTransicao(transicao)}
title="Excluir transição" title="Excluir transição"
> >
@@ -540,7 +540,7 @@
{editingEtapaId ? 'Editar Etapa' : 'Nova Etapa'} {editingEtapaId ? 'Editar Etapa' : 'Nova Etapa'}
</h3> </h3>
</div> </div>
<button class="btn btn-circle btn-ghost btn-sm" onclick={closeEtapaModal}> <button class="btn btn-circle btn-sm" onclick={closeEtapaModal}>
<X size={20} /> <X size={20} />
</button> </button>
</div> </div>
@@ -697,7 +697,7 @@
</div> </div>
<h3 class="text-lg font-black tracking-tight uppercase">Nova Transição</h3> <h3 class="text-lg font-black tracking-tight uppercase">Nova Transição</h3>
</div> </div>
<button class="btn btn-circle btn-ghost btn-sm" onclick={closeTransicaoModal}> <button class="btn btn-circle btn-sm" onclick={closeTransicaoModal}>
<X size={20} /> <X size={20} />
</button> </button>
</div> </div>
@@ -753,9 +753,7 @@
</select> </select>
</div> </div>
<div <div class="alert alert-info border-info/20 mt-4 flex items-start gap-3 rounded-xl">
class="alert alert-info bg-info/5 border-info/20 mt-4 flex items-start gap-3 rounded-xl"
>
<div class="bg-info text-info-content mt-0.5 rounded-full p-1"> <div class="bg-info text-info-content mt-0.5 rounded-full p-1">
<Star size={10} fill="currentColor" /> <Star size={10} fill="currentColor" />
</div> </div>
@@ -780,9 +778,7 @@
Criar Transição Criar Transição
{/if} {/if}
</button> </button>
<button class="btn btn-ghost btn-block btn-sm" onclick={closeTransicaoModal}> <button class="btn btn-block btn-sm" onclick={closeTransicaoModal}> Descartar </button>
Descartar
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -37,6 +37,7 @@ import type * as contratos from "../contratos.js";
import type * as crons from "../crons.js"; import type * as crons from "../crons.js";
import type * as cursos from "../cursos.js"; import type * as cursos from "../cursos.js";
import type * as dashboard from "../dashboard.js"; import type * as dashboard from "../dashboard.js";
import type * as debug from "../debug.js";
import type * as documentos from "../documentos.js"; import type * as documentos from "../documentos.js";
import type * as email from "../email.js"; import type * as email from "../email.js";
import type * as empresas from "../empresas.js"; import type * as empresas from "../empresas.js";
@@ -138,6 +139,7 @@ declare const fullApi: ApiFromModules<{
crons: typeof crons; crons: typeof crons;
cursos: typeof cursos; cursos: typeof cursos;
dashboard: typeof dashboard; dashboard: typeof dashboard;
debug: typeof debug;
documentos: typeof documentos; documentos: typeof documentos;
email: typeof email; email: typeof email;
empresas: typeof empresas; empresas: typeof empresas;

View File

@@ -0,0 +1,23 @@
import { query } from './_generated/server';
import { v } from 'convex/values';
export const inspectOrder = query({
args: { pedidoId: v.id('pedidos') },
handler: async (ctx, args) => {
const pedido = await ctx.db.get(args.pedidoId);
const historicoEtapas = await ctx.db
.query('pedidoEtapasHistorico')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
.collect();
const etapas = await ctx.db.query('pedidoFluxoEtapa').collect();
const transicoes = await ctx.db.query('pedidoFluxoTransicao').collect();
return {
pedido,
historicoEtapas,
etapasConfiguradas: etapas,
transicoesConfiguradas: transicoes
};
}
});

View File

@@ -20,6 +20,59 @@ async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
return usuario; return usuario;
} }
export async function iniciarFluxoPedidoInternal(
ctx: MutationCtx,
pedidoId: Id<'pedidos'>,
usuarioId: Id<'usuarios'>
) {
const pedido = await ctx.db.get(pedidoId);
if (!pedido) {
throw new Error('Pedido não encontrado');
}
// Verificar se já tem histórico
const historicoExistente = await ctx.db
.query('pedidoEtapasHistorico')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedidoId))
.first();
if (historicoExistente) {
return historicoExistente._id;
}
// Buscar primeira etapa do fluxo
const primeiraEtapa = await ctx.db.query('pedidoFluxoEtapa').withIndex('by_ordem').first();
if (!primeiraEtapa) {
console.warn('Nenhuma etapa configurada no fluxo. Timeline ficará vazia.');
return null;
}
const now = Date.now();
// Criar primeiro registro
const historicoId = await ctx.db.insert('pedidoEtapasHistorico', {
pedidoId,
etapaId: primeiraEtapa._id,
inicioData: now,
atual: true
});
// Registrar no histórico
await ctx.db.insert('historicoPedidos', {
pedidoId,
usuarioId,
acao: 'inicio_fluxo',
detalhes: JSON.stringify({
etapa: primeiraEtapa.codigo,
etapaNome: primeiraEtapa.nome
}),
data: now
});
return historicoId;
}
async function getEtapaAtualDoPedido(ctx: QueryCtx | MutationCtx, pedidoId: Id<'pedidos'>) { async function getEtapaAtualDoPedido(ctx: QueryCtx | MutationCtx, pedidoId: Id<'pedidos'>) {
const etapaAtual = await ctx.db const etapaAtual = await ctx.db
.query('pedidoEtapasHistorico') .query('pedidoEtapasHistorico')
@@ -720,55 +773,10 @@ export const iniciarFluxoPedido = mutation({
args: { args: {
pedidoId: v.id('pedidos') pedidoId: v.id('pedidos')
}, },
returns: v.id('pedidoEtapasHistorico'), returns: v.union(v.id('pedidoEtapasHistorico'), v.null()),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const usuario = await getUsuarioAutenticado(ctx); const usuario = await getUsuarioAutenticado(ctx);
return await iniciarFluxoPedidoInternal(ctx, args.pedidoId, usuario._id);
const pedido = await ctx.db.get(args.pedidoId);
if (!pedido) {
throw new Error('Pedido não encontrado');
}
// Verificar se já tem histórico
const historicoExistente = await ctx.db
.query('pedidoEtapasHistorico')
.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
.first();
if (historicoExistente) {
throw new Error('Este pedido já possui histórico de etapas');
}
// Buscar primeira etapa do fluxo
const primeiraEtapa = await ctx.db.query('pedidoFluxoEtapa').withIndex('by_ordem').first();
if (!primeiraEtapa) {
throw new Error('Nenhuma etapa configurada no fluxo');
}
const now = Date.now();
// Criar primeiro registro
const historicoId = await ctx.db.insert('pedidoEtapasHistorico', {
pedidoId: args.pedidoId,
etapaId: primeiraEtapa._id,
inicioData: now,
atual: true
});
// Registrar no histórico
await ctx.db.insert('historicoPedidos', {
pedidoId: args.pedidoId,
usuarioId: usuario._id,
acao: 'inicio_fluxo',
detalhes: JSON.stringify({
etapa: primeiraEtapa.codigo,
etapaNome: primeiraEtapa.nome
}),
data: now
});
return historicoId;
} }
}); });

View File

@@ -4,6 +4,7 @@ import type { Doc, Id } from './_generated/dataModel';
import type { MutationCtx, QueryCtx } from './_generated/server'; import type { MutationCtx, QueryCtx } from './_generated/server';
import { internalMutation, mutation, query } from './_generated/server'; import { internalMutation, mutation, query } from './_generated/server';
import { getCurrentUserFunction } from './auth'; import { getCurrentUserFunction } from './auth';
import { iniciarFluxoPedidoInternal } from './pedidoFlow';
import { getTodayYMD, isWithinRangeYMD, maxYMD } from './utils/datas'; import { getTodayYMD, isWithinRangeYMD, maxYMD } from './utils/datas';
// ========== HELPERS ========== // ========== HELPERS ==========
@@ -1292,6 +1293,9 @@ export const create = mutation({
data: Date.now() data: Date.now()
}); });
// 5. Iniciar Fluxo se houver etapas
await iniciarFluxoPedidoInternal(ctx, pedidoId, user._id);
return pedidoId; return pedidoId;
} }
}); });
@@ -2079,6 +2083,9 @@ export const enviarParaAceite = mutation({
data: Date.now() data: Date.now()
}); });
// Garantir que o fluxo foi iniciado
await iniciarFluxoPedidoInternal(ctx, args.pedidoId, user._id);
await ctx.scheduler.runAfter(0, internal.pedidos.notifyStatusChange, { await ctx.scheduler.runAfter(0, internal.pedidos.notifyStatusChange, {
pedidoId: args.pedidoId, pedidoId: args.pedidoId,
oldStatus, oldStatus,