122 lines
3.6 KiB
Svelte
122 lines
3.6 KiB
Svelte
<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);
|
|
});
|
|
|
|
let 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}
|