184 lines
6.0 KiB
JavaScript
184 lines
6.0 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
||
import { Button } from './ui/button.js';
|
||
|
||
const AISuggestedMilestones = ({ id, career, careerProfileId, authFetch, activeView, projectionData }) => {
|
||
const [suggestedMilestones, setSuggestedMilestones] = useState([]);
|
||
const [selected, setSelected] = useState([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [aiLoading, setAiLoading] = useState(true); // Start loading state true initially
|
||
|
||
useEffect(() => {
|
||
const fetchAISuggestions = async () => {
|
||
if (!career || !careerProfileId || !Array.isArray(projectionData) || projectionData.length === 0) {
|
||
console.warn('Holding fetch, required data not yet available.');
|
||
setAiLoading(true);
|
||
return;
|
||
}
|
||
|
||
setAiLoading(true);
|
||
try {
|
||
const milestonesRes = await authFetch(`/api/premium/milestones?careerProfileId=${careerProfileId}`);
|
||
const { milestones } = await milestonesRes.json();
|
||
|
||
const response = await authFetch('/api/premium/milestone/ai-suggestions', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ career, careerProfileId, projectionData, existingMilestones: milestones }),
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
|
||
if (!response.ok) throw new Error('Failed to fetch AI suggestions');
|
||
const data = await response.json();
|
||
|
||
setSuggestedMilestones(data.suggestedMilestones.map((m) => ({
|
||
title: m.title,
|
||
date: m.date,
|
||
description: m.description,
|
||
progress: 0,
|
||
})));
|
||
} catch (error) {
|
||
console.error('Error fetching AI suggestions:', error);
|
||
} finally {
|
||
setAiLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchAISuggestions();
|
||
}, [career, careerProfileId, projectionData, authFetch]);
|
||
|
||
const regenerateSuggestions = async () => {
|
||
setAiLoading(true);
|
||
try {
|
||
const milestonesRes = await authFetch(`/api/premium/milestones?careerProfileId=${careerProfileId}`);
|
||
const { milestones } = await milestonesRes.json();
|
||
|
||
const previouslySuggestedMilestones = suggestedMilestones;
|
||
|
||
// Explicitly reduce projection data size by sampling every 6 months
|
||
const sampledProjectionData = projectionData.filter((_, i) => i % 6 === 0);
|
||
|
||
// Fetch career goals explicitly if defined (you'll implement this later; for now send empty or placeholder)
|
||
// const careerGoals = selectedCareer?.careerGoals || '';
|
||
|
||
const response = await authFetch('/api/premium/milestone/ai-suggestions', {
|
||
method: 'POST',
|
||
body: JSON.stringify({
|
||
career,
|
||
careerProfileId,
|
||
projectionData: sampledProjectionData,
|
||
existingMilestones: milestones,
|
||
previouslySuggestedMilestones,
|
||
regenerate: true,
|
||
//careerGoals, // explicitly included
|
||
}),
|
||
headers: { 'Content-Type': 'application/json' },
|
||
});
|
||
|
||
if (!response.ok) throw new Error('Failed to fetch AI suggestions');
|
||
const data = await response.json();
|
||
|
||
setSuggestedMilestones(
|
||
data.suggestedMilestones.map((m) => ({
|
||
title: m.title,
|
||
date: m.date,
|
||
description: m.description,
|
||
progress: 0,
|
||
}))
|
||
);
|
||
} catch (error) {
|
||
console.error('Error regenerating AI suggestions:', error);
|
||
} finally {
|
||
setAiLoading(false);
|
||
}
|
||
};
|
||
|
||
|
||
const toggleSelect = (index) => {
|
||
setSelected((prev) =>
|
||
prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index]
|
||
);
|
||
};
|
||
|
||
const confirmSelectedMilestones = async () => {
|
||
const milestonesToSend = selected.map((index) => {
|
||
const m = suggestedMilestones[index];
|
||
return {
|
||
title: m.title,
|
||
description: m.description,
|
||
date: m.date,
|
||
progress: m.progress,
|
||
milestone_type: activeView || 'Career',
|
||
career_profile_id: careerProfileId
|
||
};
|
||
});
|
||
|
||
try {
|
||
setLoading(true);
|
||
const res = await authFetch(`/api/premium/milestone`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ milestones: milestonesToSend }),
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
if (!res.ok) throw new Error('Failed to save selected milestones');
|
||
|
||
setSelected([]);
|
||
window.location.reload();
|
||
} catch (error) {
|
||
console.error('Error saving selected milestones:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
// Explicit spinner shown whenever aiLoading is true
|
||
if (aiLoading) {
|
||
return (
|
||
<div className="mt-4 flex items-center justify-center">
|
||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-500"></div>
|
||
<span className="ml-2 text-gray-600">Generating AI-suggested milestones...</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!suggestedMilestones.length) return null;
|
||
|
||
return (
|
||
<div className="mt-4 p-4 border rounded bg-gray-50 shadow">
|
||
<div className="flex items-center justify-between">
|
||
<h4 className="text-lg font-semibold mb-2">AI-Suggested Milestones</h4>
|
||
<Button
|
||
className="mb-2"
|
||
onClick={() => regenerateSuggestions()}
|
||
disabled={aiLoading}
|
||
variant="outline"
|
||
>
|
||
{aiLoading ? 'Regenerating...' : 'Regenerate Suggestions'}
|
||
</Button>
|
||
</div>
|
||
|
||
<ul className="space-y-1">
|
||
{suggestedMilestones.map((m, i) => (
|
||
<li key={i} className="flex items-center gap-2">
|
||
<input
|
||
type="checkbox"
|
||
className="rounded border-gray-300 text-indigo-600 shadow-sm"
|
||
checked={selected.includes(i)}
|
||
onChange={() => toggleSelect(i)}
|
||
/>
|
||
<span className="text-sm">{m.title} – {m.date}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
<Button
|
||
className="mt-3"
|
||
onClick={confirmSelectedMilestones}
|
||
disabled={loading || selected.length === 0}
|
||
>
|
||
{loading ? 'Saving...' : 'Confirm Selected'}
|
||
</Button>
|
||
</div>
|
||
);
|
||
|
||
};
|
||
|
||
export default AISuggestedMilestones;
|