--- name: new-features description: "GUARDiA Messenger 신규 기능 화면 구현 스킬. DR 모니터링, 네트워크 장비 현황, CSAP 준수율, 오프라인 모드, 생체인증(expo-local-authentication), Kanban SR 보드, 다크모드, 멀티기관 계정, 인시던트 빠른 대응, 실시간 알림 등 10가지 신규 기능을 React Native + Expo SDK 51 패턴으로 구현한다. 다음 상황에서 반드시 사용: (1) 'DR 화면', '네트워크 화면', 'CSAP 화면' 구현 요청; (2) '오프라인 모드', '생체인증', '지문 로그인' 구현; (3) 'Kanban 보드', '다크모드', '멀티기관' 기능 추가; (4) 신규 탭 추가, 새 화면 개발; (5) 다시 실행, 업데이트, 수정, 보완 요청. expo-router 파일 기반 라우팅과 빌드 금기사항을 반드시 준수한다." --- # GUARDiA Messenger 신규 기능 구현 스킬 ## 구현 패턴 — API 서비스 추가 신규 기능마다 `services/api.ts`에 함수를 추가한다. ```typescript // services/api.ts 추가 패턴 import { axiosInstance } from './api' // 기존 인스턴스 재사용 // DR API export const getDRDashboard = () => axiosInstance.get('/api/dr/dashboard') export const getDRScenarios = () => axiosInstance.get('/api/dr/scenarios') export const getDRRtoRpo = () => axiosInstance.get('/api/dr/rto-rpo') // 네트워크 장비 API export const getNetworkDevices = (instId?: number) => axiosInstance.get('/api/network/devices', { params: { inst_id: instId } }) export const getNetworkTopology = (instId?: number) => axiosInstance.get('/api/network/topology', { params: { inst_id: instId } }) // CSAP API export const getCSAPDashboard = () => axiosInstance.get('/api/compliance/csap/dashboard') export const getCSAPItems = () => axiosInstance.get('/api/compliance/csap/items') ``` ## 기능별 구현 가이드 ### 1. DR 모니터링 화면 (`app/(tabs)/dr.tsx`) ```tsx // 핵심 구조 export default function DRScreen() { const [dashboard, setDashboard] = useState(null) const [rtoRpo, setRtoRpo] = useState(null) // 폴링: 30초마다 갱신 (WS 없이도 준실시간) useEffect(() => { const load = async () => { const [d, r] = await Promise.all([getDRDashboard(), getDRRtoRpo()]) setDashboard(d.data) setRtoRpo(r.data) } load() const timer = setInterval(load, 30_000) return () => clearInterval(timer) }, []) return ( {/* 시나리오별 PASS/FAIL 배지 */} {/* RTO/RPO 목표 vs 실적 바 차트 */} {/* 최근 테스트 이력 5건 */} ) } ``` **타입 정의:** ```typescript interface DRDashboard { total_scenarios: number pass_count: number fail_count: number untested_count: number recent_tests: DRTestSummary[] } interface RtoScenario { scenario_name: string rto_target: number rto_actual_avg: number | null rto_met: boolean | null last_test_result: string } ``` ### 2. 네트워크 장비 현황 (`app/(tabs)/network.tsx`) ```tsx // 장비 타입별 그룹핑 + 최근 백업 상태 표시 // 설정 변경 감지 시 황색 뱃지 const DeviceCard = ({ device }: { device: NetworkDeviceOut }) => ( {device.device_name} {!device.last_backup_at && } {device.vendor} · {device.location} ) ``` ### 3. CSAP 준수율 대시보드 (`app/(tabs)/csap.tsx`) ```tsx // 준수율 원형 게이지 + 등급 배지 + FAIL 항목 목록 const GradeColor = { A: '#28a745', B: '#17a2b8', C: '#ffc107', D: '#dc3545' } const ComplianceGauge = ({ rate, grade }: { rate: number; grade: string }) => ( {rate}% {grade}등급 ) ``` ### 4. 오프라인 모드 (`hooks/useOfflineCache.ts`) ```typescript import * as SecureStore from 'expo-secure-store' import NetInfo from '@react-native-community/netinfo' // 주의: EAS에서 자동 설치됨 export function useOfflineCache(key: string, fetcher: () => Promise) { const [data, setData] = useState(null) const [isOffline, setOffline] = useState(false) useEffect(() => { const unsubscribe = NetInfo.addEventListener(state => { setOffline(!state.isConnected) }) return () => unsubscribe() }, []) const load = async () => { if (isOffline) { const cached = await SecureStore.getItemAsync(key) if (cached) setData(JSON.parse(cached)) return } try { const result = await fetcher() setData(result) await SecureStore.setItemAsync(key, JSON.stringify(result)) } catch { const cached = await SecureStore.getItemAsync(key) if (cached) setData(JSON.parse(cached)) } } return { data, isOffline, load } } ``` ### 5. 생체인증 (`hooks/useBiometric.ts`) ```typescript import * as LocalAuth from 'expo-local-authentication' export async function authenticateWithBiometric(): Promise { const hasHardware = await LocalAuth.hasHardwareAsync() const isEnrolled = await LocalAuth.isEnrolledAsync() if (!hasHardware || !isEnrolled) return false const result = await LocalAuth.authenticateAsync({ promptMessage: 'GUARDiA 로그인', cancelLabel: '취소', fallbackLabel: '비밀번호 사용', }) return result.success } ``` **`app.json` 설정 추가 (플러그인 아님, permissions만):** ```json { "expo": { "android": { "permissions": ["USE_BIOMETRIC", "USE_FINGERPRINT"] }, "ios": { "infoPlist": { "NSFaceIDUsageDescription": "GUARDiA 빠른 로그인을 위해 Face ID를 사용합니다." } } } } ``` ### 6. Kanban SR 보드 (`app/(tabs)/kanban.tsx`) ```tsx // 상태별 컬럼 (가로 스크롤) + 카드 탭으로 상태 변경 const STATUS_COLS = ['RECEIVED', 'IN_PROGRESS', 'PENDING_PM_VALIDATION', 'COMPLETED'] const STATUS_LABEL: Record = { RECEIVED: '접수', IN_PROGRESS: '진행중', PENDING_PM_VALIDATION: 'PM검토', COMPLETED: '완료' } // 카드 롱프레스 → ActionSheet로 상태 이동 (드래그&드롭 대신) // react-native-draggable-flatlist는 EAS 빌드 이슈 가능 → 탭/ActionSheet 방식 권장 ``` ### 7. 다크모드 (`hooks/useTheme.ts`) ```typescript import { useColorScheme } from 'react-native' export function useTheme() { const scheme = useColorScheme() // 'light' | 'dark' | null const isDark = scheme === 'dark' return { isDark, bg: isDark ? '#1a1a2e' : '#f5f7fa', card: isDark ? '#16213e' : '#ffffff', text: isDark ? '#e0e0e0' : '#333333', border: isDark ? '#2d2d44' : '#e2e8f0', } } ``` ### 8. 탭 메뉴 추가 (`app/(tabs)/_layout.tsx`) 신규 탭 추가 시 기존 `_layout.tsx`에 `` 항목만 추가: ```tsx ``` ## 빌드 안전성 체크리스트 신규 기능 구현 후 반드시 확인: - [ ] `@react-native-community/netinfo` → `package.json` dependencies에 추가 필요 (EAS 빌드 시 자동 설치) - [ ] `expo-local-authentication` → Expo SDK 51 기본 포함, 별도 설치 불필요 - [ ] `app.json` 플러그인 배열에 추가한 항목 없는지 확인 (expo-notifications 제외 원칙) - [ ] 신규 `import` 패키지가 `package.json`에 있는지 확인 > 상세 화면 목업과 UI 패턴은 `references/screen-mockups.md` 참조 (필요 시 생성).