функция раскрытия на весь экран вынесена в отдельный хук useFullScreen

This commit is contained in:
Grog 2026-11-06 08:51:16 +03:00
parent db8641a1a8
commit 29a630c5e5
4 changed files with 58 additions and 93 deletions

View File

@ -0,0 +1,29 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useNotificationApi } from "../utils/useNotification";
export function useFullscreen<T extends HTMLElement = HTMLDivElement>() {
const ref = useRef<T>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const notification = useNotificationApi();
useEffect(() => {
const onChange = () => setIsFullscreen(document.fullscreenElement === ref.current);
document.addEventListener('fullscreenchange', onChange);
return () => document.removeEventListener('fullscreenchange', onChange);
}, []);
const toggle = useCallback(async () => {
if (!ref.current) return;
try {
if (document.fullscreenElement) {
await document.exitFullscreen();
} else {
await ref.current.requestFullscreen();
}
} catch (e) {
notification.error({ description: e instanceof Error ? e.message : 'Не удалось переключить полноэкранный режим' });
}
}, [notification]);
return { ref, isFullscreen, toggle };
}

View File

@ -1,6 +1,6 @@
import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
import { Button, ConfigProvider } from 'antd'; import { Button, ConfigProvider } from 'antd';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ApiError } from '../../../api/client'; import { ApiError } from '../../../api/client';
import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter'; import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter';
@ -20,6 +20,7 @@ import { useNotificationApi } from '../../../utils/useNotification';
import { ConsolidationChessResTable } from '../ConsolidationChessResTable/ConsolidationChessResTable'; import { ConsolidationChessResTable } from '../ConsolidationChessResTable/ConsolidationChessResTable';
import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable'; import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable';
import { ConsolidationChessTableFilter } from '../ConsolidationChessTableFilter/ConsolidationChessTableFilter'; import { ConsolidationChessTableFilter } from '../ConsolidationChessTableFilter/ConsolidationChessTableFilter';
import { useFullscreen } from '../../../hooks/useFullScreen';
export const ChessContent = ({ card }: { card: VersionCard | null }) => { export const ChessContent = ({ card }: { card: VersionCard | null }) => {
const { version_id } = useParams<{ version_id: string }>(); const { version_id } = useParams<{ version_id: string }>();
@ -40,38 +41,17 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
const [chessResDecimals, setChessResDecimals] = useState(2); const [chessResDecimals, setChessResDecimals] = useState(2);
const [chessResLoading, setChessResLoading] = useState(false); const [chessResLoading, setChessResLoading] = useState(false);
const chessTableRef = useRef<HTMLDivElement>(null); const {
const resTableRef = useRef<HTMLDivElement>(null); ref: chessTableRef,
const [isChessFullscreen, setIsChessFullscreen] = useState(false); isFullscreen: isChessFullscreen,
const [isResFullscreen, setIsResFullscreen] = useState(false); toggle: toggleChessFullscreen,
} = useFullscreen<HTMLDivElement>();
const toggleFullscreen = async (ref: React.RefObject<HTMLDivElement>, setFullscreen: (val: boolean) => void) => { const {
if (!ref.current) return; ref: resTableRef,
try { isFullscreen: isResFullscreen,
if (!document.fullscreenElement) { toggle: toggleResFullscreen,
await ref.current.requestFullscreen(); } = useFullscreen<HTMLDivElement>();
setFullscreen(true);
} else {
await document.exitFullscreen();
setFullscreen(false);
}
} catch (e) {
notification.error({
description: e instanceof Error ? e.message : 'Не удалось переключить полноэкранный режим',
});
}
};
useEffect(() => {
const handleFullscreenChange = () => {
if (!document.fullscreenElement) {
setIsChessFullscreen(false);
setIsResFullscreen(false);
}
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
}, []);
const matrixMode = chessMatrixMode(debit, credit); const matrixMode = chessMatrixMode(debit, credit);
const isHalfFiltered = matrixMode === 'half'; const isHalfFiltered = matrixMode === 'half';
@ -251,7 +231,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
Экспорт Экспорт
</Button> </Button>
<Button <Button
onClick={() => toggleFullscreen(chessTableRef, setIsChessFullscreen)} onClick={() => toggleChessFullscreen()}
icon={isChessFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}> icon={isChessFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}>
{isChessFullscreen ? 'Свернуть' : 'На весь экран'} {isChessFullscreen ? 'Свернуть' : 'На весь экран'}
</Button> </Button>
@ -300,7 +280,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
Экспорт Экспорт
</Button> </Button>
<Button <Button
onClick={() => toggleFullscreen(resTableRef, setIsResFullscreen)} onClick={() => toggleResFullscreen()}
icon={isResFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}> icon={isResFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}>
{isResFullscreen ? 'Свернуть' : 'На весь экран'} {isResFullscreen ? 'Свернуть' : 'На весь экран'}
</Button> </Button>

View File

@ -1,6 +1,6 @@
import { DownloadOutlined, EditOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; import { DownloadOutlined, EditOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
import { Alert, Button, ConfigProvider, Select, Space, Tooltip } from 'antd'; import { Alert, Button, ConfigProvider, Select, Space, Tooltip } from 'antd';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter'; import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter';
import { type ColumnDef, ColumnSettings, useVisibleColumns } from '../../../components/GenericTable'; import { type ColumnDef, ColumnSettings, useVisibleColumns } from '../../../components/GenericTable';
@ -11,6 +11,7 @@ import { saveBlob, xlsxFilename } from '../../../utils/download';
import { useNotificationApi } from '../../../utils/useNotification'; import { useNotificationApi } from '../../../utils/useNotification';
import { ConsolidationGoodwillTable } from '../ConsolidationGoodwillTable/ConsolidationGoodwillTable'; import { ConsolidationGoodwillTable } from '../ConsolidationGoodwillTable/ConsolidationGoodwillTable';
import { getGoodwillColumns } from '../ConsolidationGoodwillTable/config'; import { getGoodwillColumns } from '../ConsolidationGoodwillTable/config';
import { useFullscreen } from '../../../hooks/useFullScreen';
// #TODO --- ТЕСТОВЫЕ ДАННЫЕ --- // #TODO --- ТЕСТОВЫЕ ДАННЫЕ ---
// const MOCK_DATA: GoodwillTableResponse = { // const MOCK_DATA: GoodwillTableResponse = {
@ -111,8 +112,6 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => {
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const [editedRows, setEditedRows] = useState<Record<string, Partial<GoodwillRow>>>({}); const [editedRows, setEditedRows] = useState<Record<string, Partial<GoodwillRow>>>({});
const goodwillTableRef = useRef<HTMLDivElement>(null);
const [isGoodwillFullscreen, setIsGoodwillFullscreen] = useState(false);
const [goodwillFilter, setGoodwillFilter] = useState('allValue'); const [goodwillFilter, setGoodwillFilter] = useState('allValue');
const [exportLoading, setExportLoading] = useState(false); const [exportLoading, setExportLoading] = useState(false);
const versionInfo = card ? { groupName: card.company_group.name, year: card.year, quarter: card.quarter } : null; const versionInfo = card ? { groupName: card.company_group.name, year: card.year, quarter: card.quarter } : null;
@ -179,20 +178,6 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => {
const { visibleKeys, setVisibleKeys } = useVisibleColumns(groupsConfig); const { visibleKeys, setVisibleKeys } = useVisibleColumns(groupsConfig);
const toggleFullscreen = async (ref: React.RefObject<HTMLDivElement>, setFullscreen: (val: boolean) => void) => {
if (!ref.current) return;
try {
if (!document.fullscreenElement) {
await ref.current.requestFullscreen();
setFullscreen(true);
} else {
await document.exitFullscreen();
setFullscreen(false);
}
} catch (e) {
notification.error({ description: e instanceof Error ? e.message : 'Ошибка раскрытия на весь экран' });
}
};
const dropFiltersHandler = () => { const dropFiltersHandler = () => {
setGoodwillFilter('allValue'); setGoodwillFilter('allValue');
@ -303,18 +288,11 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => {
return !hasErrors; return !hasErrors;
}, [editedRows, isEditMode]); }, [editedRows, isEditMode]);
useEffect(() => { const {
const handleFullscreenChange = () => { ref: goodwillTableRef,
if (!document.fullscreenElement) { isFullscreen: isGoodwillFullscreen,
setIsGoodwillFullscreen(false); toggle: toggleGoodwillFullscreen,
} } = useFullscreen<HTMLDivElement>();
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, []);
useEffect(() => { useEffect(() => {
if (version_id) { if (version_id) {
@ -424,7 +402,7 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => {
Экспорт Экспорт
</Button> </Button>
<Button <Button
onClick={() => toggleFullscreen(goodwillTableRef, setIsGoodwillFullscreen)} onClick={() => toggleGoodwillFullscreen()}
icon={isGoodwillFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}> icon={isGoodwillFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}>
{isGoodwillFullscreen ? 'Свернуть' : 'На весь экран'} {isGoodwillFullscreen ? 'Свернуть' : 'На весь экран'}
</Button> </Button>

View File

@ -16,6 +16,7 @@ import { COLUMN_DEFS, type ColKey, type ColShape } from './columns';
import { errorOutcome, readOnlyHint } from './readOnly'; import { errorOutcome, readOnlyHint } from './readOnly';
import { EMPTY_FILTERS, type TableFilters, aggregateKey, buildRows, filterTable, lineOptions, visibleTotals } from './tableModel'; import { EMPTY_FILTERS, type TableFilters, aggregateKey, buildRows, filterTable, lineOptions, visibleTotals } from './tableModel';
import { useCorrectionTable } from './useCorrectionTable'; import { useCorrectionTable } from './useCorrectionTable';
import { useFullscreen } from '../../hooks/useFullScreen';
const LOAD_ERROR_KEY = 'corrections-load-error'; const LOAD_ERROR_KEY = 'corrections-load-error';
@ -23,10 +24,9 @@ type Props = { versionId: string; card?: VersionCard | null };
export function CorrectionContent({ versionId, card = null }: Props) { export function CorrectionContent({ versionId, card = null }: Props) {
const { table, loading, error, reload, replace, replaceAggregate } = useCorrectionTable(versionId); const { table, loading, error, reload, replace, replaceAggregate } = useCorrectionTable(versionId);
const boxRef = useRef<HTMLDivElement>(null);
// уведомления рисуются внутри бокса вкладки: в полноэкранном режиме виден только полноэкранный элемент, // уведомления рисуются внутри бокса вкладки: в полноэкранном режиме виден только полноэкранный элемент,
// глобальные уведомления за ним не видны // глобальные уведомления за ним не видны
const [notification, holder] = antdNotification.useNotification({ getContainer: () => boxRef.current ?? document.body }); const [notification, holder] = antdNotification.useNotification({ getContainer: () => tableRef.current ?? document.body });
const notify = useMemo( const notify = useMemo(
() => ({ () => ({
error: ({ description }: { description: ReactNode }) => error: ({ description }: { description: ReactNode }) =>
@ -40,7 +40,6 @@ export function CorrectionContent({ versionId, card = null }: Props) {
const [expanded, setExpanded] = useState<ReadonlySet<string>>(new Set()); const [expanded, setExpanded] = useState<ReadonlySet<string>>(new Set());
const { visibleKeys, setVisibleKeys } = useVisibleColumns<ColShape>({ rowKey: 'include', columns: COLUMN_DEFS }); const { visibleKeys, setVisibleKeys } = useVisibleColumns<ColShape>({ rowKey: 'include', columns: COLUMN_DEFS });
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
// идёт запрос решения по строке: на это время контролы строк неактивны, чтобы не слать два запроса подряд // идёт запрос решения по строке: на это время контролы строк неактивны, чтобы не слать два запроса подряд
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
// ref, а не состояние: setBusy применяется после перерисовки, а два клика могут прийти до неё — ref меняется мгновенно // ref, а не состояние: setBusy применяется после перерисовки, а два клика могут прийти до неё — ref меняется мгновенно
@ -71,28 +70,7 @@ export function CorrectionContent({ versionId, card = null }: Props) {
// ушли с вкладки — уведомление об ошибке закрываем, оно относится к этому экрану // ушли с вкладки — уведомление об ошибке закрываем, оно относится к этому экрану
useEffect(() => () => notification.destroy(LOAD_ERROR_KEY), [notification]); useEffect(() => () => notification.destroy(LOAD_ERROR_KEY), [notification]);
useEffect(() => { const { ref: tableRef, isFullscreen: isFullscreen, toggle: toggleFullscreen } = useFullscreen<HTMLDivElement>();
const onChange = () => {
if (!document.fullscreenElement) setFullscreen(false);
};
document.addEventListener('fullscreenchange', onChange);
return () => document.removeEventListener('fullscreenchange', onChange);
}, []);
const toggleFullscreen = async () => {
if (!boxRef.current) return;
try {
if (!document.fullscreenElement) {
await boxRef.current.requestFullscreen();
setFullscreen(true);
} else {
await document.exitFullscreen();
setFullscreen(false);
}
} catch (e) {
notify.error({ description: e instanceof Error ? e.message : 'Не удалось переключить полноэкранный режим' });
}
};
const download = async () => { const download = async () => {
setExporting(true); setExporting(true);
@ -171,7 +149,7 @@ export function CorrectionContent({ versionId, card = null }: Props) {
onFiltersChange={setFilters} onFiltersChange={setFilters}
lineOptions={lines} lineOptions={lines}
visibleKeys={visibleKeys as ColKey[]} visibleKeys={visibleKeys as ColKey[]}
scrollY={fullscreen ? 'calc(100vh - 190px)' : 'calc(100vh - 360px)'} scrollY={isFullscreen ? 'calc(100vh - 190px)' : 'calc(100vh - 360px)'}
emptyText={emptyText} emptyText={emptyText}
/> />
</Spin> </Spin>
@ -194,7 +172,7 @@ export function CorrectionContent({ versionId, card = null }: Props) {
return ( return (
<ConfigProvider getPopupContainer={() => (document.fullscreenElement as HTMLElement) || document.body}> <ConfigProvider getPopupContainer={() => (document.fullscreenElement as HTMLElement) || document.body}>
<div <div
ref={boxRef} ref={tableRef}
style={{ style={{
background: '#fff', background: '#fff',
padding: 12, padding: 12,
@ -203,7 +181,7 @@ export function CorrectionContent({ versionId, card = null }: Props) {
gap: 12, gap: 12,
flex: 1, flex: 1,
minHeight: 0, minHeight: 0,
height: fullscreen ? '100vh' : undefined, height: isFullscreen ? '100vh' : undefined,
overflow: 'hidden', overflow: 'hidden',
}}> }}>
{holder} {holder}
@ -221,7 +199,7 @@ export function CorrectionContent({ versionId, card = null }: Props) {
onExport={download} onExport={download}
exporting={exporting} exporting={exporting}
exportDisabled={!table} exportDisabled={!table}
fullscreen={fullscreen} fullscreen={isFullscreen}
onToggleFullscreen={toggleFullscreen} onToggleFullscreen={toggleFullscreen}
/> />
{body} {body}