Files
jobtrackingapp/job-tracker-ui/src/components/JobInsightTabs.tsx
T
cesnimda 56fed05d70
CI and Deploy / test (push) Successful in 2m42s
CI and Deploy / deploy (push) Failing after 9s
feat: complete phase 2 UX improvements
2026-07-30 21:35:52 +02:00

118 lines
8.3 KiB
TypeScript

import React from "react";
import { Box, Button, Chip, CircularProgress, Typography } from "@mui/material";
import { useI18n } from "../i18n/I18nProvider";
import { CandidateFit, FocusPlanResponse, InterviewPrepResponse, MatchScore, ReadinessResponse } from "../types";
import { DraftCard, ListCard, MatchScoreCard, SectionChips, TwoColumnSection } from "./JobDetailsPanels";
type Props = {
tab: number;
matchScore: MatchScore | null;
loadingMatchScore: boolean;
candidateFit: CandidateFit | null;
loadingCandidateFit: boolean;
regenerateCandidateFit: () => void;
fitLevel: { label: string; color: "success" | "warning" | "default" } | null;
focusPlan: FocusPlanResponse | null;
loadingFocusPlan: boolean;
regenerateFocusPlan: () => void;
interviewPrep: InterviewPrepResponse | null;
loadingInterviewPrep: boolean;
regenerateInterviewPrep: () => void;
readiness: ReadinessResponse | null;
loadingReadiness: boolean;
};
export default function JobInsightTabs(props: Props) {
const { t } = useI18n();
const { tab, matchScore, loadingMatchScore, candidateFit, loadingCandidateFit, regenerateCandidateFit, fitLevel, focusPlan, loadingFocusPlan, regenerateFocusPlan, interviewPrep, loadingInterviewPrep, regenerateInterviewPrep, readiness, loadingReadiness } = props;
return <>
{tab === 5 && (
<Box>
<MatchScoreCard score={matchScore} loading={loadingMatchScore} />
<Box sx={{ display: "flex", justifyContent: "flex-end", my: 1.5 }}>
<Button size="small" variant="outlined" disabled={loadingCandidateFit} onClick={regenerateCandidateFit}>
{loadingCandidateFit ? "Regenerating..." : "Regenerate"}
</Button>
</Box>
{loadingCandidateFit ? <Box sx={{ py: 4, display: "flex", justifyContent: "center" }}><CircularProgress size={28} /></Box> : candidateFit ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2.5 }}>
<Typography variant="caption" sx={{ color: "text.secondary", display: "block", mt: -1 }}>{t("jobDetailsAiFitHint")}</Typography>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
<Box><Typography variant="overline">{t("jobDetailsHowYouMatch")}</Typography><Typography sx={{ whiteSpace: "pre-wrap" }}>{candidateFit.matchSummary}</Typography></Box>
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", alignItems: "center" }}>
<Chip label={t("jobDetailsMatchPercent", { count: candidateFit.matchScore })} color={candidateFit.matchScore >= 75 ? "success" : candidateFit.matchScore >= 55 ? "warning" : "default"} size="small" />
{fitLevel ? <Chip label={fitLevel.label} color={fitLevel.color} size="small" /> : null}
</Box>
</Box>
<DraftCard title={t("jobDetailsTailoredPitch")} content={candidateFit.tailoredPitch} />
<SectionChips title={t("jobDetailsStrongMatches")} items={candidateFit.strengths} color="success" />
<SectionChips title={t("jobDetailsPossibleGaps")} items={candidateFit.gaps} color="warning" outlined />
<TwoColumnSection leftTitle={t("jobDetailsWhatToMention")} leftItems={candidateFit.mention} rightTitle={t("jobDetailsWhatNotToOverstate")} rightItems={candidateFit.avoid} />
<TwoColumnSection leftTitle={t("jobDetailsImproveCv")} leftItems={candidateFit.cvImprovements} rightTitle={t("jobDetailsMissingKeywords")} rightItems={candidateFit.missingKeywords} />
<TwoColumnSection leftTitle={t("jobDetailsTabInterviewPrep")} leftItems={candidateFit.interviewPrep} rightTitle={t("jobDetailsCvGuidance")} rightItems={candidateFit.guidance.cv} />
<TwoColumnSection leftTitle={t("jobDetailsCoverLetterGuidance")} leftItems={candidateFit.guidance.coverLetter} rightTitle={t("jobDetailsRecruiterMessageGuidance")} rightItems={candidateFit.guidance.recruiterMessage} />
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2 }}>
<DraftCard title={t("jobDetailsCoverLetterDraft")} content={candidateFit.coverLetterDraft ?? t("jobDetailsNoDraftAvailableYet")} />
<DraftCard title={t("jobDetailsRecruiterMessageDraft")} content={candidateFit.recruiterMessageDraft ?? t("jobDetailsNoDraftAvailableYet")} />
</Box>
</Box>
) : <Typography sx={{ color: "text.secondary" }}>{t("jobDetailsCandidateFitEmpty")}</Typography>}
</Box>
)}
{tab === 6 && (
<Box>
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 1.5 }}>
<Button size="small" variant="outlined" disabled={loadingFocusPlan} onClick={regenerateFocusPlan}>
{loadingFocusPlan ? "Regenerating..." : "Regenerate"}
</Button>
</Box>
{loadingFocusPlan ? <Box sx={{ py: 4, display: "flex", justifyContent: "center" }}><CircularProgress size={28} /></Box> : focusPlan ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<DraftCard title={t("jobDetailsFocusSummary")} content={focusPlan.strategicSummary} />
<TwoColumnSection leftTitle={t("jobDetailsImmediatePriorities")} leftItems={focusPlan.immediatePriorities} rightTitle={t("jobDetailsProofPoints")} rightItems={focusPlan.proofPointsToLeadWith} />
<TwoColumnSection leftTitle={t("jobDetailsCvBulletIdeas")} leftItems={focusPlan.cvBulletIdeas} rightTitle={t("jobDetailsCoverLetterAngles")} rightItems={focusPlan.coverLetterAngles} />
<ListCard title={t("jobDetailsFollowUpApproach")} items={focusPlan.followUpApproach} />
</Box>
) : <Typography sx={{ color: "text.secondary" }}>{t("jobDetailsNoFocusPlan")}</Typography>}
</Box>
)}
{tab === 7 && (
<Box>
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 1.5 }}>
<Button size="small" variant="outlined" disabled={loadingInterviewPrep} onClick={regenerateInterviewPrep}>
{loadingInterviewPrep ? "Regenerating..." : "Regenerate"}
</Button>
</Box>
{loadingInterviewPrep ? <Box sx={{ py: 4, display: "flex", justifyContent: "center" }}><CircularProgress size={28} /></Box> : interviewPrep ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<DraftCard title={t("jobDetailsInterviewPrepBrief")} content={interviewPrep.summary} />
<TwoColumnSection leftTitle={t("jobDetailsTalkingPoints")} leftItems={interviewPrep.talkingPoints} rightTitle={t("jobDetailsLikelyQuestions")} rightItems={interviewPrep.likelyQuestions} />
<ListCard title={t("jobDetailsWeakSpots")} items={interviewPrep.weakSpots} />
</Box>
) : <Typography sx={{ color: "text.secondary" }}>{t("jobDetailsNoInterviewPrep")}</Typography>}
</Box>
)}
{tab === 8 && (
<Box>
{loadingReadiness ? <Box sx={{ py: 4, display: "flex", justifyContent: "center" }}><CircularProgress size={28} /></Box> : readiness ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, flexWrap: "wrap" }}>
<Typography variant="h6">{t("jobDetailsApplicationReadiness")}</Typography>
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
<Chip label={t("jobDetailsReadyPercent", { count: readiness.score })} color={readiness.score >= 80 ? "success" : readiness.score >= 60 ? "warning" : "default"} />
<Chip label={readiness.level} variant="outlined" />
</Box>
</Box>
<TwoColumnSection leftTitle={t("jobDetailsCompleted")} leftItems={readiness.completed} rightTitle={t("jobDetailsStillMissing")} rightItems={readiness.missing} />
<ListCard title={t("jobDetailsSmartReminders")} items={readiness.reminders} />
</Box>
) : <Typography sx={{ color: "text.secondary" }}>{t("jobDetailsNoReadiness")}</Typography>}
</Box>
)}
</>;
}