- Replaced references to "Solicitar Acesso" with "Abrir Chamado" across the application for consistency in terminology. - Updated routing logic to reflect the new ticket management flow, ensuring that the dashboard and sidebar components point to the correct paths. - Removed the obsolete "Solicitar Acesso" page, streamlining the user experience and reducing unnecessary navigation options. - Enhanced backend schema to support new ticket functionalities, including ticket creation and management.
87 lines
2.8 KiB
Svelte
87 lines
2.8 KiB
Svelte
<script lang="ts">
|
|
import type { Doc } from "@sgse-app/backend/convex/_generated/dataModel";
|
|
import {
|
|
formatarData,
|
|
formatarTimelineEtapa,
|
|
prazoRestante,
|
|
timelineStatus,
|
|
} from "$lib/utils/chamados";
|
|
|
|
type Ticket = Doc<"tickets">;
|
|
type TimelineEntry = NonNullable<Ticket["timeline"]>[number];
|
|
|
|
interface Props {
|
|
timeline?: Array<TimelineEntry>;
|
|
}
|
|
|
|
const props = $props<Props>();
|
|
const timeline = $derived<Array<TimelineEntry>>(props.timeline ?? []);
|
|
|
|
const badgeClasses: Record<string, string> = {
|
|
success: "bg-success/20 text-success border-success/40",
|
|
warning: "bg-warning/20 text-warning border-warning/40",
|
|
error: "bg-error/20 text-error border-error/40",
|
|
info: "bg-info/20 text-info border-info/40",
|
|
};
|
|
|
|
function getBadgeClass(entry: TimelineEntry) {
|
|
const status = timelineStatus(entry);
|
|
return badgeClasses[status] ?? badgeClasses.info;
|
|
}
|
|
|
|
function getStatusLabel(entry: TimelineEntry) {
|
|
if (entry.status === "concluido") return "Concluído";
|
|
if (entry.status === "em_andamento") return "Em andamento";
|
|
if (entry.status === "vencido") return "Vencido";
|
|
return "Pendente";
|
|
}
|
|
|
|
function getPrazoDescricao(entry: TimelineEntry) {
|
|
if (entry.status === "concluido" && entry.concluidoEm) {
|
|
return `Concluído em ${formatarData(entry.concluidoEm)}`;
|
|
}
|
|
if (!entry.prazo) return "Sem prazo definido";
|
|
return `${formatarData(entry.prazo)} • ${prazoRestante(entry.prazo) ?? ""}`;
|
|
}
|
|
</script>
|
|
|
|
<div class="space-y-4">
|
|
{#if timeline.length === 0}
|
|
<div class="alert alert-info">
|
|
<span>Nenhuma etapa registrada ainda.</span>
|
|
</div>
|
|
{:else}
|
|
{#each timeline as entry (entry.etapa + entry.prazo)}
|
|
<div class="flex gap-3">
|
|
<div class="relative flex flex-col items-center">
|
|
<div class={`badge border ${getBadgeClass(entry)}`}>
|
|
{formatarTimelineEtapa(entry.etapa)}
|
|
</div>
|
|
{#if entry !== timeline[timeline.length - 1]}
|
|
<div class="bg-base-200/80 mt-2 h-full w-px flex-1"></div>
|
|
{/if}
|
|
</div>
|
|
<div class="flex-1 rounded-2xl border border-base-200 bg-base-100/80 p-4 shadow-sm">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="text-sm font-semibold text-base-content">
|
|
{getStatusLabel(entry)}
|
|
</span>
|
|
{#if entry.status !== "concluido" && entry.prazo}
|
|
<span class="badge badge-sm badge-outline">
|
|
{prazoRestante(entry.prazo)}
|
|
</span>
|
|
{/if}
|
|
</div>
|
|
{#if entry.observacao}
|
|
<p class="text-base-content/70 mt-2 text-sm">{entry.observacao}</p>
|
|
{/if}
|
|
<p class="text-base-content/50 mt-3 text-xs uppercase tracking-wide">
|
|
{getPrazoDescricao(entry)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
|