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",
|
||||
"ms": "^2.1.3",
|
||||
"multer": "2.1.1",
|
||||
"nuqs": "^2.8.9",
|
||||
"otplib": "^13.4.0",
|
||||
"prisma": "6.13.0",
|
||||
"qrcode": "^1.5.4",
|
||||
|
||||
Generated
+37
@@ -176,6 +176,9 @@ importers:
|
||||
multer:
|
||||
specifier: 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:
|
||||
specifier: ^13.4.0
|
||||
version: 13.4.0
|
||||
@@ -1735,6 +1738,9 @@ packages:
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
|
||||
'@standard-schema/spec@1.0.0':
|
||||
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
@@ -3499,6 +3505,27 @@ packages:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==}
|
||||
engines: {node: ^14.16.0 || >=16.10.0}
|
||||
@@ -6361,6 +6388,8 @@ snapshots:
|
||||
dependencies:
|
||||
solid-js: 1.9.12
|
||||
|
||||
'@standard-schema/spec@1.0.0': {}
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@tabler/icons-react@3.44.0(react@19.2.6)':
|
||||
@@ -8265,6 +8294,14 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
citty: 0.1.6
|
||||
|
||||
+5
-1
@@ -6,6 +6,7 @@ import ThemeProvider from '@/components/ThemeProvider';
|
||||
import { type ZiplineTheme } from '@/lib/theme';
|
||||
import { type Config } from '@/lib/config/validate';
|
||||
import { Button, Text } from '@mantine/core';
|
||||
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7';
|
||||
|
||||
const AlertModal = ({ context, id, innerProps }: ContextModalProps<{ modalBody: string }>) => (
|
||||
<>
|
||||
@@ -61,7 +62,10 @@ export default function Root({
|
||||
modals={contextModals}
|
||||
>
|
||||
<Notifications position='top-center' zIndex={10000000} />
|
||||
<Outlet />
|
||||
|
||||
<NuqsAdapter>
|
||||
<Outlet />
|
||||
</NuqsAdapter>
|
||||
</ModalsProvider>
|
||||
</ThemeProvider>
|
||||
</SWRConfig>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useApiPagination } from '@/components/pages/files/useApiPagination';
|
||||
import { type Response } from '@/lib/api/response';
|
||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
||||
import { useTitle } from '@/lib/client/hooks/useTitle';
|
||||
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
||||
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 { Link, Params, useLoaderData, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useQueryState, parseAsInteger } from 'nuqs';
|
||||
|
||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||
const DashboardFileModal = lazy(() => import('@/components/file/DashboardFile/DashboardFileModal'));
|
||||
@@ -78,8 +78,8 @@ export function Component() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const [page, setPage] = useQueryState('page', 1);
|
||||
const [perpage] = useQueryState('perpage', 15);
|
||||
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
||||
const [perpage] = useQueryState('perpage', parseAsInteger.withDefault(15));
|
||||
|
||||
const { data, isLoading } = useApiPagination<Response['/api/server/folder/[id]']>(
|
||||
{
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { IncompleteFile } from '@/lib/db/models/incompleteFile';
|
||||
import { fetchApi } from '@/lib/fetchApi';
|
||||
import { UpdateFn } from '@/lib/client/hooks/useObjectState';
|
||||
import { IncompleteFileStatus } from '@/prisma/client';
|
||||
import { Badge, Button, Card, Group, Modal, Paper, Stack, Text } from '@mantine/core';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconFileDots, IconTrashFilled } from '@tabler/icons-react';
|
||||
import { ReactNode } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { DashboardFilesModals } from '.';
|
||||
import { DashboardFilesModals, DashboardFilesModalsUpdate } from '.';
|
||||
|
||||
const badgeMap: Record<IncompleteFileStatus, ReactNode> = {
|
||||
PENDING: (
|
||||
@@ -38,7 +37,7 @@ export default function PendingFilesModal({
|
||||
setModals,
|
||||
}: {
|
||||
modals: DashboardFilesModals;
|
||||
setModals: UpdateFn<DashboardFilesModals>;
|
||||
setModals: DashboardFilesModalsUpdate;
|
||||
}) {
|
||||
const { data: incompleteFiles, mutate } = useSWR<
|
||||
Extract<IncompleteFile[], Response['/api/user/files/incomplete']>
|
||||
@@ -72,7 +71,7 @@ export default function PendingFilesModal({
|
||||
};
|
||||
|
||||
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'>
|
||||
{incompleteFiles?.map((incompleteFile) => (
|
||||
<Card key={incompleteFile.id} withBorder>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
||||
import useObjectState, { type UpdateFn } from '@/lib/client/hooks/useObjectState';
|
||||
import { useViewStore } from '@/lib/client/store/view';
|
||||
import { ActionIcon, Group, Menu, Title, Tooltip } from '@mantine/core';
|
||||
import {
|
||||
@@ -10,7 +9,8 @@ import {
|
||||
IconTableOptions,
|
||||
IconTags,
|
||||
} 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 TagsModal from './tags/TagsModal';
|
||||
import FavoriteFiles from './views/FavoriteFiles';
|
||||
@@ -24,48 +24,21 @@ export type DashboardFilesModals = {
|
||||
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() {
|
||||
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, 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 });
|
||||
};
|
||||
const [modals, setModals] = useModals();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -92,12 +65,15 @@ export default function DashboardFiles() {
|
||||
</Tooltip>
|
||||
</Menu.Target>
|
||||
<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
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<IconFileDots size='1rem' />}
|
||||
onClick={() => setModals('pending', !modals.pending)}
|
||||
onClick={() => setModals({ pending: !modals.pending })}
|
||||
>
|
||||
View Pending Files
|
||||
</Menu.Item>
|
||||
@@ -106,13 +82,13 @@ export default function DashboardFiles() {
|
||||
<Menu.Label>Table Options</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<IconGridPatternFilled size='1rem' />}
|
||||
onClick={() => setModals('idSearch', !modals.idSearch)}
|
||||
onClick={() => setModals({ idSearch: !modals.idSearch })}
|
||||
>
|
||||
Search by ID
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<IconTableOptions size='1rem' />}
|
||||
onClick={() => setModals('table', !modals.table)}
|
||||
onClick={() => setModals({ table: !modals.table })}
|
||||
>
|
||||
Table Options
|
||||
</Menu.Item>
|
||||
|
||||
@@ -2,13 +2,12 @@ import { mutateFiles } from '@/components/file/actions';
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { Tag } from '@/lib/db/models/tag';
|
||||
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 { showNotification } from '@mantine/notifications';
|
||||
import { IconPencil, IconPlus, IconTagOff, IconTrashFilled } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { DashboardFilesModals } from '..';
|
||||
import { DashboardFilesModals, DashboardFilesModalsUpdate } from '..';
|
||||
import CreateTagModal from './CreateTagModal';
|
||||
import EditTagModal from './EditTagModal';
|
||||
import TagPill from './TagPill';
|
||||
@@ -18,7 +17,7 @@ export default function TagsModals({
|
||||
setModals,
|
||||
}: {
|
||||
modals: DashboardFilesModals;
|
||||
setModals: UpdateFn<DashboardFilesModals>;
|
||||
setModals: DashboardFilesModalsUpdate;
|
||||
}) {
|
||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||
const [selectedTag, setSelectedTag] = useState<Tag | null>(null);
|
||||
@@ -55,7 +54,7 @@ export default function TagsModals({
|
||||
|
||||
<Modal
|
||||
opened={modals.tags}
|
||||
onClose={() => setModals('tags', false)}
|
||||
onClose={() => setModals({ tags: false })}
|
||||
title={
|
||||
<Group>
|
||||
<Title>Tags</Title>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
@@ -16,11 +15,12 @@ import { IconFileUpload, IconFilesOff } from '@tabler/icons-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useApiPagination } from '../useApiPagination';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||
|
||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||
|
||||
export default function FavoriteFiles() {
|
||||
const [page, setPage] = useQueryState('fpage', 1);
|
||||
const [page, setPage] = useQueryState('fpage', parseAsInteger.withDefault(1));
|
||||
|
||||
const { data, isLoading } = useApiPagination({
|
||||
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 {
|
||||
Button,
|
||||
@@ -14,20 +14,19 @@ import {
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
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 { useShallow } from 'zustand/shallow';
|
||||
|
||||
import DashboardFile from '@/components/file/DashboardFile';
|
||||
import { useApiPagination } from '../useApiPagination';
|
||||
|
||||
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 }) {
|
||||
const [page, setPage] = useQueryState('page', 1);
|
||||
const [perpage, setPerpage] = useState(15);
|
||||
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
||||
const [perpage, setPerpage] = useQueryState('perpage', parseAsInteger.withDefault(15));
|
||||
|
||||
const { data, isLoading } = useApiPagination({
|
||||
page,
|
||||
|
||||
@@ -4,7 +4,6 @@ import FolderComboboxOptions from '@/components/folders/FolderComboboxOptions';
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { bytes } from '@/lib/bytes';
|
||||
import { useFolders } from '@/lib/client/hooks/useFolders';
|
||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
||||
import { useFileNavStore } from '@/lib/client/store/fileNav';
|
||||
import { NAMES, useFileTableSettingsStore } from '@/lib/client/store/fileTableSettings';
|
||||
import { useSettingsStore } from '@/lib/client/store/settings';
|
||||
@@ -41,13 +40,12 @@ import {
|
||||
IconTrashFilled,
|
||||
} from '@tabler/icons-react';
|
||||
import { DataTable } from 'mantine-datatable';
|
||||
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||
import { lazy, useEffect, useMemo, useReducer, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
|
||||
import { UpdateFn } from '@/lib/client/hooks/useObjectState';
|
||||
import { DashboardFilesModals } from '..';
|
||||
import { DashboardFilesModals, DashboardFilesModalsUpdate } from '..';
|
||||
import TableEditModal from '../TableEditModal';
|
||||
import { bulkDelete, bulkFavorite } from '../bulk';
|
||||
import TagPill from '../tags/TagPill';
|
||||
@@ -60,7 +58,7 @@ type ReducerQuery = {
|
||||
action: { field: string; query: string };
|
||||
};
|
||||
|
||||
const PER_PAGE_OPTIONS = [10, 20, 50];
|
||||
const PER_PAGE_OPTIONS = [10, 20, 50, 70, 100];
|
||||
|
||||
function SearchFilter({
|
||||
setSearchField,
|
||||
@@ -189,7 +187,7 @@ export default function FileTable({
|
||||
id?: string;
|
||||
folderId?: string;
|
||||
modals?: Partial<DashboardFilesModals>;
|
||||
setModals?: UpdateFn<DashboardFilesModals>;
|
||||
setModals?: DashboardFilesModalsUpdate;
|
||||
}) {
|
||||
const clipboard = useClipboard();
|
||||
const warnDeletion = useSettingsStore((state) => state.settings.warnDeletion);
|
||||
@@ -203,8 +201,8 @@ export default function FileTable({
|
||||
return buildFolderHierarchy(folders);
|
||||
}, [folders]);
|
||||
|
||||
const [page, setPage] = useQueryState('page', 1);
|
||||
const [perpage, setPerpage] = useState(20);
|
||||
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
||||
const [perpage, setPerpage] = useQueryState('perpage', parseAsInteger.withDefault(20));
|
||||
const [sort, setSort] = useState<
|
||||
| 'id'
|
||||
| 'createdAt'
|
||||
@@ -394,7 +392,7 @@ export default function FileTable({
|
||||
/>
|
||||
|
||||
{modals && setModals && (
|
||||
<TableEditModal opened={!!modals.table} onClose={() => setModals('table', false)} />
|
||||
<TableEditModal opened={!!modals.table} onClose={() => setModals({ table: false })} />
|
||||
)}
|
||||
|
||||
<Box>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useQueryState } from '@/lib/client/hooks/useQueryState';
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
@@ -16,11 +15,12 @@ import { IconFileUpload, IconFilesOff } from '@tabler/icons-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useApiPagination } from '../files/useApiPagination';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||
|
||||
const DashboardFile = lazy(() => import('@/components/file/DashboardFile'));
|
||||
|
||||
export default function FavoriteFiles() {
|
||||
const [page, setPage] = useQueryState('fpage', 1);
|
||||
const [page, setPage] = useQueryState('fpage', parseAsInteger.withDefault(1));
|
||||
const { data, isLoading } = useApiPagination({
|
||||
page,
|
||||
favorite: true,
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { type loader } from '@/client/pages/dashboard/admin/users/[id]/files';
|
||||
import GridTableSwitcher from '@/components/GridTableSwitcher';
|
||||
import useObjectState from '@/lib/client/hooks/useObjectState';
|
||||
import { useViewStore } from '@/lib/client/store/view';
|
||||
import { ActionIcon, Group, Title, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBackUp, IconGridPatternFilled, IconTableOptions } from '@tabler/icons-react';
|
||||
import { Link, useLoaderData } from 'react-router-dom';
|
||||
import { DashboardFilesModals } from '../files';
|
||||
import FilesTableView from '../files/views/FilesTableView';
|
||||
import { useModals } from '../files';
|
||||
import FilesGridView from '../files/views/FilesGridView';
|
||||
import FilesTableView from '../files/views/FilesTableView';
|
||||
|
||||
export default function ViewUserFiles() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
const view = useViewStore((state) => state.files);
|
||||
const [modals, setModals] = useObjectState<Partial<DashboardFilesModals>>({
|
||||
table: false,
|
||||
idSearch: false,
|
||||
});
|
||||
const [modals, setModals] = useModals();
|
||||
|
||||
if (!data) return;
|
||||
|
||||
@@ -34,13 +30,13 @@ export default function ViewUserFiles() {
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label='Table Options'>
|
||||
<ActionIcon variant='outline' onClick={() => setModals('table', !modals.table)}>
|
||||
<ActionIcon variant='outline' onClick={() => setModals({ table: !modals.table })}>
|
||||
<IconTableOptions size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label='Search by ID'>
|
||||
<ActionIcon variant='outline' onClick={() => setModals('idSearch', !modals.idSearch)}>
|
||||
<ActionIcon variant='outline' onClick={() => setModals({ idSearch: !modals.idSearch })}>
|
||||
<IconGridPatternFilled size='1rem' />
|
||||
</ActionIcon>
|
||||
</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