diff --git a/src/components/tickets/ticket-summary-header.tsx b/src/components/tickets/ticket-summary-header.tsx index 7801209..0eabe08 100644 --- a/src/components/tickets/ticket-summary-header.tsx +++ b/src/components/tickets/ticket-summary-header.tsx @@ -1,6 +1,6 @@ "use client" -import { useCallback, useEffect, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { format, formatDistanceToNow } from "date-fns" import { ptBR } from "date-fns/locale" import { IconClock, IconDownload, IconInfoCircle, IconPlayerPause, IconPlayerPlay } from "@tabler/icons-react" @@ -387,6 +387,9 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { const isPlaying = Boolean(workSummary?.activeSession) const [now, setNow] = useState(() => Date.now()) + // Guarda um marcador local do início da sessão atual para evitar inflar tempo com + // timestamps defasados vindos da rede. Escolhemos o MAIOR entre (remoto, local). + const localStartAtRef = useRef(0) useEffect(() => { if (!workSummary?.activeSession) return @@ -394,7 +397,19 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { setNow(Date.now()) }, 1000) return () => clearInterval(interval) - }, [workSummary?.activeSession, workSummary?.activeSession?.id]) + }, [workSummary?.activeSession?.id]) + + // Sempre que a sessão ativa (id) mudar, sincroniza o ponteiro local + useEffect(() => { + if (!workSummary?.activeSession) { + localStartAtRef.current = 0 + return + } + const remoteStart = Number(workSummary.activeSession.startedAt) || 0 + if (remoteStart > localStartAtRef.current) { + localStartAtRef.current = remoteStart + } + }, [workSummary?.activeSession?.id, workSummary?.activeSession?.startedAt]) useEffect(() => { if (!pauseDialogOpen) { @@ -404,7 +419,13 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { } }, [pauseDialogOpen]) - const currentSessionMs = workSummary?.activeSession ? Math.max(0, now - workSummary.activeSession.startedAt) : 0 + const currentSessionMs = workSummary?.activeSession + ? (() => { + const remoteStart = Number(workSummary.activeSession.startedAt) || 0 + const effectiveStart = Math.max(remoteStart, localStartAtRef.current || 0) + return Math.max(0, now - effectiveStart) + })() + : 0 const totalWorkedMs = workSummary ? workSummary.totalWorkedMs + currentSessionMs : 0 const internalWorkedMs = workSummary ? workSummary.internalWorkedMs + (workSummary.activeSession?.workType === "INTERNAL" ? currentSessionMs : 0) @@ -432,6 +453,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { } // Otimização local: garantir startedAt correto imediatamente const startedAtMs = typeof result?.startedAt === "number" ? result.startedAt : Date.now() + localStartAtRef.current = startedAtMs const sessionId = (result as { sessionId?: unknown })?.sessionId as Id<"ticketWorkSessions"> | undefined setWorkSummary((prev) => { const base: WorkSummarySnapshot = prev ?? { @@ -477,6 +499,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { setPauseDialogOpen(false) // Otimização local: aplicar duração retornada no total e limpar sessão ativa const delta = typeof (result as { durationMs?: unknown })?.durationMs === "number" ? (result as { durationMs?: number }).durationMs! : 0 + localStartAtRef.current = 0 setWorkSummary((prev) => { if (!prev) return prev const workType = prev.activeSession?.workType ?? "INTERNAL"