feat(frontend): persist page filters in query params

This commit is contained in:
2026-04-29 11:31:12 +03:30
parent 06c05ba8e9
commit 06d083c818
12 changed files with 680 additions and 345 deletions

View File

@@ -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>