feat(jobs): add dedicated workspace page
CI and Deploy / test (pull_request) Failing after 2m51s
CI and Deploy / deploy (pull_request) Has been skipped

Make /jobs/:id the canonical application workspace while preserving list state and compatibility links. Replace popup and expandable-row navigation with accessible whole-row routing and richer job details.
This commit is contained in:
cesnimda
2026-08-15 13:33:00 +02:00
parent 0dfaac18a1
commit 109745edb0
18 changed files with 310 additions and 266 deletions
+12 -5
View File
@@ -13,11 +13,10 @@ import AlarmIcon from "@mui/icons-material/Alarm";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import ShieldIcon from "@mui/icons-material/Shield";
import SearchIcon from "@mui/icons-material/Search";
import MailOutlineIcon from "@mui/icons-material/MailOutline";
import MemoryIcon from "@mui/icons-material/Memory";
import DescriptionOutlinedIcon from "@mui/icons-material/DescriptionOutlined";
import { Navigate, Route, Routes, useLocation, useNavigate, createBrowserRouter, RouterProvider } from "react-router-dom";
import { Navigate, Route, Routes, useLocation, useNavigate, useParams, createBrowserRouter, RouterProvider } from "react-router-dom";
import { ToastProvider } from "./toast";
import { ConfirmProvider } from "./confirm";
@@ -83,6 +82,7 @@ type MeResponse = {
function breadcrumbsFor(path: string, t: (k: any) => string): string[] {
if (path.startsWith("/dashboard")) return [t("home"), t("analytics"), t("overview")];
if (path.startsWith("/discover")) return [t("home"), "Discover jobs"];
if (/^\/jobs\/\d+/.test(path)) return [t("home"), t("jobApplications"), "Job details"];
if (path.startsWith("/jobs")) return [t("home"), t("jobApplications")];
if (path.startsWith("/reminders")) return [t("home"), t("reminders")];
if (path.startsWith("/operations")) return [t("home"), "Operations"];
@@ -107,6 +107,7 @@ function titleFor(path: string, t: (k: any) => string): string {
if (path.startsWith("/reminders")) return t("reminders");
if (path.startsWith("/operations")) return "Operations";
if (path.startsWith("/discover")) return "Discover jobs";
if (/^\/jobs\/\d+/.test(path)) return "Job details";
if (path.startsWith("/jobs")) return t("jobApplications");
if (path.startsWith("/kanban")) return t("kanbanBoard");
if (path.startsWith("/companies")) return t("companies");
@@ -127,6 +128,7 @@ function titleFor(path: string, t: (k: any) => string): string {
function subtitleFor(path: string, t: (k: any) => string): string | undefined {
if (path === "/dashboard") return t("dashboardPageSubtitle");
if (path.startsWith("/discover")) return "Search official job-board feeds and save opportunities to your tracker.";
if (/^\/jobs\/\d+/.test(path)) return "Manage this application, its documents, timeline, and correspondence.";
if (path.startsWith("/jobs")) return t("jobsPageSubtitle");
if (path.startsWith("/kanban")) return t("kanbanPageSubtitle");
if (path.startsWith("/reminders")) return t("remindersPageSubtitle");
@@ -139,6 +141,12 @@ function PageLoader() {
return <Box sx={{ p: 4 }}><Typography variant="h6">Loading...</Typography></Box>;
}
function LegacyApplicationRedirect() {
const { id } = useParams();
const location = useLocation();
return <Navigate to={`/jobs/${id ?? ""}${location.search}`} replace state={location.state} />;
}
function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMode, onThemeModeChange }: { jobPageSize: 15 | 20 | 25; setJobPageSize: (n: 15 | 20 | 25) => void; jobColumns: JobTableColumns; setJobColumns: (c: JobTableColumns) => void; themeMode: ThemeModePref; onThemeModeChange: (v: ThemeModePref) => void; }) {
const location = useLocation();
const navigate = useNavigate();
@@ -270,8 +278,6 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
{ to: "/reminders", label: t("reminders"), icon: <AlarmIcon fontSize="small" />, badgeCount: reminderCount, section: t("manage") },
{ to: "/kanban", label: t("kanbanBoard"), icon: <ViewKanbanIcon fontSize="small" />, section: t("manage") },
{ to: "/companies", label: t("companies"), icon: <BusinessIcon fontSize="small" />, section: t("manage") },
{ to: "/correspondence", label: "Correspondence", icon: <MailOutlineIcon fontSize="small" />, section: t("manage") },
{ to: "/correspondence/review", label: "Gmail review", icon: <MailOutlineIcon fontSize="small" />, section: t("manage") },
{ to: "/career", label: "Career Workspace", icon: <DescriptionOutlinedIcon fontSize="small" />, section: t("manage") },
{ to: "/career/builder", label: "CV Builder", icon: <DescriptionOutlinedIcon fontSize="small" />, section: t("manage") },
{ to: "/trash", label: t("trash"), icon: <DeleteOutlineIcon fontSize="small" />, section: t("manage") },
@@ -353,6 +359,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
<Route path="/dashboard" element={<DashboardView />} />
<Route path="/discover" element={<JobDiscoveryPage />} />
<Route path="/jobs" element={<JobTable refreshToken={refreshToken} pageSize={jobPageSize} onPageSizeChange={setAndPersistPageSize} columns={jobColumns} onColumnsChange={setAndPersistColumns} mode="jobs" />} />
<Route path="/jobs/:id" element={<ApplicationWorkspacePage />} />
<Route path="/reminders" element={<RemindersView />} />
<Route path="/operations" element={<OperationsPage />} />
<Route path="/kanban" element={<KanbanBoard />} />
@@ -360,7 +367,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
<Route path="/correspondence" element={<CorrespondenceInboxPage />} />
<Route path="/correspondence/review" element={<Navigate to="/correspondence?view=review" replace />} />
<Route path="/profile" element={<ProfilePage />} />
<Route path="/applications/:id" element={<ApplicationWorkspacePage />} />
<Route path="/applications/:id" element={<LegacyApplicationRedirect />} />
<Route path="/career" element={<CareerWorkspacePage />} />
<Route path="/career/builder" element={<CvBuilderPage />} />
<Route path="/career/builder/:id" element={<CvBuilderEditor />} />