import { useState, useCallback, useMemo } from 'react' import { usePoller } from '../hooks/usePoller.ts' import { useI18n } from '../hooks/useI18n.ts' import { batchApi } from '../api/batchApi.ts' import type { JobExecution, StepExecution } from '../api/types.ts' import DataTable, { type Column } from '../components/common/DataTable.tsx' import StatusBadge from '../components/common/StatusBadge.tsx' import { formatDuration, formatNumber, formatDateTime } from '../utils/formatters.ts' import type { TranslationKey } from '../i18n/I18nContext.tsx' const POLL_INTERVAL = 30_000 const JOB_FILTERS: { labelKey: TranslationKey; value: string }[] = [ { labelKey: 'jobs.all', value: '' }, { labelKey: 'jobs.track5min', value: 'vesselTrackAggregationJob' }, { labelKey: 'jobs.hourly', value: 'hourlyAggregationJob' }, { labelKey: 'jobs.daily', value: 'dailyAggregationJob' }, ] const STATUS_FILTERS = ['ALL', 'COMPLETED', 'FAILED', 'RUNNING', 'STOPPED'] export default function JobMonitor() { const { t } = useI18n() const [jobs, setJobs] = useState([]) const [jobFilter, setJobFilter] = useState('') const [statusFilter, setStatusFilter] = useState('ALL') const [selectedJob, setSelectedJob] = useState(null) const [steps, setSteps] = useState([]) const [stepsLoading, setStepsLoading] = useState(false) usePoller(async () => { const data = await batchApi.getJobHistory(jobFilter || undefined, 100) setJobs(data) }, POLL_INTERVAL, [jobFilter]) const filtered = statusFilter === 'ALL' ? jobs : jobs.filter(j => j.status === statusFilter) const handleRowClick = useCallback(async (job: JobExecution) => { setSelectedJob(job) setStepsLoading(true) try { const data = await batchApi.getStepDetails(job.executionId) setSteps(data) } catch { setSteps([]) } finally { setStepsLoading(false) } }, []) const jobColumns: Column[] = useMemo(() => [ { key: 'status', label: t('jobs.status'), render: (row) => , sortable: true, }, { key: 'jobName', label: t('jobs.job'), sortable: true }, { key: 'executionId', label: t('jobs.id'), sortable: true, align: 'center' as const }, { key: 'startTime', label: t('jobs.start'), render: (row) => {formatDateTime(row.startTime)}, sortable: true, }, { key: 'durationSeconds', label: t('jobs.duration'), render: (row) => formatDuration(row.durationSeconds), sortable: true, align: 'right' as const, }, { key: 'totalRead', label: t('jobs.read'), render: (row) => formatNumber(row.totalRead), sortable: true, align: 'right' as const, }, { key: 'totalWrite', label: t('jobs.write'), render: (row) => formatNumber(row.totalWrite), sortable: true, align: 'right' as const, }, { key: 'totalSkip', label: t('jobs.skip'), render: (row) => row.totalSkip > 0 ? {formatNumber(row.totalSkip)} : '0', sortable: true, align: 'right' as const, }, ], [t]) const stepColumns: Column[] = useMemo(() => [ { key: 'status', label: t('jobs.status'), render: (row) => , }, { key: 'stepName', label: t('jobs.step') }, { key: 'startTime', label: t('jobs.start'), render: (row) => {formatDateTime(row.startTime)}, }, { key: 'durationSeconds', label: t('jobs.duration'), render: (row) => formatDuration(row.durationSeconds), align: 'right' as const, }, { key: 'readCount', label: t('jobs.read'), render: (row) => formatNumber(row.readCount), align: 'right' as const, }, { key: 'writeCount', label: t('jobs.write'), render: (row) => formatNumber(row.writeCount), align: 'right' as const, }, { key: 'commitCount', label: t('jobs.commits'), render: (row) => formatNumber(row.commitCount), align: 'right' as const, }, { key: 'errors', label: t('jobs.errors'), render: (row) => row.errors?.length > 0 ? {row.errors.length} : -, align: 'center' as const, }, ], [t]) return (

{t('jobs.title')}

{/* Filters */}
{JOB_FILTERS.map(j => ( ))}
{STATUS_FILTERS.map(s => ( ))}
{filtered.length}{t('common.items')}
{/* Job Table */} row.executionId} onRowClick={handleRowClick} /> {/* Step Detail */} {selectedJob && (
{t('jobs.stepDetails')}
{selectedJob.jobName} #{selectedJob.executionId}
{stepsLoading ? (
{t('common.loading')}
) : ( row.stepName} pageSize={50} /> )} {steps.some(s => s.errors?.length > 0) && (
{t('jobs.errors')}
{steps .filter(s => s.errors?.length > 0) .map(s => (
{s.stepName}
{s.errors.map((err, i) => (
                        {err}
                      
))}
))}
)}
)}
) }