115 lines
6.2 KiB
TypeScript
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 },
|
|
});
|