fix: reconcile ticket timer with server start
This commit is contained in:
parent
1df7e13c8f
commit
3b5676ed35
3 changed files with 96 additions and 5 deletions
|
|
@ -31,6 +31,7 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { reconcileLocalSessionStart, type SessionStartOrigin } from "./ticket-timer.utils"
|
||||
|
||||
interface TicketHeaderProps {
|
||||
ticket: TicketWithDetails
|
||||
|
|
@ -390,6 +391,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
|||
// 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)
|
||||
const localStartOriginRef = useRef<SessionStartOrigin>("unknown")
|
||||
|
||||
useEffect(() => {
|
||||
if (!workSummary?.activeSession) return
|
||||
|
|
@ -403,12 +405,16 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
|||
useEffect(() => {
|
||||
if (!workSummary?.activeSession) {
|
||||
localStartAtRef.current = 0
|
||||
localStartOriginRef.current = "unknown"
|
||||
return
|
||||
}
|
||||
const remoteStart = Number(workSummary.activeSession.startedAt) || 0
|
||||
if (remoteStart > localStartAtRef.current) {
|
||||
localStartAtRef.current = remoteStart
|
||||
}
|
||||
const { localStart, origin } = reconcileLocalSessionStart({
|
||||
remoteStart: Number(workSummary.activeSession.startedAt) || 0,
|
||||
localStart: localStartAtRef.current,
|
||||
origin: localStartOriginRef.current,
|
||||
})
|
||||
localStartAtRef.current = localStart
|
||||
localStartOriginRef.current = origin
|
||||
}, [workSummary?.activeSession?.id, workSummary?.activeSession?.startedAt])
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -446,13 +452,21 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
|||
toast.loading("Iniciando atendimento...", { id: "work" })
|
||||
try {
|
||||
const result = await startWork({ ticketId: ticket.id as Id<"tickets">, actorId: convexUserId as Id<"users">, workType })
|
||||
if (result?.status === "already_started") {
|
||||
const status = (result as { status?: string } | null)?.status ?? "started"
|
||||
if (status === "already_started") {
|
||||
toast.info("O atendimento já estava em andamento", { id: "work" })
|
||||
} else {
|
||||
toast.success("Atendimento iniciado", { id: "work" })
|
||||
}
|
||||
// Otimização local: garantir startedAt correto imediatamente
|
||||
const startedAtMs = typeof result?.startedAt === "number" ? result.startedAt : Date.now()
|
||||
if (typeof result?.startedAt === "number") {
|
||||
localStartOriginRef.current = "remote"
|
||||
} else if (status === "already_started") {
|
||||
localStartOriginRef.current = "already-running-fallback"
|
||||
} else {
|
||||
localStartOriginRef.current = "fresh-local"
|
||||
}
|
||||
localStartAtRef.current = startedAtMs
|
||||
const sessionId = (result as { sessionId?: unknown })?.sessionId as Id<"ticketWorkSessions"> | undefined
|
||||
setWorkSummary((prev) => {
|
||||
|
|
@ -500,6 +514,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
|||
// 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
|
||||
localStartOriginRef.current = "unknown"
|
||||
setWorkSummary((prev) => {
|
||||
if (!prev) return prev
|
||||
const workType = prev.activeSession?.workType ?? "INTERNAL"
|
||||
|
|
|
|||
40
src/components/tickets/ticket-timer.utils.ts
Normal file
40
src/components/tickets/ticket-timer.utils.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
export type SessionStartOrigin = "unknown" | "remote" | "fresh-local" | "already-running-fallback"
|
||||
|
||||
interface ReconcileArgs {
|
||||
remoteStart: number
|
||||
localStart: number
|
||||
origin: SessionStartOrigin
|
||||
}
|
||||
|
||||
interface ReconcileResult {
|
||||
localStart: number
|
||||
origin: SessionStartOrigin
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcilia o início da sessão local com o timestamp vindo do servidor.
|
||||
* Mantém a proteção contra timestamps remotos defasados (que geravam tempo inflado),
|
||||
* mas permite substituir o fallback local quando sabemos que já existia uma sessão em andamento.
|
||||
*/
|
||||
export function reconcileLocalSessionStart({ remoteStart, localStart, origin }: ReconcileArgs): ReconcileResult {
|
||||
const sanitizedRemote = Number(remoteStart) || 0
|
||||
const sanitizedLocal = Number(localStart) || 0
|
||||
|
||||
if (sanitizedRemote <= 0) {
|
||||
return { localStart: sanitizedLocal, origin }
|
||||
}
|
||||
|
||||
if (sanitizedLocal <= 0) {
|
||||
return { localStart: sanitizedRemote, origin: "remote" }
|
||||
}
|
||||
|
||||
if (sanitizedRemote >= sanitizedLocal) {
|
||||
return { localStart: sanitizedRemote, origin: "remote" }
|
||||
}
|
||||
|
||||
if (origin === "already-running-fallback") {
|
||||
return { localStart: sanitizedRemote, origin: "remote" }
|
||||
}
|
||||
|
||||
return { localStart: sanitizedLocal, origin }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue