SarahXia0405 commited on
Commit
8fdae75
·
verified ·
1 Parent(s): b7878a0

Update web/src/components/LeftSidebar.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/LeftSidebar.tsx +114 -18
web/src/components/LeftSidebar.tsx CHANGED
@@ -69,14 +69,101 @@ function formatSub(ts: any) {
69
  }
70
  }
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  export function LeftSidebar(props: Props) {
73
  const {
74
  savedChats,
75
  onLoadChat,
76
  onDeleteSavedChat,
77
  onRenameSavedChat,
 
 
 
 
78
  } = props;
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  // ===== rename state =====
81
  const [editingId, setEditingId] = useState<string | null>(null);
82
  const [draftTitle, setDraftTitle] = useState<string>("");
@@ -110,7 +197,19 @@ export function LeftSidebar(props: Props) {
110
 
111
  return (
112
  <div className="h-full w-full flex flex-col min-h-0">
113
- {/* You can keep other sections above; here we keep minimal structure */}
 
 
 
 
 
 
 
 
 
 
 
 
114
  <div className="flex-shrink-0 px-4 pt-4 pb-3">
115
  <div className="flex items-center gap-2">
116
  <Bookmark className="h-4 w-4" />
@@ -118,24 +217,25 @@ export function LeftSidebar(props: Props) {
118
  </div>
119
  </div>
120
 
121
- <div className="px-4 flex-shrink-0">
122
- <Separator />
123
- </div>
124
-
125
- {/* list */}
126
- <div className="flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3">
127
  {sortedChats.length === 0 ? (
128
- <div className="text-sm text-muted-foreground">No saved chats yet.</div>
 
 
 
 
 
 
129
  ) : (
130
  sortedChats.map((chat) => {
131
  const isEditing = editingId === chat.id;
132
- const sub = formatSub(chat.timestamp);
133
 
134
  return (
135
- <div
136
- key={chat.id}
137
- className="rounded-xl border border-border bg-card px-4 py-3"
138
- >
139
  <div className="flex items-start justify-between gap-3">
140
  {/* left */}
141
  <div className="min-w-0 flex-1">
@@ -186,11 +286,7 @@ export function LeftSidebar(props: Props) {
186
  title="Open saved chat"
187
  >
188
  <div className="font-semibold truncate">{chat.title || "Untitled chat"}</div>
189
- {sub ? (
190
- <div className="text-xs text-muted-foreground mt-1">
191
- {sub}
192
- </div>
193
- ) : null}
194
  </button>
195
  )}
196
  </div>
 
69
  }
70
  }
71
 
72
+ function buildMailto(email: string, subject: string, body?: string) {
73
+ const s = encodeURIComponent(subject);
74
+ const b = body ? `&body=${encodeURIComponent(body)}` : "";
75
+ return `mailto:${encodeURIComponent(email)}?subject=${s}${b}`;
76
+ }
77
+
78
+ function CourseInfoHeader({
79
+ course,
80
+ }: {
81
+ course: CourseInfo;
82
+ }) {
83
+ const instructorHref = buildMailto(
84
+ course.instructor.email,
85
+ `[Clare] Question about ${course.name}`,
86
+ `Hi ${course.instructor.name},\n\nI have a question about ${course.name}:\n\n(Write your question here)\n\nThanks,\n`
87
+ );
88
+
89
+ const taHref = buildMailto(
90
+ course.teachingAssistant.email,
91
+ `[Clare] Help request for ${course.name}`,
92
+ `Hi ${course.teachingAssistant.name},\n\nI need help with ${course.name}:\n\n(Write your question here)\n\nThanks,\n`
93
+ );
94
+
95
+ return (
96
+ <div className="px-4 pt-4 pb-3">
97
+ <div className="space-y-1">
98
+ <div className="text-base font-semibold text-foreground truncate">{course.name}</div>
99
+
100
+ <div className="text-sm text-muted-foreground">
101
+ Instructor:{" "}
102
+ <a
103
+ href={instructorHref}
104
+ className="text-primary hover:underline"
105
+ title={`Email ${course.instructor.name}`}
106
+ onClick={(e) => e.stopPropagation()}
107
+ >
108
+ {course.instructor.name}
109
+ </a>
110
+ </div>
111
+
112
+ <div className="text-sm text-muted-foreground">
113
+ TA:{" "}
114
+ <a
115
+ href={taHref}
116
+ className="text-primary hover:underline"
117
+ title={`Email ${course.teachingAssistant.name}`}
118
+ onClick={(e) => e.stopPropagation()}
119
+ >
120
+ {course.teachingAssistant.name}
121
+ </a>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ );
126
+ }
127
+
128
  export function LeftSidebar(props: Props) {
129
  const {
130
  savedChats,
131
  onLoadChat,
132
  onDeleteSavedChat,
133
  onRenameSavedChat,
134
+ currentWorkspaceId,
135
+ workspaces,
136
+ selectedCourse,
137
+ availableCourses,
138
  } = props;
139
 
140
+ // =========================
141
+ // Course info must ALWAYS show
142
+ // Logic kept consistent with App.tsx:
143
+ // - If workspace is group/course and has courseInfo -> use it
144
+ // - Else fallback to selectedCourse (My Space) -> availableCourses match
145
+ // =========================
146
+ const currentCourseInfo: CourseInfo | null = useMemo(() => {
147
+ const ws = workspaces?.find((w) => w.id === currentWorkspaceId);
148
+
149
+ // group course workspace preferred
150
+ if (ws?.type === "group" && ws?.category === "course") {
151
+ const ci = (ws as any)?.courseInfo as CourseInfo | undefined;
152
+ if (ci?.id && ci?.name) return ci;
153
+
154
+ // fallback: match by courseName if courseInfo missing
155
+ const name = (ws as any)?.courseName as string | undefined;
156
+ if (name) {
157
+ const byName = availableCourses?.find((c) => c.name === name);
158
+ if (byName) return byName;
159
+ }
160
+ }
161
+
162
+ // individual workspace (or anything else): match selectedCourse id
163
+ const byId = availableCourses?.find((c) => c.id === selectedCourse);
164
+ return byId || (availableCourses?.[0] ?? null);
165
+ }, [availableCourses, currentWorkspaceId, selectedCourse, workspaces]);
166
+
167
  // ===== rename state =====
168
  const [editingId, setEditingId] = useState<string | null>(null);
169
  const [draftTitle, setDraftTitle] = useState<string>("");
 
197
 
198
  return (
199
  <div className="h-full w-full flex flex-col min-h-0">
200
+ {/* =========================
201
+ 0) Course Info (ALWAYS)
202
+ ========================= */}
203
+ {currentCourseInfo ? <CourseInfoHeader course={currentCourseInfo} /> : null}
204
+
205
+ {/* Divider between course info and Saved Chat section */}
206
+ <div className="px-4 flex-shrink-0">
207
+ <Separator />
208
+ </div>
209
+
210
+ {/* =========================
211
+ 1) Saved Chat header
212
+ ========================= */}
213
  <div className="flex-shrink-0 px-4 pt-4 pb-3">
214
  <div className="flex items-center gap-2">
215
  <Bookmark className="h-4 w-4" />
 
217
  </div>
218
  </div>
219
 
220
+ {/* =========================
221
+ 2) Saved Chat list
222
+ ========================= */}
223
+ <div className="flex-1 min-h-0 overflow-y-auto px-4 pb-3 space-y-3">
 
 
224
  {sortedChats.length === 0 ? (
225
+ <div className="text-sm text-muted-foreground text-center py-10">
226
+ <div className="mx-auto mb-3 h-10 w-10 rounded-full bg-muted flex items-center justify-center">
227
+ <Bookmark className="h-5 w-5 text-muted-foreground" />
228
+ </div>
229
+ <div className="font-medium">No saved chats yet</div>
230
+ <div className="text-xs text-muted-foreground mt-1">Save conversations to view them here</div>
231
+ </div>
232
  ) : (
233
  sortedChats.map((chat) => {
234
  const isEditing = editingId === chat.id;
235
+ const sub = formatSub((chat as any).timestamp || (chat as any).savedAt || (chat as any).createdAt);
236
 
237
  return (
238
+ <div key={chat.id} className="rounded-xl border border-border bg-card px-4 py-3">
 
 
 
239
  <div className="flex items-start justify-between gap-3">
240
  {/* left */}
241
  <div className="min-w-0 flex-1">
 
286
  title="Open saved chat"
287
  >
288
  <div className="font-semibold truncate">{chat.title || "Untitled chat"}</div>
289
+ {sub ? <div className="text-xs text-muted-foreground mt-1">{sub}</div> : null}
 
 
 
 
290
  </button>
291
  )}
292
  </div>