Market View: % suffix on price index badge, poll scrape completion for auto-refresh

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-05 14:51:41 +00:00
parent ba6c000903
commit ea9484e3fa

View file

@ -1,4 +1,4 @@
import React, { useState, useMemo, useCallback } from 'react' import React, { useState, useMemo, useCallback, useEffect } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import api from '../api' import api from '../api'
@ -922,18 +922,42 @@ const RateMatrixTab: React.FC = () => {
const [showDirect, setShowDirect] = useState(false) const [showDirect, setShowDirect] = useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
// The scrape endpoint returns immediately (runs in background), so we watch
// scraper status until a new batch finishes, then refetch the matrix.
const [scrapeWatch, setScrapeWatch] = useState<{ prevBatch: string | null, startedAt: number } | null>(null)
const dateScrapeM = useMutation({ const dateScrapeM = useMutation({
mutationFn: async (d: string) => { mutationFn: async (d: string) => {
const st = (await api.get('/competitors/status')).data as ScraperStatus
setScrapingDate(d) setScrapingDate(d)
setScrapeWatch({ prevBatch: st?.last_scrape?.batch_id ?? null, startedAt: Date.now() })
return (await api.post('/competitors/scrape', { from_date: d, to_date: d })).data return (await api.post('/competitors/scrape', { from_date: d, to_date: d })).data
}, },
onSuccess: () => { onError: () => {
setScrapingDate(null)
setScrapeWatch(null)
},
})
const { data: watchStatus } = useQuery<ScraperStatus>({
queryKey: ['scraper-status'],
queryFn: async () => (await api.get('/competitors/status')).data,
enabled: scrapeWatch !== null,
refetchInterval: scrapeWatch !== null ? 5000 : false,
})
useEffect(() => {
if (!scrapeWatch) return
const ls = watchStatus?.last_scrape
const finished = ls && ls.batch_id !== scrapeWatch.prevBatch && ls.status !== 'running'
const timedOut = Date.now() - scrapeWatch.startedAt > 10 * 60 * 1000
if (finished || timedOut) {
queryClient.invalidateQueries({ queryKey: ['competitor-matrix'] }) queryClient.invalidateQueries({ queryKey: ['competitor-matrix'] })
queryClient.invalidateQueries({ queryKey: ['scraper-status'] }) queryClient.invalidateQueries({ queryKey: ['scraper-status'] })
setScrapingDate(null) setScrapingDate(null)
}, setScrapeWatch(null)
onError: () => setScrapingDate(null), }
}) }, [watchStatus, scrapeWatch, queryClient])
const onCellEnter = useCallback((row: number, col: number) => { const onCellEnter = useCallback((row: number, col: number) => {
setHoveredCell({ row, col }) setHoveredCell({ row, col })
@ -1229,7 +1253,7 @@ const RateMatrixTab: React.FC = () => {
priceIndexBadge = ( priceIndexBadge = (
<span style={{ display: 'block', fontSize: 9, fontWeight: 700, color: fg, background: bg, <span style={{ display: 'block', fontSize: 9, fontWeight: 700, color: fg, background: bg,
borderRadius: 4, padding: '0 3px', lineHeight: '14px', marginTop: 1 }}> borderRadius: 4, padding: '0 3px', lineHeight: '14px', marginTop: 1 }}>
{idx} {idx}%
</span> </span>
) )
} }