From 6128c20da08210c57cc0072179eca1109d8d1f8a Mon Sep 17 00:00:00 2001 From: killer-cf Date: Tue, 25 Nov 2025 14:14:43 -0300 Subject: [PATCH] feat: implement sub-steps management in workflow editor - Added functionality for creating, updating, and deleting sub-steps within the workflow editor. - Introduced a modal for adding new sub-steps, including fields for name and description. - Enhanced the UI to display sub-steps with status indicators and options for updating their status. - Updated navigation links to reflect changes in the workflow structure, ensuring consistency across the application. - Refactored related components to accommodate the new sub-steps feature, improving overall workflow management. --- .../src/lib/components/RelogioPrazo.svelte | 124 ++ .../routes/(dashboard)/fluxos/+page.svelte | 4 +- .../[id] => [id]-fluxo}/+page.svelte | 23 +- .../fluxos/[id]/editor/+page.svelte | 201 +++ .../fluxos/instancias/+page.svelte | 4 +- .../(dashboard)/licitacoes/+page.svelte | 106 +- .../licitacoes/fluxos/+page.svelte | 365 +++++ .../licitacoes/fluxos/[id]/+page.svelte | 1251 +++++++++++++++++ .../funcionarios/+page.svelte | 209 ++- packages/backend/convex/flows.ts | 1103 ++++++++++++++- packages/backend/convex/schema.ts | 57 +- packages/backend/convex/setores.ts | 144 +- 12 files changed, 3503 insertions(+), 88 deletions(-) create mode 100644 apps/web/src/lib/components/RelogioPrazo.svelte rename apps/web/src/routes/(dashboard)/fluxos/{instancias/[id] => [id]-fluxo}/+page.svelte (97%) create mode 100644 apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte create mode 100644 apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte diff --git a/apps/web/src/lib/components/RelogioPrazo.svelte b/apps/web/src/lib/components/RelogioPrazo.svelte new file mode 100644 index 0000000..14c0b91 --- /dev/null +++ b/apps/web/src/lib/components/RelogioPrazo.svelte @@ -0,0 +1,124 @@ + + +{#if tempoInfo} + {@const info = tempoInfo} +
+ {#if info.tipo === 'concluida'} + + + Concluída em {info.dias > 0 ? `${info.dias} ${info.dias === 1 ? 'dia' : 'dias'} e ` : ''} + {info.horas} {info.horas === 1 ? 'hora' : 'horas'} + {#if !info.dentroDoPrazo && info.diasAtrasado > 0} + ({info.diasAtrasado} {info.diasAtrasado === 1 ? 'dia' : 'dias'} fora do prazo) + {/if} + + {:else if info.tipo === 'andamento'} + + + {#if info.atrasado} + {info.dias > 0 ? `${info.dias} ${info.dias === 1 ? 'dia' : 'dias'} e ` : ''} + {info.horas} {info.horas === 1 ? 'hora' : 'horas'} atrasado + {:else} + {info.dias > 0 ? `${info.dias} ${info.dias === 1 ? 'dia' : 'dias'} e ` : ''} + {info.horas} {info.horas === 1 ? 'hora' : 'horas'} para concluir + {/if} + + {/if} +
+{/if} + diff --git a/apps/web/src/routes/(dashboard)/fluxos/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/+page.svelte index 0ad027c..6464732 100644 --- a/apps/web/src/routes/(dashboard)/fluxos/+page.svelte +++ b/apps/web/src/routes/(dashboard)/fluxos/+page.svelte @@ -302,11 +302,11 @@
- + - Ver Instâncias de Fluxo + Ver Fluxos de Trabalho
diff --git a/apps/web/src/routes/(dashboard)/fluxos/instancias/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/[id]-fluxo/+page.svelte similarity index 97% rename from apps/web/src/routes/(dashboard)/fluxos/instancias/[id]/+page.svelte rename to apps/web/src/routes/(dashboard)/fluxos/[id]-fluxo/+page.svelte index 05e9150..18af39c 100644 --- a/apps/web/src/routes/(dashboard)/fluxos/instancias/[id]/+page.svelte +++ b/apps/web/src/routes/(dashboard)/fluxos/[id]-fluxo/+page.svelte @@ -12,8 +12,11 @@ // Query da instância com passos const instanceQuery = useQuery(api.flows.getInstanceWithSteps, () => ({ id: instanceId })); - // Query de usuários (para reatribuição) + // Query de usuários (para reatribuição) - será filtrado por setor no modal const usuariosQuery = useQuery(api.usuarios.listar, {}); + + // Query de usuários por setor para atribuição + let usuariosPorSetorQuery = $state> | null>(null); // Estado de operações let isProcessing = $state(false); @@ -286,8 +289,8 @@ -

Instância não encontrada

- Voltar para lista +

Fluxo não encontrado

+ Voltar para lista {:else} {@const instance = instanceQuery.data.instance} @@ -302,7 +305,7 @@
- + @@ -317,10 +320,12 @@ {instance.templateName ?? 'Fluxo'}
-
- {instance.targetType} - {instance.targetId} -
+ {#if instance.contratoId} +
+ Contrato + {instance.contratoId} +
+ {/if}
+ +
+
+ Sub-etapas + +
+
+ {#if subEtapasQuery.isLoading} +
+ +
+ {:else if subEtapasQuery.data && subEtapasQuery.data.length > 0} + {#each subEtapasQuery.data as subEtapa (subEtapa._id)} +
+
+
{subEtapa.name}
+ {#if subEtapa.description} +
{subEtapa.description}
+ {/if} +
+ + {subEtapa.status === 'completed' ? 'Concluída' : subEtapa.status === 'in_progress' ? 'Em Andamento' : subEtapa.status === 'blocked' ? 'Bloqueada' : 'Pendente'} + +
+
+
+ + +
+
+ {/each} + {:else} +
+ Nenhuma sub-etapa adicionada +
+ {/if} +
+
+
+ +
+ +
+ +
+{/if} + diff --git a/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte index a60ec04..96b96a7 100644 --- a/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte @@ -61,7 +61,7 @@ managerId: managerId as Id<'usuarios'> }); closeCreateModal(); - goto(`/fluxos/instancias/${instanceId}`); + goto(`/licitacoes/fluxos/${instanceId}`); } catch (e) { createError = e instanceof Error ? e.message : 'Erro ao criar instância'; } finally { @@ -237,7 +237,7 @@ {formatDate(instance.startedAt)}
- + - -
-
-
- -
-

Fluxos de Trabalho

+ +
diff --git a/apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte new file mode 100644 index 0000000..a78a8bd --- /dev/null +++ b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte @@ -0,0 +1,365 @@ + + +
+ +
+
+
+
+
+
+ + + Templates + + + Execução + +
+

+ Fluxos de Trabalho +

+

+ Acompanhe e gerencie os fluxos de trabalho. Visualize o progresso, + documentos e responsáveis de cada etapa. +

+
+
+ + + + + + +
+
+
+ + +
+ {#if instancesQuery.isLoading} +
+ +
+ {:else if !instancesQuery.data || instancesQuery.data.length === 0} +
+ +

Nenhum fluxo encontrado

+

+ {statusFilter ? 'Não há fluxos com este status.' : 'Clique em "Novo Fluxo" para iniciar um fluxo.'} +

+
+ {:else} +
+ + + + + + + + + + + + + + {#each instancesQuery.data as instance (instance._id)} + {@const statusBadge = getStatusBadge(instance.status)} + {@const progressPercent = getProgressPercentage(instance.progress.completed, instance.progress.total)} + + + + + + + + + + {/each} + +
TemplateContratoGerenteProgressoStatusIniciado emAções
+
{instance.templateName ?? 'Template desconhecido'}
+
+ {#if instance.contratoId} + {instance.contratoId} + {:else} + - + {/if} + {instance.managerName ?? '-'} +
+ + + {instance.progress.completed}/{instance.progress.total} + +
+
+ {statusBadge.label} + {formatDate(instance.startedAt)} + + + Ver + +
+
+ {/if} +
+
+ + +{#if showCreateModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte new file mode 100644 index 0000000..f8fd33e --- /dev/null +++ b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte @@ -0,0 +1,1251 @@ + + +
+ {#if instanceQuery.isLoading} +
+ +
+ {:else if !instanceQuery.data} +
+ +

Fluxo não encontrado

+ Voltar para lista +
+ {:else} + {@const instance = instanceQuery.data.instance} + {@const steps = instanceQuery.data.steps} + {@const statusBadge = getInstanceStatusBadge(instance.status)} + + +
+
+
+
+
+ + {statusBadge.label} +
+ +
+
+

+ {instance.templateName ?? 'Fluxo'} +

+
+ {#if instance.contratoId} +
+ Contrato + {instance.contratoId} +
+ {/if} +
+ +
+ Gerente: {instance.managerName ?? '-'} + +
+
+
+ + Iniciado: {formatDate(instance.startedAt)} +
+
+ +
+
+
+ + {#if instance.status === 'active'} + + + + {/if} +
+
+
+ + + {#if processingError} +
+ + {processingError} + +
+ {/if} + + +
+

Timeline do Fluxo

+ +
+ {#each steps as step, index (step._id)} + {@const stepStatus = getStatusBadge(step.status)} + {@const isCurrent = isStepCurrent(step._id)} + {@const overdue = step.status !== 'completed' && isOverdue(step.dueDate)} + {@const subEtapasQuery = useQuery(api.flows.listarSubEtapas, () => ({ flowInstanceStepId: step._id }))} + {@const subEtapas = subEtapasQuery.data} + {@const subEtapasCount = subEtapas?.length ?? 0} + {@const subEtapasCompleted = subEtapas?.filter((s: { status: string }) => s.status === 'completed').length ?? 0} + +
+ + {#if index < steps.length - 1} +
+ {/if} + + +
+ {#if step.status === 'completed'} + + {:else if step.status === 'blocked'} + + {:else} + {index + 1} + {/if} +
+ + +
+ +
+
+
+

{step.stepName}

+ {stepStatus.label} + +
+ {#if step.stepDescription} +

{step.stepDescription}

+ {/if} +
+ + + {step.setorNome ?? 'Setor não definido'} + + {#if step.assignedToName} + + + {step.assignedToName} + + {/if} + {#if step.dueDate} + + + Prazo: {formatDate(step.dueDate)} + + {/if} +
+
+ + + {#if instance.status === 'active'} +
+ {#if step.status === 'pending'} + + + + {:else if step.status === 'in_progress'} + + + + + {:else if step.status === 'blocked'} + + + + {/if} + + + + +
+ {/if} +
+ + +
+
+
+ +

Sub-etapas

+ {#if subEtapasCount > 0} + + {subEtapasCompleted} / {subEtapasCount} concluídas + + {/if} +
+ {#if instance.status === 'active'} + + {/if} +
+ + {#if subEtapasQuery.isLoading} +
+ +
+ {:else if subEtapas && subEtapas.length > 0} +
+ {#each subEtapas as subEtapa (subEtapa._id)} +
+
+
+
{subEtapa.name}
+ + {subEtapa.status === 'completed' ? 'Concluída' : subEtapa.status === 'in_progress' ? 'Em Andamento' : subEtapa.status === 'blocked' ? 'Bloqueada' : 'Pendente'} + +
+ {#if subEtapa.description} +
{subEtapa.description}
+ {/if} +
+
+ {#if instance.status === 'active'} + + + {/if} +
+
+ {/each} +
+ {:else} +
+ Nenhuma sub-etapa adicionada +
+ {/if} +
+ + +
+
+
+ +

Documentos

+ {#if step.documents && step.documents.length > 0} + {step.documents.length} + {/if} +
+ {#if instance.status === 'active'} + + + + {/if} +
+ + {#if step.documents && step.documents.length > 0} +
+ {#each step.documents as doc (doc._id)} +
+ +
+
{doc.name}
+
+ {#if instance.status === 'active'} + + + + {/if} +
+ {/each} +
+ {:else} +
+ Nenhum documento anexado +
+ {/if} +
+ + +
+
+
+ +

Notas e Comentários

+ {#if step.notes} + Atualizado + {/if} +
+ +
+ + {#if step.notes} +
+

{step.notes}

+ {#if step.notesUpdatedByName || step.notesUpdatedAt} +
+ + {#if step.notesUpdatedByName} + Atualizado por {step.notesUpdatedByName} + {/if} + {#if step.notesUpdatedAt} + + {formatDate(step.notesUpdatedAt)} + {/if} +
+ {/if} +
+ {:else} +
+ Nenhuma nota ou comentário adicionado +
+ {/if} +
+ + + {#if step.startedAt || step.finishedAt} +
+ {#if step.startedAt} + + + Iniciado: {formatDate(step.startedAt)} + + {/if} + {#if step.finishedAt} + + + Concluído: {formatDate(step.finishedAt)} + + {/if} +
+ {/if} +
+
+ {/each} +
+
+ {/if} + + + {#if showAlterarGestorModal} + + {/if} + + + {#if showSubEtapaModal && stepIdParaSubEtapas} + + {/if} + + + {#if toastMessage} +
+
+ {toastMessage} + +
+
+ {/if} +
+ + +{#if showReassignModal && stepToReassign} + +{/if} + + +{#if showNotesModal && stepForNotes} + +{/if} + + +{#if showUploadModal && stepForUpload} + +{/if} + + +{#if showCancelModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte index 515fd6a..ca05422 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte @@ -1,23 +1,42 @@
-