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