добавлене программ, проектов в 4 форме

This commit is contained in:
PotapovaA 2026-07-28 16:03:52 +03:00
parent d257c3f1c1
commit 8c978b4e33
15 changed files with 335 additions and 188 deletions

View 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

View 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="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

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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>

View File

@ -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}
/>
</> </>
); );
}; };

View File

@ -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>
</> </>
} }

View File

@ -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);

View File

@ -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;
}); });
}); });

View File

@ -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;
} }

View File

@ -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;
}

View 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}
/>
);

View File

@ -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;

View File

@ -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;

View File

@ -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,