diff --git a/application/api/user/routes.py b/application/api/user/routes.py index e305845d4..b6bd5feac 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -479,11 +479,22 @@ def get(self): sort_order = request.args.get("order", "desc") # Default to 'desc' page = int(request.args.get("page", 1)) # Default to 1 rows_per_page = int(request.args.get("rows", 10)) # Default to 10 + # add .strip() to remove leading and trailing whitespaces + search_term = request.args.get( + "search", "" + ).strip() # add search for filter documents - # Prepare + # Prepare query for filtering query = {"user": user} + if search_term: + query["name"] = { + "$regex": search_term, + "$options": "i", # using case-insensitive search + } + total_documents = sources_collection.count_documents(query) total_pages = max(1, math.ceil(total_documents / rows_per_page)) + page = min(max(1, page), total_pages) # add this to make sure page inbound is within the range sort_order = 1 if sort_order == "asc" else -1 skip = (page - 1) * rows_per_page diff --git a/application/celery_init.py b/application/celery_init.py index c5838083c..185cc87f1 100644 --- a/application/celery_init.py +++ b/application/celery_init.py @@ -2,14 +2,22 @@ from application.core.settings import settings from celery.signals import setup_logging + def make_celery(app_name=__name__): - celery = Celery(app_name, broker=settings.CELERY_BROKER_URL, backend=settings.CELERY_RESULT_BACKEND) + celery = Celery( + app_name, + broker=settings.CELERY_BROKER_URL, + backend=settings.CELERY_RESULT_BACKEND, + ) celery.conf.update(settings) return celery + @setup_logging.connect def config_loggers(*args, **kwargs): from application.core.logging_config import setup_logging + setup_logging() + celery = make_celery() diff --git a/frontend/src/components/DocumentPagination.tsx b/frontend/src/components/DocumentPagination.tsx index b0532362b..f02ef1c05 100644 --- a/frontend/src/components/DocumentPagination.tsx +++ b/frontend/src/components/DocumentPagination.tsx @@ -19,7 +19,10 @@ const Pagination: React.FC = ({ onPageChange, onRowsPerPageChange, }) => { - const [rowsPerPageOptions] = useState([5, 10, 15, 20]); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const rowsPerPageOptions = [5, 10, 20, 50]; + + const toggleDropdown = () => setIsDropdownOpen((prev) => !prev); const handlePreviousPage = () => { if (currentPage > 1) { @@ -41,31 +44,51 @@ const Pagination: React.FC = ({ onPageChange(totalPages); }; + const handleSelectRowsPerPage = (rows: number) => { + setIsDropdownOpen(false); + onRowsPerPageChange(rows); + }; + return (
-
+ {/* Rows per page dropdown */} +
Rows per page: - +
+ +
+ {rowsPerPageOptions.map((option) => ( +
handleSelectRowsPerPage(option)} + className={`cursor-pointer px-4 py-2 text-xs hover:bg-gray-100 dark:hover:bg-neutral-700 ${ + rowsPerPage === option + ? 'bg-gray-100 dark:bg-neutral-700 dark:text-light-gray' + : 'bg-white dark:bg-dark-charcoal dark:text-light-gray' + }`} + > + {option} +
+ ))} +
+
+ {/* Pagination controls */}
Page {currentPage} of {totalPages}
-
@@ -85,7 +108,7 @@ const Pagination: React.FC = ({ > arrow @@ -96,7 +119,7 @@ const Pagination: React.FC = ({ > arrow @@ -107,7 +130,7 @@ const Pagination: React.FC = ({ > arrow diff --git a/frontend/src/components/DropdownMenu.tsx b/frontend/src/components/DropdownMenu.tsx index 787d3b847..2e6c922c4 100644 --- a/frontend/src/components/DropdownMenu.tsx +++ b/frontend/src/components/DropdownMenu.tsx @@ -48,7 +48,7 @@ export default function DropdownMenu({
diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index db2e58555..8e563f65a 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import userService from '../api/services/userService'; import SyncIcon from '../assets/sync.svg'; @@ -40,25 +40,18 @@ const Documents: React.FC = ({ const { t } = useTranslation(); const dispatch = useDispatch(); // State for search input - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); // State for modal: active/inactive const [modalState, setModalState] = useState('INACTIVE'); // Initialize with inactive state - const [isOnboarding, setIsOnboarding] = useState(false); // State for onboarding flag - const [loading, setLoading] = useState(false); + const [isOnboarding, setIsOnboarding] = useState(false); // State for onboarding flag + const [loading, setLoading] = useState(false); const [sortField, setSortField] = useState<'date' | 'tokens'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // Pagination const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(10); const [totalPages, setTotalPages] = useState(1); - // const [totalDocuments, setTotalDocuments] = useState(0); - // Filter documents based on the search term - const filteredDocuments = paginatedDocuments?.filter((document) => - document.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); - // State for documents - const currentDocuments = filteredDocuments ?? []; - console.log('currentDocuments', currentDocuments); + const currentDocuments = paginatedDocuments ?? []; const syncOptions = [ { label: 'Never', value: 'never' }, { label: 'Daily', value: 'daily' }, @@ -66,36 +59,50 @@ const Documents: React.FC = ({ { label: 'Monthly', value: 'monthly' }, ]; - const refreshDocs = ( - field: 'date' | 'tokens' | undefined, - pageNumber?: number, - rows?: number, - ) => { - const page = pageNumber ?? currentPage; - const rowsPerPg = rows ?? rowsPerPage; + const refreshDocs = useCallback( + ( + field: 'date' | 'tokens' | undefined, + pageNumber?: number, + rows?: number, + ) => { + const page = pageNumber ?? currentPage; + const rowsPerPg = rows ?? rowsPerPage; + + // If field is undefined, (Pagination or Search) use the current sortField + const newSortField = field ?? sortField; + + // If field is undefined, (Pagination or Search) use the current sortOrder + const newSortOrder = + field === sortField + ? sortOrder === 'asc' + ? 'desc' + : 'asc' + : sortOrder; - if (field !== undefined) { - if (field === sortField) { - // Toggle sort order - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); - } else { - // Change sort field and reset order to 'desc' - setSortField(field); - setSortOrder('desc'); + // If field is defined, update the sortField and sortOrder + if (field) { + setSortField(newSortField); + setSortOrder(newSortOrder); } - } - getDocsWithPagination(sortField, sortOrder, page, rowsPerPg) - .then((data) => { - //dispatch(setSourceDocs(data ? data.docs : [])); - dispatch(setPaginatedDocuments(data ? data.docs : [])); - setTotalPages(data ? data.totalPages : 0); - //setTotalDocuments(data ? data.totalDocuments : 0); - }) - .catch((error) => console.error(error)) - .finally(() => { - setLoading(false); - }); - }; + + getDocsWithPagination( + newSortField, + newSortOrder, + page, + rowsPerPg, + searchTerm, + ) + .then((data) => { + dispatch(setPaginatedDocuments(data ? data.docs : [])); + setTotalPages(data ? data.totalPages : 0); + }) + .catch((error) => console.error(error)) + .finally(() => { + setLoading(false); + }); + }, + [currentPage, rowsPerPage, sortField, sortOrder, searchTerm], + ); const handleManageSync = (doc: Doc, sync_frequency: string) => { setLoading(true); @@ -127,10 +134,16 @@ const Documents: React.FC = ({ }; useEffect(() => { + // console.log('modalState', modalState); if (modalState === 'INACTIVE') { refreshDocs(sortField, currentPage, rowsPerPage); } - }, [modalState, sortField, currentPage, rowsPerPage]); + }, [modalState]); + + useEffect(() => { + // undefine to prevent reset the sort order + refreshDocs(undefined, 1, rowsPerPage); + }, [searchTerm]); return (
@@ -145,11 +158,18 @@ const Documents: React.FC = ({ type="text" id="document-search-input" value={searchTerm} - onChange={(e) => setSearchTerm(e.target.value)} // Handle search input change + onChange={(e) => { + setSearchTerm(e.target.value); + setCurrentPage(1); + // refreshDocs(sortField, 1, rowsPerPage); + // do not call refreshDocs here the state is async + // so it will not have the updated value + }} // Handle search input change />
+
+ )} + {/* outside scrollable area */} + { + setCurrentPage(page); + refreshDocs(undefined, page, rowsPerPage); + }} + onRowsPerPageChange={(rows) => { + setRowsPerPage(rows); + setCurrentPage(1); + refreshDocs(undefined, 1, rows); + }} + /> + {/* Conditionally render the Upload modal based on modalState */} {modalState === 'ACTIVE' && (
@@ -263,25 +325,6 @@ const Documents: React.FC = ({
)} - {/* Pagination component with props: - # Note: Every time the page changes, - the refreshDocs function is called with the updated page number and rows per page. - and reset cursor paginated query parameter to undefined. - */} - { - setCurrentPage(page); - refreshDocs(sortField, page, rowsPerPage); - }} - onRowsPerPageChange={(rows) => { - setRowsPerPage(rows); - setCurrentPage(1); - refreshDocs(sortField, 1, rows); - }} - /> ); };