jd can be edited

This commit is contained in:
DaKheera47
2026-01-15 22:44:52 +00:00
parent 2af9537516
commit 4fcace18f3
2 changed files with 59 additions and 6 deletions
@@ -247,6 +247,7 @@ const TailorMode: React.FC<TailorModeProps> = ({
}) => { }) => {
const [catalog, setCatalog] = useState<ResumeProjectCatalogItem[]>([]); const [catalog, setCatalog] = useState<ResumeProjectCatalogItem[]>([]);
const [summary, setSummary] = useState(job.tailoredSummary || ""); const [summary, setSummary] = useState(job.tailoredSummary || "");
const [jobDescription, setJobDescription] = useState(job.jobDescription || "");
const [selectedIds, setSelectedIds] = useState<Set<string>>(() => { const [selectedIds, setSelectedIds] = useState<Set<string>>(() => {
const saved = job.selectedProjectIds?.split(",").filter(Boolean) ?? []; const saved = job.selectedProjectIds?.split(",").filter(Boolean) ?? [];
return new Set(saved); return new Set(saved);
@@ -265,13 +266,15 @@ const TailorMode: React.FC<TailorModeProps> = ({
// Reset form when job changes // Reset form when job changes
useEffect(() => { useEffect(() => {
setSummary(job.tailoredSummary || ""); setSummary(job.tailoredSummary || "");
setJobDescription(job.jobDescription || "");
const saved = job.selectedProjectIds?.split(",").filter(Boolean) ?? []; const saved = job.selectedProjectIds?.split(",").filter(Boolean) ?? [];
setSelectedIds(new Set(saved)); setSelectedIds(new Set(saved));
setDraftStatus("saved"); setDraftStatus("saved");
}, [job.id, job.tailoredSummary, job.selectedProjectIds]); }, [job.id, job.tailoredSummary, job.selectedProjectIds, job.jobDescription]);
// Track unsaved changes // Track unsaved changes
const savedSummary = job.tailoredSummary || ""; const savedSummary = job.tailoredSummary || "";
const savedDescription = job.jobDescription || "";
const savedIds = useMemo(() => { const savedIds = useMemo(() => {
const saved = job.selectedProjectIds?.split(",").filter(Boolean) ?? []; const saved = job.selectedProjectIds?.split(",").filter(Boolean) ?? [];
return new Set(saved); return new Set(saved);
@@ -279,12 +282,13 @@ const TailorMode: React.FC<TailorModeProps> = ({
const hasChanges = useMemo(() => { const hasChanges = useMemo(() => {
if (summary !== savedSummary) return true; if (summary !== savedSummary) return true;
if (jobDescription !== savedDescription) return true;
if (selectedIds.size !== savedIds.size) return true; if (selectedIds.size !== savedIds.size) return true;
for (const id of selectedIds) { for (const id of selectedIds) {
if (!savedIds.has(id)) return true; if (!savedIds.has(id)) return true;
} }
return false; return false;
}, [summary, savedSummary, selectedIds, savedIds]); }, [summary, savedSummary, jobDescription, savedDescription, selectedIds, savedIds]);
// Update draft status when changes are made // Update draft status when changes are made
useEffect(() => { useEffect(() => {
@@ -302,6 +306,7 @@ const TailorMode: React.FC<TailorModeProps> = ({
setDraftStatus("saving"); setDraftStatus("saving");
await api.updateJob(job.id, { await api.updateJob(job.id, {
tailoredSummary: summary, tailoredSummary: summary,
jobDescription: jobDescription,
selectedProjectIds: Array.from(selectedIds).join(","), selectedProjectIds: Array.from(selectedIds).join(","),
}); });
setDraftStatus("saved"); setDraftStatus("saved");
@@ -311,7 +316,7 @@ const TailorMode: React.FC<TailorModeProps> = ({
}, 1500); }, 1500);
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
}, [summary, selectedIds, hasChanges, draftStatus, job.id]); }, [summary, jobDescription, selectedIds, hasChanges, draftStatus, job.id]);
const handleToggleProject = (id: string) => { const handleToggleProject = (id: string) => {
const next = new Set(selectedIds); const next = new Set(selectedIds);
@@ -323,8 +328,19 @@ const TailorMode: React.FC<TailorModeProps> = ({
const handleGenerateWithAI = async () => { const handleGenerateWithAI = async () => {
try { try {
setIsGenerating(true); setIsGenerating(true);
// Save any pending changes first so AI uses the latest description
if (hasChanges) {
await api.updateJob(job.id, {
tailoredSummary: summary,
jobDescription: jobDescription,
selectedProjectIds: Array.from(selectedIds).join(","),
});
}
const updatedJob = await api.summarizeJob(job.id, { force: true }); const updatedJob = await api.summarizeJob(job.id, { force: true });
setSummary(updatedJob.tailoredSummary || ""); setSummary(updatedJob.tailoredSummary || "");
setJobDescription(updatedJob.jobDescription || "");
if (updatedJob.selectedProjectIds) { if (updatedJob.selectedProjectIds) {
setSelectedIds( setSelectedIds(
new Set(updatedJob.selectedProjectIds.split(",").filter(Boolean)) new Set(updatedJob.selectedProjectIds.split(",").filter(Boolean))
@@ -348,6 +364,7 @@ const TailorMode: React.FC<TailorModeProps> = ({
setIsSaving(true); setIsSaving(true);
await api.updateJob(job.id, { await api.updateJob(job.id, {
tailoredSummary: summary, tailoredSummary: summary,
jobDescription: jobDescription,
selectedProjectIds: Array.from(selectedIds).join(","), selectedProjectIds: Array.from(selectedIds).join(","),
}); });
} catch { } catch {
@@ -441,6 +458,20 @@ const TailorMode: React.FC<TailorModeProps> = ({
</Button> </Button>
</div> </div>
{/* Job Description */}
<div className='space-y-2'>
<label className='text-xs font-medium text-muted-foreground'>
Job Description (Edit to help AI tailoring)
</label>
<textarea
className='w-full min-h-[120px] max-h-[250px] rounded-lg border border-border/60 bg-background/50 px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'
value={jobDescription}
onChange={(e) => setJobDescription(e.target.value)}
placeholder='The raw job description...'
disabled={isGenerating || isFinalizing}
/>
</div>
{/* Tailored Summary */} {/* Tailored Summary */}
<div className='space-y-2'> <div className='space-y-2'>
<label className='text-xs font-medium text-muted-foreground'> <label className='text-xs font-medium text-muted-foreground'>
@@ -26,6 +26,7 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
}) => { }) => {
const [catalog, setCatalog] = useState<ResumeProjectCatalogItem[]>([]); const [catalog, setCatalog] = useState<ResumeProjectCatalogItem[]>([]);
const [summary, setSummary] = useState(job.tailoredSummary || ""); const [summary, setSummary] = useState(job.tailoredSummary || "");
const [jobDescription, setJobDescription] = useState(job.jobDescription || "");
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const [isSummarizing, setIsSummarizing] = useState(false); const [isSummarizing, setIsSummarizing] = useState(false);
const [isGeneratingPdf, setIsGeneratingPdf] = useState(false); const [isGeneratingPdf, setIsGeneratingPdf] = useState(false);
@@ -44,7 +45,9 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
return false; return false;
}, [selectedIds, savedSelectedIds]); }, [selectedIds, savedSelectedIds]);
const isDirty = summary !== (job.tailoredSummary || "") || hasSelectionDiff; const isDirty = summary !== (job.tailoredSummary || "") ||
jobDescription !== (job.jobDescription || "") ||
hasSelectionDiff;
useEffect(() => { useEffect(() => {
onDirtyChange?.(isDirty); onDirtyChange?.(isDirty);
@@ -58,7 +61,8 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
if (job.selectedProjectIds) { if (job.selectedProjectIds) {
setSelectedIds(new Set(job.selectedProjectIds.split(',').filter(Boolean))); setSelectedIds(new Set(job.selectedProjectIds.split(',').filter(Boolean)));
} }
}, [job.selectedProjectIds]); setJobDescription(job.jobDescription || "");
}, [job.selectedProjectIds, job.jobDescription]);
useEffect(() => { useEffect(() => {
setSummary(job.tailoredSummary || ""); setSummary(job.tailoredSummary || "");
@@ -70,6 +74,7 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
setIsSaving(true); setIsSaving(true);
await api.updateJob(job.id, { await api.updateJob(job.id, {
tailoredSummary: summary, tailoredSummary: summary,
jobDescription: jobDescription,
selectedProjectIds: Array.from(selectedIds).join(","), selectedProjectIds: Array.from(selectedIds).join(","),
}); });
if (showToast) toast.success("Changes saved"); if (showToast) toast.success("Changes saved");
@@ -81,7 +86,7 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
setIsSaving(false); setIsSaving(false);
} }
}, },
[job.id, onUpdate, selectedIds, summary], [job.id, onUpdate, selectedIds, summary, jobDescription],
); );
useEffect(() => { useEffect(() => {
@@ -106,8 +111,13 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
const handleSummarize = async () => { const handleSummarize = async () => {
try { try {
setIsSummarizing(true); setIsSummarizing(true);
// Save changes first so AI uses latest description
if (isDirty) {
await saveChanges({ showToast: false });
}
const updatedJob = await api.summarizeJob(job.id, { force: true }); const updatedJob = await api.summarizeJob(job.id, { force: true });
setSummary(updatedJob.tailoredSummary || ""); setSummary(updatedJob.tailoredSummary || "");
setJobDescription(updatedJob.jobDescription || "");
if (updatedJob.selectedProjectIds) { if (updatedJob.selectedProjectIds) {
setSelectedIds(new Set(updatedJob.selectedProjectIds.split(',').filter(Boolean))); setSelectedIds(new Set(updatedJob.selectedProjectIds.split(',').filter(Boolean)));
} }
@@ -168,6 +178,18 @@ export const TailoringEditor: React.FC<TailoringEditorProps> = ({
</div> </div>
<div className="space-y-4 rounded-lg border bg-card p-4 shadow-sm"> <div className="space-y-4 rounded-lg border bg-card p-4 shadow-sm">
<div className="space-y-2">
<label className="text-sm font-medium">Job Description (Edit to help AI tailoring)</label>
<textarea
className="w-full min-h-[120px] max-h-[250px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
value={jobDescription}
onChange={(e) => setJobDescription(e.target.value)}
placeholder="The raw job description..."
/>
</div>
<Separator />
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Tailored Summary</label> <label className="text-sm font-medium">Tailored Summary</label>
<textarea <textarea