2025-06-27 17:50:08 +05:00
|
|
|
|
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../modalwindows/style";
|
|
|
|
|
|
import { MainButton } from "../UI/buttons/mainButton";
|
2025-07-02 17:38:09 +05:00
|
|
|
|
import {useCallback, useEffect, useMemo, useState} from "react";
|
2025-06-27 17:50:08 +05:00
|
|
|
|
import styled from "styled-components";
|
2025-07-02 17:38:09 +05:00
|
|
|
|
import {Setting, useSettingsStore} from "../../store/settings/settingsStore";
|
2025-06-27 17:50:08 +05:00
|
|
|
|
import {SettingItem} from "./SettingItem";
|
|
|
|
|
|
import {IconComponent} from "../../const/buttons/icons";
|
|
|
|
|
|
import {buttonNames} from "../../const/buttons/names";
|
|
|
|
|
|
import {ModalMap} from "../modalwindows/windowsType";
|
|
|
|
|
|
import {workSpaceNames} from "../../const/workspace/names";
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const SectionsSidebar = styled.ul`
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: start;
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
width: 20%;
|
|
|
|
|
|
min-width: 180px;
|
|
|
|
|
|
list-style-type: none;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
`
|
|
|
|
|
|
const SectionsBTN = styled.li`
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding:10px 16px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
border-radius: 0 12px 12px 0;
|
|
|
|
|
|
&.active{
|
|
|
|
|
|
background-color: ${(props) => props.theme.accent};
|
|
|
|
|
|
color: ${props => props.theme.primary};
|
|
|
|
|
|
}
|
|
|
|
|
|
`
|
|
|
|
|
|
const SectionActive = styled.div`
|
|
|
|
|
|
width: 80%;
|
|
|
|
|
|
`
|
|
|
|
|
|
const SectionLine = styled.span`
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
width: 1px;
|
|
|
|
|
|
background-color: #9D9D9D;
|
|
|
|
|
|
`
|
|
|
|
|
|
const CloseBtn = styled.span`
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
`
|
2025-07-02 17:38:09 +05:00
|
|
|
|
export const Settings = ({ onClose }: Props) => {
|
|
|
|
|
|
const settingsConfig = useSettingsStore(state => state.settingsConfig);
|
|
|
|
|
|
const settingsValues = useSettingsStore(state => state.settingsValues);
|
|
|
|
|
|
const activeSection = useSettingsStore(state => state.activeSection);
|
|
|
|
|
|
const pendingChanges = useSettingsStore(state => state.pendingChanges);
|
|
|
|
|
|
const setActiveSection = useSettingsStore(state => state.setActiveSection);
|
|
|
|
|
|
const updateSetting = useSettingsStore(state => state.updateSetting);
|
|
|
|
|
|
const applyChanges = useSettingsStore(state => state.applyChanges);
|
|
|
|
|
|
const discardChanges = useSettingsStore(state => state.discardChanges);
|
|
|
|
|
|
const getSetting = useSettingsStore(state => state.getSetting);
|
|
|
|
|
|
const initializeSettings = useSettingsStore(state => state.initializeSettings);
|
2025-06-27 17:50:08 +05:00
|
|
|
|
|
2025-07-02 17:38:09 +05:00
|
|
|
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
|
|
|
|
|
|
|
|
|
|
|
const handleValidationError = useCallback((fieldId: string, error: string | null) => {
|
|
|
|
|
|
setFieldErrors(prev => {
|
|
|
|
|
|
const newErrors = { ...prev };
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
newErrors[fieldId] = error;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
delete newErrors[fieldId];
|
|
|
|
|
|
}
|
|
|
|
|
|
return newErrors;
|
|
|
|
|
|
});
|
|
|
|
|
|
}, []);
|
2025-06-27 17:50:08 +05:00
|
|
|
|
|
|
|
|
|
|
const settingsBySection = useMemo(() => {
|
2025-07-01 17:21:42 +05:00
|
|
|
|
return settingsConfig.reduce((acc, setting) => {
|
2025-06-27 17:50:08 +05:00
|
|
|
|
if (!acc[setting.sectionId]) {
|
|
|
|
|
|
acc[setting.sectionId] = [];
|
|
|
|
|
|
}
|
2025-07-01 17:21:42 +05:00
|
|
|
|
acc[setting.sectionId].push({
|
|
|
|
|
|
...setting,
|
|
|
|
|
|
value: settingsValues[setting.id] ?? setting.defaultValue
|
|
|
|
|
|
});
|
2025-06-27 17:50:08 +05:00
|
|
|
|
return acc;
|
|
|
|
|
|
}, {} as Record<string, Setting[]>);
|
2025-07-01 17:21:42 +05:00
|
|
|
|
}, [settingsConfig, settingsValues]);
|
2025-06-27 17:50:08 +05:00
|
|
|
|
|
2025-07-02 17:38:09 +05:00
|
|
|
|
const sections = useMemo(() => Object.keys(settingsBySection), [settingsBySection]);
|
2025-06-27 17:50:08 +05:00
|
|
|
|
|
2025-07-01 17:21:42 +05:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
initializeSettings();
|
|
|
|
|
|
}, [initializeSettings]);
|
2025-06-27 17:50:08 +05:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (sections.length > 0 && !activeSection) {
|
|
|
|
|
|
setActiveSection(sections[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [sections, activeSection, setActiveSection]);
|
|
|
|
|
|
|
2025-07-02 17:38:09 +05:00
|
|
|
|
const handleSave = useCallback(() => {
|
|
|
|
|
|
const hasErrors = Object.values(fieldErrors).some(error => error?.trim());
|
|
|
|
|
|
if (hasErrors) {
|
|
|
|
|
|
alert('Исправьте ошибки перед сохранением');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const requiredFields = settingsConfig.filter(s => s.required);
|
|
|
|
|
|
const emptyRequiredFields = requiredFields.filter(f => {
|
|
|
|
|
|
const value = pendingChanges[f.id] ?? settingsValues[f.id] ?? f.defaultValue;
|
|
|
|
|
|
return value == null || value === '';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (emptyRequiredFields.length > 0) {
|
|
|
|
|
|
alert('Заполните все обязательные поля');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
applyChanges();
|
2025-07-01 17:21:42 +05:00
|
|
|
|
onClose();
|
2025-07-02 17:38:09 +05:00
|
|
|
|
}, [fieldErrors, settingsConfig, pendingChanges, settingsValues, applyChanges, onClose]);
|
2025-07-01 17:21:42 +05:00
|
|
|
|
|
2025-07-02 17:38:09 +05:00
|
|
|
|
const handleCancel = useCallback(() => {
|
|
|
|
|
|
discardChanges();
|
|
|
|
|
|
setFieldErrors({});
|
|
|
|
|
|
onClose();
|
|
|
|
|
|
}, [discardChanges, onClose]);
|
2025-07-01 17:21:42 +05:00
|
|
|
|
|
2025-06-27 17:50:08 +05:00
|
|
|
|
return (
|
|
|
|
|
|
<ModalContainer style={{ height: '60vh', width: '50vw'}}>
|
|
|
|
|
|
<ModalTitle>
|
|
|
|
|
|
<h3>Настройки</h3>
|
|
|
|
|
|
<CloseBtn>
|
2025-07-02 17:38:09 +05:00
|
|
|
|
<IconComponent name={buttonNames[12]} width="30" height="30" onClick={handleCancel} />
|
2025-06-27 17:50:08 +05:00
|
|
|
|
</CloseBtn>
|
|
|
|
|
|
</ModalTitle>
|
|
|
|
|
|
|
|
|
|
|
|
<ModalContent style={{ display: 'flex', height:"100%", gap: '24px', padding:0 }}>
|
|
|
|
|
|
<SectionsSidebar>
|
|
|
|
|
|
{sections.map((sectionId) => (
|
|
|
|
|
|
<SectionsBTN
|
|
|
|
|
|
key={sectionId}
|
|
|
|
|
|
className={activeSection === sectionId ? 'active' : ''}
|
|
|
|
|
|
onClick={()=>setActiveSection(sectionId)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{sectionId}
|
|
|
|
|
|
</SectionsBTN>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SectionsSidebar>
|
|
|
|
|
|
<SectionLine/>
|
|
|
|
|
|
<SectionActive>
|
|
|
|
|
|
{activeSection && settingsBySection[activeSection] && (
|
|
|
|
|
|
<>
|
2025-07-01 17:21:42 +05:00
|
|
|
|
{settingsBySection[activeSection].map((setting) => (
|
|
|
|
|
|
<SettingItem
|
|
|
|
|
|
key={setting.id}
|
|
|
|
|
|
setting={setting}
|
2025-07-02 17:38:09 +05:00
|
|
|
|
value={getSetting(setting.id).currentValue}
|
|
|
|
|
|
onValidationError={(error) => handleValidationError(setting.id, error)}
|
2025-07-01 17:21:42 +05:00
|
|
|
|
onChange={(value) => updateSetting(setting.id, value)}
|
|
|
|
|
|
/>
|
2025-06-27 17:50:08 +05:00
|
|
|
|
))}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</SectionActive>
|
|
|
|
|
|
</ModalContent>
|
|
|
|
|
|
|
|
|
|
|
|
<ButtonContainer>
|
|
|
|
|
|
<MainButton
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
title='Сохранить'
|
2025-07-01 17:21:42 +05:00
|
|
|
|
onClick={handleSave}
|
2025-07-02 17:38:09 +05:00
|
|
|
|
|
2025-06-27 17:50:08 +05:00
|
|
|
|
/>
|
|
|
|
|
|
</ButtonContainer>
|
|
|
|
|
|
</ModalContainer>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|