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:
parent
0d82162a0e
commit
216feca971
16 changed files with 884 additions and 552 deletions
|
|
@ -648,8 +648,6 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d
|
|||
const somePeopleSelected = selectedPeopleUsers.length > 0 && !allPeopleSelected
|
||||
|
||||
const selectedMachineUsers = useMemo(() => filteredMachineUsers.filter((u) => machineSelection.has(u.id)), [filteredMachineUsers, machineSelection])
|
||||
const allMachinesSelected = selectedMachineUsers.length > 0 && selectedMachineUsers.length === filteredMachineUsers.length
|
||||
const someMachinesSelected = selectedMachineUsers.length > 0 && !allMachinesSelected
|
||||
|
||||
const [inviteSelection, setInviteSelection] = useState<Set<string>>(new Set())
|
||||
const selectedInvites = useMemo(() => invites.filter((i) => inviteSelection.has(i.id)), [invites, inviteSelection])
|
||||
|
|
|
|||
|
|
@ -982,12 +982,9 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
api.companies.list,
|
||||
companyQueryArgs ?? ("skip" as const)
|
||||
) as Array<{ id: string; name: string; slug?: string }> | undefined
|
||||
// Convex codegen precisa ser atualizado para tipar `machines.listAlerts`.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const machinesApi: any = api
|
||||
const alertsHistory = useQuery(
|
||||
machine && machinesApi?.machines?.listAlerts ? machinesApi.machines.listAlerts : "skip",
|
||||
machine && machinesApi?.machines?.listAlerts ? { machineId: machine.id as Id<"machines">, limit: 50 } : ("skip" as const)
|
||||
machine ? api.machines.listAlerts : "skip",
|
||||
machine ? { machineId: machine.id as Id<"machines">, limit: 50 } : ("skip" as const)
|
||||
) as MachineAlertEntry[] | undefined
|
||||
const machineAlertsHistory = alertsHistory ?? []
|
||||
const metadata = machine?.inventory ?? null
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import {
|
|||
Timer,
|
||||
MonitorCog,
|
||||
UserPlus,
|
||||
BellRing,
|
||||
ChevronDown,
|
||||
ShieldCheck,
|
||||
Users,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue