Professor reorder
This commit is contained in:
@ -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);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user