feat(frontend): persist page filters in query params
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Plus, Trash2, Pencil, Eye } from 'lucide-react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Plus, Trash2, Pencil, Eye, LayoutDashboard } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { fetchWorkspaces, deleteWorkspace, type Workspace } from '../api/workspaces';
|
||||
import { useTranslation } from '../hooks/useTranslation';
|
||||
@@ -17,6 +17,7 @@ import { Input } from '../components/ui/input';
|
||||
import { Card, CardContent, CardTitle } from '../components/ui/card';
|
||||
import { Pagination } from '../components/Pagination';
|
||||
import { Modal } from '../components/Modal';
|
||||
import { readNumberParam, readStringParam, updateQueryParams } from '../lib/queryParams';
|
||||
|
||||
const RoleBadge = ({ role }: { role?: WorkspaceRole }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -39,18 +40,18 @@ const RoleBadge = ({ role }: { role?: WorkspaceRole }) => {
|
||||
export default function Workspaces() {
|
||||
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [ordering, setOrdering] = useState('-created_at');
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalItems, setTotalItems] = useState(0);
|
||||
const [limit, setLimit] = useState(10);
|
||||
|
||||
const [deleteModal, setDeleteModal] = useState<{isOpen: boolean; workspace: Workspace | null}>({isOpen: false, workspace: null});
|
||||
const [deleteInput, setDeleteInput] = useState('');
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { t } = useTranslation();
|
||||
const searchQuery = readStringParam(searchParams, 'search', '');
|
||||
const ordering = readStringParam(searchParams, 'ordering', '-created_at');
|
||||
const currentPage = Math.max(1, readNumberParam(searchParams, 'page', 1));
|
||||
const limit = Math.max(1, readNumberParam(searchParams, 'limit', 10));
|
||||
|
||||
const orderingOptions = [
|
||||
{ value: '-created_at', label: t.ordering?.createdAtDesc || 'Newest First' },
|
||||
@@ -60,10 +61,6 @@ export default function Workspaces() {
|
||||
{ value: '-updated_at', label: t.ordering?.updatedAtDesc || 'Recently Updated' },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [searchQuery, ordering]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
loadWorkspaces();
|
||||
@@ -116,6 +113,19 @@ export default function Workspaces() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateListParams = (updates: Record<string, string | number | null | undefined>) => {
|
||||
setSearchParams(
|
||||
(current) =>
|
||||
updateQueryParams(current, updates, {
|
||||
search: '',
|
||||
ordering: '-created_at',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
}),
|
||||
{ replace: true },
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex min-h-full max-w-7xl flex-col p-4 md:p-6">
|
||||
<div className="flex flex-1 flex-col gap-5">
|
||||
@@ -139,9 +149,9 @@ export default function Workspaces() {
|
||||
<div className="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900 sm:p-5">
|
||||
<FilterBar
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
setSearchQuery={(value) => updateListParams({ search: value, page: 1 })}
|
||||
ordering={ordering}
|
||||
setOrdering={setOrdering}
|
||||
setOrdering={(value) => updateListParams({ ordering: value, page: 1 })}
|
||||
orderingOptions={orderingOptions}
|
||||
searchPlaceholder={t.workspace?.searchPlaceholder || 'Search...'}
|
||||
/>
|
||||
@@ -220,10 +230,12 @@ export default function Workspaces() {
|
||||
})}
|
||||
|
||||
{workspaces.length === 0 && (
|
||||
<div className="flex flex-1 rounded-3xl border-2 border-dashed border-slate-200 bg-white py-16 shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<p className="text-slate-500 dark:text-slate-400 font-medium">{t.workspace?.emptyState || 'No workspaces found'}</p>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 rounded-3xl border-2 border-dashed border-slate-200 bg-white p-12 text-center shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
||||
<LayoutDashboard className="mx-auto mb-3 h-12 w-12 text-slate-300 dark:text-slate-700" />
|
||||
<h3 className="text-lg font-medium text-slate-900 dark:text-white">{t.workspace.noWorkspace}</h3>
|
||||
<p className="mt-1 text-slate-500 dark:text-slate-400">
|
||||
{searchQuery ? t.workspace.noWorkspaceSearch : t.workspace?.emptyState}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -232,8 +244,8 @@ export default function Workspaces() {
|
||||
currentPage={currentPage}
|
||||
totalCount={totalItems}
|
||||
limit={limit}
|
||||
onPageChange={setCurrentPage}
|
||||
onLimitChange={setLimit}
|
||||
onPageChange={(page) => updateListParams({ page })}
|
||||
onLimitChange={(pageLimit) => updateListParams({ limit: pageLimit, page: 1 })}
|
||||
pageSizeOptions={[10, 20, 50]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user