feat(ui): modernise application tracking and right context
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user