itsm_ysm/mobile/app/(tabs)/notices.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

61 lines
2.6 KiB
TypeScript

import { useCallback, useState } from 'react'
import { View, Text, FlatList, TouchableOpacity, StyleSheet, RefreshControl } from 'react-native'
import { useFocusEffect, router } from 'expo-router'
import { useTheme } from '../../constants/ThemeContext'
import { type ThemeTokens } from '../../constants/ItmsTheme'
import { EmptyState } from '../../components/ui'
import { rowTitle, rowDate, rowAuthor } from '../../constants/rows'
import { itmsBoardList, listRows, itmsErrorMessage, type RowMap } from '../itmsApi'
/*
* 공지/게시판 목록(BBSAdminManageController /bbs/admin/selectBoardList.do).
* 서버 미기동/무권한 시 빈 상태.
*/
export default function Notices() {
const { t } = useTheme()
const s = makeStyles(t)
const [rows, setRows] = useState<RowMap[]>([])
const [loading, setLoading] = useState(false)
const [err, setErr] = useState('')
const load = useCallback(async () => {
setLoading(true); setErr('')
try { setRows(listRows(await itmsBoardList())) }
catch (e) { setErr(itmsErrorMessage(e)); setRows([]) }
finally { setLoading(false) }
}, [])
useFocusEffect(useCallback(() => { load() }, [load]))
return (
<View style={s.wrap}>
<View style={s.head}><Text style={s.title}></Text></View>
<FlatList
data={rows}
keyExtractor={(_, i) => String(i)}
contentContainerStyle={rows.length === 0 ? { flexGrow: 1 } : { padding: 16 }}
refreshControl={<RefreshControl refreshing={loading} onRefresh={load} tintColor={t.accent} />}
ListEmptyComponent={loading ? null : <EmptyState icon="bell" text={err || '표시할 공지가 없습니다.'} />}
renderItem={({ item }) => (
<TouchableOpacity
style={s.card}
onPress={() => router.push({ pathname: '/notice-detail', params: { raw: JSON.stringify(item) } })}
>
<Text style={s.cardTitle} numberOfLines={2}>{rowTitle(item)}</Text>
<Text style={s.cardSub}>{[rowAuthor(item), rowDate(item)].filter(Boolean).join(' · ')}</Text>
</TouchableOpacity>
)}
/>
</View>
)
}
const makeStyles = (t: ThemeTokens) => StyleSheet.create({
wrap: { flex: 1, backgroundColor: t.bg },
head: { paddingHorizontal: 16, paddingTop: 16, paddingBottom: 8 },
title: { color: t.text, fontSize: 22, fontWeight: '900' },
card: { backgroundColor: t.card, borderRadius: 12, borderWidth: 1, borderColor: t.border, padding: 14, marginBottom: 10 },
cardTitle: { color: t.text, fontSize: 15, fontWeight: '700' },
cardSub: { color: t.muted, fontSize: 12, marginTop: 4 },
})