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

95 lines
4.4 KiB
TypeScript

import React, { useState, useRef, useCallback } from 'react'
import { View, Text, TouchableOpacity, StyleSheet, Alert, ScrollView, TextInput } from 'react-native'
import { useFocusEffect } from 'expo-router'
import { COLORS } from '../../constants/Config'
import client from '../../services/api'
export default function ESignatureScreen() {
const [docs, setDocs] = useState<any[]>([])
const [selected, setSelected] = useState<any>(null)
const [pin, setPin] = useState('')
const [loading, setLoading] = useState(false)
const load = useCallback(async () => {
setLoading(true)
try { const r = await client.get('/api/approvals/pending-docs'); setDocs(r.data?.docs ?? r.data?.items ?? []) }
catch { setDocs([]) } finally { setLoading(false) }
}, [])
useFocusEffect(useCallback(() => { load() }, [load]))
const sign = async () => {
if (!selected) return
if (!pin || pin.length < 4) { Alert.alert('오류', 'PIN 4자리 이상 입력하세요.'); return }
try {
await client.post(`/api/approvals/${selected.id}/sign`, { pin_hash: pin })
Alert.alert('완료', '전자서명이 완료됐습니다.')
setSelected(null)
setPin('')
load()
} catch { Alert.alert('오류', '서명에 실패했습니다.') }
}
if (selected) {
return (
<View style={s.container}>
<View style={s.docCard}>
<Text style={s.docTitle}>{selected.title}</Text>
<Text style={s.docMeta}>: {selected.requester_name ?? '-'} · {selected.created_at?.slice(0, 10) ?? ''}</Text>
<Text style={s.docDesc} numberOfLines={4}>{selected.content ?? selected.description ?? ''}</Text>
</View>
<Text style={s.pinLabel}> PIN </Text>
<TextInput
style={s.pinInput}
value={pin}
onChangeText={setPin}
placeholder="PIN (4자리 이상)"
secureTextEntry
keyboardType="numeric"
maxLength={8}
/>
<TouchableOpacity style={s.signBtn} onPress={sign}>
<Text style={s.signText}> </Text>
</TouchableOpacity>
<TouchableOpacity style={s.cancelBtn} onPress={() => { setSelected(null); setPin('') }}>
<Text style={s.cancelText}></Text>
</TouchableOpacity>
</View>
)
}
return (
<ScrollView style={s.container} contentContainerStyle={{ padding: 12 }}>
<Text style={s.header}> </Text>
{docs.length === 0 && <Text style={s.empty}> .</Text>}
{docs.map((doc, i) => (
<TouchableOpacity key={i} style={s.card} onPress={() => setSelected(doc)}>
<Text style={s.cardTitle}>{doc.title}</Text>
<Text style={s.cardMeta}>{doc.requester_name ?? '-'} · {doc.created_at?.slice(0, 10) ?? ''}</Text>
<Text style={s.cardAction}> </Text>
</TouchableOpacity>
))}
</ScrollView>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 },
card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 },
cardTitle: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 4 },
cardMeta: { fontSize: 11, color: COLORS.muted, marginBottom: 8 },
cardAction: { color: COLORS.accent, fontSize: 13, fontWeight: '700' },
docCard: { backgroundColor: '#fff', borderRadius: 12, padding: 16, margin: 12, elevation: 1 },
docTitle: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 4 },
docMeta: { fontSize: 12, color: COLORS.muted, marginBottom: 10 },
docDesc: { fontSize: 13, color: COLORS.text, lineHeight: 20 },
pinLabel: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginHorizontal: 12, marginTop: 8 },
pinInput: { backgroundColor: '#fff', borderRadius: 10, padding: 14, margin: 12, fontSize: 18, letterSpacing: 4, elevation: 1, color: COLORS.text },
signBtn: { backgroundColor: COLORS.success, borderRadius: 10, padding: 14, margin: 12, alignItems: 'center' },
signText: { color: '#fff', fontSize: 15, fontWeight: '800' },
cancelBtn: { borderRadius: 10, padding: 12, marginHorizontal: 12, alignItems: 'center' },
cancelText: { color: COLORS.muted, fontSize: 14 },
empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 },
})