import { Timeline, TimelineContent, TimelineDate, TimelineHeader, TimelineIndicator, TimelineItem, TimelineSeparator, TimelineTitle, } from '@/components/reui/timeline' import { Badge } from '@/components/reui/badge' import { jobStatusRu, jobTriggerRu } from '@/lib/ui-labels' import type { JobRow } from '@/types/api' /** * ReUI Timeline в раскрытии строки задач: создана → взята в работу → результат. * Активный шаг выводится из фактических отметок времени задачи. * @see https://reui.io/docs/components/base/timeline */ const ruDateTime = (value: string | null | undefined) => value ? new Date(value).toLocaleString('ru-RU') : undefined function resultSubtitle(job: JobRow): string | undefined { if (job.error) return job.error const summary = job.meta?.apply_summary if (summary && typeof summary === 'object') { const message = (summary as Record).message if (typeof message === 'string' && message !== '') return message } return undefined } function applyCounts(job: JobRow) { const value = job.meta?.apply_summary if (!value || typeof value !== 'object') return undefined const summary = value as Record const count = (key: string) => typeof summary[key] === 'number' ? summary[key] as number : undefined return { published: count('published_count'), applied: count('applied_count'), unconfirmed: count('unconfirmed_count'), failed: count('failed_count'), } } function dispatchConfirmations(job: JobRow): string[] { const dispatch = job.meta?.node_dispatch if (!dispatch || typeof dispatch !== 'object') return [] const results = (dispatch as Record).results if (!Array.isArray(results)) return [] return results.flatMap((item) => { if (!item || typeof item !== 'object') return [] const result = item as Record const speaker = typeof result.speaker_id === 'string' ? result.speaker_id.slice(0, 8) : 'Спикер' const label = result.confirmation === 'agent_reported' ? 'агент подтвердил' : result.confirmation === 'local_applied' ? 'применено локально' : result.confirmation === 'unconfirmed' ? 'без подтверждения агента' : 'подтверждение не указано' return [`${speaker}: ${label}`] }) } export function JobTimeline({ job }: { job: JobRow }) { const trigger = jobTriggerRu(job.meta?.trigger) const completed = job.finished_at ? 3 : job.started_at ? 2 : 1 const failed = ['failed', 'error'].includes(job.status.toLowerCase()) const subtitle = resultSubtitle(job) const counts = applyCounts(job) const confirmations = dispatchConfirmations(job) return (
{ruDateTime(job.created_at)} Создана{trigger ? ` (${trigger})` : ''} Идентификатор: {job.job_id} {ruDateTime(job.started_at)} Взята в работу {ruDateTime(job.finished_at)} {job.finished_at ? jobStatusRu(job.status) : 'Ещё выполняется'} {subtitle || counts || confirmations.length > 0 ? (
{subtitle ? {subtitle} : null} {counts ? (
{counts.published != null ? Опубликовано: {counts.published} : null} {counts.applied != null ? Подтверждено: {counts.applied} : null} {counts.unconfirmed != null && counts.unconfirmed > 0 ? Без подтверждения агента: {counts.unconfirmed} : null} {counts.failed != null && counts.failed > 0 ? Ошибок: {counts.failed} : null}
) : null} {confirmations.length > 0 ? (
    {confirmations.map((confirmation, index) =>
  • {confirmation}
  • )}
) : null}
) : null}
) }