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

83 lines
4.4 KiB
TypeScript

import React, { useState } from 'react'
import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, ActivityIndicator } from 'react-native'
import { COLORS } from '../../constants/Config'
import client from '../../services/api'
export default function NFCAssetScreen() {
const [scanning, setScanning] = useState(false)
const [asset, setAsset] = useState<any>(null)
const startScan = async () => {
setScanning(true)
setAsset(null)
try {
const NFC = (() => { try { return require('react-native-nfc-manager').default } catch { return null } })()
if (!NFC) { Alert.alert('NFC 미지원', '이 기기는 NFC를 지원하지 않거나 모듈이 설치되지 않았습니다.'); setScanning(false); return }
await NFC.start()
await NFC.requestTechnology(['Ndef'])
const tag = await NFC.getTag()
const payload = tag?.ndefMessage?.[0]?.payload
const assetId = payload ? String.fromCharCode(...payload).replace(/^\x02en/, '') : null
if (!assetId) { Alert.alert('오류', 'NFC 태그에서 자산 ID를 읽을 수 없습니다.'); setScanning(false); return }
const r = await client.get(`/api/cmdb/assets/${assetId}`)
setAsset(r.data)
} catch (e: any) {
if (!e.message?.includes('cancel')) Alert.alert('스캔 실패', e.message ?? 'NFC 스캔에 실패했습니다.')
} finally {
setScanning(false)
try { const NFC = require('react-native-nfc-manager').default; NFC.cancelTechnologyRequest() } catch {}
}
}
const checkin = async () => {
if (!asset) return
try {
await client.post('/api/servers/field-checkin', { server_id: asset.id, source: 'nfc', method: 'nfc_tag' })
Alert.alert('완료', `${asset.hostname ?? asset.name} 실사 체크인 완료!`)
} catch { Alert.alert('오류', '체크인에 실패했습니다.') }
}
return (
<ScrollView style={s.container} contentContainerStyle={{ padding: 16 }}>
<Text style={s.title}>NFC </Text>
<Text style={s.subtitle}>NFC · .</Text>
<TouchableOpacity style={[s.scanBtn, { opacity: scanning ? 0.6 : 1 }]} onPress={startScan} disabled={scanning}>
{scanning ? <ActivityIndicator color="#fff" /> : <Text style={s.scanText}>NFC </Text>}
</TouchableOpacity>
{asset && (
<View style={s.assetCard}>
<Text style={s.assetName}>{asset.hostname ?? asset.name}</Text>
<View style={s.infoRow}><Text style={s.label}>IP</Text><Text style={s.val}>***.***.***</Text></View>
<View style={s.infoRow}><Text style={s.label}>OS</Text><Text style={s.val}>{asset.os_name ?? '-'}</Text></View>
<View style={s.infoRow}><Text style={s.label}></Text><Text style={s.val}>{asset.location ?? '-'}</Text></View>
<View style={s.infoRow}><Text style={s.label}></Text><Text style={s.val}>{asset.institution_name ?? '-'}</Text></View>
<View style={s.infoRow}><Text style={s.label}></Text><Text style={[s.val, { color: asset.status === 'active' ? COLORS.success : COLORS.muted }]}>{asset.status ?? '-'}</Text></View>
<TouchableOpacity style={s.checkinBtn} onPress={checkin}>
<Text style={s.checkinText}> </Text>
</TouchableOpacity>
</View>
)}
</ScrollView>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
title: { fontSize: 22, fontWeight: '800', color: COLORS.text, marginBottom: 8 },
subtitle: { fontSize: 13, color: COLORS.muted, marginBottom: 24 },
scanBtn: { backgroundColor: COLORS.accent, borderRadius: 16, padding: 24, alignItems: 'center', marginBottom: 24, elevation: 3 },
scanText: { color: '#fff', fontSize: 18, fontWeight: '800' },
assetCard: { backgroundColor: '#fff', borderRadius: 16, padding: 16, elevation: 2 },
assetName: { fontSize: 20, fontWeight: '800', color: COLORS.text, marginBottom: 16 },
infoRow: { flexDirection: 'row', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: COLORS.border },
label: { width: 60, fontSize: 12, color: COLORS.muted, fontWeight: '600' },
val: { flex: 1, fontSize: 13, color: COLORS.text },
checkinBtn: { backgroundColor: COLORS.success, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 16 },
checkinText:{ color: '#fff', fontSize: 14, fontWeight: '800' },
})