import { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native'; import { router, useLocalSearchParams } from 'expo-router'; import * as DocumentPicker from 'expo-document-picker'; import * as ImagePicker from 'expo-image-picker'; import * as WebBrowser from 'expo-web-browser'; import { fetchPlantById, fetchPlantFiles, Plant, ProjectFile, uploadPlantFile } from '@/src/lib/api'; import { useAuth } from '@/src/providers/auth-provider'; const PRIMARY = '#69c350'; function formatDateTime(value: unknown): string { if (!value) return '-'; const date = new Date(String(value)); if (Number.isNaN(date.getTime())) return String(value); return date.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit', }); } function getCustomerName(raw: Plant['customer']): string { if (!raw) return '-'; if (typeof raw === 'object') return String(raw.name || raw.id || '-'); return String(raw); } export default function PlantDetailScreen() { const params = useLocalSearchParams<{ id?: string }>(); const plantId = Number(params.id); const { token } = useAuth(); const [plant, setPlant] = useState(null); const [files, setFiles] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const validId = useMemo(() => Number.isFinite(plantId) && plantId > 0, [plantId]); const rows = useMemo(() => { if (!plant) return []; return [ { label: 'Name', value: String(plant.name || '-') }, { label: 'Kunde', value: getCustomerName(plant.customer) }, { label: 'Typ', value: String(plant.type || '-') }, { label: 'Stadt', value: String(plant.city || '-') }, { label: 'Strasse', value: String(plant.street || '-') }, { label: 'PLZ', value: String(plant.zip || '-') }, { label: 'Erstellt', value: formatDateTime(plant.createdAt || plant.created_at) }, { label: 'Aktualisiert', value: formatDateTime(plant.updatedAt || plant.updated_at) }, ]; }, [plant]); const load = useCallback(async (showSpinner = true) => { if (!token || !validId) return; if (showSpinner) setLoading(true); setError(null); try { const [plantData, fileData] = await Promise.all([ fetchPlantById(token, plantId), fetchPlantFiles(token, plantId), ]); setPlant(plantData); setFiles(fileData); } catch (err) { setError(err instanceof Error ? err.message : 'Objektdaten konnten nicht geladen werden.'); } finally { setLoading(false); setRefreshing(false); } }, [plantId, token, validId]); useEffect(() => { if (!token || !validId) return; void load(true); }, [load, token, validId]); async function onRefresh() { setRefreshing(true); await load(false); } async function onOpenFile(file: ProjectFile) { if (!file.url) return; await WebBrowser.openBrowserAsync(file.url, { presentationStyle: WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, controlsColor: PRIMARY, showTitle: true, enableDefaultShareMenuItem: true, }); } async function onPickAndUpload() { if (!token || !validId || uploading) return; const result = await DocumentPicker.getDocumentAsync({ multiple: false, copyToCacheDirectory: true, type: ['image/*', 'application/pdf', '*/*'], }); if (result.canceled || !result.assets?.length) return; const asset = result.assets[0]; const filename = asset.name || `upload-${Date.now()}`; setUploading(true); setError(null); try { await uploadPlantFile(token, { plantId, uri: asset.uri, filename, mimeType: asset.mimeType || 'application/octet-stream', }); await load(false); } catch (err) { setError(err instanceof Error ? err.message : 'Upload fehlgeschlagen.'); } finally { setUploading(false); } } async function uploadImageFromUri(uri: string, filename: string, mimeType?: string) { if (!token || !validId) return; setUploading(true); setError(null); try { await uploadPlantFile(token, { plantId, uri, filename, mimeType: mimeType || 'image/jpeg', }); await load(false); } catch (err) { setError(err instanceof Error ? err.message : 'Upload fehlgeschlagen.'); } finally { setUploading(false); } } async function onPickImage() { if (!token || !validId || uploading) return; const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!permission.granted) { setError('Bitte erlaube den Zugriff auf deine Fotos.'); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], quality: 0.85, allowsEditing: false, }); if (result.canceled || !result.assets?.length) return; const asset = result.assets[0]; const filename = asset.fileName || `bild-${Date.now()}.jpg`; await uploadImageFromUri(asset.uri, filename, asset.mimeType || 'image/jpeg'); } async function onTakePhoto() { if (!token || !validId || uploading) return; const permission = await ImagePicker.requestCameraPermissionsAsync(); if (!permission.granted) { setError('Bitte erlaube den Zugriff auf die Kamera.'); return; } const result = await ImagePicker.launchCameraAsync({ mediaTypes: ['images'], quality: 0.85, allowsEditing: false, }); if (result.canceled || !result.assets?.length) return; const asset = result.assets[0]; const filename = asset.fileName || `foto-${Date.now()}.jpg`; await uploadImageFromUri(asset.uri, filename, asset.mimeType || 'image/jpeg'); } return ( }> {error ? {error} : null} {loading ? ( Objekt wird geladen... ) : null} {!loading && plant ? ( <> Informationen router.push({ pathname: '/more/wiki', params: { entityType: 'plants', entityId: String(plantId), title: `Objekt-Wiki: ${String(plant.name || plantId)}`, }, }) }> Wiki {rows.map((row) => ( {row.label} {row.value} ))} Dokumente ({files.length}) {uploading ? 'Upload...' : 'Foto aufnehmen'} {uploading ? 'Upload...' : 'Bild auswählen'} {uploading ? 'Upload...' : 'Dokument hochladen'} {files.length === 0 ? ( Noch keine Dokumente vorhanden. ) : ( files.map((file) => ( onOpenFile(file)}> {file.name || file.path?.split('/').pop() || file.id} {file.mimeType || 'Datei'} )) )} ) : null} ); } const styles = StyleSheet.create({ container: { padding: 16, gap: 12, backgroundColor: '#f9fafb', }, card: { backgroundColor: '#ffffff', borderRadius: 12, borderWidth: 1, borderColor: '#e5e7eb', padding: 12, gap: 8, }, title: { color: '#111827', fontSize: 18, fontWeight: '700', }, table: { borderWidth: 1, borderColor: '#e5e7eb', borderRadius: 10, overflow: 'hidden', }, row: { borderBottomWidth: 1, borderBottomColor: '#e5e7eb', paddingHorizontal: 10, paddingVertical: 8, gap: 2, }, label: { color: '#6b7280', fontSize: 12, textTransform: 'uppercase', fontWeight: '600', }, value: { color: '#111827', fontSize: 14, fontWeight: '500', }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 10, }, sectionTitle: { color: '#111827', fontSize: 16, fontWeight: '700', }, uploadButton: { minHeight: 38, borderRadius: 9, backgroundColor: PRIMARY, paddingHorizontal: 12, alignItems: 'center', justifyContent: 'center', }, uploadActions: { gap: 8, }, uploadButtonText: { color: '#ffffff', fontWeight: '700', fontSize: 13, }, fileRow: { borderWidth: 1, borderColor: '#e5e7eb', borderRadius: 10, padding: 10, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', gap: 10, }, fileInfo: { flex: 1, minWidth: 0, }, fileName: { color: '#111827', fontSize: 14, fontWeight: '600', }, fileMeta: { color: '#6b7280', fontSize: 12, marginTop: 2, }, empty: { color: '#6b7280', fontSize: 13, paddingVertical: 4, }, loadingBox: { paddingVertical: 20, alignItems: 'center', gap: 8, }, loadingText: { color: '#6b7280', }, error: { color: '#dc2626', fontSize: 13, }, buttonDisabled: { opacity: 0.6, }, });