feat(tickets): preserve requester/assignee/company snapshots + timeline fallbacks; chore: add requester index\n\n- Add requesterSnapshot, assigneeSnapshot, companySnapshot to tickets\n- Use snapshots as fallback in list/get/play\n- Update snapshots on assignee changes/startWork\n- Preserve snapshots before deleting users/companies\n- Add index tickets.by_tenant_requester\n- Add migrations.backfillTicketSnapshots\n\nchore(convex): upgrade to ^1.28.0 and run codegen\nchore(next): upgrade Next.js to 15.5.6 and update React/eslint-config-next\nfix: remove any and lint warnings; tighten types across API routes and components\ndocs: add docs/ticket-snapshots.md

This commit is contained in:
Esdras Renan 2025-10-20 10:13:37 -03:00
parent 0d82162a0e
commit 216feca971
16 changed files with 884 additions and 552 deletions

View file

@ -419,7 +419,9 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
})
}, [workSummaryRemote, calibrateServerOffset])
const isPlaying = Boolean(workSummary?.activeSession)
const activeSessionId = workSummary?.activeSession?.id ?? null
const activeSessionStartedAt = workSummary?.activeSession?.startedAt ?? null
const isPlaying = Boolean(activeSessionId)
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).
@ -427,28 +429,28 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
const localStartOriginRef = useRef<SessionStartOrigin>("unknown")
useEffect(() => {
if (!workSummary?.activeSession) return
if (!activeSessionId) return
const interval = setInterval(() => {
setNow(Date.now())
}, 1000)
return () => clearInterval(interval)
}, [workSummary?.activeSession?.id])
}, [activeSessionId])
// Sempre que a sessão ativa (id) mudar, sincroniza o ponteiro local
useEffect(() => {
if (!workSummary?.activeSession) {
if (!activeSessionId) {
localStartAtRef.current = 0
localStartOriginRef.current = "unknown"
return
}
const { localStart, origin } = reconcileLocalSessionStart({
remoteStart: Number(workSummary.activeSession.startedAt) || 0,
remoteStart: Number(activeSessionStartedAt) || 0,
localStart: localStartAtRef.current,
origin: localStartOriginRef.current,
})
localStartAtRef.current = localStart
localStartOriginRef.current = origin
}, [workSummary?.activeSession?.id, workSummary?.activeSession?.startedAt])
}, [activeSessionId, activeSessionStartedAt])
useEffect(() => {
if (!pauseDialogOpen) {