import React, { useEffect, useState } from 'react'; import { Alert, KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import * as DocumentPicker from 'expo-document-picker'; import { Ionicons } from '@expo/vector-icons'; import { api, DocCategory, Document, LocalFile } from '@/api'; import { useT } from '@/i18n'; import { AppBar } from '@/components/AppBar'; import { Button, Field, Loading, TextField } from '@/components/ui'; import { DateField } from '@/components/DateField'; import { FileGlyph } from '@/components/domain'; import { clearCachedFile } from '@/lib/documentFile'; import { pickImageFromLibrary } from '@/lib/pickImage'; import { isCameraBlocked, openPermissionSettings } from '@/lib/permissions'; import { scanDocumentToPdf } from '@/scanner/scanToPdf'; import { formatBytes } from '@/lib/format'; import { categoryColor, colors, fontSize, radii, spacing } from '@/theme'; export default function EditDocumentScreen() { const { id, propertyId } = useLocalSearchParams<{ id: string; propertyId?: string }>(); const docId = Number(id); const pid = propertyId ? Number(propertyId) : undefined; const router = useRouter(); const qc = useQueryClient(); const t = useT(); // From the property's cached list: the backend has no per-file GET. const listQuery = useQuery({ queryKey: ['documents', pid], queryFn: () => api.documents.listByProperty(pid as number), enabled: pid != null, }); const doc: Document | undefined = listQuery.data?.find((d: Document) => d.id === docId); const catsQuery = useQuery({ queryKey: ['categories'], queryFn: () => api.reference.categories() }); const categories: DocCategory[] = catsQuery.data ?? []; const [expiry, setExpiry] = useState(null); const [note, setNote] = useState(''); const [title, setTitle] = useState(''); const [category, setCategory] = useState(''); const [loaded, setLoaded] = useState(false); const [replacing, setReplacing] = useState(false); useEffect(() => { if (!doc || loaded) return; setExpiry(doc.expiryDate ?? null); setNote(doc.note ?? ''); setTitle(doc.name ?? ''); setCategory(doc.category); setLoaded(true); }, [doc, loaded]); const save = useMutation({ mutationFn: () => api.documents.update({ id: docId, title: title.trim() || null, expiryDate: expiry, note: note.trim() || null, // Re-filing costs an extra backend lookup, so only send it if changed. ...(doc && category !== doc.category ? { category } : {}), }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['documents'] }); qc.invalidateQueries({ queryKey: ['all-documents'] }); router.back(); }, onError: (e: any) => Alert.alert(t('editDocument.failed'), e?.message ?? t('common.tryAgain')), }); // A separate endpoint from the metadata update, so it applies immediately. async function replaceWith(file: LocalFile | null) { if (!file || !doc) return; setReplacing(true); try { await api.documents.replaceFile(doc.id, file); await clearCachedFile(doc); // otherwise the old copy stays on screen qc.invalidateQueries({ queryKey: ['documents'] }); qc.invalidateQueries({ queryKey: ['all-documents'] }); qc.removeQueries({ queryKey: ['document-file', doc.id] }); Alert.alert(t('editDocument.replaced')); } catch (e: any) { Alert.alert(t('editDocument.replaceFailed'), e?.message ?? t('common.tryAgain')); } finally { setReplacing(false); } } async function replaceByScan() { try { const result = await scanDocumentToPdf(doc?.name || t('upload.scannedName')); await replaceWith(result?.file ?? null); } catch (e: any) { if (await isCameraBlocked()) openPermissionSettings('permissions.cameraBody'); else Alert.alert(t('upload.scannerUnavailable'), e?.message ?? t('upload.scannerUnavailableBody')); } } async function replaceByFile() { const res = await DocumentPicker.getDocumentAsync({ type: ['application/pdf', 'image/*'], copyToCacheDirectory: true, }); if (res.canceled || !res.assets?.length) return; const a = res.assets[0]; await replaceWith({ uri: a.uri, name: a.name, mimeType: a.mimeType ?? 'application/octet-stream', sizeBytes: a.size ?? 0, }); } async function replaceByPhoto() { await replaceWith(await pickImageFromLibrary({ quality: 0.9, fallbackName: 'photo' })); } if (!doc) { return ( ); } return ( {/* Current file + replacement */} {doc.name} {formatBytes(doc.sizeBytes)} {t('editDocument.replaceHint')} {categories.map((c) => { const active = c.slug === category; return ( setCategory(c.slug)} style={[ styles.catChip, active && { backgroundColor: categoryColor(c.slug) + '18', borderColor: categoryColor(c.slug) }, ]} > {c.name} ); })} {t('editDocument.hint')}