help with pagination

This commit is contained in:
timothycarambat
2023-07-28 19:29:52 -07:00
parent 9d1522c496
commit 422ca3bd9c
8 changed files with 209 additions and 83 deletions
+3 -1
View File
@@ -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);
+8 -2
View File
@@ -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();
+15 -4
View File
@@ -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();
@@ -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 (
<div className="my-4 flex justify-center">
<ul className="pagination pagination-sm">
{pageItems.map((item, i) =>
typeof item === 'number' ? (
<button
key={item}
className={`border px-3 py-2 text-sm ${
currentPage === item
? 'border-blue-500 text-blue-500'
: 'border-gray-300 text-gray-500'
} ${i === 0 ? 'rounded-l-lg' : ''} ${
i === pageItems.length - 1 ? 'rounded-r-lg' : ''
}`}
onClick={() => gotoPage(item)}
>
{numberWithCommas(item)}
</button>
) : (
<button
key={item}
className={`border border-gray-300 px-3 py-2 text-sm text-gray-500`}
>
...
</button>
)
)}
</ul>
</div>
);
}
+13 -8
View File
@@ -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) => {
+11 -4
View File
@@ -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) => {
@@ -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 (
<div
key={document.uid}
id={`document-row-${document.id}`}
key={document.id}
className="flex w-full items-center gap-5 px-7.5 py-3 text-gray-600 hover:bg-gray-3 dark:hover:bg-meta-4"
>
<div className="flex w-full items-center gap-3">
@@ -144,42 +160,40 @@ export default function DocumentsList({
</div>
</div>
<div className="w-6/12 2xsm:w-5/12 md:w-3/12">
<span className="font-medium text-slate-700">
{truncate(document.workspace.name, 20)}
</span>
<a
href={paths.workspace(
organization.slug,
document.workspace.slug
)}
className="hover:text-blue-500 hover:underline"
>
<span className="font-medium">
{document.workspace.name || ''}
</span>
</a>
</div>
<div className="hidden w-4/12 md:block xl:w-3/12">
<span>
{moment(document.created_at).format('MMM D, YYYY')}
<div className="hidden w-3/12 overflow-x-scroll md:block xl:w-3/12">
<span className="font-medium">
{moment.unix(document.createdAt).format('lll')}
</span>
</div>
<div className="w-5/12 2xsm:w-4/12 md:w-3/12 xl:w-2/12">
<div className="flex items-center gap-x-1">
<span className="font-medium text-slate-700">
{document.status}
</span>
</div>
<span className="inline-block rounded bg-green-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-green-500">
Cached
</span>
</div>
<div className="hidden w-2/12 text-center 2xsm:block md:w-1/12">
<Link
to={paths.document(organization.slug, document.uid)}
className="h-6 w-6"
<div className=" flex items-center gap-x-2">
<a
href={paths.document(
organization.slug,
document.workspace.slug,
document.id
)}
className="rounded-lg px-2 py-1 text-blue-400 transition-all duration-300 hover:bg-blue-50 hover:text-blue-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="h-6 w-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</Link>
Details
</a>
</div>
</div>
</div>
@@ -188,38 +202,30 @@ export default function DocumentsList({
</>
</div>
) : (
<div className="flex h-60 w-full items-center justify-center px-7.5">
<div className="text-center">
<p className="mb-4 text-lg font-semibold text-black dark:text-white">
No documents found
</p>
<p className="mb-6 text-sm font-medium text-black dark:text-white">
Once you upload a document, you'll see it here.
</p>
<div>
<div className="flex min-h-[40vh] w-full px-8">
<div className="flex flex h-auto w-full flex-col items-center justify-center gap-y-2 rounded-lg bg-slate-50">
<p>You have no documents in any workspaces!</p>
<p>
Get started managing documents by adding them to workspaces
via the UI or code.
</p>
<button
type="button"
className="text-xl text-blue-500 underline"
>
Show code example (coming soon)
</button>
</div>
</div>
</div>
)}
</div>
<div className="my-4 flex justify-center">
{Array.from(
{ length: Math.ceil(totalDocuments! / pageSize) },
(_, i) => i + 1
).map((page) => (
<button
key={page}
className={`border px-3 py-2 text-sm ${
currentPage === page
? 'border-blue-500 text-blue-500'
: 'border-gray-300 text-gray-500'
}`}
onClick={() => setCurrentPage(page)}
>
{page}
</button>
))}
</div>
<DocumentListPagination
pageCount={Math.ceil(totalDocuments! / Organization.documentPageSize)}
currentPage={currentPage}
gotoPage={updatePage}
/>
{canUpload ? (
<UploadDocumentModal workspaces={workspaces} />
) : (
@@ -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({
</>
)}
</div>
<DocumentListPagination
pageCount={Math.ceil(totalDocuments! / Workspace.documentPageSize)}
currentPage={currentPage}
gotoPage={updatePage}
/>
{canUpload ? (
<UploadDocumentModal workspace={workspace} />
) : (