tickets: prevent inflated running time by clamping session start to max(remote, local click); reset on pause; tighten interval deps

This commit is contained in:
Esdras Renan 2025-10-19 19:12:57 -03:00
parent f550fa5952
commit 1df7e13c8f

View file

@ -1,6 +1,6 @@
"use client" "use client"
import { useCallback, useEffect, useMemo, useState } from "react" import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { format, formatDistanceToNow } from "date-fns" import { format, formatDistanceToNow } from "date-fns"
import { ptBR } from "date-fns/locale" import { ptBR } from "date-fns/locale"
import { IconClock, IconDownload, IconInfoCircle, IconPlayerPause, IconPlayerPlay } from "@tabler/icons-react" 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 isPlaying = Boolean(workSummary?.activeSession)
const [now, setNow] = useState(() => Date.now()) 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<number>(0)
useEffect(() => { useEffect(() => {
if (!workSummary?.activeSession) return if (!workSummary?.activeSession) return
@ -394,7 +397,19 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
setNow(Date.now()) setNow(Date.now())
}, 1000) }, 1000)
return () => clearInterval(interval) 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(() => { useEffect(() => {
if (!pauseDialogOpen) { if (!pauseDialogOpen) {
@ -404,7 +419,13 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
} }
}, [pauseDialogOpen]) }, [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 totalWorkedMs = workSummary ? workSummary.totalWorkedMs + currentSessionMs : 0
const internalWorkedMs = workSummary const internalWorkedMs = workSummary
? workSummary.internalWorkedMs + (workSummary.activeSession?.workType === "INTERNAL" ? currentSessionMs : 0) ? workSummary.internalWorkedMs + (workSummary.activeSession?.workType === "INTERNAL" ? currentSessionMs : 0)
@ -432,6 +453,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
} }
// Otimização local: garantir startedAt correto imediatamente // Otimização local: garantir startedAt correto imediatamente
const startedAtMs = typeof result?.startedAt === "number" ? result.startedAt : Date.now() const startedAtMs = typeof result?.startedAt === "number" ? result.startedAt : Date.now()
localStartAtRef.current = startedAtMs
const sessionId = (result as { sessionId?: unknown })?.sessionId as Id<"ticketWorkSessions"> | undefined const sessionId = (result as { sessionId?: unknown })?.sessionId as Id<"ticketWorkSessions"> | undefined
setWorkSummary((prev) => { setWorkSummary((prev) => {
const base: WorkSummarySnapshot = prev ?? { const base: WorkSummarySnapshot = prev ?? {
@ -477,6 +499,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
setPauseDialogOpen(false) setPauseDialogOpen(false)
// Otimização local: aplicar duração retornada no total e limpar sessão ativa // 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 const delta = typeof (result as { durationMs?: unknown })?.durationMs === "number" ? (result as { durationMs?: number }).durationMs! : 0
localStartAtRef.current = 0
setWorkSummary((prev) => { setWorkSummary((prev) => {
if (!prev) return prev if (!prev) return prev
const workType = prev.activeSession?.workType ?? "INTERNAL" const workType = prev.activeSession?.workType ?? "INTERNAL"