Версия 1.2.11 Доработка окна выбора авторов в админке.

This commit is contained in:
anibilag 2025-11-09 23:13:50 +03:00
parent 9dd25faf8a
commit 593a620f29
3 changed files with 92 additions and 64 deletions

View File

@ -1,7 +1,7 @@
{
"name": "vite-react-typescript-starter",
"private": true,
"version": "1.2.10",
"version": "1.2.11",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -6,6 +6,8 @@ Disallow: /admin/
Disallow: /admin/*
Disallow: /search
Disallow: /search*
Disallow: /bookmarks
Disallow: /bookmarks*
Disallow: /*?page=
Disallow: /*?category=

View File

@ -26,10 +26,26 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
existingAuthors,
}) => {
const [activeTab, setActiveTab] = useState<AuthorRole>(AuthorRole.WRITER);
const [selectedAuthorId, setSelectedAuthorId] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState('');
const itemsPerPage = 10;
const itemsPerPage = 6;
// Обработчик нажатия клавиши Esc
useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscKey);
}
return () => {
document.removeEventListener('keydown', handleEscKey);
};
}, [isOpen, onClose]);
// Используем useMemo для оптимальной фильтрации и сортировки авторов
const { popularAuthors, otherAuthors } = useMemo(() => {
@ -39,7 +55,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
!existingAuthors.some(existing => existing.authorId === author.id && existing.role === activeTab)
);
// Сортируем по полю order (по возрастанию) и берем первые 6
// Сортируем по полю order (по возрастанию) и берем первые 5
const sortedByOrder = [...filteredAuthors].sort((a, b) => a.order - b.order);
const popularAuthors = sortedByOrder.slice(0, 6);
@ -51,6 +67,20 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
return { popularAuthors, otherAuthors };
}, [authors, existingAuthors, activeTab]);
// Получаем уникальные первые буквы имен остальных авторов
const firstLetters = useMemo(() => {
const letters = new Set<string>();
otherAuthors.forEach(author => {
// Берем первую букву имени, приводим к верхнему регистру
const firstLetter = author.displayName.charAt(0).toUpperCase();
letters.add(firstLetter);
});
// Преобразуем Set в массив и сортируем
return Array.from(letters).sort();
}, [otherAuthors]);
// Фильтруем остальных авторов по поисковому запросу
const filteredOtherAuthors = useMemo(() => {
if (!searchTerm.trim()) {
@ -65,27 +95,17 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
// Вычисляем данные для пагинации только для отфильтрованных остальных авторов
const paginationData = useMemo(() => {
const showPagination = filteredOtherAuthors.length > itemsPerPage;
// Если пагинация не нужна, показываем всех отфильтрованных остальных авторов
if (!showPagination) {
return {
showPagination: false,
displayedOtherAuthors: filteredOtherAuthors,
totalPages: 1
};
}
// Если пагинация нужна
const totalPages = Math.ceil(filteredOtherAuthors.length / itemsPerPage);
const totalAuthors = filteredOtherAuthors.length;
const totalPages = Math.ceil(totalAuthors / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const displayedOtherAuthors = filteredOtherAuthors.slice(startIndex, endIndex);
return {
showPagination: true,
totalAuthors,
totalPages,
displayedOtherAuthors,
totalPages
showPagination: totalPages > 1
};
}, [filteredOtherAuthors, currentPage, itemsPerPage]);
@ -94,19 +114,16 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
setCurrentPage(1);
}, [searchTerm]);
const handleAddAuthor = () => {
if (selectedAuthorId) {
onAddAuthor(selectedAuthorId, activeTab);
setSelectedAuthorId(null);
onClose();
}
// Обработчик выбора автора
const handleAuthorSelect = (authorId: string) => {
onAddAuthor(authorId, activeTab);
onClose();
};
const handleTabChange = (role: AuthorRole) => {
setActiveTab(role);
setCurrentPage(1);
setSearchTerm('');
setSelectedAuthorId(null);
};
const handlePageChange = (page: number) => {
@ -123,9 +140,13 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
setSearchTerm('');
};
const handleLetterClick = (letter: string) => {
setSearchTerm(letter.toLowerCase());
};
if (!isOpen) return null;
const { showPagination, displayedOtherAuthors, totalPages } = paginationData;
const { totalAuthors, totalPages, displayedOtherAuthors, showPagination } = paginationData;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
@ -174,7 +195,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
<div className="flex-1 overflow-y-auto p-6">
{/* Популярные авторы (всегда отображаются, если есть) */}
{popularAuthors.length > 0 && (
<div className="mb-8">
<div key={`popular-${activeTab}`} className="mb-8">
<div className="flex items-center mb-4">
<h3 className="text-lg font-semibold text-gray-700">Популярные</h3>
<span className="ml-2 px-2 py-1 bg-blue-100 text-blue-800 text-xs font-medium rounded-full">
@ -191,8 +212,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
<AuthorCard
key={author.id}
author={author}
isSelected={selectedAuthorId === author.id}
onSelect={() => setSelectedAuthorId(author.id)}
onSelect={() => handleAuthorSelect(author.id)}
showOrder={true}
/>
</CSSTransition>
@ -202,10 +222,10 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
)}
{/* Все остальные авторы с поиском и пагинацией */}
<div id="other-authors-section">
<div id="other-authors-section" key={`other-${activeTab}`}>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-700">
Остальные {roleLabels[activeTab].toLowerCase()}
Все {roleLabels[activeTab].toLowerCase()}
</h3>
{showPagination && (
<span className="text-sm text-gray-500">
@ -214,6 +234,37 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
)}
</div>
{/* Кнопки с первыми буквами */}
{firstLetters.length > 0 && (
<div className="mb-4">
<div className="flex flex-wrap gap-2">
<button
onClick={clearSearch}
className={`px-3 py-1.5 text-sm rounded-full ${
searchTerm === ''
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
Все
</button>
{firstLetters.map(letter => (
<button
key={letter}
onClick={() => handleLetterClick(letter)}
className={`px-3 py-1.5 text-sm rounded-full ${
searchTerm.toLowerCase() === letter.toLowerCase()
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{letter}
</button>
))}
</div>
</div>
)}
{/* Поле поиска */}
<div className="mb-6">
<div className="relative">
@ -238,7 +289,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
</div>
{searchTerm && (
<p className="mt-2 text-sm text-gray-500">
Найдено: {filteredOtherAuthors.length} {filteredOtherAuthors.length === 1 ? 'автор' : 'авторов'}
Найдено: {totalAuthors} {totalAuthors === 1 ? 'автор' : 'авторов'}
</p>
)}
</div>
@ -250,15 +301,14 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
<AuthorCard
key={author.id}
author={author}
isSelected={selectedAuthorId === author.id}
onSelect={() => setSelectedAuthorId(author.id)}
onSelect={() => handleAuthorSelect(author.id)}
showOrder={false}
/>
))}
</div>
{/* Пагинация */}
{showPagination && totalPages > 1 && (
{showPagination && (
<div className="flex justify-center mt-8">
<div className="flex items-center space-x-2">
<button
@ -312,47 +362,23 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
)}
</div>
</div>
{/* Кнопки действий */}
<div className="flex justify-end space-x-3 p-6 border-t">
<button
onClick={onClose}
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
>
Отмена
</button>
<button
onClick={handleAddAuthor}
disabled={!selectedAuthorId}
className={`px-4 py-2 rounded-md text-white ${
selectedAuthorId
? 'bg-blue-600 hover:bg-blue-700'
: 'bg-gray-400 cursor-not-allowed'
}`}
>
Добавить
</button>
</div>
</div>
</div>
);
};
// Компонент карточки автора (без изменений)
// Обновленный компонент карточки автора
interface AuthorCardProps {
author: Author;
isSelected: boolean;
onSelect: () => void;
showOrder: boolean;
}
const AuthorCard: React.FC<AuthorCardProps> = ({ author, isSelected, onSelect, showOrder }) => {
const AuthorCard: React.FC<AuthorCardProps> = ({ author, onSelect, showOrder }) => {
return (
<div
className={`border rounded-lg p-4 cursor-pointer transition-all ${
isSelected
? 'border-blue-500 bg-blue-50 shadow-sm'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
className={`border rounded-lg p-4 cursor-pointer transition-all hover:border-blue-500 hover:bg-blue-50 hover:shadow-md ${
showOrder ? 'border-gray-200' : 'border-gray-200'
}`}
onClick={onSelect}
>