Версия 1.2.7 Добавлен поиск по имени при выборе авторов и фотографов статьи.

This commit is contained in:
anibilag 2025-10-31 12:19:33 +03:00
parent 790f8aa8e7
commit 898fc159c9
2 changed files with 72 additions and 13 deletions

View File

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

View File

@ -1,6 +1,6 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo, useEffect } from 'react';
import { Author, AuthorRole } from '../types'; import { Author, AuthorRole } from '../types';
import { X, ChevronLeft, ChevronRight } from 'lucide-react'; import { X, ChevronLeft, ChevronRight, Search } from 'lucide-react';
import { CSSTransition, TransitionGroup } from 'react-transition-group'; import { CSSTransition, TransitionGroup } from 'react-transition-group';
interface AuthorSelectionModalProps { interface AuthorSelectionModalProps {
@ -28,6 +28,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
const [activeTab, setActiveTab] = useState<AuthorRole>(AuthorRole.WRITER); const [activeTab, setActiveTab] = useState<AuthorRole>(AuthorRole.WRITER);
const [selectedAuthorId, setSelectedAuthorId] = useState<string | null>(null); const [selectedAuthorId, setSelectedAuthorId] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState('');
const itemsPerPage = 10; const itemsPerPage = 10;
// Используем useMemo для оптимальной фильтрации и сортировки авторов // Используем useMemo для оптимальной фильтрации и сортировки авторов
@ -50,31 +51,48 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
return { popularAuthors, otherAuthors }; return { popularAuthors, otherAuthors };
}, [authors, existingAuthors, activeTab]); }, [authors, existingAuthors, activeTab]);
// Вычисляем данные для пагинации только для остальных авторов // Фильтруем остальных авторов по поисковому запросу
const paginationData = useMemo(() => { const filteredOtherAuthors = useMemo(() => {
const showPagination = otherAuthors.length > itemsPerPage; if (!searchTerm.trim()) {
return otherAuthors;
}
// Если пагинация не нужна, показываем всех остальных авторов const searchLower = searchTerm.toLowerCase().trim();
return otherAuthors.filter(author =>
author.displayName.toLowerCase().startsWith(searchLower)
);
}, [otherAuthors, searchTerm]);
// Вычисляем данные для пагинации только для отфильтрованных остальных авторов
const paginationData = useMemo(() => {
const showPagination = filteredOtherAuthors.length > itemsPerPage;
// Если пагинация не нужна, показываем всех отфильтрованных остальных авторов
if (!showPagination) { if (!showPagination) {
return { return {
showPagination: false, showPagination: false,
displayedOtherAuthors: otherAuthors, displayedOtherAuthors: filteredOtherAuthors,
totalPages: 1 totalPages: 1
}; };
} }
// Если пагинация нужна // Если пагинация нужна
const totalPages = Math.ceil(otherAuthors.length / itemsPerPage); const totalPages = Math.ceil(filteredOtherAuthors.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage; const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage; const endIndex = startIndex + itemsPerPage;
const displayedOtherAuthors = otherAuthors.slice(startIndex, endIndex); const displayedOtherAuthors = filteredOtherAuthors.slice(startIndex, endIndex);
return { return {
showPagination: true, showPagination: true,
displayedOtherAuthors, displayedOtherAuthors,
totalPages totalPages
}; };
}, [otherAuthors, currentPage, itemsPerPage]); }, [filteredOtherAuthors, currentPage, itemsPerPage]);
// Сбрасываем страницу при изменении поискового запроса
useEffect(() => {
setCurrentPage(1);
}, [searchTerm]);
const handleAddAuthor = () => { const handleAddAuthor = () => {
if (selectedAuthorId) { if (selectedAuthorId) {
@ -87,6 +105,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
const handleTabChange = (role: AuthorRole) => { const handleTabChange = (role: AuthorRole) => {
setActiveTab(role); setActiveTab(role);
setCurrentPage(1); setCurrentPage(1);
setSearchTerm('');
setSelectedAuthorId(null); setSelectedAuthorId(null);
}; };
@ -96,6 +115,14 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
document.getElementById('other-authors-section')?.scrollIntoView({ behavior: 'smooth' }); document.getElementById('other-authors-section')?.scrollIntoView({ behavior: 'smooth' });
}; };
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
};
const clearSearch = () => {
setSearchTerm('');
};
if (!isOpen) return null; if (!isOpen) return null;
const { showPagination, displayedOtherAuthors, totalPages } = paginationData; const { showPagination, displayedOtherAuthors, totalPages } = paginationData;
@ -174,7 +201,7 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
</div> </div>
)} )}
{/* Все остальные авторы с пагинацией */} {/* Все остальные авторы с поиском и пагинацией */}
<div id="other-authors-section"> <div id="other-authors-section">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-700"> <h3 className="text-lg font-semibold text-gray-700">
@ -187,6 +214,35 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
)} )}
</div> </div>
{/* Поле поиска */}
<div className="mb-6">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search size={20} className="text-gray-400" />
</div>
<input
type="text"
placeholder="Поиск по имени..."
className="w-full pl-10 pr-10 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={searchTerm}
onChange={handleSearchChange}
/>
{searchTerm && (
<button
onClick={clearSearch}
className="absolute inset-y-0 right-0 pr-3 flex items-center"
>
<X size={20} className="text-gray-400 hover:text-gray-600" />
</button>
)}
</div>
{searchTerm && (
<p className="mt-2 text-sm text-gray-500">
Найдено: {filteredOtherAuthors.length} {filteredOtherAuthors.length === 1 ? 'автор' : 'авторов'}
</p>
)}
</div>
{displayedOtherAuthors.length > 0 ? ( {displayedOtherAuthors.length > 0 ? (
<> <>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@ -248,7 +304,10 @@ const AuthorSelectionModal: React.FC<AuthorSelectionModalProps> = ({
</> </>
) : ( ) : (
<p className="text-gray-500 text-center py-8"> <p className="text-gray-500 text-center py-8">
Нет доступных {searchTerm
? `Нет авторов, начинающихся с "${searchTerm}"`
: `Нет доступных ${roleLabels[activeTab].toLowerCase()}`
}
</p> </p>
)} )}
</div> </div>