mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-08 13:05:17 +00:00
refactor(dashboard): streamline search query handling and URL synchronization in project display
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
@@ -66,16 +66,12 @@ export const ShowProjects = () => {
|
|||||||
const { data, isLoading } = api.project.all.useQuery();
|
const { data, isLoading } = api.project.all.useQuery();
|
||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
const { mutateAsync } = api.project.remove.useMutation();
|
const { mutateAsync } = api.project.remove.useMutation();
|
||||||
const [searchQuery, setSearchQuery] = useState(() => {
|
|
||||||
// Initialize from URL query parameter on mount
|
const [searchQuery, setSearchQuery] = useState(
|
||||||
if (typeof window !== "undefined") {
|
router.isReady && typeof router.query.q === "string" ? router.query.q : "",
|
||||||
const urlQuery = router.query.q;
|
);
|
||||||
return typeof urlQuery === "string" ? urlQuery : "";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
const debouncedSearchQuery = useDebounce(searchQuery, 500);
|
const debouncedSearchQuery = useDebounce(searchQuery, 500);
|
||||||
const isUrlSync = useRef(false);
|
|
||||||
const [sortBy, setSortBy] = useState<string>(() => {
|
const [sortBy, setSortBy] = useState<string>(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
return localStorage.getItem("projectsSort") || "createdAt-desc";
|
return localStorage.getItem("projectsSort") || "createdAt-desc";
|
||||||
@@ -87,63 +83,33 @@ export const ShowProjects = () => {
|
|||||||
localStorage.setItem("projectsSort", sortBy);
|
localStorage.setItem("projectsSort", sortBy);
|
||||||
}, [sortBy]);
|
}, [sortBy]);
|
||||||
|
|
||||||
// Sync URL changes back to state (e.g., browser back/forward)
|
|
||||||
// NOTE: This effect MUST appear before the URL sync effect below
|
|
||||||
// because React executes effects in order. When the back button is pressed,
|
|
||||||
// we need to set isUrlSync.current = true BEFORE the URL sync effect runs.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!router.isReady) return;
|
if (!router.isReady) return;
|
||||||
|
const urlQuery = typeof router.query.q === "string" ? router.query.q : "";
|
||||||
const urlQuery = router.query.q;
|
if (urlQuery !== searchQuery) {
|
||||||
const urlSearchQuery = typeof urlQuery === "string" ? urlQuery : "";
|
setSearchQuery(urlQuery);
|
||||||
|
|
||||||
// Update local state if URL changed externally (e.g., browser navigation)
|
|
||||||
if (urlSearchQuery !== searchQuery) {
|
|
||||||
isUrlSync.current = true;
|
|
||||||
setSearchQuery(urlSearchQuery);
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [router.isReady, router.query.q]);
|
}, [router.isReady, router.query.q]);
|
||||||
|
|
||||||
// Sync search query to URL when debounced value changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!router.isReady) return;
|
if (!router.isReady) return;
|
||||||
|
const urlQuery = typeof router.query.q === "string" ? router.query.q : "";
|
||||||
|
if (debouncedSearchQuery === urlQuery) return;
|
||||||
|
|
||||||
// Skip if this change came from URL navigation (browser back/forward)
|
const newQuery = { ...router.query };
|
||||||
if (isUrlSync.current) {
|
if (debouncedSearchQuery) {
|
||||||
isUrlSync.current = false;
|
newQuery.q = debouncedSearchQuery;
|
||||||
return;
|
} else {
|
||||||
|
delete newQuery.q;
|
||||||
}
|
}
|
||||||
|
router.replace({ pathname: router.pathname, query: newQuery }, undefined, {
|
||||||
const currentQuery = router.query.q;
|
shallow: true,
|
||||||
const normalizedCurrentQuery =
|
});
|
||||||
typeof currentQuery === "string" ? currentQuery : "";
|
}, [debouncedSearchQuery]);
|
||||||
|
|
||||||
// Only update URL if the debounced value is different from current URL
|
|
||||||
if (debouncedSearchQuery !== normalizedCurrentQuery) {
|
|
||||||
const newQuery = { ...router.query };
|
|
||||||
|
|
||||||
if (debouncedSearchQuery) {
|
|
||||||
newQuery.q = debouncedSearchQuery;
|
|
||||||
} else {
|
|
||||||
delete newQuery.q;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push(
|
|
||||||
{
|
|
||||||
pathname: router.pathname,
|
|
||||||
query: newQuery,
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{ shallow: false },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [debouncedSearchQuery, router]);
|
|
||||||
|
|
||||||
const filteredProjects = useMemo(() => {
|
const filteredProjects = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
|
|
||||||
// First filter by search query
|
|
||||||
const filtered = data.filter(
|
const filtered = data.filter(
|
||||||
(project) =>
|
(project) =>
|
||||||
project.name
|
project.name
|
||||||
|
|||||||
Reference in New Issue
Block a user