From a2ce9043e72bf1fb3e254b3090e1099563774970 Mon Sep 17 00:00:00 2001 From: Vladiysss <139554971+Vladiysss@users.noreply.github.com> Date: Sun, 22 Feb 2026 21:26:36 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=B0=D0=BD=D0=B1=D0=B0=D0=BD?= =?UTF-8?q?=20=D0=B4=D0=BE=D1=81=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - просмотр/создание/изменение/удаление категорий; - просмотр/создание/изменение/удаление задач; - просмотр общей информации о канбан доске --- src/KBBoard.js | 354 ++++++++++++++++++++++++++++++++++++++++++++-- src/css/Board.css | 242 +++++++++++++++++++++++++++++++ 2 files changed, 582 insertions(+), 14 deletions(-) create mode 100644 src/css/Board.css diff --git a/src/KBBoard.js b/src/KBBoard.js index b59b347..c581fb4 100644 --- a/src/KBBoard.js +++ b/src/KBBoard.js @@ -1,29 +1,355 @@ -import { useState, useEffect } from 'react'; -import { useParams } from 'react-router-dom'; +import { useState, useEffect, useCallback } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; import axios from 'axios'; import Header from './Header'; +import './css/Board.css'; const KBBoard = () => { + const navigate = useNavigate(); const { id } = useParams(); - const [user, setUser] = useState(null); const [error, setError] = useState(''); + const [info, setInfo] = useState({}); + const [categories, setCategories] = useState([]); + + const [crTask, setCrTask] = useState(false); + const [crCateg, setCrCateg] = useState(false); + const [edTask, setEdTask] = useState(false); + const [edCateg, setEdCateg] = useState(false); + const [delTask, setDelTask] = useState(false); + const [delCateg, setDelCateg] = useState(false); + const [categoryTitle, setCategoryTitle] = useState(''); + const [categoryPosition, setCategoryPosition] = useState(null); + const [taskTitle, setTaskTitle] = useState(''); + const [taskDescription, setTaskDescription] = useState(''); + const [taskPosition, setTaskPosition] = useState(null); + const [taskCategory, setTaskCategory] = useState(null); + const [taskCategori, setTaskCategori] = useState(null); + const [editedTask, setEditedTask] = useState({}); + const [editedCateg, setEditedCateg] = useState({}); + + + + const loadBoardData = useCallback(async () => { + try { + setError('') + const response = await axios.post('/api/boards/load', { id }); + setInfo(response.data); + setCategories(response.data.categories); + } catch (err) { + if (err.response?.data?.message === 'Token Error' || err.response?.data?.message === 'Invalid Token') { + setError('Вы не авторизованы'); + setTimeout(() => { + navigate('/login'); + }, 1000); + } else { + setError('Ошибка загрузки доски'); + } + } + }, [id, navigate]); useEffect(() => { - const checkSession = async () => { - try { - const response = await axios.post('/api/boards/load', { id }); - } catch {} - }; - checkSession(); - }, [id]); + if (id) loadBoardData(); + }, [id, loadBoardData]); + + + const modalCrTask = (categori) => () => { + setCrTask(!crTask); + setTaskCategori(categori); + setTaskTitle(''); + setTaskDescription(''); + } + const modalCrCateg = () => { + setCrCateg(!crCateg); + setCategoryTitle(''); + } + const modalEditTask = (task, id_categ) => () => { + setEdTask(!edTask); + setTaskCategori(id_categ); + setEditedTask(task); + setTaskTitle(task.title); + setTaskDescription(task.description); + setTaskPosition(task.position); + setTaskCategory(task.category_id); + } + const modalEditCateg = (categ) => () => { + setEdCateg(!edCateg); + setEditedCateg(categ); + setCategoryTitle(categ.title); + setCategoryPosition(categ.position); + } + const modalDelTask = () => { + setDelTask(!delTask); + } + const modalDelCateg = () => { + setDelCateg(!delCateg); + } + + + + const createTask = async (e) => { + e.preventDefault(); + const newTask = { category_id: taskCategori, title: taskTitle, description: taskDescription }; + axios.post('/api/boards/categories/tasks/create', newTask); + await loadBoardData(); + modalCrTask(null)(); + }; + const createCategory = async (e) => { + e.preventDefault(); + try { + const newCategory = { board_id: id, title: categoryTitle}; + await axios.post('/api/boards/categories/create', newCategory); + await loadBoardData(); + } catch (err) { + setError(err.response.data.message) + } finally { + modalCrCateg(); + } + }; + + const editTask = async (e) => { + e.preventDefault(); + var newTask = { id: editedTask.id, update_method: "title", value: taskTitle }; + await axios.put('/api/boards/categories/tasks/update', newTask); + newTask = { id: editedTask.id, update_method: "description", value: taskDescription }; + await axios.put('/api/boards/categories/tasks/update', newTask); + newTask = { id: editedTask.id, update_method: "category", value: Number(taskCategory) }; + await axios.put('/api/boards/categories/tasks/update', newTask); + await loadBoardData(); + modalEditTask({}, null)(); + }; + const editCategory = async (e) => { + e.preventDefault(); + try { + const newCategory = { id: editedCateg.id, update_method: "title", value: categoryTitle}; + await axios.put('/api/boards/categories/update', newCategory); + await loadBoardData(); + } catch (err) { + setError(err.response.data.message) + } finally { + modalEditCateg({})(); + } + }; + + + const deleteCategory = async (e) => { + e.preventDefault(); + await axios.delete('/api/boards/categories/delete', { data: {id: editedCateg.id} }); + await loadBoardData(); + modalDelCateg(); + modalEditCateg({})(); + } + const deleteTask = async (e) => { + e.preventDefault(); + await axios.delete('/api/boards/categories/tasks/delete', { data: { id: editedTask.id } }); + await loadBoardData(); + modalDelTask(); + modalEditTask({},null)(); + } + return ( - <> +
-
- +
+ { + error &&
{error}
+ } +
+
+

{info.title}

+

+ Участники: В разработке +

+

+ Владелец: {" "+info.owner?.display_name} + +

+
+
+

Описание: {info.description ? info.description : 'Отсутствует'}

+
+
+
+

SetingPanel:В разработке

+
+
+ {categories.map((category) => ( +
+ +
+

Позиция: {category.position}

+

Задач: {category.tasks.length}

+
+
+ +
+
+ {category.tasks.length > 0 ? ( + category.tasks.map((task) => ( + + )) + ) : ( +

Нет задач

+ )} +
+
+ ))} + {categories.length < 10 ? ( +
+
+ +
+
+ ) : ( + <> + )} +
+ + + + + {crCateg && ( +
+
+

Новая категория

+
+
+ setCategoryTitle(e.target.value)} + required + /> +
+ +
+ +
+
+ )} + + {crTask && ( +
+
+

Новая задача

+
+
+ setTaskTitle(e.target.value)} + required + /> + setTaskDescription(e.target.value)} + required + /> +
+ +
+ +
+
+ )} + + {edCateg && ( +
+
+

Изменение категории

+
+
+ setCategoryTitle(e.target.value)} + required + /> +
+ +
+ + +
+
+ )} + + {edTask && ( +
+
+

Изменение задачи

+
+
+ setTaskTitle(e.target.value)} + required + /> + setTaskDescription(e.target.value)} + required + /> +
+ + +
+
+ +
+ + +
+
+ )} + + {delTask && ( +
+
+

Удаление задачи

+
+ + + +
+
+
+ )} + + {delCateg && ( +
+
+

Удаление категории

+
+ + + +
+
+
+ )} +
- +
); } diff --git a/src/css/Board.css b/src/css/Board.css new file mode 100644 index 0000000..6fc2e30 --- /dev/null +++ b/src/css/Board.css @@ -0,0 +1,242 @@ +.inf-panel{ + flex: 0 0 auto; + background-color: #2b3245; + padding: 10px; + border-radius: 6px; + border-radius: 6px; + margin-bottom: 16px; + display: flex; + flex-direction: column; + justify-content: space-around; +} + +.inf-panel strong { + margin-right: 1ch; +} + +.inf-panel .row{ + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} + +.row p, .row h3{ + display: flex; + margin: 0px 0px; + word-break: break-all; + align-items: center +} + +.row+.row p { + margin: 20px 0px 0px 0px; +} + + + + + + + + + + +.set-panel{ + flex: 0 0 auto; + background-color: #2b3245; + padding: 10px; + border-radius: 6px; + margin-bottom: 16px; + display: flex; + flex-direction: column; + justify-content: space-around; +} + +.board-panel{ + flex: 1 1 auto; + background-color: #2b3245; + overflow-x: auto; + overflow-y: hidden; + gap: 15px; + padding: 5px; + border: 5px dashed #2b3245; + border-radius: 6px; + margin-bottom: 0px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: stretch; +} + +.categori{ + flex: 1 1 auto; /* занимает всё свободное место */ + overflow-y: auto; /* вертикальный скролл при переполнении */ + overflow-x: hidden; /* горизонтальный скролл отключён */ + border: 3px solid #3d4763; + background-color: #3d4763; + min-width: 300px; + padding: 10px; + border-radius: 6px; + margin-bottom: 0px; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: center; +} + +.categori>button{ + display: flex; + background-color: #0000; + padding: 0px; + margin: 0px; + justify-content: center; +} +.categori>button:hover{ + background-color: #0000; +} + +.bib { + width: 100%; + height: 100%; +} + +.categ-h{ + display: flex; + justify-content: space-around; + width: 100%; + max-width: 100%; +} + +.task-list { + flex: 1 1 auto; /* занимает всё свободное место */ + overflow-y: auto; /* вертикальный скролл при переполнении */ + overflow-x: hidden; /* горизонтальный скролл отключён */ + display: flex; + flex-direction: column; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: center; + min-width: 300px; + margin-top: 16px; + margin-right: -4px; /* компенсируем отступ контейнера */ + padding-right: 4px; /* создаём отступ для контента */ +} + +.task { + text-align: left; + margin: 0px; + + color: #CAD1D8; + padding: 4px; + display: flex; + justify-content: space-around; + flex-direction: column; + border-radius: 6px; + font-size: 16px; + background-color: #2b3245; + border: 2px solid #617099; + width: 100%; + box-sizing: border-box; + +} + + +.task+.task { + margin-top: 12px; +} + +.task:hover { + background-color: #3d4763; +} + +.create { + margin-top: 16px; + background-color: #fff0; + border: 3px dashed #3d4763; + display: flex; + flex-direction: row; + align-items: center; + overflow-y: hidden; + button{ + height: 100%; + background-color: #fff0; + font-size: 20px; + color: #3d4763; + margin: 0px; + div{ + font-size: 50px; + } + } + button:hover{ + background-color: #3d4763; + color: #CAD1D8; + } +} + +.categori .create { + margin-top: 0px; + background-color: #fff0; + border: 3px dashed #617099; + display: flex; + flex-direction: row; + align-items: center; + overflow-y: unset; + button{ + height: 50px; + background-color: #fff0; + font-size: 20px; + color: #617099; + margin: 0px; + div{ + font-size: 10px; + } + } + button:hover{ + background-color: #617099; + color: #CAD1D8; + } +} + +.categori.create { + margin-top: 0px; +} + +.task.create { + margin-top: 16px; + margin-right: 0px; +} + + + + + + + +/* Стили для полосы прокрутки (опционально) */ +.task-list::-webkit-scrollbar{ + width: 8px; +} + +.board-panel::-webkit-scrollbar { + height: 8px; +} + +.task-list::-webkit-scrollbar-track , +.board-panel::-webkit-scrollbar-track { + background: #fff; + border-radius: 4px; + margin-left: 8px; +} + +.task-list::-webkit-scrollbar-thumb, +.board-panel::-webkit-scrollbar-thumb { + background: #aaa; + border-radius: 4px; + margin-left: 8px; +} + +.task-list::-webkit-scrollbar-thumb:hover, +.board-panel::-webkit-scrollbar-thumb:hover { + background: #888; +} \ No newline at end of file