diff --git a/packages/backend/src/events/events.controller.ts b/packages/backend/src/events/events.controller.ts index 72af490..4c405c8 100644 --- a/packages/backend/src/events/events.controller.ts +++ b/packages/backend/src/events/events.controller.ts @@ -226,14 +226,14 @@ export class EventsController { }; // Check for file removal flag - const removeEventImage = updateEventDto.removeEventImage === 'true'; + // const removeEventImage = updateEventDto.removeEventImage === 'true'; return this.eventsService.update( +id, user, updatedEvent, file, - removeEventImage, + // removeEventImage, ); } @@ -277,7 +277,7 @@ export class EventsController { 'Delete event with ID. \n\n REQUIRED ROLES: **ADMIN | MENTOR**', }) @ApiBearerAuth() - remove(@Param('id') id: string) { - return this.eventsService.remove(+id); + remove(@GetUser() user: JwtPayload, @Param('id') id: string) { + return this.eventsService.remove(user, +id); } } diff --git a/packages/backend/src/events/events.service.ts b/packages/backend/src/events/events.service.ts index 7ac8239..e8cf74f 100644 --- a/packages/backend/src/events/events.service.ts +++ b/packages/backend/src/events/events.service.ts @@ -294,7 +294,7 @@ export class EventsService { user: JwtPayload, updateEventDto: UpdateEventDto, file: Express.Multer.File | null, - removeEventImage: boolean = false, + // removeEventImage: boolean = false, ) { try { // VALIDATE EVENT EXIST OR THROW EXCEPTION @@ -322,7 +322,7 @@ export class EventsService { // Handle image updates let imagePath: string | undefined; - if (removeEventImage && eventToUpdate.image) { + if (file && eventToUpdate.image) { // Delete existing image file try { await this.filesService.deleteFile(eventToUpdate.image); @@ -390,7 +390,33 @@ export class EventsService { } } - remove(id: number) { - return `This action removes a #${id} event`; + async remove(user: JwtPayload, event_id: number) { + try { + // VALIDATE EVENT EXIST OR THROW EXCEPTION + const eventToUpdate = await this.findOne(event_id); + if (!eventToUpdate) { + throw new NotFoundException(`Event with ID: ${event_id} not found.`); + } + + // VALIDATE IF THE USER IS ADMIN OR MANAGER + const allowedToUpdate = + user.roles === 'ADMIN' + ? true + : await this.eventsManagersService.isEventManager(user.sub, event_id); + if (!allowedToUpdate) { + throw new UnauthorizedException( + 'You are not authorized to edit this event.', + ); + } + + // DELETE EVENT + const deletedEvent = await this.prisma.events.delete({ + where: { id: event_id }, + }); + + return deletedEvent; + } catch (error) { + throw new BadRequestException('Error deleting event: ' + error.message); + } } } diff --git a/packages/backend/src/mentor/entities/mentor.entity.ts b/packages/backend/src/mentor/entities/mentor.entity.ts index f349828..7576c0c 100644 --- a/packages/backend/src/mentor/entities/mentor.entity.ts +++ b/packages/backend/src/mentor/entities/mentor.entity.ts @@ -1,5 +1,15 @@ -import { IsInt, IsString, IsOptional, IsEnum, Min, Max } from 'class-validator'; -import { mentors_status } from '@prisma/client'; +import { + IsInt, + IsString, + IsOptional, + IsEnum, + Min, + Max, + IsNumber, + IsArray, + IsObject, +} from 'class-validator'; +import { mentors_status, matching_status } from '@prisma/client'; import { ApiProperty } from '@nestjs/swagger'; export class Mentors { @@ -43,3 +53,133 @@ export class Mentors { @IsInt() user_id: number; } + +export class MentorStatistics { + @ApiProperty({ description: 'Total minutes mentored', example: 1893 }) + @IsNumber() + minutesMentored: number = 0; + + @ApiProperty({ + description: 'Percentage difference with previous month', + example: 25.5, + }) + @IsNumber() + minutesMentoredDiff: number = 0; + + @ApiProperty({ description: 'Total number of mentees', example: 15 }) + @IsNumber() + mentees: number = 0; + + @ApiProperty({ + description: 'Percentage difference with previous month', + example: -2.5, + }) + @IsNumber() + menteesDiff: number = 0; + + @ApiProperty({ description: 'Total number of live sessions', example: 64 }) + @IsNumber() + liveSessions: number = 0; + + @ApiProperty({ + description: 'Percentage difference with previous month', + example: -10.0, + }) + @IsNumber() + liveSessionsDiff: number = 0; +} + +export class MentorUpcomingSessions { + @ApiProperty({ description: 'ID of the upcoming session', example: '10' }) + @IsNumber() + id?: number; + + @ApiProperty({ + description: 'Profile picture of the mentee', + example: 'https://example.com/avatar.jpg', + }) + @IsOptional() + @IsString() + avatar?: string; + + @ApiProperty({ description: 'Full name of the mentee', example: 'John Doe' }) + @IsString() + mentee: string; + + @ApiProperty({ + description: 'Last session with the mentee', + example: '2024-01-15T14:00:00Z', + }) + @IsOptional() + @IsString() + lastMet?: string; + + @ApiProperty({ + description: 'Profession of the mentee', + example: 'Software Engineer', + }) + @IsOptional() + @IsString() + profession?: string; + + @ApiProperty({ + description: 'Link for the upcoming session', + example: 'https://meet.google.com/abc-defg-hij', + }) + @IsOptional() + @IsString() + link?: string; +} + +export class MyMentees { + @ApiProperty({ description: 'ID of the mentee', example: '10' }) + @IsNumber() + id?: number; + + @ApiProperty({ + description: 'Full name of the mentee', + example: 'Jane Smith', + }) + @IsString() + mentee: string; + + @ApiProperty({ + description: 'Email of the mentee', + example: 'jane.smith@example.com', + }) + @IsString() + email: string; + + @ApiProperty({ + description: 'Profession of the mentee', + example: 'Data Scientist', + }) + @IsOptional() + @IsString() + profession?: string; + + @ApiProperty({ + description: 'Status of the matching', + example: 'APPROVED', + enum: matching_status, + }) + @IsEnum(matching_status) + status: matching_status; +} + +export class MyMentorDashboard { + @ApiProperty({ description: 'Mentor statistics', type: MentorStatistics }) + @IsObject() + statistics: MentorStatistics; + + @ApiProperty({ + description: 'List of upcoming sessions', + type: [MentorUpcomingSessions], + }) + @IsArray() + upcomingSessions: MentorUpcomingSessions[]; + + @ApiProperty({ description: 'List of mentees', type: [MyMentees] }) + @IsArray() + mentees: MyMentees[]; +} diff --git a/packages/backend/src/mentor/mentor.controller.ts b/packages/backend/src/mentor/mentor.controller.ts index 3220609..56df074 100644 --- a/packages/backend/src/mentor/mentor.controller.ts +++ b/packages/backend/src/mentor/mentor.controller.ts @@ -34,13 +34,22 @@ import { ApiBadRequestResponse, ApiConsumes, } from '@nestjs/swagger'; -import { Mentors } from './entities/mentor.entity'; +import { + Mentors, + MentorStatistics, + MentorUpcomingSessions, + MyMentees, + MyMentorDashboard, +} from './entities/mentor.entity'; @UseGuards(JwtAuthGuard, RolesGuard) @ApiTags('Mentors') @Controller('mentors') export class MentorController { constructor(private readonly mentorService: MentorService) {} + // ------------------------------ + // MENTOR APPLICATIONS + // ------------------------------ @Get() @Roles('ADMIN') @@ -52,7 +61,7 @@ export class MentorController { description: 'Fetch any mentor applications. \n\n REQUIRED ROLES: **ADMIN**', }) - @ApiBearerAuth() + @ApiBearerAuth('JWT') findAll(@Body() filters: FilterMentorDto) { return this.mentorService.findAll(filters); } @@ -70,12 +79,12 @@ export class MentorController { description: 'There is no mentor application for this user (USER ID: #`:id` )', }) - @ApiBearerAuth() + @ApiBearerAuth('JWT') findMyApplication(@GetUser() user: JwtPayload) { return this.mentorService.findOneByUserId(user.sub); } - @Get(':mentorApplicationId') + @Get('application-id/:mentorApplicationId') @Roles('ADMIN') @ApiParam({ name: 'mentorApplicationId' }) @ApiOkResponse({ type: Mentors }) @@ -89,7 +98,7 @@ export class MentorController { description: 'There is no mentor application with ID: `:mentorApplicationId`. Make sure you are not using a user ID', }) - @ApiBearerAuth() + @ApiBearerAuth('JWT') findOne(@Param('mentorApplicationId') mentorApplicationId: string) { return this.mentorService.findOneById(+mentorApplicationId); } @@ -156,7 +165,7 @@ export class MentorController { @ApiNotFoundResponse({ description: 'Mentor application for user with ID: `user_id` not found', }) - @ApiBearerAuth() + @ApiBearerAuth('JWT') update( @GetUser() user: JwtPayload, @Body() updateMentorDto: UpdateMentorDto, @@ -172,7 +181,7 @@ export class MentorController { return this.mentorService.update(+user.sub, updatedMentor); } - @Put(':mentorApplicationId') + @Put('application-id/:mentorApplicationId') @Roles('ADMIN') @ApiParam({ name: 'mentorApplicationId' }) @ApiBody({ type: UpdateMentorStatusDto }) @@ -193,7 +202,7 @@ export class MentorController { @ApiBadRequestResponse({ description: 'Error updating mentor application status: `[ERROR MESSAGE]`', }) - @ApiBearerAuth() + @ApiBearerAuth('JWT') updateStatus( @Param('mentorApplicationId') mentorApplicationId: string, @Body() updateMentorStatusDto: UpdateMentorStatusDto, @@ -205,7 +214,6 @@ export class MentorController { } // TO-DO: REMOVE MENTOR INFORMATION? OR CHANGE STATUS TO "REJECTED" OR "DELETED" TO KEEP THE INFORMATION? - @Delete(':id') @Roles('ADMIN') @ApiOkResponse({ type: String }) @@ -219,4 +227,64 @@ export class MentorController { remove(@Param('id') id: string) { return this.mentorService.remove(+id); } + + // ------------------------------ + // MENTOR DASHBOARD + // ------------------------------ + + @Get('my-dashboard') + @Roles('MENTOR') + @ApiOkResponse({ type: MyMentorDashboard }) + @ApiInternalServerErrorResponse({ description: 'Internal server error' }) + @ApiOperation({ + summary: 'Get mentor dashboard', + description: + 'Get complete mentor dashboard with statistics, upcoming sessions, and mentees. \n\n REQUIRED ROLES: **MENTOR**', + }) + @ApiBearerAuth('JWT') + async getMyDashboard(@GetUser() user: JwtPayload) { + return await this.mentorService.getMyDashboard(user.sub); + } + + @Get('my-statistics') + @Roles('MENTOR') + @ApiOkResponse({ type: MentorStatistics }) + @ApiInternalServerErrorResponse({ description: 'Internal server error' }) + @ApiOperation({ + summary: 'Get mentor statistics', + description: + 'Get mentor statistics including minutes mentored, mentees count, and live sessions with percentage differences. \n\n REQUIRED ROLES: **MENTOR**', + }) + @ApiBearerAuth('JWT') + async getMyStatistics(@GetUser() user: JwtPayload) { + return await this.mentorService.getMyStatistics(user.sub); + } + + @Get('my-upcoming-sessions') + @Roles('MENTOR') + @ApiOkResponse({ type: [MentorUpcomingSessions] }) + @ApiInternalServerErrorResponse({ description: 'Internal server error' }) + @ApiOperation({ + summary: 'Get mentor upcoming sessions', + description: + 'Get list of upcoming sessions with mentee details. \n\n REQUIRED ROLES: **MENTOR**', + }) + @ApiBearerAuth('JWT') + async getMyUpcomingSessions(@GetUser() user: JwtPayload) { + return await this.mentorService.getMyUpcomingSessions(user.sub); + } + + @Get('my-mentees') + @Roles('MENTOR') + @ApiOkResponse({ type: [MyMentees] }) + @ApiInternalServerErrorResponse({ description: 'Internal server error' }) + @ApiOperation({ + summary: 'Get mentor mentees', + description: + 'Get list of mentees assigned to the mentor. \n\n REQUIRED ROLES: **MENTOR**', + }) + @ApiBearerAuth('JWT') + async getMyMentees(@GetUser() user: JwtPayload) { + return await this.mentorService.getMyMentees(user.sub); + } } diff --git a/packages/backend/src/mentor/mentor.service.ts b/packages/backend/src/mentor/mentor.service.ts index c5fc4c6..8e592da 100644 --- a/packages/backend/src/mentor/mentor.service.ts +++ b/packages/backend/src/mentor/mentor.service.ts @@ -19,6 +19,10 @@ export class MentorService { private filesService: FilesService, ) {} + // ------------------------------ + // MENTOR APPLICATIONS + // ------------------------------ + private async addUserInterestsFormatted( user_id: number, interests: string | number[], @@ -401,4 +405,262 @@ export class MentorService { // TO-DO: REMOVE MENTOR INFORMATION? OR CHANGE STATUS TO "REJECTED" OR "DELETED" TO KEEP THE INFORMATION? return `This action removes a #${id} mentor`; } + + // ------------------------------ + // MENTOR DASHBOARD + // ------------------------------ + + async getMyDashboard(mentorId: number) { + try { + // Get statistics + const statistics = await this.getMyStatistics(mentorId); + + // Get upcoming sessions + const upcomingSessions = await this.getMyUpcomingSessions(mentorId); + + // Get mentees + const mentees = await this.getMyMentees(mentorId); + + return { statistics, upcomingSessions, mentees }; + } catch (error) { + if (error instanceof NotFoundException) { + throw error; + } + throw new InternalServerErrorException( + `Error getting mentor dashboard: ${error.message}`, + ); + } + } + + async getMyStatistics(mentorId: number) { + try { + // Get current month and previous month dates + const now = new Date(); + const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); + const previousMonthStart = new Date( + now.getFullYear(), + now.getMonth() - 1, + 1, + ); + const previousMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0); + + // Get all sessions for this mentor + const allSessions = await this.prisma.mentorshipSessions.findMany({ + where: { + mentorId: mentorId, + }, + select: { + dateStart: true, + dateEnd: true, + createdAt: true, + }, + }); + + // Calculate total minutes mentored + let totalMinutesMentored = 0; + allSessions.forEach((session) => { + const duration = + new Date(session.dateEnd).getTime() - + new Date(session.dateStart).getTime(); + totalMinutesMentored += Math.floor(duration / (1000 * 60)); // Convert to minutes + }); + + // Calculate current month minutes + const currentMonthSessions = allSessions.filter( + (session) => new Date(session.createdAt) >= currentMonthStart, + ); + let currentMonthMinutes = 0; + currentMonthSessions.forEach((session) => { + const duration = + new Date(session.dateEnd).getTime() - + new Date(session.dateStart).getTime(); + currentMonthMinutes += Math.floor(duration / (1000 * 60)); + }); + + // Calculate previous month minutes + const previousMonthSessions = allSessions.filter( + (session) => + new Date(session.createdAt) >= previousMonthStart && + new Date(session.createdAt) <= previousMonthEnd, + ); + let previousMonthMinutes = 0; + previousMonthSessions.forEach((session) => { + const duration = + new Date(session.dateEnd).getTime() - + new Date(session.dateStart).getTime(); + previousMonthMinutes += Math.floor(duration / (1000 * 60)); + }); + + // Calculate minutes difference percentage + const minutesDiff = + previousMonthMinutes > 0 + ? ((currentMonthMinutes - previousMonthMinutes) / + previousMonthMinutes) * + 100 + : 0; + + // Get mentees count + const menteesCount = await this.prisma.matchedMentorMentee.count({ + where: { + mentorId: mentorId, + status: 'APPROVED', + }, + }); + + // Get previous month mentees count + const previousMonthMentees = await this.prisma.matchedMentorMentee.count({ + where: { + mentorId: mentorId, + status: 'APPROVED', + createdAt: { + gte: previousMonthStart, + lte: previousMonthEnd, + }, + }, + }); + + // Calculate mentees difference percentage + const menteesDiff = + previousMonthMentees > 0 + ? ((menteesCount - previousMonthMentees) / previousMonthMentees) * 100 + : 0; + + // Get live sessions count (total sessions) + const liveSessionsCount = allSessions.length; + + // Get previous month sessions count + const previousMonthSessionsCount = previousMonthSessions.length; + + // Calculate sessions difference percentage + const sessionsDiff = + previousMonthSessionsCount > 0 + ? ((liveSessionsCount - previousMonthSessionsCount) / + previousMonthSessionsCount) * + 100 + : 0; + + return { + minutesMentored: totalMinutesMentored, + minutesMentoredDiff: Math.round(minutesDiff * 100) / 100, // Round to 2 decimal places + mentees: menteesCount, + menteesDiff: Math.round(menteesDiff * 100) / 100, + liveSessions: liveSessionsCount, + liveSessionsDiff: Math.round(sessionsDiff * 100) / 100, + }; + } catch (error) { + if (error instanceof NotFoundException) { + throw error; + } + throw new InternalServerErrorException( + `Error getting mentor statistics: ${error.message}`, + ); + } + } + + async getMyUpcomingSessions(mentorId: number) { + try { + // Get upcoming sessions (sessions that haven't started yet) + const upcomingSessions = await this.prisma.mentorshipSessions.findMany({ + where: { + mentorId: mentorId, + dateStart: { + gt: new Date(), // Sessions that start in the future + }, + }, + include: { + mentee: { + select: { + first_name: true, + last_name: true, + picture_upload_link: true, + profession: true, + }, + }, + }, + orderBy: { + dateStart: 'asc', // Order by start date + }, + take: 10, // Limit to 10 upcoming sessions + }); + + // Get the last session for each mentee to calculate "last met" + const upcomingSessionsWithLastMet = await Promise.all( + upcomingSessions.map(async (session) => { + // Find the last completed session with this mentee + const lastSession = await this.prisma.mentorshipSessions.findFirst({ + where: { + mentorId: mentorId, + menteeId: session.menteeId, + dateEnd: { + lt: new Date(), // Sessions that have ended + }, + }, + orderBy: { + dateEnd: 'desc', + }, + }); + + return { + id: session.id, + avatar: session.mentee.picture_upload_link, + mentee: `${session.mentee.first_name} ${session.mentee.last_name}`, + lastMet: lastSession ? lastSession.dateEnd.toISOString() : null, + profession: session.mentee.profession || 'Not specified', + link: session.link || '', + }; + }), + ); + + return upcomingSessionsWithLastMet; + } catch (error) { + if (error instanceof NotFoundException) { + throw error; + } + throw new InternalServerErrorException( + `Error getting upcoming sessions: ${error.message}`, + ); + } + } + + async getMyMentees(mentorId: number) { + try { + // Get all mentees matched with this mentor + const mentees = await this.prisma.matchedMentorMentee.findMany({ + where: { + mentorId: mentorId, + }, + include: { + mentee: { + select: { + first_name: true, + last_name: true, + email: true, + profession: true, + }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + }); + + // Transform the data to match the MyMentees entity + const transformedMentees = mentees.map((matching) => ({ + id: matching.id, + mentee: `${matching.mentee.first_name} ${matching.mentee.last_name}`, + email: matching.mentee.email, + profession: matching.mentee.profession || 'Not specified', + status: matching.status, + })); + + return transformedMentees; + } catch (error) { + if (error instanceof NotFoundException) { + throw error; + } + throw new InternalServerErrorException( + `Error getting mentees: ${error.message}`, + ); + } + } } diff --git a/packages/frontend/src/features/mentorship/api/mentorship-api.ts b/packages/frontend/src/features/mentorship/api/mentorship-api.ts index 804896a..2846279 100644 --- a/packages/frontend/src/features/mentorship/api/mentorship-api.ts +++ b/packages/frontend/src/features/mentorship/api/mentorship-api.ts @@ -6,7 +6,11 @@ import { PendingApplicationResponse, AdminMentorshipDashboardTotals, MentorshipAdmin, - Mentee + Mentee, + MyMentorDashboard, + MentorStatistics, + MentorUpcomingSessions, + MyMentees } from '../types'; export const mentorshipApi = { @@ -19,7 +23,7 @@ export const mentorshipApi = { apiMethods.post('/mentees', data), getMentor: (mentorId: number) => - apiMethods.get(`/mentors/${mentorId}`), + apiMethods.get(`/mentors/application-id/${mentorId}`), getPendingApplications: () => apiMethods.get(`/users/pending-applications`), @@ -36,9 +40,23 @@ export const mentorshipApi = { apiMethods.get(`/admin/mentorship/mentee-applications`), updateMentorStatus: (mentorId: number, status: string) => - apiMethods.put(`/mentors/${mentorId}`, { status }), + apiMethods.put(`/mentors/application-id/${mentorId}`, { + status + }), // Add mentee status update method updateMenteeStatus: (menteeId: number, status: string) => - apiMethods.put(`/mentees/${menteeId}`, { status }) + apiMethods.put(`/mentees/${menteeId}`, { status }), + + // New mentor dashboard endpoints + getMyDashboard: () => + apiMethods.get('/mentors/my-dashboard'), + + getMyStatistics: () => + apiMethods.get('/mentors/my-statistics'), + + getMyUpcomingSessions: () => + apiMethods.get('/mentors/my-upcoming-sessions'), + + getMyMentees: () => apiMethods.get('/mentors/my-mentees') }; diff --git a/packages/frontend/src/features/mentorship/components/mentor-dashboard.tsx b/packages/frontend/src/features/mentorship/components/mentor-dashboard.tsx index 9691b11..db7b4a3 100644 --- a/packages/frontend/src/features/mentorship/components/mentor-dashboard.tsx +++ b/packages/frontend/src/features/mentorship/components/mentor-dashboard.tsx @@ -7,12 +7,12 @@ import { MentorshipSection } from './common/mentorship-section'; import { MentorshipIcons } from './icons'; import { DataTable } from './table/data-table'; import { sessionsColumns } from './table/sessions-columns'; -import { menteesData } from './table/data'; import { menteesColumns } from './table/mentees-column'; import MentorCard from './common/mentor-card'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { MentorModal } from './common/modals/mentor-modal'; -// import { useMentorshipStore } from '@/features/mentorship/store'; +import { useMentorshipStore } from '../store'; +import { MenteeStatus } from '../types'; interface ProfileData { firstName: string; @@ -27,12 +27,42 @@ interface ProfileData { const MentorDashboard = () => { const { user } = useUserStore(); - // const { mentor } = useMentorshipStore(); + const { + // mentorDashboard, + mentorStatistics, + mentorUpcomingSessions, + mentorMentees, + getMyDashboard, + getMyStatistics, + getMyUpcomingSessions, + getMyMentees, + isLoading, + error + } = useMentorshipStore(); + const [isModalOpen, setIsModalOpen] = useState(false); const [selectedProfile, setSelectedProfile] = useState( null ); + useEffect(() => { + // Fetch all dashboard data when component mounts + const fetchDashboardData = async () => { + try { + await Promise.all([ + getMyDashboard(), + getMyStatistics(), + getMyUpcomingSessions(), + getMyMentees() + ]); + } catch (error) { + console.error('Error fetching dashboard data:', error); + } + }; + + fetchDashboardData(); + }, [getMyDashboard, getMyStatistics, getMyUpcomingSessions, getMyMentees]); + const mentorData: ProfileData = { firstName: user?.firstName || '', lastName: user?.lastName || '', @@ -49,6 +79,61 @@ const MentorDashboard = () => { setIsModalOpen(true); }; + // Transform upcoming sessions data for the table + const upcomingSessionsData = mentorUpcomingSessions.map((session) => ({ + id: session.id, + date: session.lastMet || new Date().toISOString(), // Add date + identity: { + firstName: session.mentee.split(' ')[0] || '', + lastName: session.mentee.split(' ').slice(1).join(' ') || '', + email: '', + avatar: session.avatar || '/profile/default-avatar.png' + }, + link: session.link, + profession: session.profession || 'Not specified', + lastSession: session.lastMet + ? new Date(session.lastMet).toLocaleDateString() + : 'Never', + nextSession: session.link ? 'Scheduled' : 'Not scheduled' + })); + + // Transform mentees data for the table + const menteesTableData = mentorMentees.map((mentee, index) => ({ + id: mentee.id, + menteeApplicationId: index, + identity: { + avatar: mentee.avatar || '/profile/default-avatar.png', + firstName: mentee.mentee.split(' ')[0] || '', + lastName: mentee.mentee.split(' ').slice(1).join(' ') || '', + email: mentee.email + }, + date: new Date().toISOString(), + profession: mentee.profession || 'Not specified', + status: mentee.status as MenteeStatus, // Cast to match MenteeStatus enum + email: mentee.email, + reason: '', + experience: '', + resume: '' + })); + + if (isLoading) { + return ( +
+
Loading dashboard...
+
+ ); + } + + if (error) { + return ( +
+
+ Error loading dashboard: {error} +
+
+ ); + } + return (
@@ -73,55 +158,71 @@ const MentorDashboard = () => { />
+
= 0 + ? 'Up from last month' + : 'Down from last month' + } + trendUp={(mentorStatistics?.minutesMentoredDiff || 0) >= 0} /> = 0 + ? 'Up from last month' + : 'Down from last month' + } + trendUp={(mentorStatistics?.menteesDiff || 0) >= 0} /> = 0 + ? 'Up from last month' + : 'Down from last month' + } + trendUp={(mentorStatistics?.liveSessionsDiff || 0) >= 0} />
-
+ +
Upcoming Sessions
- +
-
My Meentees
+
My Mentees
-
12
+
+ {mentorStatistics?.mentees || 0} +
@@ -129,7 +230,7 @@ const MentorDashboard = () => { { setSelectedProfile({ firstName: rowData.identity.firstName, diff --git a/packages/frontend/src/features/mentorship/components/table/data.ts b/packages/frontend/src/features/mentorship/components/table/data.ts index 88a12cf..ce07eaf 100644 --- a/packages/frontend/src/features/mentorship/components/table/data.ts +++ b/packages/frontend/src/features/mentorship/components/table/data.ts @@ -1,24 +1,26 @@ import { - Mentee, - MenteeStatus, MentorshipAdmin, + // Mentee, + // MenteeStatus, + // MentorshipAdmin, RatingsGroup } from '../../types'; export type Sessions = { - id: string; + id: number; identity: { avatar: string; firstName: string; lastName: string; email: string; }; + link: string; date: string; profession: string; }; export interface MentorRating { - id: string; + id: number; identity: { avatar: string; firstName: string; @@ -33,82 +35,82 @@ export interface MentorRating { ratingsGroup?: RatingsGroup; } -export const menteesData: Mentee[] = [ - { - id: '728ed52f', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com' - }, - date: '2024-01-01', - profession: 'Software Engineer', - status: MenteeStatus.PENDING, - email: '', - reason: '' - }, - { - id: '489e1d42', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Jane', - lastName: 'Doe', - email: 'jane.doe@example.com' - }, - date: '2024-01-02', - profession: 'Product Manager', - status: MenteeStatus.PENDING, - email: '', - reason: '' - }, - { - id: '489e1d42', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'FranK', - lastName: 'Doe', - email: 'fran@example.com' - }, - date: '2024-01-02', - profession: 'UX Designer', - status: MenteeStatus.PENDING, - email: '', - reason: '' - }, - { - id: '489e1d42', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Jones', - lastName: 'Doe', - email: 'jones@example.com' - }, - date: '2024-01-02', - profession: 'QA Engineer', - status: MenteeStatus.PENDING, - email: '', - reason: '' - }, - { - id: '489e1d42', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Liberty', - lastName: 'Doe', - email: 'liberty@example.com' - }, - date: '2024-01-02', - profession: 'Business Analyst', - status: MenteeStatus.PENDING, - email: '', - reason: '' - } -]; +// export const menteesData: Mentee[] = [ +// { +// id: 1, +// identity: { +// avatar: 'https://github.com/shadcn.png', +// firstName: 'John', +// lastName: 'Doe', +// email: 'john.doe@example.com' +// }, +// date: '2024-01-01', +// profession: 'Software Engineer', +// status: MenteeStatus.PENDING, +// email: '', +// reason: '' +// }, +// { +// id: 2, +// identity: { +// avatar: 'https://github.com/shadcn.png', +// firstName: 'Jane', +// lastName: 'Doe', +// email: 'jane.doe@example.com' +// }, +// date: '2024-01-02', +// profession: 'Product Manager', +// status: MenteeStatus.PENDING, +// email: '', +// reason: '' +// }, +// { +// id: 3, +// identity: { +// avatar: 'https://github.com/shadcn.png', +// firstName: 'FranK', +// lastName: 'Doe', +// email: 'fran@example.com' +// }, +// date: '2024-01-02', +// profession: 'UX Designer', +// status: MenteeStatus.PENDING, +// email: '', +// reason: '' +// }, +// { +// id: '489e1d42', +// identity: { +// avatar: 'https://github.com/shadcn.png', +// firstName: 'Jones', +// lastName: 'Doe', +// email: 'jones@example.com' +// }, +// date: '2024-01-02', +// profession: 'QA Engineer', +// status: MenteeStatus.PENDING, +// email: '', +// reason: '' +// }, +// { +// id: '489e1d42', +// identity: { +// avatar: 'https://github.com/shadcn.png', +// firstName: 'Liberty', +// lastName: 'Doe', +// email: 'liberty@example.com' +// }, +// date: '2024-01-02', +// profession: 'Business Analyst', +// status: MenteeStatus.PENDING, +// email: '', +// reason: '' +// } +// ]; export const mentorshipAdminData: MentorshipAdmin[] = [ { - id: '728ed52f', + id: 1, identity: { avatar: 'https://github.com/shadcn.png', firstName: 'John', @@ -127,7 +129,7 @@ export const mentorshipAdminData: MentorshipAdmin[] = [ ratings: 4 }, { - id: '489e1d42', + id: 2, identity: { avatar: 'https://github.com/shadcn.png', firstName: 'Sarah', @@ -146,7 +148,7 @@ export const mentorshipAdminData: MentorshipAdmin[] = [ ratings: 3 }, { - id: 'a45b9c23', + id: 3, identity: { avatar: 'https://github.com/shadcn.png', firstName: 'Michael', @@ -165,7 +167,7 @@ export const mentorshipAdminData: MentorshipAdmin[] = [ ratings: 4 }, { - id: 'b67d2e14', + id: 5, identity: { avatar: 'https://github.com/shadcn.png', firstName: 'Emma', @@ -182,221 +184,12 @@ export const mentorshipAdminData: MentorshipAdmin[] = [ lastSession: 'Jan 18, 2024', sessionsBooked: 3, ratings: 2 - }, - { - id: 'c89f3g25', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'David', - lastName: 'Garcia' - }, - experience: '10 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Product Manager', - email: 'd.garcia@example.com', - status: 'Approved', - capacity: '8', - availability: '10 hours/week', - lastSession: 'Jan 20, 2024', - sessionsBooked: 8, - ratings: 4 - }, - { - id: 'd12h4i36', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Lisa', - lastName: 'Taylor' - }, - experience: '7 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Frontend Developer', - email: 'lisa.t@example.com', - status: 'Pending', - capacity: '6', - availability: '8 hours/week', - lastSession: 'Jan 18, 2024', - sessionsBooked: 3, - ratings: 2 - }, - { - id: 'e34j5k47', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'James', - lastName: 'Anderson' - }, - experience: '9 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Backend Developer', - email: 'j.anderson@example.com', - status: 'Approved', - capacity: '8', - availability: '10 hours/week', - lastSession: 'Jan 20, 2024', - sessionsBooked: 8, - ratings: 4 - }, - { - id: 'f56l7m58', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Maria', - lastName: 'Rodriguez' - }, - experience: '11 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'System Architect', - email: 'm.rodriguez@example.com', - status: 'Pending', - capacity: '10', - availability: '12 hours/week', - lastSession: 'Jan 18, 2024', - sessionsBooked: 3, - ratings: 5 - }, - { - id: 'g78n9p69', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Robert', - lastName: 'Kim' - }, - experience: '13 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Cloud Engineer', - email: 'r.kim@example.com', - status: 'Approved', - capacity: '12', - availability: '14 hours/week', - lastSession: 'Jan 20, 2024', - sessionsBooked: 8, - ratings: 4 - }, - { - id: 'h90q1r70', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Anna', - lastName: 'Martinez' - }, - experience: '5 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Mobile Developer', - email: 'a.martinez@example.com', - status: 'Rejected', - capacity: '4', - availability: '6 hours/week', - lastSession: 'Jan 18, 2024', - sessionsBooked: 3, - ratings: 2 - }, - { - id: 'i12s3t81', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Thomas', - lastName: 'Brown' - }, - experience: '14 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Security Engineer', - email: 't.brown@example.com', - status: 'Approved', - capacity: '10', - availability: '12 hours/week', - lastSession: 'Jan 20, 2024', - sessionsBooked: 8, - ratings: 4 - }, - { - id: 'j34u5v92', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Sophie', - lastName: 'Lee' - }, - experience: '8 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'ML Engineer', - email: 's.lee@example.com', - status: 'Pending', - capacity: '6', - availability: '8 hours/week', - lastSession: 'Jan 18, 2024', - sessionsBooked: 3, - ratings: 5 - }, - { - id: 'k56w7x03', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Daniel', - lastName: 'White' - }, - experience: '6 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'QA Engineer', - email: 'd.white@example.com', - status: 'Approved', - capacity: '4', - availability: '6 hours/week', - lastSession: 'Jan 20, 2024', - sessionsBooked: 8, - ratings: 4 - }, - { - id: 'l78y9z14', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Rachel', - lastName: 'Clark' - }, - experience: '9 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Technical Lead', - email: 'r.clark@example.com', - status: 'Pending', - capacity: '6', - availability: '8 hours/week', - lastSession: 'Jan 18, 2024', - sessionsBooked: 3, - ratings: 2 - }, - { - id: 'm90a1b25', - identity: { - avatar: 'https://github.com/shadcn.png', - firstName: 'Kevin', - lastName: 'Patel' - }, - experience: '7 years', - experienceDescription: - 'Led multiple teams across various projects, mentored junior developers, and implemented enterprise-scale solutions.', - profession: 'Blockchain Developer', - email: 'k.patel@example.com', - status: 'Approved', - capacity: '6', - availability: '8 hours/week', - lastSession: 'Jan 20, 2024', - sessionsBooked: 8, - ratings: 4 } ]; export const mentorRatingData: MentorRating[] = [ { - id: 'm1', + id: 1, identity: { avatar: 'https://randomuser.me/api/portraits/women/24.jpg', firstName: 'Sarah', @@ -417,7 +210,7 @@ export const mentorRatingData: MentorRating[] = [ } }, { - id: 'm2', + id: 2, identity: { avatar: 'https://randomuser.me/api/portraits/men/32.jpg', firstName: 'Michael', @@ -438,7 +231,7 @@ export const mentorRatingData: MentorRating[] = [ } }, { - id: 'm3', + id: 3, identity: { avatar: 'https://randomuser.me/api/portraits/women/45.jpg', firstName: 'Priya', @@ -457,7 +250,7 @@ export const mentorRatingData: MentorRating[] = [ } }, { - id: 'm4', + id: 4, identity: { avatar: 'https://randomuser.me/api/portraits/men/67.jpg', firstName: 'James', @@ -478,7 +271,7 @@ export const mentorRatingData: MentorRating[] = [ } }, { - id: 'm5', + id: 5, identity: { avatar: 'https://randomuser.me/api/portraits/women/82.jpg', firstName: 'Emma', diff --git a/packages/frontend/src/features/mentorship/components/table/sessions-columns.tsx b/packages/frontend/src/features/mentorship/components/table/sessions-columns.tsx index 31ea46e..c9c4cae 100644 --- a/packages/frontend/src/features/mentorship/components/table/sessions-columns.tsx +++ b/packages/frontend/src/features/mentorship/components/table/sessions-columns.tsx @@ -7,7 +7,8 @@ import { AvatarFallback, AvatarImage } from '@/shared/components/ui/avatar'; -import { Button } from '@/shared/components/ui/button'; +// import { Button } from '@/shared/components/ui/button'; +import Link from 'next/link'; export const sessionsColumns: ColumnDef[] = [ { @@ -54,21 +55,23 @@ export const sessionsColumns: ColumnDef[] = [
), cell: ({ row }) => ( -
- +
) } diff --git a/packages/frontend/src/features/mentorship/store/index.ts b/packages/frontend/src/features/mentorship/store/index.ts index a78c25a..b73de78 100644 --- a/packages/frontend/src/features/mentorship/store/index.ts +++ b/packages/frontend/src/features/mentorship/store/index.ts @@ -8,7 +8,11 @@ import { PendingApplicationResponse, AdminMentorshipDashboardTotals, MentorshipAdmin, - Mentee + Mentee, + MyMentorDashboard, + MentorStatistics, + MentorUpcomingSessions, + MyMentees } from '../types'; interface MentorshipState { @@ -22,6 +26,11 @@ interface MentorshipState { adminMentorshipTotals: AdminMentorshipDashboardTotals; adminMentorApplications: MentorshipAdmin[]; adminMenteeApplications: Mentee[]; + // New mentor dashboard state + mentorDashboard: MyMentorDashboard | null; + mentorStatistics: MentorStatistics | null; + mentorUpcomingSessions: MentorUpcomingSessions[]; + mentorMentees: MyMentees[]; // Actions createMentor: (mentorData: FormData) => Promise; createMentee: (menteeData: FormData) => Promise; @@ -31,6 +40,11 @@ interface MentorshipState { getAdminMentorshipTotals: () => Promise; getAdminMentorApplications: () => Promise; getAdminMenteeApplications: () => Promise; + // New mentor dashboard actions + getMyDashboard: () => Promise; + getMyStatistics: () => Promise; + getMyUpcomingSessions: () => Promise; + getMyMentees: () => Promise; } export const useMentorshipStore = create()( @@ -41,6 +55,10 @@ export const useMentorshipStore = create()( isLoading: false, error: null, mentor: null, + mentorDashboard: null, + mentorStatistics: null, + mentorUpcomingSessions: [], + mentorMentees: [], createMentor: async (mentorData) => { set({ isLoading: true, error: null }); @@ -96,7 +114,6 @@ export const useMentorshipStore = create()( getAdminMentorApplications: async () => { const response = await mentorshipApi.getAdminMentorApplications(); - console.log('| - - - - - - > response in store:', response.data); set({ adminMentorApplications: response.data }); return response; }, @@ -107,21 +124,91 @@ export const useMentorshipStore = create()( return response; }, - fetchInterests: async () => { + // New mentor dashboard actions + getMyDashboard: async () => { set({ isLoading: true, error: null }); try { - const response = await mentorshipApi.getInterests(); + const response = await mentorshipApi.getMyDashboard(); + set({ + mentorDashboard: response.data, + isLoading: false + }); + return response; + } catch (error) { + set({ + error: error instanceof Error ? error.message : 'An error occurred', + isLoading: false + }); + throw error; + } + }, - const data = response.data; + getMyStatistics: async () => { + set({ isLoading: true, error: null }); + try { + const response = await mentorshipApi.getMyStatistics(); + set({ + mentorStatistics: response.data, + isLoading: false + }); + return response; + } catch (error) { + set({ + error: error instanceof Error ? error.message : 'An error occurred', + isLoading: false + }); + throw error; + } + }, - console.log('data in store:', data); + getMyUpcomingSessions: async () => { + set({ isLoading: true, error: null }); + try { + const response = await mentorshipApi.getMyUpcomingSessions(); + set({ + mentorUpcomingSessions: response.data, + isLoading: false + }); + return response; + } catch (error) { + set({ + error: error instanceof Error ? error.message : 'An error occurred', + isLoading: false + }); + throw error; + } + }, + + getMyMentees: async () => { + set({ isLoading: true, error: null }); + try { + const response = await mentorshipApi.getMyMentees(); + set({ + mentorMentees: response.data, + isLoading: false + }); + return response; + } catch (error) { + set({ + error: error instanceof Error ? error.message : 'An error occurred', + isLoading: false + }); + throw error; + } + }, - set({ interests: data, isLoading: false }); + fetchInterests: async () => { + set({ isLoading: true, error: null }); + try { + const response = await mentorshipApi.getInterests(); + set({ interests: response.data, isLoading: false }); + return response.data; } catch (error) { set({ error: error instanceof Error ? error.message : 'An error occurred', isLoading: false }); + throw error; } } }), diff --git a/packages/frontend/src/features/mentorship/types/index.ts b/packages/frontend/src/features/mentorship/types/index.ts index 6a317b1..fec6b45 100644 --- a/packages/frontend/src/features/mentorship/types/index.ts +++ b/packages/frontend/src/features/mentorship/types/index.ts @@ -65,7 +65,7 @@ export type RatingsGroup = { }; export interface MentorshipAdmin { - id: string; + id: number; mentorApplicationId?: number; // Add this field identity: { avatar: string; @@ -88,7 +88,7 @@ export interface MentorshipAdmin { } export type Mentee = { - id: string; + id: number; menteeApplicationId?: number; // Add this field identity: { avatar: string; @@ -104,3 +104,37 @@ export type Mentee = { experience?: string; resume?: string; // Add this field }; + +// New types for mentor dashboard +export interface MentorStatistics { + minutesMentored: number; + minutesMentoredDiff: number; + mentees: number; + menteesDiff: number; + liveSessions: number; + liveSessionsDiff: number; +} + +export interface MentorUpcomingSessions { + id: number; + avatar?: string; + mentee: string; + lastMet?: string; + profession?: string; + link: string; +} + +export interface MyMentees { + id: number; + avatar?: string; + mentee: string; + email: string; + profession?: string; + status: string; +} + +export interface MyMentorDashboard { + statistics: MentorStatistics; + upcomingSessions: MentorUpcomingSessions[]; + mentees: MyMentees[]; +}