213 lines
7.7 KiB
JavaScript
213 lines
7.7 KiB
JavaScript
import { useState, useEffect, useRef, useCallback } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import axios from 'axios';
|
||
import Header from './Header';
|
||
import './css/BoardList.css';
|
||
|
||
const KBBoardsList = () => {
|
||
const navigate = useNavigate();
|
||
const debounceRef = useRef(null);
|
||
const [error, setError] = useState('');
|
||
const [loading, setLoading] = useState(false);
|
||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||
const [timer, setTimer] = useState(400)
|
||
|
||
const [sort_method, setSortMethod] = useState('title');
|
||
const [reverse, setReverse] = useState(false);
|
||
const [search_text, setSearchText] = useState('');
|
||
const [page, setPage] = useState(1);
|
||
const [limit, setLimit] = useState(5);
|
||
|
||
const [title, setTitle] = useState('');
|
||
const [description, setDescription] = useState('');
|
||
const [items, setItems] = useState([]);
|
||
const [totalItems, setTotalItems] = useState(null);
|
||
|
||
|
||
|
||
|
||
|
||
function ListItem({ item }) {
|
||
if (!item) return null;
|
||
const openBoard = () => { navigate('/kanban-board/' + item.id); };
|
||
return (
|
||
<li>
|
||
<button onClick={openBoard}>
|
||
<div className="sort-row">
|
||
<h3>{item.title}</h3>
|
||
<p><strong>Владелец:</strong> {item.owner_display_name}</p>
|
||
</div>
|
||
<div className="sort-row">
|
||
<p><strong>Описание:</strong> {item.description ? item.description : 'Отсутствует'}</p>
|
||
</div>
|
||
</button>
|
||
</li>
|
||
);
|
||
};
|
||
|
||
const Pagination = () => {
|
||
const totalPages = Math.ceil(totalItems / limit);
|
||
if (totalPages <= 1) return null;
|
||
const pages = [];
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
pages.push(i);
|
||
}
|
||
return (
|
||
<div className="pagination">
|
||
{pages.map((pageNum) => (
|
||
<button
|
||
key={pageNum}
|
||
onClick={() => setPage(pageNum)}
|
||
className={page === pageNum ? 'active' : ''}
|
||
disabled={loading}
|
||
> {pageNum} </button>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
|
||
|
||
const loadBoardList = useCallback(async () => {
|
||
clearTimeout(debounceRef.current);
|
||
debounceRef.current = setTimeout(async () => {
|
||
try {
|
||
const newList = { sort_method, reverse, search_text, page, limit };
|
||
const response = await axios.post('/api/boards/list', newList, {withCredentials: true });
|
||
if (Array.isArray(response.data.boards)) {
|
||
setItems(response.data.boards);
|
||
setTotalItems(response.data.count || response.data.boards.length);
|
||
} else {
|
||
setItems([]);
|
||
setTotalItems(0)
|
||
if (response.data?.detail === 'Доски отсутствуют.') {
|
||
console.log('Доски отсутствуют');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
if (err.response?.data?.message === 'Token Error' || err.response?.data?.message === 'Invalid Token') {
|
||
setError('Вы не авторизованы');
|
||
setTimeout(() => {
|
||
navigate('/login');
|
||
}, 1000);
|
||
} else {
|
||
setError('Ошибка загрузки досок');
|
||
console.log(err);
|
||
setItems([]);
|
||
}
|
||
}
|
||
setTimer(400)
|
||
}, timer);
|
||
}, [sort_method, reverse, search_text, page, limit, timer, setItems, setTotalItems, setError, navigate]);
|
||
|
||
useEffect(() => {
|
||
loadBoardList();
|
||
}, [page, loadBoardList]);
|
||
|
||
const setFilter = (method) => () => {
|
||
const newMethod = method;
|
||
const newReverse = sort_method === method ? !reverse : false;
|
||
|
||
setSortMethod(newMethod);
|
||
setReverse(newReverse);
|
||
setTimer(100);
|
||
};
|
||
|
||
const createBoard = async (e) => {
|
||
e.preventDefault();
|
||
try {
|
||
setLoading(true)
|
||
const newBoard = { title, description };
|
||
await axios.post('/api/boards/create', newBoard);
|
||
setShowCreateModal(false);
|
||
await loadBoardList();
|
||
} catch (err) {
|
||
setError('Ошибка');
|
||
} finally {
|
||
setLoading(false);
|
||
|
||
}
|
||
};
|
||
|
||
const openCreateModal = () => { setShowCreateModal(true) };
|
||
const closeCreateModal = () => { setShowCreateModal(false); setDescription(''); setTitle('') };
|
||
|
||
return (
|
||
<>
|
||
<Header />
|
||
<div className="profile-page">
|
||
{
|
||
error && <div className="error">{error}</div>
|
||
}
|
||
<div className="kan-ban-list-sort">
|
||
<h3>Сортировка по:</h3>
|
||
<div className="nav-sort">
|
||
<button onClick={setFilter('title')}>
|
||
Названию
|
||
</button>
|
||
<button onClick={setFilter('owner')}>
|
||
Создателю
|
||
</button>
|
||
<button onClick={setFilter('update_time')}>
|
||
Дате обновления
|
||
</button>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
placeholder="Поиск по названию..."
|
||
value={search_text}
|
||
onChange={(e) => setSearchText(e.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="kan-ban-list">
|
||
<div className="inf">
|
||
<h3>Доступные канбан доски:</h3>
|
||
<button onClick={openCreateModal}>Создать канбан-доску</button>
|
||
</div>
|
||
{items.length > 0 ? (
|
||
<ul>
|
||
{items.map((item) => (
|
||
<ListItem key={item.id} item={item} />
|
||
))}
|
||
</ul>
|
||
) : (
|
||
<p>Нет данных</p>
|
||
)}
|
||
</div>
|
||
<Pagination />
|
||
</div>
|
||
{showCreateModal && (
|
||
<div className="confirm-modal">
|
||
<div className="modal-content">
|
||
<p><strong>Придумайте название и описание канбан доски</strong></p>
|
||
<form onSubmit={createBoard}>
|
||
<div>
|
||
<label>Название:</label>
|
||
<input
|
||
type="text"
|
||
value={title}
|
||
onChange={(e) => setTitle(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label>Описание:</label>
|
||
<input
|
||
type="text"
|
||
value={description}
|
||
onChange={(e) => setDescription(e.target.value)}
|
||
/>
|
||
</div>
|
||
<div className="modal-buttons">
|
||
<button type="submit" disabled={loading}>{loading ? 'Создание...' : 'Создать'}</button>
|
||
<button onClick={closeCreateModal}>Отменить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
|
||
export default KBBoardsList; |