Compare commits
No commits in common. "cad325fc8938048cd095d9d21eefcfed6cc2b560" and "fdc03457d04751a34fbf284738f3337cde669b18" have entirely different histories.
cad325fc89
...
fdc03457d0
@ -12,7 +12,6 @@ import NewTablePage from '../pages/NewTablePage.jsx';
|
||||
import NavigationProjectPage from '../pages/ProjectPages/NavigationProjectPage.jsx';
|
||||
import ProjectsPage from '../pages/ProjectsPage/ProjectsPage.jsx';
|
||||
import SvodPage from '../pages/SvodPage/SvodPage.jsx';
|
||||
import { SvodProvider } from '../pages/SvodPage/SvodContext.jsx';
|
||||
import TablePage from '../pages/TablePage';
|
||||
import TableTempPage from '../pages/TableTempPage/TableTempPage';
|
||||
import TablesTest from '../pages/TablesTest.jsx';
|
||||
@ -56,7 +55,7 @@ export const AppRoutes = () => {
|
||||
<Route path='/tables/:form/:sheetName' element={<NewFormTablePage />} />
|
||||
<Route path='/table/form/:formId/form-type/:formType/:sheetName/:direction/:year' element={<NewFormTablePage />} />
|
||||
<Route path='/project-mock-summary' element={<NavigationProjectPage />} />
|
||||
<Route path='/svod' element={<SvodProvider><SvodPage /></SvodProvider>} />
|
||||
<Route path='/svod' element={<SvodPage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@ -5,21 +5,14 @@ import RealtimeTable from '../components/RealtimeTable';
|
||||
import { RealtimeProvider } from '../components/RealtimeTable/contexts/RealtimeContext';
|
||||
import { BackButton } from '../components/common/Buttons/BackButton';
|
||||
import { NameTask, TaskInfoContainer } from '../components/common/SwitchFormTask/SwitchFormTask.style';
|
||||
import { DIRECTION_TRANSLATE, SHEET_NAME } from '../constants/constants';
|
||||
import { SHEET_NAME } from '../constants/constants';
|
||||
|
||||
const TableInfo = ({ formId, sheetName, direction, isProject = false }) => {
|
||||
const path = (isProject ? '/project/' : '/task/') + formId;
|
||||
const tableName = SHEET_NAME[sheetName] || sheetName;
|
||||
const directionName = DIRECTION_TRANSLATE[direction] || direction;
|
||||
const TableInfo = ({ formId, sheetName, isProject = false }) => {
|
||||
const path = (isProject ? `/project/` : '/task/') + formId;
|
||||
const portalContent = (
|
||||
<TaskInfoContainer>
|
||||
<BackButton to={path} />
|
||||
{sheetName && (
|
||||
<NameTask>
|
||||
{tableName}
|
||||
{directionName ? ` — ${directionName}` : ''}
|
||||
</NameTask>
|
||||
)}
|
||||
{sheetName && <NameTask>{SHEET_NAME[sheetName] || sheetName}</NameTask>}
|
||||
</TaskInfoContainer>
|
||||
);
|
||||
|
||||
@ -33,25 +26,24 @@ const TableInfo = ({ formId, sheetName, direction, isProject = false }) => {
|
||||
export default function NewTablePage() {
|
||||
const { formId, formType, sheetName, direction, year } = useParams();
|
||||
const { user } = useAuth();
|
||||
const isProject = formType === 'PROJECT';
|
||||
const normalizedDirection = direction === 'null' ? null : direction;
|
||||
const isProject = formType == 'PROJECT';
|
||||
|
||||
return (
|
||||
<>
|
||||
{formId && <TableInfo formId={formId} sheetName={sheetName} direction={normalizedDirection} isProject={isProject} />}
|
||||
{formId && <TableInfo formId={formId} sheetName={sheetName} isProject={isProject} />}
|
||||
{user && (
|
||||
<RealtimeProvider
|
||||
formId={formId}
|
||||
sheetName={sheetName}
|
||||
userId={user.id}
|
||||
direction={normalizedDirection}
|
||||
direction={direction === 'null' ? null : direction}
|
||||
isProject={isProject}
|
||||
year={year}>
|
||||
<RealtimeTable
|
||||
formType={formType}
|
||||
formId={formId}
|
||||
sheetName={sheetName}
|
||||
direction={normalizedDirection}
|
||||
direction={direction === 'null' ? null : direction}
|
||||
year={year}
|
||||
/>
|
||||
</RealtimeProvider>
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { SummaryApi } from '../../api/summary';
|
||||
|
||||
const SvodContext = createContext(null);
|
||||
|
||||
const SHEET_VALUES = ['MAIN', 'FORM_1', 'FORM_2', 'FORM_3', 'FORM_4'];
|
||||
|
||||
const createDefaultFilters = () => ({
|
||||
year: new Date().getFullYear(),
|
||||
selectedOrganizations: [],
|
||||
search: '',
|
||||
});
|
||||
|
||||
const createInitialFilters = () => Object.fromEntries(SHEET_VALUES.map((sheet) => [sheet, createDefaultFilters()]));
|
||||
|
||||
const createCacheKey = (year, organizationIds) => JSON.stringify([year, organizationIds.map(String).sort()]);
|
||||
|
||||
const getSheetCache = (cache, sheet) => {
|
||||
if (!cache.has(sheet)) cache.set(sheet, new Map());
|
||||
return cache.get(sheet);
|
||||
};
|
||||
|
||||
export const SvodProvider = ({ children }) => {
|
||||
const [filtersBySheet, setFiltersBySheet] = useState(createInitialFilters);
|
||||
const summaryCacheRef = useRef(new Map());
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
summaryCacheRef.current.clear();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const getSheetFilters = useCallback((sheet) => filtersBySheet[sheet] || createDefaultFilters(), [filtersBySheet]);
|
||||
|
||||
const updateSheetFilters = useCallback((sheet, changes) => {
|
||||
setFiltersBySheet((currentFilters) => ({
|
||||
...currentFilters,
|
||||
[sheet]: {
|
||||
...(currentFilters[sheet] || createDefaultFilters()),
|
||||
...changes,
|
||||
},
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const getSummary = useCallback((sheet, year, organizationIds = []) => {
|
||||
const sheetCache = getSheetCache(summaryCacheRef.current, sheet);
|
||||
const cacheKey = createCacheKey(year, organizationIds);
|
||||
const cachedRequest = sheetCache.get(cacheKey);
|
||||
if (cachedRequest) return cachedRequest;
|
||||
|
||||
const request = SummaryApi.get(sheet, year, organizationIds)
|
||||
.then((response) => {
|
||||
if (sheetCache.get(cacheKey) === request) {
|
||||
sheetCache.set(cacheKey, Promise.resolve(response));
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (sheetCache.get(cacheKey) === request) {
|
||||
sheetCache.delete(cacheKey);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
sheetCache.set(cacheKey, request);
|
||||
return request;
|
||||
}, []);
|
||||
|
||||
const invalidateSummary = useCallback((sheet, year, organizationIds = []) => {
|
||||
summaryCacheRef.current.get(sheet)?.delete(createCacheKey(year, organizationIds));
|
||||
}, []);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
getSheetFilters,
|
||||
updateSheetFilters,
|
||||
getSummary,
|
||||
invalidateSummary,
|
||||
}),
|
||||
[getSheetFilters, getSummary, invalidateSummary, updateSheetFilters],
|
||||
);
|
||||
|
||||
return <SvodContext.Provider value={value}>{children}</SvodContext.Provider>;
|
||||
};
|
||||
|
||||
export const useSvod = () => {
|
||||
const context = useContext(SvodContext);
|
||||
if (!context) throw new Error('useSvod must be used within SvodProvider');
|
||||
return context;
|
||||
};
|
||||
@ -5,15 +5,15 @@ import {
|
||||
MenuItem, Paper, Tab, Tabs, TextField, Typography,
|
||||
} from '@mui/material';
|
||||
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { SummaryApi } from '../../api/summary';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||
import { blueColumn } from '../../components/RealtimeTable/constants/columnColors';
|
||||
import { sectionCodeColor } from '../../components/RealtimeTable/constants/columnConfig';
|
||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
||||
import { useSvod } from './SvodContext';
|
||||
|
||||
const SHEETS = [
|
||||
{ value: 'MAIN', label: 'Общий свод'},
|
||||
@ -169,18 +169,16 @@ const getAvailableYears = () => {
|
||||
|
||||
export default function SvodPage() {
|
||||
const { user } = useAuth();
|
||||
const { getSheetFilters, updateSheetFilters, getSummary, invalidateSummary } = useSvod();
|
||||
const [sheet, setSheet] = useState('MAIN');
|
||||
const [year, setYear] = useState(new Date().getFullYear());
|
||||
const [organizations, setOrganizations] = useState([]);
|
||||
const [selectedOrganizations, setSelectedOrganizations] = useState([]);
|
||||
const [rows, setRows] = useState([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [organizationsLoading, setOrganizationsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const latestSummaryRequestRef = useRef(0);
|
||||
const { year, selectedOrganizations, search } = getSheetFilters(sheet);
|
||||
const updateCurrentSheetFilters = useCallback((changes) => {
|
||||
updateSheetFilters(sheet, changes);
|
||||
}, [sheet, updateSheetFilters]);
|
||||
const [requestVersion, setRequestVersion] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
@ -204,34 +202,20 @@ export default function SvodPage() {
|
||||
}, [user]);
|
||||
|
||||
const loadSummary = useCallback(async () => {
|
||||
const requestId = latestSummaryRequestRef.current + 1;
|
||||
latestSummaryRequestRef.current = requestId;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const response = await getSummary(sheet, year, selectedOrganizations.map(({ id }) => id));
|
||||
if (latestSummaryRequestRef.current !== requestId) return;
|
||||
const response = await SummaryApi.get(sheet, year, selectedOrganizations.map(({ id }) => id));
|
||||
setRows([...(response.result || [])].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)));
|
||||
} catch (loadError) {
|
||||
if (latestSummaryRequestRef.current !== requestId) return;
|
||||
setRows([]);
|
||||
setError(loadError.response?.data?.detail || 'Не удалось загрузить свод. Попробуйте ещё раз.');
|
||||
} finally {
|
||||
if (latestSummaryRequestRef.current === requestId) setLoading(false);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [getSummary, selectedOrganizations, sheet, year]);
|
||||
}, [selectedOrganizations, sheet, year]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSummary();
|
||||
return () => {
|
||||
latestSummaryRequestRef.current += 1;
|
||||
};
|
||||
}, [loadSummary]);
|
||||
|
||||
const refreshSummary = useCallback(() => {
|
||||
invalidateSummary(sheet, year, selectedOrganizations.map(({ id }) => id));
|
||||
loadSummary();
|
||||
}, [invalidateSummary, loadSummary, selectedOrganizations, sheet, year]);
|
||||
useEffect(() => { loadSummary(); }, [loadSummary, requestVersion]);
|
||||
|
||||
const filteredRows = useMemo(() => {
|
||||
const normalizedSearch = search.trim().toLowerCase();
|
||||
@ -297,15 +281,10 @@ export default function SvodPage() {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
boxShadow: 'none',
|
||||
backgroundColor: '#f8f9fa', borderRight: '0.0625rem solid #e5e7eb',
|
||||
backgroundColor: '#f8f9fa', borderRight: '0.0625rem solid #e5e7eb', borderBottom: '0.0625rem solid #dfe3e7',
|
||||
color: '#4a5565', fontSize: '0.75rem', fontWeight: 700, lineHeight: 1.2, px: '0.75rem', py: '0.5rem', whiteSpace: 'nowrap',
|
||||
},
|
||||
},
|
||||
muiTableHeadRowProps: {
|
||||
sx: {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
muiTableBodyCellProps: ({ row, column }) => {
|
||||
const backgroundColor = getSummaryRowColor(row);
|
||||
return {
|
||||
@ -333,13 +312,13 @@ export default function SvodPage() {
|
||||
</Tabs>
|
||||
<Box sx={{ p: '1rem', display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
||||
<TextField select size='small' label='Год' value={year}
|
||||
onChange={(event) => updateCurrentSheetFilters({ year: Number(event.target.value) })} sx={{ width: '7.5rem' }}>
|
||||
onChange={(event) => setYear(Number(event.target.value))} sx={{ width: '7.5rem' }}>
|
||||
{getAvailableYears().map((availableYear) => <MenuItem key={availableYear} value={availableYear}>{availableYear}</MenuItem>)}
|
||||
</TextField>
|
||||
<Autocomplete
|
||||
multiple disableCloseOnSelect size='small' options={organizations} loading={organizationsLoading}
|
||||
value={selectedOrganizations} isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
getOptionLabel={(option) => option.title || ''} onChange={(_event, value) => updateCurrentSheetFilters({ selectedOrganizations: value })}
|
||||
getOptionLabel={(option) => option.title || ''} onChange={(_event, value) => setSelectedOrganizations(value)}
|
||||
sx={{ minWidth: '18.75rem', flex: '1 1 22.5rem', maxWidth: '32.5rem' }}
|
||||
renderTags={(value, getTagProps) => value.slice(0, 2).map((option, index) => {
|
||||
const tagProps = getTagProps({ index });
|
||||
@ -352,7 +331,7 @@ export default function SvodPage() {
|
||||
endAdornment: <>{organizationsLoading && <CircularProgress size='1.125rem' />}{params.InputProps?.endAdornment}</>,
|
||||
}} />}
|
||||
/>
|
||||
<TextField size='small' value={search} onChange={(event) => updateCurrentSheetFilters({ search: event.target.value })}
|
||||
<TextField size='small' value={search} onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder='Код или статья расходов' sx={{ minWidth: '16.25rem', flex: '1 1 17.5rem', maxWidth: '23.75rem' }}
|
||||
InputProps={{ startAdornment: <InputAdornment position='start'><SearchRoundedIcon sx={{ color: '#99a1af' }} /></InputAdornment> }} />
|
||||
{!loading && filteredRowsCount !== rows.length && (
|
||||
@ -365,7 +344,7 @@ export default function SvodPage() {
|
||||
color='default'
|
||||
startIcon={<RefreshRoundedIcon />}
|
||||
disabled={loading}
|
||||
onClick={refreshSummary}
|
||||
onClick={() => setRequestVersion((version) => version + 1)}
|
||||
sx={{ ml: 'auto', height: '2.5rem', flexShrink: 0 }}>
|
||||
Обновить
|
||||
</Button>
|
||||
@ -373,7 +352,7 @@ export default function SvodPage() {
|
||||
</Paper>
|
||||
{error ? (
|
||||
<Alert severity='error' action={<Button color='inherit' size='small'
|
||||
onClick={refreshSummary}>Повторить</Button>}>{error}</Alert>
|
||||
onClick={() => setRequestVersion((version) => version + 1)}>Повторить</Button>}>{error}</Alert>
|
||||
) : <MaterialReactTable table={table} />}
|
||||
</Box>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user