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 { Popover, Spin, Tag } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { getDocument } from '../../services/api/documents';
|
import { getDocument } from '../../services/api/documents';
|
||||||
import type { IssueOut, Severity } from '../../types/api';
|
import type { IssueOut, Severity } from '../../types/api';
|
||||||
import { severityLabel } from '../../utils/labels';
|
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 }) {
|
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: 8 }}>
|
<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 }) {
|
export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: { count: number; level: Severity } | null }) {
|
||||||
const [issues, setIssues] = useState<IssuesState | null>(null);
|
const [issues, setIssues] = useState<IssuesState | null>(null);
|
||||||
|
// номер текущего открытия: ответ от прошлого открытия или после закрытия игнорируется
|
||||||
|
const requestNo = useRef(0);
|
||||||
|
|
||||||
if (!badge) return null;
|
if (!badge) return null;
|
||||||
|
|
||||||
// предупреждения документа грузим один раз за открытие: при закрытии сбрасываем, повторное открытие запрашивает заново
|
// предупреждения документа грузим один раз за открытие: при закрытии сбрасываем, повторное открытие запрашивает заново
|
||||||
const onOpenChange = (open: boolean) => {
|
const onOpenChange = (open: boolean) => {
|
||||||
|
requestNo.current += 1;
|
||||||
|
const mine = requestNo.current;
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setIssues(null);
|
setIssues(null);
|
||||||
return;
|
return;
|
||||||
@ -51,12 +65,16 @@ export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: {
|
|||||||
if (!row.documentId) return;
|
if (!row.documentId) return;
|
||||||
setIssues({ status: 'loading' });
|
setIssues({ status: 'loading' });
|
||||||
getDocument(row.documentId)
|
getDocument(row.documentId)
|
||||||
.then((d) => setIssues({ status: 'done', issues: d.issues }))
|
.then((d) => mine === requestNo.current && setIssues({ status: 'done', issues: d.issues }))
|
||||||
.catch(() => setIssues({ status: 'error' }));
|
.catch(() => mine === requestNo.current && setIssues({ status: 'error' }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = (
|
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 && (
|
{row.fileWarnings.length > 0 && (
|
||||||
<Section title='Предупреждения файла'>
|
<Section title='Предупреждения файла'>
|
||||||
{row.fileWarnings.map((w, i) => (
|
{row.fileWarnings.map((w, i) => (
|
||||||
@ -69,10 +87,10 @@ export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: {
|
|||||||
)}
|
)}
|
||||||
{row.mappingWarnings.length > 0 && (
|
{row.mappingWarnings.length > 0 && (
|
||||||
<Section title='Предупреждения обработки'>
|
<Section title='Предупреждения обработки'>
|
||||||
{row.mappingWarnings.map((w) => (
|
{uniqueKeys(row.mappingWarnings).map((w) => (
|
||||||
<div key={w}>
|
<div key={w.key}>
|
||||||
<Tag color={tagColor('info')}>{severityLabel('info')}</Tag>
|
<Tag color={tagColor('info')}>{severityLabel('info')}</Tag>
|
||||||
{w}
|
{w.text}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</Section>
|
</Section>
|
||||||
@ -89,6 +107,7 @@ export function FileWarningsPopover({ row, badge }: { row: DocumentRow; badge: {
|
|||||||
<Popover trigger='click' title='Предупреждения' content={content} onOpenChange={onOpenChange}>
|
<Popover trigger='click' title='Предупреждения' content={content} onOpenChange={onOpenChange}>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
|
aria-label={`Предупреждения: ${badge.count}`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|||||||
@ -170,6 +170,10 @@ describe('rowBadge', () => {
|
|||||||
it('error документа без файла', () => {
|
it('error документа без файла', () => {
|
||||||
expect(rowBadge(row([], { error: 1, info: 0 }))).toMatchObject({ level: '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', () => {
|
it('нули — null', () => {
|
||||||
expect(rowBadge(row([], { error: 0, info: 0 }))).toBeNull();
|
expect(rowBadge(row([], { error: 0, info: 0 }))).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user