url change for tab change
This commit is contained in:
parent
6de6d87263
commit
0eb0d3205d
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { Route, Routes, useLocation } from "react-router-dom";
|
||||
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { CSSTransition, SwitchTransition } from "react-transition-group";
|
||||
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
@ -16,11 +16,20 @@ export const App: React.FC = () => {
|
||||
const location = useLocation();
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Determine a stable key for transitions to avoid unnecessary unmounts when switching sub-tabs
|
||||
const pageKey = React.useMemo(() => {
|
||||
const firstSegment = location.pathname.split("/")[1] || "ready";
|
||||
if (["ready", "discovered", "applied", "all"].includes(firstSegment)) {
|
||||
return "orchestrator";
|
||||
}
|
||||
return firstSegment;
|
||||
}, [location.pathname]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwitchTransition mode="out-in">
|
||||
<CSSTransition
|
||||
key={location.pathname}
|
||||
key={pageKey}
|
||||
nodeRef={nodeRef}
|
||||
timeout={100}
|
||||
classNames="page"
|
||||
@ -28,10 +37,11 @@ export const App: React.FC = () => {
|
||||
>
|
||||
<div ref={nodeRef}>
|
||||
<Routes location={location}>
|
||||
<Route path="/" element={<OrchestratorPage />} />
|
||||
<Route path="/" element={<Navigate to="/ready" replace />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/ukvisajobs" element={<UkVisaJobsPage />} />
|
||||
<Route path="/visa-sponsors" element={<VisaSponsorsPage />} />
|
||||
<Route path="/:tab" element={<OrchestratorPage />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
|
||||
@ -81,7 +81,7 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
onClick={() => handleNavClick(to)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground text-left",
|
||||
location.pathname === to
|
||||
location.pathname === to || (to === "/" && ["/ready", "/discovered", "/applied", "/all"].includes(location.pathname))
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useParams, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@ -24,18 +25,82 @@ import { usePipelineSources } from "./orchestrator/usePipelineSources";
|
||||
import { getJobCounts } from "./orchestrator/utils";
|
||||
|
||||
export const OrchestratorPage: React.FC = () => {
|
||||
const { tab } = useParams<{ tab: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const activeTab = useMemo(() => {
|
||||
const validTabs: FilterTab[] = ["ready", "discovered", "applied", "all"];
|
||||
if (tab && validTabs.includes(tab as FilterTab)) {
|
||||
return tab as FilterTab;
|
||||
}
|
||||
return "ready";
|
||||
}, [tab]);
|
||||
|
||||
// Sync searchQuery with URL
|
||||
const searchQuery = searchParams.get("q") || "";
|
||||
const setSearchQuery = (q: string) => {
|
||||
setSearchParams(
|
||||
(prev) => {
|
||||
if (q) prev.set("q", q);
|
||||
else prev.delete("q");
|
||||
return prev;
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
};
|
||||
|
||||
// Sync sourceFilter with URL
|
||||
const sourceFilter = (searchParams.get("source") as JobSource | "all") || "all";
|
||||
const setSourceFilter = (source: JobSource | "all") => {
|
||||
setSearchParams(
|
||||
(prev) => {
|
||||
if (source !== "all") prev.set("source", source);
|
||||
else prev.delete("source");
|
||||
return prev;
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
};
|
||||
|
||||
// Sync sort with URL
|
||||
const sort = useMemo((): JobSort => {
|
||||
const s = searchParams.get("sort");
|
||||
if (!s) return DEFAULT_SORT;
|
||||
const [key, direction] = s.split(":");
|
||||
return { key: key as any, direction: direction as any };
|
||||
}, [searchParams]);
|
||||
|
||||
const setSort = (newSort: JobSort) => {
|
||||
setSearchParams(
|
||||
(prev) => {
|
||||
prev.set("sort", `${newSort.key}:${newSort.direction}`);
|
||||
return prev;
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
};
|
||||
|
||||
// Effect to sync URL if it was invalid
|
||||
useEffect(() => {
|
||||
const validTabs: FilterTab[] = ["ready", "discovered", "applied", "all"];
|
||||
if (tab && !validTabs.includes(tab as FilterTab)) {
|
||||
navigate("/ready", { replace: true });
|
||||
}
|
||||
}, [tab, navigate]);
|
||||
|
||||
const [navOpen, setNavOpen] = useState(false);
|
||||
const [isManualImportOpen, setIsManualImportOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<FilterTab>("ready");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [sourceFilter, setSourceFilter] = useState<JobSource | "all">("all");
|
||||
const [sort, setSort] = useState<JobSort>(DEFAULT_SORT);
|
||||
const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
|
||||
const [isDetailDrawerOpen, setIsDetailDrawerOpen] = useState(false);
|
||||
const [isDesktop, setIsDesktop] = useState(
|
||||
() => (typeof window !== "undefined" ? window.matchMedia("(min-width: 1024px)").matches : false),
|
||||
);
|
||||
|
||||
const setActiveTab = (newTab: FilterTab) => {
|
||||
navigate(`/${newTab}`);
|
||||
};
|
||||
|
||||
const { pipelineSources, setPipelineSources, toggleSource } = usePipelineSources();
|
||||
const { jobs, stats, isLoading, isPipelineRunning, setIsPipelineRunning, loadJobs } = useOrchestratorData();
|
||||
|
||||
|
||||
@ -344,7 +344,7 @@ export const UkVisaJobsPage: React.FC = () => {
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground text-left",
|
||||
location.pathname === to
|
||||
location.pathname === to || (to === "/" && ["/ready", "/discovered", "/applied", "/all"].includes(location.pathname))
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
|
||||
@ -95,7 +95,7 @@ export const OrchestratorHeader: React.FC<OrchestratorHeaderProps> = ({
|
||||
setTimeout(() => navigate(to), 150);
|
||||
}}
|
||||
className={`flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground text-left ${
|
||||
location.pathname === to
|
||||
location.pathname === to || (to === "/" && ["/ready", "/discovered", "/applied", "/all"].includes(location.pathname))
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user