import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import userService from '../api/services/userService'; import SyncIcon from '../assets/sync.svg'; import Trash from '../assets/trash.svg'; import caretSort from '../assets/caret-sort.svg'; import DropdownMenu from '../components/DropdownMenu'; import SkeletonLoader from '../components/SkeletonLoader'; import Input from '../components/Input'; import Upload from '../upload/Upload'; // Import the Upload component import Pagination from '../components/DocumentPagination'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { Doc, DocumentsProps, ActiveState } from '../models/misc'; // Ensure ActiveState type is imported import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi'; import { setSourceDocs } from '../preferences/preferenceSlice'; import { setPaginatedDocuments } from '../preferences/preferenceSlice'; import { truncate } from '../utils/stringUtils'; // Utility function to format numbers const formatTokens = (tokens: number): string => { const roundToTwoDecimals = (num: number): string => { return (Math.round((num + Number.EPSILON) * 100) / 100).toString(); }; if (tokens >= 1_000_000_000) { return roundToTwoDecimals(tokens / 1_000_000_000) + 'b'; } else if (tokens >= 1_000_000) { return roundToTwoDecimals(tokens / 1_000_000) + 'm'; } else if (tokens >= 1_000) { return roundToTwoDecimals(tokens / 1_000) + 'k'; } else { return tokens.toString(); } }; const Documents: React.FC = ({ paginatedDocuments, handleDeleteDocument, }) => { const { t } = useTranslation(); const dispatch = useDispatch(); // State for search input 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 [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 currentDocuments = paginatedDocuments ?? []; const syncOptions = [ { label: 'Never', value: 'never' }, { label: 'Daily', value: 'daily' }, { label: 'Weekly', value: 'weekly' }, { label: 'Monthly', value: 'monthly' }, ]; 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 is defined, update the sortField and sortOrder if (field) { setSortField(newSortField); setSortOrder(newSortOrder); } setLoading(true); 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); userService .manageSync({ source_id: doc.id, sync_frequency }) .then(() => { // First, fetch the updated source docs return getDocs(); }) .then((data) => { dispatch(setSourceDocs(data)); return getDocsWithPagination( sortField, sortOrder, currentPage, rowsPerPage, ); }) .then((paginatedData) => { dispatch( setPaginatedDocuments(paginatedData ? paginatedData.docs : []), ); setTotalPages(paginatedData ? paginatedData.totalPages : 0); }) .catch((error) => console.error('Error in handleManageSync:', error)) .finally(() => { setLoading(false); }); }; useEffect(() => { if (modalState === 'INACTIVE') { refreshDocs(sortField, currentPage, rowsPerPage); } }, [modalState]); useEffect(() => { // undefine to prevent reset the sort order refreshDocs(undefined, 1, rowsPerPage); }, [searchTerm]); return (
{ 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 />
{loading ? ( ) : (
{/*} */} {!currentDocuments?.length && ( )} {Array.isArray(currentDocuments) && currentDocuments.map((document, index) => ( {/*} */} ))}
{t('settings.documents.name')}
{t('settings.documents.date')} refreshDocs('date')} src={caretSort} alt="sort" />
{t('settings.documents.tokenUsage')} refreshDocs('tokens')} src={caretSort} alt="sort" />
{t('settings.documents.type')}
{' '}
{t('settings.documents.noData')}
{truncate(document.name, 50)} {document.date} {document.tokens ? formatTokens(+document.tokens) : ''} {document.type === 'remote' ? 'Pre-loaded' : 'Private'}
{document.type !== 'remote' && ( Delete { event.stopPropagation(); handleDeleteDocument(index, document); }} /> )} {document.syncFrequency && (
{ handleManageSync(document, value); }} defaultValue={document.syncFrequency} icon={SyncIcon} />
)}
)}
{/* 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' && (
{/* Your Upload component */} setModalState('INACTIVE')} />
)}
); }; Documents.propTypes = { //documents: PropTypes.array.isRequired, handleDeleteDocument: PropTypes.func.isRequired, }; export default Documents;