Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions apis/data-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,10 @@ export interface CreateReservationRequest {
*/
reservationDates: string[];
/**
* 운영 시간 (형식: HH:MM ~ HH:MM)
* 운영 시간 (형식: HH:MM-HH:MM)
* @minLength 1
* @pattern ^([01]\d|2[0-3]):([0-5]\d) ~ ([01]\d|2[0-3]):([0-5]\d)$
* @example "15:00 ~ 16:00"
* @pattern ^([01]\d|2[0-3]):([0-5]\d)-([01]\d|2[0-3]):([0-5]\d)$
* @example "15:00-16:00"
*/
operationHour: string;
/**
Expand Down Expand Up @@ -788,8 +788,8 @@ export interface ReservationResponse {
*/
reservationDates?: string[];
/**
* 운영 시간 (형식: HH:MM ~ HH:MM)
* @example "15:00 ~ 16:00"
* 운영 시간 (형식: HH:MM-HH:MM)
* @example "15:00-16:00"
*/
operationHour?: string;
/**
Expand Down Expand Up @@ -1461,11 +1461,8 @@ export interface FoodTruckDetailResponse {
* @example "서울 광진구, 서울 강남구, 서울 영등포구"
*/
serviceAreas?: string;
/**
* 호출 가능 지역 코드
* @example [1,2]
*/
regionCodes?: number[];
/** 호출 가능 지역 정보 리스트 */
regionCodes?: RegionResponse[];
/**
* 푸드트럭 메뉴 카테고리 (라벨 리스트)
* @example ["한식","분식"]
Expand Down Expand Up @@ -1689,12 +1686,24 @@ export interface ChatRoomMetaDataResponse {
* @example "맛있는푸드트럭"
*/
foodTruckName?: string;
/**
* 푸드트럭 식별자
* @format int64
* @example 1
*/
foodTruckId?: number;
/**
* 채팅방과 관련된 예약 ID (있는 경우: ID 반환, 없는 경우: null)
* @format int64
* @example 1
*/
reservationId?: number;
/**
* 예약자(일반 유저) 식별자
* @format int64
* @example 2
*/
memberId?: number;
}

export interface BaseResponseListChatMessageResponse {
Expand Down
34 changes: 23 additions & 11 deletions src/pages/chat-list/ChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import { Icon } from '@icon/Icon';
import OverlayModal from '@layout/overlay/Overlay';
import Navigation from '@layout/navigation/Navigation';
import Button from '@ui/button/Button';
import { useGetChatList } from '@pages/chat-list/api/chat-list-api';
import Loading from '@layout/loading/Loading';

export default function ChatList() {
const navigate = useNavigate();
const handleClickBack = () => navigate(-1);

//TODO: 유저,오너 구분 확인 로직 필요
const isOwner = true;
const { data: chatListData, isPending, isError } = useGetChatList(isOwner);

const {
isEditing,
activeFilter,
setActiveFilter,
chatList,
selectChatList,
handleToggleEdit,
handleCheckChange,
Expand All @@ -25,6 +30,13 @@ export default function ChatList() {
handleCloseModal,
} = useChatList();

//TODO: 에러 처리 필요
if (isPending) {
return <Loading />;
}
if (isError) {
return <div>채팅 목록을 불러오는 중에 오류가 발생했습니다.</div>;
}
return (
<>
<Navigation
Expand Down Expand Up @@ -69,19 +81,19 @@ export default function ChatList() {
</div>
</div>
</OverlayModal>
<div className='flex flex-col overflow-y-scroll pb-[2.4rem] pt-[7.8rem] scrollbar-hide'>
{(chatList ?? []).map(item => {
<div className='scrollbar-hide flex flex-col overflow-y-scroll pb-[2.4rem] pt-[7.8rem]'>
{(chatListData?.content ?? []).map(item => {
return (
<ChatListItem
key={item.clientId}
key={item.id}
isEditing={isEditing}
clientName={item.clientName}
tagTitle={item.tagTitle}
lastChat={item.lastChat}
lastChatTime={item.lastChatTime}
unreadCount={item.unreadCount}
isChecked={selectChatList.has(item.clientId)}
handleCheckChange={() => handleCheckChange(item.clientId)}
name={item.name ?? ''}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null 예외처리를 빈 문자열 대신 '로딩중', '알 수 없음' 등은 어떨까요?

foodTruckName={item.foodTruckName ?? ''}
lastMessage={item.lastMessage ?? ''}
lastMessageSendTime={item.lastMessageSendTime ?? ''}
unreadCount={item.unreadCount ?? 0}
isChecked={selectChatList.has(item.id ?? 0)}
handleCheckChange={() => handleCheckChange(item.id ?? 0)}
Comment on lines 87 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

profileImageUrl prop이 누락되었고 item.id 기본값 처리를 개선하세요.

  1. ChatListItemprofileImageUrl prop을 기대하지만 전달되지 않고 있습니다.
  2. item.id ?? 0을 사용하면 실제로 id가 없는 항목이 id가 0인 항목처럼 처리되어 Set 연산에서 문제가 발생할 수 있습니다.

다음과 같이 수정하세요:

         {(chatListData?.content ?? []).map(item => {
+          if (!item.id) {
+            console.warn('Chat item missing id:', item);
+            return null;
+          }
           return (
             <ChatListItem
               key={item.id}
+              profileImageUrl={item.profileImageUrl}
               isEditing={isEditing}
               name={item.name ?? ''}
               foodTruckName={item.foodTruckName ?? ''}
               lastMessage={item.lastMessage ?? ''}
               lastMessageSendTime={item.lastMessageSendTime ?? ''}
               unreadCount={item.unreadCount ?? 0}
-              isChecked={selectChatList.has(item.id ?? 0)}
-              handleCheckChange={() => handleCheckChange(item.id ?? 0)}
+              isChecked={selectChatList.has(item.id)}
+              handleCheckChange={() => handleCheckChange(item.id)}
             />
           );
         })}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/pages/chat-list/ChatList.tsx around lines 87 to 96, the ChatListItem is
missing the profileImageUrl prop and uses item.id ?? 0 which can conflate
undefined ids with a real id of 0; add profileImageUrl by passing
item.profileImageUrl ?? '' to the component and stop defaulting id to 0 —
instead compute a local const id = item.id; if id == null skip rendering this
item (or otherwise handle it upstream) and use that id for
selectChatList.has(id) and handleCheckChange(() => handleCheckChange(id)); this
ensures profile images are passed and prevents accidental collisions from a
falsy numeric default.

/>
);
})}
Expand Down
66 changes: 66 additions & 0 deletions src/pages/chat-list/api/chat-list-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { apiRequest } from '@api/apiRequest';
import { USER_INFO } from '@shared/querykey/user-info';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type {
CreateChatRoomData,
GetChatRoomsData,
MarkMessagesAsReadData,
} from 'apis/data-contracts';

const getChatList = async (isOwner: boolean) => {
const response = await apiRequest<GetChatRoomsData>({
endPoint: '/chat/rooms',
method: 'GET',
params: {
isOwner,
},
});
return response.data;
};

const postChatRoom = async (foodTruckId: number) => {
const response = await apiRequest<CreateChatRoomData>({
endPoint: '/chat/rooms',
method: 'POST',
params: {
foodTruckId,
},
});
return response.data;
};
Comment on lines +21 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

foodTruckId를 요청 바디에 포함해야 합니다.

CreateChatRoomRequest 인터페이스에 따르면 foodTruckId는 요청 바디에 포함되어야 하는데, 현재 쿼리 파라미터로 전달되고 있습니다.

다음과 같이 수정하세요:

 const postChatRoom = async (foodTruckId: number) => {
   const response = await apiRequest<CreateChatRoomData>({
     endPoint: '/chat/rooms',
     method: 'POST',
-    params: {
+    data: {
       foodTruckId,
     },
   });
   return response.data;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const postChatRoom = async (foodTruckId: number) => {
const response = await apiRequest<CreateChatRoomData>({
endPoint: '/chat/rooms',
method: 'POST',
params: {
foodTruckId,
},
});
return response.data;
};
const postChatRoom = async (foodTruckId: number) => {
const response = await apiRequest<CreateChatRoomData>({
endPoint: '/chat/rooms',
method: 'POST',
data: {
foodTruckId,
},
});
return response.data;
};
🤖 Prompt for AI Agents
In src/pages/chat-list/api/chat-list-api.ts around lines 21 to 30, the function
posts foodTruckId as query params but the CreateChatRoomRequest expects it in
the request body; update postChatRoom to send { foodTruckId } in the request
body (e.g., the apiRequest "data" or "body" property) instead of params, and
keep the function return type the same (response.data) so the request payload
matches the CreateChatRoomRequest interface.


const patchChatRoom = async (chatRoomId: number) => {
const response = await apiRequest<MarkMessagesAsReadData>({
endPoint: `/chat/rooms/${chatRoomId}/read`,
method: 'PATCH',
});
return response.data;
};

export const useGetChatList = (isOwner: boolean) => {
return useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: USER_INFO.CHATS(),
queryFn: () => getChatList(isOwner),
});
};
Comment on lines +40 to +46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

queryKey에 isOwner를 포함해야 합니다.

isOwner 값에 따라 다른 데이터를 가져오지만 queryKey에 이 의존성이 누락되어 있습니다. ESLint 경고를 비활성화하는 것은 문제를 해결하지 않습니다. 이로 인해 isOwner 값이 다른 호출 간에 잘못된 캐시 데이터가 반환될 수 있습니다.

예: useGetChatList(true) 호출 후 useGetChatList(false)를 호출하면, 두 번째 호출이 첫 번째 호출의 캐시된 데이터를 반환합니다.

다음과 같이 수정하세요:

 export const useGetChatList = (isOwner: boolean) => {
   return useQuery({
-    // eslint-disable-next-line @tanstack/query/exhaustive-deps
-    queryKey: USER_INFO.CHATS(),
+    queryKey: [...USER_INFO.CHATS(), isOwner],
     queryFn: () => getChatList(isOwner),
   });
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useGetChatList = (isOwner: boolean) => {
return useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: USER_INFO.CHATS(),
queryFn: () => getChatList(isOwner),
});
};
export const useGetChatList = (isOwner: boolean) => {
return useQuery({
queryKey: [...USER_INFO.CHATS(), isOwner],
queryFn: () => getChatList(isOwner),
});
};
🤖 Prompt for AI Agents
In src/pages/chat-list/api/chat-list-api.ts around lines 40 to 46, the queryKey
for useGetChatList omits the isOwner dependency causing cache collisions;
include isOwner in the queryKey (e.g., USER_INFO.CHATS(isOwner) or
[USER_INFO.CHATS(), isOwner]) so React Query caches per isOwner value, remove
the eslint-disable comment, and keep the queryFn as getChatList(isOwner) to
ensure correct keyed caching.


export const usePostChatRoom = (foodTruckId: number) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => postChatRoom(foodTruckId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: USER_INFO.CHATS() });
},
});
};

export const usePatchChatRoom = (chatRoomId: number) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => patchChatRoom(chatRoomId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: USER_INFO.CHATS() });
},
});
};
77 changes: 0 additions & 77 deletions src/pages/chat-list/constant/mocks.ts

This file was deleted.

12 changes: 2 additions & 10 deletions src/pages/chat-list/hooks/use-chat-list.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { mockup } from '@pages/chat-list/constant/mocks';
import type { Chat } from '@pages/chat-list/types/chat-list-type';
import { useEffect, useState } from 'react';
import { useState } from 'react';

export const useChatList = () => {
const [isEditing, setIsEditing] = useState(false);
const [activeFilter, setActiveFilter] = useState('전체보기');
const [chatList, setChatList] = useState<Chat[]>([]);

const [selectChatList, setSelectChatList] = useState(new Set<number>());
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);

Expand Down Expand Up @@ -36,16 +34,10 @@ export const useChatList = () => {

const handleCloseModal = () => setIsDeleteModalOpen(false);

useEffect(() => {
/** API 준비 전 더미 데이터 사용*/
setChatList(mockup);
}, []);

return {
isEditing,
activeFilter,
setActiveFilter,
chatList,
selectChatList,
handleToggleEdit,
handleCheckChange,
Expand Down
8 changes: 0 additions & 8 deletions src/pages/chat-list/types/chat-list-type.ts

This file was deleted.

Loading