Init
This commit is contained in:
233
client/src/KBBoardsList.js
Normal file
233
client/src/KBBoardsList.js
Normal file
@@ -0,0 +1,233 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user