mirror of
https://github.com/Mintplex-Labs/vector-admin.git
synced 2026-08-27 20:30:05 -04:00
help with pagination
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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} />
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user