c8a8681a61
CI / build (push) Successful in 27s
- FeedbackDialog: 잘 놀았어요/안 맞았어요 + BAD 사유 선택·추가입력 - 채팅 '산책 완료' 버튼 + 2시간 FEEDBACK_REQUEST 알림 → 피드백 팝업 - AffinityBadges: 동네 찰떡지수(전체), 우리 찰떡지수(매칭 쌍·유료 게이팅) 스팟 메이트/AI 추천 카드 및 우리 강아지 프로필에 노출 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
167 lines
4.7 KiB
Vue
167 lines
4.7 KiB
Vue
<template>
|
|
<q-dialog :model-value="modelValue" @update:model-value="onDialog" persistent>
|
|
<q-card class="feedback-card">
|
|
<q-card-section class="text-center q-pb-none">
|
|
<div class="text-h6 text-weight-bold">오늘 산책 어떠셨나요?</div>
|
|
<div v-if="otherDogName" class="text-caption text-grey-7 q-mt-xs">
|
|
{{ otherDogName }}와의 산책을 평가해 주세요
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<!-- 1단계: 잘 놀았어요 / 안 맞았어요 -->
|
|
<q-card-section v-if="!showReasons" class="q-gutter-sm">
|
|
<q-btn
|
|
class="full-width verdict-btn"
|
|
unelevated
|
|
color="positive"
|
|
no-caps
|
|
:loading="submitting"
|
|
@click="submitGood"
|
|
>
|
|
<q-icon name="sentiment_very_satisfied" size="26px" class="q-mr-sm" />
|
|
잘 놀았어요
|
|
</q-btn>
|
|
<q-btn
|
|
class="full-width verdict-btn"
|
|
outline
|
|
color="grey-8"
|
|
no-caps
|
|
:disable="submitting"
|
|
@click="showReasons = true"
|
|
>
|
|
<q-icon name="sentiment_dissatisfied" size="26px" class="q-mr-sm" />
|
|
안 맞았어요
|
|
</q-btn>
|
|
</q-card-section>
|
|
|
|
<!-- 2단계: 안 맞았어요 사유 -->
|
|
<q-card-section v-else>
|
|
<div class="text-subtitle2 q-mb-sm">어떤 점이 아쉬웠나요? (선택)</div>
|
|
<q-option-group
|
|
v-model="selectedReasons"
|
|
:options="reasonOptions"
|
|
type="checkbox"
|
|
color="primary"
|
|
/>
|
|
<q-input
|
|
v-model="extraReason"
|
|
class="q-mt-sm"
|
|
dense
|
|
outlined
|
|
type="textarea"
|
|
rows="2"
|
|
maxlength="200"
|
|
placeholder="추가로 남기고 싶은 점이 있다면 적어주세요"
|
|
/>
|
|
<div class="row q-gutter-sm q-mt-md">
|
|
<q-btn flat color="grey-7" label="뒤로" no-caps :disable="submitting" @click="showReasons = false" />
|
|
<q-btn
|
|
class="col"
|
|
unelevated
|
|
color="primary"
|
|
label="제출"
|
|
no-caps
|
|
:loading="submitting"
|
|
@click="submitBad"
|
|
/>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch } from 'vue'
|
|
import { useQuasar } from 'quasar'
|
|
import { affinityApi } from '@/api/affinity'
|
|
import { ApiError } from '@/api/http'
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
matchId: number
|
|
raterDogId: number
|
|
otherDogName?: string
|
|
}>()
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', v: boolean): void
|
|
(e: 'submitted'): void
|
|
}>()
|
|
|
|
const $q = useQuasar()
|
|
|
|
const showReasons = ref(false)
|
|
const submitting = ref(false)
|
|
const selectedReasons = ref<string[]>([])
|
|
const extraReason = ref('')
|
|
|
|
const reasonOptions = [
|
|
{ label: '강아지 성향이 안 맞아요', value: '강아지 성향이 안 맞아요' },
|
|
{ label: '간식/장난감 때문에 으르릉거려요', value: '간식/장난감 때문에 으르릉거려요' },
|
|
{ label: '일방적으로 입질/공격성을 보였어요', value: '일방적으로 입질/공격성을 보였어요' },
|
|
{ label: '보호자 매너가 아쉬워요', value: '보호자 매너가 아쉬워요' },
|
|
]
|
|
|
|
// 열릴 때마다 초기화
|
|
watch(
|
|
() => props.modelValue,
|
|
(open) => {
|
|
if (open) {
|
|
showReasons.value = false
|
|
selectedReasons.value = []
|
|
extraReason.value = ''
|
|
submitting.value = false
|
|
}
|
|
},
|
|
)
|
|
|
|
function onDialog(v: boolean) {
|
|
emit('update:modelValue', v)
|
|
}
|
|
|
|
async function submitGood() {
|
|
await submit('GOOD')
|
|
}
|
|
|
|
async function submitBad() {
|
|
const reasons = [...selectedReasons.value]
|
|
const extra = extraReason.value.trim()
|
|
if (extra) reasons.push(extra)
|
|
await submit('BAD', reasons.join(', '))
|
|
}
|
|
|
|
async function submit(verdict: 'GOOD' | 'BAD', reasons?: string) {
|
|
submitting.value = true
|
|
try {
|
|
await affinityApi.feedback(props.matchId, props.raterDogId, verdict, reasons)
|
|
$q.notify({
|
|
color: 'positive',
|
|
icon: 'favorite',
|
|
message: '소중한 피드백 고마워요! 찰떡지수에 반영했어요.',
|
|
position: 'top',
|
|
timeout: 2500,
|
|
})
|
|
emit('submitted')
|
|
emit('update:modelValue', false)
|
|
} catch (e) {
|
|
// 이미 제출한 경우(409) 등은 조용히 닫기
|
|
const message = e instanceof ApiError ? e.message : '피드백 전송에 실패했어요.'
|
|
$q.notify({ color: 'grey-8', icon: 'info', message, position: 'top', timeout: 2500 })
|
|
emit('update:modelValue', false)
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.feedback-card {
|
|
width: 340px;
|
|
max-width: 90vw;
|
|
border-radius: 16px;
|
|
}
|
|
.verdict-btn {
|
|
height: 56px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
</style> |