186 lines
No EOL
6.5 KiB
TypeScript
186 lines
No EOL
6.5 KiB
TypeScript
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../modalwindows/style";
|
||
import { MainButton } from "../UI/buttons/mainButton";
|
||
import {useCallback, useEffect, useMemo, useState} from "react";
|
||
import styled from "styled-components";
|
||
import {Setting, useSettingsStore} from "../../store/settings/settingsStore";
|
||
import {SettingItem} from "./SettingItem";
|
||
import {IconComponent} from "../../const/buttons/icons";
|
||
import {buttonNames} from "../../const/buttons/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;
|
||
`
|
||
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);
|
||
|
||
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;
|
||
});
|
||
}, []);
|
||
|
||
const settingsBySection = useMemo(() => {
|
||
return settingsConfig.reduce((acc, setting) => {
|
||
if (!acc[setting.sectionId]) {
|
||
acc[setting.sectionId] = [];
|
||
}
|
||
acc[setting.sectionId].push({
|
||
...setting,
|
||
value: settingsValues[setting.id] ?? setting.defaultValue
|
||
});
|
||
return acc;
|
||
}, {} as Record<string, Setting[]>);
|
||
}, [settingsConfig, settingsValues]);
|
||
|
||
const sections = useMemo(() => Object.keys(settingsBySection), [settingsBySection]);
|
||
|
||
useEffect(() => {
|
||
initializeSettings();
|
||
}, [initializeSettings]);
|
||
|
||
useEffect(() => {
|
||
if (sections.length > 0 && !activeSection) {
|
||
setActiveSection(sections[0]);
|
||
}
|
||
}, [sections, activeSection, setActiveSection]);
|
||
|
||
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();
|
||
onClose();
|
||
}, [fieldErrors, settingsConfig, pendingChanges, settingsValues, applyChanges, onClose]);
|
||
|
||
const handleCancel = useCallback(() => {
|
||
discardChanges();
|
||
setFieldErrors({});
|
||
onClose();
|
||
}, [discardChanges, onClose]);
|
||
|
||
return (
|
||
<ModalContainer style={{ height: '60vh', width: '50vw', minWidth: "800px" }}>
|
||
<ModalTitle>
|
||
<h3>Настройки</h3>
|
||
<CloseBtn>
|
||
<IconComponent name={buttonNames[12]} width="30" height="30" onClick={handleCancel} />
|
||
</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] && (
|
||
<>
|
||
{settingsBySection[activeSection].map((setting) => (
|
||
<SettingItem
|
||
key={setting.id}
|
||
setting={setting}
|
||
value={getSetting(setting.id).currentValue}
|
||
onValidationError={(error) => handleValidationError(setting.id, error)}
|
||
onChange={(value) => updateSetting(setting.id, value)}
|
||
/>
|
||
))}
|
||
</>
|
||
)}
|
||
</SectionActive>
|
||
</ModalContent>
|
||
|
||
<ButtonContainer>
|
||
<MainButton
|
||
type="button"
|
||
size="small"
|
||
title='Отменить'
|
||
onClick={handleCancel}
|
||
|
||
/>
|
||
<MainButton
|
||
type="button"
|
||
size="small"
|
||
title='Сохранить'
|
||
onClick={handleSave}
|
||
|
||
/>
|
||
</ButtonContainer>
|
||
</ModalContainer>
|
||
);
|
||
}; |