diff --git a/backend/endpoints/v1/organizations/index.js b/backend/endpoints/v1/organizations/index.js index 8f8a873..aac0fa4 100644 --- a/backend/endpoints/v1/organizations/index.js +++ b/backend/endpoints/v1/organizations/index.js @@ -504,7 +504,9 @@ function organizationEndpoints(app) { true ); - const totalDocuments = await WorkspaceDocument.count(`organization_id = ${organization.id}`); + const totalDocuments = await WorkspaceDocument.count( + `organization_id = ${organization.id}` + ); response.status(200).json({ documents, totalDocuments }); } catch (e) { console.log(e.message, e); diff --git a/backend/endpoints/v1/workspaces/index.js b/backend/endpoints/v1/workspaces/index.js index 040ccfd..2f826b6 100644 --- a/backend/endpoints/v1/workspaces/index.js +++ b/backend/endpoints/v1/workspaces/index.js @@ -174,6 +174,8 @@ function workspaceEndpoints(app) { async function (request, response) { try { const { orgSlug, wsSlug } = request.params; + const page = parseInt(request.query.page) || 1; + const pageSize = parseInt(request.query.pageSize) || 10; const user = await userFromSession(request); if (!user) { response.sendStatus(403).end(); @@ -196,10 +198,14 @@ function workspaceEndpoints(app) { const documents = await WorkspaceDocument.where( `organization_id = ${organization.id} AND workspace_id = ${workspace.id}`, - null, + pageSize, + (page - 1) * pageSize, true ); - response.status(200).json({ documents }); + const totalDocuments = await WorkspaceDocument.count( + `organization_id = ${organization.id} AND workspace_id = ${workspace.id}` + ); + response.status(200).json({ documents, totalDocuments }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); diff --git a/backend/models/workspaceDocument.js b/backend/models/workspaceDocument.js index 9bae90e..2dc77b5 100644 --- a/backend/models/workspaceDocument.js +++ b/backend/models/workspaceDocument.js @@ -134,16 +134,25 @@ const WorkspaceDocument = { count: async function (clause = null) { const db = await this.db(); const result = await db.get( - `SELECT COUNT(*) as total FROM ${this.tablename} ${clause ? `WHERE ${clause}` : ""}` + `SELECT COUNT(*) as total FROM ${this.tablename} ${ + clause ? `WHERE ${clause}` : "" + }` ); await db.close(); return result.total; }, - where: async function (clause = null, limit = null, offset = null, withReferences = false) { + where: async function ( + clause = null, + limit = null, + offset = null, + withReferences = false + ) { if (!withReferences) { const db = await this.db(); const results = await db.all( - `SELECT * FROM ${this.tablename} ${clause ? `WHERE ${clause}` : ""} ${offset ? `OFFSET ${offset}` : ""} ${limit ? `LIMIT ${limit}` : ""}` + `SELECT * FROM ${this.tablename} ${clause ? `WHERE ${clause}` : ""} ${ + offset ? `OFFSET ${offset}` : "" + } ${limit ? `LIMIT ${limit}` : ""}` ); await db.close(); return results; @@ -162,7 +171,9 @@ const WorkspaceDocument = { LEFT JOIN ${ OrganizationWorkspace.tablename } as ow ON ow.id = wd.workspace_id - ${clause ? `WHERE wd.${clause}` : ""} ${limit ? `LIMIT ${limit}` : ""} ${offset ? `OFFSET ${offset}` : ""}` + ${clause ? `WHERE wd.${clause}` : ""} ${limit ? `LIMIT ${limit}` : ""} ${ + offset ? `OFFSET ${offset}` : "" + }` ); await db.close(); diff --git a/frontend/src/components/DocumentPaginator/index.tsx b/frontend/src/components/DocumentPaginator/index.tsx new file mode 100644 index 0000000..3d72889 --- /dev/null +++ b/frontend/src/components/DocumentPaginator/index.tsx @@ -0,0 +1,63 @@ +import { numberWithCommas } from '../../utils/numbers'; + +function generatePageItems(total: number, current: number) { + if (total <= 1) return []; + const center = [current - 2, current - 1, current, current + 1, current + 2], + filteredCenter = center.filter((p) => p > 1 && p < total), + includeThreeLeft = current === 5, + includeThreeRight = current === total - 4, + includeLeftDots = current > 5, + includeRightDots = current < total - 4; + + if (includeThreeLeft) filteredCenter.unshift(2); + if (includeThreeRight) filteredCenter.push(total - 1); + + if (includeLeftDots) filteredCenter.unshift('...'); + if (includeRightDots) filteredCenter.push('...'); + + return [1, ...filteredCenter, total]; +} + +interface IPaginationProps { + pageCount: number; + currentPage?: number; + gotoPage: (page: number) => void; +} + +export default function DocumentListPagination({ + pageCount, + currentPage = 0, + gotoPage, +}: IPaginationProps) { + const pageItems = generatePageItems(pageCount, currentPage); + return ( +
+ +
+ ); +} diff --git a/frontend/src/models/organization.ts b/frontend/src/models/organization.ts index 60fd4ee..a136282 100644 --- a/frontend/src/models/organization.ts +++ b/frontend/src/models/organization.ts @@ -2,6 +2,7 @@ import { API_BASE } from '../utils/constants'; import { baseHeaders } from '../utils/request'; const Organization = { + documentPageSize: 25, create: async (orgName: string) => { let error; const organization = await fetch(`${API_BASE}/v1/org/create`, { @@ -58,20 +59,24 @@ const Organization = { headers: baseHeaders(), }).then((res) => res.json()); }, - documents: async (slug: string, page: number = 1) => { - return fetch(`${API_BASE}/v1/org/${slug}/documents?page=${page}`, { - method: 'GET', - cache: 'no-cache', - headers: baseHeaders(), - }) + documents: async (slug: string, page: number = 1, pageSize?: number) => { + return fetch( + `${API_BASE}/v1/org/${slug}/documents?page=${page}&pageSize=${ + pageSize || Organization.documentPageSize + }`, + { + method: 'GET', + cache: 'no-cache', + headers: baseHeaders(), + } + ) .then((res) => { console.log('Response:', res); return res.json(); }) - .catch((e) => { console.error(e); - return {documents: [], totalDocuments: 0}; + return { documents: [], totalDocuments: 0 }; }); }, workspaces: async (slug: string) => { diff --git a/frontend/src/models/workspace.ts b/frontend/src/models/workspace.ts index 717bdd8..b55f2a0 100644 --- a/frontend/src/models/workspace.ts +++ b/frontend/src/models/workspace.ts @@ -2,6 +2,7 @@ import { API_BASE } from '../utils/constants'; import { baseHeaders } from '../utils/request'; const Workspace = { + documentPageSize: 100, createNew: async (orgSlug: string, workspaceName: string) => { let error; const workspace = await fetch( @@ -54,9 +55,16 @@ const Workspace = { } ).then((res) => res.json()); }, - documents: async (orgSlug: string, workspaceSlug: string) => { + documents: async ( + orgSlug: string, + workspaceSlug: string, + page: number = 1, + pageSize?: number + ) => { return fetch( - `${API_BASE}/v1/org/${orgSlug}/workspace/${workspaceSlug}/documents`, + `${API_BASE}/v1/org/${orgSlug}/workspace/${workspaceSlug}/documents?page=${page}&pageSize=${ + pageSize || Workspace.documentPageSize + }`, { method: 'GET', cache: 'no-cache', @@ -64,10 +72,9 @@ const Workspace = { } ) .then((res) => res.json()) - .then((res) => res?.documents || []) .catch((e) => { console.error(e); - return []; + return { documents: [], totalDocuments: 0 }; }); }, delete: async (orgSlug: string, workspaceSlug: string) => { diff --git a/frontend/src/pages/Dashboard/DocumentsList/index.tsx b/frontend/src/pages/Dashboard/DocumentsList/index.tsx index a8149cc..b6c59d3 100644 --- a/frontend/src/pages/Dashboard/DocumentsList/index.tsx +++ b/frontend/src/pages/Dashboard/DocumentsList/index.tsx @@ -9,6 +9,8 @@ import truncate from 'truncate'; import System from '../../../models/system'; import UploadDocumentModal from './UploadModal'; import UploadModalNoKey from './UploadModal/UploadModalNoKey'; +import DocumentListPagination from '../../../components/DocumentPaginator'; +import useQuery from '../../../hooks/useQuery'; export default function DocumentsList({ organization, @@ -19,12 +21,25 @@ export default function DocumentsList({ workspaces: any; knownConnector: any; }) { + const query = useQuery(); const [loading, setLoading] = useState(true); const [documents, setDocuments] = useState([]); - const [totalDocuments, setTotalDocuments] = useState(); + const [totalDocuments, setTotalDocuments] = useState(0); const [canUpload, setCanUpload] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const pageSize = 10; + const [currentPage, setCurrentPage] = useState( + Number(query.get('docPage')) || 1 + ); + + function updatePage(pgNum: number) { + const setTo = pgNum <= 0 ? 1 : pgNum; + query.set('docPage', setTo.toString()); + window.history.replaceState( + {}, + '', + `${location.pathname}?${query.toString()}` + ); + setCurrentPage(setTo); + } useEffect(() => { async function getDocs(slug?: string) { @@ -131,7 +146,8 @@ export default function DocumentsList({ {documents.map((document) => { return (
@@ -144,42 +160,40 @@ export default function DocumentsList({
- - {truncate(document.workspace.name, 20)} - + + + {document.workspace.name || ''} + +
-
- - {moment(document.created_at).format('MMM D, YYYY')} +
+ + {moment.unix(document.createdAt).format('lll')}
-
- - {document.status} - -
+ + Cached +
-
- + - - - - + Details +
@@ -188,38 +202,30 @@ export default function DocumentsList({ ) : ( -
-
-

- No documents found -

-

- Once you upload a document, you'll see it here. -

+
+
+
+

You have no documents in any workspaces!

+

+ Get started managing documents by adding them to workspaces + via the UI or code. +

+ +
)}
- -
- {Array.from( - { length: Math.ceil(totalDocuments! / pageSize) }, - (_, i) => i + 1 - ).map((page) => ( - - ))} -
- + {canUpload ? ( ) : ( diff --git a/frontend/src/pages/WorkspaceDashboard/DocumentsList/index.tsx b/frontend/src/pages/WorkspaceDashboard/DocumentsList/index.tsx index f24ff18..710162f 100644 --- a/frontend/src/pages/WorkspaceDashboard/DocumentsList/index.tsx +++ b/frontend/src/pages/WorkspaceDashboard/DocumentsList/index.tsx @@ -10,8 +10,10 @@ import System from '../../../models/system'; import UploadDocumentModal from './UploadModal'; import UploadModalNoKey from './UploadModal/UploadModalNoKey'; import Document from '../../../models/document'; +import useQuery from '../../../hooks/useQuery'; import { APP_NAME } from '../../../utils/constants'; import { useParams } from 'react-router-dom'; +import DocumentListPagination from '../../../components/DocumentPaginator'; export default function DocumentsList({ knownConnector, @@ -24,9 +26,26 @@ export default function DocumentsList({ workspace: any; workspaces: any[]; }) { + const query = useQuery(); const [loading, setLoading] = useState(true); const [documents, setDocuments] = useState([]); + const [totalDocuments, setTotalDocuments] = useState(0); const [canUpload, setCanUpload] = useState(false); + const [currentPage, setCurrentPage] = useState( + Number(query.get('docPage')) || 1 + ); + + function updatePage(pgNum: number) { + const setTo = pgNum <= 0 ? 1 : pgNum; + query.set('docPage', setTo.toString()); + window.history.replaceState( + {}, + '', + `${location.pathname}?${query.toString()}` + ); + setCurrentPage(setTo); + } + const deleteDocument = async (documentId: number) => { if ( !confirm( @@ -42,11 +61,13 @@ export default function DocumentsList({ useEffect(() => { async function getDocs(orgSlug: string, wsSlug?: string) { if (!orgSlug || !wsSlug) return false; - const documents = await Workspace.documents(orgSlug, wsSlug); + const response = await Workspace.documents(orgSlug, wsSlug, currentPage); const { exists: hasOpenAIKey } = await System.hasSetting( 'open_ai_api_key' ); - setDocuments(documents); + + setTotalDocuments(response.totalDocuments); + setDocuments(response.documents); setCanUpload(hasOpenAIKey); setLoading(false); } @@ -221,6 +242,11 @@ export default function DocumentsList({ )}
+ {canUpload ? ( ) : (