125 lines
4.9 KiB
TypeScript
125 lines
4.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import {
|
|
View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Alert,
|
|
} from 'react-native'
|
|
import { COLORS } from '../constants/Config'
|
|
import { getSRComments, addSRComment } from '../services/api'
|
|
|
|
interface CommentItem {
|
|
id: number | string
|
|
content: string
|
|
author?: string
|
|
is_internal?: boolean
|
|
created_at?: string
|
|
}
|
|
|
|
interface Props {
|
|
srId: number
|
|
}
|
|
|
|
/**
|
|
* 기능 #13 — 내부/외부 코멘트 구분 컴포넌트
|
|
* 내부: 자물쇠(담당자 전용), 외부: 지구본(기관 공개)
|
|
* POST /api/tasks/{id}/comments { content, is_internal }
|
|
*/
|
|
export default function Comment({ srId }: Props) {
|
|
const [items, setItems] = useState<CommentItem[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [text, setText] = useState('')
|
|
const [isInternal, setIsInternal] = useState(true)
|
|
const [sending, setSending] = useState(false)
|
|
|
|
const load = async () => {
|
|
try {
|
|
const res = await getSRComments(srId)
|
|
setItems(res.data?.content ?? res.data?.items ?? res.data ?? [])
|
|
} catch { setItems([]) }
|
|
finally { setLoading(false) }
|
|
}
|
|
|
|
useEffect(() => { load() }, [srId])
|
|
|
|
const submit = async () => {
|
|
if (!text.trim()) return
|
|
setSending(true)
|
|
try {
|
|
await addSRComment(srId, text.trim(), isInternal)
|
|
setText('')
|
|
await load()
|
|
} catch (e: any) {
|
|
Alert.alert('오류', e.response?.data?.detail ?? '코멘트 등록 실패')
|
|
} finally { setSending(false) }
|
|
}
|
|
|
|
return (
|
|
<View>
|
|
{loading ? (
|
|
<ActivityIndicator color={COLORS.accent} style={{ marginVertical: 12 }} />
|
|
) : items.length === 0 ? (
|
|
<Text style={s.empty}>코멘트가 없습니다.</Text>
|
|
) : (
|
|
items.map(c => (
|
|
<View key={c.id} style={[s.bubble, c.is_internal ? s.internal : s.external]}>
|
|
<View style={s.bubbleHead}>
|
|
<Text style={s.tag}>{c.is_internal ? '🔒 내부' : '🌐 외부'}</Text>
|
|
<Text style={s.author}>{c.author ?? '담당자'}</Text>
|
|
</View>
|
|
<Text style={s.content}>{c.content}</Text>
|
|
{!!c.created_at && <Text style={s.time}>{c.created_at.slice(0, 16).replace('T', ' ')}</Text>}
|
|
</View>
|
|
))
|
|
)}
|
|
|
|
{/* 입력 영역 */}
|
|
<View style={s.toggleRow}>
|
|
<TouchableOpacity
|
|
style={[s.toggle, isInternal && s.toggleActive]}
|
|
onPress={() => setIsInternal(true)}
|
|
>
|
|
<Text style={[s.toggleText, isInternal && s.toggleTextActive]}>🔒 내부</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[s.toggle, !isInternal && s.toggleActive]}
|
|
onPress={() => setIsInternal(false)}
|
|
>
|
|
<Text style={[s.toggleText, !isInternal && s.toggleTextActive]}>🌐 외부</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
<View style={s.inputRow}>
|
|
<TextInput
|
|
style={s.input}
|
|
value={text}
|
|
onChangeText={setText}
|
|
placeholder={isInternal ? '내부 코멘트 (담당자만 열람)' : '외부 코멘트 (기관 공개)'}
|
|
placeholderTextColor={COLORS.muted}
|
|
multiline
|
|
/>
|
|
<TouchableOpacity style={[s.sendBtn, sending && { opacity: 0.6 }]} onPress={submit} disabled={sending}>
|
|
{sending ? <ActivityIndicator color="#fff" size="small" /> : <Text style={s.sendText}>등록</Text>}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const s = StyleSheet.create({
|
|
empty: { color: COLORS.muted, fontSize: 12, paddingVertical: 8 },
|
|
bubble: { borderRadius: 10, padding: 12, marginBottom: 8, borderLeftWidth: 3 },
|
|
internal: { backgroundColor: '#FFF7ED', borderLeftColor: COLORS.warning },
|
|
external: { backgroundColor: COLORS.light, borderLeftColor: COLORS.accent },
|
|
bubbleHead: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 },
|
|
tag: { fontSize: 11, fontWeight: '700', color: COLORS.text },
|
|
author: { fontSize: 11, color: COLORS.muted },
|
|
content: { fontSize: 13, color: COLORS.text, lineHeight: 18 },
|
|
time: { fontSize: 10, color: COLORS.muted, marginTop: 4 },
|
|
toggleRow: { flexDirection: 'row', gap: 8, marginTop: 12, marginBottom: 8 },
|
|
toggle: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: COLORS.border },
|
|
toggleActive: { backgroundColor: COLORS.primary, borderColor: COLORS.primary },
|
|
toggleText: { fontSize: 12, color: COLORS.text },
|
|
toggleTextActive: { color: '#fff', fontWeight: '700' },
|
|
inputRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 8 },
|
|
input: { flex: 1, borderWidth: 1.5, borderColor: COLORS.border, borderRadius: 9, padding: 10, fontSize: 13, color: COLORS.text, maxHeight: 100 },
|
|
sendBtn: { backgroundColor: COLORS.accent, borderRadius: 9, paddingHorizontal: 16, paddingVertical: 11 },
|
|
sendText: { color: '#fff', fontSize: 13, fontWeight: '700' },
|
|
})
|