guardia-messenger/components/SRSatisfaction.tsx

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' },
})