From f6527af4e2f0ebd543da6ff27a72b7f951deecfe Mon Sep 17 00:00:00 2001 From: Obebe Date: Thu, 6 Aug 2026 20:24:22 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EB=8B=A4=EB=A5=B8=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=88=98=EC=A0=95=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../estimate/detail/EstimateChatAction.tsx | 56 +++++++++++++++++++ .../pending/PendingEstimateDetailView.tsx | 32 ++++++----- .../estimate/sent/SentEstimateChatAction.tsx | 13 +++++ .../estimate/sent/SentEstimateDetailPage.tsx | 10 +++- src/lib/constants/appRoutes.ts | 4 ++ 5 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/components/estimate/detail/EstimateChatAction.tsx create mode 100644 src/components/estimate/sent/SentEstimateChatAction.tsx diff --git a/src/components/estimate/detail/EstimateChatAction.tsx b/src/components/estimate/detail/EstimateChatAction.tsx new file mode 100644 index 00000000..4d7b68a7 --- /dev/null +++ b/src/components/estimate/detail/EstimateChatAction.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +import Button from "@/components/common/Button/Button"; +import Toast from "@/components/common/Toast/Toast"; +import { useGetOrCreateChatRoom } from "@/hooks/useChatRoom"; +import { WriteIcon } from "@/icons"; +import { getApiErrorMessage } from "@/lib/api/getApiErrorMessage"; +import { APP_ROUTES } from "@/lib/constants/appRoutes"; +import { cn } from "@/lib/utils/cn"; + +interface EstimateChatActionProps { + estimateId: number; + className?: string; +} + +/** + * 견적 상세 채팅방 진입 CTA + * // 2026.08.06 김성현 - [추가] 고객·기사 상세 공통 채팅방 생성 및 이동 + */ +export default function EstimateChatAction({ estimateId, className }: EstimateChatActionProps) { + const router = useRouter(); + const [toastMessage, setToastMessage] = useState(null); + const chatRoomMutation = useGetOrCreateChatRoom(); + + const handleClick = async () => { + try { + const room = await chatRoomMutation.mutateAsync({ estimateId }); + router.push(APP_ROUTES.CHATS.ROOM(room.id)); + } catch (error) { + setToastMessage(getApiErrorMessage(error, "채팅방을 준비하지 못했습니다.")); + } + }; + + return ( + <> +
+ +
+ + {toastMessage ? setToastMessage(null)}>{toastMessage} : null} + + ); +} diff --git a/src/components/estimate/pending/PendingEstimateDetailView.tsx b/src/components/estimate/pending/PendingEstimateDetailView.tsx index 2912de69..c5f21492 100644 --- a/src/components/estimate/pending/PendingEstimateDetailView.tsx +++ b/src/components/estimate/pending/PendingEstimateDetailView.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import Toast from "@/components/common/Toast/Toast"; +import EstimateChatAction from "@/components/estimate/detail/EstimateChatAction"; import EstimateDetailActions from "@/components/estimate/detail/EstimateDetailActions"; import EstimateDetailComment from "@/components/estimate/detail/EstimateDetailComment"; import EstimateDetailDriverSummary from "@/components/estimate/detail/EstimateDetailDriverSummary"; @@ -42,6 +43,7 @@ function PendingEstimateDetailContent({ estimateId, data }: PendingEstimateDetai const estimateRequestId = data.estimateRequest.id; const canCancelRequest = isCancelableEstimateRequestStatus(data.estimateRequest.status); const displayName = data.mover.nickname || data.mover.name; + const showChatAction = data.status === "SENT"; const confirmMutation = useConfirmEstimate(estimateId, { onSuccess: () => setConfirmToastMessage("견적이 확정되었습니다."), @@ -77,19 +79,22 @@ function PendingEstimateDetailContent({ estimateId, data }: PendingEstimateDetai } aside={ - confirmMutation.mutate()} - canCancelRequest={canCancelRequest} - isCanceling={cancelFlow.isCancelPending} - onCancelRequest={cancelFlow.openCancelModal} - cancelButtonRef={cancelFlow.cancelButtonRef} - /> +
+ confirmMutation.mutate()} + canCancelRequest={canCancelRequest} + isCanceling={cancelFlow.isCancelPending} + onCancelRequest={cancelFlow.openCancelModal} + cancelButtonRef={cancelFlow.cancelButtonRef} + /> + {showChatAction ? : null} +
} /> @@ -119,6 +124,7 @@ function PendingEstimateDetailContent({ estimateId, data }: PendingEstimateDetai * // 2026.07.25 정슬기 - [추가] * // 2026.07.30 정슬기 - [수정] useEstimateDetail·Layout·Actions 통합 * // 2026.08.03 정슬기 - [수정] 레이아웃·코멘트·요청 취소 액션 · Header 뒤로가기 + * // 2026.08.06 김성현 - [수정] 대기 견적 상세 채팅방 진입 CTA 추가 */ export default function PendingEstimateDetailView({ estimateId }: PendingEstimateDetailViewProps) { const { data, isLoading, isError, error, refetch } = useEstimateDetail(estimateId); diff --git a/src/components/estimate/sent/SentEstimateChatAction.tsx b/src/components/estimate/sent/SentEstimateChatAction.tsx new file mode 100644 index 00000000..0891eee2 --- /dev/null +++ b/src/components/estimate/sent/SentEstimateChatAction.tsx @@ -0,0 +1,13 @@ +import EstimateChatAction from "@/components/estimate/detail/EstimateChatAction"; + +interface SentEstimateChatActionProps { + estimateId: number; +} + +/** + * // 2026.08.06 김성현 - [추가] 보낸 견적 상세 채팅방 진입 CTA + * // 2026.08.06 김성현 - [수정] 공통 EstimateChatAction 재사용 + */ +export default function SentEstimateChatAction({ estimateId }: SentEstimateChatActionProps) { + return ; +} diff --git a/src/components/estimate/sent/SentEstimateDetailPage.tsx b/src/components/estimate/sent/SentEstimateDetailPage.tsx index 823eb019..6d829815 100644 --- a/src/components/estimate/sent/SentEstimateDetailPage.tsx +++ b/src/components/estimate/sent/SentEstimateDetailPage.tsx @@ -2,10 +2,12 @@ import { Text } from "@/components/common/Text"; import EstimateDetailLayout, { + ESTIMATE_DETAIL_LAYOUT_CLASSES, EstimateDetailQueryState, } from "@/components/estimate/detail/EstimateDetailLayout"; import { EstimateDetailInfoSection } from "@/components/estimate/detail/EstimateDetailInfoSection"; import EstimateDetailPrice from "@/components/estimate/detail/EstimateDetailPrice"; +import SentEstimateChatAction from "@/components/estimate/sent/SentEstimateChatAction"; import { MoveTypeChip } from "@/components/common/Chip/MoveTypeChip"; import DesignatedChip from "@/components/estimate/DesignatedChip"; import { useSentEstimateDetail } from "@/hooks/useSentEstimates"; @@ -124,14 +126,17 @@ export default function SentEstimateDetailPage({ estimateId }: SentEstimateDetai const estimate = query.data; const request = estimate.estimateRequest; + // 2026.08.06 김성현 - [수정] 견적 조율 가능한 보낸 견적에서 채팅방 진입 CTA 노출 + const showChatAction = estimate.status === "SENT"; return (
@@ -170,6 +175,7 @@ export default function SentEstimateDetailPage({ estimateId }: SentEstimateDetai } + aside={showChatAction ? : undefined} /> ); } diff --git a/src/lib/constants/appRoutes.ts b/src/lib/constants/appRoutes.ts index 733be649..5cffab91 100644 --- a/src/lib/constants/appRoutes.ts +++ b/src/lib/constants/appRoutes.ts @@ -62,4 +62,8 @@ export const APP_ROUTES = { SENT_DETAIL: (estimateId: number) => `/estimate/sent/${estimateId}`, REJECTED: "/estimate/rejected", }, + // 2026.08.06 김성현 - [추가] 채팅방 상세 페이지 경로 + CHATS: { + ROOM: (roomId: number) => `/chats/${roomId}`, + }, } as const; From 62bbb0f80488fb2961386d29ce15648182de418d Mon Sep 17 00:00:00 2001 From: Obebe Date: Fri, 7 Aug 2026 10:47:03 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=EA=B0=84=EA=B2=A9=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/estimate/detail/EstimateChatAction.tsx | 9 +++++++-- src/components/estimate/detail/EstimateDetailLayout.tsx | 2 +- .../estimate/pending/PendingEstimateDetailView.tsx | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/estimate/detail/EstimateChatAction.tsx b/src/components/estimate/detail/EstimateChatAction.tsx index 4d7b68a7..08591206 100644 --- a/src/components/estimate/detail/EstimateChatAction.tsx +++ b/src/components/estimate/detail/EstimateChatAction.tsx @@ -14,13 +14,18 @@ import { cn } from "@/lib/utils/cn"; interface EstimateChatActionProps { estimateId: number; className?: string; + buttonClassName?: string; } /** * 견적 상세 채팅방 진입 CTA * // 2026.08.06 김성현 - [추가] 고객·기사 상세 공통 채팅방 생성 및 이동 */ -export default function EstimateChatAction({ estimateId, className }: EstimateChatActionProps) { +export default function EstimateChatAction({ + estimateId, + className, + buttonClassName, +}: EstimateChatActionProps) { const router = useRouter(); const [toastMessage, setToastMessage] = useState(null); const chatRoomMutation = useGetOrCreateChatRoom(); @@ -40,7 +45,7 @@ export default function EstimateChatAction({ estimateId, className }: EstimateCh
} />