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:
parent
f550fa5952
commit
1df7e13c8f
1 changed files with 26 additions and 3 deletions
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue