import { useEffect, useState } from 'react' import { View, Text, ScrollView, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, RefreshControl, } from 'react-native' import { COLORS, PRIORITY_COLOR, STATUS_COLOR } from '../../constants/Config' import { getSRList, batchUpdateSR } from '../../services/api' const STATUS_OPTIONS = ['IN_PROGRESS', 'PENDING_APPROVAL', 'COMPLETED', 'REJECTED'] interface SR { id: number sr_id?: string title: string status?: string priority?: string } /** * 기능 #12 — 일괄 SR 상태 변경 * 체크박스 다중 선택 → PATCH /api/tasks/batch { ids, status } */ export default function SRBatchScreen() { const [items, setItems] = useState([]) const [selected, setSelected] = useState>(new Set()) const [loading, setLoading] = useState(true) const [refresh, setRefresh] = useState(false) const [applying, setApplying] = useState(false) const [target, setTarget] = useState('IN_PROGRESS') const load = async (r = false) => { r ? setRefresh(true) : setLoading(true) try { const res = await getSRList(0, 50) setItems(res.data?.content ?? res.data?.items ?? res.data ?? []) } catch { setItems([]) } finally { setLoading(false); setRefresh(false) } } useEffect(() => { load() }, []) const toggle = (id: number) => { setSelected(prev => { const next = new Set(prev) next.has(id) ? next.delete(id) : next.add(id) return next }) } const toggleAll = () => { setSelected(prev => prev.size === items.length ? new Set() : new Set(items.map(i => i.id)) ) } const apply = async () => { if (selected.size === 0) { Alert.alert('SR을 선택하세요.'); return } Alert.alert( '일괄 변경', `${selected.size}건을 '${target}' 상태로 변경할까요?`, [ { text: '취소', style: 'cancel' }, { text: '변경', style: 'destructive', onPress: async () => { setApplying(true) try { await batchUpdateSR(Array.from(selected), target) setSelected(new Set()) await load() Alert.alert('완료', '일괄 상태 변경이 적용되었습니다.') } catch (e: any) { Alert.alert('오류', e.response?.data?.detail ?? '일괄 변경 실패') } finally { setApplying(false) } }, }, ] ) } return ( {selected.size === items.length && items.length > 0 ? '☑ 전체 해제' : '☐ 전체 선택'} {selected.size}건 선택 {/* 대상 상태 선택 */} {STATUS_OPTIONS.map(st => ( setTarget(st)} > {st} ))} {loading ? ( ) : ( load(true)} />}> {items.map(sr => { const on = selected.has(sr.id) return ( toggle(sr.id)}> {on ? '☑' : '☐'} {sr.sr_id ?? `#${sr.id}`} {!!sr.status && ( {sr.status} )} {sr.title} {!!sr.priority && ( ● {sr.priority} )} ) })} )} {applying ? : 선택 {selected.size}건 → {target}} ) } const s = StyleSheet.create({ toolbar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: COLORS.border }, selAll: { fontSize: 14, fontWeight: '700', color: COLORS.accent }, count: { fontSize: 13, color: COLORS.muted }, statusRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, padding: 12, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, statusChip: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: COLORS.border }, statusChipText: { fontSize: 11, color: COLORS.text }, card: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#fff', marginHorizontal: 16, marginTop: 10, borderRadius: 10, padding: 14 }, cardOn: { borderWidth: 1.5, borderColor: COLORS.accent, backgroundColor: COLORS.light }, check: { fontSize: 22, color: COLORS.muted }, checkOn: { color: COLORS.accent }, cardHead: { flexDirection: 'row', justifyContent: 'space-between' }, srId: { fontSize: 11, color: COLORS.accent, fontWeight: '700' }, status: { fontSize: 10, fontWeight: '700' }, title: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginTop: 3 }, pri: { fontSize: 10, fontWeight: '700', marginTop: 4 }, footer: { position: 'absolute', bottom: 0, left: 0, right: 0, padding: 14, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: COLORS.border }, applyBtn: { backgroundColor: COLORS.primary, borderRadius: 12, padding: 15, alignItems: 'center' }, applyText: { color: '#fff', fontSize: 15, fontWeight: '800' }, })