import { ChevronLeft } from 'lucide-react-native'; import React, { useState, useEffect } from 'react'; import { View, Text, TouchableOpacity, TextInput, KeyboardAvoidingView, ScrollView, Platform, Dimensions, ActivityIndicator } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import PlaceFilter from '@/components/PlaceFilter'; import { Place } from '@/types/types'; import api from '@/utils/api'; import { useAlert } from '@/components/AlertComponent'; import RemovablePhotoTile from '@/components/RemovablePhotoTile'; import CameraAddTile from '@/components/CameraAddTile'; import * as ImagePicker from 'expo-image-picker'; export default function AddJournalScreen() { const router = useRouter(); const alert = useAlert(); const [places, setPlaces] = useState([]); const [selectedPlaceId, setSelectedPlaceId] = useState(null); const [description, setDescription] = useState(''); const [photos, setPhotos] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [uploadProgress, setUploadProgress] = useState<{ current: number, total: number } | null>(null); // Dynamic calculation of grid items (4 items per row) const windowWidth = Dimensions.get('window').width; const padding = 24; const gap = 12; const itemsPerRow = 4; const itemSize = (windowWidth - (padding * 2) - (gap * (itemsPerRow - 1))) / itemsPerRow; // Function to pick images from the gallery const pickFromGallery = async () => { const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (permissionResult.granted === false) { alert.showAlert('error', 'Permessi Negati', 'È necessario consentire l\'accesso alla galleria per caricare foto.'); return; } const limit = 50 - photos.length; if (limit <= 0) return; try { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], // Only images allowsMultipleSelection: true, selectionLimit: limit, quality: 0.8, // Apply light compression and permanently fix the correct orientation }); if (!result.canceled && result.assets) { setPhotos(prev => [...prev, ...result.assets]); } } catch (error) { console.error('Errore nella galleria:', error); alert.showAlert('error', 'Errore', 'Impossibile accedere alla galleria.'); } }; // Function to take a photo using the camera const takePhoto = async () => { const permissionResult = await ImagePicker.requestCameraPermissionsAsync(); if (permissionResult.granted === false) { alert.showAlert('error', 'Permessi Negati', 'È necessario consentire l\'accesso alla fotocamera per scattare foto.'); return; } if (photos.length >= 50) return; try { const result = await ImagePicker.launchCameraAsync({ mediaTypes: ['images'], quality: 0.8, // Apply light compression and permanently fix the correct orientation }); if (!result.canceled && result.assets && result.assets.length > 0) { setPhotos(prev => [...prev, result.assets[0]]); } } catch (error) { console.error('Errore nella fotocamera:', error); alert.showAlert('error', 'Errore', 'Impossibile accedere alla fotocamera.'); } }; const removePhoto = (index: number) => { setPhotos(prev => prev.filter((_, i) => i !== index)); }; const handleSave = async () => { if (!selectedPlaceId) return; setIsSubmitting(true); setUploadProgress(null); try { // Create record in the database first const params = { place: selectedPlaceId, description: description, n_files: photos.length }; const response = await api.post('/journal/add', params); if (response.data?.success) { const journalId = response.data.id; // Sequential asynchronous upload if (photos.length > 0) { setUploadProgress({ current: 0, total: photos.length }); for (let i = 0; i < photos.length; i++) { const file = photos[i]; let formData = new FormData(); const fileName = file.fileName || file.uri.split('/').pop() || `photo_${i}.jpg`; const fileType = file.mimeType || 'image/jpeg'; const fileUri = Platform.OS === 'android' ? file.uri : file.uri.replace('file://', ''); formData.append("files", { name: fileName, type: fileType, uri: fileUri } as any); formData.append('model_classname', 'JournalUpdate'); formData.append('model_id', journalId); formData.append('method', 'put'); formData.append('name', fileName); formData.append('type', fileType); await api.post('/journal/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); setUploadProgress({ current: i + 1, total: photos.length }); } } alert.showAlert('success', 'Ottimo Lavoro', 'Aggiornamento caricato con successo!'); router.replace('/(protected)/journal'); } else { alert.showAlert('error', 'Errore', response.data?.message || 'Impossibile creare l\'avanzamento.'); } } catch (error: any) { console.error('Errore durante il salvataggio:', error); alert.showAlert('error', 'Errore di connessione', 'Verifica la tua connessione e riprova.'); } finally { setIsSubmitting(false); setUploadProgress(null); } }; useEffect(() => { fetchPlaces(); }, []); const fetchPlaces = async () => { try { // This endpoint returns the UUID of the construction site necessary for saving const response = await api.get('/journal/get-places'); if (response.data?.success) { const mappedPlaces = response.data.places.map((p: any) => ({ id: p.value, // value = UUID label: p.label, code: '' })); setPlaces(mappedPlaces); } } catch (error) { console.error('Errore nel recupero dei cantieri:', error); } }; return ( {/* Header */} router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100"> Nuovo Aggiornamento {/* Place Filter Component */} {/* Description Area */} Descrizione {/* Attachments Area */} Allegati ({photos.length}/50) {photos.length < 50 && ( Aggiungi )} {photos.map((photo, index) => ( removePhoto(index)} /> ))} {photos.length < 50 && ( )} {/* Save Button */} Salva {/* Loading Overlay */} {isSubmitting && ( {uploadProgress ? 'Caricamento foto...' : 'Salvataggio...'} {uploadProgress && ( {uploadProgress.current} / {uploadProgress.total} )} )} ); }