Add attachment metadata and overview strategy snapshot
This commit is contained in:
@@ -34,6 +34,8 @@ type AttachmentItem = {
|
||||
uploadDate: string;
|
||||
fileType: string;
|
||||
fileSize: number;
|
||||
purpose?: string | null;
|
||||
useForAi: boolean;
|
||||
};
|
||||
|
||||
type FollowUpDraft = {
|
||||
@@ -97,6 +99,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
const [focusPlan, setFocusPlan] = useState<FocusPlanResponse | null>(null);
|
||||
const [loadingCandidateFit, setLoadingCandidateFit] = useState(false);
|
||||
const [loadingFocusPlan, setLoadingFocusPlan] = useState(false);
|
||||
const [loadingStrategySnapshot, setLoadingStrategySnapshot] = useState(false);
|
||||
const [interviewPrep, setInterviewPrep] = useState<InterviewPrepResponse | null>(null);
|
||||
const [loadingInterviewPrep, setLoadingInterviewPrep] = useState(false);
|
||||
const [readiness, setReadiness] = useState<ReadinessResponse | null>(null);
|
||||
@@ -137,7 +140,8 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
api.get<AttachmentItem[]>(`/attachments/${jobId}`).then((r) => {
|
||||
const items = Array.isArray(r.data) ? r.data : [];
|
||||
setJobAttachments(items);
|
||||
setSelectedAttachmentIds(items.slice(0, 3).map((item) => item.id));
|
||||
const defaultIds = items.filter((item) => item.useForAi !== false).slice(0, 3).map((item) => item.id);
|
||||
setSelectedAttachmentIds(defaultIds.length > 0 ? defaultIds : items.slice(0, 3).map((item) => item.id));
|
||||
}).catch(() => {
|
||||
setJobAttachments([]);
|
||||
setSelectedAttachmentIds([]);
|
||||
@@ -273,6 +277,40 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
|
||||
{tab === 0 && (
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 2 }}>
|
||||
<Box sx={{ gridColumn: "1 / -1", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
|
||||
<Typography variant="overline">{t("jobDetailsStrategySnapshot")}</Typography>
|
||||
<Button size="small" variant="outlined" disabled={loadingStrategySnapshot} onClick={async () => {
|
||||
if (!jobId) return;
|
||||
setLoadingStrategySnapshot(true);
|
||||
try {
|
||||
const [fitRes, focusRes] = await Promise.all([
|
||||
api.get<CandidateFit>(`/jobapplications/${jobId}/candidate-fit`, { params: { attachmentIds: selectedAttachmentCsv || undefined } }),
|
||||
api.get<FocusPlanResponse>(`/jobapplications/${jobId}/focus-plan`, { params: { attachmentIds: selectedAttachmentCsv || undefined } }),
|
||||
]);
|
||||
setCandidateFit(fitRes.data);
|
||||
setFocusPlan(focusRes.data);
|
||||
} catch {
|
||||
toast(t("jobDetailsStrategySnapshotFailed"), "error");
|
||||
} finally {
|
||||
setLoadingStrategySnapshot(false);
|
||||
}
|
||||
}}>{loadingStrategySnapshot ? t("jobDetailsRefreshing") : t("jobDetailsGenerateStrategySnapshot")}</Button>
|
||||
</Box>
|
||||
{candidateFit || focusPlan ? (
|
||||
<Box sx={{ gridColumn: "1 / -1", p: 1.5, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.default" }}>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", alignItems: "center", mb: 1 }}>
|
||||
{candidateFit ? <Chip size="small" color={candidateFit.matchScore >= 75 ? "success" : candidateFit.matchScore >= 55 ? "warning" : "default"} label={t("jobDetailsMatchPercent", { count: candidateFit.matchScore })} /> : null}
|
||||
{candidateFit?.fitLevel ? <Chip size="small" variant="outlined" label={candidateFit.fitLevel} /> : null}
|
||||
</Box>
|
||||
{focusPlan?.strategicSummary ? <Typography sx={{ whiteSpace: "pre-wrap", mb: 1 }}>{focusPlan.strategicSummary}</Typography> : null}
|
||||
{candidateFit?.matchSummary ? <Typography sx={{ color: "text.secondary", whiteSpace: "pre-wrap", mb: 1.5 }}>{candidateFit.matchSummary}</Typography> : null}
|
||||
{focusPlan?.immediatePriorities?.length ? <ListCard title={t("jobDetailsImmediatePriorities")} items={focusPlan.immediatePriorities.slice(0, 3)} /> : null}
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ gridColumn: "1 / -1" }}>
|
||||
<Typography sx={{ color: "text.secondary" }}>{t("jobDetailsStrategySnapshotEmpty")}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box><Typography variant="overline">{t("jobDetailsDateApplied")}</Typography><Typography>{job ? new Date(job.dateApplied).toLocaleDateString() : ""}</Typography></Box>
|
||||
<Box><Typography variant="overline">{t("jobDetailsDaysSince")}</Typography><Typography>{job?.daysSince ?? ""}</Typography></Box>
|
||||
<Box><Typography variant="overline">{t("jobTableLocation")}</Typography><Typography>{job?.location ?? ""}</Typography></Box>
|
||||
|
||||
Reference in New Issue
Block a user