75 lines
3.1 KiB
TypeScript
75 lines
3.1 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { ScrollView, StyleSheet, Text, View } from 'react-native'
|
|
import { useLocalSearchParams } from 'expo-router'
|
|
import { useTheme } from '../constants/ThemeContext'
|
|
import { type ThemeTokens } from '../constants/ItmsTheme'
|
|
import { Card, Loading, StatusPill, ErrText, SectionTitle } from '../components/ui'
|
|
import { rowTitle, rowStatus, rowEntries } from '../constants/rows'
|
|
import { itmsIncidentDetail, itmsErrorMessage, type RowMap } from './itmsApi'
|
|
|
|
/*
|
|
* 인시던트 상세(SvcDeskController /srm/getIncidentBySeq.do — body {inIncidentSeq:int}).
|
|
* 목록 raw 즉시 표시 후 상세 API 로 보강.
|
|
*/
|
|
export default function IncidentDetail() {
|
|
const { seq, raw } = useLocalSearchParams<{ seq?: string; raw?: string }>()
|
|
const { t } = useTheme()
|
|
const s = makeStyles(t)
|
|
const seed: RowMap = raw ? safeParse(raw) : {}
|
|
const [row, setRow] = useState<RowMap>(seed)
|
|
const seqNum = seq ? Number(seq) : NaN
|
|
const [loading, setLoading] = useState(Number.isFinite(seqNum))
|
|
const [err, setErr] = useState('')
|
|
|
|
useEffect(() => {
|
|
let alive = true
|
|
if (!Number.isFinite(seqNum)) { setLoading(false); return }
|
|
;(async () => {
|
|
try {
|
|
const detail = await itmsIncidentDetail(seqNum)
|
|
const merged = { ...seed, ...(detail?.resultVo ?? detail?.incidentVo ?? detail ?? {}) }
|
|
if (alive) setRow(merged)
|
|
} catch (e) { if (alive) setErr(itmsErrorMessage(e)) }
|
|
finally { if (alive) setLoading(false) }
|
|
})()
|
|
return () => { alive = false }
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [seq])
|
|
|
|
if (loading && Object.keys(seed).length === 0) return <Loading />
|
|
|
|
const entries = rowEntries(row)
|
|
return (
|
|
<ScrollView style={s.wrap} contentContainerStyle={s.content}>
|
|
<Text style={s.title}>{rowTitle(row)}</Text>
|
|
<View style={{ marginTop: 8 }}><StatusPill label={rowStatus(row)} /></View>
|
|
<ErrText msg={err} />
|
|
<SectionTitle style={s.mt}>상세 항목</SectionTitle>
|
|
<Card>
|
|
{entries.length === 0 ? (
|
|
<Text style={s.empty}>표시할 항목이 없습니다.</Text>
|
|
) : entries.map((e) => (
|
|
<View key={e.key} style={s.row}>
|
|
<Text style={s.k} numberOfLines={1}>{e.key}</Text>
|
|
<Text style={s.v} numberOfLines={4}>{e.value}</Text>
|
|
</View>
|
|
))}
|
|
</Card>
|
|
<View style={{ height: 24 }} />
|
|
</ScrollView>
|
|
)
|
|
}
|
|
|
|
function safeParse(raw: string): RowMap { try { return JSON.parse(raw) as RowMap } catch { return {} } }
|
|
|
|
const makeStyles = (t: ThemeTokens) => StyleSheet.create({
|
|
wrap: { flex: 1, backgroundColor: t.bg },
|
|
content: { padding: 16 },
|
|
title: { color: t.text, fontSize: 19, fontWeight: '900' },
|
|
mt: { marginTop: 20 },
|
|
row: { flexDirection: 'row', justifyContent: 'space-between', gap: 12, paddingVertical: 8, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: t.border },
|
|
k: { color: t.muted, fontSize: 12, flexShrink: 0, maxWidth: '40%' },
|
|
v: { color: t.text, fontSize: 13, fontWeight: '600', flex: 1, textAlign: 'right' },
|
|
empty: { color: t.muted, fontSize: 13 },
|
|
})
|