diff --git a/web/src/hooks/useFullScreen.tsx b/web/src/hooks/useFullScreen.tsx new file mode 100644 index 0000000..baac1d6 --- /dev/null +++ b/web/src/hooks/useFullScreen.tsx @@ -0,0 +1,29 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { useNotificationApi } from "../utils/useNotification"; + +export function useFullscreen() { + const ref = useRef(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 }; +} \ No newline at end of file diff --git a/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx b/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx index 874abf1..c4bc6a4 100644 --- a/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx +++ b/web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx @@ -1,6 +1,6 @@ import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; 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 { ApiError } from '../../../api/client'; import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter'; @@ -20,6 +20,7 @@ import { useNotificationApi } from '../../../utils/useNotification'; import { ConsolidationChessResTable } from '../ConsolidationChessResTable/ConsolidationChessResTable'; import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable'; import { ConsolidationChessTableFilter } from '../ConsolidationChessTableFilter/ConsolidationChessTableFilter'; +import { useFullscreen } from '../../../hooks/useFullScreen'; export const ChessContent = ({ card }: { card: VersionCard | null }) => { const { version_id } = useParams<{ version_id: string }>(); @@ -40,38 +41,17 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { const [chessResDecimals, setChessResDecimals] = useState(2); const [chessResLoading, setChessResLoading] = useState(false); - const chessTableRef = useRef(null); - const resTableRef = useRef(null); - const [isChessFullscreen, setIsChessFullscreen] = useState(false); - const [isResFullscreen, setIsResFullscreen] = useState(false); + const { + ref: chessTableRef, + isFullscreen: isChessFullscreen, + toggle: toggleChessFullscreen, + } = useFullscreen(); - const toggleFullscreen = async (ref: React.RefObject, 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 : 'Не удалось переключить полноэкранный режим', - }); - } - }; - - useEffect(() => { - const handleFullscreenChange = () => { - if (!document.fullscreenElement) { - setIsChessFullscreen(false); - setIsResFullscreen(false); - } - }; - document.addEventListener('fullscreenchange', handleFullscreenChange); - return () => document.removeEventListener('fullscreenchange', handleFullscreenChange); - }, []); + const { + ref: resTableRef, + isFullscreen: isResFullscreen, + toggle: toggleResFullscreen, + } = useFullscreen(); const matrixMode = chessMatrixMode(debit, credit); const isHalfFiltered = matrixMode === 'half'; @@ -251,7 +231,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { Экспорт @@ -300,7 +280,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => { Экспорт diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx index b051c9e..9424918 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx @@ -1,6 +1,6 @@ import { DownloadOutlined, EditOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; 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 { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter'; import { type ColumnDef, ColumnSettings, useVisibleColumns } from '../../../components/GenericTable'; @@ -11,6 +11,7 @@ import { saveBlob, xlsxFilename } from '../../../utils/download'; import { useNotificationApi } from '../../../utils/useNotification'; import { ConsolidationGoodwillTable } from '../ConsolidationGoodwillTable/ConsolidationGoodwillTable'; import { getGoodwillColumns } from '../ConsolidationGoodwillTable/config'; +import { useFullscreen } from '../../../hooks/useFullScreen'; // #TODO --- ТЕСТОВЫЕ ДАННЫЕ --- // const MOCK_DATA: GoodwillTableResponse = { @@ -111,8 +112,6 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { const [isEditMode, setIsEditMode] = useState(false); const [editedRows, setEditedRows] = useState>>({}); - const goodwillTableRef = useRef(null); - const [isGoodwillFullscreen, setIsGoodwillFullscreen] = useState(false); const [goodwillFilter, setGoodwillFilter] = useState('allValue'); const [exportLoading, setExportLoading] = useState(false); 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 toggleFullscreen = async (ref: React.RefObject, 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 = () => { setGoodwillFilter('allValue'); @@ -303,18 +288,11 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { return !hasErrors; }, [editedRows, isEditMode]); - useEffect(() => { - const handleFullscreenChange = () => { - if (!document.fullscreenElement) { - setIsGoodwillFullscreen(false); - } - }; - - document.addEventListener('fullscreenchange', handleFullscreenChange); - return () => { - document.removeEventListener('fullscreenchange', handleFullscreenChange); - }; - }, []); + const { + ref: goodwillTableRef, + isFullscreen: isGoodwillFullscreen, + toggle: toggleGoodwillFullscreen, + } = useFullscreen(); useEffect(() => { if (version_id) { @@ -424,7 +402,7 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { Экспорт diff --git a/web/src/widgets/CorrectionTable/CorrectionContent.tsx b/web/src/widgets/CorrectionTable/CorrectionContent.tsx index a8fc454..4aa77ab 100644 --- a/web/src/widgets/CorrectionTable/CorrectionContent.tsx +++ b/web/src/widgets/CorrectionTable/CorrectionContent.tsx @@ -16,6 +16,7 @@ import { COLUMN_DEFS, type ColKey, type ColShape } from './columns'; import { errorOutcome, readOnlyHint } from './readOnly'; import { EMPTY_FILTERS, type TableFilters, aggregateKey, buildRows, filterTable, lineOptions, visibleTotals } from './tableModel'; import { useCorrectionTable } from './useCorrectionTable'; +import { useFullscreen } from '../../hooks/useFullScreen'; 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) { const { table, loading, error, reload, replace, replaceAggregate } = useCorrectionTable(versionId); - const boxRef = useRef(null); // уведомления рисуются внутри бокса вкладки: в полноэкранном режиме виден только полноэкранный элемент, // глобальные уведомления за ним не видны - const [notification, holder] = antdNotification.useNotification({ getContainer: () => boxRef.current ?? document.body }); + const [notification, holder] = antdNotification.useNotification({ getContainer: () => tableRef.current ?? document.body }); const notify = useMemo( () => ({ error: ({ description }: { description: ReactNode }) => @@ -40,7 +40,6 @@ export function CorrectionContent({ versionId, card = null }: Props) { const [expanded, setExpanded] = useState>(new Set()); const { visibleKeys, setVisibleKeys } = useVisibleColumns({ rowKey: 'include', columns: COLUMN_DEFS }); const [exporting, setExporting] = useState(false); - const [fullscreen, setFullscreen] = useState(false); // идёт запрос решения по строке: на это время контролы строк неактивны, чтобы не слать два запроса подряд const [busy, setBusy] = useState(false); // ref, а не состояние: setBusy применяется после перерисовки, а два клика могут прийти до неё — ref меняется мгновенно @@ -71,28 +70,7 @@ export function CorrectionContent({ versionId, card = null }: Props) { // ушли с вкладки — уведомление об ошибке закрываем, оно относится к этому экрану useEffect(() => () => notification.destroy(LOAD_ERROR_KEY), [notification]); - useEffect(() => { - 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 { ref: tableRef, isFullscreen: isFullscreen, toggle: toggleFullscreen } = useFullscreen(); const download = async () => { setExporting(true); @@ -171,7 +149,7 @@ export function CorrectionContent({ versionId, card = null }: Props) { onFiltersChange={setFilters} lineOptions={lines} visibleKeys={visibleKeys as ColKey[]} - scrollY={fullscreen ? 'calc(100vh - 190px)' : 'calc(100vh - 360px)'} + scrollY={isFullscreen ? 'calc(100vh - 190px)' : 'calc(100vh - 360px)'} emptyText={emptyText} /> @@ -194,7 +172,7 @@ export function CorrectionContent({ versionId, card = null }: Props) { return ( (document.fullscreenElement as HTMLElement) || document.body}>
{holder} @@ -221,7 +199,7 @@ export function CorrectionContent({ versionId, card = null }: Props) { onExport={download} exporting={exporting} exportDisabled={!table} - fullscreen={fullscreen} + fullscreen={isFullscreen} onToggleFullscreen={toggleFullscreen} /> {body}