feat(jobs): embed route-backed workspace
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
||||
Checkbox,
|
||||
Chip,
|
||||
Collapse,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
@@ -56,6 +58,8 @@ import { useI18n } from "../i18n/I18nProvider";
|
||||
import { JobApplication } from "../types";
|
||||
import { useViewResource } from "../hooks/useViewResource";
|
||||
import { getWorkflowAction, needsInterviewPrep, needsWorkflowWork } from "../jobWorkflowSignals";
|
||||
import { ApplicationWorkspace } from "../views/ApplicationWorkspacePage";
|
||||
import { workspaceSection, WorkspaceSectionKey } from "../applicationWorkspace";
|
||||
|
||||
interface PagedResult<T> {
|
||||
items: T[];
|
||||
@@ -179,6 +183,37 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
const [statusJobId, setStatusJobId] = useState<number | null>(null);
|
||||
const [sortBy, setSortBy] = useState<"dateApplied" | "company" | "jobTitle" | "status" | "daysSince" | "location">("dateApplied");
|
||||
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
||||
const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
||||
const workspaceJobId = Number(searchParams.get("workspace")) || null;
|
||||
const workspaceSectionKey = workspaceSection(searchParams.get("section"));
|
||||
|
||||
const updateWorkspaceRoute = (jobId: number, section: WorkspaceSectionKey = "overview") => {
|
||||
const next = new URLSearchParams(location.search);
|
||||
next.set("workspace", String(jobId));
|
||||
if (section === "overview") next.delete("section");
|
||||
else next.set("section", section);
|
||||
navigate({ pathname: location.pathname, search: `?${next.toString()}` }, { state: { workspaceOverlay: true } });
|
||||
};
|
||||
|
||||
const updateWorkspaceSection = (section: WorkspaceSectionKey) => {
|
||||
if (!workspaceJobId) return;
|
||||
const next = new URLSearchParams(location.search);
|
||||
next.set("workspace", String(workspaceJobId));
|
||||
if (section === "overview") next.delete("section");
|
||||
else next.set("section", section);
|
||||
navigate({ pathname: location.pathname, search: `?${next.toString()}` }, { replace: true, state: location.state });
|
||||
};
|
||||
|
||||
const closeWorkspace = () => {
|
||||
if (location.state?.workspaceOverlay) {
|
||||
navigate(-1);
|
||||
return;
|
||||
}
|
||||
const next = new URLSearchParams(location.search);
|
||||
next.delete("workspace");
|
||||
next.delete("section");
|
||||
navigate({ pathname: location.pathname, search: next.toString() ? `?${next.toString()}` : "" }, { replace: true });
|
||||
};
|
||||
|
||||
const params = useMemo(() => ({
|
||||
page: page + 1,
|
||||
@@ -655,7 +690,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
<Button variant="outlined" startIcon={<MoreHorizIcon />} onClick={(e) => { setStatusJobId(job.id); setStatusAnchor(e.currentTarget); }} sx={{ minHeight: 42, fontWeight: 700 }}>
|
||||
{t("jobTableQuickStatus")}
|
||||
</Button>
|
||||
<Button variant="outlined" startIcon={<LaunchIcon />} onClick={() => setDetailsJobId(job.id)} sx={{ minHeight: 42, fontWeight: 700 }}>
|
||||
<Button variant="outlined" startIcon={<LaunchIcon />} onClick={() => updateWorkspaceRoute(job.id)} sx={{ minHeight: 42, fontWeight: 700 }}>
|
||||
{t("jobTableOpen")}
|
||||
</Button>
|
||||
{(mode === "trash" || (includeDeleted && job.isDeleted)) ? (
|
||||
@@ -744,7 +779,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", gap: 0.5 }}>
|
||||
<Tooltip title={t("jobTableEdit")}><IconButton size="small" onClick={() => setEditJobId(job.id)}><EditOutlinedIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title={t("jobTableQuickStatus")}><IconButton size="small" onClick={(e) => { setStatusJobId(job.id); setStatusAnchor(e.currentTarget); }}><MoreHorizIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title={t("jobTableOpen")}><IconButton size="small" onClick={() => setDetailsJobId(job.id)}><LaunchIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title={t("jobTableOpen")}><IconButton size="small" aria-label={`${t("jobTableOpen")}: ${job.jobTitle}`} onClick={() => updateWorkspaceRoute(job.id)}><LaunchIcon fontSize="small" /></IconButton></Tooltip>
|
||||
{(mode === "trash" || (includeDeleted && job.isDeleted)) ? <Tooltip title={t("jobTableRestore")}><IconButton size="small" onClick={() => void restore(job.id)}><RestoreFromTrashOutlinedIcon fontSize="small" /></IconButton></Tooltip> : <Tooltip title={t("jobTableSoftDelete")}><IconButton size="small" onClick={() => void softDelete(job)}><DeleteOutlineIcon fontSize="small" /></IconButton></Tooltip>}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -776,7 +811,27 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
<TablePagination component="div" count={total} page={page} onPageChange={(_, next) => setPage(next)} rowsPerPage={pageSize} onRowsPerPageChange={(e) => { onPageSizeChange(Number(e.target.value) as 15 | 20 | 25); setPage(0); }} rowsPerPageOptions={[15, 20, 25]} />
|
||||
</Paper>
|
||||
|
||||
<JobDetailsDialog open={detailsJobId !== null} jobId={detailsJobId} initialTab={detailsInitialTab} initialFollowUpMode={detailsFollowUpMode} onClose={() => { setDetailsJobId(null); setDetailsInitialTab(0); setDetailsFollowUpMode(undefined); }} onOpenWorkspace={(id) => { setDetailsJobId(null); navigate(`/applications/${id}`); }} />
|
||||
<JobDetailsDialog open={detailsJobId !== null} jobId={detailsJobId} initialTab={detailsInitialTab} initialFollowUpMode={detailsFollowUpMode} onClose={() => { setDetailsJobId(null); setDetailsInitialTab(0); setDetailsFollowUpMode(undefined); }} onOpenWorkspace={(id) => { setDetailsJobId(null); updateWorkspaceRoute(id); }} />
|
||||
<Dialog
|
||||
open={workspaceJobId !== null}
|
||||
onClose={closeWorkspace}
|
||||
fullScreen={isMobile}
|
||||
fullWidth
|
||||
maxWidth="xl"
|
||||
slotProps={{ paper: { "aria-label": "Application workspace" } }}
|
||||
>
|
||||
<DialogContent sx={{ p: { xs: 1.5, sm: 2.5 } }}>
|
||||
{workspaceJobId ? (
|
||||
<ApplicationWorkspace
|
||||
jobIdOverride={workspaceJobId}
|
||||
sectionOverride={workspaceSectionKey}
|
||||
onSectionChange={updateWorkspaceSection}
|
||||
onClose={closeWorkspace}
|
||||
fullPageHref={`/applications/${workspaceJobId}?section=${workspaceSectionKey}`}
|
||||
/>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<EditJobDialog open={editJobId !== null} jobId={editJobId} onClose={() => setEditJobId(null)} onSaved={() => setReloadToken((token) => token + 1)} />
|
||||
<Menu anchorEl={statusAnchor} open={Boolean(statusAnchor)} onClose={() => { setStatusAnchor(null); setStatusJobId(null); }}>
|
||||
{statusOptions.map((status) => <MenuItem key={status} onClick={() => { if (statusJobId) void setStatusQuick(statusJobId, status); setStatusAnchor(null); setStatusJobId(null); }}>{t("jobTableSetStatus", { status })}</MenuItem>)}
|
||||
|
||||
Reference in New Issue
Block a user