itsm_ysm/mobile/app/request-detail.tsx
itms-merge-dev efc3e66635 feat(itms): 모바일 앱 신규 (Expo, 12화면, OAuth password grant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:10:46 +09:00

74 lines
3.0 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 { itmsReqIncidentDetail, itmsErrorMessage, type RowMap } from './itmsApi'
/*
* 서비스 요청 상세(SvcDeskReqController /srm/getReqIncidentBySeq.do).
* 목록에서 넘어온 raw 를 즉시 표시하고, 상세 API 로 보강(실패 시 raw 유지).
*/
export default function RequestDetail() {
const { num, raw } = useLocalSearchParams<{ num?: string; raw?: string }>()
const { t } = useTheme()
const s = makeStyles(t)
const seed: RowMap = raw ? safeParse(raw) : {}
const [row, setRow] = useState<RowMap>(seed)
const [loading, setLoading] = useState(!!num)
const [err, setErr] = useState('')
useEffect(() => {
let alive = true
if (!num) { setLoading(false); return }
;(async () => {
try {
const detail = await itmsReqIncidentDetail(num)
const merged = { ...seed, ...(detail?.resultVo ?? detail?.incidentReqVo ?? 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
}, [num])
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 },
})