95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
View, Text, Modal, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Alert,
|
|
} from 'react-native'
|
|
import { COLORS } from '../constants/Config'
|
|
import { rateSR } from '../services/api'
|
|
|
|
interface Props {
|
|
visible: boolean
|
|
srId: number
|
|
onClose: () => void
|
|
onSubmitted?: (score: number) => void
|
|
}
|
|
|
|
const LABELS = ['', '매우 불만족', '불만족', '보통', '만족', '매우 만족']
|
|
|
|
/**
|
|
* 기능 #14 — 완료 후 만족도 별점 모달
|
|
* 별점(1~5) + 한줄 피드백 → POST /api/tasks/{id}/rating
|
|
*/
|
|
export default function SRSatisfaction({ visible, srId, onClose, onSubmitted }: Props) {
|
|
const [score, setScore] = useState(0)
|
|
const [comment, setComment] = useState('')
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
const submit = async () => {
|
|
if (score === 0) { Alert.alert('별점을 선택하세요.'); return }
|
|
setSaving(true)
|
|
try {
|
|
await rateSR(srId, score, comment)
|
|
onSubmitted?.(score)
|
|
setScore(0); setComment('')
|
|
onClose()
|
|
Alert.alert('감사합니다', '소중한 평가가 등록되었습니다.')
|
|
} catch (e: any) {
|
|
Alert.alert('오류', e.response?.data?.detail ?? '평가 등록 실패')
|
|
} finally { setSaving(false) }
|
|
}
|
|
|
|
return (
|
|
<Modal visible={visible} animationType="fade" transparent onRequestClose={onClose}>
|
|
<View style={s.overlay}>
|
|
<View style={s.box}>
|
|
<Text style={s.title}>서비스 만족도 평가</Text>
|
|
<Text style={s.sub}>SR 처리에 만족하셨나요?</Text>
|
|
|
|
<View style={s.stars}>
|
|
{[1, 2, 3, 4, 5].map(n => (
|
|
<TouchableOpacity key={n} onPress={() => setScore(n)}>
|
|
<Text style={[s.star, n <= score && s.starActive]}>★</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
{score > 0 && <Text style={s.scoreLabel}>{LABELS[score]}</Text>}
|
|
|
|
<TextInput
|
|
style={s.input}
|
|
value={comment}
|
|
onChangeText={setComment}
|
|
placeholder="한줄 피드백 (선택)"
|
|
placeholderTextColor={COLORS.muted}
|
|
multiline
|
|
/>
|
|
|
|
<View style={s.actions}>
|
|
<TouchableOpacity style={s.cancel} onPress={onClose}>
|
|
<Text style={s.cancelText}>나중에</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity style={[s.submit, saving && { opacity: 0.6 }]} onPress={submit} disabled={saving}>
|
|
{saving ? <ActivityIndicator color="#fff" /> : <Text style={s.submitText}>평가 제출</Text>}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
const s = StyleSheet.create({
|
|
overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'center', padding: 28 },
|
|
box: { backgroundColor: '#fff', borderRadius: 16, padding: 22 },
|
|
title: { fontSize: 17, fontWeight: '800', color: COLORS.text, textAlign: 'center' },
|
|
sub: { fontSize: 13, color: COLORS.muted, textAlign: 'center', marginTop: 6 },
|
|
stars: { flexDirection: 'row', justifyContent: 'center', gap: 6, marginTop: 18 },
|
|
star: { fontSize: 40, color: COLORS.border },
|
|
starActive: { color: '#f59e0b' },
|
|
scoreLabel: { textAlign: 'center', fontSize: 13, fontWeight: '700', color: COLORS.accent, marginTop: 8 },
|
|
input: { borderWidth: 1.5, borderColor: COLORS.border, borderRadius: 10, padding: 12, fontSize: 13, color: COLORS.text, marginTop: 16, minHeight: 60, textAlignVertical: 'top' },
|
|
actions: { flexDirection: 'row', gap: 10, marginTop: 18 },
|
|
cancel: { flex: 1, paddingVertical: 13, borderRadius: 10, borderWidth: 1.5, borderColor: COLORS.border, alignItems: 'center' },
|
|
cancelText: { fontSize: 14, fontWeight: '600', color: COLORS.muted },
|
|
submit: { flex: 2, paddingVertical: 13, borderRadius: 10, backgroundColor: COLORS.primary, alignItems: 'center' },
|
|
submitText: { fontSize: 14, fontWeight: '700', color: '#fff' },
|
|
})
|