From 023655b8093920f0e5b7005d8baf24e6b4403952 Mon Sep 17 00:00:00 2001 From: yujin5959 Date: Thu, 16 Jul 2026 19:57:32 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=B1=97=EB=B4=87=20=EB=8C=80?= =?UTF-8?q?=ED=99=94=20=ED=9E=88=EC=8A=A4=ED=86=A0=EB=A6=AC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=B0=8F=20=EA=B3=B5=EC=9C=A0=20=EC=B7=A8=EC=86=8C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/Common/AIChatModal.tsx | 42 +++++++++++++++++---- src/features/Friends/SharedScheduleItem.tsx | 37 ++++++++++++++++-- src/shared/api/friends/eventShare.ts | 7 ++++ src/shared/api/home/home.ts | 5 +++ src/shared/types/eventShare/eventShare.ts | 2 + src/shared/types/home/home.ts | 13 +++++++ 6 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/features/Common/AIChatModal.tsx b/src/features/Common/AIChatModal.tsx index ef4c5b9..67fc104 100644 --- a/src/features/Common/AIChatModal.tsx +++ b/src/features/Common/AIChatModal.tsx @@ -1,4 +1,4 @@ -import { useQueryClient } from '@tanstack/react-query' // 1. useQueryClient 임포트 +import { useQueryClient, useQuery } from '@tanstack/react-query' import React, { useEffect, useRef, useState } from 'react' import ChatIcon from '@/assets/icons/common/chat.svg' @@ -20,11 +20,31 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { const [isLoading, setIsLoading] = useState(false) const chatBoxRef = useRef(null) + const { data: historyData, isLoading: isHistoryLoading } = useQuery({ + queryKey: ['chatHistory'], + queryFn: nlpApi.getHistory, + }) + + useEffect(() => { + if (historyData?.isSuccess && historyData.result) { + const rawMessages = historyData.result.messages + const mappedMessages: ChatMessage[] = Array.isArray(rawMessages) + ? rawMessages.map((msg) => ({ + id: crypto.randomUUID(), + sender: msg.role === 'user' ? 'user' : 'bot', + text: msg.content ?? '', + })) + : [] + + setMessages(mappedMessages) + } + }, [historyData]) + useEffect(() => { if (chatBoxRef.current) { chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight } - }, [messages, isLoading]) + }, [messages, isLoading, isHistoryLoading]) const handleSendMessage = async () => { if (!inputValue.trim() || isLoading) return @@ -52,6 +72,8 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { } setMessages((prev) => [...prev, botMessage]) + queryClient.invalidateQueries({ queryKey: ['chatHistory'] }) + if (response.result.action === 'UPDATED') { queryClient.invalidateQueries({ queryKey: ['calendar'] }) queryClient.invalidateQueries({ queryKey: ['events'] }) @@ -67,7 +89,6 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { }, ]) } - // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { setMessages((prev) => [ ...prev, @@ -88,7 +109,7 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { } } - const isChatEmpty = messages.length === 0 && !isLoading + const isChatEmpty = messages.length === 0 && !isLoading && !isHistoryLoading const hideEmptyChatBox = !isHome && isChatEmpty return ( @@ -101,7 +122,14 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { - {isChatEmpty ? ( + {isHistoryLoading ? ( + + robot + + 잠시만 기다려주세요... + + + ) : isChatEmpty ? ( 채팅 시작 @@ -150,11 +178,11 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { onChange={(e) => setInputValue(e.target.value)} onKeyDown={handleKeyDown} placeholder="예시) 내일 오후 3시 치과 진료 받으러 감" - disabled={isLoading} + disabled={isLoading || isHistoryLoading} /> ↑ diff --git a/src/features/Friends/SharedScheduleItem.tsx b/src/features/Friends/SharedScheduleItem.tsx index 1d19a6c..4346202 100644 --- a/src/features/Friends/SharedScheduleItem.tsx +++ b/src/features/Friends/SharedScheduleItem.tsx @@ -1,3 +1,7 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { eventShareApi } from '@/shared/api/friends/eventShare' + import * as S from './SharedScheduleItem.style' interface SharedScheduleItemProps { @@ -19,6 +23,8 @@ export default function SharedScheduleItem({ accentColor = '#5c6ac4', onCancelSuccess, }: SharedScheduleItemProps) { + const queryClient = useQueryClient() + const formatDate = (dateStr: string) => { if (!dateStr) return '' const date = new Date(dateStr) @@ -35,9 +41,27 @@ export default function SharedScheduleItem({ ? formatDate(startDate) : `${formatDate(startDate)} - ${formatDate(endDate)}` + const leaveEventMutation = useMutation({ + mutationFn: () => eventShareApi.leaveEvent(eventId), + onSuccess: (response) => { + if (response.isSuccess) { + queryClient.invalidateQueries({ queryKey: ['calendar'] }) + queryClient.invalidateQueries({ queryKey: ['events'] }) + queryClient.invalidateQueries({ queryKey: ['todos'] }) + onCancelSuccess?.() + } else { + alert(response.message || '탈퇴 처리에 실패했습니다.') + } + }, + onError: () => { + alert('서버 연결에 실패했습니다. 잠시 후 다시 시도해주세요.') + }, + }) + const handleCancelShare = () => { - console.log(`eventId ${eventId} 공유 취소 클릭`) - onCancelSuccess?.() + if (window.confirm('정말 이 공유 이벤트에서 탈퇴하시겠습니까?')) { + leaveEventMutation.mutate() + } } return ( @@ -50,8 +74,13 @@ export default function SharedScheduleItem({ 공유자: {sharerName} - - 공유 취소 + + {leaveEventMutation.isPending ? '취소 중...' : '공유 취소'} diff --git a/src/shared/api/friends/eventShare.ts b/src/shared/api/friends/eventShare.ts index ceb5742..d234902 100644 --- a/src/shared/api/friends/eventShare.ts +++ b/src/shared/api/friends/eventShare.ts @@ -1,6 +1,7 @@ import type { ActionApiResponse, InvitationsApiResponse, + LeaveEventResponse, SharedEventsApiResponse, } from '@/shared/types/eventShare/eventShare' @@ -32,4 +33,10 @@ export const eventShareApi = { const { data } = await axiosInstance.get(`${BASE_URL}/invitations`) return data }, + leaveEvent: async (eventId: number): Promise => { + const { data } = await axiosInstance.delete( + `/events/${eventId}/participants/leave`, + ) + return data + }, } diff --git a/src/shared/api/home/home.ts b/src/shared/api/home/home.ts index 7bb53ca..7458adc 100644 --- a/src/shared/api/home/home.ts +++ b/src/shared/api/home/home.ts @@ -1,5 +1,6 @@ import type { BriefingResponse, + ChatHistoryResponse, ChatResponse, ReminderResponse, SuggestionListResponse, @@ -47,4 +48,8 @@ export const nlpApi = { const res = await axiosInstance.post('/chat', { message }) return res.data }, + getHistory: async (): Promise => { + const res = await axiosInstance.get('/chat/history') + return res.data + }, } diff --git a/src/shared/types/eventShare/eventShare.ts b/src/shared/types/eventShare/eventShare.ts index 3999690..43ffe2e 100644 --- a/src/shared/types/eventShare/eventShare.ts +++ b/src/shared/types/eventShare/eventShare.ts @@ -32,3 +32,5 @@ export interface InvitationsResult { export type InvitationsApiResponse = TCommonResponse export type ActionApiResponse = TCommonResponse + +export type LeaveEventResponse = TCommonResponse diff --git a/src/shared/types/home/home.ts b/src/shared/types/home/home.ts index 732e8dc..326252d 100644 --- a/src/shared/types/home/home.ts +++ b/src/shared/types/home/home.ts @@ -75,3 +75,16 @@ export interface ChatMessage { text: string action?: ChatActionType } + +export type ChatHistoryResponse = TCommonResponse + +export interface ChatHistory { + messages: boolean | undefined + result: { + messages: { + role: 'user' | 'assistant' + content: string + }[] + summary: string | null + } +} From eb40535510a27dcbc63b24a3e7ae08ed47bc168a Mon Sep 17 00:00:00 2001 From: yujin5959 Date: Thu, 16 Jul 2026 20:43:28 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20Toast=20=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20Error=20Type=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/Common/AIChatModal.tsx | 9 +++++-- src/features/Friends/SharedScheduleItem.tsx | 27 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/features/Common/AIChatModal.tsx b/src/features/Common/AIChatModal.tsx index 67fc104..457e7e8 100644 --- a/src/features/Common/AIChatModal.tsx +++ b/src/features/Common/AIChatModal.tsx @@ -1,4 +1,4 @@ -import { useQueryClient, useQuery } from '@tanstack/react-query' +import { useQuery, useQueryClient } from '@tanstack/react-query' import React, { useEffect, useRef, useState } from 'react' import ChatIcon from '@/assets/icons/common/chat.svg' @@ -90,12 +90,17 @@ function AIChatModal({ isHome = true }: AIChatModalProps) { ]) } } catch (error) { + const errorMessage = + error instanceof Error + ? error.message + : 'AI 비서 서버와 연결에 실패했습니다. 잠시 후 다시 시도해주세요.' + setMessages((prev) => [ ...prev, { id: crypto.randomUUID(), sender: 'bot', - text: 'AI 비서 서버와 연결에 실패했습니다. 잠시 후 다시 시도해주세요.', + text: `연결에 실패했습니다. (원인: ${errorMessage})`, }, ]) } finally { diff --git a/src/features/Friends/SharedScheduleItem.tsx b/src/features/Friends/SharedScheduleItem.tsx index 4346202..53cad40 100644 --- a/src/features/Friends/SharedScheduleItem.tsx +++ b/src/features/Friends/SharedScheduleItem.tsx @@ -1,6 +1,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { eventShareApi } from '@/shared/api/friends/eventShare' +import { getErrorMessage } from '@/shared/utils' +import { useToastStore } from '@/store/useToastStore' import * as S from './SharedScheduleItem.style' @@ -24,6 +26,7 @@ export default function SharedScheduleItem({ onCancelSuccess, }: SharedScheduleItemProps) { const queryClient = useQueryClient() + const { showToast } = useToastStore() const formatDate = (dateStr: string) => { if (!dateStr) return '' @@ -48,13 +51,31 @@ export default function SharedScheduleItem({ queryClient.invalidateQueries({ queryKey: ['calendar'] }) queryClient.invalidateQueries({ queryKey: ['events'] }) queryClient.invalidateQueries({ queryKey: ['todos'] }) + + showToast({ + title: '그룹 탈퇴 완료', + message: '성공적으로 탈퇴되었습니다.', + toastType: 'success', + }) + onCancelSuccess?.() } else { - alert(response.message || '탈퇴 처리에 실패했습니다.') + showToast({ + title: '탈퇴 처리 실패', + message: response.message || '탈퇴 처리에 실패했습니다.', + toastType: 'error', + }) } }, - onError: () => { - alert('서버 연결에 실패했습니다. 잠시 후 다시 시도해주세요.') + onError: (error) => { + console.error(error) + const errorMessage = getErrorMessage(error) + + showToast({ + title: '오류 발생', + message: errorMessage || '서버 연결에 실패했습니다. 잠시 후 다시 시도해주세요.', + toastType: 'error', + }) }, })