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