SarahXia0405 commited on
Commit
f0949b9
·
verified ·
1 Parent(s): 9aa8914

Update web/src/components/sidebar/SavedChatSection.tsx

Browse files
web/src/components/sidebar/SavedChatSection.tsx CHANGED
@@ -1,215 +1,10 @@
1
- // web/src/components/sidebar/SavedChatSection.tsx
2
- import React, { useEffect, useRef, useState } from "react";
3
- import { MessageSquare, Trash2, Edit2, Check, X as XIcon } from "lucide-react";
4
  import { Card } from "../ui/card";
5
  import { Button } from "../ui/button";
6
- import { Input } from "../ui/input";
 
7
  import type { SavedChat } from "../../App";
8
 
9
- // ================================
10
- // Saved Chat Item (moved from LeftSidebar, unchanged)
11
- // ================================
12
- function SavedChatItem({
13
- chat,
14
- onLoadChat,
15
- onDeleteSavedChat,
16
- onRenameSavedChat,
17
- }: {
18
- chat: SavedChat;
19
- onLoadChat: (chat: SavedChat) => void;
20
- onDeleteSavedChat: (id: string) => void;
21
- onRenameSavedChat?: (id: string, newTitle: string) => void;
22
- }) {
23
- const [isEditing, setIsEditing] = useState(false);
24
- const [editTitle, setEditTitle] = useState(chat.title);
25
- const [originalTitle, setOriginalTitle] = useState(chat.title);
26
- const inputRef = useRef<HTMLInputElement>(null);
27
- const cancelButtonRef = useRef<HTMLButtonElement>(null);
28
- const saveButtonRef = useRef<HTMLButtonElement>(null);
29
-
30
- useEffect(() => {
31
- if (!isEditing) {
32
- setOriginalTitle(chat.title);
33
- setEditTitle(chat.title);
34
- }
35
- }, [chat.title, isEditing]);
36
-
37
- const handleStartEdit = (e: React.MouseEvent) => {
38
- e.preventDefault();
39
- e.stopPropagation();
40
- setOriginalTitle(chat.title);
41
- setEditTitle(chat.title);
42
- setIsEditing(true);
43
- setTimeout(() => inputRef.current?.focus(), 0);
44
- };
45
-
46
- const handleSaveEdit = (e: React.MouseEvent) => {
47
- e.preventDefault();
48
- e.stopPropagation();
49
- if (editTitle.trim() && onRenameSavedChat) {
50
- onRenameSavedChat(chat.id, editTitle.trim());
51
- setIsEditing(false);
52
- }
53
- };
54
-
55
- const handleCancelEdit = (e: React.MouseEvent) => {
56
- e.preventDefault();
57
- e.stopPropagation();
58
- setEditTitle(originalTitle);
59
- setIsEditing(false);
60
- inputRef.current?.blur();
61
- };
62
-
63
- const handleInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
64
- const relatedTarget = e.relatedTarget as HTMLElement;
65
- if (
66
- relatedTarget &&
67
- (cancelButtonRef.current?.contains(relatedTarget) ||
68
- saveButtonRef.current?.contains(relatedTarget))
69
- ) {
70
- return;
71
- }
72
- if (editTitle.trim() && editTitle !== originalTitle && onRenameSavedChat) {
73
- onRenameSavedChat(chat.id, editTitle.trim());
74
- }
75
- setIsEditing(false);
76
- };
77
-
78
- const handleKeyDown = (e: React.KeyboardEvent) => {
79
- if (e.key === "Enter") {
80
- e.preventDefault();
81
- e.stopPropagation();
82
- if (editTitle.trim() && onRenameSavedChat) {
83
- onRenameSavedChat(chat.id, editTitle.trim());
84
- setIsEditing(false);
85
- }
86
- } else if (e.key === "Escape") {
87
- e.preventDefault();
88
- e.stopPropagation();
89
- setEditTitle(originalTitle);
90
- setIsEditing(false);
91
- }
92
- };
93
-
94
- return (
95
- <Card
96
- className="p-3 cursor-pointer hover:bg-muted/50 transition-all bg-muted/30"
97
- onClick={() => !isEditing && onLoadChat(chat)}
98
- >
99
- <div className="flex items-start gap-2">
100
- <MessageSquare className="h-3.5 w-3.5 mt-0.5 flex-shrink-0 text-muted-foreground" />
101
- <div className="flex-1 min-w-0">
102
- <div className="flex items-start justify-between gap-2">
103
- {isEditing ? (
104
- <Input
105
- ref={inputRef}
106
- value={editTitle}
107
- onChange={(e) => setEditTitle(e.target.value)}
108
- onKeyDown={handleKeyDown}
109
- onClick={(e) => e.stopPropagation()}
110
- onBlur={handleInputBlur}
111
- className="h-auto text-sm font-medium px-2 py-1 border border-border bg-background focus-visible:ring-2 focus-visible:ring-ring flex-1"
112
- style={{ height: "auto" }}
113
- />
114
- ) : (
115
- <h4
116
- className="text-sm font-medium truncate flex-1 cursor-text"
117
- onDoubleClick={(e) => {
118
- e.preventDefault();
119
- e.stopPropagation();
120
- handleStartEdit(e);
121
- }}
122
- onClick={(e) => e.stopPropagation()}
123
- title="Double click to rename"
124
- >
125
- {chat.title}
126
- </h4>
127
- )}
128
-
129
- <div className="flex items-center gap-1 flex-shrink-0">
130
- {isEditing ? (
131
- <>
132
- <Button
133
- ref={saveButtonRef}
134
- variant="ghost"
135
- size="icon"
136
- className="h-5 w-5 flex-shrink-0 hover:bg-green-500/20"
137
- onClick={(e) => {
138
- e.preventDefault();
139
- e.stopPropagation();
140
- handleSaveEdit(e);
141
- }}
142
- title="Save"
143
- type="button"
144
- >
145
- <Check className="h-3 w-3" />
146
- </Button>
147
- <Button
148
- ref={cancelButtonRef}
149
- variant="ghost"
150
- size="icon"
151
- className="h-5 w-5 flex-shrink-0 hover:bg-destructive/20"
152
- onClick={(e) => {
153
- e.preventDefault();
154
- e.stopPropagation();
155
- handleCancelEdit(e);
156
- }}
157
- title="Cancel"
158
- type="button"
159
- >
160
- <XIcon className="h-3 w-3" />
161
- </Button>
162
- </>
163
- ) : (
164
- <>
165
- {onRenameSavedChat && (
166
- <Button
167
- variant="ghost"
168
- size="icon"
169
- className="h-5 w-5 flex-shrink-0 hover:bg-muted"
170
- onClick={handleStartEdit}
171
- title="Rename chat"
172
- >
173
- <Edit2 className="h-3 w-3" />
174
- </Button>
175
- )}
176
- <Button
177
- variant="ghost"
178
- size="icon"
179
- className="h-5 w-5 flex-shrink-0 hover:bg-destructive/20"
180
- onClick={(e) => {
181
- e.stopPropagation();
182
- onDeleteSavedChat(chat.id);
183
- }}
184
- title="Delete chat"
185
- >
186
- <Trash2 className="h-3 w-3" />
187
- </Button>
188
- </>
189
- )}
190
- </div>
191
- </div>
192
-
193
- <p className="text-xs text-muted-foreground mt-1">
194
- {chat.chatMode === "ask"
195
- ? "Ask"
196
- : chat.chatMode === "review"
197
- ? "Review"
198
- : "Quiz"}{" "}
199
- • {chat.timestamp.toLocaleDateString()}
200
- </p>
201
- <p className="text-xs text-muted-foreground/70 mt-1">
202
- {chat.messages.length} message{chat.messages.length !== 1 ? "s" : ""}
203
- </p>
204
- </div>
205
- </div>
206
- </Card>
207
- );
208
- }
209
-
210
- // ================================
211
- // Saved Chat Section (NEW)
212
- // ================================
213
  export function SavedChatSection({
214
  isLoggedIn,
215
  savedChats,
@@ -226,29 +21,54 @@ export function SavedChatSection({
226
  if (!isLoggedIn) return null;
227
 
228
  return (
 
229
  <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
230
- <div className="p-4 border-b border-border flex-shrink-0">
231
- <h3 className="text-base font-medium">Saved Chat</h3>
 
 
 
 
232
  </div>
233
 
234
- {/* ONLY this area scrolls */}
235
- <div className="flex-1 min-h-0 panelScroll p-4">
 
 
236
  {savedChats.length === 0 ? (
237
- <div className="text-sm text-muted-foreground text-center py-4">
238
- <MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" />
239
- <p>No saved chats yet</p>
240
- <p className="text-xs mt-1">Save conversations to view them here</p>
241
- </div>
 
242
  ) : (
243
  <div className="space-y-2">
244
  {savedChats.map((chat) => (
245
- <SavedChatItem
246
- key={chat.id}
247
- chat={chat}
248
- onLoadChat={onLoadChat}
249
- onDeleteSavedChat={onDeleteSavedChat}
250
- onRenameSavedChat={onRenameSavedChat}
251
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  ))}
253
  </div>
254
  )}
 
1
+ import React from "react";
 
 
2
  import { Card } from "../ui/card";
3
  import { Button } from "../ui/button";
4
+ import { Separator } from "../ui/separator";
5
+ import { Bookmark, Trash2 } from "lucide-react";
6
  import type { SavedChat } from "../../App";
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  export function SavedChatSection({
9
  isLoggedIn,
10
  savedChats,
 
21
  if (!isLoggedIn) return null;
22
 
23
  return (
24
+ // ✅ Eat remaining height in LeftSidebar
25
  <div className="flex-1 min-h-0 flex flex-col overflow-hidden">
26
+ {/* fixed header */}
27
+ <div className="px-4 py-3 flex-shrink-0">
28
+ <div className="flex items-center gap-2">
29
+ <Bookmark className="h-4 w-4" />
30
+ <div className="font-semibold">Saved Chat</div>
31
+ </div>
32
  </div>
33
 
34
+ <Separator className="flex-shrink-0" />
35
+
36
+ {/* ✅ ONLY THIS SCROLLS */}
37
+ <div className="flex-1 min-h-0 overflow-y-auto px-4 py-4">
38
  {savedChats.length === 0 ? (
39
+ <Card className="p-8 text-center">
40
+ <div className="text-sm text-muted-foreground">No saved chats yet</div>
41
+ <div className="text-xs text-muted-foreground mt-1">
42
+ Save conversations to view them here
43
+ </div>
44
+ </Card>
45
  ) : (
46
  <div className="space-y-2">
47
  {savedChats.map((chat) => (
48
+ <Card key={chat.id} className="p-3">
49
+ <div className="flex items-start justify-between gap-2">
50
+ <button
51
+ type="button"
52
+ onClick={() => onLoadChat(chat)}
53
+ className="text-left flex-1"
54
+ >
55
+ <div className="text-sm font-medium leading-5">{chat.title}</div>
56
+ <div className="text-xs text-muted-foreground mt-1">
57
+ {chat.timestamp.toLocaleString()}
58
+ </div>
59
+ </button>
60
+
61
+ <Button
62
+ variant="ghost"
63
+ size="icon"
64
+ className="h-8 w-8"
65
+ onClick={() => onDeleteSavedChat(chat.id)}
66
+ title="Delete"
67
+ >
68
+ <Trash2 className="h-4 w-4" />
69
+ </Button>
70
+ </div>
71
+ </Card>
72
  ))}
73
  </div>
74
  )}