226 lines
8.6 KiB
JavaScript
226 lines
8.6 KiB
JavaScript
import React, { useState } from "react";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Progress } from "@/components/ui/progress";
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
import { CheckCircle, Clock, Target, PlusCircle, Search } from "lucide-react";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
|
|
|
|
|
|
|
const MilestoneTracker = () => {
|
|
const location = useLocation();
|
|
const initialCareer = location.state?.career || "";
|
|
const [careerClusters, setCareerClusters] = useState({});
|
|
const [selectedCluster, setSelectedCluster] = useState(null);
|
|
const [selectedSubdivision, setSelectedSubdivision] = useState(null);
|
|
const [selectedCareer, setSelectedCareer] = useState(initialCareer);
|
|
const [filteredClusters, setFilteredClusters] = useState([]);
|
|
const [activeTab, setActiveTab] = useState("career");
|
|
const [customMilestones, setCustomMilestones] = useState({ career: [], financial: [], retirement: [] });
|
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
const [newMilestone, setNewMilestone] = useState("");
|
|
const [careerSearch, setCareerSearch] = useState("");
|
|
const [filteredCareers, setFilteredCareers] = useState(careerClusters);
|
|
|
|
|
|
useEffect(() => {
|
|
const fetchUserProfile = async () => {
|
|
try {
|
|
const response = await fetch("/api/user/profile", {
|
|
method: "GET",
|
|
credentials: "include", // Ensure cookies/session are sent
|
|
});
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setIsPremiumUser(data.is_premium === 1); // Expecting { is_premium: 0 or 1 }
|
|
} else {
|
|
setIsPremiumUser(false); // Default to false if there's an error
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching user profile:", error);
|
|
setIsPremiumUser(false);
|
|
}
|
|
};
|
|
fetchUserProfile();
|
|
}, []);
|
|
|
|
if (isPremiumUser === null) {
|
|
return <div className="p-6 text-center">Loading...</div>; // Show loading state while fetching
|
|
}
|
|
|
|
if (!isPremiumUser) {
|
|
return (
|
|
<div className="p-6 text-center">
|
|
<Lock className="mx-auto text-gray-400 w-16 h-16 mb-4" />
|
|
<h2 className="text-xl font-bold">Access Restricted</h2>
|
|
<p className="text-gray-600">Upgrade to Aptiva Premium to access the Milestone Tracker.</p>
|
|
<Button className="mt-4">Upgrade Now</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetch("/data/career_clusters.json")
|
|
.then((response) => response.json())
|
|
.then((data) => {
|
|
setCareerClusters(data);
|
|
setFilteredClusters(Object.keys(data));
|
|
})
|
|
.catch((error) => console.error("Error loading career clusters:", error));
|
|
}, []);
|
|
|
|
const handleAddMilestone = () => {
|
|
if (newMilestone.trim() !== "") {
|
|
setCustomMilestones((prev) => ({
|
|
...prev,
|
|
[activeTab]: [...prev[activeTab], { title: newMilestone, status: "upcoming", progress: 0 }],
|
|
}));
|
|
setNewMilestone("");
|
|
setIsDialogOpen(false);
|
|
}
|
|
};
|
|
|
|
const handleCareerSearch = (e) => {
|
|
const query = e.target.value.toLowerCase();
|
|
setCareerSearch(query);
|
|
setFilteredCareers(
|
|
careerClusters.filter((career) => career.toLowerCase().includes(query))
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<h1 className="text-2xl font-bold mb-4">Milestone Tracker</h1>
|
|
|
|
{/* Career Cluster Selection */}
|
|
<div className="mb-6 p-4 border rounded-lg shadow-md bg-white">
|
|
<h2 className="text-xl font-semibold mb-2">Search & Select a Career Cluster</h2>
|
|
<Input
|
|
value={careerSearch}
|
|
onChange={handleCareerSearch}
|
|
placeholder="Search for a career cluster"
|
|
className="mb-2"
|
|
/>
|
|
<div className="max-h-40 overflow-y-auto border rounded p-2">
|
|
{filteredClusters.map((cluster, index) => (
|
|
<div
|
|
key={index}
|
|
className={`p-2 hover:bg-gray-200 cursor-pointer rounded ${cluster === selectedCluster ? 'bg-blue-200' : ''}`}
|
|
onClick={() => {
|
|
setSelectedCluster(cluster);
|
|
setSelectedSubdivision(null);
|
|
setSelectedCareer(null);
|
|
}}
|
|
>
|
|
<strong>{cluster}</strong>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Subdivision Selection within Cluster */}
|
|
{selectedCluster && careerClusters[selectedCluster] && (
|
|
<div className="mb-6 p-4 border rounded-lg shadow-md bg-white">
|
|
<h2 className="text-xl font-semibold mb-2">Select a Specialization in {selectedCluster}</h2>
|
|
<div className="max-h-40 overflow-y-auto border rounded p-2">
|
|
{Object.keys(careerClusters[selectedCluster]).map((subdivision, index) => (
|
|
<div
|
|
key={index}
|
|
className={`p-2 hover:bg-gray-200 cursor-pointer rounded ${subdivision === selectedSubdivision ? 'bg-blue-200' : ''}`}
|
|
onClick={() => {
|
|
setSelectedSubdivision(subdivision);
|
|
setSelectedCareer(null);
|
|
}}
|
|
>
|
|
{subdivision}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Career Selection within Subdivision */}
|
|
{selectedSubdivision && careerClusters[selectedCluster][selectedSubdivision] && (
|
|
<div className="mb-6 p-4 border rounded-lg shadow-md bg-white">
|
|
<h2 className="text-xl font-semibold mb-2">Select a Career in {selectedSubdivision}</h2>
|
|
<div className="max-h-40 overflow-y-auto border rounded p-2">
|
|
{Object.keys(careerClusters[selectedCluster][selectedSubdivision]).map((career, index) => (
|
|
<div
|
|
key={index}
|
|
className={`p-2 hover:bg-gray-200 cursor-pointer rounded ${career === selectedCareer ? 'bg-blue-200' : ''}`}
|
|
onClick={() => setSelectedCareer(career)}
|
|
>
|
|
{career}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Milestone Tracker Section */}
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="mb-6">
|
|
<TabsList>
|
|
<TabsTrigger value="career">Career</TabsTrigger>
|
|
<TabsTrigger value="financial">Financial</TabsTrigger>
|
|
<TabsTrigger value="retirement">Retirement</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
|
|
{/* Add Milestone Section */}
|
|
<Button onClick={() => setIsDialogOpen(true)} className="mb-4 flex items-center gap-2">
|
|
<PlusCircle /> Add Milestone
|
|
</Button>
|
|
|
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Add a New Milestone</DialogTitle>
|
|
</DialogHeader>
|
|
<Input value={newMilestone} onChange={(e) => setNewMilestone(e.target.value)} placeholder="Enter milestone title" />
|
|
<DialogFooter>
|
|
<Button onClick={handleAddMilestone}>Save</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Display User-Added Milestones */}
|
|
<TabsContent value={activeTab}>
|
|
{customMilestones[activeTab].map((milestone, index) => (
|
|
<Card key={index} className="mb-4 p-4">
|
|
<CardContent className="flex justify-between items-center">
|
|
<div className="flex items-center gap-4">
|
|
{milestone.status === "completed" && <CheckCircle className="text-green-500" />}
|
|
{milestone.status === "in-progress" && <Clock className="text-yellow-500" />}
|
|
{milestone.status === "upcoming" && <Target className="text-gray-500" />}
|
|
<h2 className="text-lg font-semibold">{milestone.title}</h2>
|
|
</div>
|
|
<Progress value={milestone.progress} className="w-40" />
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</TabsContent>
|
|
|
|
|
|
<Button onClick={() => setIsDialogOpen(true)} className="mb-4 flex items-center gap-2">
|
|
<PlusCircle /> Add Milestone
|
|
</Button>
|
|
|
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Add a New Milestone</DialogTitle>
|
|
</DialogHeader>
|
|
<Input value={newMilestone} onChange={(e) => setNewMilestone(e.target.value)} placeholder="Enter milestone title" />
|
|
<DialogFooter>
|
|
<Button onClick={handleAddMilestone}>Save</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
|
|
export default MilestoneTracker; |