guardia-messenger/components/SRTemplates.tsx

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