mirror of
https://github.com/diced/zipline.git
synced 2026-06-12 10:51:17 -07:00
refactor: nuqs
This commit is contained in:
@@ -78,6 +78,7 @@
|
|||||||
"marked-react": "^4.0.0",
|
"marked-react": "^4.0.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"multer": "2.1.1",
|
"multer": "2.1.1",
|
||||||
|
"nuqs": "^2.8.9",
|
||||||
"otplib": "^13.4.0",
|
"otplib": "^13.4.0",
|
||||||
"prisma": "6.13.0",
|
"prisma": "6.13.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
|
|||||||
Generated
+37
@@ -176,6 +176,9 @@ importers:
|
|||||||
multer:
|
multer:
|
||||||
specifier: 2.1.1
|
specifier: 2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
nuqs:
|
||||||
|
specifier: ^2.8.9
|
||||||
|
version: 2.8.9(react-router-dom@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)
|
||||||
otplib:
|
otplib:
|
||||||
specifier: ^13.4.0
|
specifier: ^13.4.0
|
||||||
version: 13.4.0
|
version: 13.4.0
|
||||||
@@ -1735,6 +1738,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
solid-js: ^1.6.12
|
solid-js: ^1.6.12
|
||||||
|
|
||||||
|
'@standard-schema/spec@1.0.0':
|
||||||
|
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0':
|
'@standard-schema/spec@1.1.0':
|
||||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
@@ -3499,6 +3505,27 @@ packages:
|
|||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
nuqs@2.8.9:
|
||||||
|
resolution: {integrity: sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@remix-run/react': '>=2'
|
||||||
|
'@tanstack/react-router': ^1
|
||||||
|
next: '>=14.2.0'
|
||||||
|
react: '>=18.2.0 || ^19.0.0-0'
|
||||||
|
react-router: ^5 || ^6 || ^7
|
||||||
|
react-router-dom: ^5 || ^6 || ^7
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@remix-run/react':
|
||||||
|
optional: true
|
||||||
|
'@tanstack/react-router':
|
||||||
|
optional: true
|
||||||
|
next:
|
||||||
|
optional: true
|
||||||
|
react-router:
|
||||||
|
optional: true
|
||||||
|
react-router-dom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
nypm@0.6.1:
|
nypm@0.6.1:
|
||||||
resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==}
|
resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==}
|
||||||
engines: {node: ^14.16.0 || >=16.10.0}
|
engines: {node: ^14.16.0 || >=16.10.0}
|
||||||
@@ -6361,6 +6388,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
solid-js: 1.9.12
|
solid-js: 1.9.12
|
||||||
|
|
||||||
|
'@standard-schema/spec@1.0.0': {}
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0': {}
|
'@standard-schema/spec@1.1.0': {}
|
||||||
|
|
||||||
'@tabler/icons-react@3.44.0(react@19.2.6)':
|
'@tabler/icons-react@3.44.0(react@19.2.6)':
|
||||||
@@ -8265,6 +8294,14 @@ snapshots:
|
|||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
|
|
||||||
|
nuqs@2.8.9(react-router-dom@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6):
|
||||||
|
dependencies:
|
||||||
|
'@standard-schema/spec': 1.0.0
|
||||||
|
react: 19.2.6
|
||||||
|
optionalDependencies:
|
||||||
|
react-router: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||||
|
react-router-dom: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
|
||||||
|
|
||||||
nypm@0.6.1:
|
nypm@0.6.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
citty: 0.1.6
|
citty: 0.1.6
|
||||||
|
|||||||
+5
-1
@@ -6,6 +6,7 @@ import ThemeProvider from '@/components/ThemeProvider';
|
|||||||
import { type ZiplineTheme } from '@/lib/theme';
|
import { type ZiplineTheme } from '@/lib/theme';
|
||||||
import { type Config } from '@/lib/config/validate';
|
import { type Config } from '@/lib/config/validate';
|
||||||
import { Button, Text } from '@mantine/core';
|
import { Button, Text } from '@mantine/core';
|
||||||
|
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7';
|
||||||
|
|
||||||
const AlertModal = ({ context, id, innerProps }: ContextModalProps<{ modalBody: string }>) => (
|
const AlertModal = ({ context, id, innerProps }: ContextModalProps<{ modalBody: string }>) => (
|
||||||
<>
|
<>
|
||||||
@@ -61,7 +62,10 @@ export default function Root({
|
|||||||
modals={contextModals}
|
modals={contextModals}
|
||||||
>
|
>
|
||||||
<Notifications position='top-center' zIndex={10000000} />
|
<Notifications position='top-center' zIndex={10000000} />
|
||||||
<Outlet />
|
|
||||||
|
<NuqsAdapter>
|
||||||
|
<Outlet />
|
||||||
|
</NuqsAdapter>
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useApiPagination } from '@/components/pages/files/useApiPagination';
|
import { useApiPagination } from '@/components/pages/files/useApiPagination';
|
||||||
import { type Response } from '@/lib/api/response';
|
import { type Response } from '@/lib/api/response';
|
||||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
|
||||||
import { useTitle } from '@/lib/client/hooks/useTitle';
|
import { useTitle } from '@/lib/client/hooks/useTitle';
|
||||||
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
||||||
import { Folder } from '@/lib/db/models/folder';
|
import { Folder } from '@/lib/db/models/folder';
|
||||||
@@ -24,6 +23,7 @@ import { IconFolder, IconUpload } from '@tabler/icons-react';
|
|||||||
import { lazy, Suspense, useEffect, useMemo } from 'react';
|
import { lazy, Suspense, useEffect, useMemo } from 'react';
|
||||||
import { Link, Params, useLoaderData, useNavigate, useSearchParams } from 'react-router-dom';
|
import { Link, Params, useLoaderData, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
import { useQueryState, parseAsInteger } from 'nuqs';
|
||||||
|
|
||||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||||
const DashboardFileModal = lazy(() => import('@/components/file/DashboardFile/DashboardFileModal'));
|
const DashboardFileModal = lazy(() => import('@/components/file/DashboardFile/DashboardFileModal'));
|
||||||
@@ -78,8 +78,8 @@ export function Component() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [, setSearchParams] = useSearchParams();
|
const [, setSearchParams] = useSearchParams();
|
||||||
const [page, setPage] = useQueryState('page', 1);
|
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
||||||
const [perpage] = useQueryState('perpage', 15);
|
const [perpage] = useQueryState('perpage', parseAsInteger.withDefault(15));
|
||||||
|
|
||||||
const { data, isLoading } = useApiPagination<Response['/api/server/folder/[id]']>(
|
const { data, isLoading } = useApiPagination<Response['/api/server/folder/[id]']>(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Response } from '@/lib/api/response';
|
import { Response } from '@/lib/api/response';
|
||||||
import { IncompleteFile } from '@/lib/db/models/incompleteFile';
|
import { IncompleteFile } from '@/lib/db/models/incompleteFile';
|
||||||
import { fetchApi } from '@/lib/fetchApi';
|
import { fetchApi } from '@/lib/fetchApi';
|
||||||
import { UpdateFn } from '@/lib/client/hooks/useObjectState';
|
|
||||||
import { IncompleteFileStatus } from '@/prisma/client';
|
import { IncompleteFileStatus } from '@/prisma/client';
|
||||||
import { Badge, Button, Card, Group, Modal, Paper, Stack, Text } from '@mantine/core';
|
import { Badge, Button, Card, Group, Modal, Paper, Stack, Text } from '@mantine/core';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconFileDots, IconTrashFilled } from '@tabler/icons-react';
|
import { IconFileDots, IconTrashFilled } from '@tabler/icons-react';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { DashboardFilesModals } from '.';
|
import { DashboardFilesModals, DashboardFilesModalsUpdate } from '.';
|
||||||
|
|
||||||
const badgeMap: Record<IncompleteFileStatus, ReactNode> = {
|
const badgeMap: Record<IncompleteFileStatus, ReactNode> = {
|
||||||
PENDING: (
|
PENDING: (
|
||||||
@@ -38,7 +37,7 @@ export default function PendingFilesModal({
|
|||||||
setModals,
|
setModals,
|
||||||
}: {
|
}: {
|
||||||
modals: DashboardFilesModals;
|
modals: DashboardFilesModals;
|
||||||
setModals: UpdateFn<DashboardFilesModals>;
|
setModals: DashboardFilesModalsUpdate;
|
||||||
}) {
|
}) {
|
||||||
const { data: incompleteFiles, mutate } = useSWR<
|
const { data: incompleteFiles, mutate } = useSWR<
|
||||||
Extract<IncompleteFile[], Response['/api/user/files/incomplete']>
|
Extract<IncompleteFile[], Response['/api/user/files/incomplete']>
|
||||||
@@ -72,7 +71,7 @@ export default function PendingFilesModal({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal opened={modals.pending} onClose={() => setModals('pending', false)} title='Pending Files'>
|
<Modal opened={modals.pending} onClose={() => setModals({ pending: false })} title='Pending Files'>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
{incompleteFiles?.map((incompleteFile) => (
|
{incompleteFiles?.map((incompleteFile) => (
|
||||||
<Card key={incompleteFile.id} withBorder>
|
<Card key={incompleteFile.id} withBorder>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
||||||
import useObjectState, { type UpdateFn } from '@/lib/client/hooks/useObjectState';
|
|
||||||
import { useViewStore } from '@/lib/client/store/view';
|
import { useViewStore } from '@/lib/client/store/view';
|
||||||
import { ActionIcon, Group, Menu, Title, Tooltip } from '@mantine/core';
|
import { ActionIcon, Group, Menu, Title, Tooltip } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +9,8 @@ import {
|
|||||||
IconTableOptions,
|
IconTableOptions,
|
||||||
IconTags,
|
IconTags,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { parseAsBoolean, useQueryStates } from 'nuqs';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import PendingFilesModal from './PendingFilesModal';
|
import PendingFilesModal from './PendingFilesModal';
|
||||||
import TagsModal from './tags/TagsModal';
|
import TagsModal from './tags/TagsModal';
|
||||||
import FavoriteFiles from './views/FavoriteFiles';
|
import FavoriteFiles from './views/FavoriteFiles';
|
||||||
@@ -24,48 +24,21 @@ export type DashboardFilesModals = {
|
|||||||
pending: boolean;
|
pending: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function useModals() {
|
||||||
|
return useQueryStates({
|
||||||
|
table: parseAsBoolean.withDefault(false),
|
||||||
|
idSearch: parseAsBoolean.withDefault(false),
|
||||||
|
tags: parseAsBoolean.withDefault(false),
|
||||||
|
pending: parseAsBoolean.withDefault(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DashboardFilesModalsUpdate = ReturnType<typeof useModals>[1];
|
||||||
|
|
||||||
export default function DashboardFiles() {
|
export default function DashboardFiles() {
|
||||||
const view = useViewStore((state) => state.files);
|
const view = useViewStore((state) => state.files);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const modalKeys: Array<keyof DashboardFilesModals> = ['table', 'idSearch', 'tags', 'pending'];
|
|
||||||
|
|
||||||
const modalQS = (key: keyof DashboardFilesModals) => searchParams.get(key) === 'true';
|
const [modals, setModals] = useModals();
|
||||||
|
|
||||||
const [modals, setModalState] = useObjectState<DashboardFilesModals>({
|
|
||||||
table: modalQS('table'),
|
|
||||||
idSearch: modalQS('idSearch'),
|
|
||||||
tags: modalQS('tags'),
|
|
||||||
pending: modalQS('pending'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateModalQuery = (updates: Partial<DashboardFilesModals>) => {
|
|
||||||
setSearchParams(
|
|
||||||
(prev) => {
|
|
||||||
const next = new URLSearchParams(prev);
|
|
||||||
|
|
||||||
for (const key of modalKeys) {
|
|
||||||
if (!(key in updates)) continue;
|
|
||||||
|
|
||||||
if (updates[key]) next.set(key, 'true');
|
|
||||||
else next.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next;
|
|
||||||
},
|
|
||||||
{ replace: true },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setModals: UpdateFn<DashboardFilesModals> = (keyOrObj: any, value?: any) => {
|
|
||||||
if (typeof keyOrObj === 'object' && value === undefined) {
|
|
||||||
setModalState(keyOrObj);
|
|
||||||
updateModalQuery(keyOrObj);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setModalState(keyOrObj, value);
|
|
||||||
updateModalQuery({ [keyOrObj]: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -92,12 +65,15 @@ export default function DashboardFiles() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<Menu.Item leftSection={<IconTags size='1rem' />} onClick={() => setModals('tags', !modals.tags)}>
|
<Menu.Item
|
||||||
|
leftSection={<IconTags size='1rem' />}
|
||||||
|
onClick={() => setModals({ tags: !modals.tags })}
|
||||||
|
>
|
||||||
Manage Tags
|
Manage Tags
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<IconFileDots size='1rem' />}
|
leftSection={<IconFileDots size='1rem' />}
|
||||||
onClick={() => setModals('pending', !modals.pending)}
|
onClick={() => setModals({ pending: !modals.pending })}
|
||||||
>
|
>
|
||||||
View Pending Files
|
View Pending Files
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
@@ -106,13 +82,13 @@ export default function DashboardFiles() {
|
|||||||
<Menu.Label>Table Options</Menu.Label>
|
<Menu.Label>Table Options</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<IconGridPatternFilled size='1rem' />}
|
leftSection={<IconGridPatternFilled size='1rem' />}
|
||||||
onClick={() => setModals('idSearch', !modals.idSearch)}
|
onClick={() => setModals({ idSearch: !modals.idSearch })}
|
||||||
>
|
>
|
||||||
Search by ID
|
Search by ID
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<IconTableOptions size='1rem' />}
|
leftSection={<IconTableOptions size='1rem' />}
|
||||||
onClick={() => setModals('table', !modals.table)}
|
onClick={() => setModals({ table: !modals.table })}
|
||||||
>
|
>
|
||||||
Table Options
|
Table Options
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import { mutateFiles } from '@/components/file/actions';
|
|||||||
import { Response } from '@/lib/api/response';
|
import { Response } from '@/lib/api/response';
|
||||||
import { Tag } from '@/lib/db/models/tag';
|
import { Tag } from '@/lib/db/models/tag';
|
||||||
import { fetchApi } from '@/lib/fetchApi';
|
import { fetchApi } from '@/lib/fetchApi';
|
||||||
import { UpdateFn } from '@/lib/client/hooks/useObjectState';
|
|
||||||
import { ActionIcon, Group, Modal, Paper, Stack, Text, Title, Tooltip } from '@mantine/core';
|
import { ActionIcon, Group, Modal, Paper, Stack, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconPencil, IconPlus, IconTagOff, IconTrashFilled } from '@tabler/icons-react';
|
import { IconPencil, IconPlus, IconTagOff, IconTrashFilled } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { DashboardFilesModals } from '..';
|
import { DashboardFilesModals, DashboardFilesModalsUpdate } from '..';
|
||||||
import CreateTagModal from './CreateTagModal';
|
import CreateTagModal from './CreateTagModal';
|
||||||
import EditTagModal from './EditTagModal';
|
import EditTagModal from './EditTagModal';
|
||||||
import TagPill from './TagPill';
|
import TagPill from './TagPill';
|
||||||
@@ -18,7 +17,7 @@ export default function TagsModals({
|
|||||||
setModals,
|
setModals,
|
||||||
}: {
|
}: {
|
||||||
modals: DashboardFilesModals;
|
modals: DashboardFilesModals;
|
||||||
setModals: UpdateFn<DashboardFilesModals>;
|
setModals: DashboardFilesModalsUpdate;
|
||||||
}) {
|
}) {
|
||||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||||
const [selectedTag, setSelectedTag] = useState<Tag | null>(null);
|
const [selectedTag, setSelectedTag] = useState<Tag | null>(null);
|
||||||
@@ -55,7 +54,7 @@ export default function TagsModals({
|
|||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
opened={modals.tags}
|
opened={modals.tags}
|
||||||
onClose={() => setModals('tags', false)}
|
onClose={() => setModals({ tags: false })}
|
||||||
title={
|
title={
|
||||||
<Group>
|
<Group>
|
||||||
<Title>Tags</Title>
|
<Title>Tags</Title>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
Button,
|
Button,
|
||||||
@@ -16,11 +15,12 @@ import { IconFileUpload, IconFilesOff } from '@tabler/icons-react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useApiPagination } from '../useApiPagination';
|
import { useApiPagination } from '../useApiPagination';
|
||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
|
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||||
|
|
||||||
export default function FavoriteFiles() {
|
export default function FavoriteFiles() {
|
||||||
const [page, setPage] = useQueryState('fpage', 1);
|
const [page, setPage] = useQueryState('fpage', parseAsInteger.withDefault(1));
|
||||||
|
|
||||||
const { data, isLoading } = useApiPagination({
|
const { data, isLoading } = useApiPagination({
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
import DashboardFile from '@/components/file/DashboardFile';
|
||||||
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -14,20 +14,19 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconFilesOff, IconFileUpload } from '@tabler/icons-react';
|
import { IconFilesOff, IconFileUpload } from '@tabler/icons-react';
|
||||||
import { lazy, Suspense, useEffect, useMemo, useState } from 'react';
|
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||||
|
import { lazy, Suspense, useEffect, useMemo } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
|
||||||
import DashboardFile from '@/components/file/DashboardFile';
|
|
||||||
import { useApiPagination } from '../useApiPagination';
|
import { useApiPagination } from '../useApiPagination';
|
||||||
|
|
||||||
const DashboardFileModal = lazy(() => import('@/components/file/DashboardFile/DashboardFileModal'));
|
const DashboardFileModal = lazy(() => import('@/components/file/DashboardFile/DashboardFileModal'));
|
||||||
|
|
||||||
const PER_PAGE_OPTIONS = [9, 12, 15, 30, 45];
|
const PER_PAGE_OPTIONS = [9, 12, 15, 30, 45, 60];
|
||||||
|
|
||||||
export default function Files({ id, folderId }: { id?: string; folderId?: string }) {
|
export default function Files({ id, folderId }: { id?: string; folderId?: string }) {
|
||||||
const [page, setPage] = useQueryState('page', 1);
|
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
||||||
const [perpage, setPerpage] = useState(15);
|
const [perpage, setPerpage] = useQueryState('perpage', parseAsInteger.withDefault(15));
|
||||||
|
|
||||||
const { data, isLoading } = useApiPagination({
|
const { data, isLoading } = useApiPagination({
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import FolderComboboxOptions from '@/components/folders/FolderComboboxOptions';
|
|||||||
import { Response } from '@/lib/api/response';
|
import { Response } from '@/lib/api/response';
|
||||||
import { bytes } from '@/lib/bytes';
|
import { bytes } from '@/lib/bytes';
|
||||||
import { useFolders } from '@/lib/client/hooks/useFolders';
|
import { useFolders } from '@/lib/client/hooks/useFolders';
|
||||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
|
||||||
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
||||||
import { NAMES, useFileTableSettingsStore } from '@/lib/client/store/fileTableSettings';
|
import { NAMES, useFileTableSettingsStore } from '@/lib/client/store/fileTableSettings';
|
||||||
import { useSettingsStore } from '@/lib/client/store/settings';
|
import { useSettingsStore } from '@/lib/client/store/settings';
|
||||||
@@ -41,13 +40,12 @@ import {
|
|||||||
IconTrashFilled,
|
IconTrashFilled,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { DataTable } from 'mantine-datatable';
|
import { DataTable } from 'mantine-datatable';
|
||||||
|
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||||
import { lazy, useEffect, useMemo, useReducer, useState } from 'react';
|
import { lazy, useEffect, useMemo, useReducer, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
import { DashboardFilesModals, DashboardFilesModalsUpdate } from '..';
|
||||||
import { UpdateFn } from '@/lib/client/hooks/useObjectState';
|
|
||||||
import { DashboardFilesModals } from '..';
|
|
||||||
import TableEditModal from '../TableEditModal';
|
import TableEditModal from '../TableEditModal';
|
||||||
import { bulkDelete, bulkFavorite } from '../bulk';
|
import { bulkDelete, bulkFavorite } from '../bulk';
|
||||||
import TagPill from '../tags/TagPill';
|
import TagPill from '../tags/TagPill';
|
||||||
@@ -60,7 +58,7 @@ type ReducerQuery = {
|
|||||||
action: { field: string; query: string };
|
action: { field: string; query: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
const PER_PAGE_OPTIONS = [10, 20, 50];
|
const PER_PAGE_OPTIONS = [10, 20, 50, 70, 100];
|
||||||
|
|
||||||
function SearchFilter({
|
function SearchFilter({
|
||||||
setSearchField,
|
setSearchField,
|
||||||
@@ -189,7 +187,7 @@ export default function FileTable({
|
|||||||
id?: string;
|
id?: string;
|
||||||
folderId?: string;
|
folderId?: string;
|
||||||
modals?: Partial<DashboardFilesModals>;
|
modals?: Partial<DashboardFilesModals>;
|
||||||
setModals?: UpdateFn<DashboardFilesModals>;
|
setModals?: DashboardFilesModalsUpdate;
|
||||||
}) {
|
}) {
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
|
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
|
||||||
@@ -203,8 +201,8 @@ export default function FileTable({
|
|||||||
return buildFolderHierarchy(folders);
|
return buildFolderHierarchy(folders);
|
||||||
}, [folders]);
|
}, [folders]);
|
||||||
|
|
||||||
const [page, setPage] = useQueryState('page', 1);
|
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
||||||
const [perpage, setPerpage] = useState(20);
|
const [perpage, setPerpage] = useQueryState('perpage', parseAsInteger.withDefault(20));
|
||||||
const [sort, setSort] = useState<
|
const [sort, setSort] = useState<
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'createdAt'
|
| 'createdAt'
|
||||||
@@ -394,7 +392,7 @@ export default function FileTable({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{modals && setModals && (
|
{modals && setModals && (
|
||||||
<TableEditModal opened={!!modals.table} onClose={() => setModals('table', false)} />
|
<TableEditModal opened={!!modals.table} onClose={() => setModals({ table: false })} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
Button,
|
Button,
|
||||||
@@ -16,11 +15,12 @@ import { IconFileUpload, IconFilesOff } from '@tabler/icons-react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useApiPagination } from '../files/useApiPagination';
|
import { useApiPagination } from '../files/useApiPagination';
|
||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
|
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||||
|
|
||||||
export default function FavoriteFiles() {
|
export default function FavoriteFiles() {
|
||||||
const [page, setPage] = useQueryState('fpage', 1);
|
const [page, setPage] = useQueryState('fpage', parseAsInteger.withDefault(1));
|
||||||
const { data, isLoading } = useApiPagination({
|
const { data, isLoading } = useApiPagination({
|
||||||
page,
|
page,
|
||||||
favorite: true,
|
favorite: true,
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import { type loader } from '@/client/pages/dashboard/admin/users/[id]/files';
|
import { type loader } from '@/client/pages/dashboard/admin/users/[id]/files';
|
||||||
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
||||||
import useObjectState from '@/lib/client/hooks/useObjectState';
|
|
||||||
import { useViewStore } from '@/lib/client/store/view';
|
import { useViewStore } from '@/lib/client/store/view';
|
||||||
import { ActionIcon, Group, Title, Tooltip } from '@mantine/core';
|
import { ActionIcon, Group, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBackUp, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
|
import { IconArrowBackUp, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
|
||||||
import { Link, useLoaderData } from 'react-router-dom';
|
import { Link, useLoaderData } from 'react-router-dom';
|
||||||
import { DashboardFilesModals } from '../files';
|
import { useModals } from '../files';
|
||||||
import FilesTableView from '../files/views/FilesTableView';
|
|
||||||
import FilesGridView from '../files/views/FilesGridView';
|
import FilesGridView from '../files/views/FilesGridView';
|
||||||
|
import FilesTableView from '../files/views/FilesTableView';
|
||||||
|
|
||||||
export default function ViewUserFiles() {
|
export default function ViewUserFiles() {
|
||||||
const data = useLoaderData<typeof loader>();
|
const data = useLoaderData<typeof loader>();
|
||||||
|
|
||||||
const view = useViewStore((state) => state.files);
|
const view = useViewStore((state) => state.files);
|
||||||
const [modals, setModals] = useObjectState<Partial<DashboardFilesModals>>({
|
const [modals, setModals] = useModals();
|
||||||
table: false,
|
|
||||||
idSearch: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
@@ -34,13 +30,13 @@ export default function ViewUserFiles() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip label='Table Options'>
|
<Tooltip label='Table Options'>
|
||||||
<ActionIcon variant='outline' onClick={() => setModals('table', !modals.table)}>
|
<ActionIcon variant='outline' onClick={() => setModals({ table: !modals.table })}>
|
||||||
<IconTableOptions size='1rem' />
|
<IconTableOptions size='1rem' />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip label='Search by ID'>
|
<Tooltip label='Search by ID'>
|
||||||
<ActionIcon variant='outline' onClick={() => setModals('idSearch', !modals.idSearch)}>
|
<ActionIcon variant='outline' onClick={() => setModals({ idSearch: !modals.idSearch })}>
|
||||||
<IconGridPatternFilled size='1rem' />
|
<IconGridPatternFilled size='1rem' />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
function parseValue<T>(value: string | null, defaultValue: T): T {
|
|
||||||
if (value === null) return defaultValue;
|
|
||||||
|
|
||||||
if (typeof defaultValue === 'number') {
|
|
||||||
const parsed = Number(value);
|
|
||||||
return isNaN(parsed) ? defaultValue : (parsed as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof defaultValue === 'boolean') {
|
|
||||||
return (value === 'true') as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useQueryState<T>(key: string, defaultValue: T): [T, (value: T | null) => void] {
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
const rawValue = searchParams.get(key);
|
|
||||||
const value: T = parseValue(rawValue, defaultValue);
|
|
||||||
|
|
||||||
const setValue = (newValue: T | null) => {
|
|
||||||
setSearchParams((prev) => {
|
|
||||||
const next = new URLSearchParams(prev);
|
|
||||||
if (newValue === null) {
|
|
||||||
next.delete(key);
|
|
||||||
} else {
|
|
||||||
next.set(key, String(newValue));
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return [value, setValue];
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user