Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

juni

WelKo_채팅 목록 및 예약자 명단 본문

프로젝트/WelKo

WelKo_채팅 목록 및 예약자 명단

juni_shin 2024. 8. 1. 22:45
  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);

필요한 조건

  1. 외래 키 관계: likes 테이블의 post_id 컬럼이 posts 테이블의 id 컬럼을 참조하는 외래 키로 설정되어 있어야 합니다.
  2. 참조 무결성: 데이터베이스는 참조 무결성을 유지해야 합니다. 즉, 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