diff --git a/src/pages/MentorDashboard.tsx b/src/pages/MentorDashboard.tsx index dbdf0b3c..fe78cd6f 100644 --- a/src/pages/MentorDashboard.tsx +++ b/src/pages/MentorDashboard.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect, useState } from "react"; import { useRole } from "@/contexts/RoleContext"; import { useAuth } from "@/contexts/useAuth"; @@ -8,14 +7,7 @@ import SessionCard from "@/components/SessionCard"; import { MentorshipMilestones } from "@/components/mentorship/MentorshipMilestones"; import { sessionJoinPath } from "@/lib/sessionJoinPath"; import type { Session } from "@/types"; - -type MentorSessionRow = { - id: number; - title: string | null; - scheduled_at: string | null; - duration_minutes: number | null; - status: string | null; -}; +import { toSessionCardModel, type MentorSessionRow } from "./mentorSessionCard"; type MentorProfile = { name: string | null; @@ -24,6 +16,7 @@ type MentorProfile = { rating: number | null; }; + const toSessionCardModel = (session: MentorSessionRow): Session => { const scheduledAt = session.scheduled_at ? new Date(session.scheduled_at) : null; const id = String(session.id); @@ -72,16 +65,20 @@ const MentorDashboard = () => { setProfile(profileData); } - // Fetch Upcoming Mentor Sessions + // Fetch Upcoming Mentor Sessions with booked learner profiles const { data: sessionData } = await supabase .from("sessions") - .select("id,title,scheduled_at,duration_minutes,status") + .select( + "id,title,scheduled_at,duration_minutes,status,student_id, student:profiles!student_id(id, name, avatar_url)" + ) .eq("status", "scheduled") .eq("mentor_id", user.id) .limit(4); if (sessionData) { - setUpcomingSessions(sessionData.map(toSessionCardModel)); + setUpcomingSessions( + (sessionData as MentorSessionRow[]).map(toSessionCardModel) + ); } } catch (err) { console.error("Failed to fetch mentor dashboard data", err); @@ -161,4 +158,3 @@ const MentorDashboard = () => { }; export default MentorDashboard; - diff --git a/src/pages/mentorSessionCard.test.ts b/src/pages/mentorSessionCard.test.ts new file mode 100644 index 00000000..04aa19ba --- /dev/null +++ b/src/pages/mentorSessionCard.test.ts @@ -0,0 +1,77 @@ +import { describe, it, expect } from "vitest"; +import { toSessionCardModel } from "./mentorSessionCard"; + +describe("toSessionCardModel", () => { + it("maps distinct learner profiles onto different session cards", () => { + const alice = toSessionCardModel({ + id: 1, + title: "React fundamentals", + scheduled_at: "2026-08-10T15:00:00.000Z", + duration_minutes: 45, + status: "scheduled", + student_id: "learner-alice", + student: { + id: "learner-alice", + name: "Alice Chen", + avatar_url: "https://cdn.example/alice.png", + }, + }); + + const bob = toSessionCardModel({ + id: 2, + title: "System design", + scheduled_at: "2026-08-11T16:00:00.000Z", + duration_minutes: 60, + status: "scheduled", + student_id: "learner-bob", + student: { + id: "learner-bob", + name: "Bob Patel", + avatar_url: "https://cdn.example/bob.png", + }, + }); + + expect(alice.peerId).toBe("learner-alice"); + expect(alice.peerName).toBe("Alice Chen"); + expect(alice.peerAvatar).toBe("https://cdn.example/alice.png"); + + expect(bob.peerId).toBe("learner-bob"); + expect(bob.peerName).toBe("Bob Patel"); + expect(bob.peerAvatar).toBe("https://cdn.example/bob.png"); + + expect(alice.peerName).not.toBe(bob.peerName); + }); + + it("falls back to placeholder learner details when the profile is missing", () => { + const session = toSessionCardModel({ + id: 3, + title: null, + scheduled_at: null, + duration_minutes: null, + status: "scheduled", + student_id: null, + student: null, + }); + + expect(session.peerId).toBe(""); + expect(session.peerName).toBe("Learner"); + expect(session.peerAvatar).toBe("/placeholder.svg"); + expect(session.subject).toBe("Mentorship session"); + }); + + it("uses student_id when the nested profile relation is empty", () => { + const session = toSessionCardModel({ + id: 4, + title: "Algorithms", + scheduled_at: "2026-08-12T12:00:00.000Z", + duration_minutes: 30, + status: "scheduled", + student_id: "orphan-learner", + student: null, + }); + + expect(session.peerId).toBe("orphan-learner"); + expect(session.peerName).toBe("Learner"); + expect(session.peerAvatar).toBe("/placeholder.svg"); + }); +}); diff --git a/src/pages/mentorSessionCard.ts b/src/pages/mentorSessionCard.ts new file mode 100644 index 00000000..ac9eb0cb --- /dev/null +++ b/src/pages/mentorSessionCard.ts @@ -0,0 +1,46 @@ +import type { Session } from "@/types"; + +type LearnerProfile = { + id: string; + name: string | null; + avatar_url: string | null; +}; + +export type MentorSessionRow = { + id: number; + title: string | null; + scheduled_at: string | null; + duration_minutes: number | null; + status: string | null; + student_id: string | null; + student: LearnerProfile | LearnerProfile[] | null; +}; + +const resolveLearner = (session: MentorSessionRow): LearnerProfile | null => { + if (Array.isArray(session.student)) { + return session.student[0] ?? null; + } + return session.student ?? null; +}; + +export const toSessionCardModel = (session: MentorSessionRow): Session => { + const scheduledAt = session.scheduled_at ? new Date(session.scheduled_at) : null; + const learner = resolveLearner(session); + + return { + id: String(session.id), + peerId: learner?.id ?? session.student_id ?? "", + peerName: learner?.name?.trim() || "Learner", + peerAvatar: learner?.avatar_url || "/placeholder.svg", + subject: session.title || "Mentorship session", + date: scheduledAt ? scheduledAt.toLocaleDateString() : "Not scheduled", + time: scheduledAt + ? scheduledAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) + : "", + duration: session.duration_minutes ?? 60, + status: + session.status === "ended" || session.status === "completed" + ? "completed" + : "upcoming", + }; +};