fix: collaboration

deskripsi:
- fix use server menjadi API
- di terapkan pada main detail , room chat
This commit is contained in:
2025-05-27 17:12:38 +08:00
parent e4b55fd201
commit bdff760f70
10 changed files with 981 additions and 44 deletions

View File

@@ -0,0 +1,198 @@
// components/ChatWindow.tsx
import {
Box,
Button,
Flex,
ScrollArea,
Text,
TextInput
} from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import React, { useRef, useState } from "react";
interface Message {
id: string;
content: string;
senderId: string;
createdAt: Date;
}
// components/ChatWindow.tsx
type Props = {
initialMessages: Message[];
currentUserId: string;
loadMoreMessages: () => Promise<Message[]>;
sendMessage: (content: string) => Promise<void>;
};
const ChatWindow: React.FC<Props> = ({
initialMessages,
currentUserId,
loadMoreMessages,
sendMessage,
}) => {
const [messages, setMessages] = useState<Message[]>(initialMessages);
const [newMessage, setNewMessage] = useState("");
const scrollAreaRef = useRef<HTMLDivElement>(null);
const messageContainerRef = useRef<HTMLDivElement>(null);
const [showNewMessageIndicator, setShowNewMessageIndicator] = useState(false);
const [isAtBottom, setIsAtBottom] = useState(true);
// Cek apakah scroll berada di bawah
const checkIfAtBottom = () => {
if (!scrollAreaRef.current || !messageContainerRef.current) return true;
const container = scrollAreaRef.current;
const offsetBottom =
messageContainerRef.current.clientHeight -
container.clientHeight -
container.scrollTop;
return offsetBottom < 50; // toleransi 50px
};
// Scroll ke bawah
const scrollToBottom = () => {
if (messageContainerRef.current && scrollAreaRef.current) {
scrollAreaRef.current.scrollTop =
messageContainerRef.current.clientHeight -
scrollAreaRef.current.clientHeight;
}
};
// Handle infinite scroll up
const handleScroll = async () => {
if (!scrollAreaRef.current) return;
const { scrollTop } = scrollAreaRef.current;
if (scrollTop === 0) {
const newMessages = await loadMoreMessages();
setMessages((prev) => [...newMessages, ...prev]);
}
// Cek apakah user di bottom
const atBottom = checkIfAtBottom();
setIsAtBottom(atBottom);
setShowNewMessageIndicator(!atBottom);
};
// Kirim pesan
const handleSendMessage = async () => {
if (!newMessage.trim()) return;
// Simpan pesan baru ke state
const tempMessage: Message = {
id: Math.random().toString(36).substr(2, 9),
content: newMessage,
senderId: currentUserId,
createdAt: new Date(),
};
setMessages((prev) => [...prev, tempMessage]);
// Reset input
setNewMessage("");
// Kirim ke server
await sendMessage(newMessage);
// Jika user di bawah, scroll otomatis
if (checkIfAtBottom()) {
scrollToBottom();
}
};
// Scroll otomatis saat komponen pertama kali di-render
useShallowEffect(() => {
scrollToBottom();
}, []);
// Tambah event listener scroll
useShallowEffect(() => {
const scrollArea = scrollAreaRef.current;
if (!scrollArea) return;
scrollArea.addEventListener("scroll", handleScroll);
return () => {
scrollArea.removeEventListener("scroll", handleScroll);
};
}, []);
// Scroll otomatis jika user di bottom dan ada pesan baru
useShallowEffect(() => {
if (isAtBottom) {
scrollToBottom();
}
}, [messages.length]);
return (
<Box h="100%" display="flex" style={{ flexDirection: "column" }}>
{/* Area Chat */}
<ScrollArea
ref={scrollAreaRef}
style={{ flex: 1, position: "relative" }}
onScrollPositionChange={() => {}}
>
<div ref={messageContainerRef}>
{messages.map((msg) => (
<Box
key={msg.id}
p="sm"
style={{
textAlign: msg.senderId === currentUserId ? "right" : "left",
}}
>
<Text
bg={msg.senderId === currentUserId ? "blue" : "gray"}
c="white"
px="md"
py="xs"
// radius="lg"
display="inline-block"
>
{msg.content}
</Text>
</Box>
))}
</div>
</ScrollArea>
{/* Notifikasi Pesan Baru */}
{!isAtBottom && showNewMessageIndicator && (
<Button
onClick={() => {
scrollToBottom();
setShowNewMessageIndicator(false);
}}
color="blue"
style={{
position: "absolute",
bottom: 80,
left: "50%",
transform: "translateX(-50%)",
}}
>
Ada pesan baru
</Button>
)}
{/* Input Pengiriman */}
<Flex gap="sm" p="md" align="center">
<TextInput
value={newMessage}
onChange={(e) => setNewMessage(e.currentTarget.value)}
placeholder="Ketik pesan..."
onKeyDown={(e) => e.key === "Enter" && handleSendMessage()}
// flex={1}
style={{
flex: 1,
}}
/>
<Button onClick={handleSendMessage}>Kirim</Button>
</Flex>
</Box>
);
};
export default ChatWindow;

View File

@@ -0,0 +1,83 @@
// app/chat/[id]/page.tsx
"use client";
import React, { useState, useCallback } from "react";
import ChatWindow from "./chat_window";
import { useShallowEffect } from "@mantine/hooks";
interface Message {
id: string;
content: string;
senderId: string;
createdAt: Date;
}
type Props = {
params: {
id: string;
};
};
export default function ChatPage({ params }: Props) {
const chatId = params.id;
const currentUserId = "user_123"; // Ganti dengan dynamic user ID dari auth
const [messages, setMessages] = useState<Message[]>([]);
const [cursor, setCursor] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(true);
// Load initial messages
const loadInitialMessages = useCallback(async () => {
const res = await fetch(`/api/messages?chatId=${chatId}`);
const data = await res.json();
if (data.length > 0) {
setCursor(data[0]?.id); // Simpan cursor dari pesan pertama
} else {
setHasMore(false);
}
setMessages(data);
}, [chatId]);
// Load more messages (scroll up)
const loadMoreMessages = useCallback(async () => {
if (!hasMore || !cursor) return [];
const res = await fetch(`/api/messages?chatId=${chatId}&cursor=${cursor}`);
const data = await res.json();
if (data.length < 20) setHasMore(false);
if (data.length > 0) setCursor(data[0]?.id);
setMessages((prev) => [...data, ...prev]);
return data;
}, [chatId, cursor, hasMore]);
// Send message
const sendMessage = useCallback(
async (content: string) => {
await fetch("/api/messages", {
method: "POST",
body: JSON.stringify({
content,
senderId: currentUserId,
chatId,
}),
});
},
[chatId, currentUserId]
);
// Load pesan saat pertama kali
useShallowEffect(() => {
loadInitialMessages();
}, [loadInitialMessages]);
return (
<ChatWindow
initialMessages={messages}
currentUserId={currentUserId}
loadMoreMessages={loadMoreMessages}
sendMessage={sendMessage}
/>
);
}

View File

@@ -1,15 +1,16 @@
import { funGetUserIdByToken } from "@/app_modules/_global/fun/get";
import adminColab_getOneRoomChatById from "@/app_modules/admin/colab/fun/get/get_one_room_chat_by_id";
import { Colab_GroupChatView } from "@/app_modules/colab";
import Colab_NewGroupChatView from "@/app_modules/colab/detail/group/new_detail_group";
import colab_getMessageByRoomId from "@/app_modules/colab/fun/get/room_chat/get_message_by_room_id";
import { user_getOneByUserId } from "@/app_modules/home/fun/get/get_one_user_by_id";
import _ from "lodash";
export const dynamic = "force-dynamic";
export default async function Page({ params }: { params: { id: string } }) {
const roomId = params.id;
const userLoginId = await funGetUserIdByToken();
// const userLoginId = await funGetUserIdByToken();
// const dataUserLogin = await user_getOneByUserId(userLoginId as string);
const getData = (await adminColab_getOneRoomChatById({ roomId: roomId }))
.data;
@@ -18,26 +19,18 @@ export default async function Page({ params }: { params: { id: string } }) {
"ProjectCollaboration_AnggotaRoomChat",
]);
let listMsg = await colab_getMessageByRoomId({ roomId: roomId, page: 1 });
const dataUserLogin = await user_getOneByUserId(userLoginId as string);
return (
<>
{/* <Colab_DetailGrupDiskusi
userLoginId={userLoginId}
listMsg={listMsg}
selectRoom={dataRoom as any}
dataUserLogin={dataUserLogin as any}
roomId={roomId}
/> */}
<Colab_GroupChatView
{/* <Colab_GroupChatView
userLoginId={userLoginId as string}
listMsg={listMsg}
selectRoom={dataRoom as any}
dataUserLogin={dataUserLogin as any}
/>
/> */}
<Colab_NewGroupChatView selectRoom={dataRoom as any} listMsg={listMsg} />
</>
);
}

View File

@@ -1,14 +1,9 @@
import { funGetUserIdByToken } from "@/app_modules/_global/fun/get";
import { Colab_MainDetail } from "@/app_modules/colab";
export default async function Page() {
const userLoginId = await funGetUserIdByToken();
return (
<>
<Colab_MainDetail
userLoginId={userLoginId as string}
/>
<Colab_MainDetail />
</>
);
}