import React, { useState, useEffect, useMemo } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, useWindowDimensions, Platform } from 'react-native'; import { useTheme } from '@react-navigation/native'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { Document } from '../services/document'; import { supabase } from '../utils/supabase'; import Markdown from 'react-native-markdown-display'; type DocumentWithTitle = Document & { conversation_title: string; }; export default function DocumentsScreen() { const { colors } = useTheme(); const router = useRouter(); const { width } = useWindowDimensions(); const [documents, setDocuments] = useState([]); const [isLoading, setIsLoading] = useState(true); const [userId, setUserId] = useState(null); // Berechne die Anzahl der Spalten basierend auf der Bildschirmbreite const columnsCount = useMemo(() => { // Mobile (schmaler Bildschirm) if (width < 600) { return 1; } // Tablet if (width < 1100) { return 2; } // Desktop oder großes Tablet return 3; }, [width]); // Berechne die Breite jeder Karte basierend auf der Spaltenanzahl const cardWidth = useMemo(() => { const padding = 16; // Container-Padding rechts und links const gap = 16; // Abstand zwischen Karten const contentWidth = width - (padding * 2); const gapTotal = gap * (columnsCount - 1); const availableWidth = contentWidth - gapTotal; // Verhältnis für schmalere Karten, je nach Spaltenanzahl anpassen const widthRatio = columnsCount === 1 ? 0.95 : // Fast volle Breite bei 1 Spalte columnsCount === 2 ? 0.48 : // Etwas schmaler bei 2 Spalten 0.31; // Noch schmaler bei 3 Spalten return (availableWidth * widthRatio); }, [width, columnsCount]); useEffect(() => { const checkUser = async () => { const { data } = await supabase.auth.getUser(); if (data?.user) { setUserId(data.user.id); } else { // In einer echten App würden wir hier zur Login-Seite weiterleiten // Für jetzt verwenden wir eine Test-ID setUserId('test-user-id'); } }; checkUser(); }, []); useEffect(() => { if (userId) { loadDocuments(); } }, [userId]); const loadDocuments = async () => { try { setIsLoading(true); // Lade alle Konversationen des Benutzers, die im Dokumentmodus sind const { data: conversations, error: convError } = await supabase .from('conversations') .select('id, title, document_mode') .eq('user_id', userId) .eq('document_mode', true); if (convError) { console.error('Fehler beim Laden der Konversationen:', convError); setIsLoading(false); return; } if (!conversations || conversations.length === 0) { setDocuments([]); setIsLoading(false); return; } // Für jede Konversation den neuesten Dokumentstand laden const latestDocuments: DocumentWithTitle[] = []; for (const conv of conversations) { const { data: docData, error: docError } = await supabase .from('documents') .select('*') .eq('conversation_id', conv.id) .order('version', { ascending: false }) .limit(1) .single(); if (docError) { if (docError.code !== 'PGRST116') { // Ignore "No rows found" error console.error(`Fehler beim Laden des Dokuments für Konversation ${conv.id}:`, docError); } continue; } if (docData) { latestDocuments.push({ ...docData, conversation_title: conv.title || 'Unbenannte Konversation' }); } } setDocuments(latestDocuments); } catch (error) { console.error('Fehler beim Laden der Dokumente:', error); } finally { setIsLoading(false); } }; const navigateToConversation = (conversationId: string) => { router.push(`/conversation/${conversationId}`); }; // Funktion zum Extrahieren eines Titels aus dem Dokumentinhalt const extractDocumentTitle = (content: string): string => { // Suche nach einer Markdown-Überschrift Ebene 1 am Anfang const titleMatch = content.match(/^#\s+(.+)$/m); if (titleMatch && titleMatch[1]) { return titleMatch[1].trim(); } // Alternativ: Suche nach einer Markdown-Überschrift Ebene 2 const subtitleMatch = content.match(/^##\s+(.+)$/m); if (subtitleMatch && subtitleMatch[1]) { return subtitleMatch[1].trim(); } // Wenn keine Überschrift gefunden wurde, nimm die ersten Wörter const firstLine = content.split('\n')[0].trim(); if (firstLine.length > 0) { return firstLine.length > 40 ? `${firstLine.substring(0, 37)}...` : firstLine; } return 'Dokument ohne Titel'; }; // Funktion zum Entfernen nur der ersten H1-Überschrift aus dem Inhalt const removeHeadingFromContent = (content: string, title: string): string => { // Prüfe, ob das Dokument mit einer H1-Überschrift beginnt const firstLineMatch = content.match(/^#\s+(.+)$/m); if (firstLineMatch && firstLineMatch.index === 0) { // Entferne nur die erste H1-Überschrift am Anfang des Dokuments const parts = content.split('\n'); parts.shift(); // Entferne die erste Zeile (H1-Überschrift) // Entferne leere Zeilen am Anfang let modifiedContent = parts.join('\n').replace(/^\s+/, ''); return modifiedContent; } // Wenn keine H1-Überschrift am Anfang gefunden wurde, // gib den ursprünglichen Inhalt zurück return content; }; // Funktion zum Formatieren des Datums const formatDate = (dateString: string): string => { const date = new Date(dateString); const day = date.getDate().toString().padStart(2, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const year = date.getFullYear(); return `${day}.${month}.${year}`; }; return ( router.back()}> Alle Dokumente {isLoading ? ( Dokumente werden geladen... ) : documents.length === 0 ? ( Keine Dokumente gefunden Erstelle ein neues Dokument in einer Konversation mit aktiviertem Dokumentmodus. ) : ( {documents.map((doc) => ( navigateToConversation(doc.conversation_id)} > {extractDocumentTitle(doc.content)} {doc.conversation_title} {formatDate(doc.updated_at)} v{doc.version} {removeHeadingFromContent(doc.content, extractDocumentTitle(doc.content))} ))} )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', padding: 16, borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.1)', }, backButton: { padding: 6, }, headerTitle: { fontSize: 20, fontWeight: 'bold', flex: 1, paddingLeft: 12, }, refreshButton: { padding: 6, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { marginTop: 12, fontSize: 16, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, emptyIcon: { marginBottom: 20, opacity: 0.6, }, emptyText: { fontSize: 18, fontWeight: 'bold', marginBottom: 8, }, emptySubtext: { fontSize: 14, textAlign: 'center', opacity: 0.7, maxWidth: '80%', }, scrollContainer: { flex: 1, }, documentsContainer: { flexDirection: 'row', flexWrap: 'wrap', padding: 16, // In einem flexiblen Layout nicht mehr space-between verwenden // sondern einen festen Abstand zwischen Items gap: 20, // Alignment um die Karten horizontal zu zentrieren justifyContent: 'center' }, documentCard: { // width wird dynamisch basierend auf columnsCount berechnet borderRadius: 12, borderWidth: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', // Shadow für die Karten hinzufügen ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 3, }, web: { boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', }, }), }, documentHeader: { padding: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.1)', }, documentTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 8, lineHeight: 22, }, documentMeta: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingRight: 8, }, conversationTitle: { fontSize: 12, opacity: 0.7, flex: 1, }, metaRight: { flexDirection: 'row', alignItems: 'center', gap: 8, }, documentDate: { fontSize: 11, opacity: 0.7, }, documentVersion: { fontSize: 12, fontWeight: 'bold', backgroundColor: 'rgba(0,0,0,0.1)', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 10, }, contentContainer: { flex: 1, // Vorschau-Bereich kleiner machen maxHeight: 180, }, documentContent: { padding: 12, // Zusätzliche Eigenschaften für einen besseren Vorschaubereich paddingTop: 8, }, });