105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
|
|
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<ApiReportStatus, ReportStatus> = {
|
|||
|
|
pending: 'queued',
|
|||
|
|
processing: 'running',
|
|||
|
|
done: 'done',
|
|||
|
|
failed: 'failed',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const TYPE_TITLES: Record<ApiReportJob['type'], string> = {
|
|||
|
|
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<ReportType, ApiReportJob['type']> = {
|
|||
|
|
deals: 'deals_export',
|
|||
|
|
managers: 'managers_summary',
|
|||
|
|
sources: 'sources_summary',
|
|||
|
|
billing: 'billing_summary',
|
|||
|
|
};
|
|||
|
|
return map[uiType];
|
|||
|
|
}
|