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.
This commit is contained in:
2025-11-25 14:14:43 -03:00
parent f8d9c17f63
commit 6128c20da0
12 changed files with 3503 additions and 88 deletions

View File

@@ -0,0 +1,124 @@
<script lang="ts">
const {
dueDate,
startedAt,
finishedAt,
status,
expectedDuration
} = $props<{
dueDate: number | undefined;
startedAt: number | undefined;
finishedAt: number | undefined;
status: 'pending' | 'in_progress' | 'completed' | 'blocked';
expectedDuration: number | undefined;
}>();
let now = $state(Date.now());
// Atualizar a cada minuto
$effect(() => {
const interval = setInterval(() => {
now = Date.now();
}, 60000); // Atualizar a cada minuto
return () => clearInterval(interval);
});
const tempoInfo = $derived.by(() => {
// Para etapas concluídas
if (status === 'completed' && finishedAt && startedAt) {
const tempoExecucao = finishedAt - startedAt;
const diasExecucao = Math.floor(tempoExecucao / (1000 * 60 * 60 * 24));
const horasExecucao = Math.floor((tempoExecucao % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
// Verificar se foi dentro ou fora do prazo
const dentroDoPrazo = dueDate ? finishedAt <= dueDate : true;
const diasAtrasado = !dentroDoPrazo && dueDate
? Math.floor((finishedAt - dueDate) / (1000 * 60 * 60 * 24))
: 0;
return {
tipo: 'concluida',
dias: diasExecucao,
horas: horasExecucao,
dentroDoPrazo,
diasAtrasado
};
}
// Para etapas em andamento
if (status === 'in_progress' && startedAt && expectedDuration) {
// Calcular prazo baseado em startedAt + expectedDuration
const prazoCalculado = startedAt + expectedDuration * 24 * 60 * 60 * 1000;
const diff = prazoCalculado - now;
const dias = Math.floor(Math.abs(diff) / (1000 * 60 * 60 * 24));
const horas = Math.floor((Math.abs(diff) % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
return {
tipo: 'andamento',
atrasado: diff < 0,
dias,
horas
};
}
// Para etapas pendentes ou bloqueadas, não mostrar nada
return null;
});
</script>
{#if tempoInfo}
{@const info = tempoInfo}
<div class="flex items-center gap-2">
{#if info.tipo === 'concluida'}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 {info.dentroDoPrazo ? 'text-info' : 'text-error'}"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span class="text-sm font-medium {info.dentroDoPrazo ? 'text-info' : 'text-error'}">
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}
<span> ({info.diasAtrasado} {info.diasAtrasado === 1 ? 'dia' : 'dias'} fora do prazo)</span>
{/if}
</span>
{:else if info.tipo === 'andamento'}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 {info.atrasado ? 'text-error' : 'text-success'}"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span class="text-sm font-medium {info.atrasado ? 'text-error' : 'text-success'}">
{#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}
</span>
{/if}
</div>
{/if}