guardia-messenger/app/(tabs)/sr.tsx
DESKTOP-TKLFCPRython f29f525c77 refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포
- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스)
- Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포
- 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:09:17 +09:00

197 lines
8.7 KiB
TypeScript

import { useEffect, useState } from 'react'
import {
View, Text, ScrollView, StyleSheet, TouchableOpacity,
RefreshControl, ActivityIndicator, TextInput, Modal, Alert,
} from 'react-native'
import { COLORS, PRIORITY_COLOR, STATUS_COLOR } from '../../constants/Config'
import { getSRList, createSR } from '../../services/api'
const PRIORITIES = ['CRITICAL','HIGH','MEDIUM','LOW']
const TYPES = ['DEPLOY','RESTART','LOG','INQUIRY','OTHER']
export default function SRScreen() {
const [items, setItems] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [refresh, setRefresh] = useState(false)
const [creating, setCreating] = useState(false)
const [form, setForm] = useState({ title: '', description: '', priority: 'MEDIUM', sr_type: 'OTHER' })
const [modal, setModal] = useState(false)
const [saving, setSaving] = useState(false)
const load = async (r = false) => {
r ? setRefresh(true) : setLoading(true)
try {
const res = await getSRList()
setItems(res.data.content ?? res.data.items ?? res.data ?? [])
} catch {}
setLoading(false); setRefresh(false)
}
useEffect(() => { load() }, [])
const submit = async () => {
if (!form.title.trim()) { Alert.alert('제목을 입력하세요.'); return }
setSaving(true)
try {
await createSR(form)
setModal(false)
setForm({ title:'', description:'', priority:'MEDIUM', sr_type:'OTHER' })
await load()
Alert.alert('등록 완료', 'SR이 접수되었습니다.')
} catch (e: any) {
Alert.alert('오류', e.response?.data?.detail ?? 'SR 등록 실패')
} finally { setSaving(false) }
}
return (
<View style={{ flex: 1, backgroundColor: COLORS.bg }}>
<View style={s.toolbar}>
<Text style={s.toolbarTitle}> </Text>
<TouchableOpacity style={s.addBtn} onPress={() => setModal(true)}>
<Text style={s.addBtnText}>+ SR</Text>
</TouchableOpacity>
</View>
{loading
? <ActivityIndicator style={{ marginTop: 60 }} color={COLORS.accent} />
: (
<ScrollView
refreshControl={<RefreshControl refreshing={refresh} onRefresh={() => load(true)} />}
>
{items.length === 0 && (
<Text style={{ textAlign:'center', color:COLORS.muted, marginTop:60 }}>
SR이 .
</Text>
)}
{items.map(sr => (
<View key={sr.id} style={s.card}>
<View style={s.cardHead}>
<Text style={s.srId}>{sr.sr_id}</Text>
<View style={[s.statusBadge, { backgroundColor: STATUS_COLOR[sr.status]+'22' }]}>
<Text style={[s.statusText, { color: STATUS_COLOR[sr.status] ?? COLORS.muted }]}>
{sr.status}
</Text>
</View>
</View>
<Text style={s.srTitle} numberOfLines={2}>{sr.title}</Text>
<View style={s.cardFoot}>
<View style={[s.priBadge, { backgroundColor: PRIORITY_COLOR[sr.priority]+'22' }]}>
<Text style={[s.priText, { color: PRIORITY_COLOR[sr.priority] }]}>
{sr.priority}
</Text>
</View>
<Text style={s.metaText}>{sr.requested_by}</Text>
<Text style={s.metaText}>{sr.created_at?.slice(0,10)}</Text>
</View>
</View>
))}
<View style={{ height: 24 }} />
</ScrollView>
)
}
{/* SR 등록 모달 */}
<Modal visible={modal} animationType="slide" presentationStyle="pageSheet">
<View style={s.modal}>
<View style={s.modalHead}>
<Text style={s.modalTitle}> SR </Text>
<TouchableOpacity onPress={() => setModal(false)}>
<Text style={{ fontSize: 24, color: COLORS.muted }}></Text>
</TouchableOpacity>
</View>
<ScrollView style={{ padding: 20 }}>
{[
{ label: '제목 *', key: 'title', ph: 'SR 제목을 입력하세요', multi: false },
{ label: '설명', key: 'description', ph: '상세 내용을 입력하세요', multi: true },
].map(f => (
<View key={f.key} style={s.field}>
<Text style={s.label}>{f.label}</Text>
<TextInput
style={[s.input, f.multi && { height: 100, textAlignVertical: 'top' }]}
value={(form as any)[f.key]}
onChangeText={v => setForm(p => ({ ...p, [f.key]: v }))}
placeholder={f.ph}
placeholderTextColor={COLORS.muted}
multiline={f.multi}
/>
</View>
))}
<View style={s.field}>
<Text style={s.label}></Text>
<View style={s.chips}>
{PRIORITIES.map(p => (
<TouchableOpacity key={p}
style={[s.chip, form.priority === p && { backgroundColor: PRIORITY_COLOR[p], borderColor: PRIORITY_COLOR[p] }]}
onPress={() => setForm(f => ({ ...f, priority: p }))}
>
<Text style={[s.chipText, form.priority === p && { color: '#fff' }]}>{p}</Text>
</TouchableOpacity>
))}
</View>
</View>
<View style={s.field}>
<Text style={s.label}></Text>
<View style={s.chips}>
{TYPES.map(t => (
<TouchableOpacity key={t}
style={[s.chip, form.sr_type === t && s.chipActive]}
onPress={() => setForm(f => ({ ...f, sr_type: t }))}
>
<Text style={[s.chipText, form.sr_type === t && { color: '#fff' }]}>{t}</Text>
</TouchableOpacity>
))}
</View>
</View>
<TouchableOpacity style={[s.submitBtn, saving && { opacity: .6 }]} onPress={submit} disabled={saving}>
{saving
? <ActivityIndicator color="#fff" />
: <Text style={s.submitText}>SR </Text>
}
</TouchableOpacity>
</ScrollView>
</View>
</Modal>
</View>
)
}
const s = StyleSheet.create({
toolbar: { flexDirection:'row', justifyContent:'space-between', alignItems:'center',
backgroundColor:'#fff', paddingHorizontal:16, paddingVertical:12,
borderBottomWidth:1, borderBottomColor:COLORS.border },
toolbarTitle:{ fontSize:15, fontWeight:'700', color:COLORS.text },
addBtn: { backgroundColor:COLORS.accent, paddingHorizontal:14, paddingVertical:7, borderRadius:8 },
addBtnText: { color:'#fff', fontSize:13, fontWeight:'600' },
card: { backgroundColor:'#fff', marginHorizontal:16, marginTop:10, borderRadius:10,
padding:14, elevation:1 },
cardHead: { flexDirection:'row', justifyContent:'space-between', marginBottom:6 },
srId: { fontSize:11, color:COLORS.accent, fontWeight:'600' },
statusBadge: { paddingHorizontal:8, paddingVertical:2, borderRadius:10 },
statusText: { fontSize:10, fontWeight:'600' },
srTitle: { fontSize:14, fontWeight:'600', color:COLORS.text, marginBottom:8 },
cardFoot: { flexDirection:'row', alignItems:'center', gap:8 },
priBadge: { paddingHorizontal:8, paddingVertical:2, borderRadius:10 },
priText: { fontSize:10, fontWeight:'700' },
metaText: { fontSize:11, color:COLORS.muted },
modal: { flex:1, backgroundColor:'#fff' },
modalHead: { flexDirection:'row', justifyContent:'space-between', alignItems:'center',
padding:20, borderBottomWidth:1, borderBottomColor:COLORS.border },
modalTitle: { fontSize:17, fontWeight:'700', color:COLORS.text },
field: { marginBottom:16 },
label: { fontSize:11, fontWeight:'600', color:COLORS.muted, marginBottom:6,
textTransform:'uppercase', letterSpacing:.5 },
input: { borderWidth:1.5, borderColor:COLORS.border, borderRadius:9,
padding:12, fontSize:14, color:COLORS.text },
chips: { flexDirection:'row', flexWrap:'wrap', gap:8 },
chip: { paddingHorizontal:12, paddingVertical:6, borderRadius:20,
borderWidth:1, borderColor:COLORS.border },
chipActive: { backgroundColor:COLORS.accent, borderColor:COLORS.accent },
chipText: { fontSize:12, color:COLORS.text },
submitBtn: { backgroundColor:COLORS.primary, borderRadius:10, padding:15,
alignItems:'center', marginTop:8, marginBottom:32 },
submitText: { color:'#fff', fontSize:15, fontWeight:'700' },
})