diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 86fb636..539e4b3 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -30,7 +30,7 @@ import { DictVspApi } from '../../api/dict-vsp'; import { useAuth } from '../../app/context/AuthProvider'; import { ROLES_NAME_ID } from '../../constants/constants'; import { FormsNoCanAddRow } from './constants/formConfig'; -import { loadFromStorage, saveToStorage } from './utils/localStorageUtils'; +import useDenomination from '../common/useDenomination'; const CONFIG_CACHE = new Map(); @@ -163,12 +163,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { const { sizeMult, setSizeMult, tableScaleStyle, tableWrapperStyle } = useTableScale(); const denominationStorageKey = `${formId}_${formType}_${sheetName}_${direction}_denomination`; - const [denominationByTable, setDenominationByTable] = useState({}); - const denomination = denominationByTable[denominationStorageKey] ?? loadFromStorage(denominationStorageKey, 'rub'); - const setTableDenomination = useCallback((value) => { - saveToStorage(denominationStorageKey, value); - setDenominationByTable((current) => ({ ...current, [denominationStorageKey]: value })); - }, [denominationStorageKey]); + const { denomination, setDenomination: setTableDenomination } = useDenomination(denominationStorageKey); useEffect(() => { rowVirtualizerRef.current?.measure?.(); diff --git a/web/src/components/RealtimeTable/tableColumns.jsx b/web/src/components/RealtimeTable/tableColumns.jsx index d7c6c23..cf557e0 100644 --- a/web/src/components/RealtimeTable/tableColumns.jsx +++ b/web/src/components/RealtimeTable/tableColumns.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react' import { createPortal } from 'react-dom'; import { useParams } from 'react-router'; import { useVspOptions } from './contexts/RealtimeContext'; +import { getDenominationDivisor } from '../common/useDenomination'; import { ROLES_NAME_ID } from '../../constants/constants'; import { blueColumn } from './constants/columnColors'; @@ -120,7 +121,7 @@ export const getTableColumns = ({ isCellInvalid, denomination = 'rub', }) => { - const displayDivisor = denomination === 'billion' ? 1000000000 : denomination === 'million' ? 1000000 : denomination === 'thousand' ? 1000 : 1; + const displayDivisor = getDenominationDivisor(denomination); const columnColors = { ...columnsConfig.colors }; const columns = structuredClone(columnsConfig.columns); diff --git a/web/src/components/common/useDenomination.js b/web/src/components/common/useDenomination.js new file mode 100644 index 0000000..72999e7 --- /dev/null +++ b/web/src/components/common/useDenomination.js @@ -0,0 +1,35 @@ +import { useCallback, useEffect, useState } from 'react'; + +const CHANGE_EVENT = 'denomination-change'; +const readValue = (key) => { + try { return localStorage.getItem(key) || 'rub'; } catch { return 'rub'; } +}; + +export const getDenominationDivisor = (denomination) => ({ + rub: 1, + thousand: 1000, + million: 1000000, + billion: 1000000000, +}[denomination] || 1); + +export default function useDenomination(storageKey) { + const [values, setValues] = useState({}); + const denomination = values[storageKey] ?? readValue(storageKey); + const setDenomination = useCallback((value) => { + try { localStorage.setItem(storageKey, value); } catch { /* storage may be unavailable */ } + setValues((current) => ({ ...current, [storageKey]: value })); + if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent(CHANGE_EVENT, { detail: { key: storageKey, value } })); + } + }, [storageKey]); + + useEffect(() => { + const sync = (event) => { + if (event.detail?.key === storageKey) setValues((current) => ({ ...current, [storageKey]: event.detail.value })); + }; + window.addEventListener(CHANGE_EVENT, sync); + return () => window.removeEventListener(CHANGE_EVENT, sync); + }, [storageKey]); + + return { denomination, setDenomination, divisor: getDenominationDivisor(denomination) }; +} diff --git a/web/src/pages/Svod2Page/Svod2Page.jsx b/web/src/pages/Svod2Page/Svod2Page.jsx index ea3ac2e..76bd120 100644 --- a/web/src/pages/Svod2Page/Svod2Page.jsx +++ b/web/src/pages/Svod2Page/Svod2Page.jsx @@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom'; import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch'; import { PrimaryButton } from '../../components/common/Buttons/Buttons'; import DenominationSelect from '../../components/common/DenominationSelect'; +import useDenomination from '../../components/common/useDenomination'; import { useSvod } from '../SvodPage/SvodContext'; import { SvodFilters } from '../SvodPage/SvodFilters'; @@ -367,15 +368,8 @@ export default function Svod2Page() { const navigate = useNavigate(); const { getSheetFilters, updateSheetFilters, getSummary, invalidateSummary } = useSvod(); const [sheet, setSheet] = useState('GO'); - const [denominations, setDenominations] = useState({}); const denominationStorageKey = `svod2_${sheet}_denomination`; - const denomination = denominations[denominationStorageKey] ?? (() => { try { return localStorage.getItem(denominationStorageKey) || 'rub'; } catch { return 'rub'; } })(); - const divisor = denomination === 'billion' ? 1000000000 : denomination === 'million' ? 1000000 : denomination === 'thousand' ? 1000 : 1; - - const handleDenominationChange = (value) => { - setDenominations((current) => ({ ...current, [denominationStorageKey]: value })); - try { localStorage.setItem(denominationStorageKey, value); } catch { /* storage may be unavailable */ } - }; + const { denomination, setDenomination: handleDenominationChange, divisor } = useDenomination(denominationStorageKey); const [tableStateBySheet, setTableStateBySheet] = useState(createInitialTableState); const [columnFiltersBySheet, setColumnFiltersBySheet] = useState(() => Object.fromEntries(SHEETS.map(({ value }) => [value, []]))); const latestSummaryRequestRef = useRef(Object.fromEntries(SHEETS.map(({ value }) => [value, 0]))); diff --git a/web/src/pages/SvodPage/SummaryTable.jsx b/web/src/pages/SvodPage/SummaryTable.jsx index 9c9d66c..eb41a65 100644 --- a/web/src/pages/SvodPage/SummaryTable.jsx +++ b/web/src/pages/SvodPage/SummaryTable.jsx @@ -1,6 +1,7 @@ import { MaterialReactTable, useMaterialReactTable } from 'material-react-table'; -import { memo, useEffect, useMemo, useState } from 'react'; +import { memo, useMemo } from 'react'; import DenominationSelect from '../../components/common/DenominationSelect'; +import useDenomination from '../../components/common/useDenomination'; import { blueColumn, greenColumn, orangeColumn } from '../../components/RealtimeTable/constants/columnColors'; import { sectionCodeColor } from '../../components/RealtimeTable/constants/columnConfig'; import { buildRowHierarchy } from '../../components/RealtimeTable/utils/rowUtils'; @@ -57,9 +58,6 @@ export const filterSummaryTree = (tree, query) => export const countSummaryTree = (tree) => tree.reduce((count, row) => count + 1 + countSummaryTree(row.subRows), 0); const denominationKey = (tableKey) => `summary_${tableKey}_denomination`; -const loadDenomination = (key) => { - try { return localStorage.getItem(key) || 'rub'; } catch { return 'rub'; } -}; const scaleColumns = (columns, divisor) => columns.map((column) => { const children = column.columns ? scaleColumns(column.columns, divisor) : undefined; @@ -80,17 +78,8 @@ const scaleColumns = (columns, divisor) => columns.map((column) => { }); export const SummaryTable = memo(function SummaryTable({ columns, data, loading, colorBySource = false, tableKey = 'default' }) { - const [denominations, setDenominations] = useState({}); const storageKey = denominationKey(tableKey); - const denomination = denominations[storageKey] ?? loadDenomination(storageKey); - useEffect(() => { - const sync = (event) => { - if (event.detail?.key === storageKey) setDenominations((current) => ({ ...current, [storageKey]: event.detail.value })); - }; - window.addEventListener('summary-denomination-change', sync); - return () => window.removeEventListener('summary-denomination-change', sync); - }, [storageKey]); - const divisor = denomination === 'billion' ? 1000000000 : denomination === 'million' ? 1000000 : denomination === 'thousand' ? 1000 : 1; + const { divisor } = useDenomination(storageKey); const scaledColumns = useMemo(() => scaleColumns(columns, divisor), [columns, divisor]); const table = useMaterialReactTable({ @@ -192,11 +181,6 @@ export const SummaryTable = memo(function SummaryTable({ columns, data, loading, export function SummaryDenominationControl({ tableKey = 'default' }) { const storageKey = denominationKey(tableKey); - const [values, setValues] = useState({}); - const value = values[storageKey] ?? loadDenomination(storageKey); - return { - setValues((current) => ({ ...current, [storageKey]: next })); - try { localStorage.setItem(storageKey, next); } catch { /* storage may be unavailable */ } - window.dispatchEvent(new CustomEvent('summary-denomination-change', { detail: { key: storageKey, value: next } })); - }} />; + const { denomination, setDenomination } = useDenomination(storageKey); + return ; }