feat(#195): переход с вкладки ОСВ, режим пар без лишних запросов, пара в адресе при «Свод»
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b9a55873e5
commit
8054ff205d
@ -11,6 +11,7 @@ import {
|
|||||||
consStatusDescription,
|
consStatusDescription,
|
||||||
counterPairsKey,
|
counterPairsKey,
|
||||||
counterPairsWarning,
|
counterPairsWarning,
|
||||||
|
decideOsvLeave,
|
||||||
endSentence,
|
endSentence,
|
||||||
isLockedBy,
|
isLockedBy,
|
||||||
isValidReason,
|
isValidReason,
|
||||||
@ -222,3 +223,25 @@ describe('counterPairsWarning', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('decideOsvLeave (T4.2)', () => {
|
||||||
|
const pairs = [{ holder_id: 'a', missing_id: 'b' } as CounterPairOut];
|
||||||
|
const noStorage = { getItem: () => null };
|
||||||
|
const base = { versionId: 'v', status: 'CONSOLIDATION_RF' as ConsStatus, storage: noStorage };
|
||||||
|
it('сводки нет и версия открыта — запросить', () => {
|
||||||
|
expect(decideOsvLeave({ ...base, summary: null })).toBe('perform');
|
||||||
|
});
|
||||||
|
it('версия закрыта — переход без запроса', () => {
|
||||||
|
expect(decideOsvLeave({ ...base, status: 'VERIFIED', summary: null })).toBe('go');
|
||||||
|
});
|
||||||
|
it('сводка есть, пары есть и не показывали — предупредить', () => {
|
||||||
|
expect(decideOsvLeave({ ...base, summary: { counter_pairs: pairs } })).toBe('warn');
|
||||||
|
});
|
||||||
|
it('сводка есть, пар нет — перейти', () => {
|
||||||
|
expect(decideOsvLeave({ ...base, summary: { counter_pairs: [] } })).toBe('go');
|
||||||
|
});
|
||||||
|
it('список уже показывали — перейти', () => {
|
||||||
|
const shown = { getItem: () => 'a>b' };
|
||||||
|
expect(decideOsvLeave({ ...base, storage: shown, summary: { counter_pairs: pairs } })).toBe('go');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Чистые правила экранов консолидаций (спека `consolidations-ui`): подписи и пояснения статусов версии, ручные
|
// Чистые правила экранов консолидаций (спека `consolidations-ui`): подписи и пояснения статусов версии, ручные
|
||||||
// переходы, блокировка, «Версия», фильтр «Только в работе». Без React, store и API.
|
// переходы, блокировка, «Версия», фильтр «Только в работе». Без React, store и API.
|
||||||
import type { CounterPairOut } from '../types/api';
|
import type { CounterPairOut, OsvSummaryOut } from '../types/api';
|
||||||
import type { ConsRussianStatus, ConsStatus, LockOut } from '../types/consolidations/common_types';
|
import type { ConsRussianStatus, ConsStatus, LockOut } from '../types/consolidations/common_types';
|
||||||
import { pluralRu } from './labels';
|
import { pluralRu } from './labels';
|
||||||
|
|
||||||
@ -154,3 +154,19 @@ export const counterPairsWarning = (pairs: readonly CounterPairOut[], unmappedTo
|
|||||||
details: unmappedTotal > 0 ? `${base} Ещё ${unmappedTotal} строк без ИНН учтены как внешние.` : base,
|
details: unmappedTotal > 0 ? `${base} Ещё ${unmappedTotal} строк без ИНН учтены как внешние.` : base,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Решение при уходе с вкладки «ОСВ»: `perform` — сводки ещё нет, запросить; `warn` — показать модалку; `go` — перейти. */
|
||||||
|
export type OsvLeaveDecision = 'perform' | 'warn' | 'go';
|
||||||
|
|
||||||
|
export const decideOsvLeave = (args: {
|
||||||
|
summary: Pick<OsvSummaryOut, 'counter_pairs'> | null;
|
||||||
|
versionId: string;
|
||||||
|
status: ConsStatus | undefined;
|
||||||
|
storage: Pick<Storage, 'getItem'> | null;
|
||||||
|
}): OsvLeaveDecision => {
|
||||||
|
const { summary, versionId, status, storage } = args;
|
||||||
|
// закрытой версии модалка не нужна — и запрашивать нечего
|
||||||
|
if (status && FINISHED_STATUSES.includes(status)) return 'go';
|
||||||
|
if (summary === null) return 'perform';
|
||||||
|
return shouldWarnCounterPairs(versionId, status, summary.counter_pairs, storage) ? 'warn' : 'go';
|
||||||
|
};
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { WarningOutlined } from '@ant-design/icons';
|
|||||||
import { Button, Modal, Segmented, Tooltip } from 'antd';
|
import { Button, Modal, Segmented, Tooltip } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { getOsvSummary } from '../../services/api/osv';
|
||||||
import type { CounterPairOut, OsvSummaryOut } from '../../types/api';
|
import type { CounterPairOut, OsvSummaryOut } from '../../types/api';
|
||||||
import type { ConsStatus } from '../../types/consolidations/common_types';
|
import type { ConsStatus } from '../../types/consolidations/common_types';
|
||||||
import { counterPairsWarning, rememberCounterPairsShown, reportTabNavigation, shouldWarnCounterPairs } from '../../utils/consolidations';
|
import { counterPairsWarning, decideOsvLeave, rememberCounterPairsShown, reportTabNavigation } from '../../utils/consolidations';
|
||||||
import { pairsViewSearch } from './counterPairs';
|
import { pairsViewSearch } from './counterPairs';
|
||||||
|
|
||||||
const TAB_CONFIG = [
|
const TAB_CONFIG = [
|
||||||
@ -72,12 +73,36 @@ export function OsvTabs({ summary, versionId, status }: Props) {
|
|||||||
navigate(to, options);
|
navigate(to, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (value: string | number) => {
|
// сводка, догруженная при уходе, пока страница её не передала (или запрос страницы упал)
|
||||||
|
const [fetched, setFetched] = useState<OsvSummaryOut | null>(null);
|
||||||
|
const [checking, setChecking] = useState(false);
|
||||||
|
const shownPairs = (summary ?? fetched)?.counter_pairs ?? [];
|
||||||
|
|
||||||
|
const handleTabChange = async (value: string | number) => {
|
||||||
const newKey = String(value);
|
const newKey = String(value);
|
||||||
if (newKey === activeKey) return;
|
if (newKey === activeKey || checking) return;
|
||||||
const pairs = summary?.counter_pairs ?? [];
|
if (activeKey !== 'osv') {
|
||||||
if (activeKey === 'osv' && shouldWarnCounterPairs(versionId, status, pairs, safeSessionStorage())) {
|
go(newKey);
|
||||||
rememberCounterPairsShown(versionId, pairs, safeSessionStorage());
|
return;
|
||||||
|
}
|
||||||
|
const storage = safeSessionStorage();
|
||||||
|
let known = summary ?? fetched;
|
||||||
|
let decision = decideOsvLeave({ summary: known, versionId, status, storage });
|
||||||
|
if (decision === 'perform') {
|
||||||
|
setChecking(true);
|
||||||
|
try {
|
||||||
|
known = await getOsvSummary(versionId);
|
||||||
|
setFetched(known);
|
||||||
|
decision = decideOsvLeave({ summary: known, versionId, status, storage });
|
||||||
|
} catch {
|
||||||
|
// не удалось проверить — не держим пользователя на вкладке
|
||||||
|
decision = 'go';
|
||||||
|
} finally {
|
||||||
|
setChecking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (decision === 'warn' && known) {
|
||||||
|
rememberCounterPairsShown(versionId, known.counter_pairs, storage);
|
||||||
setPending(newKey);
|
setPending(newKey);
|
||||||
} else {
|
} else {
|
||||||
go(newKey);
|
go(newKey);
|
||||||
@ -110,13 +135,14 @@ export function OsvTabs({ summary, versionId, status }: Props) {
|
|||||||
<Segmented
|
<Segmented
|
||||||
value={activeKey}
|
value={activeKey}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
|
disabled={checking}
|
||||||
style={{ alignSelf: 'flex-start', maxWidth: '100%', overflowX: 'auto' }}
|
style={{ alignSelf: 'flex-start', maxWidth: '100%', overflowX: 'auto' }}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
<CounterPairsModal
|
<CounterPairsModal
|
||||||
open={pending !== null}
|
open={pending !== null}
|
||||||
pairs={summary?.counter_pairs ?? []}
|
pairs={shownPairs}
|
||||||
unmappedTotal={summary?.unmapped_total ?? 0}
|
unmappedTotal={(summary ?? fetched)?.unmapped_total ?? 0}
|
||||||
onShow={() => {
|
onShow={() => {
|
||||||
setPending(null);
|
setPending(null);
|
||||||
navigate(
|
navigate(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { CounterPairOut } from '../../types/api';
|
import type { CounterPairOut } from '../../types/api';
|
||||||
import { pairLabel, pairsViewSearch, readPairsView, selectPair } from './counterPairs';
|
import { pairLabel, pairsViewSearch, readPairsView, selectPair, switchPairsView } from './counterPairs';
|
||||||
|
|
||||||
const pair = (holder: string, missing: string): CounterPairOut => ({
|
const pair = (holder: string, missing: string): CounterPairOut => ({
|
||||||
holder_id: holder,
|
holder_id: holder,
|
||||||
@ -15,7 +15,16 @@ const pair = (holder: string, missing: string): CounterPairOut => ({
|
|||||||
describe('readPairsView / pairsViewSearch', () => {
|
describe('readPairsView / pairsViewSearch', () => {
|
||||||
it('table по умолчанию', () => {
|
it('table по умолчанию', () => {
|
||||||
expect(readPairsView(new URLSearchParams(''))).toEqual({ view: 'table', holder: null, missing: null });
|
expect(readPairsView(new URLSearchParams(''))).toEqual({ view: 'table', holder: null, missing: null });
|
||||||
expect(pairsViewSearch({ view: 'table', holder: 'a', missing: 'b' })).toBe('');
|
expect(pairsViewSearch({ view: 'table', holder: null, missing: null })).toBe('');
|
||||||
|
});
|
||||||
|
it('пара сохраняется в режиме «Свод» (T5.5)', () => {
|
||||||
|
const v = { view: 'table' as const, holder: 'a', missing: 'b' };
|
||||||
|
const s = pairsViewSearch(v);
|
||||||
|
expect(s).toBe('?holder=a&missing=b');
|
||||||
|
expect(readPairsView(new URLSearchParams(s))).toEqual(v);
|
||||||
|
// переключение «Свод» → «Встречные записи» возвращает ту же пару
|
||||||
|
expect(switchPairsView({ view: 'pairs', holder: 'a', missing: 'b' }, 'table')).toEqual(v);
|
||||||
|
expect(switchPairsView(v, 'pairs')).toEqual({ view: 'pairs', holder: 'a', missing: 'b' });
|
||||||
});
|
});
|
||||||
it('туда-обратно', () => {
|
it('туда-обратно', () => {
|
||||||
const v = { view: 'pairs' as const, holder: 'a', missing: 'b' };
|
const v = { view: 'pairs' as const, holder: 'a', missing: 'b' };
|
||||||
|
|||||||
@ -4,18 +4,23 @@ import type { CounterPairOut } from '../../types/api';
|
|||||||
export type PairsView = { view: 'table' | 'pairs'; holder: string | null; missing: string | null };
|
export type PairsView = { view: 'table' | 'pairs'; holder: string | null; missing: string | null };
|
||||||
|
|
||||||
/** `?view=pairs[&holder=..&missing=..]`; всё остальное — обычная таблица свода. */
|
/** `?view=pairs[&holder=..&missing=..]`; всё остальное — обычная таблица свода. */
|
||||||
export const readPairsView = (search: URLSearchParams): PairsView =>
|
export const readPairsView = (search: URLSearchParams): PairsView => ({
|
||||||
search.get('view') === 'pairs'
|
view: search.get('view') === 'pairs' ? 'pairs' : 'table',
|
||||||
? { view: 'pairs', holder: search.get('holder'), missing: search.get('missing') }
|
holder: search.get('holder'),
|
||||||
: { view: 'table', holder: null, missing: null };
|
missing: search.get('missing'),
|
||||||
|
});
|
||||||
|
|
||||||
/** Строка запроса для режима; у таблицы пусто. */
|
/** Переключение «Свод» / «Встречные записи»: выбранная пара не теряется. */
|
||||||
|
export const switchPairsView = (v: PairsView, view: PairsView['view']): PairsView => ({ ...v, view });
|
||||||
|
|
||||||
|
/** Строка запроса для режима; пара (`holder`/`missing`) хранится и в режиме «Свод» — для возврата. */
|
||||||
export const pairsViewSearch = (v: PairsView): string => {
|
export const pairsViewSearch = (v: PairsView): string => {
|
||||||
if (v.view !== 'pairs') return '';
|
const params = new URLSearchParams();
|
||||||
const params = new URLSearchParams({ view: 'pairs' });
|
if (v.view === 'pairs') params.set('view', 'pairs');
|
||||||
if (v.holder) params.set('holder', v.holder);
|
if (v.holder) params.set('holder', v.holder);
|
||||||
if (v.missing) params.set('missing', v.missing);
|
if (v.missing) params.set('missing', v.missing);
|
||||||
return `?${params.toString()}`;
|
const text = params.toString();
|
||||||
|
return text ? `?${text}` : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Заданная пара; нет такой (закрыта сопоставлением) — первая оставшаяся; список пуст — `null`. */
|
/** Заданная пара; нет такой (закрыта сопоставлением) — первая оставшаяся; список пуст — `null`. */
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { message } from 'antd';
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { ApiError } from '../../api/client';
|
import { ApiError } from '../../api/client';
|
||||||
import { getConsolidatedOsv, getOsvSummary } from '../../services/api/osv';
|
import { getConsolidatedOsv, getOsvSummary } from '../../services/api/osv';
|
||||||
import type { ConsolidatedRowOut, OsvFilters, OsvSummaryOut } from '../../types/api';
|
import type { ConsolidatedRowOut, OsvSummaryOut } from '../../types/api';
|
||||||
import { type OsvFilterDraft, applyFilterPatch } from './osvFilterState';
|
import { type OsvFilterDraft, type OsvFilterState, applyFilterPatch } from './osvFilterState';
|
||||||
|
|
||||||
export const OSV_PAGE_SIZE = 500;
|
export const OSV_PAGE_SIZE = 500;
|
||||||
|
|
||||||
@ -17,9 +17,14 @@ export function osvErrorText(e: unknown): string {
|
|||||||
return 'Не удалось получить сводную ОСВ';
|
return 'Не удалось получить сводную ОСВ';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useConsolidatedOsv(versionId: string) {
|
/**
|
||||||
const [filters, setFilters] = useState<OsvFilters>({});
|
* @param pairsMode режим «Встречные записи»: строки свода не нужны и не запрашиваются (сводка — запрашивается).
|
||||||
const [page, setPage] = useState(1);
|
* Фильтры и страница — одно состояние: смена фильтра с возвратом на 1-ю страницу даёт один запрос, а не два.
|
||||||
|
*/
|
||||||
|
export function useConsolidatedOsv(versionId: string, pairsMode = false) {
|
||||||
|
const [query, setQuery] = useState<OsvFilterState>({ filters: {}, page: 1 });
|
||||||
|
const { filters, page } = query;
|
||||||
|
const setPage = useCallback((p: number) => setQuery((q) => ({ ...q, page: p })), []);
|
||||||
const [rows, setRows] = useState<ConsolidatedRowOut[]>([]);
|
const [rows, setRows] = useState<ConsolidatedRowOut[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [summary, setSummary] = useState<OsvSummaryOut | null>(null);
|
const [summary, setSummary] = useState<OsvSummaryOut | null>(null);
|
||||||
@ -71,18 +76,17 @@ export function useConsolidatedOsv(versionId: string) {
|
|||||||
}, [versionId, filters, page]);
|
}, [versionId, filters, page]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (pairsMode) return;
|
||||||
void load();
|
void load();
|
||||||
}, [load]);
|
}, [load, pairsMode]);
|
||||||
|
|
||||||
const patchFilters = (patch: OsvFilterDraft) => {
|
const patchFilters = (patch: OsvFilterDraft) => {
|
||||||
const next = applyFilterPatch({ filters, page }, patch);
|
setQuery((q) => applyFilterPatch(q, patch));
|
||||||
setFilters(next.filters);
|
|
||||||
setPage(next.page);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** После ручного сопоставления: «Осталось N» и строки меняются сразу у нескольких строк (автоприменение) — перечитать обе. */
|
/** После ручного сопоставления: «Осталось N» и строки меняются сразу у нескольких строк (автоприменение) — перечитать обе. */
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
void load();
|
if (!pairsMode) void load();
|
||||||
void loadSummary();
|
void loadSummary();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { DownloadOutlined, FilterOutlined } from '@ant-design/icons';
|
import { DownloadOutlined, FilterOutlined } from '@ant-design/icons';
|
||||||
import { Alert, Button, Segmented, Select, message } from 'antd';
|
import { Alert, Button, Segmented, Select, Spin, message } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { ApiError } from '../../api/client';
|
import { ApiError } from '../../api/client';
|
||||||
@ -11,7 +11,7 @@ import { CounterPairsView } from '../ConsolidatedOsv/CounterPairsView';
|
|||||||
import { CounterpartyMapModal } from '../ConsolidatedOsv/CounterpartyMapModal';
|
import { CounterpartyMapModal } from '../ConsolidatedOsv/CounterpartyMapModal';
|
||||||
import { OsvGaps } from '../ConsolidatedOsv/OsvGaps';
|
import { OsvGaps } from '../ConsolidatedOsv/OsvGaps';
|
||||||
import { OsvTable } from '../ConsolidatedOsv/OsvTable';
|
import { OsvTable } from '../ConsolidatedOsv/OsvTable';
|
||||||
import { pairsViewSearch, readPairsView, selectPair } from '../ConsolidatedOsv/counterPairs';
|
import { pairsViewSearch, readPairsView, selectPair, switchPairsView } from '../ConsolidatedOsv/counterPairs';
|
||||||
import { mapEntryId } from '../ConsolidatedOsv/counterpartyMapping';
|
import { mapEntryId } from '../ConsolidatedOsv/counterpartyMapping';
|
||||||
import { OSV_PAGE_SIZE, useConsolidatedOsv } from '../ConsolidatedOsv/useConsolidatedOsv';
|
import { OSV_PAGE_SIZE, useConsolidatedOsv } from '../ConsolidatedOsv/useConsolidatedOsv';
|
||||||
import { PRECISIONS, UNITS, useNumberDisplay } from '../ConsolidatedOsv/useNumberDisplay';
|
import { PRECISIONS, UNITS, useNumberDisplay } from '../ConsolidatedOsv/useNumberDisplay';
|
||||||
@ -28,7 +28,11 @@ export function OsvContent({ card, onSummaryChange }: Props) {
|
|||||||
|
|
||||||
// строка, для которой открыт выбор организации («Указать» или ИНН сопоставленного ЮЛ)
|
// строка, для которой открыт выбор организации («Указать» или ИНН сопоставленного ЮЛ)
|
||||||
const [mapping, setMapping] = useState<ConsolidatedRowOut | null>(null);
|
const [mapping, setMapping] = useState<ConsolidatedRowOut | null>(null);
|
||||||
const osv = useConsolidatedOsv(version_id ?? '');
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const pairsView = readPairsView(searchParams);
|
||||||
|
const inPairs = pairsView.view === 'pairs';
|
||||||
|
const osv = useConsolidatedOsv(version_id ?? '', inPairs);
|
||||||
|
const s = osv.summary;
|
||||||
const { unit, setUnit, precision, setPrecision, format } = useNumberDisplay();
|
const { unit, setUnit, precision, setPrecision, format } = useNumberDisplay();
|
||||||
|
|
||||||
const download = async () => {
|
const download = async () => {
|
||||||
@ -46,12 +50,8 @@ export function OsvContent({ card, onSummaryChange }: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const s = osv.summary;
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const pairsView = readPairsView(searchParams);
|
|
||||||
const pairs = s?.counter_pairs ?? [];
|
const pairs = s?.counter_pairs ?? [];
|
||||||
const selectedPair = selectPair(pairs, pairsView.holder, pairsView.missing);
|
const selectedPair = selectPair(pairs, pairsView.holder, pairsView.missing);
|
||||||
const inPairs = pairsView.view === 'pairs';
|
|
||||||
const setView = (v: Parameters<typeof pairsViewSearch>[0]) => setSearchParams(pairsViewSearch(v), { replace: true });
|
const setView = (v: Parameters<typeof pairsViewSearch>[0]) => setSearchParams(pairsViewSearch(v), { replace: true });
|
||||||
const unmapped = s?.unmapped_total ?? 0;
|
const unmapped = s?.unmapped_total ?? 0;
|
||||||
const onlyUnmapped = osv.filters.unmapped === true;
|
const onlyUnmapped = osv.filters.unmapped === true;
|
||||||
@ -71,6 +71,19 @@ export function OsvContent({ card, onSummaryChange }: Props) {
|
|||||||
// вне вкладки «ОСВ» предупреждать не о чем
|
// вне вкладки «ОСВ» предупреждать не о чем
|
||||||
useEffect(() => () => onSummaryChange?.(null), [onSummaryChange]);
|
useEffect(() => () => onSummaryChange?.(null), [onSummaryChange]);
|
||||||
|
|
||||||
|
const pairsBody = s ? (
|
||||||
|
<CounterPairsView
|
||||||
|
versionId={version_id ?? ''}
|
||||||
|
pairs={pairs}
|
||||||
|
selected={selectedPair}
|
||||||
|
numFormat={format}
|
||||||
|
onSelect={(holder, missing) => setView({ view: 'pairs', holder, missing })}
|
||||||
|
onMapped={osv.refresh}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
!osv.error && <Spin style={{ alignSelf: 'center', margin: 24 }} />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main
|
<main
|
||||||
@ -110,12 +123,11 @@ export function OsvContent({ card, onSummaryChange }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<Segmented
|
<Segmented
|
||||||
value={pairsView.view}
|
value={pairsView.view}
|
||||||
onChange={(v) =>
|
onChange={(v) => setView(switchPairsView(pairsView, v === 'pairs' ? 'pairs' : 'table'))}
|
||||||
setView(v === 'pairs' ? { view: 'pairs', holder: null, missing: null } : { view: 'table', holder: null, missing: null })
|
|
||||||
}
|
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Свод', value: 'table' },
|
{ label: 'Свод', value: 'table' },
|
||||||
{ label: `Встречные записи (${pairs.length})`, value: 'pairs' },
|
// пока сводки нет — без числа
|
||||||
|
{ label: s ? `Встречные записи (${pairs.length})` : 'Встречные записи', value: 'pairs' },
|
||||||
]}
|
]}
|
||||||
style={{ alignSelf: 'flex-start' }}
|
style={{ alignSelf: 'flex-start' }}
|
||||||
/>
|
/>
|
||||||
@ -149,16 +161,7 @@ export function OsvContent({ card, onSummaryChange }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{inPairs ? (
|
{inPairs ? (
|
||||||
s && (
|
pairsBody
|
||||||
<CounterPairsView
|
|
||||||
versionId={version_id ?? ''}
|
|
||||||
pairs={pairs}
|
|
||||||
selected={selectedPair}
|
|
||||||
numFormat={format}
|
|
||||||
onSelect={(holder, missing) => setView({ view: 'pairs', holder, missing })}
|
|
||||||
onMapped={osv.refresh}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<OsvGaps gaps={s?.gaps ?? []} />
|
<OsvGaps gaps={s?.gaps ?? []} />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user