233 lines
8.5 KiB
JavaScript
233 lines
8.5 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import axios from 'axios';
|
|
import Header from './Header';
|
|
|
|
function ListItem({ item }) {
|
|
const navigate = useNavigate();
|
|
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}</p>
|
|
<p><strong>Обновлено:</strong> {new Date(item.updated_at).toLocaleString()}</p>
|
|
</div>
|
|
</button>
|
|
</li>
|
|
);
|
|
};
|
|
|
|
const KBBoardsList = () => {
|
|
const [error, setError] = useState('');
|
|
const [sort_method, setSortMethod] = useState('title');
|
|
const [reverse, setReverse] = useState(false);
|
|
const [search_text, setSearchText] = useState('');
|
|
const [title, setTitle] = useState('');
|
|
const [description, setDescription] = useState('');
|
|
const [items, setItems] = useState([]);
|
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [filteredItems, setFilteredItems] = useState([]);
|
|
const [page, setPage] = useState(1);
|
|
const [list, setList] = useState(20);
|
|
const debounceRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
const loadBoardList = async () => {
|
|
try {
|
|
var newList = { sort_method, reverse, search_text, page, list };
|
|
const response = await axios.post('/api/boards/list', newList);
|
|
if (Array.isArray(response.data)) {
|
|
setItems(response.data);
|
|
} else {
|
|
// Если данных нет или они не массив — ставим пустой массив
|
|
setItems([]);
|
|
if (response.data?.detail === 'Доски отсутствуют.') {
|
|
console.log('Доски отсутствуют');
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (err.response.data.message === 'Token Error' || err.response.data.message === 'Invalid Token') {
|
|
setError('Вы не авторизованы');
|
|
setTimeout(() => {
|
|
window.location.href = '/login';
|
|
}, 1500);
|
|
} else {
|
|
setError('Ошибка загрузки досок');
|
|
console.log(err);
|
|
setItems([]); // Гарантируем, что items — массив
|
|
}
|
|
}
|
|
};
|
|
loadBoardList()
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
clearTimeout(debounceRef.current);
|
|
debounceRef.current = setTimeout(() => {
|
|
setFilteredItems(
|
|
items.filter(item =>
|
|
item.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
)
|
|
);
|
|
}, 300); // 300 мс задержка
|
|
}, [searchQuery, items]);
|
|
|
|
const sortList = async (method = sort_method, isReverse = reverse) => {
|
|
try {
|
|
const newList = { sort_method: method, reverse: isReverse, search_text };
|
|
const response = await axios.post('/api/boards/list', newList);
|
|
|
|
if (Array.isArray(response.data)) {
|
|
setItems(response.data);
|
|
} else {
|
|
setItems([]);
|
|
if (response.data?.detail === 'Доски отсутствуют.') {
|
|
console.log('Доски отсутствуют');
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (err.response?.data?.message === 'Token Error' || err.response?.data?.message === 'Invalid Token') {
|
|
setError('Вы не авторизованы');
|
|
setTimeout(() => {
|
|
window.location.href = '/login';
|
|
}, 1500);
|
|
} else {
|
|
setError('Ошибка загрузки досок');
|
|
console.log(err);
|
|
setItems([]);
|
|
}
|
|
}
|
|
};
|
|
|
|
const setFilterTitle = async () => {
|
|
const newMethod = 'title';
|
|
const newReverse = sort_method === 'title' ? !reverse : false;
|
|
|
|
setSortMethod(newMethod);
|
|
setReverse(newReverse);
|
|
|
|
await sortList(newMethod, newReverse);
|
|
};
|
|
|
|
const setFilterOwner = async () => {
|
|
const newMethod = 'owner';
|
|
const newReverse = sort_method === 'owner' ? !reverse : false;
|
|
|
|
setSortMethod(newMethod);
|
|
setReverse(newReverse);
|
|
|
|
await sortList(newMethod, newReverse);
|
|
};
|
|
|
|
const setFilterUptime = async () => {
|
|
const newMethod = 'update_time';
|
|
const newReverse = sort_method === 'update_time' ? !reverse : false;
|
|
|
|
setSortMethod(newMethod);
|
|
setReverse(newReverse);
|
|
|
|
await sortList(newMethod, newReverse);
|
|
};
|
|
|
|
const createBoard = async () => {
|
|
try {
|
|
const newBoard = { title, description };
|
|
await axios.post('/api/boards/create', newBoard);
|
|
setShowCreateModal(false)
|
|
} catch (err) {
|
|
setError('Ошибка');
|
|
}
|
|
};
|
|
|
|
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={setFilterTitle}>
|
|
Названию
|
|
</button>
|
|
<button onClick={setFilterOwner}>
|
|
Создателю
|
|
</button>
|
|
<button onClick={setFilterUptime}>
|
|
Дате обновления
|
|
</button>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
placeholder="Поиск по названию..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="kan-ban-list">
|
|
<div className="inf">
|
|
<h3>Доступные канбан доски:</h3>
|
|
<button onClick={openCreateModal}>Создать канбан-доску</button>
|
|
</div>
|
|
{filteredItems.length > 0 ? (
|
|
<ul>
|
|
{filteredItems.map((item) => (
|
|
<ListItem key={item.id} item={item} />
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p>Нет данных</p>
|
|
)}
|
|
</div>
|
|
</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; |