import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
import {
Alert, Box, Button, Chip, Divider, IconButton, List, ListItemButton, ListItemText, Paper,
Skeleton, Stack, Tooltip, Typography,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import DescriptionOutlinedIcon from "@mui/icons-material/DescriptionOutlined";
import MailOutlineIcon from "@mui/icons-material/MailOutline";
import FolderOutlinedIcon from "@mui/icons-material/FolderOutlined";
import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
import ChecklistIcon from "@mui/icons-material/Checklist";
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import { getApiErrorMessage } from "../api";
import Attachments from "../components/Attachments";
import Correspondence from "../components/Correspondence";
import AiWorkspacePanel from "../components/AiWorkspacePanel";
import ApplicationChecklist from "../components/ApplicationChecklist";
import {
ApplicationAnalysis, ApplicationMatch, ApplicationTimeline,
} from "../components/ApplicationIntelligence";
import {
ApplicationCoverLetterSection, ApplicationCvSection,
} from "../components/ApplicationAssets";
import { ApplicationInterviewPrep } from "../components/InterviewPrep";
import EditJobDialog from "../components/EditJobDialog";
import {
WORKSPACE_SECTIONS, WorkspaceOverview, WorkspaceSectionKey, applicationWorkspaceApi, workspaceSection,
} from "../applicationWorkspace";
// Phase 5 Milestone 1 — the dedicated Application Workspace.
//
// This is a surface, not a new data store: the overview is one aggregate read, and each section
// reuses the component that already owns that domain (Attachments, Correspondence, AiWorkspacePanel).
// docs/architecture/application-workspace.md.
export default function ApplicationWorkspacePage() {
return ;
}
type ApplicationWorkspaceProps = {
jobIdOverride?: number;
sectionOverride?: WorkspaceSectionKey;
onSectionChange?: (section: WorkspaceSectionKey) => void;
onClose?: () => void;
fullPageHref?: string;
};
export function ApplicationWorkspace({
jobIdOverride,
sectionOverride,
onSectionChange,
onClose,
fullPageHref,
}: ApplicationWorkspaceProps) {
const { id } = useParams();
const jobId = jobIdOverride ?? Number(id);
const location = useLocation();
const navigate = useNavigate();
const [params, setParams] = useSearchParams();
const section = sectionOverride ?? workspaceSection(params.get("section"));
const [overview, setOverview] = useState(null);
const [error, setError] = useState(null);
const [editOpen, setEditOpen] = useState(false);
const load = useCallback(async () => {
if (!Number.isInteger(jobId) || jobId <= 0) {
setOverview(null);
setError("This application link is invalid.");
return;
}
try {
setError(null);
setOverview(await applicationWorkspaceApi.overview(jobId));
} catch (err) {
setError(getApiErrorMessage(err, "Could not open this application."));
}
}, [jobId]);
useEffect(() => {
load();
}, [load]);
const go = (next: WorkspaceSectionKey) => {
if (onSectionChange) onSectionChange(next);
else setParams({ section: next }, { replace: true, state: location.state });
};
const close = onClose ?? (() => {
const from = (location.state as { from?: unknown } | null)?.from;
navigate(typeof from === "string" && from.startsWith("/") && !from.startsWith("//") ? from : "/jobs", { replace: true });
});
if (error) {
return (
} onClick={close}>Back to applications
{error}
);
}
return (
Workspace
{fullPageHref ? (
) : null}
{WORKSPACE_SECTIONS.map((s) => (
go(s.key)} sx={{ borderRadius: 2 }}>
))}
setEditOpen(true)} />
{section === "overview" && }
{section === "job-details" && setEditOpen(true)} />}
{/* Deterministic answer first, then the AI panel below it — the page never generates on load. */}
{section === "analysis" && jobId > 0 && }
{section === "match" && jobId > 0 && }
{section === "timeline" && jobId > 0 && }
{section === "interview" && jobId > 0 && }
{(section === "analysis" || section === "match" || section === "interview") && jobId > 0 && (
)}
{section === "documents" && jobId > 0 && (
)}
{section === "communication" && jobId > 0 && (
)}
{section === "checklist" && jobId > 0 && (
)}
{section === "cv" && jobId > 0 && }
{section === "cover-letter" && jobId > 0 && (
<>
{/* Generation stays an explicit user action, below the editor the user owns. */}
>
)}
0 ? jobId : null}
onClose={() => setEditOpen(false)}
onSaved={() => { setEditOpen(false); void load(); }}
/>
);
}
function WorkspaceHeader({ overview, onEdit }: { overview: WorkspaceOverview | null; onEdit: () => void }) {
if (!overview) return ;
return (
{overview.jobTitle}
{[overview.company, overview.location, overview.salary].filter(Boolean).join(" · ") || "—"}
{overview.source ? : null}
{overview.jobUrl && (
)}
);
}
function OverviewSection({ overview, onGo, onReload }: {
overview: WorkspaceOverview | null;
onGo: (s: WorkspaceSectionKey) => void;
onReload: () => void;
}) {
const stats = useMemo(() => overview ? [
{ icon: , label: "CV", value: overview.cv.variantName ?? (overview.cv.hasTailoredCvText ? "Tailored text" : "Not prepared"), ok: !!overview.cv.variantId || overview.cv.hasTailoredCvText, go: "cv" as const },
{ icon: , label: "Cover letter", value: overview.hasCoverLetter ? "Ready" : "Not written", ok: overview.hasCoverLetter, go: "cover-letter" as const },
{ icon: , label: "Documents", value: overview.documentCount ? `${overview.documentCount} attached` : "None", ok: overview.documentCount > 0, go: "documents" as const },
{ icon: , label: "AI suggestions", value: overview.aiInteractionCount ? `${overview.aiInteractionCount} saved` : "None yet", ok: overview.aiInteractionCount > 0, go: "analysis" as const },
{ icon: , label: "Checklist", value: overview.checklistProgress ? `${overview.checklistProgress.completed}/${overview.checklistProgress.total} done` : "—", ok: (overview.checklistProgress?.percent ?? 0) === 100, go: "checklist" as const },
] : [], [overview]);
if (!overview) {
return {[0, 1].map((i) => )};
}
return (
{overview.nextStep ? (
Next recommended action
{overview.nextStep.label}
{overview.nextStep.reason}
}
onClick={() => overview.nextStep?.section && onGo(overview.nextStep.section as WorkspaceSectionKey)}>
{overview.nextStep.label}
) : (
Nothing outstanding — this application is fully prepared.
)}
{stats.map((s) => (
onGo(s.go)}
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onGo(s.go); } }}
sx={{ p: 1.5, borderRadius: 3, cursor: "pointer", "&:focus-visible": { boxShadow: 3 } }}>
{s.icon}
{s.label}
{s.value}
))}
Recent activity
{overview.recentActivity.length === 0 ? (
No activity recorded yet.
) : (
{overview.recentActivity.map((a, i) => (
{a.type}{a.detail ? ` — ${a.detail}` : ""}
{new Date(a.at).toLocaleDateString()}
))}
)}
);
}
function JobDetailsSection({ overview, onEdit }: { overview: WorkspaceOverview | null; onEdit: () => void }) {
if (!overview) return ;
const rows: [string, string][] = [
["Company", overview.company ?? "—"],
["Location", overview.location ?? "—"],
["Country", overview.countryCode ?? "—"],
["Source", overview.source ?? "—"],
["Salary", overview.salary ?? "—"],
["Status", overview.status],
["Discovered", overview.savedAt ? new Date(overview.savedAt).toLocaleDateString() : "—"],
["Applied", overview.dateApplied ? new Date(overview.dateApplied).toLocaleDateString() : "—"],
["Deadline", overview.deadline ? new Date(overview.deadline).toLocaleDateString() : "—"],
["Follow-up", overview.followUpAt ? new Date(overview.followUpAt).toLocaleDateString() : "Not scheduled"],
["Next action", overview.nextAction ?? "—"],
];
return (
Application information
} onClick={onEdit}>Edit
{rows.map(([k, v]) => (
{k}
{v}
))}
{overview.tags.length > 0 ? (
{overview.tags.map((tag) => )}
) : null}
{overview.notes ? (
Notes
{overview.notes}
) : null}
Job description
{!overview.hasJobDescription ? (
Add advert}>
No advert text saved. Analysis and matching need the job description.
) : (
{overview.translatedDescription ? (
Translated advert
{overview.translatedDescription}
) : null}
{overview.description ? (
{overview.translatedDescription ? Original advert{overview.descriptionLanguage ? ` · ${overview.descriptionLanguage.toUpperCase()}` : ""} : null}
{overview.description}
) : null}
)}
);
}