Skip to content

Commit 9fbdf02

Browse files
y4nderclaude
andauthored
FAC-WEB feat: show faculty profile picture on evaluation and analytics screens (#143)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5c61f79 commit 9fbdf02

7 files changed

Lines changed: 108 additions & 15 deletions

File tree

app/(dashboard)/student/courses/[courseId]/evaluation/_components/evaluation-form.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type EvaluationFormProps = {
3030
courseName: string;
3131
courseShortname: string;
3232
facultyName: string;
33+
facultyProfilePicture?: string | null;
3334
enrollmentSectionName?: string;
3435
activeVersion: { id: string };
3536
faculty: { id: string };
@@ -44,6 +45,7 @@ export function EvaluationForm({
4445
courseName,
4546
courseShortname,
4647
facultyName,
48+
facultyProfilePicture,
4749
enrollmentSectionName,
4850
activeVersion,
4951
faculty,
@@ -151,6 +153,7 @@ export function EvaluationForm({
151153
courseName={courseName}
152154
courseShortname={courseShortname}
153155
facultyName={facultyName}
156+
facultyProfilePicture={facultyProfilePicture}
154157
enrollmentSectionName={enrollmentSectionName}
155158
>
156159
<div className="mt-5 sm:mt-8">
@@ -165,6 +168,7 @@ export function EvaluationForm({
165168
defaultValues={defaultValues}
166169
onChange={handleChange}
167170
facultyName={facultyName}
171+
facultyProfilePicture={facultyProfilePicture}
168172
progressTrailing={
169173
draftStatus !== "idle" ? (
170174
<Badge

app/(dashboard)/student/courses/[courseId]/evaluation/_components/evaluation-page-shell.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Link from "next/link";
22
import { ArrowLeft } from "lucide-react";
33

4+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
45
import { Button } from "@/components/ui/button";
56
import { Card, CardContent } from "@/components/ui/card";
67

@@ -9,14 +10,27 @@ type EvaluationPageShellProps = {
910
courseName?: string;
1011
courseShortname?: string;
1112
facultyName?: string;
13+
facultyProfilePicture?: string | null;
1214
enrollmentSectionName?: string;
1315
};
1416

17+
function getInitials(fullName: string) {
18+
return (
19+
fullName
20+
.split(" ")
21+
.map((part) => part[0])
22+
.join("")
23+
.slice(0, 2)
24+
.toUpperCase() || "F"
25+
);
26+
}
27+
1528
export function EvaluationPageShell({
1629
children,
1730
courseName,
1831
courseShortname,
1932
facultyName,
33+
facultyProfilePicture,
2034
enrollmentSectionName,
2135
}: EvaluationPageShellProps) {
2236
return (
@@ -60,9 +74,19 @@ export function EvaluationPageShell({
6074
<p className="text-[0.62rem] font-semibold uppercase tracking-[0.22em] text-muted-foreground sm:text-[0.68rem]">
6175
Instructor
6276
</p>
63-
<p className="font-playfair text-base font-semibold leading-tight text-foreground sm:mt-2 sm:text-xl">
64-
{facultyName}
65-
</p>
77+
<div className="flex items-center gap-2.5 sm:mt-2 sm:flex-col sm:gap-2">
78+
<Avatar size="default" className="border border-border/70">
79+
{facultyProfilePicture ? (
80+
<AvatarImage src={facultyProfilePicture} alt={facultyName} />
81+
) : null}
82+
<AvatarFallback className="bg-slate-100 text-xs font-semibold text-slate-700">
83+
{getInitials(facultyName ?? "")}
84+
</AvatarFallback>
85+
</Avatar>
86+
<p className="font-playfair text-base font-semibold leading-tight text-foreground sm:text-xl">
87+
{facultyName}
88+
</p>
89+
</div>
6690
</div>
6791
<div className="flex flex-col items-start gap-0.5 border-t border-border/60 px-4 py-3.5 text-left sm:items-center sm:gap-0 sm:px-6 sm:py-5 sm:text-center md:border-t-0 md:border-l">
6892
<p className="text-[0.62rem] font-semibold uppercase tracking-[0.22em] text-muted-foreground sm:text-[0.68rem]">

app/(dashboard)/student/courses/[courseId]/evaluation/page.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function FacultyEvaluationPage() {
2121
courseName={ctx?.courseName}
2222
courseShortname={ctx?.courseShortname}
2323
facultyName={ctx?.facultyName}
24+
facultyProfilePicture={ctx?.faculty.profilePicture}
2425
enrollmentSectionName={ctx?.enrollmentSectionName}
2526
>
2627
<EvaluationLoading message={result.message} />
@@ -53,12 +54,14 @@ export default function FacultyEvaluationPage() {
5354
}
5455

5556
if (result.status === "no-version") {
56-
const { courseName, courseShortname, facultyName, enrollmentSectionName } = result.context;
57+
const { courseName, courseShortname, facultyName, faculty, enrollmentSectionName } =
58+
result.context;
5759
return (
5860
<EvaluationPageShell
5961
courseName={courseName}
6062
courseShortname={courseShortname}
6163
facultyName={facultyName}
64+
facultyProfilePicture={faculty.profilePicture}
6265
enrollmentSectionName={enrollmentSectionName}
6366
>
6467
<EvaluationError message="No active questionnaire is available for evaluation at this time." />
@@ -73,12 +76,19 @@ export default function FacultyEvaluationPage() {
7376
courseName={context.courseName}
7477
courseShortname={context.courseShortname}
7578
facultyName={context.facultyName}
79+
facultyProfilePicture={context.faculty.profilePicture}
7680
enrollmentSectionName={context.enrollmentSectionName}
7781
>
7882
<EvaluationAlreadySubmitted submittedAt={submittedAt} />
7983
</EvaluationPageShell>
8084
);
8185
}
8286

83-
return <EvaluationForm courseId={courseId} {...result.data} />;
87+
return (
88+
<EvaluationForm
89+
courseId={courseId}
90+
{...result.data}
91+
facultyProfilePicture={result.data.faculty.profilePicture}
92+
/>
93+
);
8494
}

features/faculty-analytics/components/faculty-report-screen.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useMemo, useState } from "react";
44
import Link from "next/link";
55

6+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
67
import { Button } from "@/components/ui/button";
78
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
89
import { APP_ROLES, type AppRole } from "@/constants/roles";
@@ -49,6 +50,17 @@ const REPORT_EXPORT_ROLES: ReadonlySet<AppRole> = new Set([
4950
APP_ROLES.CAMPUS_HEAD,
5051
]);
5152

53+
function getInitials(fullName: string) {
54+
return (
55+
fullName
56+
.split(" ")
57+
.map((part) => part[0])
58+
.join("")
59+
.slice(0, 2)
60+
.toUpperCase() || "F"
61+
);
62+
}
63+
5264
export function FacultyReportScreen({ facultyId }: FacultyReportScreenProps) {
5365
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
5466
const viewModel = useFacultyReportDetailViewModel({ facultyId });
@@ -147,9 +159,22 @@ export function FacultyReportScreen({ facultyId }: FacultyReportScreenProps) {
147159
{/* Title row — mirrors the dashboard / faculty-list header pattern */}
148160
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
149161
<div className="min-w-0">
150-
<h1 className="font-playfair text-2xl font-semibold tracking-tight sm:text-3xl">
151-
{viewModel.report.faculty.name}
152-
</h1>
162+
<div className="flex items-center gap-3">
163+
<Avatar size="lg" className="border border-border/70">
164+
{viewModel.report.faculty.profilePicture ? (
165+
<AvatarImage
166+
src={viewModel.report.faculty.profilePicture}
167+
alt={viewModel.report.faculty.name}
168+
/>
169+
) : null}
170+
<AvatarFallback className="bg-slate-100 text-sm font-semibold text-slate-700">
171+
{getInitials(viewModel.report.faculty.name)}
172+
</AvatarFallback>
173+
</Avatar>
174+
<h1 className="font-playfair text-2xl font-semibold tracking-tight sm:text-3xl">
175+
{viewModel.report.faculty.name}
176+
</h1>
177+
</div>
153178
<p className="mt-3 max-w-3xl text-sm text-muted-foreground">
154179
Review per-question faculty evaluation results for{" "}
155180
<span className="font-medium text-foreground">{viewModel.semesterLabel}</span>.

features/faculty-analytics/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export type FacultyReportCommentsQuery = FacultyReportQuery & {
253253
export type FacultyReportFacultyDto = {
254254
id: string;
255255
name: string;
256+
profilePicture?: string | null;
256257
};
257258

258259
export type FacultyReportSemesterDto = {

features/questionnaires/components/form/questionnaire-form-progress.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { memo } from "react";
44

5+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
56
import { Progress } from "@/components/ui/progress";
67

78
import { useAnsweredCountFor, useHasQualitativeContent } from "./questionnaire-form-store";
@@ -13,13 +14,27 @@ type QuestionnaireFormProgressProps = {
1314
trailing?: React.ReactNode;
1415
/** When provided, displays the faculty being evaluated above the progress row. */
1516
facultyName?: string;
17+
/** Optional profile picture URL for the faculty being evaluated. */
18+
facultyProfilePicture?: string | null;
1619
};
1720

21+
function getInitials(fullName: string) {
22+
return (
23+
fullName
24+
.split(" ")
25+
.map((part) => part[0])
26+
.join("")
27+
.slice(0, 2)
28+
.toUpperCase() || "F"
29+
);
30+
}
31+
1832
function QuestionnaireFormProgressBase({
1933
requiredIds,
2034
qualitativeRequired,
2135
trailing,
2236
facultyName,
37+
facultyProfilePicture,
2338
}: QuestionnaireFormProgressProps) {
2439
const answeredCount = useAnsweredCountFor(requiredIds);
2540
const hasQualitativeContent = useHasQualitativeContent();
@@ -32,13 +47,23 @@ function QuestionnaireFormProgressBase({
3247
return (
3348
<div className="sticky top-0 z-20 -mx-4 space-y-2 border-b border-border/60 bg-background/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80 sm:mx-0">
3449
{facultyName && (
35-
<div className="flex flex-col">
36-
<span className="text-[0.62rem] font-semibold uppercase tracking-[0.22em] text-muted-foreground sm:text-[0.68rem]">
37-
Evaluating
38-
</span>
39-
<span className="truncate font-playfair text-sm font-semibold leading-tight text-foreground sm:text-base">
40-
{facultyName}
41-
</span>
50+
<div className="flex items-center gap-2.5">
51+
<Avatar size="sm" className="border border-border/70">
52+
{facultyProfilePicture ? (
53+
<AvatarImage src={facultyProfilePicture} alt={facultyName} />
54+
) : null}
55+
<AvatarFallback className="bg-slate-100 text-[0.65rem] font-semibold text-slate-700">
56+
{getInitials(facultyName)}
57+
</AvatarFallback>
58+
</Avatar>
59+
<div className="flex min-w-0 flex-col">
60+
<span className="text-[0.62rem] font-semibold uppercase tracking-[0.22em] text-muted-foreground sm:text-[0.68rem]">
61+
Evaluating
62+
</span>
63+
<span className="truncate font-playfair text-sm font-semibold leading-tight text-foreground sm:text-base">
64+
{facultyName}
65+
</span>
66+
</div>
4267
</div>
4368
)}
4469
<div className="flex items-center justify-between gap-3 text-sm">

features/questionnaires/components/form/questionnaire-form-renderer.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type QuestionnaireFormRendererProps = {
3030
progressTrailing?: React.ReactNode;
3131
/** Faculty being evaluated; surfaced inside the sticky progress header. */
3232
facultyName?: string;
33+
/** Optional faculty profile picture URL for the progress header avatar. */
34+
facultyProfilePicture?: string | null;
3335
};
3436

3537
/**
@@ -52,6 +54,7 @@ export function QuestionnaireFormRenderer({
5254
onChange,
5355
progressTrailing,
5456
facultyName,
57+
facultyProfilePicture,
5558
}: QuestionnaireFormRendererProps) {
5659
const isInteractive = mode === "interactive";
5760

@@ -94,6 +97,7 @@ export function QuestionnaireFormRenderer({
9497
qualitativeRequired={model.qualitative.required}
9598
trailing={progressTrailing}
9699
facultyName={facultyName}
100+
facultyProfilePicture={facultyProfilePicture}
97101
/>
98102
)}
99103

0 commit comments

Comments
 (0)