feat: 헤더에 진행중 채팅 목록 — 닫은 매칭 채팅 다시 열기
CI / build (push) Failing after 13m46s

수락된 매칭 채팅을 닫으면 재진입 불가하던 문제 대응.
헤더 forum 아이콘 → 진행중(수락) 매칭 목록 → 탭하면 채팅 다시 열림.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sb
2026-07-12 14:27:44 +09:00
parent 060b458eb9
commit befd49fd29
3 changed files with 63 additions and 2 deletions
+10
View File
@@ -20,6 +20,15 @@ export interface ReceivedMatch {
expiresAt: string | null
}
/** 진행중(수락된) 매칭 — 채팅 재진입 목록용. */
export interface OngoingMatch {
matchId: number
otherDogId: number
otherDogName: string
otherDogBreed: string | null
respondedAt: string | null
}
/** STOMP /topic/users/{userId} 로 오는 매칭 알림. */
export interface MatchNotification {
type: 'NEW_REQUEST' | 'ACCEPTED' | 'REJECTED' | 'EXPIRED' | 'FEEDBACK_REQUEST'
@@ -35,4 +44,5 @@ export const matchesApi = {
reject: (id: number) => http.post<Match>(`/api/matches/${id}/reject`),
received: (dogId: number) => http.get<ReceivedMatch[]>(`/api/matches/received/${dogId}`),
sent: (dogId: number) => http.get<Match[]>(`/api/matches/sent/${dogId}`),
ongoing: (dogId: number) => http.get<OngoingMatch[]>(`/api/matches/ongoing/${dogId}`),
}
+37
View File
@@ -39,6 +39,37 @@
</q-menu>
</q-btn>
<!-- 진행중 매칭 채팅 (닫아도 다시 열기) -->
<q-btn flat round dense icon="forum" class="q-mr-xs">
<q-badge v-if="matching.ongoing.length" color="teal" floating>
{{ matching.ongoing.length }}
</q-badge>
<q-menu anchor="bottom right" self="top right">
<q-list style="min-width: 260px">
<q-item-label header>진행중 채팅</q-item-label>
<q-item v-if="!matching.ongoing.length">
<q-item-section class="text-grey-6">진행중인 매칭이 없어요.</q-item-section>
</q-item>
<q-item
v-for="o in matching.ongoing"
:key="o.matchId"
clickable
v-close-popup
@click="openMatchChat(o.matchId)"
>
<q-item-section avatar>
<q-avatar color="primary" text-color="white" icon="favorite" />
</q-item-section>
<q-item-section>
<q-item-label class="text-weight-medium">{{ o.otherDogName }}</q-item-label>
<q-item-label caption>{{ o.otherDogBreed || '견종 미상' }} · 대화 열기</q-item-label>
</q-item-section>
<q-item-section side><q-icon name="chat" color="primary" /></q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<q-btn flat round dense icon="account_circle">
<q-menu>
<q-list style="min-width: 160px">
@@ -128,6 +159,12 @@ function openFeedback(id: number) {
feedbackOpen.value = true
}
/** 진행중 채팅 목록에서 특정 매칭 채팅 다시 열기. */
function openMatchChat(matchId: number) {
matchChatId.value = matchId
matchChatOpen.value = true
}
onMounted(async () => {
await dogs.loadMyDogs()
if (auth.member && dogs.currentDogId != null) {
+16 -2
View File
@@ -1,7 +1,12 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { Notify } from 'quasar'
import { matchesApi, type MatchNotification, type ReceivedMatch } from '@/api/matches'
import {
matchesApi,
type MatchNotification,
type OngoingMatch,
type ReceivedMatch,
} from '@/api/matches'
import { createUserNotifications } from '@/lib/chatSocket'
/**
@@ -13,6 +18,7 @@ import { createUserNotifications } from '@/lib/chatSocket'
*/
export const useMatchingStore = defineStore('matching', () => {
const received = ref<ReceivedMatch[]>([])
const ongoing = ref<OngoingMatch[]>([])
const sentPendingTargetIds = ref<Set<number>>(new Set())
const openChatMatchId = ref<number | null>(null)
/** 산책 후 피드백(찰떡지수) 팝업을 띄울 매칭 id — 알림 수신 시 세팅. */
@@ -24,8 +30,13 @@ export const useMatchingStore = defineStore('matching', () => {
async function refresh(dogId: number): Promise<void> {
myDogId = dogId
try {
const [rec, snt] = await Promise.all([matchesApi.received(dogId), matchesApi.sent(dogId)])
const [rec, snt, ong] = await Promise.all([
matchesApi.received(dogId),
matchesApi.sent(dogId),
matchesApi.ongoing(dogId).catch(() => [] as OngoingMatch[]),
])
received.value = rec
ongoing.value = ong
sentPendingTargetIds.value = new Set(snt.map((m) => m.targetDogId))
} catch {
// 무시 (미로그인/네트워크)
@@ -44,6 +55,7 @@ export const useMatchingStore = defineStore('matching', () => {
socket?.deactivate()
socket = null
received.value = []
ongoing.value = []
sentPendingTargetIds.value = new Set()
}
@@ -69,6 +81,7 @@ export const useMatchingStore = defineStore('matching', () => {
await matchesApi.accept(id)
received.value = received.value.filter((m) => m.id !== id)
openChatMatchId.value = id // 수락자도 바로 채팅 열기
if (myDogId != null) void refresh(myDogId) // 진행중 목록에 반영
}
async function reject(id: number): Promise<void> {
@@ -89,6 +102,7 @@ export const useMatchingStore = defineStore('matching', () => {
return {
received,
ongoing,
sentPendingTargetIds,
openChatMatchId,
feedbackMatchId,