Professor reorder

This commit is contained in:
2026-05-12 12:17:03 +05:30
parent b5b33cf809
commit 8a155b689b

View File

@ -25,6 +25,8 @@ export interface TeamMember {
officeLocation?: string; officeLocation?: string;
joinDate?: string; joinDate?: string;
category: string; category: string;
/** Controls display order within each section. Lower = earlier. */
displayOrder: number;
} }
export interface Skill { export interface Skill {
@ -55,42 +57,38 @@ export class FacultyService {
private static baseUrl = private static baseUrl =
process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'; process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
// ✅ FIX: Add cache busting to all fetch calls
static async getAllFaculty(): Promise<TeamMember[]> { static async getAllFaculty(): Promise<TeamMember[]> {
try { try {
// ✅ ADD: Cache busting with timestamp
const timestamp = new Date().getTime(); const timestamp = new Date().getTime();
const response = await fetch(`${this.baseUrl}/professor?size=100&_t=${timestamp}`, { const response = await fetch(`${this.baseUrl}/professor?size=100&_t=${timestamp}`, {
// ✅ ADD: Disable caching
cache: 'no-store', cache: 'no-store',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Cache-Control': 'no-cache, no-store, must-revalidate', 'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache', 'Pragma': 'no-cache',
'Expires': '0' 'Expires': '0',
}, },
}); });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data: FacultyApiResponse = await response.json(); const data: FacultyApiResponse = await response.json();
return this.transformProfessorsToTeamMembers(data.content); // The backend already orders by displayOrder ASC, lastName ASC,
// but we sort again client-side as a safety net.
const members = this.transformProfessorsToTeamMembers(data.content);
return this.sortByDisplayOrder(members);
} catch (error) { } catch (error) {
console.error('Error fetching faculty data:', error); console.error('Error fetching faculty data:', error);
return this.getFallbackData(); return this.getFallbackData();
} }
} }
// ✅ FIXED: Now accepts both numeric ID and UUID professorId
static async getFacultyById(id: number | string): Promise<TeamMember | null> { static async getFacultyById(id: number | string): Promise<TeamMember | null> {
try { try {
// If it's a UUID (string with dashes), fetch directly from API
if (typeof id === 'string' && id.includes('-')) { if (typeof id === 'string' && id.includes('-')) {
return await this.getFacultyByProfessorId(id); return await this.getFacultyByProfessorId(id);
} }
// Otherwise, fetch all and find by numeric ID
const allFaculty = await this.getAllFaculty(); const allFaculty = await this.getAllFaculty();
return allFaculty.find(member => member.id === id) || null; return allFaculty.find(member => member.id === id) || null;
} catch (error) { } catch (error) {
@ -99,38 +97,24 @@ export class FacultyService {
} }
} }
// ✅ FIX: Add cache busting to professor fetch
static async getFacultyByProfessorId(professorId: string): Promise<TeamMember | null> { static async getFacultyByProfessorId(professorId: string): Promise<TeamMember | null> {
try { try {
// ✅ ADD: Cache busting with timestamp
const timestamp = new Date().getTime(); const timestamp = new Date().getTime();
const response = await fetch(`${this.baseUrl}/professor/${professorId}?_t=${timestamp}`, { const response = await fetch(`${this.baseUrl}/professor/${professorId}?_t=${timestamp}`, {
// ✅ ADD: Disable caching
cache: 'no-store', cache: 'no-store',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Cache-Control': 'no-cache, no-store, must-revalidate', 'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache', 'Pragma': 'no-cache',
'Expires': '0' 'Expires': '0',
}, },
}); });
if (!response.ok) { if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(`HTTP error! status: ${response.status}`);
}
const professor = await response.json(); const professor = await response.json();
// ✅ DEBUG: Log the awards received from backend
console.log('Awards received from backend:', professor.awards?.length || 0);
console.log('Awards data:', professor.awards);
const transformed = this.transformProfessorsToTeamMembers([professor]); const transformed = this.transformProfessorsToTeamMembers([professor]);
// ✅ DEBUG: Log transformed awards
console.log('Awards after transformation:', transformed[0]?.awards?.length || 0);
return transformed[0] || null; return transformed[0] || null;
} catch (error) { } catch (error) {
console.error('Error fetching professor by ID:', error); console.error('Error fetching professor by ID:', error);
@ -148,58 +132,63 @@ export class FacultyService {
} }
} }
// Helper method to check if an image URL is accessible
static async checkImageExists(imageUrl: string): Promise<boolean> { static async checkImageExists(imageUrl: string): Promise<boolean> {
try { try {
const response = await fetch(imageUrl, { method: 'HEAD' }); const response = await fetch(imageUrl, { method: 'HEAD' });
return response.ok; return response.ok;
} catch (error) { } catch {
return false; return false;
} }
} }
// Helper method to get a working image URL with fallback static async getWorkingImageUrl(
static async getWorkingImageUrl(originalUrl: string, fallbackUrl: string = '/images/default-avatar.jpg'): Promise<string> { originalUrl: string,
fallbackUrl: string = '/images/default-avatar.jpg'
): Promise<string> {
const isAccessible = await this.checkImageExists(originalUrl); const isAccessible = await this.checkImageExists(originalUrl);
return isAccessible ? originalUrl : fallbackUrl; return isAccessible ? originalUrl : fallbackUrl;
} }
/**
* Sort team members by displayOrder (ascending), then lastName (alphabetical).
* This mirrors the backend ORDER BY clause and keeps the UI deterministic.
*/
private static sortByDisplayOrder(members: TeamMember[]): TeamMember[] {
return [...members].sort((a, b) => {
const orderDiff = (a.displayOrder ?? 0) - (b.displayOrder ?? 0);
if (orderDiff !== 0) return orderDiff;
return (a.lastName ?? '').localeCompare(b.lastName ?? '');
});
}
private static transformProfessorsToTeamMembers(professors: any[]): TeamMember[] { private static transformProfessorsToTeamMembers(professors: any[]): TeamMember[] {
return professors.map((prof, index) => { return professors.map((prof, index) => {
// Create proper image URL - remove /api from baseUrl for images
const imageBaseUrl = this.baseUrl; const imageBaseUrl = this.baseUrl;
let imageUrl = '/images/default-avatar.jpg'; // Default fallback let imageUrl = '/images/default-avatar.jpg';
if (prof.profileImageUrl) { if (prof.profileImageUrl) {
// If it's already a full URL, use it as is
if (prof.profileImageUrl.startsWith('http')) { if (prof.profileImageUrl.startsWith('http')) {
imageUrl = prof.profileImageUrl; imageUrl = prof.profileImageUrl.replace('/user/', '/professor/');
// ✅ FIX: Replace /user/ with /professor/ in the URL
imageUrl = imageUrl.replace('/user/', '/professor/');
} else { } else {
// If it's a relative URL, construct the full URL const relativePath = prof.profileImageUrl.replace('/user/', '/professor/');
let relativePath = prof.profileImageUrl;
// ✅ FIX: Replace /user/ with /professor/ in relative paths too
relativePath = relativePath.replace('/user/', '/professor/');
imageUrl = `${imageBaseUrl}${relativePath}`; imageUrl = `${imageBaseUrl}${relativePath}`;
} }
} }
// ✅ FIX: Don't add default awards if backend returns awards const hasBackendAwards =
// Only use fallback awards if backend returns empty/null array prof.awards && Array.isArray(prof.awards) && prof.awards.length > 0;
const hasBackendAwards = prof.awards && Array.isArray(prof.awards) && prof.awards.length > 0;
const awards = hasBackendAwards const awards = hasBackendAwards
? prof.awards.map((award: any) => ({ ? prof.awards.map((award: any) => ({
title: award.title, title: award.title,
year: award.year, year: award.year,
description: award.description || '', description: award.description || '',
image: award.imageUrl || '/images/award-icon.png' image: award.imageUrl || '/images/award-icon.png',
})) }))
: []; // ✅ IMPORTANT: Return empty array, not default award : [];
return { return {
id: index + 1, // Generate sequential ID for UI id: index + 1,
professorId: prof.professorId, professorId: prof.professorId,
name: prof.name || `${prof.firstName} ${prof.lastName}`, name: prof.name || `${prof.firstName} ${prof.lastName}`,
firstName: prof.firstName, firstName: prof.firstName,
@ -207,30 +196,30 @@ export class FacultyService {
position: prof.position || 'Faculty Member', position: prof.position || 'Faculty Member',
designation: prof.designation || prof.position || 'Faculty Member', designation: prof.designation || prof.position || 'Faculty Member',
image: imageUrl, image: imageUrl,
// ✅ FIXED: Use professorId in URL instead of numeric ID
profileUrl: `/faculty/${prof.professorId}`, profileUrl: `/faculty/${prof.professorId}`,
phone: prof.phone || 'Not available', phone: prof.phone || 'Not available',
email: prof.email, email: prof.email,
experience: prof.experience || 'Not specified', experience: prof.experience || 'Not specified',
description: prof.description || `${prof.firstName} ${prof.lastName} is a dedicated member of our faculty.`, description:
prof.description ||
`${prof.firstName} ${prof.lastName} is a dedicated member of our faculty.`,
specialty: prof.specialty || prof.department || 'General Medicine', specialty: prof.specialty || prof.department || 'General Medicine',
certification: prof.certification || 'Medical certification details not available', certification: prof.certification || 'Medical certification details not available',
training: prof.training || 'Professional training details not available', training: prof.training || 'Professional training details not available',
workDays: prof.workDays || ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], workDays: prof.workDays || ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
skills: prof.skills?.map((skill: any) => ({ skills: prof.skills?.map((skill: any) => ({ name: skill.name, level: skill.level })) || [
name: skill.name, { name: 'Clinical Practice', level: 90 },
level: skill.level { name: 'Research', level: 85 },
})) || [ { name: 'Teaching', level: 88 },
{ name: 'Clinical Practice', level: 90 }, ],
{ name: 'Research', level: 85 }, awards,
{ name: 'Teaching', level: 88 }
],
awards: awards, // ✅ Use the properly handled awards
status: prof.status, status: prof.status,
department: prof.department, department: prof.department,
officeLocation: prof.officeLocation, officeLocation: prof.officeLocation,
joinDate: prof.joinDate, joinDate: prof.joinDate,
category: prof.category || 'FACULTY' category: prof.category || 'FACULTY',
// ← NEW: carry displayOrder through so client-side sort stays correct
displayOrder: prof.displayOrder ?? 0,
}; };
}); });
} }
@ -240,40 +229,35 @@ export class FacultyService {
{ {
id: 1, id: 1,
professorId: 'fallback-1', professorId: 'fallback-1',
name: "Loading Faculty Data...", name: 'Loading Faculty Data...',
firstName: "Loading", firstName: 'Loading',
lastName: "Data", lastName: 'Data',
position: "Please wait while we fetch the latest faculty information", position: 'Please wait while we fetch the latest faculty information',
designation: "System Message", designation: 'System Message',
image: "/images/default-avatar.jpg", image: '/images/default-avatar.jpg',
profileUrl: '/faculty/fallback-1', profileUrl: '/faculty/fallback-1',
phone: "Not available", phone: 'Not available',
email: "support@institution.edu", email: 'support@institution.edu',
experience: "N/A", experience: 'N/A',
description: "Faculty data is currently being loaded from the server.", description: 'Faculty data is currently being loaded from the server.',
specialty: "N/A", specialty: 'N/A',
certification: "N/A", certification: 'N/A',
training: "N/A", training: 'N/A',
workDays: [], workDays: [],
skills: [], skills: [],
awards: [], awards: [],
category: 'FACULTY' category: 'FACULTY',
} displayOrder: 0,
},
]; ];
} }
} }
// Helper function to get team members (for backward compatibility) export const getTeamMembers = async (): Promise<TeamMember[]> =>
export const getTeamMembers = async (): Promise<TeamMember[]> => { FacultyService.getAllFaculty();
return await FacultyService.getAllFaculty();
};
// Helper function to get team member by ID (now accepts both numeric and UUID) export const getTeamMemberById = async (id: number | string): Promise<TeamMember | null> =>
export const getTeamMemberById = async (id: number | string): Promise<TeamMember | null> => { FacultyService.getFacultyById(id);
return await FacultyService.getFacultyById(id);
};
// Helper function to get team member by professorId (UUID) export const getTeamMemberByProfessorId = async (professorId: string): Promise<TeamMember | null> =>
export const getTeamMemberByProfessorId = async (professorId: string): Promise<TeamMember | null> => { FacultyService.getFacultyByProfessorId(professorId);
return await FacultyService.getFacultyByProfessorId(professorId);
};