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

132 lines
5.9 KiB
TypeScript

import React, { useState, useEffect } from 'react'
import {
View, Text, TextInput, TouchableOpacity, StyleSheet,
ScrollView, Alert, ActivityIndicator,
} from 'react-native'
import { COLORS } from '../../constants/Config'
import { getDelegation, setDelegation, cancelDelegation, searchUsers } from '../../services/api'
export default function DelegationScreen() {
const [current, setCurrent] = useState<any>(null)
const [loading, setLoading] = useState(true)
const [query, setQuery] = useState('')
const [users, setUsers] = useState<any[]>([])
const [selected, setSelected] = useState<any>(null)
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const [reason, setReason] = useState('')
const [saving, setSaving] = useState(false)
useEffect(() => {
getDelegation().then(r => setCurrent(r.data)).catch(() => {}).finally(() => setLoading(false))
}, [])
const search = async () => {
if (!query.trim()) return
try {
const r = await searchUsers(query)
setUsers(r.data?.items ?? r.data ?? [])
} catch { setUsers([]) }
}
const save = async () => {
if (!selected || !startDate || !endDate || !reason.trim()) {
Alert.alert('입력 오류', '모든 항목을 입력해주세요.')
return
}
setSaving(true)
try {
await setDelegation({ delegate_to: selected.id, start_date: startDate, end_date: endDate, reason })
Alert.alert('완료', '대리결재가 설정됐습니다.')
const r = await getDelegation()
setCurrent(r.data)
} catch { Alert.alert('오류', '저장 중 오류가 발생했습니다.') }
finally { setSaving(false) }
}
const cancel = async (id: number) => {
Alert.alert('취소 확인', '대리결재를 해제하시겠습니까?', [
{ text: '아니오', style: 'cancel' },
{ text: '해제', style: 'destructive', onPress: async () => {
await cancelDelegation(id)
setCurrent(null)
}},
])
}
if (loading) return <ActivityIndicator style={{ flex: 1 }} color={COLORS.accent} />
return (
<ScrollView style={s.container} contentContainerStyle={{ padding: 16 }}>
{current && (
<View style={s.card}>
<Text style={s.label}> </Text>
<Text style={s.value}>: {current.delegate_name ?? current.delegate_to}</Text>
<Text style={s.value}>: {current.start_date} ~ {current.end_date}</Text>
<Text style={s.value}>: {current.reason}</Text>
<TouchableOpacity style={s.cancelBtn} onPress={() => cancel(current.id)}>
<Text style={s.cancelBtnText}></Text>
</TouchableOpacity>
</View>
)}
<Text style={s.sectionTitle}> </Text>
<View style={s.row}>
<TextInput
style={[s.input, { flex: 1 }]}
value={query}
onChangeText={setQuery}
placeholder="사용자 검색..."
onSubmitEditing={search}
/>
<TouchableOpacity style={s.searchBtn} onPress={search}>
<Text style={s.searchBtnText}></Text>
</TouchableOpacity>
</View>
{users.map((u: any) => (
<TouchableOpacity
key={u.id ?? u.username}
style={[s.userItem, selected?.id === u.id && s.userItemSelected]}
onPress={() => setSelected(u)}
>
<Text style={s.userName}>{u.display_name ?? u.username}</Text>
<Text style={s.userRole}>{u.role}</Text>
</TouchableOpacity>
))}
{selected && <Text style={s.selectedText}>: {selected.display_name ?? selected.username}</Text>}
<TextInput style={s.input} value={startDate} onChangeText={setStartDate} placeholder="시작일 (YYYY-MM-DD)" />
<TextInput style={s.input} value={endDate} onChangeText={setEndDate} placeholder="종료일 (YYYY-MM-DD)" />
<TextInput style={[s.input, { height: 80 }]} value={reason} onChangeText={setReason} placeholder="사유" multiline />
<TouchableOpacity style={s.saveBtn} onPress={save} disabled={saving}>
<Text style={s.saveBtnText}>{saving ? '저장 중...' : '대리결재 설정'}</Text>
</TouchableOpacity>
</ScrollView>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 16, elevation: 1 },
label: { fontSize: 12, color: COLORS.muted, marginBottom: 4 },
value: { fontSize: 14, color: COLORS.text, marginBottom: 2 },
cancelBtn: { marginTop: 8, backgroundColor: COLORS.danger, borderRadius: 6, padding: 8, alignItems: 'center' },
cancelBtnText: { color: '#fff', fontWeight: '700' },
sectionTitle: { fontSize: 16, fontWeight: '700', color: COLORS.text, marginBottom: 12 },
row: { flexDirection: 'row', gap: 8, marginBottom: 8 },
input: { backgroundColor: '#fff', borderRadius: 8, borderWidth: 1, borderColor: COLORS.border, paddingHorizontal: 12, paddingVertical: 10, marginBottom: 10, fontSize: 14, color: COLORS.text },
searchBtn: { backgroundColor: COLORS.accent, borderRadius: 8, paddingHorizontal: 16, justifyContent: 'center' },
searchBtnText: { color: '#fff', fontWeight: '700' },
userItem: { backgroundColor: '#fff', borderRadius: 8, padding: 10, marginBottom: 4, flexDirection: 'row', justifyContent: 'space-between', borderWidth: 1, borderColor: COLORS.border },
userItemSelected: { borderColor: COLORS.accent, backgroundColor: COLORS.light },
userName: { fontSize: 14, fontWeight: '600', color: COLORS.text },
userRole: { fontSize: 12, color: COLORS.muted },
selectedText: { fontSize: 13, color: COLORS.accent, marginBottom: 8 },
saveBtn: { backgroundColor: COLORS.accent, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 8 },
saveBtnText: { color: '#fff', fontWeight: '800', fontSize: 15 },
})