jd can be edited
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user