feat: calendar
This commit is contained in:
@@ -167,9 +167,9 @@ function ImportPanel({ stats, onImported }: { stats: FFStats; onImported: () =>
|
||||
const [syncMsg, setSyncMsg] = useState<string | null>(null)
|
||||
const calPollRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
const [teHtmlMsg, setTeHtmlMsg] = useState<string | null>(null)
|
||||
const [teHtmlBusy, setTeHtmlBusy] = useState(false)
|
||||
const teHtmlPollRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const [ffPageMsg, setFfPageMsg] = useState<string | null>(null)
|
||||
const [ffPageBusy, setFfPageBusy] = useState(false)
|
||||
const ffPagePollRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const fileInputId = useId()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -193,7 +193,7 @@ function ImportPanel({ stats, onImported }: { stats: FFStats; onImported: () =>
|
||||
return () => {
|
||||
if (pollRef.current) clearInterval(pollRef.current)
|
||||
if (calPollRef.current) clearInterval(calPollRef.current)
|
||||
if (teHtmlPollRef.current) clearInterval(teHtmlPollRef.current)
|
||||
if (ffPagePollRef.current) clearInterval(ffPagePollRef.current)
|
||||
}
|
||||
}, [onImported])
|
||||
|
||||
@@ -210,12 +210,11 @@ function ImportPanel({ stats, onImported }: { stats: FFStats; onImported: () =>
|
||||
const r = s.last_result || {}
|
||||
if (r.error) {
|
||||
setSyncMsg(`Erreur: ${r.error}`)
|
||||
} else if (s.source === 'ff_fallback') {
|
||||
const tw = r.thisweek?.events ?? '?'
|
||||
const nw = r.nextweek?.events ?? '?'
|
||||
setSyncMsg(`✓ FF fallback: ${tw}+${nw} events`)
|
||||
} else {
|
||||
setSyncMsg(`✓ FXStreet: ${r.total_upserted} events`)
|
||||
const tw = r.live?.thisweek?.events ?? r.live?.events ?? '?'
|
||||
const nw = r.live?.nextweek?.events ?? '?'
|
||||
const sc = r.scrape?.total_upserted ?? 0
|
||||
setSyncMsg(`✓ FF: ${tw}+${nw} live, ${sc} upcoming`)
|
||||
}
|
||||
onImported()
|
||||
}
|
||||
@@ -223,35 +222,35 @@ function ImportPanel({ stats, onImported }: { stats: FFStats; onImported: () =>
|
||||
} catch { setSyncMsg('sync error'); setCalStatus(s => ({ ...s, running: false })) }
|
||||
}
|
||||
|
||||
const uploadAndImportTeHtml = async (file: File) => {
|
||||
setTeHtmlBusy(true)
|
||||
setTeHtmlMsg('uploading…')
|
||||
const uploadAndImportFfPage = async (file: File) => {
|
||||
setFfPageBusy(true)
|
||||
setFfPageMsg('uploading…')
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
const up = await fetch(`${API}/api/eco/te-html-upload`, { method: 'POST', body: form }).then(x => x.json())
|
||||
if (up.error) { setTeHtmlMsg(`Upload error: ${up.error}`); setTeHtmlBusy(false); return }
|
||||
setTeHtmlMsg(`uploaded (${up.size_mb} MB) — importing…`)
|
||||
await fetch(`${API}/api/eco/te-html-import`, { method: 'POST' })
|
||||
teHtmlPollRef.current = setInterval(async () => {
|
||||
const s = await fetch(`${API}/api/eco/te-html-import/status`).then(x => x.json())
|
||||
const up = await fetch(`${API}/api/eco/ff-page-upload`, { method: 'POST', body: form }).then(x => x.json())
|
||||
if (up.error) { setFfPageMsg(`Upload error: ${up.error}`); setFfPageBusy(false); return }
|
||||
setFfPageMsg(`uploaded (${up.size_mb} MB) — importing…`)
|
||||
await fetch(`${API}/api/eco/ff-page-import`, { method: 'POST' })
|
||||
ffPagePollRef.current = setInterval(async () => {
|
||||
const s = await fetch(`${API}/api/eco/ff-page-import/status`).then(x => x.json())
|
||||
if (!s.running) {
|
||||
clearInterval(teHtmlPollRef.current!)
|
||||
setTeHtmlBusy(false)
|
||||
clearInterval(ffPagePollRef.current!)
|
||||
setFfPageBusy(false)
|
||||
const r = s.last_result || {}
|
||||
if (r.error) { setTeHtmlMsg(`Error: ${r.error}`); return }
|
||||
setTeHtmlMsg(`✓ TE HTML: ${r.total} events (${r.date_from} → ${r.date_to})`)
|
||||
if (r.error) { setFfPageMsg(`Error: ${r.error}`); return }
|
||||
setFfPageMsg(`✓ FF Page: ${r.inserted} events (${r.date_from} → ${r.date_to})`)
|
||||
onImported()
|
||||
}
|
||||
}, 2000)
|
||||
} catch { setTeHtmlBusy(false); setTeHtmlMsg('error') }
|
||||
} catch { setFfPageBusy(false); setFfPageMsg('error') }
|
||||
}
|
||||
|
||||
useEffect(() => () => { if (teHtmlPollRef.current) clearInterval(teHtmlPollRef.current) }, [])
|
||||
useEffect(() => () => { if (ffPagePollRef.current) clearInterval(ffPagePollRef.current) }, [])
|
||||
|
||||
const hasData = stats.total > 0
|
||||
const res = importStatus.last_result
|
||||
const srcLabel = calStatus.source === 'ff_fallback' ? 'FF fallback' : calStatus.source === 'fxstreet' ? 'FXStreet' : null
|
||||
const srcLabel = calStatus.source === 'ff' ? 'ForexFactory' : calStatus.source ?? null
|
||||
|
||||
return (
|
||||
<div className="bg-slate-800 border border-slate-700 rounded-lg p-4 mb-4 flex flex-wrap items-center gap-4 text-sm">
|
||||
@@ -284,27 +283,27 @@ function ImportPanel({ stats, onImported }: { stats: FFStats; onImported: () =>
|
||||
{calStatus.running ? 'Syncing…' : 'Sync Now'}
|
||||
</button>
|
||||
|
||||
{/* TE HTML import */}
|
||||
{/* FF Page import — save forexfactory.com/calendar as HTML (Ctrl+U) and upload */}
|
||||
<label
|
||||
htmlFor={fileInputId}
|
||||
className={clsx(
|
||||
'px-3 py-1.5 rounded flex items-center gap-1.5 text-white cursor-pointer',
|
||||
teHtmlBusy
|
||||
ffPageBusy
|
||||
? 'bg-amber-800 opacity-50 cursor-not-allowed'
|
||||
: 'bg-amber-700 hover:bg-amber-600'
|
||||
)}
|
||||
title="Import Trading Economics HTML page (calendar_data.txt) — 1000 events with forecasts"
|
||||
>
|
||||
<Upload size={13} />
|
||||
Import TE HTML
|
||||
Import FF Page
|
||||
</label>
|
||||
<input
|
||||
id={fileInputId}
|
||||
type="file"
|
||||
accept=".txt,.html"
|
||||
className="hidden"
|
||||
disabled={teHtmlBusy}
|
||||
onChange={e => { const f = e.target.files?.[0]; if (f) uploadAndImportTeHtml(f); e.target.value = '' }}
|
||||
disabled={ffPageBusy}
|
||||
onChange={e => { const f = e.target.files?.[0]; if (f) uploadAndImportFfPage(f); e.target.value = '' }}
|
||||
/>
|
||||
|
||||
{res && !importStatus.running && (
|
||||
@@ -317,9 +316,9 @@ function ImportPanel({ stats, onImported }: { stats: FFStats; onImported: () =>
|
||||
{syncMsg}
|
||||
</span>
|
||||
)}
|
||||
{teHtmlMsg && (
|
||||
<span className={clsx('text-xs', teHtmlMsg.startsWith('✓') ? 'text-emerald-400' : teHtmlMsg.startsWith('Error') ? 'text-red-400' : 'text-yellow-400')}>
|
||||
{teHtmlMsg}
|
||||
{ffPageMsg && (
|
||||
<span className={clsx('text-xs', ffPageMsg.startsWith('✓') ? 'text-emerald-400' : ffPageMsg.startsWith('Error') ? 'text-red-400' : 'text-yellow-400')}>
|
||||
{ffPageMsg}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user