juni
WelKo_채팅 목록 및 예약자 명단 본문
const groupedChats = chatData?.reduce((acc: { [key: string]: Chat }, message) => {
const chatId = `${message.post_id}-${[message.sender_id, message.receiver_id].sort().join('-')}`;
if (!acc[chatId]) {
acc[chatId] = {
post_id: message.post_id,
sender_id: message.sender_id,
receiver_id: message.receiver_id,
messages: []
};
}
acc[chatId].messages.push(message);
return acc;
}, {});
groupedChats는 객체로 생성되며, 객체의 키는 chatId입니다. 객체를 직접적으로 .map() 메서드로 순회할 수는 없습니다. 객체의 값들을 배열로 변환하여 순회할 수 있도록 Object.values(groupedChats)를 사용하는 것입니다. groupedChats.map()을 사용하려면 groupedChats가 배열이어야 하지만, 현재는 객체이므로 Object.values(groupedChats)로 객체의 값들을 배열로 변환한 후에 .map() 메서드를 사용할 수 있습니다.
return (
<div>
{groupedChats &&
Object.values(groupedChats).map((chat, index) => {
const postDetails = postData?.find((post) => post.id === chat.post_id);
const receiverId = userId === chat.sender_id ? chat.receiver_id : chat.sender_id;
const senderDetails = userData?.find((user) => user.id === receiverId);
const firstMessage = chat.messages[0];
return (
<div
className="mb-4"
key={index}
onClick={() =>
router.push(
`/${userId}/${receiverId}/chatpage?postId=${chat.post_id}&postTitle=${postDetails?.title}&postImage=${postDetails?.image}`
)
}
>
{postDetails && (
<div className="flex items-center">
<Image
className="mr-4"
src={postDetails.image || '/icons/upload.png'}
alt={postDetails.title || 'Default name'}
width={40}
height={40}
/>
<p>{postDetails.title}</p>
</div>
)}
{senderDetails && (
<div className="flex items-center">
<Image
className="mr-4"
src={senderDetails.avatar || '/icons/upload.png'}
alt={senderDetails.name || 'Default name'}
width={40}
height={40}
/>
<p>{senderDetails.name}</p>
</div>
)}
<p>{firstMessage?.content}</p>
<p>{new Date(firstMessage?.created_at).toLocaleString()}</p>
</div>
);
})}
</div>
);
true false값을 통해 확인 된 메시지의 경우에는 new가 사라지도록 개선 예정
'use client';
import React, { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { API_MYPAGE_CHATS, API_POST_DETAILS, API_MYPAGE_PROFILE } from '@/utils/apiConstants';
import axios from 'axios';
type ChatListProps = {
userId: string;
};
type Message = {
sender_id: string;
receiver_id: string;
content: string;
created_at: string;
post_id: string;
};
type Chat = {
post_id: string;
sender_id: string;
receiver_id: string;
messages: Message[];
};
type Post = {
id: string;
title: string;
image: string;
};
type User = {
id: string;
name: string;
avatar: string;
};
const ChatList = ({ userId }: ChatListProps) => {
const [newMessages, setNewMessages] = useState<{ [key: string]: boolean }>({});
const router = useRouter();
const fetchPostDetails = async (postId: string): Promise<Post> => {
const response = await axios.get(API_POST_DETAILS(postId));
return response.data;
};
const fetchUserDetails = async (userId: string): Promise<User> => {
const response = await axios.get(API_MYPAGE_PROFILE(userId));
return response.data;
};
const {
data: chatData,
error: chatError,
isPending: chatPending
} = useQuery<Message[]>({
queryKey: ['chatList', userId],
queryFn: async () => {
const response = await axios.get(API_MYPAGE_CHATS(userId));
return response.data;
},
refetchInterval: 5000
});
const postIds = chatData?.map((chat) => chat.post_id) || [];
const userIds = chatData ? chatData.flatMap((chat) => [chat.sender_id, chat.receiver_id]) : [];
const {
data: postData,
error: postError,
isPending: postPending
} = useQuery<Post[]>({
queryKey: ['postDetails', postIds],
queryFn: async () => {
const postDetails = await Promise.all(postIds.map((postId) => fetchPostDetails(postId)));
return postDetails;
},
enabled: postIds.length > 0
});
const {
data: userData,
error: userError,
isPending: userPending
} = useQuery<User[]>({
queryKey: ['userDetails', userIds],
queryFn: async () => {
const userDetails = await Promise.all(userIds.map((id) => fetchUserDetails(id)));
return userDetails;
},
enabled: userIds.length > 0
});
const groupedChats = chatData?.reduce((acc: { [key: string]: Chat }, message) => {
const chatId = `${message.post_id}-${[message.sender_id, message.receiver_id].sort().join('-')}`;
if (!acc[chatId]) {
acc[chatId] = {
post_id: message.post_id,
sender_id: message.sender_id,
receiver_id: message.receiver_id,
messages: []
};
}
acc[chatId].messages.push(message);
return acc;
}, {});
const handleChatClick = (chat: Chat) => {
const receiverId = userId === chat.sender_id ? chat.receiver_id : chat.sender_id;
const postDetails = postData?.find((post) => post.id === chat.post_id);
const chatId = `${chat.post_id}-${[chat.sender_id, chat.receiver_id].sort().join('-')}`;
setNewMessages((prev) => ({
...prev,
[chatId]: true
}));
router.push(
`/${userId}/${receiverId}/chatpage?postId=${chat.post_id}&postTitle=${postDetails?.title}&postImage=${postDetails?.image}`
);
};
if (chatPending || postPending || userPending) return <div>Loading...</div>;
if (chatError || postError || userError) return <div>Error loading data</div>;
return (
<div>
{groupedChats &&
Object.values(groupedChats).map((chat, index) => {
const postDetails = postData?.find((post) => post.id === chat.post_id);
const receiverId = userId === chat.sender_id ? chat.receiver_id : chat.sender_id;
const senderDetails = userData?.find((user) => user.id === receiverId);
const firstMessage = chat.messages[0];
const chatId = `${chat.post_id}-${[chat.sender_id, chat.receiver_id].sort().join('-')}`;
const isNewMessage = !newMessages[chatId] && firstMessage.sender_id !== userId;
return (
<div className="mb-4" key={index} onClick={() => handleChatClick(chat)}>
{postDetails && (
<div className="flex items-center">
<Image
className="mr-4"
src={postDetails.image || '/icons/upload.png'}
alt={postDetails.title || 'Default name'}
width={40}
height={40}
/>
<p>{postDetails.title}</p>
</div>
)}
{senderDetails && (
<div className="flex items-center">
<Image
className="mr-4"
src={senderDetails.avatar || '/icons/upload.png'}
alt={senderDetails.name || 'Default name'}
width={40}
height={40}
/>
<p>{senderDetails.name}</p>
</div>
)}
<div className="flex justify-between">
<p>{firstMessage?.content}</p>
{isNewMessage && <span className="text-red-600">New</span>}
</div>
<p>{new Date(firstMessage?.created_at).toLocaleString()}</p>
</div>
);
})}
</div>
);
};
export default ChatList;
예약자에게 채팅 보내는 기능 및 예약 취소 기능 추가 예정
'use client';
import { useSearchParams, useParams } from 'next/navigation';
import TourReservationList from './_components/TourReservationList';
const TourReservationListPage = () => {
const searchParams = useSearchParams();
const params = useParams();
const userId = Array.isArray(params.id) ? params.id[0] : params.id;
const postId = searchParams.get('postId');
return (
<div>
<h1>Tour Reservation List Page</h1>
{userId && postId ? <TourReservationList userId={userId} postId={postId} /> : <div>No Post ID Provided</div>}
</div>
);
};
export default TourReservationListPage;
page.tsx
'use client';
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import Image from 'next/image';
import { API_MYPAGE_TOURRESERVATIONLIST } from '@/utils/apiConstants';
import { Tables } from '@/types/supabase';
const fetchReservations = async (userId: string, postId: string) => {
const response = await axios.get(API_MYPAGE_TOURRESERVATIONLIST(userId, postId));
return response.data;
};
const TourReservationList = ({ userId, postId }: { userId: string; postId: string }) => {
const { data, error, isLoading } = useQuery<
(Tables<'payments'> & {
users: { email: string; name: string; avatar: string };
posts: { title: string; image: string };
})[]
>({
queryKey: ['reservations', userId, postId],
queryFn: () => fetchReservations(userId, postId),
enabled: !!userId && !!postId
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading reservations</div>;
if (!data || data.length === 0) return <div>No reservations found</div>;
return (
<div>
<div className="mt-4 flex">
<Image src={data[0].posts.image} alt={data[0]?.posts.title} width={50} height={50} />
<p className="ml-2 font-bold">Tour Title: {data[0]?.posts.title}</p>
</div>
<ul>
{data.map((reservation) => (
<li className="my-10" key={reservation.id}>
<Image src={reservation.users.avatar} alt={reservation.users.name} width={50} height={50} />
<p>Name: {reservation.users.name}</p>
<p>Email: {reservation.users.email}</p>
<p>Reserved At: {new Date(reservation.created_at).toLocaleString()}</p>
<p>Total Participants: {reservation.people}</p>
<p>Total Price: {reservation.total_price}</p>
<p>Payment State: {reservation.pay_state}</p>
</li>
))}
</ul>
</div>
);
};
export default TourReservationList;
List.tsx
supabase에 Tables<>타입을 사용하고 join한 테이블은 & 로 타입 선언
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/utils/supabase/server';
export async function GET(request: NextRequest, { params }: { params: { userId: string } }) {
const { userId } = params;
const postId = request.nextUrl.searchParams.get('postId');
if (!postId) {
return NextResponse.json({ error: 'Invalid post ID' }, { status: 400 });
}
try {
const supabase = createClient();
const { data, error } = await supabase
.from('payments')
.select('*, users (email , name , avatar), posts (title , image)')
.eq('post_id', postId);
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json(data, { status: 200 });
} catch (error) {
if (error instanceof Error) {
return NextResponse.json({ error: error.message }, { status: 500 });
} else {
return NextResponse.json({ error: 'Unknown error occurred' }, { status: 500 });
}
}
}
route.ts
3개의 테이블을 별도로 부르면 코드가 너무 길어져 필요한 컬럼만 가져 올 수 있도록 join
여러 테이블을 사용했지만 코드가 이전과 달리 간결해짐 -> 다른 페이지들도 개선 예정
const { data, error } = await supabase
.from('likes')
.select('post_id, posts (id, title, content, image, startDate, endDate, price)')
.eq('user_id', userId);
필요한 조건
- 외래 키 관계: likes 테이블의 post_id 컬럼이 posts 테이블의 id 컬럼을 참조하는 외래 키로 설정되어 있어야 합니다.
- 참조 무결성: 데이터베이스는 참조 무결성을 유지해야 합니다. 즉, likes 테이블의 post_id 값은 반드시 posts 테이블의 id 컬럼에 존재해야 합니다.
조인 쿼리 설명
- .from('likes'): likes 테이블을 기준으로 쿼리를 실행합니다.
- .select('post_id, posts (id, title, content, image, startDate, endDate, price)'): likes 테이블의 post_id와 조인된 posts 테이블의 컬럼을 선택합니다. posts (id, title, content, image, startDate, endDate, price) 부분은 posts 테이블의 데이터를 가져오는 조인입니다.
- .eq('user_id', userId): 특정 userId에 대한 필터링 조건을 설정합니다.
'프로젝트 > WelKo' 카테고리의 다른 글
WelKo_닉네임 수정 (0) | 2024.08.04 |
---|---|
WelKo_찜 카테고리 (0) | 2024.08.02 |
WelKo_채팅 로직 개선 (0) | 2024.07.30 |
WelKo_채팅 UI 및 로직 개선 (0) | 2024.07.29 |
WelKo_상세페이지에서 메시지 보내기 (0) | 2024.07.28 |