import { useEffect, useRef, useState, type ChangeEvent, type FormEvent } from 'react' import { createAccount, getTask, importCSV, runTask, testAccounts, type TaskDetail as TaskDetailData } from '../api' import { connectTaskWS, type TaskEvent } from '../ws' import { StatusBadge } from '../components/StatusBadge' const emptyAccount = { src_login: '', src_pass: '', dst_login: '', dst_pass: '' } export function TaskDetail({ id }: { id: number }) { const [data, setData] = useState(null) const [notFound, setNotFound] = useState(false) const [log, setLog] = useState<{ type: string; text: string }[]>([]) const [form, setForm] = useState(emptyAccount) const [busy, setBusy] = useState<'test' | 'run' | 'add' | 'import' | null>(null) const [error, setError] = useState(null) const fileInputRef = useRef(null) function reload() { getTask(id) .then((d) => { setData(d) setNotFound(false) }) .catch(() => setNotFound(true)) } useEffect(reload, [id]) useEffect( () => connectTaskWS(id, (ev: TaskEvent) => { setLog((l) => [{ type: ev.type, text: JSON.stringify(ev.data) }, ...l].slice(0, 300)) if (['account_started', 'account_test', 'account_done', 'progress', 'run_started', 'run_done', 'error'].includes(ev.type)) { reload() } }), [id], ) async function submitAccount(e: FormEvent) { e.preventDefault() setBusy('add') setError(null) try { await createAccount(id, form) setForm(emptyAccount) reload() } catch (err) { setError(err instanceof Error ? err.message : 'Failed to add account') } finally { setBusy(null) } } async function onFileChosen(e: ChangeEvent) { const file = e.target.files?.[0] if (!file) return setBusy('import') setError(null) try { await importCSV(id, file) reload() } catch (err) { setError(err instanceof Error ? err.message : 'CSV import failed') } finally { setBusy(null) if (fileInputRef.current) fileInputRef.current.value = '' } } async function onTest() { setBusy('test') setError(null) try { await testAccounts(id) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to start connection tests') } finally { setBusy(null) } } async function onRun() { setBusy('run') setError(null) try { await runTask(id) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to start run') } finally { setBusy(null) } } if (notFound) { return (

Task #{id} not found.

← back to tasks
) } if (!data) { return
loading task #{id}…
} const { task, accounts } = data const allTested = accounts.length > 0 && accounts.every((a) => a.test_src_status === 'ok' && a.test_dst_status === 'ok') const totals = accounts.reduce( (acc, a) => ({ copied: acc.copied + a.copied, skipped: acc.skipped + a.skipped, errors: acc.errors + a.errors }), { copied: 0, skipped: 0, errors: 0 }, ) return ( <>
← all tasks

{task.name} /// task #{task.id}

Run control
{totals.copied} copied
{totals.skipped} skipped
{totals.errors} errors
{accounts.length} accounts
{error &&
{error}
}
{!allTested && accounts.length > 0 && run unlocks once every account tests OK on both sides}
Add account
setForm({ ...form, src_login: e.target.value })} required />
setForm({ ...form, src_pass: e.target.value })} required />
setForm({ ...form, dst_login: e.target.value })} required />
setForm({ ...form, dst_pass: e.target.value })} required />
or bulk import
columns: src_login, src_pass, dst_login, dst_pass
Event log
{log.length === 0 ? (
awaiting events over websocket…
) : ( log.map((l, i) => (
{l.type} {l.text}
)) )}
Accounts ({accounts.length})
{accounts.length === 0 ? ( ) : ( accounts.map((a) => ( )) )}
Source Destination Src test Dst test Status Copied Skipped Errors
no accounts yet — add one or import a CSV above
{a.src_login} {a.dst_login} {a.copied} {a.skipped} {a.errors}
) }