From f8ddae0f94a47cf728e77a9b564ae25c1dc11807 Mon Sep 17 00:00:00 2001 From: test Date: Fri, 27 Jun 2025 17:50:08 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B8=D0=B9=20=D0=B2?= =?UTF-8?q?=D0=B8=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/UI/buttons/mainButton.tsx | 6 +- .../modalContainers/SettingItem.tsx | 47 ------- .../modalwindows/modalContainers/Settings.tsx | 72 ---------- .../components/modalwindows/modalWindow.tsx | 2 +- .../src/components/settings/SettingItem.tsx | 130 ++++++++++++++++++ .../src/components/settings/SettingSelect.tsx | 118 ++++++++++++++++ .../DEV/src/components/settings/Settings.tsx | 125 +++++++++++++++++ FRONT/DEV/src/components/settings/Test.tsx | 87 ++++++++++++ FRONT/DEV/src/const/buttons/icons.tsx | 2 + FRONT/DEV/src/const/buttons/names.ts | 1 + FRONT/DEV/src/store/settingsStore.ts | 100 +++++++++++++- 11 files changed, 563 insertions(+), 127 deletions(-) delete mode 100644 FRONT/DEV/src/components/modalwindows/modalContainers/SettingItem.tsx delete mode 100644 FRONT/DEV/src/components/modalwindows/modalContainers/Settings.tsx create mode 100644 FRONT/DEV/src/components/settings/SettingItem.tsx create mode 100644 FRONT/DEV/src/components/settings/SettingSelect.tsx create mode 100644 FRONT/DEV/src/components/settings/Settings.tsx create mode 100644 FRONT/DEV/src/components/settings/Test.tsx diff --git a/FRONT/DEV/src/components/UI/buttons/mainButton.tsx b/FRONT/DEV/src/components/UI/buttons/mainButton.tsx index 55ca7e3..a1bbfca 100644 --- a/FRONT/DEV/src/components/UI/buttons/mainButton.tsx +++ b/FRONT/DEV/src/components/UI/buttons/mainButton.tsx @@ -17,10 +17,10 @@ const Button = styled.button.withConfig({ color: white; font-size: ${(props) => props.size === "small" - ? "14px" + ? "16px" : props.size === "big" - ? "16px" - : "14px"}; // По умолчанию + ? "18px" + : "16px"}; // По умолчанию cursor: pointer; display: flex; justify-content: center; diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/SettingItem.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/SettingItem.tsx deleted file mode 100644 index f2252f0..0000000 --- a/FRONT/DEV/src/components/modalwindows/modalContainers/SettingItem.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useSettingsStore } from '../stores/settingsStore'; - -export const SettingItem = ({ setting }: { setting: Setting }) => { - const { updateSetting } = useSettingsStore(); - - const handleChange = ( - e: React.ChangeEvent - ) => { - const value = - setting.type === 'boolean' - ? (e.target as HTMLInputElement).checked - : e.target.value; - updateSetting(setting.id, value); - }; - - return ( -
- - - {setting.type === 'boolean' && ( - - )} - - {setting.type === 'select' && setting.options && ( - - )} - - {setting.type === 'text' && ( - - )} -
- ); -}; \ No newline at end of file diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/Settings.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/Settings.tsx deleted file mode 100644 index d43a11d..0000000 --- a/FRONT/DEV/src/components/modalwindows/modalContainers/Settings.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// components/modalContainers/NoteDevice.tsx -import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style"; -import { MainButton } from "../../UI/buttons/mainButton"; -import {useEffect, useState} from "react"; -import styled from "styled-components"; -import { useSettingsStore } from "../../../store/settingsStore"; - -interface Props { - onClose: () => void; -} - -const SectionsSidebar = styled.div` - display: flex; - justify-content: start; - align-items: start; - flex-direction: column; - gap: 12px; - width: 20%; - -` -const SectionsBTN = styled.button` - font-size: 18px; - border: none; - cursor: pointer; -` -const SectionActive = styled.div` - width: 80%; - background-color: red; -` - -const sections = [ - {id: 'nodesNetwork', label: 'Узлы подсети'}, - {id: 'wifiNetwork', label: 'Точки доступа'}, - {id: 'appearance', label: 'Внешний вид'}, -] - - -export const Settings = ({onClose}: Props) => { - const {activeSection, setActiveSection, initializeSettings} = useSettingsStore(); - - useEffect(() => { - initializeSettings(); - }, [initializeSettings]); - - return ( - - -

Настройки

-
- - - - {sections.map((section) => ( - {section.label} - ))} - - - {activeSection && } - - - - - - -
- ); -}; \ No newline at end of file diff --git a/FRONT/DEV/src/components/modalwindows/modalWindow.tsx b/FRONT/DEV/src/components/modalwindows/modalWindow.tsx index 487372c..b6dbac9 100644 --- a/FRONT/DEV/src/components/modalwindows/modalWindow.tsx +++ b/FRONT/DEV/src/components/modalwindows/modalWindow.tsx @@ -7,7 +7,7 @@ import {NoteDevice} from "./modalContainers/NoteDevice"; import { NoteWifi } from "./modalContainers/NoteWifi"; import {ConnectNetwork} from "./modalContainers/ConnectNetwork"; import {DisconnectConfirm} from "./modalContainers/disconnectConfirm"; -import {Settings} from "./modalContainers/Settings"; +import {Settings} from "../settings/Settings"; type Props = { isOpen: boolean, diff --git a/FRONT/DEV/src/components/settings/SettingItem.tsx b/FRONT/DEV/src/components/settings/SettingItem.tsx new file mode 100644 index 0000000..445f29d --- /dev/null +++ b/FRONT/DEV/src/components/settings/SettingItem.tsx @@ -0,0 +1,130 @@ +import {Setting, useSettingsStore} from "../../store/settingsStore"; +import {ChangeEvent} from "react"; +import styled, {useTheme} from "styled-components"; +import {IconComponent} from "../../const/buttons/icons"; +import {buttonNames} from "../../const/buttons/names"; +import SettingSelect from "./SettingSelect"; +import {MainButton} from "../UI/buttons/mainButton"; + +const SettingBlock = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + gap: 24px; + margin-bottom: 18px; + font-size: 18px; + width: 90%; + input{ + font-size: 16px; + outline: none; + background-color: ${props => props.theme.primary}; + border: 1px solid ${props => props.theme.borderColor}; + padding: 6px 8px; + border-radius: 4px; + } + input[type="number"]{ + width: 80px; + } + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } +` +const StyledCheckbox = styled.input.attrs({ type: 'checkbox' })` + width: 22px !important; + height: 22px !important; + appearance: none; + -webkit-appearance: none; + position: relative; + cursor: pointer; + overflow: hidden; + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: ${props => props.theme.primary}; + top: 0; + left: 0; + } + &:checked::after { + background: ${props => props.theme.accent}; + } + +`; +const LabelCheckbox = styled.label` + position: relative; + cursor: pointer; + padding: 0; + svg{ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +` + + +export const SettingItem = ({ setting }: { setting: Setting }) => { + const { updateSetting } = useSettingsStore(); + const theme = useTheme(); + + const handleChange = ( + e: ChangeEvent + ) => { + const value = + setting.type === 'boolean' + ? (e.target as HTMLInputElement).checked + : e.target.value; + updateSetting(setting.id, value); + }; + + return ( + + + + {setting.type === 'boolean' && ( + + + + + )} + + {setting.type === 'select' && setting.options && ( + updateSetting(setting.id, value)} + /> + )} + + {setting.type === 'text' && ( + + )} + + {setting.type === 'number' && ( + + )} + + {setting.type === 'button' && ( + + )} + + ); +}; \ No newline at end of file diff --git a/FRONT/DEV/src/components/settings/SettingSelect.tsx b/FRONT/DEV/src/components/settings/SettingSelect.tsx new file mode 100644 index 0000000..d6ff465 --- /dev/null +++ b/FRONT/DEV/src/components/settings/SettingSelect.tsx @@ -0,0 +1,118 @@ +import React, { useState, useRef, useEffect } from 'react'; +import styled from 'styled-components'; +import { IconComponent } from '../../const/buttons/icons'; +import {buttonNames} from "../../const/buttons/names"; + +interface SettingSelectProps { + value: any; + options: { value: any; label: string }[]; + onChange: (value: any) => void; + disabled?: boolean; +} + +const SelectWrapper = styled.div` + position: relative; + width: 100%; + max-width: 180px; +`; + +const SelectHeader = styled.div<{ $isOpen: boolean; $disabled?: boolean }>` + padding: 6px 8px; + border: 1px solid ${props => props.theme.borderColor}; + border-radius: 4px; + background-color: ${props => props.theme.primary}; + cursor: ${props => props.$disabled ? 'not-allowed' : 'pointer'}; + display: flex; + justify-content: space-between; + align-items: center; + opacity: ${props => props.$disabled ? 0.6 : 1}; + font-size: 16px; +`; + +const SelectOptions = styled.div<{ $isOpen: boolean }>` + position: absolute; + top: 100%; + left: 0; + right: 0; + margin-top: 4px; + max-height: 200px; + overflow-y: auto; + background: ${props => props.theme.primary}; + border: 1px solid ${props => props.theme.borderColor || '#ccc'}; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + z-index: 10; + display: ${props => props.$isOpen ? 'block' : 'none'}; +`; + +const OptionItem = styled.div<{ $isSelected: boolean }>` + padding: 6px 8px; + cursor: pointer; + background-color: ${props => props.$isSelected ? (props.theme.accent) : 'transparent'}; + color: ${props => props.$isSelected ? (props.theme.primary) : 'inherit'}; +`; + +const ArrowIcon = styled.span<{ $isOpen: boolean }>` + transition: transform 0.2s; + transform: rotate(${props => props.$isOpen ? '90deg' : '0deg'}); + display: flex; + align-items: center; +`; + +const SettingSelect: React.FC = ({value, options, onChange, disabled = false + }) => { + const [isOpen, setIsOpen] = useState(false); + const selectRef = useRef(null); + + const selectedOption = options.find(opt => opt.value === value); + + const toggleDropdown = () => { + if (!disabled) { + setIsOpen(!isOpen); + } + }; + + const handleSelect = (optionValue: any) => { + onChange(optionValue); + setIsOpen(false); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (selectRef.current && !selectRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + return ( + + + {selectedOption?.label || 'Select...'} + + + + + + {options.map((option) => ( + handleSelect(option.value)} + > + {option.label} + + ))} + + + ); +}; + +export default SettingSelect; \ No newline at end of file diff --git a/FRONT/DEV/src/components/settings/Settings.tsx b/FRONT/DEV/src/components/settings/Settings.tsx new file mode 100644 index 0000000..097c509 --- /dev/null +++ b/FRONT/DEV/src/components/settings/Settings.tsx @@ -0,0 +1,125 @@ +// components/modalContainers/NoteDevice.tsx +import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../modalwindows/style"; +import { MainButton } from "../UI/buttons/mainButton"; +import {useEffect, useMemo, useState} from "react"; +import styled from "styled-components"; +import {Setting, useSettingsStore} from "../../store/settingsStore"; +import {SettingItem} from "./SettingItem"; +import {IconComponent} from "../../const/buttons/icons"; +import {buttonNames} from "../../const/buttons/names"; +import {ModalMap} from "../modalwindows/windowsType"; +import Test from "./Test"; +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; +` + +export const Settings = ({onClose}: Props) => { + const {settings, activeSection, setActiveSection, initializeSettings} = useSettingsStore(); + + useEffect(() => { + 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); + }, [settings]); + + + const sections = Object.keys(settingsBySection); + + + useEffect(() => { + if (sections.length > 0 && !activeSection) { + setActiveSection(sections[0]); + } + }, [sections, activeSection, setActiveSection]); + + return ( + + +

Настройки

+ + onClose()} /> + +
+ + + + {sections.map((sectionId) => ( + setActiveSection(sectionId)} + > + {sectionId} + + ))} + + + + {activeSection && settingsBySection[activeSection] && ( + <> + {settingsBySection[activeSection].map((setting)=>( + + ))} + + )} + + + + + + +
+ ); +}; \ No newline at end of file diff --git a/FRONT/DEV/src/components/settings/Test.tsx b/FRONT/DEV/src/components/settings/Test.tsx new file mode 100644 index 0000000..3d6d654 --- /dev/null +++ b/FRONT/DEV/src/components/settings/Test.tsx @@ -0,0 +1,87 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; + +const DraggableList = () => { + const [items, setItems] = useState([ + 'Item 1', + 'Item 2', + 'Item 3', + 'Item 4', + 'Item 5', + ]); + const [draggingIndex, setDraggingIndex] = useState(null); + + const handleDragStart = (index) => { + setDraggingIndex(index); + }; + + const handleDragOver = (index) => { + // Останавливаем стандартное поведение браузера + if (index !== draggingIndex) { + setItems((prevItems) => { + const newItems = [...prevItems]; + const [movedItem] = newItems.splice(draggingIndex, 1); // Удаляем перетаскиваемый элемент + newItems.splice(index, 0, movedItem); // Вставляем его в новое место + return newItems; + }); + setDraggingIndex(index); // Обновляем индекс для перетаскивания + } + }; + + const handleDrop = () => { + setDraggingIndex(null); // Сбрасываем состояние перетаскивания + }; + + return ( + + {items.map((item, index) => ( + handleDragStart(index)} + onDragOver={(e) => { + e.preventDefault(); // Останавливаем стандартное поведение + handleDragOver(index); + }} + onDragEnd={handleDrop} + > + {item} + + ))} + + ); +}; + +export default DraggableList; + +// Стилизация с помощью styled-components + +const StyledList = styled.ul` + list-style-type: none; + padding: 0; + margin: 0; +`; + +const StyledItem = styled.li` + background-color: #f4f4f9; + margin: 8px 0; + padding: 16px; + border-radius: 4px; + cursor: grab; + transition: transform 0.2s ease, background-color 0.3s ease; + + &:active { + transform: scale(0.98); + } + + &.dragging { + background-color: #d0e1f9; + transform: scale(1.05); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + } + + &:hover { + background-color: #e0e0e0; + } +`; \ No newline at end of file diff --git a/FRONT/DEV/src/const/buttons/icons.tsx b/FRONT/DEV/src/const/buttons/icons.tsx index db08c85..12bf8d5 100644 --- a/FRONT/DEV/src/const/buttons/icons.tsx +++ b/FRONT/DEV/src/const/buttons/icons.tsx @@ -21,6 +21,7 @@ import close from '@iconify/icons-ic/round-close'; import wifiFind from '@iconify/icons-ic/baseline-wifi-find'; import noteEdit from '@iconify/icons-ph/note-pencil'; import swapHoriz from '@iconify/icons-ic/baseline-swap-horiz'; +import check from '@iconify/icons-ic/baseline-check'; export const ICONS = { [buttonNames[1]]: exitToApp, @@ -41,6 +42,7 @@ export const ICONS = { [buttonNames[16]]: noteEdit, [buttonNames[17]]: arrowLeft, [buttonNames[18]]: swapHoriz, + [buttonNames[19]]: check, } as const; type IconName = keyof typeof ICONS; diff --git a/FRONT/DEV/src/const/buttons/names.ts b/FRONT/DEV/src/const/buttons/names.ts index f933de5..b6c7505 100644 --- a/FRONT/DEV/src/const/buttons/names.ts +++ b/FRONT/DEV/src/const/buttons/names.ts @@ -19,4 +19,5 @@ export const buttonNames: names ={ 16: 'noteEdit', 17: 'arrowLeft', 18: 'swapHoriz', + 19: 'check' } \ No newline at end of file diff --git a/FRONT/DEV/src/store/settingsStore.ts b/FRONT/DEV/src/store/settingsStore.ts index b4a72d1..86e72d5 100644 --- a/FRONT/DEV/src/store/settingsStore.ts +++ b/FRONT/DEV/src/store/settingsStore.ts @@ -2,9 +2,11 @@ import { create } from 'zustand'; export type Setting = { id: string; - type: 'boolean' | 'select' | 'text' | 'number'; + type: 'boolean' | 'select' | 'text' | 'number' | 'button'; label: string; value: any; + min?: number; + max?: number; options?: { value: any; label: string }[]; sectionId: string; }; @@ -32,14 +34,104 @@ export const useSettingsStore = create((set) => ({ initializeSettings: () => { const defaultSettings: Setting[] = [ + // Узлы подсети { id: 'rescan-nodes', + sectionId: 'Узлы подсети', + label: 'Интервальное сканирование', + type: 'boolean', + value: false, + }, + { + id: 'interval-nodes', + sectionId: 'Узлы подсети', + label: 'Интервал сканирования узлов (мин)', type: 'number', - label: 'nodesNetwork', - value: 3000, - sectionId: 'appearance', + value: 60, + min: 1, + max: 2880 + }, + { + id: 'netnames-nodes', + sectionId: 'Узлы подсети', + label: 'Отображаемые имена при отсутствии', + type: 'select', + value: 'ip', + options: [ + { value: 'ip', label: 'IP адрес' }, + { value: 'unknown', label: 'Неизвестно' }, + ], + }, + { + id: 'paginate-nodes', + type: 'number', + label: 'Количество строк на таблице', + sectionId: 'Узлы подсети', + value: 20, + min: 1, + }, + { + id: 'customize-nodes', + type: 'button', + label: 'Отображение узлов', + sectionId: 'Узлы подсети', + value: 'Настроить', + }, + { + id: 'clean-nodes', + type: 'button', + label: 'Очистить данные сессии', + sectionId: 'Узлы подсети', + value: 'Очистить', + }, + { + id: 'export-nodes', + type: 'button', + label: 'Экспорт данных узлов', + sectionId: 'Узлы подсети', + value: 'Экспортировать', }, + // Точки доступа + { + id: 'clean-network', + type: 'button', + label: 'Очистить данные сессии', + sectionId: 'Точки доступа', + value: 'Очистить', + }, + { + id: 'export-network', + type: 'button', + label: 'Экспорт данных точек доступа', + sectionId: 'Точки доступа', + value: 'Экспортировать', + }, + + // Внешний вид + { + id: 'theme', + sectionId: 'Внешний вид', + label: 'Цветовая тема', + type: 'select', + value: 'light', + options: [ + { value: 'light', label: 'Светлая' }, + { value: 'dark', label: 'Тёмная' }, + { value: 'system', label: 'Как в системе' } + ], + }, + { + id: 'language', + sectionId: 'Внешний вид', + label: 'Язык интерфейса', + type: 'select', + value: 'ru', + options: [ + { value: 'ru', label: 'Русский' }, + { value: 'en', label: 'Английский' }, + ], + }, ]; set({ settings: defaultSettings }); },