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:
parent
ba6c000903
commit
ea9484e3fa
1 changed files with 30 additions and 6 deletions
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue