anhkhoiphan commited on
Commit
1835767
·
1 Parent(s): 76b92d9

Sửa để gửi được ảnh và pdf

Browse files
src/App.jsx CHANGED
@@ -250,7 +250,7 @@ function App() {
250
  const id = data.id;
251
  const tempId = data.tempId;
252
 
253
- if (!roomId || !content) return;
254
 
255
  console.log("[App] newMessage parsed:", { roomId, id, tempId, author, senderId });
256
 
@@ -276,6 +276,7 @@ function App() {
276
  isPinned: data.is_pinned || false,
277
  isOwn: isOwnMessage,
278
  tempId,
 
279
  },
280
  }),
281
  );
 
250
  const id = data.id;
251
  const tempId = data.tempId;
252
 
253
+ if (!roomId || (!content && !data.attachments?.length)) return;
254
 
255
  console.log("[App] newMessage parsed:", { roomId, id, tempId, author, senderId });
256
 
 
276
  isPinned: data.is_pinned || false,
277
  isOwn: isOwnMessage,
278
  tempId,
279
+ attachments: data.attachments || [],
280
  },
281
  }),
282
  );
src/components/ChatArea.jsx CHANGED
@@ -28,6 +28,7 @@ import {
28
  clearRoomUnreadCount,
29
  } from "../store/slices/spaceSlice";
30
  import socketService from "../services/socket.service";
 
31
 
32
  function ChatArea({
33
  activeView,
@@ -507,6 +508,18 @@ function ChatArea({
507
  });
508
  })();
509
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  return {
511
  id: msg.id,
512
  sender: senderName,
@@ -522,6 +535,8 @@ function ChatArea({
522
  pending: msg.pending || false,
523
  is_read: msg.is_read,
524
  created_at: msg.created_at,
 
 
525
  };
526
  });
527
 
@@ -942,6 +957,41 @@ function ChatArea({
942
  const msgTempId = `temp-${Date.now()}`;
943
  const contentTrimmed = content.trim();
944
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
945
  // Optimistic UI - same format as DM
946
  const optimisticMsg = {
947
  id: msgTempId,
@@ -955,6 +1005,7 @@ function ChatArea({
955
  created_at: new Date().toISOString(),
956
  is_read: false,
957
  pending: true,
 
958
  };
959
 
960
  // Track sending message
@@ -980,9 +1031,9 @@ function ChatArea({
980
  }, 10000);
981
 
982
  dispatch(addMessage({ roomId: room, message: optimisticMsg }));
983
-
984
- // Send with tempId so BE can echo it back
985
- socketService.sendMessage({ roomId: room, content: contentTrimmed, tempId: msgTempId });
986
 
987
  if (files?.length > 0 && /@StudyBot/i.test(contentTrimmed)) {
988
  callStudyBotWithFile(contentTrimmed, files[0], null);
 
28
  clearRoomUnreadCount,
29
  } from "../store/slices/spaceSlice";
30
  import socketService from "../services/socket.service";
31
+ import { messageService } from "../services/message.service";
32
 
33
  function ChatArea({
34
  activeView,
 
508
  });
509
  })();
510
 
511
+ const rawAttachments = msg.attachments || [];
512
+ const normalizedAttachments = rawAttachments.map((att) => ({
513
+ name: att.file_name || att.name || "file",
514
+ type: (att.file_type || att.type || "").includes("image")
515
+ ? "image"
516
+ : (att.file_name || att.name || "").toLowerCase().endsWith(".pdf") ||
517
+ (att.file_type || att.type || "").includes("pdf")
518
+ ? "pdf"
519
+ : "other",
520
+ url: att.file_url || att.url || att.previewUrl || "",
521
+ }));
522
+
523
  return {
524
  id: msg.id,
525
  sender: senderName,
 
535
  pending: msg.pending || false,
536
  is_read: msg.is_read,
537
  created_at: msg.created_at,
538
+ hasAttachment: normalizedAttachments.length > 0,
539
+ attachments: normalizedAttachments,
540
  };
541
  });
542
 
 
957
  const msgTempId = `temp-${Date.now()}`;
958
  const contentTrimmed = content.trim();
959
 
960
+ // Upload file first if present
961
+ let attachment = null;
962
+ let optimisticAttachments = [];
963
+ if (files?.length > 0) {
964
+ const fileObj = files[0];
965
+ // Show preview immediately in optimistic message
966
+ if (fileObj.preview || fileObj.file) {
967
+ const previewUrl = fileObj.preview || URL.createObjectURL(fileObj.file);
968
+ const isPdf =
969
+ fileObj.file?.type === "application/pdf" ||
970
+ (fileObj.name || "").toLowerCase().endsWith(".pdf");
971
+ optimisticAttachments = [{
972
+ file_name: fileObj.name || fileObj.file?.name || "file",
973
+ file_type: fileObj.file?.type || "",
974
+ file_url: previewUrl,
975
+ previewUrl,
976
+ }];
977
+ }
978
+ try {
979
+ const uploadRes = await messageService.uploadFileToStorage(fileObj.file, { roomId: room });
980
+ const fileData = uploadRes.data?.data || uploadRes.data;
981
+ attachment = {
982
+ file_url: fileData.fileUrl || fileData.file_url,
983
+ file_name: fileData.fileName || fileData.file_name || fileObj.name,
984
+ file_type: fileData.fileType || fileData.file_type,
985
+ file_size: fileData.fileSize || fileData.file_size,
986
+ mime_type: fileData.mimeType || fileData.mime_type,
987
+ };
988
+ // Update optimistic preview with real URL
989
+ optimisticAttachments = [{ ...attachment }];
990
+ } catch (err) {
991
+ console.error("[ChatArea] File upload failed:", err);
992
+ }
993
+ }
994
+
995
  // Optimistic UI - same format as DM
996
  const optimisticMsg = {
997
  id: msgTempId,
 
1005
  created_at: new Date().toISOString(),
1006
  is_read: false,
1007
  pending: true,
1008
+ attachments: optimisticAttachments,
1009
  };
1010
 
1011
  // Track sending message
 
1031
  }, 10000);
1032
 
1033
  dispatch(addMessage({ roomId: room, message: optimisticMsg }));
1034
+
1035
+ // Send with tempId (and attachment if uploaded) so BE can echo it back
1036
+ socketService.sendMessage({ roomId: room, content: contentTrimmed, tempId: msgTempId, attachment });
1037
 
1038
  if (files?.length > 0 && /@StudyBot/i.test(contentTrimmed)) {
1039
  callStudyBotWithFile(contentTrimmed, files[0], null);
src/services/message.service.js CHANGED
@@ -36,6 +36,16 @@ export const messageService = {
36
  });
37
  },
38
 
 
 
 
 
 
 
 
 
 
 
39
  // Pin a message
40
  pinMessage: (roomId, messageId) =>
41
  api.post(`/rooms/${roomId}/messages/${messageId}/pin`),
 
36
  });
37
  },
38
 
39
+ // Upload file to storage and get back URL (POST /files)
40
+ uploadFileToStorage: (file, options = {}) => {
41
+ const formData = new FormData();
42
+ formData.append("file", file);
43
+ if (options.roomId) formData.append("roomId", options.roomId);
44
+ return api.post("/files", formData, {
45
+ headers: { "Content-Type": "multipart/form-data" },
46
+ });
47
+ },
48
+
49
  // Pin a message
50
  pinMessage: (roomId, messageId) =>
51
  api.post(`/rooms/${roomId}/messages/${messageId}/pin`),