import type { ApiReportJob, ApiReportStatus } from '../api/reports'; import type { ReportFormat, ReportJob, ReportStatus, ReportType } from './mockReports'; /** * API → UI converter для ReportsView. * * Schema-канон → UI mock format: * pending → queued * processing → running * done → done * failed → failed * * Title строится на frontend'е (бэк не хранит) из type + period. * timeText зависит от status: «в очереди» / «в работе» / «time1 → time2» / «N дней назад». */ const STATUS_MAP: Record = { pending: 'queued', processing: 'running', done: 'done', failed: 'failed', }; const TYPE_TITLES: Record = { deals_export: 'Сделки · детально', managers_summary: 'Менеджеры', sources_summary: 'Источники', billing_summary: 'Биллинг', }; export function mapApiReportJob(api: ApiReportJob, now: Date = new Date()): ReportJob { const baseTitle = TYPE_TITLES[api.type] ?? api.type; const title = `${baseTitle} · ${formatPeriod(api.parameters.date_from, api.parameters.date_to)}`; return { id: api.id, title, format: api.parameters.format as ReportFormat, status: STATUS_MAP[api.status], sizeText: api.file_size !== null ? formatBytes(api.file_size) : null, rowsText: null, timeText: buildTimeText(api, now), progress: api.status === 'processing' ? 50 : null, attempt: api.retry_count + 1, error: api.error_message, }; } function formatPeriod(dateFrom: string, dateTo: string): string { const months = ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']; const parse = (s: string): { month: string; year: number } | null => { const parts = s.split('-'); if (parts.length < 3) return null; const yr = parseInt(parts[0]!, 10); const mo = parseInt(parts[1]!, 10); if (Number.isNaN(yr) || Number.isNaN(mo) || mo < 1 || mo > 12) return null; return { month: months[mo - 1]!, year: yr }; }; const from = parse(dateFrom); const to = parse(dateTo); if (!from || !to) return `${dateFrom} — ${dateTo}`; if (from.year === to.year && from.month === to.month) { return `${from.month} ${from.year}`; } return `${from.month} ${from.year} — ${to.month} ${to.year}`; } function buildTimeText(api: ApiReportJob, now: Date): string { if (api.status === 'pending') return 'в очереди'; if (api.status === 'processing') { const sec = api.created_at ? Math.max(1, Math.floor((now.getTime() - new Date(api.created_at).getTime()) / 1000)) : 0; return `в работе · ${sec}с`; } // done | failed if (api.finished_at !== null) { const minutesAgo = Math.floor((now.getTime() - new Date(api.finished_at).getTime()) / 60_000); if (minutesAgo < 1) return 'только что'; if (minutesAgo < 60) return `${minutesAgo} мин назад`; if (minutesAgo < 60 * 24) return `${Math.floor(minutesAgo / 60)} ч назад`; return `${Math.floor(minutesAgo / (60 * 24))} д назад`; } return ''; } function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } /** * UI ReportType slug ('deals') → API ApiReportType ('deals_export'). */ export function uiTypeToApi(uiType: ReportType): ApiReportJob['type'] { const map: Record = { deals: 'deals_export', managers: 'managers_summary', sources: 'sources_summary', billing: 'billing_summary', }; return map[uiType]; }