front-projects #25
@ -21,7 +21,7 @@ export const DictVspApi = {
|
||||
responseType: "blob",
|
||||
});
|
||||
},
|
||||
getDropdownVsp(params){
|
||||
return api.get(`/dict/info/dropdown`, params).then((r) => r.data);
|
||||
}
|
||||
getDropdownVsp(params) {
|
||||
return api.get(`/dict/info/dropdown`, { params }).then((r) => r.data);
|
||||
},
|
||||
};
|
||||
|
||||
34
web/src/api/projects.js
Normal file
34
web/src/api/projects.js
Normal file
@ -0,0 +1,34 @@
|
||||
import api from "./client";
|
||||
|
||||
export const ProjectsApi = {
|
||||
list: (params) => {
|
||||
return api.get(`/projects`, { params }).then((r) => r.data);
|
||||
},
|
||||
|
||||
get: (id) => {
|
||||
return api.get(`/projects/${id}`).then((r) => r.data);
|
||||
},
|
||||
|
||||
create: (data) => {
|
||||
return api.post("/projects", data).then((r) => r.data);
|
||||
},
|
||||
|
||||
update: (id, data) => {
|
||||
return api.patch(`/projects/${id}`, data).then((r) => r.data);
|
||||
},
|
||||
|
||||
delete: (id) => {
|
||||
return api.delete(`/projects/${id}`).then((r) => r.data);
|
||||
},
|
||||
|
||||
getTableData: (id, sheetName, year) => {
|
||||
return api
|
||||
.get(`/projects/${id}/report/${year}/${sheetName}`)
|
||||
.then((r) => r.data);
|
||||
},
|
||||
export: (id, year, sheetName) => {
|
||||
return api.get(`/export/project/${id}/report/${year}/${sheetName}`, {
|
||||
responseType: "blob",
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -17,6 +17,7 @@ import { CircularProgress } from '@mui/material';
|
||||
import NewTablePage from '../pages/NewTablePage.jsx';
|
||||
import TablesTest from '../pages/TablesTest.jsx';
|
||||
import NewFormTablePage from '../pages/NewFormTablePage.jsx'
|
||||
import ProjectsPage from '../pages/ProjectsPage/ProjectsPage.jsx';
|
||||
|
||||
export const PrivateRoute = () => {
|
||||
const { isAuthenticated, loading } = useAuth();
|
||||
@ -37,8 +38,10 @@ export const AppRoutes = () => {
|
||||
<Route element={<PrivateRoute />}>
|
||||
<Route path="/" element={<TasksPage />} />
|
||||
<Route path="/tasks" element={<TasksPage />} />
|
||||
<Route path="/projects" element={<ProjectsPage />}/>
|
||||
<Route path="/forms" element={<FormsPage />} />
|
||||
<Route path="/task/:taskId" element={<TaskPage />} />
|
||||
<Route path="/project/:projectId" element={<TaskPage />} />
|
||||
<Route path="/table/:tableId" element={<TablePage />} />
|
||||
<Route path="/table-temp/:tableId" element={<TableTempPage />} />
|
||||
<Route path="/admin_panel/users" element={<UsersPage />} />
|
||||
@ -49,7 +52,7 @@ export const AppRoutes = () => {
|
||||
<Route path="/new-table" element={<NewTablePage />} />
|
||||
<Route path="/tables-new" element={<TablesTest />} />
|
||||
<Route path="/tables/:form/:sheetName" element={<NewFormTablePage />} />
|
||||
<Route path="/table/form/:formId/form-type/:formType/:sheetName/:direction" element={<NewFormTablePage />} />
|
||||
<Route path="/table/form/:formId/form-type/:formType/:sheetName/:direction/:year" element={<NewFormTablePage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@ -4,7 +4,7 @@ import { NavLink } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { TasksSvg, AdminPanelSvg, DictSvg } from '../common/icons/icons';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../constants';
|
||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
||||
|
||||
const Switch = styled.div`
|
||||
margin-left: 1rem;
|
||||
|
||||
@ -31,12 +31,13 @@ const LockBadge = styled.div`
|
||||
`;
|
||||
|
||||
// Функция для подсветки текста
|
||||
const highlightText = (text, searchQuery) => {
|
||||
if (!searchQuery || !text) return text;
|
||||
|
||||
const escapedQuery = searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(${escapedQuery})`, 'gi');
|
||||
const highlightText = (text, searchQueries) => {
|
||||
if (!text) return text;
|
||||
const cleanSearchQueries = searchQueries.filter(q => (q !== undefined && q !== ''));
|
||||
if (cleanSearchQueries.length == 0) return text;
|
||||
|
||||
const escapedQueries = cleanSearchQueries.map(query => query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||
const regex = new RegExp(`(${ escapedQueries.join('|')})`, 'gi');
|
||||
const parts = text.split(regex);
|
||||
|
||||
return parts.map((part, index) =>
|
||||
@ -89,7 +90,7 @@ const formatNumber = (value, asInteger = false) => {
|
||||
});
|
||||
};
|
||||
|
||||
const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundColor, color, isEditable }) => {
|
||||
const Cell = React.memo(({ cell, row, column, onClick, globalFilter, columnFilter, backgroundColor, color, isEditable }) => {
|
||||
const { lockedCells } = useRealtime();
|
||||
const cellKey = `${row.id}_${column.id}`;
|
||||
|
||||
@ -105,9 +106,10 @@ const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundC
|
||||
}
|
||||
|
||||
const highlightedContent = useMemo(() => {
|
||||
return highlightText(textValue, globalFilter);
|
||||
return highlightText(textValue, [globalFilter, columnFilter]);
|
||||
}, [textValue, globalFilter]);
|
||||
|
||||
|
||||
// Базовые стили для заблокированной ячейки
|
||||
const lockedStyles = isLocked ? {
|
||||
border: '2px solid #e0e0e0',
|
||||
|
||||
@ -29,8 +29,8 @@ import { additionVspRowTable } from './constants/addingRowConfig';
|
||||
import { SelectVspModal } from './Modals/SelectVspModal';
|
||||
import { getRowId } from './utils/rowUtils';
|
||||
|
||||
const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction);
|
||||
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
||||
const [globalFilter, setGlobalFilter] = useState('');
|
||||
const [showColumnFilters, setShowColumnFilters] = useState(false);
|
||||
const [selectedColumnId, setSelectedColumnId] = useState();
|
||||
@ -229,6 +229,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
||||
minHeight: '1px',
|
||||
maxHeight: '1px',
|
||||
visibility: 'hidden',
|
||||
'& svg': {
|
||||
height: '1px',
|
||||
minHeight: '1px',
|
||||
maxHeight: '1px',
|
||||
visibility: 'hidden',
|
||||
}
|
||||
},
|
||||
},
|
||||
muiTablePaperProps: getTablePaperStyles(),
|
||||
@ -360,6 +366,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
||||
formId={formId}
|
||||
sheetName={sheetName}
|
||||
direction={direction}
|
||||
year={year}
|
||||
isProject={formType == 'PROJECT'}
|
||||
/>
|
||||
|
||||
<div style={tableWrapperStyle}>
|
||||
@ -420,8 +428,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
||||
onClose={() => setIsOpenModalSelectVsp(false)}
|
||||
formId={formId}
|
||||
onSelect={handleAddVspRow}
|
||||
// vspOptions={}
|
||||
// selectedVSP={}
|
||||
title="Выбор ВСП"
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -18,7 +18,7 @@ import CollapsibleTree from '../../common/CollapsibleTree';
|
||||
import ZoomSlider from './ZoomSlider';
|
||||
import SearchComponent from '../../common/SearchComponent';
|
||||
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
||||
import { exportSheet } from '../../../utils/exportForm';
|
||||
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
||||
|
||||
const SettingPanel = ({
|
||||
selectedColumnId,
|
||||
@ -39,6 +39,8 @@ const SettingPanel = ({
|
||||
formId,
|
||||
sheetName,
|
||||
direction,
|
||||
year,
|
||||
isProject,
|
||||
}) => {
|
||||
const [isPinned, setIsPinned] = useState(false);
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
@ -76,7 +78,16 @@ const SettingPanel = ({
|
||||
const handleClickDeleteFormula = () => { };
|
||||
|
||||
const handleExport = async () => {
|
||||
if (!formId || !sheetName || isExporting) return;
|
||||
if (isExporting) return;
|
||||
if (isProject) {
|
||||
if (!formId || !sheetName || !year) return;
|
||||
setIsExporting(true);
|
||||
await exportSheetProject(formId, sheetName, year);
|
||||
setIsExporting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formId || !sheetName) return;
|
||||
setIsExporting(true);
|
||||
await exportSheet(formId, sheetName, direction);
|
||||
setIsExporting(false);
|
||||
@ -115,40 +126,27 @@ const SettingPanel = ({
|
||||
</Tooltip>
|
||||
</GroupByObject>
|
||||
|
||||
<Divider orientation="vertical" flexItem />
|
||||
|
||||
<GroupByObject title="Строки">
|
||||
<Tooltip title="Добавить строку">
|
||||
<IconButton onClick={onAddRow} variant="outlined">
|
||||
<Plus />
|
||||
<AddLine />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{!isProject ??
|
||||
<>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<GroupByObject title="Строки">
|
||||
<Tooltip title="Добавить строку">
|
||||
<IconButton onClick={onAddRow} variant="outlined">
|
||||
<Plus />
|
||||
<AddLine />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Удалить строку">
|
||||
<IconButton onClick={onDeleteRow} variant="outlined">
|
||||
<Minus />
|
||||
<RemoveLine />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</GroupByObject>
|
||||
|
||||
{/* <Divider orientation="vertical" flexItem />
|
||||
|
||||
<GroupByObject title="Формулы">
|
||||
<Tooltip title="Добавить формулу">
|
||||
<IconButton onClick={handleClickAddFormula} variant="outlined">
|
||||
<FormulaSettingPanel />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Удалить формулу">
|
||||
<IconButton onClick={handleClickDeleteFormula} variant="outlined">
|
||||
<Minus />
|
||||
<FormulaSettingPanel />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</GroupByObject> */}
|
||||
<Tooltip title="Удалить строку">
|
||||
<IconButton onClick={onDeleteRow} variant="outlined">
|
||||
<Minus />
|
||||
<RemoveLine />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</GroupByObject>
|
||||
</>
|
||||
}
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<GroupByObject title="Столбцы">
|
||||
{columns ? (
|
||||
|
||||
@ -313,10 +313,10 @@ const FilterCell = ({ column, table }) => {
|
||||
onChange={(e) => handleFilterChange(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '4px 6px',
|
||||
fontSize: '11px',
|
||||
padding: '0.25rem 0.375rem',
|
||||
fontSize: '0.6875rem',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
borderRadius: '0.25rem',
|
||||
backgroundColor: 'white',
|
||||
}}
|
||||
>
|
||||
@ -330,23 +330,30 @@ const FilterCell = ({ column, table }) => {
|
||||
);
|
||||
|
||||
case 'range': {
|
||||
// Для диапазона можно реализовать два поля
|
||||
const [min, max] = column.getFilterValue() || ['', ''];
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
<div style={{ display: 'flex', gap: '0.25rem' }}> {/* 4px */}
|
||||
<input
|
||||
type="number"
|
||||
placeholder="От"
|
||||
value={min}
|
||||
onChange={(e) => column.setFilterValue([e.target.value, max])}
|
||||
style={{ width: '50%', padding: '4px', fontSize: '11px' }}
|
||||
style={{
|
||||
width: '50%',
|
||||
padding: '0.25rem',
|
||||
fontSize: '0.6875rem'
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="До"
|
||||
value={max}
|
||||
onChange={(e) => column.setFilterValue([min, e.target.value])}
|
||||
style={{ width: '50%', padding: '4px', fontSize: '11px' }}
|
||||
style={{
|
||||
width: '50%',
|
||||
padding: '0.25rem',
|
||||
fontSize: '0.6875rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -361,10 +368,10 @@ const FilterCell = ({ column, table }) => {
|
||||
placeholder="Поиск..."
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '4px 6px',
|
||||
fontSize: '11px',
|
||||
padding: '0.25rem 0.375rem',
|
||||
fontSize: '0.6875rem',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
borderRadius: '0.25rem',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,555 +0,0 @@
|
||||
|
||||
import {
|
||||
greenColumn,
|
||||
whiteColumn,
|
||||
orangeColumn,
|
||||
yellowColumn,
|
||||
redColumn,
|
||||
blueColumn,
|
||||
} from '../columnColors';
|
||||
export const config = {
|
||||
"config":{
|
||||
"colors":{
|
||||
"kod_razdela_kod_razdela":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"kod_razdela_kod_razdela"
|
||||
},
|
||||
"id_stati_id_stati":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"id_stati_id_stati"
|
||||
},
|
||||
"id_gruppy_nomenklatury_id_gruppy_nomenklatury":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury"
|
||||
},
|
||||
"naimenovanie_naimenovanie":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"naimenovanie_naimenovanie"
|
||||
},
|
||||
"korrektirovka_limita_po_statyam_smety":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"korrektirovka_limita_po_statyam_smety"
|
||||
},
|
||||
"field_uvelichenie_smety":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_uvelichenie_smety"
|
||||
},
|
||||
"field_za_yanvar":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_yanvar"
|
||||
},
|
||||
"field_za_fevral":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_fevral"
|
||||
},
|
||||
"field_itogo_i_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_i_kvartal"
|
||||
},
|
||||
"field_za_aprel":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_aprel"
|
||||
},
|
||||
"field_za_may":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_may"
|
||||
},
|
||||
"field_itogo_ii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_ii_kvartal"
|
||||
},
|
||||
"field_za_iyul":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_iyul"
|
||||
},
|
||||
"field_za_avgust":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_avgust"
|
||||
},
|
||||
"field_itogo_iii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_iii_kvartal"
|
||||
},
|
||||
"field_za_oktyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_oktyabr"
|
||||
},
|
||||
"field_za_noyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_noyabr"
|
||||
},
|
||||
"field_spod":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_spod"
|
||||
},
|
||||
"field_itogo_iv_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_iv_kvartal"
|
||||
},
|
||||
"itogo_itogo":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"itogo_itogo"
|
||||
},
|
||||
"fakt_zamart":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zamart"
|
||||
},
|
||||
"fakt_zaiyun":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zaiyun"
|
||||
},
|
||||
"fakt_zasentyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zasentyabr"
|
||||
},
|
||||
"fakt_zadekabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zadekabr"
|
||||
},
|
||||
"ekonomiya_pereraskhod_ekonomiya_pereraskhod":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod"
|
||||
},
|
||||
"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal"
|
||||
},
|
||||
"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal"
|
||||
},
|
||||
"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal"
|
||||
}
|
||||
},
|
||||
"columns":[
|
||||
{
|
||||
"header":"Код раздела",
|
||||
"accessorKey":"kod_razdela",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Код раздела",
|
||||
"accessorKey":"kod_razdela_kod_razdela",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"ID статьи",
|
||||
"accessorKey":"id_stati",
|
||||
"columns":[
|
||||
{
|
||||
"header":"ID статьи",
|
||||
"accessorKey":"id_stati_id_stati",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"ID группы номенклатуры",
|
||||
"accessorKey":"id_gruppy_nomenklatury",
|
||||
"columns":[
|
||||
{
|
||||
"header":"ID группы номенклатуры",
|
||||
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Наименование",
|
||||
"accessorKey":"naimenovanie",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Наименование",
|
||||
"accessorKey":"naimenovanie_naimenovanie",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Корректировка лимита",
|
||||
"accessorKey":"korrektirovka_limita",
|
||||
"columns":[
|
||||
{
|
||||
"header":"по статьям сметы *",
|
||||
"accessorKey":"korrektirovka_limita_po_statyam_smety",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"",
|
||||
"accessorKey":"field",
|
||||
"columns":[
|
||||
{
|
||||
"header":"увеличение сметы **",
|
||||
"accessorKey":"field_uvelichenie_smety",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ЯНВАРЬ",
|
||||
"accessorKey":"field_za_yanvar",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ФЕВРАЛЬ",
|
||||
"accessorKey":"field_za_fevral",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого I квартал",
|
||||
"accessorKey":"field_itogo_i_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за АПРЕЛЬ",
|
||||
"accessorKey":"field_za_aprel",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за МАЙ",
|
||||
"accessorKey":"field_za_may",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого II квартал",
|
||||
"accessorKey":"field_itogo_ii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ИЮЛЬ",
|
||||
"accessorKey":"field_za_iyul",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за АВГУСТ",
|
||||
"accessorKey":"field_za_avgust",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого III квартал",
|
||||
"accessorKey":"field_itogo_iii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ОКТЯБРЬ",
|
||||
"accessorKey":"field_za_oktyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за НОЯБРЬ",
|
||||
"accessorKey":"field_za_noyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"СПОД",
|
||||
"accessorKey":"field_spod",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого IV квартал",
|
||||
"accessorKey":"field_itogo_iv_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого",
|
||||
"accessorKey":"itogo",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Итого",
|
||||
"accessorKey":"itogo_itogo",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Факт",
|
||||
"accessorKey":"fakt",
|
||||
"columns":[
|
||||
{
|
||||
"header":"заМАРТ",
|
||||
"accessorKey":"fakt_zamart",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заИЮНЬ",
|
||||
"accessorKey":"fakt_zaiyun",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заСЕНТЯБРЬ",
|
||||
"accessorKey":"fakt_zasentyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заДЕКАБРЬ",
|
||||
"accessorKey":"fakt_zadekabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"экономия (+) перерасход (-)",
|
||||
"accessorKey":"ekonomiya_pereraskhod",
|
||||
"columns":[
|
||||
{
|
||||
"header":"экономия (+) перерасход (-)",
|
||||
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,577 +0,0 @@
|
||||
|
||||
import {
|
||||
greenColumn,
|
||||
whiteColumn,
|
||||
orangeColumn,
|
||||
yellowColumn,
|
||||
redColumn,
|
||||
blueColumn,
|
||||
} from '../columnColors';
|
||||
export const config = {
|
||||
"config":{
|
||||
"colors":{
|
||||
"kod_razdela_kod_razdela":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"kod_razdela_kod_razdela"
|
||||
},
|
||||
"id_stati_id_stati":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"id_stati_id_stati"
|
||||
},
|
||||
"id_gruppy_nomenklatury_id_gruppy_nomenklatury":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury"
|
||||
},
|
||||
"naimenovanie_naimenovanie":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"naimenovanie_naimenovanie"
|
||||
},
|
||||
"korrektirovka_plana_po_statyam_smety":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"korrektirovka_plana_po_statyam_smety"
|
||||
},
|
||||
"field_uvelichenie_smety":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_uvelichenie_smety"
|
||||
},
|
||||
"field_za_yanvar":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_yanvar"
|
||||
},
|
||||
"field_za_fevral":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_fevral"
|
||||
},
|
||||
"field_itogo_i_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_i_kvartal"
|
||||
},
|
||||
"field_za_aprel":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_aprel"
|
||||
},
|
||||
"field_za_may":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_may"
|
||||
},
|
||||
"field_itogo_ii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_ii_kvartal"
|
||||
},
|
||||
"field_za_iyul":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_iyul"
|
||||
},
|
||||
"field_za_avgust":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_avgust"
|
||||
},
|
||||
"field_itogo_iii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_iii_kvartal"
|
||||
},
|
||||
"field_za_oktyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_oktyabr"
|
||||
},
|
||||
"field_za_noyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_noyabr"
|
||||
},
|
||||
"field_spod":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_spod"
|
||||
},
|
||||
"field_itogo_iv_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_iv_kvartal"
|
||||
},
|
||||
"itogo_korrektirovka_itogo_korrektirovka":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"itogo_korrektirovka_itogo_korrektirovka"
|
||||
},
|
||||
"fakt_zamart":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zamart"
|
||||
},
|
||||
"fakt_zaiyun":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zaiyun"
|
||||
},
|
||||
"fakt_zasentyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zasentyabr"
|
||||
},
|
||||
"fakt_zadekabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zadekabr"
|
||||
},
|
||||
"ekonomiya_pereraskhod_ekonomiya_pereraskhod":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod"
|
||||
},
|
||||
"smeta_raskhodov_po_razvitiyu_na_ii_kvartal_smeta_raskhodov_po_razvitiyu_na_ii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_ii_kvartal_smeta_raskhodov_po_razvitiyu_na_ii_kvartal"
|
||||
},
|
||||
"itogo_skorrek_smeta_itogo_skorrek_smeta":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"itogo_skorrek_smeta_itogo_skorrek_smeta"
|
||||
},
|
||||
"smeta_raskhodov_po_razvitiyu_na_iii_kvartal_smeta_raskhodov_po_razvitiyu_na_iii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iii_kvartal_smeta_raskhodov_po_razvitiyu_na_iii_kvartal"
|
||||
},
|
||||
"smeta_raskhodov_po_razvitiyu_na_iv_kvartal_smeta_raskhodov_po_razvitiyu_na_iv_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iv_kvartal_smeta_raskhodov_po_razvitiyu_na_iv_kvartal"
|
||||
}
|
||||
},
|
||||
"columns":[
|
||||
{
|
||||
"header":"Код раздела",
|
||||
"accessorKey":"kod_razdela",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Код раздела",
|
||||
"accessorKey":"kod_razdela_kod_razdela",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"ID статьи",
|
||||
"accessorKey":"id_stati",
|
||||
"columns":[
|
||||
{
|
||||
"header":"ID статьи",
|
||||
"accessorKey":"id_stati_id_stati",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"ID группы номенклатуры",
|
||||
"accessorKey":"id_gruppy_nomenklatury",
|
||||
"columns":[
|
||||
{
|
||||
"header":"ID группы номенклатуры",
|
||||
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Наименование",
|
||||
"accessorKey":"naimenovanie",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Наименование",
|
||||
"accessorKey":"naimenovanie_naimenovanie",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Корректировка плана",
|
||||
"accessorKey":"korrektirovka_plana",
|
||||
"columns":[
|
||||
{
|
||||
"header":"по статьям сметы *",
|
||||
"accessorKey":"korrektirovka_plana_po_statyam_smety",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"",
|
||||
"accessorKey":"field",
|
||||
"columns":[
|
||||
{
|
||||
"header":"увеличение сметы **",
|
||||
"accessorKey":"field_uvelichenie_smety",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ЯНВАРЬ",
|
||||
"accessorKey":"field_za_yanvar",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ФЕВРАЛЬ",
|
||||
"accessorKey":"field_za_fevral",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого I квартал",
|
||||
"accessorKey":"field_itogo_i_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за АПРЕЛЬ",
|
||||
"accessorKey":"field_za_aprel",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за МАЙ",
|
||||
"accessorKey":"field_za_may",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого II квартал",
|
||||
"accessorKey":"field_itogo_ii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ИЮЛЬ",
|
||||
"accessorKey":"field_za_iyul",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за АВГУСТ",
|
||||
"accessorKey":"field_za_avgust",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого III квартал",
|
||||
"accessorKey":"field_itogo_iii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ОКТЯБРЬ",
|
||||
"accessorKey":"field_za_oktyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за НОЯБРЬ",
|
||||
"accessorKey":"field_za_noyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"СПОД",
|
||||
"accessorKey":"field_spod",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого IV квартал",
|
||||
"accessorKey":"field_itogo_iv_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого корректировка",
|
||||
"accessorKey":"itogo_korrektirovka",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Итого корректировка",
|
||||
"accessorKey":"itogo_korrektirovka_itogo_korrektirovka",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Факт",
|
||||
"accessorKey":"fakt",
|
||||
"columns":[
|
||||
{
|
||||
"header":"заМАРТ",
|
||||
"accessorKey":"fakt_zamart",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заИЮНЬ",
|
||||
"accessorKey":"fakt_zaiyun",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заСЕНТЯБРЬ",
|
||||
"accessorKey":"fakt_zasentyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заДЕКАБРЬ",
|
||||
"accessorKey":"fakt_zadekabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"экономия (+) перерасход (-)",
|
||||
"accessorKey":"ekonomiya_pereraskhod",
|
||||
"columns":[
|
||||
{
|
||||
"header":"экономия (+) перерасход (-)",
|
||||
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Смета расходов по развитию на II квартал",
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_ii_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Смета расходов по развитию на II квартал",
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_ii_kvartal_smeta_raskhodov_po_razvitiyu_na_ii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого скоррек. смета",
|
||||
"accessorKey":"itogo_skorrek_smeta",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Итого скоррек. смета",
|
||||
"accessorKey":"itogo_skorrek_smeta_itogo_skorrek_smeta",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Смета расходов по развитию на III квартал",
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iii_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Смета расходов по развитию на III квартал",
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iii_kvartal_smeta_raskhodov_po_razvitiyu_na_iii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Смета расходов по развитию на IV квартал",
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iv_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Смета расходов по развитию на IV квартал",
|
||||
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iv_kvartal_smeta_raskhodov_po_razvitiyu_na_iv_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,555 +0,0 @@
|
||||
|
||||
import {
|
||||
greenColumn,
|
||||
whiteColumn,
|
||||
orangeColumn,
|
||||
yellowColumn,
|
||||
redColumn,
|
||||
blueColumn,
|
||||
} from '../columnColors';
|
||||
export const config = {
|
||||
"config":{
|
||||
"colors":{
|
||||
"kod_razdela_kod_razdela":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"kod_razdela_kod_razdela"
|
||||
},
|
||||
"id_stati_id_stati":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"id_stati_id_stati"
|
||||
},
|
||||
"id_gruppy_nomenklatury_id_gruppy_nomenklatury":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury"
|
||||
},
|
||||
"naimenovanie_naimenovanie":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"naimenovanie_naimenovanie"
|
||||
},
|
||||
"korrektirovka_limita_po_statyam_smety":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"korrektirovka_limita_po_statyam_smety"
|
||||
},
|
||||
"field_uvelichenie_smety":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_uvelichenie_smety"
|
||||
},
|
||||
"field_za_yanvar":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_yanvar"
|
||||
},
|
||||
"field_za_fevral":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_fevral"
|
||||
},
|
||||
"field_itogo_i_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_i_kvartal"
|
||||
},
|
||||
"field_za_aprel":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_aprel"
|
||||
},
|
||||
"field_za_may":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_may"
|
||||
},
|
||||
"field_itogo_ii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_ii_kvartal"
|
||||
},
|
||||
"field_za_iyul":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_iyul"
|
||||
},
|
||||
"field_za_avgust":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_avgust"
|
||||
},
|
||||
"field_itogo_iii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_iii_kvartal"
|
||||
},
|
||||
"field_za_oktyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_oktyabr"
|
||||
},
|
||||
"field_za_noyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_za_noyabr"
|
||||
},
|
||||
"field_spod":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_spod"
|
||||
},
|
||||
"field_itogo_iv_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"field_itogo_iv_kvartal"
|
||||
},
|
||||
"itogo_itogo":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"itogo_itogo"
|
||||
},
|
||||
"fakt_zamart":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zamart"
|
||||
},
|
||||
"fakt_zaiyun":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zaiyun"
|
||||
},
|
||||
"fakt_zasentyabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zasentyabr"
|
||||
},
|
||||
"fakt_zadekabr":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"fakt_zadekabr"
|
||||
},
|
||||
"ekonomiya_pereraskhod_ekonomiya_pereraskhod":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod"
|
||||
},
|
||||
"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal"
|
||||
},
|
||||
"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal"
|
||||
},
|
||||
"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal":{
|
||||
"color_type":whiteColumn,
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal"
|
||||
}
|
||||
},
|
||||
"columns":[
|
||||
{
|
||||
"header":"Код раздела",
|
||||
"accessorKey":"kod_razdela",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Код раздела",
|
||||
"accessorKey":"kod_razdela_kod_razdela",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"ID статьи",
|
||||
"accessorKey":"id_stati",
|
||||
"columns":[
|
||||
{
|
||||
"header":"ID статьи",
|
||||
"accessorKey":"id_stati_id_stati",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"ID группы номенклатуры",
|
||||
"accessorKey":"id_gruppy_nomenklatury",
|
||||
"columns":[
|
||||
{
|
||||
"header":"ID группы номенклатуры",
|
||||
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Наименование",
|
||||
"accessorKey":"naimenovanie",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Наименование",
|
||||
"accessorKey":"naimenovanie_naimenovanie",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Корректировка лимита",
|
||||
"accessorKey":"korrektirovka_limita",
|
||||
"columns":[
|
||||
{
|
||||
"header":"по статьям сметы *",
|
||||
"accessorKey":"korrektirovka_limita_po_statyam_smety",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"",
|
||||
"accessorKey":"field",
|
||||
"columns":[
|
||||
{
|
||||
"header":"увеличение сметы **",
|
||||
"accessorKey":"field_uvelichenie_smety",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ЯНВАРЬ",
|
||||
"accessorKey":"field_za_yanvar",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ФЕВРАЛЬ",
|
||||
"accessorKey":"field_za_fevral",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого I квартал",
|
||||
"accessorKey":"field_itogo_i_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за АПРЕЛЬ",
|
||||
"accessorKey":"field_za_aprel",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за МАЙ",
|
||||
"accessorKey":"field_za_may",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого II квартал",
|
||||
"accessorKey":"field_itogo_ii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ИЮЛЬ",
|
||||
"accessorKey":"field_za_iyul",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за АВГУСТ",
|
||||
"accessorKey":"field_za_avgust",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого III квартал",
|
||||
"accessorKey":"field_itogo_iii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за ОКТЯБРЬ",
|
||||
"accessorKey":"field_za_oktyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"за НОЯБРЬ",
|
||||
"accessorKey":"field_za_noyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"СПОД",
|
||||
"accessorKey":"field_spod",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого IV квартал",
|
||||
"accessorKey":"field_itogo_iv_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Итого",
|
||||
"accessorKey":"itogo",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Итого",
|
||||
"accessorKey":"itogo_itogo",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Факт",
|
||||
"accessorKey":"fakt",
|
||||
"columns":[
|
||||
{
|
||||
"header":"заМАРТ",
|
||||
"accessorKey":"fakt_zamart",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заИЮНЬ",
|
||||
"accessorKey":"fakt_zaiyun",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заСЕНТЯБРЬ",
|
||||
"accessorKey":"fakt_zasentyabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"заДЕКАБРЬ",
|
||||
"accessorKey":"fakt_zadekabr",
|
||||
"size":150,
|
||||
"filterFn":"contains",
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"экономия (+) перерасход (-)",
|
||||
"accessorKey":"ekonomiya_pereraskhod",
|
||||
"columns":[
|
||||
{
|
||||
"header":"экономия (+) перерасход (-)",
|
||||
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||
"columns":[
|
||||
{
|
||||
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||
"size":150,
|
||||
"filterFn":"contains"
|
||||
}
|
||||
],
|
||||
"muiTableHeadCellProps":{
|
||||
"sx":{
|
||||
"backgroundColor":"#C2D69B",
|
||||
"color":"#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1003
web/src/components/RealtimeTable/constants/PROJECT/LIMIT.js
Normal file
1003
web/src/components/RealtimeTable/constants/PROJECT/LIMIT.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,17 +25,29 @@ export const RealtimeProvider = ({
|
||||
formId,
|
||||
sheetName,
|
||||
direction,
|
||||
year,
|
||||
userId,
|
||||
isProject = false,
|
||||
}) => {
|
||||
// Формируем URL (кастомные секреты на фронте пока недоступны, делаем через REACT_APP_ROOT_PATH)
|
||||
const wsUrl = `${(process.env.REACT_APP_API_URL || process.env.REACT_APP_API_URL || process.env.REACT_APP_ROOT_PATH || "ws://localhost:8000").replace(/^http/, "ws")}/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`;
|
||||
const wsUrl =
|
||||
`${(
|
||||
process.env.REACT_APP_API_URL ||
|
||||
process.env.REACT_APP_API_URL ||
|
||||
process.env.REACT_APP_ROOT_PATH ||
|
||||
"ws://localhost:8000"
|
||||
).replace(/^http/, "ws")}` +
|
||||
(!isProject
|
||||
? `/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`
|
||||
: `/api/v1/ws/projects/${formId}/report/${year}/${sheetName}`);
|
||||
|
||||
//для ячеек которые редактируются другими пользователями
|
||||
const [lockedCells, setLockedCells] = useState([]);
|
||||
const handleMessage = useCallback((data) => {
|
||||
// Обработка ошибок
|
||||
if (data.error) {
|
||||
console.log(data.error.message)
|
||||
toast.error("Server error:" + data.error.message || '');
|
||||
console.log(data.error.message);
|
||||
toast.error("Server error:" + data.error.message || "");
|
||||
onErrorRef.current?.(data.error);
|
||||
return;
|
||||
}
|
||||
@ -81,7 +93,7 @@ export const RealtimeProvider = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { isConnected, sendMessage, disconnect, lastError } = useWebSocket(
|
||||
const { isConnectedRef, isConnected, sendMessage, disconnect, lastError } = useWebSocket(
|
||||
wsUrl,
|
||||
handleMessage,
|
||||
);
|
||||
@ -145,13 +157,26 @@ export const RealtimeProvider = ({
|
||||
}
|
||||
}, [isConnected]);
|
||||
|
||||
const addCommonField = (message) => {
|
||||
if (isProject) {
|
||||
message.report_type = sheetName;
|
||||
message.project_id = formId;
|
||||
message.year = year;
|
||||
} else {
|
||||
message.sheet = sheetName;
|
||||
message.form_id = formId;
|
||||
message.direction = direction;
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
// Функция для начала редактирования ячейки
|
||||
const startEditing = useCallback(
|
||||
async (row, column) => {
|
||||
const columnId = column.id;
|
||||
const rowId = row.id;
|
||||
|
||||
if (!isConnected) {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("WebSocket не подключен");
|
||||
return false;
|
||||
}
|
||||
@ -183,7 +208,7 @@ export const RealtimeProvider = ({
|
||||
const columnId = column.id;
|
||||
const rowId = row.id;
|
||||
|
||||
if (!isConnected) {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("WebSocket не подключен");
|
||||
return false;
|
||||
}
|
||||
@ -213,7 +238,7 @@ export const RealtimeProvider = ({
|
||||
async (row, column, value) => {
|
||||
const columnId = column.id;
|
||||
const rowId = row.id;
|
||||
if (!isConnected) {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("WebSocket не подключен");
|
||||
return false;
|
||||
}
|
||||
@ -241,7 +266,7 @@ export const RealtimeProvider = ({
|
||||
|
||||
const addRow = useCallback(
|
||||
async (rowData) => {
|
||||
if (!isConnected) {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("Нет подключения или авторизации");
|
||||
return false;
|
||||
}
|
||||
@ -249,10 +274,9 @@ export const RealtimeProvider = ({
|
||||
const message = {
|
||||
event: "row_added",
|
||||
data: rowData,
|
||||
sheet: sheetName,
|
||||
form_id: formId,
|
||||
direction: direction,
|
||||
};
|
||||
addCommonField(message);
|
||||
|
||||
return sendMessage(message);
|
||||
},
|
||||
[isConnected, sendMessage, sheetName, formId, direction],
|
||||
@ -260,7 +284,7 @@ export const RealtimeProvider = ({
|
||||
|
||||
const deleteRow = useCallback(
|
||||
async (rowId) => {
|
||||
if (!isConnected) {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("Нет подключения или авторизации");
|
||||
return false;
|
||||
}
|
||||
@ -268,10 +292,8 @@ export const RealtimeProvider = ({
|
||||
const message = {
|
||||
event: "row_deleted",
|
||||
data: { row_id: rowId },
|
||||
sheet: sheetName,
|
||||
form_id: formId,
|
||||
direction: direction,
|
||||
};
|
||||
addCommonField(message);
|
||||
|
||||
return sendMessage(message);
|
||||
},
|
||||
|
||||
@ -4,8 +4,9 @@ import { useRealtime } from "../contexts/RealtimeContext";
|
||||
import { deleteRow, insertCell, updateCells } from "../utils/cellUtils";
|
||||
import { toast } from "react-toastify";
|
||||
import { isVspNewRow, isVspRow } from "../utils/rowUtils";
|
||||
import { ProjectsApi } from "../../../api/projects";
|
||||
|
||||
const useRealtimeData = (formId, sheetName, direction) => {
|
||||
const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
const [data, setData] = useState([]);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
@ -77,7 +78,10 @@ const useRealtimeData = (formId, sheetName, direction) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
let res;
|
||||
if (direction !== null) {
|
||||
|
||||
if(formType == 'PROJECT'){
|
||||
res = await ProjectsApi.getTableData(formId, sheetName, year)
|
||||
} else if (direction !== null) {
|
||||
res = await FormsSheetApi.get(formId, sheetName, {
|
||||
params: { direction: direction },
|
||||
});
|
||||
|
||||
@ -107,30 +107,16 @@ export const useWebSocket = (url, onMessage) => {
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
const login = useCallback(
|
||||
(token) => {
|
||||
if (!token) {
|
||||
console.warn("No token provided for login");
|
||||
return false;
|
||||
}
|
||||
return sendMessage({
|
||||
event: "user_login",
|
||||
data: { token },
|
||||
});
|
||||
},
|
||||
[sendMessage],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
return () => disconnect();
|
||||
}, [connect, disconnect]);
|
||||
|
||||
return {
|
||||
isConnected: isConnectedRef,
|
||||
isConnectedRef,
|
||||
isConnected,
|
||||
error,
|
||||
sendMessage,
|
||||
login,
|
||||
disconnect,
|
||||
};
|
||||
};
|
||||
|
||||
@ -112,6 +112,7 @@ export const getTableColumns = ({
|
||||
globalFilter={
|
||||
table.getState().globalFilter
|
||||
}
|
||||
columnFilter={table.getState().columnFilters?.find(f => f.id === column.id)?.value}
|
||||
isEditable={row.original?.row_type === 'INPUT' || false}
|
||||
backgroundColor={columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type]}
|
||||
color={columnColors[column.id]?.color_type?.['COLOR']}
|
||||
|
||||
@ -25,7 +25,7 @@ import { StageStatusChip } from './StageStatusChip';
|
||||
import { formatDateForApi } from './utils';
|
||||
import CollapsibleTree from '../common/CollapsibleTree';
|
||||
import { collectLeafColumns } from '../RealtimeTable/utils/columnUtils';
|
||||
import { STAGE_ROLE_RUSSIAN_NAME } from '../../constants';
|
||||
import { STAGE_ROLE_RUSSIAN_NAME } from '../../constants/constants';
|
||||
|
||||
export const AddStagesModal = ({
|
||||
isOpen,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Box, Divider, Stack } from '@mui/material';
|
||||
import { UnLockedSvg } from '../common/icons/icons';
|
||||
import { ROLES_ID_RUSSIAN_NAME, EDIT_ACCESS_RUSSIAN } from '../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME, EDIT_ACCESS_RUSSIAN } from '../../constants/constants';
|
||||
|
||||
export const StageInfoForTooltip = ({ stageData = [] }) => {
|
||||
const accessData = stageData[0] || [];
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
import { getStageStatus, getStatusColors } from './utils';
|
||||
import { formatDate } from '../../../utils/formatDate';
|
||||
import { StageStatusChip } from '../StageStatusChip';
|
||||
import { ROLES_NAME_ID, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../constants';
|
||||
import { ROLES_NAME_ID, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
|
||||
export const StagesTable = ({ stages, onEdit, onDelete }) => {
|
||||
|
||||
33
web/src/components/common/NavigationToggle.jsx
Normal file
33
web/src/components/common/NavigationToggle.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
// components/NavigationToggle.jsx
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { PrimaryOutlinedButton } from './Buttons/Buttons'; // Ваш компонент кнопки
|
||||
|
||||
const NavigationToggle = ({ currentView, onViewChange }) => {
|
||||
const handleNavigateToTasks = () => {
|
||||
onViewChange('/tasks');
|
||||
};
|
||||
|
||||
const handleNavigateToProjects = () => {
|
||||
onViewChange('/projects');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
{currentView === 'tasks' ? (
|
||||
<PrimaryOutlinedButton onClick={handleNavigateToProjects}>
|
||||
← К проектам
|
||||
</PrimaryOutlinedButton>
|
||||
) : (
|
||||
<PrimaryOutlinedButton onClick={handleNavigateToTasks}>
|
||||
← К задачам
|
||||
</PrimaryOutlinedButton>
|
||||
)}
|
||||
<Typography variant="h6" color="primary" sx={{ fontWeight: 200, fontSize: '1rem' }}>
|
||||
{currentView === 'tasks' ? 'Управление задачами' : 'Управление проектами'}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavigationToggle;
|
||||
@ -1,6 +1,6 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { Box, Paper, Typography } from '@mui/material';
|
||||
import { CustomCheckbox } from '../../components/common/StyledCheckbox';
|
||||
import { CustomCheckbox } from '../StyledCheckbox';
|
||||
|
||||
export const OrgUnitsAutocompletePaper = forwardRef(
|
||||
function OrgUnitsAutocompletePaper(
|
||||
257
web/src/components/common/PaginatedList.jsx
Normal file
257
web/src/components/common/PaginatedList.jsx
Normal file
@ -0,0 +1,257 @@
|
||||
// src/components/common/PaginatedList/PaginatedList.jsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
Select,
|
||||
MenuItem,
|
||||
Typography,
|
||||
Stack,
|
||||
} from '@mui/material';
|
||||
import { CardsSkeleton } from './Skeleton';
|
||||
import {
|
||||
ExportExitButton,
|
||||
ExportWithTextButton,
|
||||
} from './Buttons/ButtonsActions';
|
||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
|
||||
|
||||
/**
|
||||
* Универсальный компонент для отображения списка с пагинацией и экспортом
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Array} props.items - Массив элементов для отображения
|
||||
* @param {boolean} props.loading - Состояние загрузки
|
||||
* @param {number} props.totalCount - Общее количество элементов
|
||||
* @param {number} props.page - Текущая страница
|
||||
* @param {number} props.limit - Количество элементов на странице
|
||||
* @param {Function} props.onPageChange - Обработчик изменения страницы
|
||||
* @param {Function} props.onLimitChange - Обработчик изменения лимита
|
||||
* @param {Function} props.renderItem - Функция рендеринга элемента
|
||||
* @param {Function} props.getItemId - Функция получения ID элемента
|
||||
* @param {Function} props.getItemTitle - Функция получения заголовка элемента
|
||||
* @param {string} props.entityName - Название сущности (для текстов)
|
||||
* @param {string} props.emptyMessage - Сообщение при пустом списке
|
||||
* @param {number} props.skeletonCount - Количество силуэтов таблиц при загрузки
|
||||
* @param {Object} props.extraActions - Дополнительные действия справа
|
||||
* @param {boolean} props.enableExport - Включить экспорт
|
||||
* @param {Array} props.limitOptions - Опции для выбора лимита
|
||||
* @param {number} props.maxHeight - Максимальная высота контейнера со скроллом
|
||||
* @param {boolean} props.enableScroll - Включить скролл для списка
|
||||
* @param {boolean} props.filterActions - Элементы фильтрации
|
||||
*/
|
||||
export function PaginatedList({
|
||||
items = [],
|
||||
loading = false,
|
||||
totalCount = 0,
|
||||
page = 1,
|
||||
limit = 20,
|
||||
onPageChange,
|
||||
onLimitChange,
|
||||
renderItem,
|
||||
getItemId,
|
||||
getItemTitle = (item) => `Элемент ${getItemId(item)}`,
|
||||
entityName = 'элементов',
|
||||
emptyMessage = 'Элементы не найдены',
|
||||
skeletonCount = 20,
|
||||
extraActions = null,
|
||||
enableExport = true,
|
||||
limitOptions = [10, 20, 50, 100, 200],
|
||||
maxHeight = 'calc(100vh - 300px)',
|
||||
enableScroll = true,
|
||||
filterActions = null,
|
||||
}) {
|
||||
const [exportMode, setExportMode] = useState(false);
|
||||
const [exportList, setExportList] = useState([]);
|
||||
|
||||
// Сброс списка экспорта при переключении режима
|
||||
useEffect(() => {
|
||||
setExportList([]);
|
||||
}, [exportMode]);
|
||||
|
||||
const handleAddToExport = (itemId, checked) => {
|
||||
if (checked) {
|
||||
setExportList((prev) => [...prev, itemId]);
|
||||
} else {
|
||||
setExportList((prev) => prev.filter((id) => id !== itemId));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkExport = async () => {
|
||||
await exportBulkForms(exportList);
|
||||
setExportMode(false);
|
||||
};
|
||||
|
||||
const handleSingleExport = (itemId) => {
|
||||
const item = items.find((item) => getItemId(item) === itemId);
|
||||
const title = item ? getItemTitle(item) : `Элемент ${itemId}`;
|
||||
exportSingleForm(itemId, title);
|
||||
};
|
||||
|
||||
// Если включен экспорт, передаем его пропсы в дочерние элементы
|
||||
const renderItemWithExport = (item) => {
|
||||
if (enableExport) {
|
||||
return renderItem(item, {
|
||||
exportMode,
|
||||
onAddForExport: (checked) => {
|
||||
handleAddToExport(getItemId(item), checked);
|
||||
},
|
||||
onExportClick: () => {
|
||||
handleSingleExport(getItemId(item));
|
||||
},
|
||||
isSelected: exportList.includes(getItemId(item)),
|
||||
});
|
||||
}
|
||||
return renderItem(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }}>
|
||||
{/* Верхняя панель с экспортом и дополнительными действиями */}
|
||||
{enableExport && (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
{filterActions}
|
||||
</div>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: '1.5rem' }}>
|
||||
{exportMode && (
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Выделено {entityName}: {exportList.length}
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ flex: 1 }} />
|
||||
<Stack direction="row" spacing="0.5rem">
|
||||
{!exportMode ? (
|
||||
<>
|
||||
<ExportWithTextButton
|
||||
text="Пакетный экспорт"
|
||||
onClick={() => setExportMode(true)}
|
||||
height='2.5rem'
|
||||
/>
|
||||
{extraActions}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ExportWithTextButton
|
||||
text="Экспортировать"
|
||||
onClick={handleBulkExport}
|
||||
disabled={exportList.length === 0}
|
||||
height='2.5rem'
|
||||
/>
|
||||
<ExportExitButton onClick={() => setExportMode(false)} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
)}
|
||||
|
||||
{/* Контейнер со скроллом для списка */}
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
overflow: enableScroll ? 'auto' : 'visible',
|
||||
maxHeight: enableScroll ? maxHeight : 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '8px',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
background: '#f1f1f1',
|
||||
borderRadius: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
background: '#888',
|
||||
borderRadius: '4px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
background: '#555',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Список элементов */}
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
|
||||
{loading ? (
|
||||
<CardsSkeleton count={skeletonCount} />
|
||||
) : items.length === 0 ? (
|
||||
<Box sx={{ textAlign: 'center', py: 4, width: '100%' }}>
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
{emptyMessage}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
items.map((item) => renderItemWithExport(item))
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Пагинация */}
|
||||
{!loading && items.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
pt: 2,
|
||||
pb: 2,
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
flexShrink: 0,
|
||||
mt: 2,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Показать на странице:
|
||||
</Typography>
|
||||
<Select
|
||||
value={limit}
|
||||
onChange={onLimitChange}
|
||||
size="small"
|
||||
sx={{ minWidth: 70 }}
|
||||
disabled={loading}
|
||||
>
|
||||
{limitOptions.map((option) => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Всего: {totalCount}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Pagination
|
||||
count={Math.ceil(totalCount / limit)}
|
||||
page={page}
|
||||
onChange={onPageChange}
|
||||
color="primary"
|
||||
size="large"
|
||||
showFirstButton
|
||||
showLastButton
|
||||
disabled={loading}
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
{...item}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.dark',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Box sx={{ width: 120 }} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { EDIT_ACCESS_RUSSIAN, ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||
import { EDIT_ACCESS_RUSSIAN, ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import {
|
||||
SelectWrapper,
|
||||
SelectContainer,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { Switch, Buttons, StyledNavLink } from './SwitchFormTask.style';
|
||||
|
||||
export function SwitchFormTask() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_OPTIONS } from '../../../constants';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
||||
import {
|
||||
Section,
|
||||
SectionStartPart,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TableCard from '../TableCard/TableCard';
|
||||
@ -6,10 +6,10 @@ import { toast } from 'react-toastify';
|
||||
|
||||
const TablesList = ({
|
||||
filteredTables,
|
||||
projects,
|
||||
query,
|
||||
basePath = '/table',
|
||||
formInfo
|
||||
formInfo,
|
||||
isProject = false,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -22,7 +22,7 @@ const TablesList = ({
|
||||
if (!formInfo){
|
||||
return;
|
||||
}
|
||||
const path = `${basePath}/form/${formInfo.id}/form-type/${formInfo.form_type_code}/${table.sheet}/${table?.direction}`;
|
||||
const path = `${basePath}/form/${formInfo.id}/form-type/${isProject ? 'PROJECT' : formInfo.form_type_code}/${table.sheet}/${table?.direction}/${table?.year}`;
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
|
||||
@ -26,7 +26,7 @@ import { formatDate } from '../../utils/formatDate';
|
||||
import { IconWithContent } from './IconWithContent';
|
||||
import { Chip } from '../styles/StyledChip';
|
||||
import { ExportButton } from '../common/Buttons/ButtonsActions';
|
||||
import { FORM_TYPE_TRANSLATE } from '../../constants';
|
||||
import { FORM_TYPE_TRANSLATE } from '../../constants/constants';
|
||||
|
||||
const TaskCard = styled.div`
|
||||
width: 26.8rem;
|
||||
|
||||
90
web/src/constants/projectConfig.jsx
Normal file
90
web/src/constants/projectConfig.jsx
Normal file
@ -0,0 +1,90 @@
|
||||
export const DEFAULT_PROJECT_CONFIG = {
|
||||
'ССП/РФ': {
|
||||
type: 'multiselect',
|
||||
options: [],
|
||||
placeholder: 'Выберите ССП/РФ',
|
||||
defaultValue: null,
|
||||
required_field: true,
|
||||
},
|
||||
'Год': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите год',
|
||||
defaultValue: null,
|
||||
min: 0,
|
||||
step: 1,
|
||||
required_field: true,
|
||||
},
|
||||
'Тип проекта развития': {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
'Открытие ВСП',
|
||||
'Закрытие ВСП',
|
||||
'Переезд ВСП',
|
||||
'Реновация РФ',
|
||||
'Открытие УРМ',
|
||||
],
|
||||
placeholder: 'Выберите тип проекта',
|
||||
defaultValue: null,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Название проекта': {
|
||||
type: 'text',
|
||||
placeholder: 'Введите название проекта',
|
||||
defaultValue: null,
|
||||
required_field: true,
|
||||
},
|
||||
'Формат ВСП': {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
'Фланговый',
|
||||
'Типовой',
|
||||
'Розничный',
|
||||
'МСБ',
|
||||
'Лёгкий',
|
||||
'Мини',
|
||||
'Розничный-киоск',
|
||||
'МБО',
|
||||
'Офис самообслуживания',
|
||||
'Другое',
|
||||
],
|
||||
placeholder: 'Выберите формат ВСП',
|
||||
defaultValue: null,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Адрес объекта': {
|
||||
type: 'text',
|
||||
placeholder: 'Введите адрес объекта',
|
||||
defaultValue: '',
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Целевая численность, чел.': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите число',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
step: 1,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Тип размещения': {
|
||||
type: 'multiselect',
|
||||
options: ['Собственность', 'Аренда', 'Субаренда'],
|
||||
placeholder: 'Выберите тип размещения',
|
||||
defaultValue: null,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Площадь размещения, м2': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите число',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
step: 1,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch.jsx';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { Box } from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Box, Typography, Link, Chip } from '@mui/material';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../../constants/constants';
|
||||
import {
|
||||
ENTITY_RUSSIAN_NAMES,
|
||||
getRussianNamesForType,
|
||||
|
||||
@ -82,15 +82,15 @@ export const EVENT_CONFIGS = {
|
||||
},
|
||||
ACCESS_EXTEND: {
|
||||
form_id: 'Информация о задаче',
|
||||
closes_at_after: 'Дата закрытия',
|
||||
closes_at_before: 'Дата открытия',
|
||||
closes_at_after: 'Дата закрытия (новая)',
|
||||
closes_at_before: 'Дата закрытия (старая)',
|
||||
phase_name: 'Названия этапа',
|
||||
sheet: "Лист",
|
||||
},
|
||||
ACCESS_WINDOW_CHANGE: {
|
||||
form_id: 'Информация о задаче',
|
||||
closes_at: 'Дата закрытия',
|
||||
open_at: 'Дата открытия',
|
||||
opens_at: 'Дата открытия',
|
||||
phase_name: 'Названия этапа',
|
||||
role: 'Для роли',
|
||||
column_keys: 'Колонки в этапе',
|
||||
|
||||
@ -4,7 +4,7 @@ import { FormsApi } from '../../../../api/forms';
|
||||
import { SspApi } from '../../../../api/ssp';
|
||||
import { TasksApi } from '../../../../api/tasks';
|
||||
import { UsersApi } from '../../../../api/users';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants/constants';
|
||||
|
||||
export const VALUE_TRANSFORMERS = {
|
||||
Роль: transformRole,
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import {
|
||||
Box,
|
||||
|
||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import Modal from '../../../components/common/Modal/Modal';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import SelectWithOptions from '../components/SelectWithOptions';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions';
|
||||
|
||||
export const ModalEditUserSsp = ({
|
||||
|
||||
@ -15,7 +15,7 @@ import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import {
|
||||
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
||||
ROLES_ID_RUSSIAN_NAME,
|
||||
} from '../../../../constants';
|
||||
} from '../../../../constants/constants';
|
||||
import SelectWithOptions from '../../components/SelectWithOptions';
|
||||
import useAbbreviation from './hooks/useAbbreviation';
|
||||
import {
|
||||
|
||||
@ -5,12 +5,12 @@ import {
|
||||
CheckedIcon,
|
||||
IndeterminateIcon,
|
||||
} from '../../IconsForTable/icons';
|
||||
import { ROLES_NAME_ID } from '../../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
||||
import { createFilterData } from '../../utils/filters';
|
||||
import {
|
||||
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
||||
ROLES_ID_NAME,
|
||||
} from '../../../../constants';
|
||||
} from '../../../../constants/constants';
|
||||
import SelectWithOptions from '../../components/SelectWithOptions';
|
||||
import RowActionsMenu from './RowActionMenu';
|
||||
import DepartmentCell from './DepartmentCell';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { CheckIcon, CheckedIcon } from '../IconsForTable/icons';
|
||||
import { TrashSvg } from '../../../components/common/icons/icons';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
|
||||
const StyleForChecked = {
|
||||
display: 'flex',
|
||||
|
||||
@ -6,7 +6,7 @@ import { Button, Stack } from '@mui/material';
|
||||
import Select from '@mui/material/Select';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import { UserIcon, TrashSvg } from '../../../components/common/icons/icons';
|
||||
import {
|
||||
DangerOutlinedButton,
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_ID_RUSSIAN_NAME_ARRAY, ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME_ARRAY, ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { Box, TextField, Autocomplete, FormLabel } from '@mui/material';
|
||||
import SearchComponent from '../../../components/common/SearchComponent';
|
||||
@ -10,7 +10,7 @@ import { toast } from 'react-toastify';
|
||||
import { SspApi } from '../../../api/ssp';
|
||||
import { UsersApi } from '../../../api/users';
|
||||
import UserTable from './UserTable/UserTable';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import { ModalEditUserSsp } from './ModalEditUserSsp';
|
||||
import { ModalAddUsers } from './ModalAddUsers';
|
||||
import UsersActionBar from './UsersActionBar';
|
||||
|
||||
@ -13,7 +13,7 @@ import TableDictVsp from './components/TableDictVsp';
|
||||
import { PageContainer } from '../StyledForPage';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../constants';
|
||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
||||
import DictVspFilters from './components/DictVspFilters';
|
||||
|
||||
const DictVspInfo = () => {
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
PrimaryButton,
|
||||
} from '../../../../components/common/Buttons/Buttons';
|
||||
import { useAuth } from '../../../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
||||
import {
|
||||
ModalContainer,
|
||||
FieldContainer,
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
PrimaryOutlinedButton,
|
||||
} from '../../../components/common/Buttons/Buttons';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import ModalExport from './Modals/ModalExport';
|
||||
|
||||
const TableDictVsp = ({
|
||||
|
||||
@ -8,12 +8,13 @@ import { useParams } from 'react-router-dom';
|
||||
import { RealtimeProvider } from '../components/RealtimeTable/contexts/RealtimeContext';
|
||||
import { useAuth } from '../app/context/AuthProvider';
|
||||
import { BackButton } from '../components/common/Buttons/BackButton';
|
||||
import { SHEET_NAME } from '../constants';
|
||||
import { SHEET_NAME } from '../constants/constants';
|
||||
|
||||
const TableInfo = ({ formId, sheetName }) => {
|
||||
const TableInfo = ({ formId, sheetName, isProject = false }) => {
|
||||
const path = (isProject ? `/project/` : '/task/') + formId;
|
||||
const portalContent = (
|
||||
<TaskInfoContainer>
|
||||
<BackButton to={`/task/${formId}`} />
|
||||
<BackButton to={path} />
|
||||
{sheetName && (
|
||||
<NameTask>{SHEET_NAME[sheetName] || sheetName}</NameTask>
|
||||
)}
|
||||
@ -28,20 +29,29 @@ const TableInfo = ({ formId, sheetName }) => {
|
||||
};
|
||||
|
||||
export default function NewTablePage() {
|
||||
const { formId, formType, sheetName, direction } = useParams();
|
||||
const { formId, formType, sheetName, direction, year } = useParams();
|
||||
const { user } = useAuth();
|
||||
const isProject = formType == 'PROJECT';
|
||||
|
||||
return (
|
||||
<>
|
||||
{formId && <TableInfo formId={formId} sheetName={sheetName} />}
|
||||
{formId && <TableInfo formId={formId} sheetName={sheetName} isProject={isProject} />}
|
||||
{user && (
|
||||
<RealtimeProvider
|
||||
formId={formId}
|
||||
sheetName={sheetName}
|
||||
userId={user.id}
|
||||
direction={direction === 'null' ? null : direction}
|
||||
isProject={isProject}
|
||||
year={year}
|
||||
>
|
||||
<RealtimeTable formType={formType} formId={formId} sheetName={sheetName} direction={direction === 'null' ? null : direction} />
|
||||
<RealtimeTable
|
||||
formType={formType}
|
||||
formId={formId}
|
||||
sheetName={sheetName}
|
||||
direction={direction === 'null' ? null : direction}
|
||||
year={year}
|
||||
/>
|
||||
</RealtimeProvider>
|
||||
)}
|
||||
|
||||
|
||||
307
web/src/pages/ProjectsPage/ModalCreateProject.jsx
Normal file
307
web/src/pages/ProjectsPage/ModalCreateProject.jsx
Normal file
@ -0,0 +1,307 @@
|
||||
// src/components/common/ProjectCardComponent/ModalCreateProject.jsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Box, Button, Divider, TextField, Typography } from '@mui/material';
|
||||
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
|
||||
import Modal from '../../components/common/Modal/Modal';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
|
||||
export const ModalCreateProject = ({
|
||||
open,
|
||||
onClose,
|
||||
onCreate,
|
||||
config = {},
|
||||
initialData = null,
|
||||
isEdit = false,
|
||||
projectId = null,
|
||||
}) => {
|
||||
const [projectData, setProjectData] = useState(() => {
|
||||
// Если есть initialData, используем её, иначе создаем из конфига
|
||||
if (initialData) {
|
||||
return { ...initialData };
|
||||
}
|
||||
const initial = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
initial[key] = config[key].defaultValue;
|
||||
});
|
||||
return initial;
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
// Обновляем данные при изменении initialData
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setProjectData({ ...initialData });
|
||||
} else {
|
||||
const initial = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
initial[key] = config[key].defaultValue;
|
||||
});
|
||||
setProjectData(initial);
|
||||
}
|
||||
setErrors({});
|
||||
}, [initialData, config]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
setProjectData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
// Очищаем ошибку для поля при изменении
|
||||
if (errors[field]) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[field]: undefined,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const validateField = (fieldName, config) => {
|
||||
if (config.required_field) {
|
||||
const value = projectData[fieldName];
|
||||
|
||||
// Проверка для разных типов полей
|
||||
if (config.type === 'multiselect') {
|
||||
if (!value || (Array.isArray(value) && value.length === 0) || value === '') {
|
||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
||||
}
|
||||
} else if (config.type === 'text') {
|
||||
if (!value || value.trim() === '') {
|
||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
||||
}
|
||||
} else if (config.type === 'number') {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
||||
}
|
||||
// Проверка на минимальное значение
|
||||
if (config.min !== undefined && Number(value) < config.min) {
|
||||
return `Значение должно быть не меньше ${config.min}`;
|
||||
}
|
||||
// Проверка на максимальное значение
|
||||
if (config.max !== undefined && Number(value) > config.max) {
|
||||
return `Значение должно быть не больше ${config.max}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const validateAllFields = () => {
|
||||
const newErrors = {};
|
||||
let hasErrors = false;
|
||||
|
||||
Object.entries(config).forEach(([fieldName, fieldConfig]) => {
|
||||
const error = validateField(fieldName, fieldConfig);
|
||||
if (error) {
|
||||
newErrors[fieldName] = error;
|
||||
hasErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
setErrors(newErrors);
|
||||
return !hasErrors;
|
||||
};
|
||||
|
||||
const handleClickCreate = () => {
|
||||
if (validateAllFields()) {
|
||||
onCreate(projectData);
|
||||
// Не закрываем модалку здесь, пусть это делает родительский компонент
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const resetData = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
resetData[key] = config[key].defaultValue;
|
||||
});
|
||||
setProjectData(resetData);
|
||||
setErrors({});
|
||||
onClose();
|
||||
};
|
||||
|
||||
const renderField = (fieldName, config) => {
|
||||
const hasError = !!errors[fieldName];
|
||||
const errorText = errors[fieldName];
|
||||
|
||||
switch (config.type) {
|
||||
case 'multiselect':
|
||||
return (
|
||||
<Box key={fieldName}>
|
||||
<Typography
|
||||
className="gray-color"
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
paddingBottom: '.25rem',
|
||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
{config.required_field && (
|
||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
||||
)}
|
||||
</Typography>
|
||||
<SelectWithOptions
|
||||
className="sameSize"
|
||||
value={projectData[fieldName] || ''}
|
||||
options={config.options}
|
||||
placeholder={config.placeholder}
|
||||
onChange={(value) => handleChange(fieldName, value)}
|
||||
width="28.25rem"
|
||||
style={{
|
||||
borderColor: hasError ? 'var(--error-color, #d32f2f)' : undefined,
|
||||
borderWidth: hasError ? '2px' : undefined,
|
||||
}}
|
||||
/>
|
||||
{hasError && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: 'var(--error-color, #d32f2f)',
|
||||
fontSize: '0.75rem',
|
||||
marginTop: '4px',
|
||||
}}
|
||||
>
|
||||
{errorText}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
case 'text':
|
||||
return (
|
||||
<Box key={fieldName}>
|
||||
<Typography
|
||||
className="gray-color"
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
{config.required_field && (
|
||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
||||
)}
|
||||
</Typography>
|
||||
<TextField
|
||||
className="sameSize"
|
||||
value={projectData[fieldName] || ''}
|
||||
onChange={(e) => handleChange(fieldName, e.target.value)}
|
||||
placeholder={config.placeholder}
|
||||
size="small"
|
||||
error={hasError}
|
||||
helperText={hasError ? errorText : ''}
|
||||
FormHelperTextProps={{
|
||||
sx: {
|
||||
marginLeft: 0,
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
case 'number':
|
||||
return (
|
||||
<Box key={fieldName}>
|
||||
<Typography
|
||||
className="gray-color"
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
{config.required_field && (
|
||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
||||
)}
|
||||
</Typography>
|
||||
<TextField
|
||||
className="sameSize"
|
||||
type="text"
|
||||
value={projectData[fieldName] || ''}
|
||||
onChange={(e) => {
|
||||
const inputValue = e.target.value;
|
||||
//только числа
|
||||
const isValidInput =
|
||||
/^\d*\.?\d*$/.test(inputValue) || inputValue === '';
|
||||
if (isValidInput) {
|
||||
handleChange(fieldName, inputValue);
|
||||
}
|
||||
}}
|
||||
placeholder={config.placeholder}
|
||||
size="small"
|
||||
error={hasError}
|
||||
helperText={hasError ? errorText : ''}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: config.min,
|
||||
max: config.max || null,
|
||||
step: config.step || 1,
|
||||
},
|
||||
}}
|
||||
FormHelperTextProps={{
|
||||
sx: {
|
||||
marginLeft: 0,
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Редактирование проекта' : 'Создание проекта'}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
customSize={'43.75'}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="grey"
|
||||
onClick={handleClose}
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
Отменить
|
||||
</Button>
|
||||
<PrimaryButton
|
||||
variant="contained"
|
||||
onClick={handleClickCreate}
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
{isEdit ? 'Сохранить' : 'Создать'}
|
||||
</PrimaryButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Divider />
|
||||
<Box
|
||||
sx={{
|
||||
m: '1.25rem 0',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1.25rem',
|
||||
'& .sameSize': {
|
||||
height: '2.69rem',
|
||||
minWidth: '39.75rem',
|
||||
},
|
||||
'& .gray-color': { color: '#4A5565' },
|
||||
}}
|
||||
>
|
||||
{Object.entries(config).map(([fieldName, config]) =>
|
||||
renderField(fieldName, config),
|
||||
)}
|
||||
</Box>
|
||||
<Divider />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
349
web/src/pages/ProjectsPage/ProjectCard.jsx
Normal file
349
web/src/pages/ProjectsPage/ProjectCard.jsx
Normal file
@ -0,0 +1,349 @@
|
||||
// src/components/common/ProjectCardComponent/ProjectCardComponent.jsx
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
Button,
|
||||
TextField,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Box,
|
||||
Stack,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import { ProjectsApi } from '../../api/projects';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
CheckBoxCheckSvg,
|
||||
CheckBoxUncheckSvg,
|
||||
DateIcon,
|
||||
Edit,
|
||||
ExportSvg,
|
||||
UserIcon,
|
||||
BuildingIcon,
|
||||
LocationIcon,
|
||||
} from '../../components/common/icons/icons';
|
||||
import { IconWithContent } from '../../components/common/IconWithContent';
|
||||
import { ExportButton } from '../../components/common/Buttons/ButtonsActions';
|
||||
import { ModalCreateProject } from './ModalCreateProject';
|
||||
|
||||
const ProjectCard = styled.div`
|
||||
width: 26.8rem;
|
||||
height: 15rem;
|
||||
display: flex;
|
||||
padding: 1.25rem;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
border: 0.06rem solid var(--Stroke, rgba(0, 0, 0, 0.1));
|
||||
border-radius: 1rem;
|
||||
box-shadow:
|
||||
0rem 2rem 4rem -2rem rgba(0, 0, 0, 0.02),
|
||||
0rem 4rem 6rem -1rem rgba(0, 0, 0, 0.02);
|
||||
background: var(--White, rgba(255, 255, 255, 1));
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
`;
|
||||
|
||||
const CardHeader = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ProjectId = styled.div`
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
line-height: 1rem;
|
||||
letter-spacing: 0rem;
|
||||
text-align: left;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
`;
|
||||
|
||||
const CardBody = styled.div`
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8125rem;
|
||||
justify-content: flex-end;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ProjectTitle = styled.div`
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--Text-Primary, #333);
|
||||
line-height: 1.63;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const StatusChip = styled(Chip)`
|
||||
font-size: 0.7rem;
|
||||
height: 1.5rem;
|
||||
background-color: ${({ status }) => {
|
||||
switch (status) {
|
||||
case 'active': return '#4caf50';
|
||||
case 'completed': return '#2196f3';
|
||||
case 'paused': return '#ff9800';
|
||||
case 'archived': return '#9e9e9e';
|
||||
default: return '#9e9e9e';
|
||||
}
|
||||
}};
|
||||
color: white;
|
||||
`;
|
||||
|
||||
const LevelChip = styled(Chip)`
|
||||
font-size: 0.6rem;
|
||||
height: 1.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
`;
|
||||
|
||||
const ProjectInfoGrid = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
`;
|
||||
|
||||
const InfoChip = styled(Chip)`
|
||||
font-size: 0.6rem;
|
||||
height: 1.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
`;
|
||||
|
||||
const getStatusLabel = (status) => {
|
||||
const statusMap = {
|
||||
active: 'Активный',
|
||||
completed: 'Завершен',
|
||||
paused: 'Приостановлен',
|
||||
archived: 'Архивирован',
|
||||
};
|
||||
return statusMap[status] || status || 'Не указан';
|
||||
};
|
||||
|
||||
export const ProjectCardComponent = ({
|
||||
project,
|
||||
projectDataInit,
|
||||
exportMode = false,
|
||||
isLoadingFile = false,
|
||||
onAddForExport,
|
||||
onExportClick,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
org_unit_name,
|
||||
year,
|
||||
status,
|
||||
level,
|
||||
project_type,
|
||||
vsp_format,
|
||||
placement_type,
|
||||
object_address,
|
||||
staff_count,
|
||||
total_area,
|
||||
report_count,
|
||||
parent_id,
|
||||
} = project;
|
||||
|
||||
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
||||
const [currentName, setCurrentName] = useState(name || '');
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [editFormData, setEditFormData] = useState({});
|
||||
const [projectData, setProjectData] = useState(projectDataInit);
|
||||
|
||||
useEffect(() => {
|
||||
setIsChecked(false);
|
||||
}, [exportMode]);
|
||||
|
||||
|
||||
|
||||
// Функция для подготовки данных проекта к редактированию
|
||||
const prepareProjectDataForEdit = () => {
|
||||
return {
|
||||
'ССП/РФ': project.org_unit_id || null,
|
||||
'Год': year || null,
|
||||
'Тип проекта развития': project_type || null,
|
||||
'Название проекта': currentName,
|
||||
'Формат ВСП': vsp_format || null,
|
||||
'Адрес объекта': object_address || '',
|
||||
'Целевая численность, чел.': staff_count || 0,
|
||||
'Тип размещения': placement_type || null,
|
||||
'Площадь размещения, м2': total_area || 0,
|
||||
};
|
||||
};
|
||||
|
||||
const handleOpenEdit = (event) => {
|
||||
event.stopPropagation();
|
||||
setEditFormData(prepareProjectDataForEdit());
|
||||
setEditModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseEdit = () => {
|
||||
setEditModalOpen(false);
|
||||
setEditFormData({});
|
||||
};
|
||||
|
||||
const handleCheckboxClick = (event) => {
|
||||
event.stopPropagation();
|
||||
if (!isLoadingFile) {
|
||||
setIsChecked(!isChecked);
|
||||
onAddForExport(!isChecked);
|
||||
} else {
|
||||
toast.warning('Файл формируется!');
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportProject = (event) => {
|
||||
event.stopPropagation();
|
||||
onExportClick?.(id, name);
|
||||
};
|
||||
|
||||
const handleNavigate = () => {
|
||||
if (!exportMode) {
|
||||
navigate(`/project/${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Обновленная функция обновления проекта
|
||||
const updateProject = async (formData) => {
|
||||
try {
|
||||
const projectData = {
|
||||
name: formData['Название проекта'] || currentName,
|
||||
project_type: formData['Тип проекта развития'] || null,
|
||||
vsp_format: formData['Формат ВСП'] || null,
|
||||
object_address: formData['Адрес объекта'] || '',
|
||||
staff_count: Number(formData['Целевая численность, чел.']) || 0,
|
||||
placement_type: formData['Тип размещения'] || null,
|
||||
total_area: Number(formData['Площадь размещения, м2']) || 0,
|
||||
year: formData['Год'] ? Number(formData['Год']) : null,
|
||||
branch_id: formData['ССП/РФ'] || null,
|
||||
};
|
||||
|
||||
await ProjectsApi.update(id, projectData);
|
||||
|
||||
// Обновляем локальные данные
|
||||
setCurrentName(projectData.name);
|
||||
setEditModalOpen(false);
|
||||
toast.success('Данные проекта успешно обновлены');
|
||||
|
||||
// Можно добавить рефреш данных или перезагрузку страницы
|
||||
window.location.reload(); // или используйте callback для обновления списка
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error?.response?.data?.detail || 'Не удалось обновить данные проекта',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveProject = async (formData) => {
|
||||
await updateProject(formData);
|
||||
};
|
||||
|
||||
const getInitials = (name) => {
|
||||
if (!name) return '?';
|
||||
const words = name.split(' ');
|
||||
if (words.length >= 2) {
|
||||
return `${words[0][0]}${words[1][0]}`.toUpperCase();
|
||||
}
|
||||
return name.substring(0, 2).toUpperCase();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProjectCard onClick={handleNavigate} key={id}>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<CardHeader>
|
||||
<ProjectId>ID: {id}</ProjectId>
|
||||
<Stack direction={'row'} spacing={'.5rem'} sx={{
|
||||
|
||||
}} >
|
||||
{!exportMode && (
|
||||
<>
|
||||
{status && (
|
||||
<StatusChip
|
||||
status={status}
|
||||
label={getStatusLabel(status)}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
{/* {Пока нет редактирование + экспорт} */}
|
||||
{/* <ExportButton onClick={handleExportProject} />
|
||||
<Edit onClick={handleOpenEdit} /> */}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</CardHeader>
|
||||
|
||||
<ProjectTitle>{currentName}</ProjectTitle>
|
||||
|
||||
<ProjectInfoGrid>
|
||||
{project_type && (
|
||||
<InfoChip label={`Тип: ${project_type}`} size="small" />
|
||||
)}
|
||||
{report_count !== undefined && report_count !== null && (
|
||||
<InfoChip label={`Отчетов: ${report_count}`} size="small" />
|
||||
)}
|
||||
|
||||
</ProjectInfoGrid>
|
||||
</Box>
|
||||
|
||||
<Stack
|
||||
sx={{
|
||||
direction: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
alignItems: 'end',
|
||||
}}
|
||||
>
|
||||
<CardBody>
|
||||
<IconWithContent icon={<DateIcon />}>
|
||||
{year || 'Год не указан'}
|
||||
</IconWithContent>
|
||||
<IconWithContent icon={<UserIcon />}>
|
||||
{org_unit_name || 'Организация не указана'}
|
||||
</IconWithContent>
|
||||
</CardBody>
|
||||
|
||||
{exportMode && (
|
||||
<div>
|
||||
{isChecked ? (
|
||||
<CheckBoxCheckSvg size="1.625rem" />
|
||||
) : (
|
||||
<CheckBoxUncheckSvg size="1.625rem" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
</ProjectCard>
|
||||
|
||||
<ModalCreateProject
|
||||
open={isEditModalOpen}
|
||||
onClose={handleCloseEdit}
|
||||
onCreate={handleSaveProject}
|
||||
config={projectData}
|
||||
initialData={editFormData}
|
||||
isEdit={true}
|
||||
projectId={id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
298
web/src/pages/ProjectsPage/ProjectsPage.jsx
Normal file
298
web/src/pages/ProjectsPage/ProjectsPage.jsx
Normal file
@ -0,0 +1,298 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ProjectsApi } from '../../api/projects';
|
||||
import { CardsSkeleton } from '../../components/common/Skeleton';
|
||||
import SearchComponent from '../../components/common/SearchComponent';
|
||||
import { ProjectCardComponent } from './ProjectCard';
|
||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||
import { PageContainer, TasksContainer } from '../StyledForPage';
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
Button,
|
||||
Stack,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
Select,
|
||||
MenuItem,
|
||||
Typography,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
} from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ModalCreateProject } from './ModalCreateProject';
|
||||
import {
|
||||
ROLES_NAME_ID,
|
||||
} from '../../constants/constants';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { PrimaryButton, PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
||||
import {
|
||||
ExportExitButton,
|
||||
ExportWithTextButton,
|
||||
} from '../../components/common/Buttons/ButtonsActions';
|
||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
|
||||
import { WhitePlus } from '../../components/common/icons/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PaginatedList } from '../../components/common/PaginatedList';
|
||||
import { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import NavigationToggle from '../../components/common/NavigationToggle';
|
||||
|
||||
export { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const navigate = useNavigate();
|
||||
const [items, setItems] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
// Новые фильтры
|
||||
const [yearFilter, setYearFilter] = useState('');
|
||||
const [branchFilter, setBranchFilter] = useState('');
|
||||
|
||||
// Pagination state
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = useState(20);
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
|
||||
const { user } = useAuth();
|
||||
|
||||
const [exportMode, setExportMode] = useState(false);
|
||||
const [exportList, setExportList] = useState([]);
|
||||
|
||||
const [projectData, setProjectData] = useState(DEFAULT_PROJECT_CONFIG);
|
||||
|
||||
// Получаем список годов для фильтра (можно динамически или задать диапазон)
|
||||
const yearOptions = useMemo(() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [];
|
||||
for (let year = currentYear - 5; year <= currentYear + 2; year++) {
|
||||
years.push(year);
|
||||
}
|
||||
return years;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
if (user.role_id == ROLES_NAME_ID.admin) {
|
||||
SspApi.getAll().then(data => {
|
||||
if (data.success) {
|
||||
setProjectData(prev => ({
|
||||
...prev, 'ССП/РФ': {
|
||||
...prev['ССП/РФ'],
|
||||
options: data.result
|
||||
}
|
||||
}))
|
||||
return;
|
||||
}
|
||||
toast.error('Ошибка при получении ССП/РФ');
|
||||
});
|
||||
return;
|
||||
}
|
||||
setProjectData(prev => ({
|
||||
...prev, 'ССП/РФ': {
|
||||
...prev['ССП/РФ'],
|
||||
options: user.org_units,
|
||||
}
|
||||
}))
|
||||
}, [user])
|
||||
|
||||
const openModal = () => {
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const mapFormDataToApi = (formData) => {
|
||||
const apiData = {
|
||||
name: formData['Название проекта'] || '',
|
||||
project_type: formData['Тип проекта развития'] || null,
|
||||
vsp_format: formData['Формат ВСП'] || null,
|
||||
object_address: formData['Адрес объекта'] || '',
|
||||
staff_count: formData['Целевая численность, чел.'] || 0,
|
||||
placement_type: formData['Тип размещения'] || null,
|
||||
total_area: formData['Площадь размещения, м2'] || 0,
|
||||
year: formData['Год'] || null,
|
||||
branch_id: formData['ССП/РФ'] || null,
|
||||
|
||||
};
|
||||
return apiData;
|
||||
};
|
||||
|
||||
const handleCreateProject = async (formData) => {
|
||||
try {
|
||||
const apiData = mapFormDataToApi(formData);
|
||||
|
||||
await ProjectsApi.create(apiData);
|
||||
|
||||
setPage(1);
|
||||
loadProjects(1, limit);
|
||||
|
||||
setModalOpen(false);
|
||||
toast.success('Проект успешно создан');
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error.response?.data?.detail ||
|
||||
error.response?.data?.message ||
|
||||
'Ошибка при создании проекта',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const loadProjects = async (currentPage = page, currentLimit = limit) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const skip = (currentPage - 1) * currentLimit;
|
||||
const params = {
|
||||
offset: skip,
|
||||
limit: currentLimit,
|
||||
// Добавляем фильтры в параметры запроса
|
||||
...(yearFilter && { year: parseInt(yearFilter) }),
|
||||
...(branchFilter && { branch_id: parseInt(branchFilter.id) }),
|
||||
};
|
||||
|
||||
const res = await ProjectsApi.list(params);
|
||||
const data = res.result;
|
||||
|
||||
setItems(data);
|
||||
setTotalCount(res.count);
|
||||
|
||||
} catch (error) {
|
||||
toast.error('Ошибка загрузки проектов');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Сбрасываем страницу при изменении любых фильтров
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
loadProjects(1, limit);
|
||||
}, [yearFilter, branchFilter]);
|
||||
|
||||
const handlePageChange = (event, value) => {
|
||||
setPage(value);
|
||||
loadProjects(value, limit);
|
||||
};
|
||||
|
||||
const handleLimitChange = (event) => {
|
||||
const newLimit = event.target.value;
|
||||
setLimit(newLimit);
|
||||
setPage(1);
|
||||
loadProjects(1, newLimit);
|
||||
};
|
||||
|
||||
// Сброс всех фильтров
|
||||
const handleClearFilters = () => {
|
||||
setYearFilter('');
|
||||
setBranchFilter('');
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadProjects(page, limit);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setExportList([]);
|
||||
}, [exportMode]);
|
||||
|
||||
const handleChangeToExportList = (projectId, checked) => {
|
||||
if (checked) {
|
||||
setExportList((prev) => [...prev, projectId]);
|
||||
} else {
|
||||
const newExportList = exportList.filter((t) => t != projectId);
|
||||
setExportList(newExportList);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkExport = async () => {
|
||||
await exportBulkForms(exportList);
|
||||
setExportMode(false);
|
||||
};
|
||||
|
||||
const handleSingleExport = (projectId, projectTitle) => {
|
||||
exportSingleForm(projectId, projectTitle);
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadProjects(page, limit);
|
||||
toast.info('Список проектов обновлен');
|
||||
};
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
<HeaderSwitch />
|
||||
<NavigationToggle currentView={'projects'} onViewChange={(path) => navigate(path)} />
|
||||
|
||||
<PaginatedList
|
||||
items={items}
|
||||
loading={loading}
|
||||
totalCount={totalCount}
|
||||
page={page}
|
||||
limit={limit}
|
||||
onPageChange={handlePageChange}
|
||||
onLimitChange={handleLimitChange}
|
||||
entityName="проектов"
|
||||
emptyMessage="Проекты не найдены"
|
||||
renderItem={(project, exportProps) => (
|
||||
<ProjectCardComponent
|
||||
key={project.id}
|
||||
project={project}
|
||||
projectDataInit={projectData}
|
||||
{...exportProps}
|
||||
/>
|
||||
)}
|
||||
getItemId={(project) => project.id}
|
||||
getItemTitle={(project) => project.name || 'Проект'}
|
||||
extraActions={
|
||||
user.role_id === ROLES_NAME_ID.admin && (
|
||||
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />} height='2.5rem' >
|
||||
Создать проект
|
||||
</PrimaryButton>
|
||||
)
|
||||
}
|
||||
filterActions={
|
||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center', height: '2.5rem' }}>
|
||||
<Select
|
||||
size="small"
|
||||
displayEmpty
|
||||
value={yearFilter}
|
||||
onChange={(e) => setYearFilter(e.target.value)}
|
||||
sx={{ width: 130 }}
|
||||
>
|
||||
<MenuItem value="">Все годы</MenuItem>
|
||||
{yearOptions.map((year) => (
|
||||
<MenuItem key={year} value={year}>{year}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={projectData['ССП/РФ']?.options ?? []}
|
||||
noOptionsText="Не найдено"
|
||||
getOptionLabel={(option) => option.title || option.name || '-'}
|
||||
value={branchFilter}
|
||||
onChange={(event, newValue) => setBranchFilter(newValue)}
|
||||
sx={{ width: 200 }}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} placeholder="Все" size="small" />
|
||||
)}
|
||||
/>
|
||||
<PrimaryOutlinedButton onClick={handleClearFilters} height="100%"> Сбросить</PrimaryOutlinedButton>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</PageContainer>
|
||||
|
||||
<ModalCreateProject
|
||||
open={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onCreate={handleCreateProject}
|
||||
config={projectData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const PageContainer = styled.div`
|
||||
padding: 2.5rem;
|
||||
padding: 2rem;
|
||||
padding-top: 1rem;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -57,7 +57,7 @@ export default function TablePage() {
|
||||
|
||||
const connection = connectToTable(tableId, user?.id, {
|
||||
onError: () => {
|
||||
toast.error('Ошибка подключения к серверу реального времени');
|
||||
toast.error('Ошибка подключения к серверу');
|
||||
},
|
||||
onClose: (event) => {
|
||||
if (isUnmountingRef.current) {
|
||||
|
||||
@ -1,183 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { Box, Button, Divider, TextField, Typography } from '@mui/material';
|
||||
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
|
||||
import Modal from '../../components/common/Modal/Modal';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
|
||||
export const ModalCreateProject = ({
|
||||
open,
|
||||
onClose,
|
||||
onCreate,
|
||||
config = {},
|
||||
}) => {
|
||||
const [projectData, setProjectData] = useState(() => {
|
||||
const initialData = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
initialData[key] = config[key].defaultValue;
|
||||
});
|
||||
return initialData;
|
||||
});
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
setProjectData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleClickCreate = () => {
|
||||
onCreate(projectData);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const resetData = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
resetData[key] = config[key].defaultValue;
|
||||
});
|
||||
setProjectData(resetData);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const renderField = (fieldName, config) => {
|
||||
switch (config.type) {
|
||||
case 'multiselect':
|
||||
return (
|
||||
<Box key={fieldName}>
|
||||
<Typography
|
||||
className="gray-color"
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
<SelectWithOptions
|
||||
className="sameSize"
|
||||
value={projectData[fieldName] || ''}
|
||||
options={config.options}
|
||||
placeholder={config.placeholder}
|
||||
onChange={(value) => handleChange(fieldName, value)}
|
||||
width="28.25rem"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
case 'text':
|
||||
return (
|
||||
<Box key={fieldName}>
|
||||
<Typography
|
||||
className="gray-color"
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--grey-dark)',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
<TextField
|
||||
className="sameSize"
|
||||
value={projectData[fieldName] || ''}
|
||||
onChange={(e) => handleChange(fieldName, e.target.value)}
|
||||
placeholder={config.placeholder}
|
||||
size="small"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
case 'number':
|
||||
return (
|
||||
<Box key={fieldName}>
|
||||
<Typography
|
||||
className="gray-color"
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--grey-dark)',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
<TextField
|
||||
className="sameSize"
|
||||
type="text"
|
||||
value={projectData[fieldName] || ''}
|
||||
onChange={(e) => {
|
||||
const inputValue = e.target.value;
|
||||
//только числа
|
||||
const isValidInput =
|
||||
/^\d*\.?\d*$/.test(inputValue) || inputValue === '';
|
||||
if (isValidInput) {
|
||||
handleChange(fieldName, inputValue);
|
||||
}
|
||||
}}
|
||||
placeholder={config.placeholder}
|
||||
size="small"
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: config.min,
|
||||
max: config.max || null,
|
||||
step: config.step || 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={'Создание проекта'}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
customSize={'43.75'}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="grey"
|
||||
onClick={handleClose}
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
Отменить
|
||||
</Button>
|
||||
<PrimaryButton
|
||||
variant="contained"
|
||||
onClick={handleClickCreate}
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
Создать
|
||||
</PrimaryButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Divider />
|
||||
<Box
|
||||
sx={{
|
||||
m: '1.25rem 0',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1.25rem',
|
||||
'& .sameSize': {
|
||||
height: '2.69rem',
|
||||
minWidth: '39.75rem',
|
||||
},
|
||||
'& .gray-color': { color: '#4A5565' },
|
||||
}}
|
||||
>
|
||||
{Object.entries(config).map(([fieldName, config]) =>
|
||||
renderField(fieldName, config),
|
||||
)}
|
||||
</Box>
|
||||
<Divider />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { TasksApi } from '../../api/tasks';
|
||||
import { TablesApi } from '../../api/tables';
|
||||
import { ProjectsApi } from '../../api/projects'; // Добавьте импорт API для проектов
|
||||
import {
|
||||
TaskInfoContainer,
|
||||
NameTask,
|
||||
@ -18,17 +19,16 @@ import { toast } from 'react-toastify';
|
||||
import { SettingModal as SettingStageModal } from '../../components/Stages/SettingModal';
|
||||
import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
||||
import { FormsApi } from '../../api/forms';
|
||||
import { ModalCreateProject } from './ModalCreateProject';
|
||||
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
|
||||
import { exportSingleForm } from '../../utils/exportForm';
|
||||
import { exportSingleForm } from '../../utils/exportFile';
|
||||
import TablesList from '../../components/common/TableList/TableList';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants/constants';
|
||||
|
||||
const TaskInfo = ({ task }) => {
|
||||
const TaskInfo = ({ task, isProject }) => {
|
||||
const portalContent = (
|
||||
<TaskInfoContainer>
|
||||
<BackButton to="/tasks" />
|
||||
<NameTask>{FORM_TYPE_TRANSLATE[task.form_type_code] || ''}</NameTask>
|
||||
<BackButton to={isProject ? "/projects" : "/tasks"} />
|
||||
<NameTask>{isProject ? 'Проект' : FORM_TYPE_TRANSLATE[task.form_type_code] || ''}</NameTask>
|
||||
</TaskInfoContainer>
|
||||
);
|
||||
|
||||
@ -40,9 +40,13 @@ const TaskInfo = ({ task }) => {
|
||||
};
|
||||
|
||||
export default function TaskPage() {
|
||||
const { taskId } = useParams();
|
||||
const { taskId, projectId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const id = Number(taskId);
|
||||
|
||||
// Определяем, что у нас за ID и какой тип
|
||||
const isProject = !!projectId;
|
||||
const id = isProject ? Number(projectId) : Number(taskId);
|
||||
|
||||
const [task, setTask] = useState(null);
|
||||
const [tables, setTables] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -51,23 +55,42 @@ export default function TaskPage() {
|
||||
const [modalProjectOpen, setModalProjectOpen] = useState(false);
|
||||
const [tempProjects, setTempProjects] = useState([]);
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [isProjectData, setIsProjectData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getSheets = async () => {
|
||||
const getData = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await TasksApi.getSheets(id);
|
||||
// Преобразуем данные с учетом direction
|
||||
const mappedTables = data.result.all_sheets.map(sheet => ({
|
||||
name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name,
|
||||
sheet: sheet.sheet_name,
|
||||
direction: sheet.direction,
|
||||
}));
|
||||
//пока скрыт
|
||||
setTables(mappedTables.filter(t => t.sheet !== "OTCH9F",));
|
||||
if (isProject) {
|
||||
// Получаем информацию о проекте
|
||||
const projectData = await ProjectsApi.get(id);
|
||||
setTask(projectData.result);
|
||||
setIsProjectData(true);
|
||||
|
||||
const mappedTables = projectData.result.reports.map(sheet => ({
|
||||
name: SHEET_NAME[sheet.report_type] || sheet.report_type,
|
||||
sheet: sheet.report_type,
|
||||
year: sheet.year,
|
||||
}));
|
||||
setTables(mappedTables);
|
||||
} else {
|
||||
// Получаем информацию о задаче
|
||||
const taskData = await TasksApi.getById(id);
|
||||
setTask(taskData.result);
|
||||
setIsProjectData(false);
|
||||
|
||||
// Получаем таблицы задачи
|
||||
const tablesData = await TasksApi.getSheets(id);
|
||||
const mappedTables = tablesData.result.all_sheets.map(sheet => ({
|
||||
name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name,
|
||||
sheet: sheet.sheet_name,
|
||||
direction: sheet.direction,
|
||||
}));
|
||||
setTables(mappedTables.filter(t => t.sheet !== "OTCH9F"));
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error?.response?.data?.detail || 'Ошибка получения данных задачи',
|
||||
error?.response?.data?.detail || `Ошибка получения данных ${isProject ? 'проекта' : 'задачи'}`,
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@ -75,27 +98,11 @@ export default function TaskPage() {
|
||||
};
|
||||
|
||||
if (Number.isFinite(id)) {
|
||||
getSheets();
|
||||
getData();
|
||||
}
|
||||
}, [id]);
|
||||
}, [id, isProject]);
|
||||
|
||||
useEffect(() => {
|
||||
const getFormInfo = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await TasksApi.getById(id);
|
||||
setTask(data.result);
|
||||
} catch (error) {
|
||||
// обработка ошибки
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (Number.isFinite(id)) {
|
||||
getFormInfo();
|
||||
}
|
||||
}, [id]);
|
||||
// Объединили два useEffect в один
|
||||
|
||||
const handleCreateProject = (projectData) => {
|
||||
// логика создания проекта
|
||||
@ -107,6 +114,14 @@ export default function TaskPage() {
|
||||
return searchString.includes(query.toLowerCase());
|
||||
});
|
||||
|
||||
// Функция для экспорта
|
||||
const handleExport = () => {
|
||||
const fileName = isProject
|
||||
? `project_${task?.title || id}`
|
||||
: `form_${task?.title || taskId}`;
|
||||
exportSingleForm(id, fileName);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center pt-12">
|
||||
@ -117,7 +132,7 @@ export default function TaskPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{task && <TaskInfo task={task} />}
|
||||
{task && <TaskInfo task={task} isProject={isProject} />}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -143,9 +158,7 @@ export default function TaskPage() {
|
||||
onChange={setQuery}
|
||||
/>
|
||||
<Stack sx={{ flexDirection: 'row', spacing: '.5rem', justifyContent: 'end', gap: '.5rem' }}>
|
||||
<ExportWithTextButton
|
||||
onClick={() => exportSingleForm(taskId, task?.title ?? 'form')}
|
||||
/>
|
||||
<ExportWithTextButton onClick={handleExport} />
|
||||
<PrimaryOutlinedButton
|
||||
variant="outlined"
|
||||
onClick={() => setModalStageOpen(true)}
|
||||
@ -156,9 +169,10 @@ export default function TaskPage() {
|
||||
<SettingStageModal
|
||||
isOpen={modalStageOpen}
|
||||
onClose={() => setModalStageOpen(false)}
|
||||
taskId={taskId}
|
||||
mode="task"
|
||||
formTypeCode={task?.form_type_code || ''}
|
||||
taskId={isProject ? undefined : taskId}
|
||||
projectId={isProject ? projectId : undefined}
|
||||
mode={isProject ? "project" : "task"}
|
||||
formTypeCode={ task?.form_type_code || ''}
|
||||
sheets={tables}
|
||||
/>
|
||||
|
||||
@ -169,8 +183,9 @@ export default function TaskPage() {
|
||||
query={query}
|
||||
projects={projects}
|
||||
filteredTables={filteredTables}
|
||||
basePath="/table"
|
||||
basePath={"/table"}
|
||||
formInfo={task}
|
||||
isProject={isProject}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
ORG_UNIT_TYPE_OPTIONS,
|
||||
ROLES_NAME_ID,
|
||||
FORM_TYPE_TRANSLATE
|
||||
} from '../../constants';
|
||||
} from '../../constants/constants';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
@ -35,10 +35,13 @@ import {
|
||||
ExportExitButton,
|
||||
ExportWithTextButton,
|
||||
} from '../../components/common/Buttons/ButtonsActions';
|
||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportForm';
|
||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
|
||||
import { WhitePlus } from '../../components/common/icons/icons';
|
||||
import { OrgUnitsAutocompletePaper } from './OrgUnitsAutocompletePaper';
|
||||
import { renderOrgUnitValue } from './renderOrgUnitValue';
|
||||
import { OrgUnitsAutocompletePaper } from '../../components/common/OrgUnitsAutocompletePaper/OrgUnitsAutocompletePaper';
|
||||
import { renderOrgUnitValue } from '../../components/common/OrgUnitsAutocompletePaper/renderOrgUnitValue';
|
||||
import { PaginatedList } from '../../components/common/PaginatedList';
|
||||
import NavigationToggle from '../../components/common/NavigationToggle';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
const modalSelectMenuProps = {
|
||||
sx: { zIndex: 1301 },
|
||||
@ -52,6 +55,8 @@ const modalSelectSx = {
|
||||
};
|
||||
|
||||
export default function TasksPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [items, setItems] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingUpdate, setLoadingUpdate] = useState(false);
|
||||
@ -255,128 +260,34 @@ export default function TasksPage() {
|
||||
<>
|
||||
<PageContainer>
|
||||
<HeaderSwitch />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
{exportMode && <span>Выделено задач: {exportList.length}</span>}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<div style={{ width: '20rem' }}>
|
||||
{/* <SearchComponent
|
||||
placeholder="Поиск по названию задачи"
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
/> */}
|
||||
</div>
|
||||
<Stack direction={'row'} spacing={'.5rem'}>
|
||||
{!exportMode ? (
|
||||
<>
|
||||
<ExportWithTextButton
|
||||
text={'Пакетный экспорт'}
|
||||
onClick={() => {
|
||||
setExportMode(true);
|
||||
}}
|
||||
></ExportWithTextButton>
|
||||
{user.role_id == ROLES_NAME_ID.admin && (
|
||||
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />}>
|
||||
Создать задачу
|
||||
</PrimaryButton>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ExportWithTextButton
|
||||
text={'Экспортировать'}
|
||||
onClick={handleBulkExport}
|
||||
disabled={exportList.length === 0}
|
||||
/>
|
||||
<ExportExitButton onClick={() => setExportMode(false)} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* TODO: вернуть, когда будет реализовано на бэке */}
|
||||
{/* <FilterComponent statusFilter={statusFilter} setStatusFilter={setStatusFilter} /> */}
|
||||
<TasksContainer>
|
||||
{loading ? (
|
||||
<CardsSkeleton count={limit} />
|
||||
) : (
|
||||
<>
|
||||
{items.map((t) => (
|
||||
<TaskCardComponent
|
||||
exportMode={exportMode}
|
||||
taskForm={t}
|
||||
key={t.id + '-task-id'}
|
||||
onAddForExport={(checked) => {
|
||||
handleChangeToExportList(t.id, checked);
|
||||
}}
|
||||
onExportClick={handleSingleExport}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TasksContainer>
|
||||
|
||||
{/* Pagination Footer */}
|
||||
{!loading && items.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
pt: 2,
|
||||
pb: 2,
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Показать на странице:
|
||||
</Typography>
|
||||
<Select
|
||||
value={limit}
|
||||
onChange={handleLimitChange}
|
||||
size="small"
|
||||
sx={{ minWidth: 70 }}
|
||||
disabled={loading}
|
||||
>
|
||||
<MenuItem value={10}>10</MenuItem>
|
||||
<MenuItem value={20}>20</MenuItem>
|
||||
<MenuItem value={50}>50</MenuItem>
|
||||
<MenuItem value={100}>100</MenuItem>
|
||||
<MenuItem value={200}>200</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
<Pagination
|
||||
count={Math.ceil(totalCount / limit)}
|
||||
page={page}
|
||||
onChange={handlePageChange}
|
||||
color="primary"
|
||||
size="large"
|
||||
showFirstButton
|
||||
showLastButton
|
||||
disabled={loading}
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
{...item}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.dark',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<NavigationToggle currentView={'tasks'} onViewChange={(path) => navigate(path)} />
|
||||
<PaginatedList
|
||||
items={items}
|
||||
loading={loading}
|
||||
totalCount={totalCount}
|
||||
page={page}
|
||||
limit={limit}
|
||||
onPageChange={handlePageChange}
|
||||
onLimitChange={handleLimitChange}
|
||||
entityName="задач"
|
||||
emptyMessage="Задачи не найдены"
|
||||
renderItem={(task, exportProps) => (
|
||||
<TaskCardComponent
|
||||
key={task.id}
|
||||
taskForm={task}
|
||||
{...exportProps}
|
||||
/>
|
||||
<Box sx={{ width: 120 }} /> {/* Spacer for symmetry */}
|
||||
</Box>
|
||||
)}
|
||||
)}
|
||||
getItemId={(task) => task.id}
|
||||
getItemTitle={(task) => task.title || 'Задача'}
|
||||
extraActions={
|
||||
user.role_id === ROLES_NAME_ID.admin && (
|
||||
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />} height={'2.5rem'}>
|
||||
Создать задачу
|
||||
</PrimaryButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</PageContainer>
|
||||
<Modal
|
||||
open={modalOpen}
|
||||
|
||||
64
web/src/utils/exportFile.js
Normal file
64
web/src/utils/exportFile.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { FormsSheetApi } from "../api/form_sheet";
|
||||
import { ProjectsApi } from "../api/projects";
|
||||
import { TasksApi } from "../api/tasks";
|
||||
import { downloadFile } from "./downloadFile";
|
||||
import { getApiErrorMessage } from "./getApiErrorMessage";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const exportSingleForm = async (taskId, taskTitle) => {
|
||||
try {
|
||||
const response = await TasksApi.exportTask(taskId);
|
||||
await downloadFile(response, taskTitle, "xlsx");
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
"Ошибка при скачивании файла",
|
||||
);
|
||||
toast.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
export const exportSheet = async (formId, sheetName, direction) => {
|
||||
try {
|
||||
const normalizedDirection =
|
||||
direction && direction !== "null" ? direction : undefined;
|
||||
const params = normalizedDirection
|
||||
? { direction: normalizedDirection }
|
||||
: {};
|
||||
const response = await FormsSheetApi.export(formId, sheetName, params);
|
||||
await downloadFile(response, sheetName, "xlsx");
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
"Ошибка при скачивании файла",
|
||||
);
|
||||
toast.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
export const exportSheetProject = async (formId, sheetName, year) => {
|
||||
try {
|
||||
const response = await ProjectsApi.export(formId, year, sheetName);
|
||||
await downloadFile(response, sheetName, "xlsx");
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
"Ошибка при скачивании файла",
|
||||
);
|
||||
toast.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
export const exportBulkForms = async (formIds, fileName = "Задачи") => {
|
||||
try {
|
||||
const response = await TasksApi.exportTasks({ form_ids: formIds });
|
||||
await downloadFile(response, fileName, "xlsx");
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
"Ошибка при скачивании файла",
|
||||
);
|
||||
toast.error(message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@ -1,48 +0,0 @@
|
||||
import { FormsSheetApi } from '../api/form_sheet';
|
||||
import { TasksApi } from '../api/tasks';
|
||||
import { downloadFile } from './downloadFile';
|
||||
import { getApiErrorMessage } from './getApiErrorMessage';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export const exportSingleForm = async (taskId, taskTitle) => {
|
||||
try {
|
||||
const response = await TasksApi.exportTask(taskId);
|
||||
await downloadFile(response, taskTitle, 'xlsx');
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
'Ошибка при скачивании файла',
|
||||
);
|
||||
toast.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
export const exportSheet = async (formId, sheetName, direction) => {
|
||||
try {
|
||||
const normalizedDirection =
|
||||
direction && direction !== 'null' ? direction : undefined;
|
||||
const params = normalizedDirection ? { direction: normalizedDirection } : {};
|
||||
const response = await FormsSheetApi.export(formId, sheetName, params);
|
||||
await downloadFile(response, sheetName, 'xlsx');
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
'Ошибка при скачивании файла',
|
||||
);
|
||||
toast.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
export const exportBulkForms = async (formIds, fileName = 'Задачи') => {
|
||||
try {
|
||||
const response = await TasksApi.exportTasks({ form_ids: formIds });
|
||||
await downloadFile(response, fileName, 'xlsx');
|
||||
} catch (error) {
|
||||
const message = await getApiErrorMessage(
|
||||
error,
|
||||
'Ошибка при скачивании файла',
|
||||
);
|
||||
toast.error(message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user