Merge pull request 'optimize table and preserve tab state' (#145) from optimize-svod2-table into test
Reviewed-on: #145
This commit is contained in:
commit
ac1adfc6b5
@ -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);
|
||||
|
||||
@ -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() {
|
||||
<Paper elevation={0} sx={{ border: '0.0625rem solid #e5e7eb', borderRadius: '0.75rem', overflow: 'hidden', flexShrink: 0 }}>
|
||||
<Tabs
|
||||
value={sheet}
|
||||
onChange={(_event, value) => {
|
||||
setSheet(value);
|
||||
setError('');
|
||||
}}
|
||||
onChange={(_event, value) => setSheet(value)}
|
||||
sx={{ px: '0.5rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}>
|
||||
{SHEETS.map((item) => (
|
||||
<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';
|
||||
|
||||
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 (
|
||||
<Box sx={{ p: '1rem', display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
||||
@ -116,8 +132,8 @@ export function SvodFilters({
|
||||
/>
|
||||
<TextField
|
||||
size='small'
|
||||
value={search}
|
||||
onChange={(event) => 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={{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user