url change for tab change

This commit is contained in:
DaKheera47 2026-01-20 11:22:48 +00:00
parent 6de6d87263
commit 0eb0d3205d
5 changed files with 85 additions and 10 deletions

View File

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

View File

@ -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"
)}

View File

@ -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();

View File

@ -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"
)}

View File

@ -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"
}`}