mirror of
https://github.com/diced/zipline.git
synced 2026-04-28 10:43:06 -07:00
fix: #1029
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { type Response } from '@/lib/api/response';
|
||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
||||
import { useTitle } from '@/lib/client/hooks/useTitle';
|
||||
import { Folder } from '@/lib/db/models/folder';
|
||||
import { FolderBreadcrumb } from '@/lib/folderHierarchy';
|
||||
import { useTitle } from '@/lib/client/hooks/useTitle';
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
Card,
|
||||
Container,
|
||||
Group,
|
||||
Pagination,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Stack,
|
||||
@@ -16,7 +19,7 @@ import {
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { IconFolder, IconUpload } from '@tabler/icons-react';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { lazy, Suspense, useMemo, useState } from 'react';
|
||||
import { Link, Params, useLoaderData, useNavigate } from 'react-router-dom';
|
||||
|
||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||
@@ -58,6 +61,8 @@ function PublicFolderCard({ folder }: { folder: Partial<Folder> }) {
|
||||
);
|
||||
}
|
||||
|
||||
const PER_PAGE_OPTIONS = [9, 12, 15, 30, 45];
|
||||
|
||||
export function Component() {
|
||||
const { folder } = useLoaderData<typeof loader>();
|
||||
const navigate = useNavigate();
|
||||
@@ -81,6 +86,21 @@ export function Component() {
|
||||
const breadcrumbs = buildBreadcrumbs();
|
||||
const children = (folder.children ?? []) as Partial<Folder>[];
|
||||
|
||||
const [perpage, setPerpage] = useState(15);
|
||||
const [page, setPage] = useQueryState('page', 1);
|
||||
|
||||
const from = (page - 1) * perpage + 1;
|
||||
const to = Math.min(page * perpage, folder.files?.length ?? 0);
|
||||
const totalRecords = folder.files?.length ?? 0;
|
||||
const cachedPages = Math.ceil(totalRecords / perpage);
|
||||
|
||||
const visible = useMemo(() => {
|
||||
if (!folder.files) return [];
|
||||
|
||||
const start = (page - 1) * perpage;
|
||||
return folder.files.slice(start, start + perpage);
|
||||
}, [folder.files, page, perpage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container my='lg'>
|
||||
@@ -132,7 +152,7 @@ export function Component() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{(folder.files?.length ?? 0) > 0 && (
|
||||
{(visible.length ?? 0) > 0 && (
|
||||
<>
|
||||
<Title order={3} mt='md' mb='sm'>
|
||||
Files
|
||||
@@ -145,7 +165,7 @@ export function Component() {
|
||||
}}
|
||||
spacing='md'
|
||||
>
|
||||
{folder.files?.map((file: any) => (
|
||||
{visible.map((file: any) => (
|
||||
<Suspense fallback={<Skeleton height={350} animate />} key={file.id}>
|
||||
<DashboardFile file={file} reduce />
|
||||
</Suspense>
|
||||
@@ -159,6 +179,33 @@ export function Component() {
|
||||
This folder is empty.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Group justify='space-between' align='center' mt='md'>
|
||||
<Text size='sm'>{`${from} - ${to} / ${totalRecords} files`}</Text>
|
||||
|
||||
<Group gap='sm'>
|
||||
<Select
|
||||
value={perpage.toString()}
|
||||
data={PER_PAGE_OPTIONS.map((val) => ({ value: val.toString(), label: `${val}` }))}
|
||||
onChange={(value) => {
|
||||
setPerpage(Number(value));
|
||||
setPage(1);
|
||||
}}
|
||||
w={80}
|
||||
size='xs'
|
||||
variant='filled'
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={setPage}
|
||||
total={cachedPages}
|
||||
size='sm'
|
||||
withControls
|
||||
withEdges
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -21,3 +21,26 @@ export function zValidatePath(val: string | undefined, ctx: z.RefinementCtx) {
|
||||
export const zStringTrimmed = z.string().trim().min(1);
|
||||
|
||||
export const zQsBoolean = z.enum(['true', 'false']).transform((val) => val === 'true');
|
||||
|
||||
export const paginationQs = z.object({
|
||||
page: z.coerce.number(),
|
||||
perpage: z.coerce.number().default(15),
|
||||
filter: z.enum(['dashboard', 'none', 'all']).optional().default('none'),
|
||||
favorite: zQsBoolean.default(false).optional(),
|
||||
sortBy: z
|
||||
.enum([
|
||||
'id',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'deletesAt',
|
||||
'name',
|
||||
'originalName',
|
||||
'size',
|
||||
'type',
|
||||
'views',
|
||||
'favorite',
|
||||
])
|
||||
.optional()
|
||||
.default('createdAt'),
|
||||
order: z.enum(['asc', 'desc']).optional().default('desc'),
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ApiError } from '@/lib/api/errors';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { File, cleanFiles, fileSchema, fileSelect } from '@/lib/db/models/file';
|
||||
import { canInteract } from '@/lib/role';
|
||||
import { zQsBoolean } from '@/lib/validation';
|
||||
import { paginationQs, zQsBoolean } from '@/lib/validation';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import typedPlugin from '@/server/typedPlugin';
|
||||
import z from 'zod';
|
||||
@@ -29,27 +29,7 @@ export default typedPlugin(
|
||||
schema: {
|
||||
description:
|
||||
'List, filter, and search files for the authenticated user (or another user if permitted).',
|
||||
querystring: z.object({
|
||||
page: z.coerce.number(),
|
||||
perpage: z.coerce.number().default(15),
|
||||
filter: z.enum(['dashboard', 'none', 'all']).optional().default('none'),
|
||||
favorite: zQsBoolean.default(false).optional(),
|
||||
sortBy: z
|
||||
.enum([
|
||||
'id',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'deletesAt',
|
||||
'name',
|
||||
'originalName',
|
||||
'size',
|
||||
'type',
|
||||
'views',
|
||||
'favorite',
|
||||
])
|
||||
.optional()
|
||||
.default('createdAt'),
|
||||
order: z.enum(['asc', 'desc']).optional().default('desc'),
|
||||
querystring: paginationQs.extend({
|
||||
searchField: z.enum(['name', 'originalName', 'type', 'tags', 'id']).optional().default('name'),
|
||||
searchQuery: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
|
||||
Reference in New Issue
Block a user