diff --git a/web/src/pages/AdminPanel/SppsPage/SppsPage.jsx b/web/src/pages/AdminPanel/SppsPage/SppsPage.jsx index 34ef158..913c69a 100644 --- a/web/src/pages/AdminPanel/SppsPage/SppsPage.jsx +++ b/web/src/pages/AdminPanel/SppsPage/SppsPage.jsx @@ -21,7 +21,6 @@ const SspsPage = () => { const [ssps, setSsps] = useState([]); const [loading, setLoading] = useState(false); const [sspForChangeUsers, setSspForChangeUsers] = useState(null); - const [needUpdateCountUsers, setNeedUpdateCountUsers] = useState(false); const [filterString, setFilterString] = useState(''); const [filterType, setFilterType] = useState(null); const [filterStatus, setFilterStatus] = useState(0); @@ -37,7 +36,7 @@ const SspsPage = () => { try { const data = await SspApi.getAll({ load_users_count: true }); if (!data.success) { - toast.error('Ошибка получения ССП/Региональные филиалы ' + data.message); + toast.error(`Ошибка получения ССП/Региональные филиалы ${data.message}`); return; } setSsps(data.result.sort((a, b) => a.id - b.id)); @@ -51,13 +50,6 @@ const SspsPage = () => { loadSsps(); }, []); - useEffect(() => { - if (openModalUsersinSspChange && openModalAdd) return; - setTimeout(() => { - setNeedUpdateCountUsers(true); - }, 500); - }, [openModalUsersinSspChange, openModalAdd]); - const handleOpenModalUsersInSspChange = useCallback( (sspId) => { setOpenModalUsersinSspChange(true); diff --git a/web/src/pages/Svod2Page/Svod2Page.jsx b/web/src/pages/Svod2Page/Svod2Page.jsx index 718037b..df7a7db 100644 --- a/web/src/pages/Svod2Page/Svod2Page.jsx +++ b/web/src/pages/Svod2Page/Svod2Page.jsx @@ -13,6 +13,8 @@ const SHEETS = [ { value: 'GO', label: 'ССП' }, { value: 'RF', label: 'РФ' }, ]; +const createInitialTableState = () => + Object.fromEntries(SHEETS.map(({ value }) => [value, { rows: [], loading: false, error: '', loadedQueryKey: '' }])); const QUARTERS = [1, 2, 3, 4]; const tableSize = (rem) => rem * 16; const numberFormatter = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 }); @@ -323,6 +325,21 @@ const disableGroupColumnFilters = (columns) => }; }); +const COLUMN_CONFIG_CACHE = new Map(); + +const getColumnConfig = (sheet) => { + if (!COLUMN_CONFIG_CACHE.has(sheet)) { + const columns = disableGroupColumnFilters(createColumns(sheet)); + COLUMN_CONFIG_CACHE.set(sheet, { + columns, + columnBands: createColumnBandMap(columns), + columnGroupEnds: createColumnGroupEndSet(columns), + }); + } + + return COLUMN_CONFIG_CACHE.get(sheet); +}; + const matchesSearch = (row, query) => { const searchableData = { ...row.data?.header, @@ -339,12 +356,12 @@ const matchesSearch = (row, query) => { export default function Svod2Page() { const { getSheetFilters, updateSheetFilters, getSummary, invalidateSummary } = useSvod(); const [sheet, setSheet] = useState('GO'); - const [rows, setRows] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [loadedQueryKey, setLoadedQueryKey] = useState(''); - const latestSummaryRequestRef = useRef(0); + 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]))); const { year, selectedOrganizations, search } = getSheetFilters(sheet); + const { rows, loading, error, loadedQueryKey } = tableStateBySheet[sheet]; + const columnFilters = columnFiltersBySheet[sheet]; const selectedOrganizationIds = useMemo( () => (selectedOrganizations.some(({ id }) => id === '__all__') ? [] : selectedOrganizations.map(({ id }) => id)), [selectedOrganizations], @@ -355,29 +372,56 @@ export default function Svod2Page() { ); const hasCurrentData = loadedQueryKey === currentQueryKey; const updateCurrentSheetFilters = useCallback((changes) => updateSheetFilters(sheet, changes), [sheet, updateSheetFilters]); + const updateCurrentColumnFilters = useCallback( + (updater) => { + setColumnFiltersBySheet((current) => { + const currentFilters = current[sheet]; + const nextFilters = typeof updater === 'function' ? updater(currentFilters) : updater; + return nextFilters === currentFilters ? current : { ...current, [sheet]: nextFilters }; + }); + }, + [sheet], + ); const loadSummary = useCallback(async () => { - const requestId = latestSummaryRequestRef.current + 1; - latestSummaryRequestRef.current = requestId; - setLoading(true); - setError(''); + const requestId = latestSummaryRequestRef.current[sheet] + 1; + latestSummaryRequestRef.current[sheet] = requestId; + setTableStateBySheet((current) => ({ + ...current, + [sheet]: { ...current[sheet], loading: true, error: '' }, + })); try { const response = await getSummary(sheet, year, selectedOrganizationIds); - if (latestSummaryRequestRef.current !== requestId) return; - setRows(response.result || []); - setLoadedQueryKey(currentQueryKey); + if (latestSummaryRequestRef.current[sheet] !== requestId) return; + setTableStateBySheet((current) => ({ + ...current, + [sheet]: { ...current[sheet], rows: response.result || [], loadedQueryKey: currentQueryKey }, + })); } catch (loadError) { - if (latestSummaryRequestRef.current !== requestId) return; - setRows([]); - setError(loadError.response?.data?.detail || 'Не удалось загрузить свод. Попробуйте ещё раз.'); + if (latestSummaryRequestRef.current[sheet] !== requestId) return; + setTableStateBySheet((current) => ({ + ...current, + [sheet]: { + ...current[sheet], + rows: [], + error: loadError.response?.data?.detail || 'Не удалось загрузить свод. Попробуйте ещё раз.', + }, + })); } finally { - if (latestSummaryRequestRef.current === requestId) setLoading(false); + if (latestSummaryRequestRef.current[sheet] === requestId) { + setTableStateBySheet((current) => ({ + ...current, + [sheet]: { ...current[sheet], loading: false }, + })); + } } }, [currentQueryKey, getSummary, selectedOrganizationIds, sheet, year]); useEffect(() => { return () => { - latestSummaryRequestRef.current += 1; + SHEETS.forEach(({ value }) => { + latestSummaryRequestRef.current[value] += 1; + }); }; }, []); @@ -391,9 +435,7 @@ export default function Svod2Page() { const normalizedSearch = search.trim().toLowerCase(); return normalizedSearch ? rows.filter((row) => matchesSearch(row, normalizedSearch)) : rows; }, [hasCurrentData, rows, search]); - const columns = useMemo(() => disableGroupColumnFilters(createColumns(sheet)), [sheet]); - const columnBands = useMemo(() => createColumnBandMap(columns), [columns]); - const columnGroupEnds = useMemo(() => createColumnGroupEndSet(columns), [columns]); + const { columns, columnBands, columnGroupEnds } = useMemo(() => getColumnConfig(sheet), [sheet]); const table = useMaterialReactTable({ columns, data: filteredRows, @@ -401,6 +443,7 @@ export default function Svod2Page() { enableColumnActions: false, enableColumnFilters: true, enableFacetedValues: true, + onColumnFiltersChange: updateCurrentColumnFilters, enableDensityToggle: false, enableFullScreenToggle: false, enableHiding: false, @@ -410,7 +453,7 @@ export default function Svod2Page() { enableBottomToolbar: false, enableSorting: false, rowVirtualizerOptions: ROW_VIRTUALIZER_OPTIONS, - state: { isLoading: loading }, + state: { isLoading: loading, columnFilters }, initialState: { columnPinning: { left: ['smeta_type', 'smeta_direction', 'org_name', 'name'] }, showColumnFilters: true, @@ -520,10 +563,7 @@ export default function Svod2Page() { { - setSheet(value); - setError(''); - }} + onChange={(_event, value) => setSheet(value)} sx={{ px: '0.5rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}> {SHEETS.map((item) => ( diff --git a/web/src/pages/SvodPage/SvodFilters.jsx b/web/src/pages/SvodPage/SvodFilters.jsx index 8d9c63a..6886e27 100644 --- a/web/src/pages/SvodPage/SvodFilters.jsx +++ b/web/src/pages/SvodPage/SvodFilters.jsx @@ -8,6 +8,7 @@ import { useAuth } from '../../app/context/AuthProvider'; import { ROLES_NAME_ID } from '../../constants/constants'; const SELECT_ALL_OPTION = { id: '__all__', title: 'Все ССП/РФ' }; +const SEARCH_DEBOUNCE_MS = 300; export const getAvailableYears = () => { const currentYear = new Date().getFullYear(); @@ -55,6 +56,21 @@ export function SvodFilters({ }) { const { organizations, organizationsLoading } = useSummaryOrganizations(); const organizationOptions = allowSelectAll ? [SELECT_ALL_OPTION, ...organizations] : organizations; + const [searchInput, setSearchInput] = useState(search); + + useEffect(() => { + setSearchInput(search); + }, [search]); + + useEffect(() => { + if (searchInput === search) return; + + const timer = setTimeout(() => { + onChange({ search: searchInput }); + }, SEARCH_DEBOUNCE_MS); + + return () => clearTimeout(timer); + }, [onChange, search, searchInput]); return ( @@ -116,8 +132,8 @@ export function SvodFilters({ /> onChange({ search: event.target.value })} + value={searchInput} + onChange={(event) => setSearchInput(event.target.value)} placeholder={searchPlaceholder} sx={{ minWidth: '16.25rem', flex: '1 1 17.5rem', maxWidth: '23.75rem' }} slotProps={{