Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
65 changes: 65 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,65 @@
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({
queryKey: USER_INFO.CHATS(),

Check failure on line 42 in src/pages/chat-list/api/chat-list-api.ts

View workflow job for this annotation

GitHub Actions / build

The following dependencies are missing in your queryKey: isOwner
queryFn: () => getChatList(isOwner),
});
};

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

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

mutation 함수가 유연하지 않습니다.

foodTruckId가 훅 생성 시점에 고정되어, 서로 다른 푸드트럭에 대해 여러 채팅방을 생성하려면 훅을 여러 번 호출해야 합니다. mutation 함수가 foodTruckId를 인자로 받도록 변경하는 것이 더 유연합니다.

다음과 같이 리팩토링을 권장합니다:

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

사용 예시:

const { mutate } = usePostChatRoom();
mutate(foodTruckId);
🤖 Prompt for AI Agents
In src/pages/chat-list/api/chat-list-api.ts around lines 47 to 55, the hook
currently captures foodTruckId at creation time which makes it inflexible;
change the hook to not accept foodTruckId and instead make the mutation function
accept an id parameter (e.g., mutationFn: (id: number) => postChatRoom(id)) so
callers can call mutate(foodTruckId). Keep onSuccess logic the same (invalidate
USER_INFO.CHATS()) and update any call sites to call usePostChatRoom() without
args and pass the foodTruckId into mutate.


export const usePatchChatRoom = (chatRoomId: number) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => patchChatRoom(chatRoomId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: USER_INFO.CHATS() });
},
});
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

mutation 함수가 유연하지 않습니다.

chatRoomId가 훅 생성 시점에 고정되어, 서로 다른 채팅방에 대해 읽음 표시를 하려면 훅을 여러 번 호출해야 합니다. mutation 함수가 chatRoomId를 인자로 받도록 변경하는 것이 더 유연합니다.

다음과 같이 리팩토링을 권장합니다:

-export const usePatchChatRoom = (chatRoomId: number) => {
+export const usePatchChatRoom = () => {
   const queryClient = useQueryClient();
   return useMutation({
-    mutationFn: () => patchChatRoom(chatRoomId),
+    mutationFn: (chatRoomId: number) => patchChatRoom(chatRoomId),
     onSuccess: () => {
       queryClient.invalidateQueries({ queryKey: USER_INFO.CHATS() });
     },
   });
 };

사용 예시:

const { mutate } = usePatchChatRoom();
mutate(chatRoomId);
🤖 Prompt for AI Agents
In src/pages/chat-list/api/chat-list-api.ts around lines 57 to 65, the hook
binds chatRoomId at creation time making it inflexible; change the hook to
accept no chatRoomId and make the mutation function take chatRoomId as an
argument (so callers call mutate(chatRoomId)). Update useMutation to use a
mutationFn that accepts the id parameter and adjust TypeScript generics/types
accordingly; keep the onSuccess handler the same and continue invalidating
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.

34 changes: 17 additions & 17 deletions src/shared/components/chat/chat-list-item/ChatListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ import Tag from '@ui/tag/Tag';
import { cn } from '@utils/cn';

interface ChatListItemProps {
profileImage?: string;
profileImageUrl?: string;
isEditing: boolean;
clientName: string;
tagTitle: string;
lastChat: string;
lastChatTime: string;
name: string;
foodTruckName: string;
lastMessage: string;
lastMessageSendTime: string;
unreadCount: number;
isChecked: boolean;
handleCheckChange: (_checked: boolean) => void;
}

export default function ChatListItem({
profileImage,
profileImageUrl,
isEditing,
clientName,
tagTitle,
lastChat,
lastChatTime,
name,
foodTruckName,
lastMessage,
lastMessageSendTime,
unreadCount,
isChecked,
handleCheckChange,
Expand Down Expand Up @@ -51,10 +51,10 @@ export default function ChatListItem({
)}

<div className='border-grayscale-200 h-[5.2rem] w-[5.2rem] flex-shrink-0 overflow-hidden rounded-full border'>
{profileImage ? (
{profileImageUrl ? (
<img
src={profileImage}
alt={`${clientName} profile`}
src={profileImageUrl}
alt={`${name} profile`}
className='h-full w-full object-cover'
/>
) : (
Expand All @@ -66,20 +66,20 @@ export default function ChatListItem({
<div className='flex flex-1 flex-col gap-[0.2rem] overflow-hidden'>
<div className='flex items-center gap-[0.6rem] overflow-hidden'>
<span className='text-grayscale-900 title-sb-16 block truncate'>
{clientName}
{name}
</span>
<Tag title={tagTitle} />
<Tag title={foodTruckName} />
</div>
<span
className={`caption-m-12 block truncate text-start ${unreadCount > 0 ? 'text-grayscale-900' : 'text-grayscale-500'}`}
>
{lastChat}
{lastMessage}
</span>
</div>

<div className='flex w-[5rem] flex-shrink-0 flex-col items-end gap-[0.6rem] pt-[0.3rem]'>
<span className='text-grayscale-500 caption-m-11 whitespace-nowrap'>
{lastChatTime}
{lastMessageSendTime}
</span>
{unreadCount > 0 && (
<div className='bg-primary-500 caption-m-12 flex h-[1.8rem] items-center justify-center whitespace-nowrap rounded-full px-[0.5rem] text-white'>
Expand Down
Loading