/* * SCR-12 시각화 갤러리 (모바일 2열) — 워터마크 상시 + 상단 고정 고지 바. * 계약: GET /api/events/{eventId}/booths/{boothId}/render-jobs?shot={S1} (스켈레톤 501 → degraded 폴백). * 실 API 호출 구조를 유지하되, 서버 미구현/오프라인 시 degraded 샘플로 폴백한다. */ import { router } from 'expo-router'; import React, { useCallback, useEffect, useState } from 'react'; import { RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native'; import { AiImage } from '../../components/AiImage'; import { Banner } from '../../components/Banner'; import { Button } from '../../components/Button'; import { useAuth } from '../../context/AuthContext'; import { api, isDegraded } from '../../lib/api'; import { AI_IMAGE_NOTICE } from '../../lib/config'; import type { RenderJobDto } from '../../lib/types'; import { colors, spacing, type } from '../../theme'; const DEMO_BOOTH = 'b-102'; // degraded 폴백 샘플 (서버 미구현/오프라인 시 표시 — 항상 워터마크 유지) const FALLBACK: RenderJobDto[] = [ mkJob('job-s1', 'S1', 'DONE'), mkJob('job-s2', 'S2', 'DONE'), mkJob('job-s3', 'S3', 'RUNNING'), mkJob('job-s7', 'S7', 'FAILED'), ]; function mkJob(jobId: string, shot: string, status: RenderJobDto['status']): RenderJobDto { return { jobId, boothId: DEMO_BOOTH, shotPreset: shot, status, imageUrl: null, schemaHash: null, modelVersion: null, watermarkRequired: true, watermarkText: AI_IMAGE_NOTICE, notice: 'AI 생성 이미지는 계약·심사 서류에 사용할 수 없습니다 (시공 기준은 도면)', }; } const SHOT_LABEL: Record = { S1: 'S1 정면', S2: 'S2 야간', S3: 'S3 통로', S4: 'S4 내부', S5: 'S5 비교', S6: 'S6 배선', S7: 'S7 홀 전경', }; export default function GalleryScreen() { const { activeWorkspace, token } = useAuth(); const [jobs, setJobs] = useState([]); const [degraded, setDegraded] = useState(false); const [loading, setLoading] = useState(true); const load = useCallback(async () => { setLoading(true); if (!activeWorkspace || !token) { setJobs(FALLBACK); setDegraded(true); setLoading(false); return; } try { const res = await api.get( `/api/events/${activeWorkspace.eventId}/booths/${DEMO_BOOTH}/render-jobs`, ); setJobs(res ?? []); setDegraded(false); } catch (e) { if (isDegraded(e)) { setJobs(FALLBACK); setDegraded(true); } else { setJobs([]); setDegraded(false); } } finally { setLoading(false); } }, [activeWorkspace, token]); useEffect(() => { load(); }, [load]); return ( {/* 상단 고정 고지 바 (제거 불가) */} AI 생성 이미지는 계약·심사 서류에 사용할 수 없습니다 — 시공 기준은 도면입니다 } > {degraded ? ( 서버 이미지 이력에 연결할 수 없어 예시를 표시합니다 (degraded). ) : null} {jobs.length === 0 && !loading ? ( 아직 생성된 이미지가 없습니다 — 설계 스튜디오에서 예상 사진을 생성하세요. ) : null} {jobs.map((j) => ( {j.status === 'FAILED' ? (