Добавление настроек в localStorage

This commit is contained in:
test 2025-07-01 17:21:42 +05:00
parent f8ddae0f94
commit 11237605b9
4 changed files with 137 additions and 63 deletions

View file

@ -66,19 +66,23 @@ const LabelCheckbox = styled.label`
} }
` `
interface SettingItemProps {
setting: Setting;
value: any;
onChange: (value: any) => void;
}
export const SettingItem = ({ setting }: { setting: Setting }) => { export const SettingItem = ({ setting, value, onChange }: SettingItemProps) => {
const { updateSetting } = useSettingsStore();
const theme = useTheme(); const theme = useTheme();
const { updateSetting } = useSettingsStore();
const handleChange = ( const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
e: ChangeEvent<HTMLInputElement | HTMLSelectElement> const newValue = setting.type === 'boolean'
) => { ? (e.target as HTMLInputElement).checked
const value = : setting.type === 'number'
setting.type === 'boolean' ? Number(e.target.value)
? (e.target as HTMLInputElement).checked
: e.target.value; : e.target.value;
updateSetting(setting.id, value); onChange(newValue);
}; };
return ( return (
@ -122,7 +126,7 @@ export const SettingItem = ({ setting }: { setting: Setting }) => {
<MainButton <MainButton
type="button" type="button"
size="small" size="small"
title={setting.value} title={setting.defaultValue || ''}
/> />
)} )}
</SettingBlock> </SettingBlock>

View file

@ -53,32 +53,45 @@ const CloseBtn = styled.span`
` `
export const Settings = ({onClose}: Props) => { export const Settings = ({onClose}: Props) => {
const {settings, activeSection, setActiveSection, initializeSettings} = useSettingsStore(); const {
settingsConfig,
settingsValues,
activeSection,
setActiveSection,
initializeSettings,
updateSetting
} = useSettingsStore();
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 = Object.keys(settingsBySection);
useEffect(() => { useEffect(() => {
initializeSettings(); initializeSettings();
}, [initializeSettings]); }, [initializeSettings]);
const settingsBySection = useMemo(() => {
return settings.reduce((acc, setting) => {
if (!acc[setting.sectionId]) {
acc[setting.sectionId] = [];
}
acc[setting.sectionId].push(setting);
return acc;
}, {} as Record<string, Setting[]>);
}, [settings]);
const sections = Object.keys(settingsBySection);
useEffect(() => { useEffect(() => {
if (sections.length > 0 && !activeSection) { if (sections.length > 0 && !activeSection) {
setActiveSection(sections[0]); setActiveSection(sections[0]);
} }
}, [sections, activeSection, setActiveSection]); }, [sections, activeSection, setActiveSection]);
const handleSave = () => {
onClose();
};
return ( return (
<ModalContainer style={{ height: '60vh', width: '50vw'}}> <ModalContainer style={{ height: '60vh', width: '50vw'}}>
<ModalTitle> <ModalTitle>
@ -104,8 +117,13 @@ export const Settings = ({onClose}: Props) => {
<SectionActive> <SectionActive>
{activeSection && settingsBySection[activeSection] && ( {activeSection && settingsBySection[activeSection] && (
<> <>
{settingsBySection[activeSection].map((setting)=>( {settingsBySection[activeSection].map((setting) => (
<SettingItem setting={setting} key={setting.id}/> <SettingItem
key={setting.id}
setting={setting}
value={settingsValues[setting.id] ?? setting.defaultValue}
onChange={(value) => updateSetting(setting.id, value)}
/>
))} ))}
</> </>
)} )}
@ -117,7 +135,7 @@ export const Settings = ({onClose}: Props) => {
type="button" type="button"
size="small" size="small"
title='Сохранить' title='Сохранить'
onClick={onClose} onClick={handleSave}
/> />
</ButtonContainer> </ButtonContainer>
</ModalContainer> </ModalContainer>

View file

@ -1,4 +1,4 @@
import React, {useState} from 'react'; import React, {useEffect, useState} from 'react';
import { DeviceUser } from '../../../../../../store/useDeviceStore'; import { DeviceUser } from '../../../../../../store/useDeviceStore';
interface TableScannerProps { interface TableScannerProps {
@ -10,6 +10,7 @@ import {buttonNames} from "../../../../../../const/buttons/names";
import {ModalMap} from "../../../../../modalwindows/windowsType"; import {ModalMap} from "../../../../../modalwindows/windowsType";
import {useModalContext} from "../../../../../../store"; import {useModalContext} from "../../../../../../store";
import Pagination from "../../../../../UI/Pagination"; import Pagination from "../../../../../UI/Pagination";
import {useSettingsStore} from "../../../../../../store/settingsStore";
const TableContainer = styled.div` const TableContainer = styled.div`
width: 100%; width: 100%;
@ -74,9 +75,19 @@ const TableScanner = ({devices}:TableScannerProps) => {
}); });
}; };
const paginateSetting = useSettingsStore(state =>
state.settingsValues['paginate-nodes'] ??
state.settingsConfig.find(s => s.id === 'paginate-nodes')?.defaultValue ??
15
);
// Пагинация // Пагинация
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [devicesPerPage] = useState(15); const [devicesPerPage, setDevicesPerPage] = useState(paginateSetting);
useEffect(() => {
setDevicesPerPage(paginateSetting);
}, [paginateSetting]);
const indexOfLastDevice = currentPage * devicesPerPage; const indexOfLastDevice = currentPage * devicesPerPage;
const indexOfFirstDevice = indexOfLastDevice - devicesPerPage; const indexOfFirstDevice = indexOfLastDevice - devicesPerPage;

View file

@ -1,10 +1,15 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware';
type SettingValue = any;
type SettingsDictionary = Record<string, SettingValue>;
export type Setting = { export type Setting = {
id: string; id: string;
type: 'boolean' | 'select' | 'text' | 'number' | 'button'; type: 'boolean' | 'select' | 'text' | 'number' | 'button';
label: string; label: string;
value: any; defaultValue: any;
value?: string;
min?: number; min?: number;
max?: number; max?: number;
options?: { value: any; label: string }[]; options?: { value: any; label: string }[];
@ -12,42 +17,31 @@ export type Setting = {
}; };
type SettingsState = { type SettingsState = {
settings: Setting[]; settingsConfig: Setting[];
settingsValues: SettingsDictionary;
activeSection: string | null; activeSection: string | null;
setActiveSection: (sectionId: string | null) => void; setActiveSection: (sectionId: string | null) => void;
updateSetting: (id: string, value: any) => void; updateSetting: (id: string, value: any) => void;
initializeSettings: () => void; initializeSettings: () => void;
getSetting: (id: string) => Setting & { currentValue: any };
resetSettings: () => void;
}; };
export const useSettingsStore = create<SettingsState>((set) => ({ const defaultSettings: Setting[] = [
settings: [],
activeSection: null,
setActiveSection: (sectionId) => set({ activeSection: sectionId }),
updateSetting: (id, value) =>
set((state) => ({
settings: state.settings.map((setting) =>
setting.id === id ? { ...setting, value } : setting
),
})),
initializeSettings: () => {
const defaultSettings: Setting[] = [
// Узлы подсети // Узлы подсети
{ {
id: 'rescan-nodes', id: 'rescan-nodes',
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
label: 'Интервальное сканирование', label: 'Интервальное сканирование',
type: 'boolean', type: 'boolean',
value: false, defaultValue: false,
}, },
{ {
id: 'interval-nodes', id: 'interval-nodes',
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
label: 'Интервал сканирования узлов (мин)', label: 'Интервал сканирования узлов (мин)',
type: 'number', type: 'number',
value: 60, defaultValue: 60,
min: 1, min: 1,
max: 2880 max: 2880
}, },
@ -56,7 +50,7 @@ export const useSettingsStore = create<SettingsState>((set) => ({
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
label: 'Отображаемые имена при отсутствии', label: 'Отображаемые имена при отсутствии',
type: 'select', type: 'select',
value: 'ip', defaultValue: 'ip',
options: [ options: [
{ value: 'ip', label: 'IP адрес' }, { value: 'ip', label: 'IP адрес' },
{ value: 'unknown', label: 'Неизвестно' }, { value: 'unknown', label: 'Неизвестно' },
@ -65,9 +59,9 @@ export const useSettingsStore = create<SettingsState>((set) => ({
{ {
id: 'paginate-nodes', id: 'paginate-nodes',
type: 'number', type: 'number',
label: 'Количество строк на таблице', label: 'Количество строк в таблице',
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
value: 20, defaultValue: 20,
min: 1, min: 1,
}, },
{ {
@ -75,21 +69,21 @@ export const useSettingsStore = create<SettingsState>((set) => ({
type: 'button', type: 'button',
label: 'Отображение узлов', label: 'Отображение узлов',
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
value: 'Настроить', defaultValue: 'Настроить',
}, },
{ {
id: 'clean-nodes', id: 'clean-nodes',
type: 'button', type: 'button',
label: 'Очистить данные сессии', label: 'Очистить данные сессии',
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
value: 'Очистить', defaultValue: 'Очистить',
}, },
{ {
id: 'export-nodes', id: 'export-nodes',
type: 'button', type: 'button',
label: 'Экспорт данных узлов', label: 'Экспорт данных узлов',
sectionId: 'Узлы подсети', sectionId: 'Узлы подсети',
value: 'Экспортировать', defaultValue: 'Экспортировать',
}, },
// Точки доступа // Точки доступа
@ -98,14 +92,14 @@ export const useSettingsStore = create<SettingsState>((set) => ({
type: 'button', type: 'button',
label: 'Очистить данные сессии', label: 'Очистить данные сессии',
sectionId: 'Точки доступа', sectionId: 'Точки доступа',
value: 'Очистить', defaultValue: 'Очистить',
}, },
{ {
id: 'export-network', id: 'export-network',
type: 'button', type: 'button',
label: 'Экспорт данных точек доступа', label: 'Экспорт данных точек доступа',
sectionId: 'Точки доступа', sectionId: 'Точки доступа',
value: 'Экспортировать', defaultValue: 'Экспортировать',
}, },
// Внешний вид // Внешний вид
@ -114,7 +108,7 @@ export const useSettingsStore = create<SettingsState>((set) => ({
sectionId: 'Внешний вид', sectionId: 'Внешний вид',
label: 'Цветовая тема', label: 'Цветовая тема',
type: 'select', type: 'select',
value: 'light', defaultValue: 'light',
options: [ options: [
{ value: 'light', label: 'Светлая' }, { value: 'light', label: 'Светлая' },
{ value: 'dark', label: 'Тёмная' }, { value: 'dark', label: 'Тёмная' },
@ -126,13 +120,60 @@ export const useSettingsStore = create<SettingsState>((set) => ({
sectionId: 'Внешний вид', sectionId: 'Внешний вид',
label: 'Язык интерфейса', label: 'Язык интерфейса',
type: 'select', type: 'select',
value: 'ru', defaultValue: 'ru',
options: [ options: [
{ value: 'ru', label: 'Русский' }, { value: 'ru', label: 'Русский' },
{ value: 'en', label: 'Английский' }, { value: 'en', label: 'Английский' },
], ],
}, },
]; ];
set({ settings: defaultSettings });
}, export const useSettingsStore = create<SettingsState>()(
})); persist(
(set, get) => ({
settingsConfig: defaultSettings,
settingsValues: {},
activeSection: null,
setActiveSection: (sectionId) => set({ activeSection: sectionId }),
updateSetting: (id, value) =>
set((state) => ({
settingsValues: { ...state.settingsValues, [id]: value }
})),
initializeSettings: () => {
const { settingsValues, settingsConfig } = get();
const newValues = { ...settingsValues };
settingsConfig.forEach(setting => {
if (newValues[setting.id] === undefined) {
newValues[setting.id] = setting.defaultValue;
}
});
set({ settingsValues: newValues });
},
getSetting: (id) => {
const setting = get().settingsConfig.find(s => s.id === id);
if (!setting) throw new Error(`Setting ${id} not found`);
return {
...setting,
currentValue: get().settingsValues[id] ?? setting.defaultValue
};
},
resetSettings: () => set({ settingsValues: {} }),
}),
{
name: 'app-settings',
partialize: (state) => ({
settingsValues: state.settingsValues
}),
onRehydrateStorage: () => (state) => {
state?.initializeSettings();
}
}
)
);