feat: добавлен список участников доски с возможностью перейти в их профиль

This commit is contained in:
Vladiysss
2026-03-08 21:39:23 +03:00
parent e79376c638
commit 1ea671967c
4 changed files with 97 additions and 14 deletions

View File

@@ -8,7 +8,7 @@ import {
addMemberAPI, assignMemberAPI, unassignMemberAPI, deleteMemberAPI, quitMemberAPI addMemberAPI, assignMemberAPI, unassignMemberAPI, deleteMemberAPI, quitMemberAPI
} from './BoardAPI'; } from './BoardAPI';
export const useBoardLogic = (id, setError, setInfo, setCategories, setLoading) => { export const useBoardLogic = (id, setError, setInfo, setCategories, setLoading, setItems) => {
const navigate = useNavigate(); const navigate = useNavigate();
const loadBoardData = useCallback(async () => { const loadBoardData = useCallback(async () => {
@@ -17,6 +17,7 @@ export const useBoardLogic = (id, setError, setInfo, setCategories, setLoading)
setError(''); setError('');
const response = await loadBoardDataAPI(id); const response = await loadBoardDataAPI(id);
setInfo(response.data); setInfo(response.data);
setItems(response.data.members)
setCategories(response.data.categories || []); setCategories(response.data.categories || []);
} catch (err) { } catch (err) {
if (err.response?.data?.message === 'Token Error' || if (err.response?.data?.message === 'Token Error' ||
@@ -32,7 +33,7 @@ export const useBoardLogic = (id, setError, setInfo, setCategories, setLoading)
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [id, setError, setInfo, setCategories, setLoading, navigate]); }, [id, setError, setInfo, setCategories, setLoading, setItems, navigate]);
const checkOwner = useCallback(async (ownerId, setIsOwner) => { const checkOwner = useCallback(async (ownerId, setIsOwner) => {
setLoading(true); setLoading(true);

View File

@@ -1,17 +1,20 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useBoardLogic } from './BoardLogic'; import { useBoardLogic } from './BoardLogic';
import Header from './../Header'; import Header from './../Header';
import './../css/Board.css'; import './../css/Board.css';
const KBBoard = () => { const KBBoard = () => {
const navigate = useNavigate();
const { id } = useParams(); const { id } = useParams();
const [error, setError] = useState(''); const [error, setError] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [info, setInfo] = useState({}); const [info, setInfo] = useState({});
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const [isOwner, setIsOwner] = useState(null); const [isOwner, setIsOwner] = useState(null);
const [items, setItems] = useState([]);
const [memList, setMemList] = useState(false);
const [crTask, setCrTask] = useState(false); const [crTask, setCrTask] = useState(false);
const [crCateg, setCrCateg] = useState(false); const [crCateg, setCrCateg] = useState(false);
const [edTask, setEdTask] = useState(false); const [edTask, setEdTask] = useState(false);
@@ -56,7 +59,20 @@ const KBBoard = () => {
deleteBoards, deleteBoards,
quitMember, quitMember,
deleteMember deleteMember
} = useBoardLogic(id, setError, setInfo, setCategories, setLoading); } = useBoardLogic(id, setError, setInfo, setCategories, setLoading, setItems);
function ListItem({ item }) {
if (!item) return null;
const user = () => { navigate('/profile/' + item.id); };
return (
<button onClick={user}>
<div className="row">
<h3>{item.display_name}<img className='members-avatar' src={item.avatar_url}></img></h3>
<p><strong>Описание:</strong> {item.description ? item.description : 'Отсутствует'}</p>
</div>
</button>
);
};
useEffect(() => { useEffect(() => {
if (id) loadBoardData(); if (id) loadBoardData();
@@ -66,6 +82,9 @@ const KBBoard = () => {
if (info?.owner?.id !== undefined) checkOwner(info?.owner?.id, setIsOwner); if (info?.owner?.id !== undefined) checkOwner(info?.owner?.id, setIsOwner);
}, [info?.owner?.id, checkOwner, setIsOwner]); }, [info?.owner?.id, checkOwner, setIsOwner]);
const modalMemList = () => {
setMemList(!memList);
}
const modalCrTask = (categori) => () => { const modalCrTask = (categori) => () => {
setCrTask(!crTask); setCrTask(!crTask);
setTaskCategori(categori); setTaskCategori(categori);
@@ -182,6 +201,7 @@ const KBBoard = () => {
<div className="row"> <div className="row">
<h3>{info.title}</h3> <h3>{info.title}</h3>
<p> <p>
<button onClick={modalMemList}>
<strong>Участники: </strong> <strong>Участники: </strong>
{(info.members || []).map((member) => ( {(info.members || []).map((member) => (
(member.id !== info.owner.id) ? ( (member.id !== info.owner.id) ? (
@@ -190,10 +210,13 @@ const KBBoard = () => {
<></> <></>
) )
))} ))}
</button>
</p> </p>
<p> <p>
<button onClick={()=>navigate('/profile/' + info.owner.id)}>
<strong>Владелец: </strong> {" "+info.owner?.display_name} <strong>Владелец: </strong> {" "+info.owner?.display_name}
<img className="nav-avatar" src={info.owner?.avatar_url} alt=''></img> <img className="nav-avatar" src={info.owner?.avatar_url} alt=''></img>
</button>
</p> </p>
</div> </div>
<div className="row"> <div className="row">
@@ -278,6 +301,25 @@ const KBBoard = () => {
)} )}
</div> </div>
{memList && (
<div className="confirm-modal">
<div className="modal-content modal-member">
<div><h3>Изменение задачи</h3></div>
<label >Участники:</label>
<div className='task-list members-list'>
{items.length > 0 ? (
items.map((item) => (
<ListItem key={item.id} item={item} />
))
) : (
<p>Нет участников</p>
)}
</div>
<button onClick={modalMemList}>Закрыть</button>
</div>
</div>
)}
{crCateg && ( {crCateg && (
<div className="confirm-modal"> <div className="confirm-modal">
<div className="modal-content"> <div className="modal-content">

View File

@@ -35,6 +35,10 @@
text-align: center; text-align: center;
} }
.modal-content .row {
margin: 0px;
}
.members-avatar { .members-avatar {
height: 32px; height: 32px;
width: 32px; width: 32px;
@@ -43,10 +47,45 @@
margin-right: -24px; margin-right: -24px;
} }
.row button {
background-color: #0000;
display: flex;
word-break: break-all;
align-items: center;
margin: 0;
padding: 0;
color: #CAD1D8;
}
.members-list{
gap: 6px;
padding: 8px;
padding-right: 8px;
}
.members-list button{
border-radius: 6px;
margin: 0px;
display: flex;
flex-direction: column;
justify-content: space-around;
background-color: #3d4763;
}
.members-list button:hover{
box-shadow: 0 0 4px 1px #08e8de78;
}
.modal-member {
flex: 0 1 auto; /* занимает всё свободное место */
overflow-y: auto; /* вертикальный скролл при переполнении */
overflow-x: hidden; /* горизонтальный скролл отключён */
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
}
.set-panel{ .set-panel{
flex: 0 0 auto; flex: 0 0 auto;

View File

@@ -17,6 +17,7 @@
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
min-width: 300px; min-width: 300px;
max-height: 80%;
} }
.modal-buttons { .modal-buttons {