guardia-messenger/app/(tabs)/sla_exception.tsx

117 lines
5.1 KiB
TypeScript

import React, { useState, useCallback } from 'react'
import {
View, Text, FlatList, Modal, TextInput, TouchableOpacity,
StyleSheet, Alert, RefreshControl, ActivityIndicator,
} from 'react-native'
import { useFocusEffect } from 'expo-router'
import { COLORS } from '../../constants/Config'
import { getSLAExceptionPending, requestSLAException } from '../../services/api'
export default function SLAExceptionScreen() {
const [items, setItems] = useState<any[]>([])
const [loading, setLoading] = useState(false)
const [modal, setModal] = useState<any>(null)
const [reason, setReason] = useState('')
const [deadline, setDeadline] = useState('')
const [saving, setSaving] = useState(false)
const load = useCallback(async () => {
setLoading(true)
try {
const r = await getSLAExceptionPending()
setItems(r.data?.items ?? r.data ?? [])
} catch { setItems([]) }
finally { setLoading(false) }
}, [])
useFocusEffect(useCallback(() => { load() }, [load]))
const open = (item: any) => { setModal(item); setReason(''); setDeadline('') }
const submit = async () => {
if (!reason.trim() || !deadline.trim()) { Alert.alert('오류', '사유와 새 기한을 입력해주세요.'); return }
setSaving(true)
try {
await requestSLAException(modal.sr_id, { reason, new_deadline: deadline })
setModal(null); load()
} catch { Alert.alert('오류', '제출 중 오류가 발생했습니다.') }
finally { setSaving(false) }
}
const renderItem = ({ item }: { item: any }) => (
<TouchableOpacity style={s.card} onPress={() => open(item)}>
<Text style={s.title} numberOfLines={2}>{item.title}</Text>
<View style={s.row}>
<View style={[s.badge, { backgroundColor: item.sla_breached ? COLORS.danger : COLORS.warning }]}>
<Text style={s.badgeText}>{item.sla_breached ? 'SLA 위반' : 'SLA 임박'}</Text>
</View>
<Text style={s.meta}>: {item.sla_deadline?.slice(0, 10) ?? '-'}</Text>
</View>
<Text style={s.hint}> </Text>
</TouchableOpacity>
)
return (
<View style={s.container}>
<FlatList
data={items}
keyExtractor={i => String(i.sr_id)}
renderItem={renderItem}
refreshControl={<RefreshControl refreshing={loading} onRefresh={load} />}
ListEmptyComponent={<Text style={s.empty}>SLA .</Text>}
contentContainerStyle={{ padding: 12 }}
/>
<Modal visible={!!modal} transparent animationType="slide">
<View style={s.overlay}>
<View style={s.modalBox}>
<Text style={s.modalTitle}>SLA </Text>
<Text style={s.modalSR}>{modal?.title}</Text>
<TextInput
style={s.input}
value={reason}
onChangeText={setReason}
placeholder="예외 사유를 입력하세요"
multiline
/>
<TextInput
style={s.input}
value={deadline}
onChangeText={setDeadline}
placeholder="새 기한 (YYYY-MM-DD HH:MM)"
/>
<View style={s.modalBtns}>
<TouchableOpacity style={[s.btn, { backgroundColor: COLORS.border }]} onPress={() => setModal(null)}>
<Text style={s.btnText}></Text>
</TouchableOpacity>
<TouchableOpacity style={[s.btn, { backgroundColor: COLORS.accent }]} onPress={submit} disabled={saving}>
<Text style={[s.btnText, { color: '#fff' }]}>{saving ? '제출 중...' : '제출'}</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</View>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 },
title: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginBottom: 6 },
row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4 },
badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 },
badgeText: { fontSize: 11, color: '#fff', fontWeight: '700' },
meta: { fontSize: 12, color: COLORS.muted },
hint: { fontSize: 11, color: COLORS.accent, marginTop: 4 },
empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 },
overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' },
modalBox: { backgroundColor: '#fff', borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 },
modalTitle: { fontSize: 17, fontWeight: '800', color: COLORS.text, marginBottom: 8 },
modalSR: { fontSize: 13, color: COLORS.muted, marginBottom: 12 },
input: { borderWidth: 1, borderColor: COLORS.border, borderRadius: 8, padding: 10, marginBottom: 10, fontSize: 14, color: COLORS.text },
modalBtns: { flexDirection: 'row', gap: 10, marginTop: 4 },
btn: { flex: 1, borderRadius: 8, padding: 12, alignItems: 'center' },
btnText: { fontWeight: '700', fontSize: 14 },
})