fit assessment UI

This commit is contained in:
DaKheera47 2026-01-15 18:14:00 +00:00
parent bdae9d13cc
commit d9187acb37
4 changed files with 36 additions and 106 deletions

View File

@ -30,6 +30,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
import * as api from "../api";
import { FitAssessment } from ".";
import type { Job, ResumeProjectCatalogItem } from "../../shared/types";
// ─────────────────────────────────────────────────────────────────────────────
@ -61,46 +62,6 @@ const formatDate = (dateStr: string | null) => {
}
};
const getScoreLabel = (
score: number | null
): { label: string; color: string; description: string } => {
if (score == null)
return {
label: "Unscored",
color: "text-muted-foreground",
description: "No AI assessment yet",
};
if (score >= 80)
return {
label: "Excellent fit",
color: "text-emerald-400",
description: "Strong match for your profile",
};
if (score >= 65)
return {
label: "Good fit",
color: "text-emerald-400/80",
description: "Solid match worth considering",
};
if (score >= 50)
return {
label: "Possible fit",
color: "text-amber-400",
description: "Some relevant aspects",
};
if (score >= 35)
return {
label: "Weak fit",
color: "text-orange-400",
description: "Limited alignment",
};
return {
label: "Poor fit",
color: "text-rose-400",
description: "May not be worth pursuing",
};
};
const stripHtml = (value: string) =>
value
.replace(/<[^>]*>/g, " ")
@ -114,44 +75,6 @@ const sourceLabel: Record<Job["source"], string> = {
ukvisajobs: "UK Visa Jobs",
};
// ─────────────────────────────────────────────────────────────────────────────
// Fit Summary Component (for Decide mode)
// ─────────────────────────────────────────────────────────────────────────────
interface FitSummaryProps {
job: Job;
}
const FitSummary: React.FC<FitSummaryProps> = ({ job }) => {
return (
<div className='space-y-3'>
{/* AI Assessment */}
{job.suitabilityReason && (
<div className='rounded-lg border border-border/40 bg-muted/10 p-3'>
<div className='text-[10px] uppercase tracking-wide text-muted-foreground/70 mb-1.5'>
AI Assessment
</div>
<p className='text-sm text-foreground/80 leading-relaxed'>
{job.suitabilityReason}
</p>
</div>
)}
{/* No assessment fallback */}
{!job.suitabilityReason && !job.suitabilityScore && (
<div className='rounded-lg border border-border/40 bg-muted/10 p-3 text-center'>
<p className='text-sm text-muted-foreground'>
No AI assessment available yet.
</p>
<p className='text-xs text-muted-foreground/70 mt-1'>
Review the job description to decide if you want to tailor.
</p>
</div>
)}
</div>
);
};
// ─────────────────────────────────────────────────────────────────────────────
// Decide Mode Panel
// ─────────────────────────────────────────────────────────────────────────────
@ -172,7 +95,6 @@ const DecideMode: React.FC<DecideModeProps> = ({
const [showDescription, setShowDescription] = useState(false);
const deadline = formatDate(job.deadline);
const jobLink = job.applicationLink || job.jobUrl;
const scoreInfo = getScoreLabel(job.suitabilityScore);
const description = useMemo(() => {
if (!job.jobDescription) return "No description available.";
@ -195,25 +117,13 @@ const DecideMode: React.FC<DecideModeProps> = ({
</p>
</div>
<div className="flex flex-col items-center space-y-2">
<div className="flex flex-col items-center justify-center">
<Badge
variant='outline'
className='text-[10px] uppercase tracking-wide text-muted-foreground border-border/50 shrink-0'
>
{sourceLabel[job.source]}
</Badge>
{job.suitabilityScore != null && (
<div className='flex justify-end'>
<span
className={cn(
"text-xl font-bold tabular-nums",
scoreInfo.color
)}
>
{job.suitabilityScore}%
</span>
</div>
)}
</div>
</div>
@ -244,7 +154,7 @@ const DecideMode: React.FC<DecideModeProps> = ({
{/* Fit Summary - the core content */}
<div className='flex-1 py-4 space-y-4 overflow-y-auto'>
<FitSummary job={job} />
<FitAssessment job={job} />
{/* Collapsible full description */}
<div className='space-y-2'>

View File

@ -0,0 +1,30 @@
import React from "react";
import { Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
import type { Job } from "../../shared/types";
interface FitAssessmentProps {
job: Job;
className?: string;
}
export const FitAssessment: React.FC<FitAssessmentProps> = ({
job,
className,
}) => {
if (!job.suitabilityReason) return null;
return (
<div className={cn("space-y-3", className)}>
<div className="rounded-lg border border-primary/20 bg-primary/5 px-3 py-2.5">
<div className="text-[11px] font-medium uppercase tracking-wide text-primary/70 mb-1.5 flex items-center gap-1.5">
<Sparkles className="h-3 w-3" />
Fit Assessment
</div>
<p className="text-xs text-foreground/90 leading-relaxed font-medium">
{job.suitabilityReason}
</p>
</div>
</div>
);
};

View File

@ -22,7 +22,6 @@ import {
Briefcase,
Building2,
FolderKanban,
Sparkles,
} from "lucide-react";
import { toast } from "sonner";
@ -43,6 +42,7 @@ import {
import { cn } from "@/lib/utils";
import { copyTextToClipboard, formatJobForWebhook } from "@client/lib/jobCopy";
import * as api from "../api";
import { FitAssessment } from ".";
import type { Job, ResumeProjectCatalogItem } from "../../shared/types";
interface ReadyPanelProps {
@ -275,18 +275,7 @@ export const ReadyPanel: React.FC<ReadyPanelProps> = ({
<div className="flex-1 py-4 space-y-4">
{/* Job identity - confirm this is the right role */}
<div className="space-y-3">
{/* AI Suitability Reasoning - Why you're a fit */}
{job.suitabilityReason && (
<div className="rounded-lg border border-primary/20 bg-primary/5 px-3 py-2.5">
<div className="text-[11px] font-medium uppercase tracking-wide text-primary/70 mb-1.5 flex items-center gap-1.5">
<Sparkles className="h-3 w-3" />
Fit Assessment
</div>
<p className="text-xs text-foreground/90 leading-relaxed font-medium">
{job.suitabilityReason}
</p>
</div>
)}
<FitAssessment job={job} />
{/* Tailored summary snippet - shows what's in the PDF */}
{tailoredSummary && (

View File

@ -2,6 +2,7 @@ export { Header } from './Header';
export { Stats } from './Stats';
export { StatusBadge } from './StatusBadge';
export { ScoreIndicator } from './ScoreIndicator';
export { FitAssessment } from './FitAssessment';
export { JobCard } from './JobCard';
export { JobTable } from './JobTable';
export { JobList } from './JobList';