Spaces:
Sleeping
Sleeping
Create CourseInfoSection.tsx
Browse files
web/src/components/sidebar/CourseInfoSection.tsx
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useMemo } from "react";
|
| 2 |
+
import { Separator } from "../ui/separator";
|
| 3 |
+
|
| 4 |
+
import type { Workspace } from "../../App";
|
| 5 |
+
import type { CourseDirectoryItem } from "../../lib/courseDirectory";
|
| 6 |
+
|
| 7 |
+
function gmailComposeLink(email: string, subject?: string, body?: string) {
|
| 8 |
+
const to = `to=${encodeURIComponent(email)}`;
|
| 9 |
+
const su = subject ? `&su=${encodeURIComponent(subject)}` : "";
|
| 10 |
+
const bd = body ? `&body=${encodeURIComponent(body)}` : "";
|
| 11 |
+
return `https://mail.google.com/mail/?view=cm&fs=1&${to}${su}${bd}`;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export function CourseInfoSection({
|
| 15 |
+
currentWorkspaceId,
|
| 16 |
+
workspaces,
|
| 17 |
+
selectedCourse,
|
| 18 |
+
availableCourses,
|
| 19 |
+
}: {
|
| 20 |
+
currentWorkspaceId: string;
|
| 21 |
+
workspaces: Workspace[];
|
| 22 |
+
selectedCourse: string;
|
| 23 |
+
availableCourses: CourseDirectoryItem[];
|
| 24 |
+
}) {
|
| 25 |
+
const currentWorkspace = useMemo(
|
| 26 |
+
() => workspaces.find((w) => w.id === currentWorkspaceId),
|
| 27 |
+
[workspaces, currentWorkspaceId]
|
| 28 |
+
);
|
| 29 |
+
|
| 30 |
+
const courseInfo: CourseDirectoryItem | null = useMemo(() => {
|
| 31 |
+
const sel = (selectedCourse || "").trim();
|
| 32 |
+
if (sel) {
|
| 33 |
+
const hitById = availableCourses.find((c) => c.id === sel);
|
| 34 |
+
if (hitById) return hitById;
|
| 35 |
+
|
| 36 |
+
const hitByName = availableCourses.find((c) => c.name === sel);
|
| 37 |
+
if (hitByName) return hitByName;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
const wsCourse = (currentWorkspace as any)?.courseInfo as
|
| 41 |
+
| { id?: string; name?: string }
|
| 42 |
+
| undefined;
|
| 43 |
+
|
| 44 |
+
const wsId = wsCourse?.id?.trim();
|
| 45 |
+
if (wsId) return availableCourses.find((c) => c.id === wsId) ?? (wsCourse as any);
|
| 46 |
+
|
| 47 |
+
const wsName = wsCourse?.name?.trim();
|
| 48 |
+
if (wsName) return availableCourses.find((c) => c.name === wsName) ?? (wsCourse as any);
|
| 49 |
+
|
| 50 |
+
return null;
|
| 51 |
+
}, [availableCourses, currentWorkspace, selectedCourse]);
|
| 52 |
+
|
| 53 |
+
if (!courseInfo) return null;
|
| 54 |
+
|
| 55 |
+
const instructorName = courseInfo?.instructor?.name ?? "N/A";
|
| 56 |
+
const instructorEmail = courseInfo?.instructor?.email?.trim() || "";
|
| 57 |
+
const taName = courseInfo?.teachingAssistant?.name ?? "N/A";
|
| 58 |
+
const taEmail = courseInfo?.teachingAssistant?.email?.trim() || "";
|
| 59 |
+
|
| 60 |
+
return (
|
| 61 |
+
<div className="w-full">
|
| 62 |
+
<div className="px-4 pt-4 pb-3 space-y-2">
|
| 63 |
+
<div className="font-semibold text-base">{courseInfo.name}</div>
|
| 64 |
+
|
| 65 |
+
<div className="text-sm text-muted-foreground">
|
| 66 |
+
Instructor:
|
| 67 |
+
{instructorEmail ? (
|
| 68 |
+
<a
|
| 69 |
+
href={gmailComposeLink(
|
| 70 |
+
instructorEmail,
|
| 71 |
+
`[Clare] Question about ${courseInfo.name}`,
|
| 72 |
+
`Hi ${instructorName},\n\nI have a question about ${courseInfo.name}:\n\n(Write your question here)\n\nThanks,\n`
|
| 73 |
+
)}
|
| 74 |
+
target="_blank"
|
| 75 |
+
rel="noopener noreferrer"
|
| 76 |
+
className="text-primary hover:underline"
|
| 77 |
+
>
|
| 78 |
+
{instructorName}
|
| 79 |
+
</a>
|
| 80 |
+
) : (
|
| 81 |
+
<span className="text-muted-foreground/60">{instructorName}</span>
|
| 82 |
+
)}
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div className="text-sm text-muted-foreground">
|
| 86 |
+
TA:
|
| 87 |
+
{taEmail ? (
|
| 88 |
+
<a
|
| 89 |
+
href={gmailComposeLink(
|
| 90 |
+
taEmail,
|
| 91 |
+
`[Clare] Help request for ${courseInfo.name}`,
|
| 92 |
+
`Hi ${taName},\n\nI need help with ${courseInfo.name}:\n\n(Write your question here)\n\nThanks,\n`
|
| 93 |
+
)}
|
| 94 |
+
target="_blank"
|
| 95 |
+
rel="noopener noreferrer"
|
| 96 |
+
className="text-primary hover:underline"
|
| 97 |
+
>
|
| 98 |
+
{taName}
|
| 99 |
+
</a>
|
| 100 |
+
) : (
|
| 101 |
+
<span className="text-muted-foreground/60">{taName}</span>
|
| 102 |
+
)}
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
{/* 固定颜色分割线 */}
|
| 107 |
+
<Separator className="bg-[#ECECF1]" />
|
| 108 |
+
</div>
|
| 109 |
+
);
|
| 110 |
+
}
|