dev1/src/components/AISuggestedMilestones.js

184 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;