AURORA-1683 комментарии: тип, запрос, время по Москве, счётчик строки панели

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-27 00:22:28 +04:00
parent 07648ba3ac
commit ff2232787f
6 changed files with 57 additions and 3 deletions

View File

@ -1,5 +1,5 @@
import { api } from '../../api/client';
import type { DocumentOut, EntryOut, Page, StatementOut, StatementPatch } from '../../types/api';
import type { CommentOut, DocumentOut, EntryOut, Page, StatementOut, StatementPatch } from '../../types/api';
const ENTRIES_PAGE = 2000;
@ -23,4 +23,7 @@ export async function getAllEntries(id: string): Promise<EntryOut[]> {
}
/** Комментарий к документу: текст 1–4000 символов после обрезки пробелов (иначе 422). */
export const postComment = (documentId: string, text: string) => api.post(`/documents/${documentId}/comments`, { text });
export const postComment = (documentId: string, text: string) => api.post<CommentOut>(`/documents/${documentId}/comments`, { text });
/** Комментарии к документу по возрастанию времени, с ФИО автора. */
export const getComments = (documentId: string) => api.get<CommentOut[]>(`/documents/${documentId}/comments`);

View File

@ -75,6 +75,13 @@ export interface DocumentBrief {
comments_count: number;
issue_counts: IssueCountsOut;
}
/** Комментарий к документу (`GET /documents/{id}/comments`); правки и удаления нет. */
export interface CommentOut {
id: string;
text: string;
author: { id: string; full_name: string };
created_at: string;
}
export type FileType = 'XLS' | 'XLSX' | 'XLSB' | 'PDF' | 'PNG' | 'JPG';
export interface FileOut {
id: string;

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { formatMoscowDate, isIsoDate } from './dates';
import { formatMoscowDate, formatMoscowDateTime, isIsoDate } from './dates';
describe('formatMoscowDate', () => {
it('момент времени из API (UTC) — дата по Москве', () => {
@ -12,6 +12,17 @@ describe('formatMoscowDate', () => {
});
});
describe('formatMoscowDateTime', () => {
it('момент из API (UTC) — дата и время по Москве', () => {
expect(formatMoscowDateTime('2026-10-07T23:11:42.981145Z')).toBe('08.10.2026 02:11');
expect(formatMoscowDateTime('2024-03-12T10:05:00Z')).toBe('12.03.2024 13:05');
});
it('пусто и мусор', () => {
expect(formatMoscowDateTime(null)).toBeNull();
expect(formatMoscowDateTime('не дата')).toBeNull();
});
});
describe('isIsoDate', () => {
it('строго ГГГГ-ММ-ДД и существующая дата', () => {
expect(isIsoDate('2024-12-31')).toBe(true);

View File

@ -7,6 +7,23 @@ export function formatMoscowDate(iso: string | null | undefined): string | null
return Number.isNaN(date.getTime()) ? null : MOSCOW.format(date);
}
const MOSCOW_DATE_TIME = new Intl.DateTimeFormat('ru-RU', {
timeZone: 'Europe/Moscow',
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
});
/** Момент времени из API (ISO, UTC) → «dd.mm.yyyy hh:mm» по Москве; пусто/мусор → null. */
export function formatMoscowDateTime(iso: string | null | undefined): string | null {
if (!iso) return null;
const date = new Date(iso);
return Number.isNaN(date.getTime()) ? null : MOSCOW_DATE_TIME.format(date).replace(', ', ' ');
}
/** Строго «ГГГГ-ММ-ДД» и существующая календарная дата (30 февраля — нет). */
export function isIsoDate(text: string): boolean {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);

View File

@ -169,6 +169,18 @@ describe('buildDocumentRows: предупреждения', () => {
const rows = buildDocumentRows([file({ documents: [doc({ issue_counts: { error: 1, info: 3 } })] })]);
expect(rows[0].issueCounts).toEqual({ error: 1, info: 3 });
});
it('comments_count документа переносится в commentsCount', () => {
const rows = buildDocumentRows([file({ documents: [doc({ comments_count: 2 })] })]);
expect(rows[0].commentsCount).toBe(2);
});
it('ядро без comments_count и строка файла без документов — 0', () => {
const old = doc() as Partial<DocumentBrief>;
old.comments_count = undefined;
expect(buildDocumentRows([file({ documents: [old as DocumentBrief] })])[0].commentsCount).toBe(0);
expect(buildDocumentRows([file({ status: 'PROCESSING' })])[0].commentsCount).toBe(0);
});
});
describe('rowBadge', () => {

View File

@ -27,6 +27,8 @@ export interface DocumentRow {
mappingWarnings: string[];
/** счётчики issues документа; у строки файла без документов — нули */
issueCounts: IssueCountsOut;
/** число комментариев документа; у строки файла без документов — 0 */
commentsCount: number;
}
/** Значок у статуса (P6-12): число предупреждений и цвет уровня; ничего нет — null. */
@ -78,6 +80,7 @@ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => s
fileWarnings,
mappingWarnings,
issueCounts: { error: 0, info: 0 },
commentsCount: 0,
},
];
}
@ -105,6 +108,7 @@ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => s
mappingWarnings,
// ядро без v5.19 не присылает issue_counts — считаем, что замечаний нет
issueCounts: d.issue_counts ?? { error: 0, info: 0 },
commentsCount: d.comments_count ?? 0,
};
});
});