feat(ui): modernise application tracking and right context

This commit is contained in:
cesnimda
2026-11-03 10:04:00 +01:00
parent dc93b589c9
commit 98e9502af5
65 changed files with 1543 additions and 28 deletions
@@ -84,6 +84,7 @@ export function ApplicationWorkspace({
const sectionId = useId();
const secondaryId = useId();
const panelRef = useRef<HTMLDivElement>(null);
const contextTitle = useRef<HTMLHeadingElement>(null);
const [loadedOverview, setOverview] = useState<WorkspaceOverview | null>(null);
const overview = loadedOverview?.id === jobId ? loadedOverview : null;
@@ -227,6 +228,7 @@ export function ApplicationWorkspace({
<Typography variant="caption" sx={{ fontWeight: 800, letterSpacing: ".08em", textTransform: "uppercase", color: "text.secondary" }}>
{t("workspace")}
</Typography>
{overview ? <Button size="small" onClick={() => contextTitle.current?.focus()} sx={{ ml: "auto" }}>{t("workspaceJumpContext")}</Button> : null}
{fullPageHref ? (
<Tooltip title={t("workspaceOpenFull")}>
<IconButton
@@ -256,6 +258,7 @@ export function ApplicationWorkspace({
</Tabs>
</Paper>
<Box data-application-layout sx={{ display: "grid", gap: 3, minWidth: 0, alignItems: "start", ...(!onClose && overview ? { "@media (min-width:1001px)": { gridTemplateColumns: "minmax(0, 1fr) minmax(16rem, 20rem)" } } : {}) }}>
<Box ref={panelRef} role="tabpanel" id={`${sectionId}-panel`} aria-labelledby={`${sectionId}-${section}`} tabIndex={0} sx={{ display: "grid", gap: 2, minWidth: 0 }}>
{section === "overview" && jobId > 0 && <ApplicationStatusSuggestion jobId={jobId} onApplied={load} />}
{section === "overview" && <OverviewSection overview={overview} onGo={go} onReload={load} onEdit={() => setEditOpen(true)} />}
@@ -303,6 +306,10 @@ export function ApplicationWorkspace({
<PageSection surface title={t("calendarTitle")}><ApplicationCalendarActions jobId={jobId} followUpAt={overview?.followUpAt ?? null} deadline={overview?.deadline ?? null} /></PageSection>
</>}
</Box>
{overview ? <Box component="aside" aria-label={t("workspaceContext")} sx={{ minWidth: 0, ...(!onClose ? { "@media (min-width:1001px)": { borderInlineStart: "1px solid", borderColor: "divider", pl: 3 } } : {}) }}>
<WorkspaceContext overview={overview} titleRef={contextTitle} onReturn={() => panelRef.current?.focus()} />
</Box> : null}
</Box>
<EditJobDialog
open={editOpen}
jobId={jobId > 0 ? jobId : null}
@@ -312,6 +319,41 @@ export function ApplicationWorkspace({
</Box>
);
}
function WorkspaceContext({ overview, titleRef, onReturn }: { overview: WorkspaceOverview; titleRef: React.Ref<HTMLHeadingElement>; onReturn: () => void }) {
const { language, t } = useI18n();
return <PageSection title={t("workspaceContext")} titleRef={titleRef}>
<Box component="dl" sx={{ m: 0, display: "grid", gap: 2, minWidth: 0 }}>
{[
[t("company"), overview.company],
[t("location"), overview.location],
[t("workspaceCountry"), overview.countryCode],
[t("workspaceSource"), overview.source],
[t("jobDetailsSalary"), overview.salary],
].map(([label, value]) => <Box key={label}>
<Typography component="dt" variant="caption" color="text.secondary">{label}</Typography>
<Typography component="dd" variant="body2" sx={{ m: 0, overflowWrap: "anywhere" }}>{value || "—"}</Typography>
</Box>)}
{[
[t("workspaceDiscovered"), overview.savedAt],
[t("workspaceApplied"), overview.dateApplied],
[t("jobDetailsDeadline"), overview.deadline],
[t("intelligenceCategoryFollowUp"), overview.followUpAt],
].map(([label, date]) => {
// Date-only deadlines are local calendar days, not midnight UTC instants.
const parsed = date ? new Date(/^\d{4}-\d{2}-\d{2}$/.test(date) ? `${date}T00:00:00` : date) : null;
return <Box key={label}>
<Typography component="dt" variant="caption" color="text.secondary">{label}</Typography>
<Typography component="dd" variant="body2" sx={{ m: 0, mt: 0.5, fontWeight: 600 }}>
{parsed && !Number.isNaN(parsed.valueOf()) ? <time dateTime={date!}>{parsed.toLocaleDateString(language === "nb" ? "nb-NO" : "en")}</time> : "—"}
</Typography>
</Box>;
})}
</Box>
{overview.tags.length ? <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.75 }}>{overview.tags.map(tag => <Chip key={tag} size="small" label={tag} sx={{ maxWidth: "100%", height: "auto", "& .MuiChip-label": { whiteSpace: "normal", overflowWrap: "anywhere" } }} />)}</Box> : null}
<Button onClick={onReturn}>{t("workspaceReturnTask")}</Button>
</PageSection>;
}
function WorkspaceHeader({ overview, onEdit }: { overview: WorkspaceOverview | null; onEdit: () => void }) {
const { language, t } = useI18n();
if (!overview) return <Box sx={{ p: 2 }} aria-hidden><Skeleton width="65%" height={34} /><Skeleton width="40%" /></Box>;
@@ -397,19 +439,6 @@ function OverviewSection({ overview, onGo, onReload, onEdit }: {
return (
<Stack spacing={2} sx={{ minWidth: 0 }}>
<PageSection surface title={t("workspaceApplicationInfo")}>
<Box component="dl" sx={{ m: 0, display: "grid", gridTemplateColumns: { xs: "1fr", sm: "repeat(2, minmax(0, 1fr))", lg: "repeat(4, minmax(0, 1fr))" }, gap: 2 }}>
{[
[t("workspaceDiscovered"), overview.savedAt],
[t("workspaceApplied"), overview.dateApplied],
[t("jobDetailsDeadline"), overview.deadline],
[t("intelligenceCategoryFollowUp"), overview.followUpAt],
].map(([label, date]) => <Box key={label}>
<Typography component="dt" variant="caption" color="text.secondary">{label}</Typography>
<Typography component="dd" variant="body2" sx={{ m: 0, mt: 0.5, fontWeight: 600 }}>{date ? new Date(date).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : "—"}</Typography>
</Box>)}
</Box>
</PageSection>
{overview.nextStep ? (
<PageSection surface title={t("workspaceNextAction")}>
<Typography component="h3" variant="subtitle1" sx={{ fontWeight: 700 }}>{nextStepLabel}</Typography>
@@ -420,7 +449,7 @@ function OverviewSection({ overview, onGo, onReload, onEdit }: {
</Button>
</PageSection>
) : (
<Alert severity="success" sx={{ borderRadius: 3 }}>
<Alert severity="info" sx={{ borderRadius: 1 }}>
{t("workspaceNothingOutstanding")}
</Alert>
)}