fix(files): клики внутри popover не открывают строку, ключи и aria-label (#43)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
fdf9358b40
commit
fc5cc16b3d
@ -1,5 +1,5 @@
|
||||
import { Popover, Spin, Tag } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { getDocument } from '../../services/api/documents';
|
||||
import type { IssueOut, Severity } from '../../types/api';
|
||||
import { severityLabel } from '../../utils/labels';
|
||||
@ -27,6 +27,16 @@ function DocumentIssues({ state }: { state: IssuesState | null }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Ключи для строк, которые могут повторяться: к тексту добавляется порядковый номер повтора (индекс массива в key линтер запрещает). */
|
||||
function uniqueKeys(list: string[]): { key: string; text: string }[] {
|
||||
const seen = new Map<string, number>();
|
||||
return list.map((text) => {
|
||||
const n = (seen.get(text) ?? 0) + 1;
|
||||
seen.set(text, n);
|
||||
return { key: `${text}-${n}`, text };
|
||||
});
|
||||
}
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
@ -39,11 +49,15 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
||||
/** Значок с числом предупреждений под плашкой статуса; по клику — подробности (файл, обработка, документ). */
|
||||
export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: { count: number; level: Severity } | null }) {
|
||||
const [issues, setIssues] = useState<IssuesState | null>(null);
|
||||
// номер текущего открытия: ответ от прошлого открытия или после закрытия игнорируется
|
||||
const requestNo = useRef(0);
|
||||
|
||||
if (!badge) return null;
|
||||
|
||||
// предупреждения документа грузим один раз за открытие: при закрытии сбрасываем, повторное открытие запрашивает заново
|
||||
const onOpenChange = (open: boolean) => {
|
||||
requestNo.current += 1;
|
||||
const mine = requestNo.current;
|
||||
if (!open) {
|
||||
setIssues(null);
|
||||
return;
|
||||
@ -51,12 +65,16 @@ export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: {
|
||||
if (!row.documentId) return;
|
||||
setIssues({ status: 'loading' });
|
||||
getDocument(row.documentId)
|
||||
.then((d) => setIssues({ status: 'done', issues: d.issues }))
|
||||
.catch(() => setIssues({ status: 'error' }));
|
||||
.then((d) => mine === requestNo.current && setIssues({ status: 'done', issues: d.issues }))
|
||||
.catch(() => mine === requestNo.current && setIssues({ status: 'error' }));
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div style={{ maxWidth: 420, maxHeight: 320, overflowY: 'auto' }}>
|
||||
<div
|
||||
// клики внутри поповера всплывают по дереву React до onClick строки таблицы и открывали бы документ
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
style={{ maxWidth: 420, maxHeight: 320, overflowY: 'auto' }}>
|
||||
{row.fileWarnings.length > 0 && (
|
||||
<Section title='Предупреждения файла'>
|
||||
{row.fileWarnings.map((w, i) => (
|
||||
@ -69,10 +87,10 @@ export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: {
|
||||
)}
|
||||
{row.mappingWarnings.length > 0 && (
|
||||
<Section title='Предупреждения обработки'>
|
||||
{row.mappingWarnings.map((w) => (
|
||||
<div key={w}>
|
||||
{uniqueKeys(row.mappingWarnings).map((w) => (
|
||||
<div key={w.key}>
|
||||
<Tag color={tagColor('info')}>{severityLabel('info')}</Tag>
|
||||
{w}
|
||||
{w.text}
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
@ -89,6 +107,7 @@ export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: {
|
||||
<Popover trigger='click' title='Предупреждения' content={content} onOpenChange={onOpenChange}>
|
||||
<button
|
||||
type='button'
|
||||
aria-label={`Предупреждения: ${badge.count}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
|
||||
@ -170,6 +170,10 @@ describe('rowBadge', () => {
|
||||
it('error документа без файла', () => {
|
||||
expect(rowBadge(row([], { error: 1, info: 0 }))).toMatchObject({ level: 'error' });
|
||||
});
|
||||
it('файл без документов: считаются предупреждения файла', () => {
|
||||
const r = buildDocumentRows([file({ status: 'ERROR', job: job({ warnings: [jsonFailed] }) })])[0];
|
||||
expect(rowBadge(r)).toEqual({ count: 1, level: 'error' });
|
||||
});
|
||||
it('нули — null', () => {
|
||||
expect(rowBadge(row([], { error: 0, info: 0 }))).toBeNull();
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user