AURORA-1683 комментарии: тип, запрос, время по Москве, счётчик строки панели
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
07648ba3ac
commit
ff2232787f
@ -1,5 +1,5 @@
|
|||||||
import { api } from '../../api/client';
|
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;
|
const ENTRIES_PAGE = 2000;
|
||||||
|
|
||||||
@ -23,4 +23,7 @@ export async function getAllEntries(id: string): Promise<EntryOut[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Комментарий к документу: текст 1–4000 символов после обрезки пробелов (иначе 422). */
|
/** Комментарий к документу: текст 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`);
|
||||||
|
|||||||
@ -75,6 +75,13 @@ export interface DocumentBrief {
|
|||||||
comments_count: number;
|
comments_count: number;
|
||||||
issue_counts: IssueCountsOut;
|
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 type FileType = 'XLS' | 'XLSX' | 'XLSB' | 'PDF' | 'PNG' | 'JPG';
|
||||||
export interface FileOut {
|
export interface FileOut {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { formatMoscowDate, isIsoDate } from './dates';
|
import { formatMoscowDate, formatMoscowDateTime, isIsoDate } from './dates';
|
||||||
|
|
||||||
describe('formatMoscowDate', () => {
|
describe('formatMoscowDate', () => {
|
||||||
it('момент времени из API (UTC) — дата по Москве', () => {
|
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', () => {
|
describe('isIsoDate', () => {
|
||||||
it('строго ГГГГ-ММ-ДД и существующая дата', () => {
|
it('строго ГГГГ-ММ-ДД и существующая дата', () => {
|
||||||
expect(isIsoDate('2024-12-31')).toBe(true);
|
expect(isIsoDate('2024-12-31')).toBe(true);
|
||||||
|
|||||||
@ -7,6 +7,23 @@ export function formatMoscowDate(iso: string | null | undefined): string | null
|
|||||||
return Number.isNaN(date.getTime()) ? null : MOSCOW.format(date);
|
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 февраля — нет). */
|
/** Строго «ГГГГ-ММ-ДД» и существующая календарная дата (30 февраля — нет). */
|
||||||
export function isIsoDate(text: string): boolean {
|
export function isIsoDate(text: string): boolean {
|
||||||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
||||||
|
|||||||
@ -169,6 +169,18 @@ describe('buildDocumentRows: предупреждения', () => {
|
|||||||
const rows = buildDocumentRows([file({ documents: [doc({ issue_counts: { error: 1, info: 3 } })] })]);
|
const rows = buildDocumentRows([file({ documents: [doc({ issue_counts: { error: 1, info: 3 } })] })]);
|
||||||
expect(rows[0].issueCounts).toEqual({ 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', () => {
|
describe('rowBadge', () => {
|
||||||
|
|||||||
@ -27,6 +27,8 @@ export interface DocumentRow {
|
|||||||
mappingWarnings: string[];
|
mappingWarnings: string[];
|
||||||
/** счётчики issues документа; у строки файла без документов — нули */
|
/** счётчики issues документа; у строки файла без документов — нули */
|
||||||
issueCounts: IssueCountsOut;
|
issueCounts: IssueCountsOut;
|
||||||
|
/** число комментариев документа; у строки файла без документов — 0 */
|
||||||
|
commentsCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Значок у статуса (P6-12): число предупреждений и цвет уровня; ничего нет — null. */
|
/** Значок у статуса (P6-12): число предупреждений и цвет уровня; ничего нет — null. */
|
||||||
@ -78,6 +80,7 @@ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => s
|
|||||||
fileWarnings,
|
fileWarnings,
|
||||||
mappingWarnings,
|
mappingWarnings,
|
||||||
issueCounts: { error: 0, info: 0 },
|
issueCounts: { error: 0, info: 0 },
|
||||||
|
commentsCount: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -105,6 +108,7 @@ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => s
|
|||||||
mappingWarnings,
|
mappingWarnings,
|
||||||
// ядро без v5.19 не присылает issue_counts — считаем, что замечаний нет
|
// ядро без v5.19 не присылает issue_counts — считаем, что замечаний нет
|
||||||
issueCounts: d.issue_counts ?? { error: 0, info: 0 },
|
issueCounts: d.issue_counts ?? { error: 0, info: 0 },
|
||||||
|
commentsCount: d.comments_count ?? 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user