106 lines
4.5 KiB
TypeScript
106 lines
4.5 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import {
|
|
View, Text, Modal, FlatList, TouchableOpacity, StyleSheet, ActivityIndicator,
|
|
} from 'react-native'
|
|
import { COLORS, PRIORITY_COLOR } from '../constants/Config'
|
|
import { getSRTemplates } from '../services/api'
|
|
|
|
export interface SRTemplate {
|
|
id: number | string
|
|
name: string
|
|
title?: string
|
|
category?: string
|
|
sr_type?: string
|
|
priority?: string
|
|
description?: string
|
|
}
|
|
|
|
interface Props {
|
|
visible: boolean
|
|
onClose: () => void
|
|
onSelect: (tpl: SRTemplate) => void
|
|
}
|
|
|
|
const FALLBACK: SRTemplate[] = [
|
|
{ id: 'deploy', name: '배포 요청', title: '[배포] 서비스 배포 요청', category: 'DEPLOY', priority: 'HIGH', description: '대상 서버 / 브랜치 / 배포 시간을 기재하세요.' },
|
|
{ id: 'restart', name: '서비스 재기동', title: '[재기동] 서비스 재시작 요청', category: 'RESTART', priority: 'MEDIUM', description: '재기동 대상 서비스명을 기재하세요.' },
|
|
{ id: 'log', name: '로그 확인', title: '[로그] 로그 조회 요청', category: 'LOG', priority: 'LOW', description: '조회 기간 / 키워드를 기재하세요.' },
|
|
{ id: 'incident',name: '장애 신고', title: '[장애] 긴급 장애 신고', category: 'OTHER', priority: 'CRITICAL',description: '발생 시각 / 증상 / 영향 범위를 기재하세요.' },
|
|
]
|
|
|
|
/**
|
|
* 기능 #10 — SR 템플릿 선택 모달
|
|
* GET /api/tasks/templates → 실패 시 기본 템플릿 사용
|
|
*/
|
|
export default function SRTemplates({ visible, onClose, onSelect }: Props) {
|
|
const [items, setItems] = useState<SRTemplate[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
if (!visible) return
|
|
let alive = true
|
|
setLoading(true)
|
|
;(async () => {
|
|
try {
|
|
const res = await getSRTemplates()
|
|
const list: SRTemplate[] = res.data?.content ?? res.data?.items ?? res.data ?? []
|
|
if (alive) setItems(list.length ? list : FALLBACK)
|
|
} catch {
|
|
if (alive) setItems(FALLBACK)
|
|
} finally {
|
|
if (alive) setLoading(false)
|
|
}
|
|
})()
|
|
return () => { alive = false }
|
|
}, [visible])
|
|
|
|
return (
|
|
<Modal visible={visible} animationType="slide" transparent onRequestClose={onClose}>
|
|
<View style={s.overlay}>
|
|
<View style={s.sheet}>
|
|
<View style={s.head}>
|
|
<Text style={s.title}>SR 템플릿 선택</Text>
|
|
<TouchableOpacity onPress={onClose}>
|
|
<Text style={s.close}>✕</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
{loading ? (
|
|
<ActivityIndicator style={{ marginVertical: 30 }} color={COLORS.accent} />
|
|
) : (
|
|
<FlatList
|
|
data={items}
|
|
keyExtractor={t => String(t.id)}
|
|
renderItem={({ item }) => (
|
|
<TouchableOpacity style={s.row} onPress={() => { onSelect(item); onClose() }}>
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={s.name}>{item.name}</Text>
|
|
{!!item.description && <Text style={s.desc} numberOfLines={1}>{item.description}</Text>}
|
|
</View>
|
|
{!!item.priority && (
|
|
<View style={[s.pri, { backgroundColor: (PRIORITY_COLOR[item.priority] ?? COLORS.muted) + '22' }]}>
|
|
<Text style={[s.priText, { color: PRIORITY_COLOR[item.priority] ?? COLORS.muted }]}>{item.priority}</Text>
|
|
</View>
|
|
)}
|
|
</TouchableOpacity>
|
|
)}
|
|
/>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
const s = StyleSheet.create({
|
|
overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', justifyContent: 'flex-end' },
|
|
sheet: { backgroundColor: '#fff', borderTopLeftRadius: 18, borderTopRightRadius: 18, maxHeight: '70%', paddingBottom: 24 },
|
|
head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 18, borderBottomWidth: 1, borderBottomColor: COLORS.border },
|
|
title: { fontSize: 16, fontWeight: '700', color: COLORS.text },
|
|
close: { fontSize: 22, color: COLORS.muted },
|
|
row: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 18, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.border, gap: 10 },
|
|
name: { fontSize: 14, fontWeight: '600', color: COLORS.text },
|
|
desc: { fontSize: 12, color: COLORS.muted, marginTop: 3 },
|
|
pri: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 10 },
|
|
priText: { fontSize: 10, fontWeight: '700' },
|
|
})
|