Skip to content

Commit ebd1089

Browse files
committed
made a file for pending user skills endorsements as well and removed from gensec endorsement page
1 parent 78cfec1 commit ebd1089

2 files changed

Lines changed: 255 additions & 260 deletions

File tree

frontend/src/Components/GenSec/GenSecEndorse.jsx

Lines changed: 9 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -1,257 +1,15 @@
11
"use client";
22

3-
import React, { useState, useEffect } from "react";
3+
import React, { useState } from "react";
44
import { getConfigByRole, endorsementTabs } from "../../config/endorseConfig";
5-
import SkillsEndorsementTab from "./SkillsEndorsementTab";
6-
import AchievementsEndorsementTab from "./AchievementsEndorsementTab";
75
import { AdminContext } from "../../context/AdminContext";
8-
import api from "../../utils/api";
9-
const fetchUnendorsedSkills = async (skillType) => {
10-
try {
11-
const res = await api.get(
12-
`/api/skills/user-skills/unendorsed/${skillType}`,
13-
);
14-
return res.data;
15-
} catch (error) {
16-
const message =
17-
error.response?.data?.message || "Failed to fetch unendorsed skills";
18-
throw new Error(message);
19-
}
20-
};
21-
22-
const endorseSkill = async (skillId) => {
23-
try {
24-
const res = await api.post(`/api/skills/user-skills/endorse/${skillId}`);
25-
return res.data;
26-
} catch (error) {
27-
const message = error.response?.data?.message || "Failed to endorse skill";
28-
throw new Error(message);
29-
}
30-
};
31-
32-
const UserSkillCard = ({ skill, onEndorse, isEndorsing }) => {
33-
const formatDate = (dateString) => {
34-
return new Date(dateString).toLocaleDateString("en-US", {
35-
year: "numeric",
36-
month: "short",
37-
day: "numeric",
38-
});
39-
};
40-
41-
const getProficiencyColor = (level) => {
42-
switch (level.toLowerCase()) {
43-
case "beginner":
44-
return "bg-gray-100 text-gray-800";
45-
case "intermediate":
46-
return "bg-blue-100 text-blue-800";
47-
case "advanced":
48-
return "bg-green-100 text-green-800";
49-
case "expert":
50-
return "bg-purple-100 text-purple-800";
51-
default:
52-
return "bg-gray-100 text-gray-800";
53-
}
54-
};
55-
56-
return (
57-
<div className="bg-pink-50 border border-pink-100 rounded-lg p-4 hover:shadow-md transition-shadow duration-200">
58-
{/* Card Header */}
59-
<div className="mb-4">
60-
<h3 className="text-lg font-semibold text-gray-900 mb-2">
61-
{skill.skill_id.name}
62-
</h3>
63-
<p className="text-sm text-gray-600 mb-2">{skill.skill_id.category}</p>
64-
<span
65-
className={`inline-block px-2 py-1 rounded-full text-xs font-medium ${getProficiencyColor(skill.proficiency_level)}`}
66-
>
67-
{skill.proficiency_level.charAt(0).toUpperCase() +
68-
skill.proficiency_level.slice(1)}
69-
</span>
70-
</div>
71-
72-
{/* User Information */}
73-
<div className="flex items-center gap-2 mb-3 text-sm">
74-
<div className="w-8 h-8 bg-gray-200 rounded-full flex items-center justify-center">
75-
<span className="text-xs font-medium text-gray-700">
76-
{skill.user_id.personal_info.name.charAt(0)}
77-
</span>
78-
</div>
79-
<div>
80-
<p className="font-medium text-gray-900">
81-
{skill.user_id.personal_info.name}
82-
</p>
83-
<p className="text-sm text-gray-600">{skill.user_id.user_id}</p>
84-
</div>
85-
</div>
86-
87-
{/* Position and Date Information */}
88-
<div className="space-y-2 mb-2">
89-
{skill.position_id && (
90-
<div className="bg-white rounded-md p-2">
91-
<p className="text-sm font-medium text-gray-900">
92-
Position: {skill.position_id.title}
93-
</p>
94-
</div>
95-
)}
96-
<div className="flex items-center gap-2 text-sm text-gray-600">
97-
<svg
98-
className="w-4 h-4"
99-
fill="none"
100-
stroke="currentColor"
101-
viewBox="0 0 24 24"
102-
>
103-
<path
104-
strokeLinecap="round"
105-
strokeLinejoin="round"
106-
strokeWidth={2}
107-
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
108-
/>
109-
</svg>
110-
<span>Submitted: {formatDate(skill.created_at)}</span>
111-
</div>
112-
</div>
113-
114-
{/* Action Button */}
115-
<div className="flex justify-end pt-2 border-t border-pink-200">
116-
<button
117-
onClick={() => onEndorse(skill._id)}
118-
disabled={isEndorsing}
119-
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
120-
>
121-
<span>{isEndorsing ? "Endorsing..." : "Endorse"}</span>
122-
</button>
123-
</div>
124-
</div>
125-
);
126-
};
127-
128-
const UserSkillsTab = ({ skillType }) => {
129-
const [skills, setSkills] = useState([]);
130-
const [loading, setLoading] = useState(true);
131-
const [endorsingSkills, setEndorsingSkills] = useState(new Set());
132-
const [error, setError] = useState(null);
1336

134-
useEffect(() => {
135-
loadSkills();
136-
}, [skillType]);
137-
138-
const loadSkills = async () => {
139-
try {
140-
setLoading(true);
141-
setError(null);
142-
const data = await fetchUnendorsedSkills(skillType);
143-
setSkills(data);
144-
} catch (err) {
145-
setError("Failed to load user skills. Please try again.");
146-
console.error("Error loading skills:", err);
147-
} finally {
148-
setLoading(false);
149-
}
150-
};
151-
152-
const handleEndorse = async (skillId) => {
153-
try {
154-
setEndorsingSkills((prev) => new Set([...prev, skillId]));
155-
156-
// Make the API call
157-
await endorseSkill(skillId);
158-
159-
// --- FIX START ---
160-
// Removed the `if (result.success)` condition.
161-
// Now, we update the state to remove the endorsed skill from the list
162-
// as long as the API call above doesn't fail.
163-
setSkills((prev) => prev.filter((skill) => skill._id !== skillId));
164-
console.log("User skill endorsed successfully");
165-
// --- FIX END ---
166-
167-
} catch (err) {
168-
console.error("Error endorsing skill:", err);
169-
setError("Failed to endorse skill. Please try again.");
170-
} finally {
171-
setEndorsingSkills((prev) => {
172-
const newSet = new Set(prev);
173-
newSet.delete(skillId);
174-
return newSet;
175-
});
176-
}
177-
};
178-
179-
if (loading) {
180-
return (
181-
<div className="flex justify-center items-center py-12">
182-
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
183-
</div>
184-
);
185-
}
186-
187-
if (skills.length === 0) {
188-
return (
189-
<div className="text-center py-12">
190-
<div className="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
191-
<svg
192-
className="w-8 h-8 text-gray-400"
193-
fill="none"
194-
stroke="currentColor"
195-
viewBox="0 0 24 24"
196-
>
197-
<path
198-
strokeLinecap="round"
199-
strokeLinejoin="round"
200-
strokeWidth={2}
201-
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
202-
/>
203-
</svg>
204-
</div>
205-
<h3 className="text-lg font-medium text-gray-900 mb-2">
206-
No Pending Endorsements
207-
</h3>
208-
<p className="text-gray-600">
209-
All {skillType} user skills have been reviewed and endorsed.
210-
</p>
211-
</div>
212-
);
213-
}
214-
215-
return (
216-
<div className="space-y-6">
217-
{error && (
218-
<div className="bg-red-50 border border-red-200 rounded-md p-4">
219-
<div className="flex">
220-
<svg
221-
className="h-5 w-5 text-red-400"
222-
fill="none"
223-
stroke="currentColor"
224-
viewBox="0 0 24 24"
225-
>
226-
<path
227-
strokeLinecap="round"
228-
strokeLinejoin="round"
229-
strokeWidth={2}
230-
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
231-
/>
232-
</svg>
233-
<div className="ml-3">
234-
<p className="text-sm text-red-800">{error}</p>
235-
</div>
236-
</div>
237-
</div>
238-
)}
239-
240-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
241-
{skills.map((skill) => (
242-
<UserSkillCard
243-
key={skill._id}
244-
skill={skill}
245-
onEndorse={handleEndorse}
246-
isEndorsing={endorsingSkills.has(skill._id)}
247-
/>
248-
))}
249-
</div>
250-
</div>
251-
);
252-
};
7+
// Import the dedicated tab components
8+
import SkillsEndorsementTab from "./SkillsEndorsementTab";
9+
import AchievementsEndorsementTab from "./AchievementsEndorsementTab";
10+
import UserSkillsEndorsementTab from "./UserSkillsEndorsementTab";
25311

254-
const TabNavigation = ({ activeTab, onTabChange, skillType }) => {
12+
const TabNavigation = ({ activeTab, onTabChange }) => {
25513
const getTabIcon = (iconType) => {
25614
switch (iconType) {
25715
case "user":
@@ -334,29 +92,24 @@ const GenSecEndorse = () => {
33492
const { userRole } = React.useContext(AdminContext);
33593
const adminRole = userRole;
33694

337-
const [currentPage, setCurrentPage] = useState("endorsement"); // 'endorsement' or 'history'
33895
const [activeTab, setActiveTab] = useState("user-skills");
33996

34097
const config = getConfigByRole(adminRole);
34198

342-
const handleBackToEndorsement = () => {
343-
setCurrentPage("endorsement");
344-
};
345-
34699
const handleTabChange = (tabId) => {
347100
setActiveTab(tabId);
348101
};
349102

350103
const renderTabContent = () => {
351104
switch (activeTab) {
352105
case "user-skills":
353-
return <UserSkillsTab skillType={config.skillType} />;
106+
return <UserSkillsEndorsementTab skillType={config.skillType} />;
354107
case "skills":
355108
return <SkillsEndorsementTab skillType={config.skillType} />;
356109
case "achievements":
357110
return <AchievementsEndorsementTab skillType={config.skillType} />;
358111
default:
359-
return <UserSkillsTab skillType={config.skillType} />;
112+
return <UserSkillsEndorsementTab skillType={config.skillType} />;
360113
}
361114
};
362115

@@ -403,11 +156,7 @@ const GenSecEndorse = () => {
403156
{/* Tab Navigation */}
404157
<div className="bg-white border-b border-gray-200">
405158
<div className="max-w-7xl mx-auto">
406-
<TabNavigation
407-
activeTab={activeTab}
408-
onTabChange={handleTabChange}
409-
skillType={config.skillType}
410-
/>
159+
<TabNavigation activeTab={activeTab} onTabChange={handleTabChange} />
411160
</div>
412161
</div>
413162

0 commit comments

Comments
 (0)