수락된 매칭 채팅을 닫으면 재진입 불가하던 문제 대응. 헤더 forum 아이콘 → 진행중(수락) 매칭 목록 → 탭하면 채팅 다시 열림. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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}`),
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user