feat(frontend): persist page filters in query params
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { CalendarDays, Check, ChevronDown, Clock3, DollarSign, MoreVertical, Pencil, Play, Plus, Search, Square, Tag as TagIcon, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -25,6 +26,11 @@ import { Input } from "../components/ui/input";
|
||||
import { SearchableSelect } from "../components/ui/SearchableSelect";
|
||||
import { useWorkspace } from "../context/WorkspaceContext";
|
||||
import { useTranslation } from "../hooks/useTranslation";
|
||||
import {
|
||||
readArrayParam,
|
||||
readStringParam,
|
||||
updateQueryParams,
|
||||
} from "../lib/queryParams";
|
||||
|
||||
type EntryModalMode = "manual" | "edit" | null;
|
||||
|
||||
@@ -2009,9 +2015,10 @@ function TimesheetSkeleton({ loadingLabel }: { loadingLabel: string }) {
|
||||
}
|
||||
|
||||
export default function Timesheet() {
|
||||
const { t, lang } = useTranslation();
|
||||
const { activeWorkspace } = useWorkspace();
|
||||
const isRtl = lang === "fa";
|
||||
const { t, lang } = useTranslation();
|
||||
const { activeWorkspace } = useWorkspace();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const isRtl = lang === "fa";
|
||||
const extendedTimesheet = (t.timesheet as {
|
||||
deleteTitle?: string;
|
||||
deleteConfirmMessage?: string;
|
||||
@@ -2047,10 +2054,19 @@ export default function Timesheet() {
|
||||
const [activeRunningEntry, setActiveRunningEntry] = useState<TimeEntry | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState("");
|
||||
const [filters, setFilters] = useState<TimeEntryFilters>(DEFAULT_ENTRY_FILTERS);
|
||||
const [hasMoreHistory, setHasMoreHistory] = useState(false);
|
||||
const searchQuery = readStringParam(searchParams, "search", "");
|
||||
const filters = useMemo<TimeEntryFilters>(
|
||||
() => ({
|
||||
projectId: readStringParam(searchParams, "project", DEFAULT_ENTRY_FILTERS.projectId),
|
||||
clientId: readStringParam(searchParams, "client", DEFAULT_ENTRY_FILTERS.clientId),
|
||||
tagIds: readArrayParam(searchParams, "tags"),
|
||||
startedAfter: readStringParam(searchParams, "from", DEFAULT_ENTRY_FILTERS.startedAfter),
|
||||
startedBefore: readStringParam(searchParams, "to", DEFAULT_ENTRY_FILTERS.startedBefore),
|
||||
}),
|
||||
[searchParams],
|
||||
);
|
||||
const [hasMoreHistory, setHasMoreHistory] = useState(false);
|
||||
const [nextOffset, setNextOffset] = useState<number | null>(0);
|
||||
const [limit] = useState(20);
|
||||
const [ticker, setTicker] = useState(Date.now());
|
||||
@@ -2134,9 +2150,6 @@ export default function Timesheet() {
|
||||
}, [activeWorkspace?.id, t.timesheet?.optionsError]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchQuery("");
|
||||
setDebouncedSearchQuery("");
|
||||
setFilters(DEFAULT_ENTRY_FILTERS);
|
||||
setGroupedHistory([]);
|
||||
setNextOffset(0);
|
||||
setHasMoreHistory(false);
|
||||
@@ -2163,10 +2176,14 @@ export default function Timesheet() {
|
||||
(project) => project.id === filters.projectId && project.client?.id === filters.clientId,
|
||||
);
|
||||
|
||||
if (!projectStillMatchesClient) {
|
||||
setFilters((current) => ({ ...current, projectId: "" }));
|
||||
}
|
||||
}, [filters.clientId, filters.projectId, projects]);
|
||||
if (!projectStillMatchesClient) {
|
||||
setSearchParams(
|
||||
(current) =>
|
||||
updateQueryParams(current, { project: "" }, { project: "" }),
|
||||
{ replace: true },
|
||||
);
|
||||
}
|
||||
}, [filters.clientId, filters.projectId, projects, setSearchParams]);
|
||||
|
||||
const loadHistory = useCallback(async ({ offset = 0, append = false }: { offset?: number; append?: boolean } = {}) => {
|
||||
if (!activeWorkspace?.id) return;
|
||||
@@ -2523,14 +2540,53 @@ export default function Timesheet() {
|
||||
}, []);
|
||||
|
||||
const handleApplyFilters = useCallback((nextFilters: TimeEntryFilters) => {
|
||||
setFilters(nextFilters);
|
||||
}, []);
|
||||
setSearchParams(
|
||||
(current) =>
|
||||
updateQueryParams(
|
||||
current,
|
||||
{
|
||||
project: nextFilters.projectId,
|
||||
client: nextFilters.clientId,
|
||||
tags: nextFilters.tagIds,
|
||||
from: nextFilters.startedAfter,
|
||||
to: nextFilters.startedBefore,
|
||||
},
|
||||
{
|
||||
project: "",
|
||||
client: "",
|
||||
from: "",
|
||||
to: "",
|
||||
},
|
||||
),
|
||||
{ replace: true },
|
||||
);
|
||||
}, [setSearchParams]);
|
||||
|
||||
const handleClearFilters = useCallback(() => {
|
||||
setSearchQuery("");
|
||||
setSearchParams(
|
||||
(current) =>
|
||||
updateQueryParams(
|
||||
current,
|
||||
{
|
||||
search: "",
|
||||
project: "",
|
||||
client: "",
|
||||
tags: [],
|
||||
from: "",
|
||||
to: "",
|
||||
},
|
||||
{
|
||||
search: "",
|
||||
project: "",
|
||||
client: "",
|
||||
from: "",
|
||||
to: "",
|
||||
},
|
||||
),
|
||||
{ replace: true },
|
||||
);
|
||||
setDebouncedSearchQuery("");
|
||||
setFilters(DEFAULT_ENTRY_FILTERS);
|
||||
}, []);
|
||||
}, [setSearchParams]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
if (!hasMoreHistory || nextOffset === null || isLoadingMore) return;
|
||||
@@ -2789,7 +2845,13 @@ export default function Timesheet() {
|
||||
<TimesheetFilterBar
|
||||
searchQuery={searchQuery}
|
||||
filters={filters}
|
||||
onSearchChange={setSearchQuery}
|
||||
onSearchChange={(value) =>
|
||||
setSearchParams(
|
||||
(current) =>
|
||||
updateQueryParams(current, { search: value }, { search: "" }),
|
||||
{ replace: true },
|
||||
)
|
||||
}
|
||||
onApply={handleApplyFilters}
|
||||
onClearFilters={handleClearFilters}
|
||||
projects={projects}
|
||||
|
||||
Reference in New Issue
Block a user