diff --git a/src/app/api/books/controller.ts b/src/app/api/books/controller.ts index 96f7ebf..26a0610 100644 --- a/src/app/api/books/controller.ts +++ b/src/app/api/books/controller.ts @@ -58,39 +58,51 @@ export const getAllBooksController = async ( limit: number = 0, withStats: boolean = false, fromDate?: Date, - endDate?: Date + endDate?: Date, + searchQuery?: string ): Promise<{ books: (BookWithRequests | (BookWithRequests & BookStats))[]; total: number; totalPages: number; }> => { try { - // Calculate the offset (skip) for pagination + console.log("🛠 getAllBooksController params:", { + page, + limit, + fromDate, + endDate, + searchQuery, // ✅ log the search query here + }); const skip = page > 0 && limit > 0 ? (page - 1) * limit : undefined; - // create the date filter const where: Prisma.BookWhereInput = {}; if (fromDate && endDate) { const toEndOfDay = new Date(endDate); toEndOfDay.setHours(23, 59, 59, 999); - where.createdAt = { gte: fromDate, lte: toEndOfDay, }; } - // Fetch paginated books and total count + + if (searchQuery && searchQuery.trim() !== "") { + where.title = { + contains: searchQuery.trim(), + mode: "insensitive", + }; + } + const [books, total] = await Promise.all([ prisma.book.findMany({ where, skip, take: limit > 0 ? limit : undefined, include: { - requests: true, // Include related requests + requests: true, }, }), - prisma.book.count({ where }), // Get the total number of books + prisma.book.count({ where }), ]); const booksWithStats = withStats diff --git a/src/app/api/books/route.ts b/src/app/api/books/route.ts index 5c4c38d..6958dd8 100644 --- a/src/app/api/books/route.ts +++ b/src/app/api/books/route.ts @@ -35,6 +35,8 @@ export async function GET(req: Request) { // grab the date filter parameters - if none are specified then don't worry about it const fromDateStr = searchParams.get("fromDate"); const endDateStr = searchParams.get("endDate"); + const searchQuery = searchParams.get("search") ?? undefined; // <-- 👈 NEW + console.log("📥 API GET /api/books searchQuery:", searchQuery); const fromDate = fromDateStr ? new Date(fromDateStr) : undefined; const endDate = endDateStr ? new Date(endDateStr) : undefined; @@ -59,7 +61,8 @@ export async function GET(req: Request) { limit, withStats, effectiveFromDate, - effectiveEndDate + effectiveEndDate, + searchQuery ); //const books = await getAllBooksController(); return NextResponse.json(books); diff --git a/src/app/dashboard/datapage/page.tsx b/src/app/dashboard/datapage/page.tsx index 89eabae..f7d0f4e 100644 --- a/src/app/dashboard/datapage/page.tsx +++ b/src/app/dashboard/datapage/page.tsx @@ -19,29 +19,40 @@ export default function DataPage() { range?.from && range?.to ? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}` : "all time"; + const [users, setUsers] = useState([]); const [requests, setRequests] = useState([]); const [bookStats, setBookStats] = useState>({}); const [books, setBooks] = useState([]); const [requestCount, setRequestCount] = useState(0); - const [currentBookPage, setCurrentBookPage] = useState(1); // For Book Catalog - const [totalBookPages, setTotalBookPages] = useState(0); // Total pages for books + const [currentBookPage, setCurrentBookPage] = useState(1); + const [totalBookPages, setTotalBookPages] = useState(0); + + const [currentUserPage, setCurrentUserPage] = useState(1); + const [totalUserPages, setTotalUserPages] = useState(0); - const [currentUserPage, setCurrentUserPage] = useState(1); // For User History - const [totalUserPages, setTotalUserPages] = useState(0); // Total pages for users + const [searchData, setSearchData] = useState(""); // NEW: search input useEffect(() => { if (activeTab === "Book Catalog") { const fetchBooks = async () => { try { + console.log("📚 Fetching books with params:", { + page: currentBookPage, + search: searchData, + from: range?.from, + to: range?.to, + }); const booksResult = await getAllBooks({ page: currentBookPage, limit: 10, withStats: true, fromDate: range?.from, endDate: range?.to, + search: searchData, }); + if (booksResult) { const { books: fetchedBooks, totalPages: fetchedTotalPages } = booksResult as { @@ -49,16 +60,17 @@ export default function DataPage() { totalPages: number; }; - const bookStats: Record = {}; + const stats: Record = {}; for (const book of fetchedBooks) { - bookStats[book.id] = { + stats[book.id] = { totalRequests: book.requests ? book.requests.length : 0, uniqueUsers: book.uniqueUsers || 0, }; } + setBooks(fetchedBooks); setTotalBookPages(fetchedTotalPages); - setBookStats(bookStats); + setBookStats(stats); } } catch (err) { console.error("Failed to fetch books:", err); @@ -67,7 +79,13 @@ export default function DataPage() { fetchBooks(); } - }, [currentBookPage, activeTab, range]); // Refetch books when currentBookPage or activeTab changes + }, [currentBookPage, activeTab, range, searchData]); // include searchData + + useEffect(() => { + if (activeTab === "Book Catalog") { + setCurrentBookPage(1); // Reset to first page when search changes + } + }, [searchData, activeTab]); useEffect(() => { if (activeTab === "User History") { @@ -82,6 +100,7 @@ export default function DataPage() { if (usersResult) { const { users: fetchedUsers, totalPages: fetchedTotalPages } = usersResult; + const allRequests = fetchedUsers.flatMap( (user) => user.requests || [] ); @@ -100,17 +119,15 @@ export default function DataPage() { fetchUsers(); } - }, [range, currentUserPage, activeTab]); // Refetch users when currentUserPage or activeTab changes + }, [range, currentUserPage, activeTab]); useEffect(() => { const fetchData = async () => { try { - // promise.allSettled so they can fail independently. const [requestCountResult] = await Promise.allSettled([ getRequestCount(range?.from, range?.to), ]); - // calculate the number of requests if ( requestCountResult.status === "fulfilled" && requestCountResult.value !== undefined @@ -126,6 +143,7 @@ export default function DataPage() { console.error("Unexpected error in fetchData:", err); } }; + fetchData(); }, [range]); @@ -165,9 +183,8 @@ export default function DataPage() { ) : null} - - {/* Tab Content */} + {activeTab === "Overview" && ( )} + {activeTab === "Book Catalog" && ( <> {totalBookPages > 1 && (
@@ -212,6 +232,7 @@ export default function DataPage() { )} )} + {activeTab === "User History" && ( <> void) | null; +interface SearchBarProps { + setSearchData: (searchData: string) => void; button: React.ReactNode; button2?: React.ReactNode; placeholderText: string; + defaultValue?: string; } -const SearchBar = (props: searchBarProps) => { - const { setSearchData, button, button2, placeholderText } = props; - +const SearchBar = ({ + setSearchData, + button, + button2, + placeholderText, + defaultValue = "", +}: SearchBarProps) => { const searchInputRef = useRef(null); + const [input, setInput] = useState(defaultValue); + + useEffect(() => { + setInput(defaultValue); // keep input in sync with parent + }, [defaultValue]); const clickBar = () => { - if (searchInputRef.current) { - searchInputRef.current.focus(); - } + searchInputRef.current?.focus(); }; - // may use this function later, also TODO: turn this into a useCallback - // function handleKeyDown(event: { key: string }) { - // if (event.key === "Enter") { - - // } - // } - const handleInputChange = (e: React.ChangeEvent) => { - const { value } = e.target; - - if (setSearchData) { - setSearchData(value.toLowerCase()); - } + const value = e.target.value; + setInput(value); + setSearchData(value.toLowerCase()); }; return ( @@ -46,6 +45,7 @@ const SearchBar = (props: searchBarProps) => { className="w-full focus:outline-none text-black placeholder-medium-grey-border text-base" name="search bar" onChange={handleInputChange} + value={input} placeholder={placeholderText} />