добавлене программ, проектов в 4 форме
This commit is contained in:
parent
d257c3f1c1
commit
8c978b4e33
5
web/public/images/Program.svg
Normal file
5
web/public/images/Program.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M8 21V7C8 6.07003 8 5.60504 8.10222 5.22354C8.37962 4.18827 9.18827 3.37962 10.2235 3.10222C10.605 3 11.07 3 12 3C12.93 3 13.395 3 13.7765 3.10222C14.8117 3.37962 15.6204 4.18827 15.8978 5.22354C16 5.60504 16 6.07003 16 7V21M5.2 21H18.8C19.9201 21 20.4802 21 20.908 20.782C21.2843 20.5903 21.5903 20.2843 21.782 19.908C22 19.4802 22 18.9201 22 17.8V10.2C22 9.07989 22 8.51984 21.782 8.09202C21.5903 7.71569 21.2843 7.40973 20.908 7.21799C20.4802 7 19.9201 7 18.8 7H5.2C4.07989 7 3.51984 7 3.09202 7.21799C2.71569 7.40973 2.40973 7.71569 2.21799 8.09202C2 8.51984 2 9.07989 2 10.2V17.8C2 18.9201 2 19.4802 2.21799 19.908C2.40973 20.2843 2.71569 20.5903 3.09202 20.782C3.51984 21 4.0799 21 5.2 21Z"
|
||||||
|
stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 900 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M14 11H8M10 15H8M16 7H8M20 6.8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V6.8C4 5.11984 4 4.27976 4.32698 3.63803C4.6146 3.07354 5.07354 2.6146 5.63803 2.32698C6.27976 2 7.11984 2 8.8 2H15.2C16.8802 2 17.7202 2 18.362 2.32698C18.9265 2.6146 19.3854 3.07354 19.673 3.63803C20 4.27976 20 5.11984 20 6.8Z"
|
||||||
|
stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 704 B |
@ -0,0 +1,110 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Stack,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Autocomplete,
|
||||||
|
TextField,
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
ModalBackdrop,
|
||||||
|
ModalContainer,
|
||||||
|
HeaderContainer,
|
||||||
|
ModalTitle,
|
||||||
|
} from '../../common/Modal/ModalStyled';
|
||||||
|
import { Cancel } from '../../common/icons/icons';
|
||||||
|
import { DictVspApi } from '../../../api/dict-vsp';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { ExpenseItemApi } from '../../../api/expense-item';
|
||||||
|
import { DangerOutlinedButton, PrimaryButton } from '../../common/Buttons/Buttons';
|
||||||
|
import { StyledTextField } from '../../common/StyledTextField';
|
||||||
|
|
||||||
|
export const CreateProgramModal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onCreate,
|
||||||
|
title = '',
|
||||||
|
isSaving = false,
|
||||||
|
sheet,
|
||||||
|
}) => {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setName('');
|
||||||
|
}
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setName('');
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackdropClick = (e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onCreate?.(name);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormValid = name !== '';
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalBackdrop isOpen={isOpen} onClick={handleBackdropClick}>
|
||||||
|
<ModalContainer height={'max-content'} >
|
||||||
|
<HeaderContainer>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
||||||
|
<ModalTitle>{title}</ModalTitle>
|
||||||
|
</Stack>
|
||||||
|
<IconButton size="small" onClick={handleCancel}>
|
||||||
|
<Cancel />
|
||||||
|
</IconButton>
|
||||||
|
</HeaderContainer>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Divider sx={{ marginBottom: '0.75rem' }} />
|
||||||
|
|
||||||
|
|
||||||
|
{/* <Input */}
|
||||||
|
<StyledTextField label={'Название программы'} onChange={(e) => setName(e.target.value)}></StyledTextField>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={0.75}
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginTop: '1.5rem',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DangerOutlinedButton
|
||||||
|
onClick={handleCancel}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</DangerOutlinedButton>
|
||||||
|
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={!isFormValid || isSaving}
|
||||||
|
loading={isSaving}>
|
||||||
|
Создать
|
||||||
|
</PrimaryButton>
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</ModalContainer>
|
||||||
|
</ModalBackdrop>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -22,6 +22,7 @@ import { Cancel } from '../../common/icons/icons';
|
|||||||
import { DictVspApi } from '../../../api/dict-vsp';
|
import { DictVspApi } from '../../../api/dict-vsp';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { ExpenseItemApi } from '../../../api/expense-item';
|
import { ExpenseItemApi } from '../../../api/expense-item';
|
||||||
|
import { DangerOutlinedButton, PrimaryButton } from '../../common/Buttons/Buttons';
|
||||||
|
|
||||||
export const SelectExpenseItemModal = ({
|
export const SelectExpenseItemModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -132,24 +133,19 @@ export const SelectExpenseItemModal = ({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DangerOutlinedButton
|
||||||
variant="outlined"
|
|
||||||
color="default"
|
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
style={{ height: '2.5rem' }}
|
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
>
|
>
|
||||||
Отмена
|
Отмена
|
||||||
</Button>
|
</DangerOutlinedButton>
|
||||||
<Button
|
|
||||||
variant="contained"
|
<PrimaryButton
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
style={{ height: '2.5rem' }}
|
|
||||||
disabled={!isFormValid || isSaving}
|
disabled={!isFormValid || isSaving}
|
||||||
loading={isSaving}
|
loading={isSaving}>
|
||||||
>
|
|
||||||
Выбрать
|
Выбрать
|
||||||
</Button>
|
</PrimaryButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</ModalContainer>
|
</ModalContainer>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
import { Cancel } from '../../common/icons/icons';
|
import { Cancel } from '../../common/icons/icons';
|
||||||
import { DictVspApi } from '../../../api/dict-vsp';
|
import { DictVspApi } from '../../../api/dict-vsp';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { DangerOutlinedButton, PrimaryButton } from '../../common/Buttons/Buttons';
|
||||||
|
|
||||||
export const SelectVspModal = ({
|
export const SelectVspModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -113,24 +114,19 @@ export const SelectVspModal = ({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DangerOutlinedButton
|
||||||
variant="outlined"
|
|
||||||
color="default"
|
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
style={{ height: '2.5rem' }}
|
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
>
|
>
|
||||||
Отмена
|
Отмена
|
||||||
</Button>
|
</DangerOutlinedButton>
|
||||||
<Button
|
|
||||||
variant="contained"
|
<PrimaryButton
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
style={{ height: '2.5rem' }}
|
|
||||||
disabled={!isFormValid || isSaving}
|
disabled={!isFormValid || isSaving}
|
||||||
loading={isSaving}
|
loading={isSaving}>
|
||||||
>
|
|
||||||
Выбрать
|
Выбрать
|
||||||
</Button>
|
</PrimaryButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</ModalContainer>
|
</ModalContainer>
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import { SelectExpenseItemModal } from './Modals/SelectExpenseItemModal';
|
|||||||
import { getRowId } from './utils/rowUtils';
|
import { getRowId } from './utils/rowUtils';
|
||||||
import { FORM_TYPE_OPTIONS } from '../../constants/constants';
|
import { FORM_TYPE_OPTIONS } from '../../constants/constants';
|
||||||
import { debounce } from '@mui/material';
|
import { debounce } from '@mui/material';
|
||||||
|
import { CreateProgramModal } from './Modals/CreateProgramProjectModal';
|
||||||
|
|
||||||
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
||||||
@ -43,6 +44,9 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
|
|
||||||
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
|
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
|
||||||
const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false);
|
const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false);
|
||||||
|
const [isOpenModalCreateProgram, setIsOpenModalCreateProgram] = useState(false);
|
||||||
|
const [isOpenModalCreateProject, setIsOpenModalCreateProject] = useState(false);
|
||||||
|
const [isProgramCreate, setIsProgramCreate] = useState(false)
|
||||||
|
|
||||||
const isVspAdditionRow = useMemo(() => {
|
const isVspAdditionRow = useMemo(() => {
|
||||||
if (!formType || !sheetName) return false;
|
if (!formType || !sheetName) return false;
|
||||||
@ -73,7 +77,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
error: wsError,
|
error: wsError,
|
||||||
updateCell: contextUpdateCell,
|
updateCell: contextUpdateCell,
|
||||||
addRow: contextAddRow,
|
addRow: contextAddRow,
|
||||||
deleteRow: contextDeleteRow,
|
|
||||||
startEditing: contextStartEditing,
|
startEditing: contextStartEditing,
|
||||||
addProgram: contextAddProgram,
|
addProgram: contextAddProgram,
|
||||||
addProject: contextAddProject,
|
addProject: contextAddProject,
|
||||||
@ -378,16 +381,42 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAddExpenseItemRow = useCallback((expense_item) => {
|
const handleAddExpenseItemRow = useCallback((expense_item) => {
|
||||||
contextAddRow({ expense_item_id: expense_item.id });
|
const allRows = table.getRowModel().rows;
|
||||||
|
const selectedRows = allRows.filter(row => rowSelection[row.id]);
|
||||||
|
const row = selectedRows[0];
|
||||||
|
contextAddRow({ expense_item_id: expense_item.id, project_id: row.original.data.header.project_id });
|
||||||
|
}, [rowSelection]);
|
||||||
|
|
||||||
|
const handleAddProgramRow = useCallback((name) => {
|
||||||
|
contextAddProgram({ name: name });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleAddProjectRow = useCallback((name) => {
|
||||||
|
const allRows = table.getRowModel().rows;
|
||||||
|
const selectedRows = allRows.filter(row => rowSelection[row.id]);
|
||||||
|
const row = selectedRows[0];
|
||||||
|
contextAddProject({ name: name, program_id: row.original.data.header.program_id });
|
||||||
|
}, [rowSelection]);
|
||||||
|
|
||||||
const handleOpenModalSelectVsp = useCallback(() => {
|
const handleOpenModalSelectVsp = useCallback(() => {
|
||||||
setIsOpenModalSelectVsp(true);
|
setIsOpenModalSelectVsp(true);
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleOpenModalSelectExpenseItem = useCallback(() => {
|
const handleOpenModalSelectExpenseItem = useCallback(() => {
|
||||||
|
const allRows = table.getRowModel().rows;
|
||||||
|
|
||||||
|
const selectedRows = allRows.filter(row => rowSelection[row.id]);
|
||||||
|
if (selectedRows.length === 0) {
|
||||||
|
toast.error('Выделите строку для вставки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const row = selectedRows[0];
|
||||||
|
if (row.original.row_type !== 'ITEM') {
|
||||||
|
toast.error('Выделите строку проекта');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsOpenModalSelectExpenseItem(true);
|
setIsOpenModalSelectExpenseItem(true);
|
||||||
}, [])
|
}, [rowSelection])
|
||||||
|
|
||||||
const handleDeleteRow = useCallback(() => {
|
const handleDeleteRow = useCallback(() => {
|
||||||
const allRows = table.getRowModel().rows;
|
const allRows = table.getRowModel().rows;
|
||||||
@ -414,6 +443,28 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
return handleAddRow();
|
return handleAddRow();
|
||||||
}, [isVspAdditionRow, isAdditionExpenseItem, handleOpenModalSelectVsp, handleAddRow]);
|
}, [isVspAdditionRow, isAdditionExpenseItem, handleOpenModalSelectVsp, handleAddRow]);
|
||||||
|
|
||||||
|
const addProgram = useCallback(() => {
|
||||||
|
setIsProgramCreate(true);
|
||||||
|
setIsOpenModalCreateProgram(true);
|
||||||
|
}, [setIsOpenModalCreateProgram])
|
||||||
|
|
||||||
|
const addProject = useCallback(() => {
|
||||||
|
setIsProgramCreate(false);
|
||||||
|
const allRows = table.getRowModel().rows;
|
||||||
|
|
||||||
|
const selectedRows = allRows.filter(row => rowSelection[row.id]);
|
||||||
|
if (selectedRows.length === 0) {
|
||||||
|
toast.error('Выделите строку для вставки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const row = selectedRows[0];
|
||||||
|
if (row.original.row_type !== 'GROUP') {
|
||||||
|
toast.error('Выделите строку программы');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsOpenModalCreateProgram(true);
|
||||||
|
}, [setIsOpenModalCreateProgram, rowSelection])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setData([]);
|
setData([]);
|
||||||
@ -441,6 +492,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
sizeMult={sizeMult}
|
sizeMult={sizeMult}
|
||||||
onChangeShowColumnFilters={setShowColumnFilters}
|
onChangeShowColumnFilters={setShowColumnFilters}
|
||||||
onAddRow={addRow}
|
onAddRow={addRow}
|
||||||
|
onAddProgram={addProgram}
|
||||||
|
onAddProject={addProject}
|
||||||
onDeleteRow={handleDeleteRow}
|
onDeleteRow={handleDeleteRow}
|
||||||
isLoadingData={isLoadingData}
|
isLoadingData={isLoadingData}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
@ -448,6 +501,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
direction={direction}
|
direction={direction}
|
||||||
year={year}
|
year={year}
|
||||||
isProject={formType == 'PROJECT'}
|
isProject={formType == 'PROJECT'}
|
||||||
|
formType={formType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={tableWrapperStyle}>
|
<div style={tableWrapperStyle}>
|
||||||
@ -518,6 +572,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
title="Добавление строки"
|
title="Добавление строки"
|
||||||
sheet={sheetName}
|
sheet={sheetName}
|
||||||
/>
|
/>
|
||||||
|
<CreateProgramModal
|
||||||
|
isOpen={isOpenModalCreateProgram}
|
||||||
|
onClose={() => setIsOpenModalCreateProgram(false)}
|
||||||
|
title={isProgramCreate ? "Создание программы" : "Создание проекта"}
|
||||||
|
onCreate={isProgramCreate ? handleAddProgramRow : handleAddProjectRow}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import {
|
|||||||
Minus,
|
Minus,
|
||||||
Pin,
|
Pin,
|
||||||
Plus,
|
Plus,
|
||||||
|
Program,
|
||||||
|
Project,
|
||||||
RemoveLine,
|
RemoveLine,
|
||||||
Search,
|
Search,
|
||||||
} from '../../common/icons/icons';
|
} from '../../common/icons/icons';
|
||||||
@ -19,6 +21,7 @@ import ZoomSlider from './ZoomSlider';
|
|||||||
import SearchComponent from '../../common/SearchComponent';
|
import SearchComponent from '../../common/SearchComponent';
|
||||||
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
||||||
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
||||||
|
import { FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
||||||
|
|
||||||
const SettingPanel = ({
|
const SettingPanel = ({
|
||||||
selectedColumnId,
|
selectedColumnId,
|
||||||
@ -34,6 +37,8 @@ const SettingPanel = ({
|
|||||||
sizeMult,
|
sizeMult,
|
||||||
onChangeShowColumnFilters,
|
onChangeShowColumnFilters,
|
||||||
onAddRow,
|
onAddRow,
|
||||||
|
onAddProgram,
|
||||||
|
onAddProject,
|
||||||
onDeleteRow,
|
onDeleteRow,
|
||||||
isLoadingData,
|
isLoadingData,
|
||||||
formId,
|
formId,
|
||||||
@ -41,13 +46,13 @@ const SettingPanel = ({
|
|||||||
direction,
|
direction,
|
||||||
year,
|
year,
|
||||||
isProject,
|
isProject,
|
||||||
|
formType,
|
||||||
}) => {
|
}) => {
|
||||||
const [isPinned, setIsPinned] = useState(false);
|
const [isPinned, setIsPinned] = useState(false);
|
||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
const [selectedColumnIds, setSelectedColumnIds] = useState();
|
const [selectedColumnIds, setSelectedColumnIds] = useState();
|
||||||
const [showColumnFilters, setShowColumnFilters] = useState(false);
|
const [showColumnFilters, setShowColumnFilters] = useState(false);
|
||||||
|
|
||||||
// TODO: console.log(!!!) везде посомтреть
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedColumnId) {
|
if (!selectedColumnId) {
|
||||||
setIsPinned(false);
|
setIsPinned(false);
|
||||||
@ -65,7 +70,7 @@ const SettingPanel = ({
|
|||||||
}, [columnVisibility]);
|
}, [columnVisibility]);
|
||||||
|
|
||||||
//TO DO: сделать
|
//TO DO: сделать
|
||||||
const { addColorForCells, addFormulaForCells, deleteFormula } = {};
|
const { addColorForCells,} = {};
|
||||||
|
|
||||||
const handleChangeShowColumnFilters = () => {
|
const handleChangeShowColumnFilters = () => {
|
||||||
const show = table.getState().showColumnFilters;
|
const show = table.getState().showColumnFilters;
|
||||||
@ -73,10 +78,6 @@ const SettingPanel = ({
|
|||||||
onChangeShowColumnFilters(!show);
|
onChangeShowColumnFilters(!show);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickAddFormula = () => { };
|
|
||||||
|
|
||||||
const handleClickDeleteFormula = () => { };
|
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
if (isExporting) return;
|
if (isExporting) return;
|
||||||
if (isProject) {
|
if (isProject) {
|
||||||
@ -111,11 +112,6 @@ const SettingPanel = ({
|
|||||||
console.warn('[ColumnPin] клик Pin: selectedColumnId пустой');
|
console.warn('[ColumnPin] клик Pin: selectedColumnId пустой');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('[ColumnPin] клик Pin:', {
|
|
||||||
selectedColumnId,
|
|
||||||
isPinned,
|
|
||||||
action: isPinned ? 'unpin' : 'pin',
|
|
||||||
});
|
|
||||||
isPinned
|
isPinned
|
||||||
? onUnpinColumn?.(selectedColumnId)
|
? onUnpinColumn?.(selectedColumnId)
|
||||||
: onPinColumn?.(selectedColumnId);
|
: onPinColumn?.(selectedColumnId);
|
||||||
@ -143,6 +139,23 @@ const SettingPanel = ({
|
|||||||
<RemoveLine />
|
<RemoveLine />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{/* проверяем на 4 форму */}
|
||||||
|
{formType == FORM_TYPE_OPTIONS[3] && <>
|
||||||
|
|
||||||
|
<Tooltip title="Добавить программу">
|
||||||
|
<IconButton onClick={onAddProgram} variant="outlined">
|
||||||
|
<Plus />
|
||||||
|
<Program />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="Добавить проект">
|
||||||
|
<IconButton onClick={onAddProject} variant="outlined">
|
||||||
|
<Plus />
|
||||||
|
<Project />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</>}
|
||||||
</GroupByObject>
|
</GroupByObject>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -305,7 +305,7 @@ export const RealtimeProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
event: "row_added",
|
event: "project_added",
|
||||||
data: rowData,
|
data: rowData,
|
||||||
};
|
};
|
||||||
addCommonField(message);
|
addCommonField(message);
|
||||||
@ -323,7 +323,7 @@ export const RealtimeProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
event: "row_added",
|
event: "program_added",
|
||||||
data: rowData,
|
data: rowData,
|
||||||
};
|
};
|
||||||
addCommonField(message);
|
addCommonField(message);
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from "react";
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import { FormsSheetApi } from "../../../api/form_sheet";
|
import { FormsSheetApi } from "../../../api/form_sheet";
|
||||||
import { useRealtime } from "../contexts/RealtimeContext";
|
import { useRealtime } from "../contexts/RealtimeContext";
|
||||||
import { deleteRow, insertCell, updateCells } from "../utils/cellUtils";
|
import {
|
||||||
|
deleteRow,
|
||||||
|
insertCell,
|
||||||
|
insertProjectRow,
|
||||||
|
updateCells,
|
||||||
|
} from "../utils/cellUtils";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { isVspNewRow, isVspRow } from "../utils/rowUtils";
|
import { getParentId, isVspNewRow, isVspRow } from "../utils/rowUtils";
|
||||||
import { ProjectsApi } from "../../../api/projects";
|
import { ProjectsApi } from "../../../api/projects";
|
||||||
|
|
||||||
const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||||
@ -184,7 +189,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
|
|
||||||
const unsubscribeRowAdds = subscribeToRowAdds((data) => {
|
const unsubscribeRowAdds = subscribeToRowAdds((data) => {
|
||||||
const updates = data.result.data;
|
const updates = data.result.data;
|
||||||
const expense_item_id = data.data.expense_item_id || null;
|
const parent_id = getParentId(data.data);
|
||||||
const newLineId = data.result.new_line_id;
|
const newLineId = data.result.new_line_id;
|
||||||
const [updatedCells, newRow] = updates.reduce(
|
const [updatedCells, newRow] = updates.reduce(
|
||||||
(res, row) => {
|
(res, row) => {
|
||||||
@ -203,18 +208,17 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
}
|
}
|
||||||
setData((prevData) => {
|
setData((prevData) => {
|
||||||
const updateData = updateCells(prevData, updatedCells);
|
const updateData = updateCells(prevData, updatedCells);
|
||||||
const newData = insertCell(updateData, newRow, expense_item_id);
|
const newData = insertCell(updateData, newRow, parent_id);
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const unsubscribeProjectAdds = subscribeToProjectAdds((data) => {
|
const unsubscribeProjectAdds = subscribeToProjectAdds((data) => {
|
||||||
const updates = data.result.data;
|
const updates = data.result[1];
|
||||||
const expense_item_id = data.data.expense_item_id || null;
|
const program_id = data.data.program_id;
|
||||||
const newLineId = data.result.new_line_id;
|
|
||||||
const [updatedCells, newRow] = updates.reduce(
|
const [updatedCells, newRow] = updates.reduce(
|
||||||
(res, row) => {
|
(res, row) => {
|
||||||
if (row[3]?.line_id === newLineId || isVspNewRow(row)) {
|
if (row[0] === "ITEM") {
|
||||||
res[1] = row;
|
res[1] = row;
|
||||||
} else {
|
} else {
|
||||||
res[0].push(row);
|
res[0].push(row);
|
||||||
@ -229,33 +233,24 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
}
|
}
|
||||||
setData((prevData) => {
|
setData((prevData) => {
|
||||||
const updateData = updateCells(prevData, updatedCells);
|
const updateData = updateCells(prevData, updatedCells);
|
||||||
const newData = insertCell(updateData, newRow, expense_item_id);
|
const newData = insertProjectRow(updateData, newRow, program_id);
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const unsubscribeProgramAdds = subscribeToProgramAdds((data) => {
|
const unsubscribeProgramAdds = subscribeToProgramAdds((data) => {
|
||||||
const updates = data.result.data;
|
const newRow = data.result[1][0];
|
||||||
const expense_item_id = data.data.expense_item_id || null;
|
|
||||||
const newLineId = data.result.new_line_id;
|
|
||||||
const [updatedCells, newRow] = updates.reduce(
|
|
||||||
(res, row) => {
|
|
||||||
if (row[3]?.line_id === newLineId || isVspNewRow(row)) {
|
|
||||||
res[1] = row;
|
|
||||||
} else {
|
|
||||||
res[0].push(row);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
[[], null],
|
|
||||||
);
|
|
||||||
if (newRow === null) {
|
|
||||||
toast.error("Ошибка получения данных");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setData((prevData) => {
|
setData((prevData) => {
|
||||||
const updateData = updateCells(prevData, updatedCells);
|
const updateData = structuredClone(prevData);
|
||||||
const newData = insertCell(updateData, newRow, expense_item_id);
|
const [row_type, depth, sort_order, dataRow] = newRow;
|
||||||
|
const newRowObject = {
|
||||||
|
row_type: row_type,
|
||||||
|
depth: depth,
|
||||||
|
sort_order: sort_order,
|
||||||
|
subRows: [],
|
||||||
|
data: dataRow,
|
||||||
|
};
|
||||||
|
const newData = [...updateData, newRowObject];
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { getParentId } from "./rowUtils";
|
||||||
|
|
||||||
export function setNewValueToData(data, colId, rowId, value) {
|
export function setNewValueToData(data, colId, rowId, value) {
|
||||||
let targetObject = data;
|
let targetObject = data;
|
||||||
if (rowId && rowId.length) {
|
if (rowId && rowId.length) {
|
||||||
@ -114,7 +116,7 @@ export function updateCells(data, newRows) {
|
|||||||
return updatedRows;
|
return updatedRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertCell(data, newRow, expense_item_id) {
|
export function insertCell(data, newRow, parent_id) {
|
||||||
const updatedRows = JSON.parse(JSON.stringify(data));
|
const updatedRows = JSON.parse(JSON.stringify(data));
|
||||||
const [row_type, depth, sort_order, dataRow] = newRow;
|
const [row_type, depth, sort_order, dataRow] = newRow;
|
||||||
const newRowObject = {
|
const newRowObject = {
|
||||||
@ -126,7 +128,7 @@ export function insertCell(data, newRow, expense_item_id) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Если expense_item_id === null, добавляем в конец
|
// Если expense_item_id === null, добавляем в конец
|
||||||
if (expense_item_id === null) {
|
if (parent_id === null) {
|
||||||
updatedRows.push(newRowObject);
|
updatedRows.push(newRowObject);
|
||||||
return updatedRows;
|
return updatedRows;
|
||||||
}
|
}
|
||||||
@ -143,7 +145,47 @@ export function insertCell(data, newRow, expense_item_id) {
|
|||||||
row.sort_order = row.sort_order + 1;
|
row.sort_order = row.sort_order + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!adding & (row.data.header.expense_item_id === expense_item_id)) {
|
if (!adding & (getParentId(row.data.header) === parent_id)) {
|
||||||
|
if (!("subRows" in row)) {
|
||||||
|
row.subRows = [];
|
||||||
|
}
|
||||||
|
row.subRows.push(newRowObject);
|
||||||
|
adding = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.subRows && Array.isArray(row.subRows) && row.subRows.length > 0) {
|
||||||
|
const found = findAndUpdate(row.subRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findAndUpdate(updatedRows);
|
||||||
|
return updatedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertProjectRow(data, newRow, program_id) {
|
||||||
|
const updatedRows = structuredClone(data);
|
||||||
|
const [row_type, depth, sort_order, dataRow] = newRow;
|
||||||
|
const newRowObject = {
|
||||||
|
row_type: row_type,
|
||||||
|
depth: depth,
|
||||||
|
sort_order: sort_order,
|
||||||
|
subRows: [],
|
||||||
|
data: dataRow,
|
||||||
|
};
|
||||||
|
|
||||||
|
let adding = false;
|
||||||
|
function findAndUpdate(rows) {
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
|
||||||
|
if (
|
||||||
|
(row.sort_order >= newRowObject.sort_order) &
|
||||||
|
(row.data.line_id != newRowObject.data.line_id)
|
||||||
|
) {
|
||||||
|
row.sort_order = row.sort_order + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adding & (row.data.header.program_id === program_id)) {
|
||||||
if (!("subRows" in row)) {
|
if (!("subRows" in row)) {
|
||||||
row.subRows = [];
|
row.subRows = [];
|
||||||
}
|
}
|
||||||
@ -173,7 +215,7 @@ export function deleteRow(data, lineId) {
|
|||||||
rows.splice(i, 1);
|
rows.splice(i, 1);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((rowDeleteSortOrder > 0) & (row.sort_order >= rowDeleteSortOrder)) {
|
if ((rowDeleteSortOrder > 0) & (row.sort_order >= rowDeleteSortOrder)) {
|
||||||
row.sort_order = row.sort_order - 1;
|
row.sort_order = row.sort_order - 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,3 +13,7 @@ export function isVspNewRow(rowData) {
|
|||||||
const newRow = rowData[3];
|
const newRow = rowData[3];
|
||||||
return !!newRow.id;
|
return !!newRow.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getParentId(data){
|
||||||
|
return data.project_id || data.expense_item_id || null;
|
||||||
|
}
|
||||||
|
|||||||
20
web/src/components/common/StyledTextField.jsx
Normal file
20
web/src/components/common/StyledTextField.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
TextField,
|
||||||
|
} from '@mui/material';
|
||||||
|
export const StyledTextField = ({ error, helperText, disabled, ...props }) => (
|
||||||
|
<TextField
|
||||||
|
className="fullWidth"
|
||||||
|
size="small"
|
||||||
|
error={!!error}
|
||||||
|
helperText={helperText || error}
|
||||||
|
disabled={disabled}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
'& .MuiInputBase-root': {
|
||||||
|
height: '2.69rem',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
@ -1,117 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import Tooltip from './Tooltip';
|
|
||||||
|
|
||||||
const TextInput = ({
|
|
||||||
label,
|
|
||||||
name,
|
|
||||||
type = 'text',
|
|
||||||
onChange,
|
|
||||||
onChangeWithName,
|
|
||||||
tooltipText = '',
|
|
||||||
value = '',
|
|
||||||
placeholder = '',
|
|
||||||
autocomplete = 'new-password',
|
|
||||||
}) => {
|
|
||||||
const [cur_value, setCurValue] = useState(value);
|
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
|
||||||
|
|
||||||
const handleChange = (inputValue) => {
|
|
||||||
if (onChangeWithName) {
|
|
||||||
onChangeWithName(name, inputValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onChange) {
|
|
||||||
onChange(inputValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TextInputDiv>
|
|
||||||
<label>
|
|
||||||
{label}
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
onChange={(e) => {
|
|
||||||
setCurValue(e.target.value);
|
|
||||||
if (!isFocused) {
|
|
||||||
handleChange(e.target.value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
handleChange(cur_value);
|
|
||||||
setIsFocused(false);
|
|
||||||
}}
|
|
||||||
onFocus={() => setIsFocused(true)}
|
|
||||||
value={cur_value}
|
|
||||||
autoComplete={autocomplete}
|
|
||||||
placeholder={placeholder}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{tooltipText !== '' && <Tooltip tooltipText={tooltipText} />}
|
|
||||||
</TextInputDiv>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TextInputDiv = styled.div`
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.2rem; /* Уменьшено еще на 10% (0.225rem * 0.9) */
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
max-width: 100%;
|
|
||||||
width: 100%;
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
height: 1.6rem; /* Уменьшено еще на 10% (1.8rem * 0.9) */
|
|
||||||
min-height: 36px; /* Уменьшено еще на 10% (40px * 0.9) */
|
|
||||||
background-color: rgb(240, 242, 246);
|
|
||||||
padding: 0 0.4rem; /* Уменьшено еще на 10% (0.45rem * 0.9) */
|
|
||||||
font-size: inherit;
|
|
||||||
transition: border-color 0.2s ease;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #258141;
|
|
||||||
box-shadow: 0 0 0 2px rgba(37, 129, 65, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Медиа-запросы для адаптивности */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
input {
|
|
||||||
height: 2rem; /* Уменьшено еще на 10% (2.25rem * 0.9) */
|
|
||||||
min-height: 39px; /* Уменьшено еще на 10% (43px * 0.9) */
|
|
||||||
font-size: 13px; /* Уменьшено еще на 10% (14.4px * 0.9) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
label {
|
|
||||||
font-size: 0.73rem; /* Уменьшено еще на 10% (0.81rem * 0.9) */
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
height: 2.3rem; /* Уменьшено еще на 10% (2.52rem * 0.9) */
|
|
||||||
min-height: 42px; /* Уменьшено еще на 10% (47px * 0.9) */
|
|
||||||
padding: 0 0.6rem; /* Уменьшено еще на 10% (0.675rem * 0.9) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default TextInput;
|
|
||||||
@ -108,6 +108,23 @@ export const AddColumn = styled.div`
|
|||||||
background-position: center;
|
background-position: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const Project = styled.div`
|
||||||
|
background-image: url('images/Project.svg');
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
`;
|
||||||
|
export const Program = styled.div`
|
||||||
|
background-image: url('images/Program.svg');
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
`;
|
||||||
|
|
||||||
export const DeleteColumn = styled.div`
|
export const DeleteColumn = styled.div`
|
||||||
background-image: url('images/RemoveColumn.svg');
|
background-image: url('images/RemoveColumn.svg');
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
background: #fcfcfd;
|
background: #fcfcfd;
|
||||||
color: #171a1c;
|
color: #171a1c;
|
||||||
|
/* color: green; */
|
||||||
font-family:
|
font-family:
|
||||||
-apple-system,
|
-apple-system,
|
||||||
Segoe UI,
|
Segoe UI,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user