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

115 lines
6.2 KiB
TypeScript

import React, { useState } from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch, Alert } from 'react-native';
import { ITSM_BASE } from '../../services/api';
interface BatchItem { id: string; label: string; type: string; selected: boolean }
const ITEMS: BatchItem[] = [
{ id: 'S01', label: 'app-01 · nginx 재시작', type: 'server', selected: false },
{ id: 'S02', label: 'app-02 · 캐시 초기화', type: 'server', selected: false },
{ id: 'S03', label: 'db-01 · 슬로우쿼리 로그 수집', type: 'db', selected: false },
{ id: 'SR2041', label: 'SR-2041 · 완료 처리', type: 'sr', selected: false },
{ id: 'SR2042', label: 'SR-2042 · 담당자 변경', type: 'sr', selected: false },
{ id: 'SR2043', label: 'SR-2043 · 우선순위 High', type: 'sr', selected: false },
];
const ACTIONS = ['상태 변경', '담당자 변경', '우선순위 변경', '일괄 완료', '그룹 알림'];
export default function BatchActionScreen() {
const [items, setItems] = useState(ITEMS);
const [action, setAction] = useState('');
const [running, setRunning] = useState(false);
const toggle = (id: string) => setItems(prev => prev.map(i => i.id === id ? { ...i, selected: !i.selected } : i));
const selectAll = () => setItems(prev => prev.map(i => ({ ...i, selected: true })));
const clearAll = () => setItems(prev => prev.map(i => ({ ...i, selected: false })));
const selected = items.filter(i => i.selected);
const run = async () => {
if (!action) { Alert.alert('선택', '실행할 액션을 선택해주세요'); return; }
if (selected.length === 0) { Alert.alert('선택', '항목을 선택해주세요'); return; }
setRunning(true);
try {
await fetch(`${ITSM_BASE}/api/tasks/bulk`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, ids: selected.map(i => i.id) }),
});
Alert.alert('완료', `${selected.length}개 항목에 "${action}" 실행 완료`);
clearAll();
} catch { Alert.alert('완료', `${selected.length}개 항목 처리 완료 (오프라인)`); }
setRunning(false);
};
const typeColor = (t: string) => ({ server: '#ff8800', db: '#bb44bb', sr: '#00A0C8' })[t] || '#888';
const typeIcon = (t: string) => ({ server: '🖥', db: '🗄', sr: '📋' })[t] || '•';
return (
<ScrollView style={s.container}>
<Text style={s.title}> </Text>
<Text style={s.sub}> </Text>
<View style={s.selectBar}>
<Text style={s.selectedCount}>{selected.length} </Text>
<TouchableOpacity onPress={selectAll}><Text style={s.barBtn}></Text></TouchableOpacity>
<TouchableOpacity onPress={clearAll}><Text style={s.barBtn}></Text></TouchableOpacity>
</View>
{items.map(item => (
<TouchableOpacity key={item.id} style={[s.itemRow, item.selected && s.itemRowSelected]} onPress={() => toggle(item.id)}>
<View style={[s.checkbox, item.selected && s.checkboxSelected]}>
{item.selected && <Text style={s.checkmark}></Text>}
</View>
<Text style={s.typeIcon}>{typeIcon(item.type)}</Text>
<View style={s.itemContent}>
<Text style={s.itemLabel}>{item.label}</Text>
</View>
<View style={[s.typeBadge, { backgroundColor: typeColor(item.type) + '33' }]}>
<Text style={[s.typeText, { color: typeColor(item.type) }]}>{item.type}</Text>
</View>
</TouchableOpacity>
))}
<Text style={s.sectionTitle}> </Text>
<View style={s.actionsRow}>
{ACTIONS.map(a => (
<TouchableOpacity key={a} style={[s.actionBtn, action === a && s.actionBtnActive]} onPress={() => setAction(a)}>
<Text style={[s.actionBtnText, action === a && s.actionBtnTextActive]}>{a}</Text>
</TouchableOpacity>
))}
</View>
<TouchableOpacity style={[s.runBtn, (selected.length === 0 || !action) && s.runBtnDisabled]} onPress={run} disabled={running || selected.length === 0 || !action}>
<Text style={s.runBtnText}>{running ? '처리 중...' : `${selected.length}개 일괄 실행`}</Text>
</TouchableOpacity>
</ScrollView>
);
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0E1A', padding: 16 },
title: { color: '#fff', fontSize: 20, fontWeight: '700', marginBottom: 4 },
sub: { color: '#888', fontSize: 13, marginBottom: 16 },
selectBar: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 10, padding: 12, marginBottom: 12, gap: 12, borderWidth: 1, borderColor: '#333' },
selectedCount: { color: '#fff', fontWeight: '600', flex: 1 },
barBtn: { color: '#00A0C8', fontSize: 13 },
itemRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 10, padding: 14, marginBottom: 8, borderWidth: 1, borderColor: '#333', gap: 10 },
itemRowSelected: { borderColor: '#00A0C8', backgroundColor: '#00A0C822' },
checkbox: { width: 22, height: 22, borderRadius: 6, borderWidth: 2, borderColor: '#555', alignItems: 'center', justifyContent: 'center' },
checkboxSelected: { backgroundColor: '#00A0C8', borderColor: '#00A0C8' },
checkmark: { color: '#fff', fontWeight: '700', fontSize: 14 },
typeIcon: { fontSize: 18 },
itemContent: { flex: 1 },
itemLabel: { color: '#fff', fontSize: 13 },
typeBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6 },
typeText: { fontSize: 11, fontWeight: '600' },
sectionTitle: { color: '#fff', fontSize: 15, fontWeight: '700', marginTop: 16, marginBottom: 10 },
actionsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 16 },
actionBtn: { paddingHorizontal: 14, paddingVertical: 8, backgroundColor: '#1A1F2E', borderRadius: 10, borderWidth: 1, borderColor: '#333' },
actionBtnActive: { backgroundColor: '#003366', borderColor: '#00A0C8' },
actionBtnText: { color: '#aaa', fontSize: 13 },
actionBtnTextActive: { color: '#fff', fontWeight: '700' },
runBtn: { backgroundColor: '#00A0C8', padding: 16, borderRadius: 12, alignItems: 'center', marginTop: 8 },
runBtnDisabled: { backgroundColor: '#333' },
runBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 },
});