Files
progeco_app/app/(protected)/activity/[id].tsx
T

258 lines
12 KiB
TypeScript

import { useAlert } from '@/components/AlertComponent';
import LoadingScreen from '@/components/LoadingScreen';
import api from '@/utils/api';
import { Image } from 'expo-image';
import ImageView from "react-native-image-viewing";
import { useLocalSearchParams, useRouter, useFocusEffect } from 'expo-router';
import { ChevronLeft, ImageIcon, Users, HardHat, MapPin, Calendar as CalendarIcon, Briefcase, TextAlignStart, CheckCircle2, Wrench, Pencil } from 'lucide-react-native';
import React, { useCallback, useState } from 'react';
import { Dimensions, RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
export default function ActivityDetailScreen() {
const router = useRouter();
const alert = useAlert();
const params = useLocalSearchParams();
const [activityData, setActivityData] = useState<any>(null);
const [placeName, setPlaceName] = useState<string>('');
const [photos, setPhotos] = useState<{uri: string}[]>([]);
// Labor states
const [operatorLabor, setOperatorLabor] = useState<any[]>([]);
const [subcontractorLabor, setSubcontractorLabor] = useState<any[]>([]);
const [otherOperatorLabor, setOtherOperatorLabor] = useState<any[]>([]);
const [equipmentLabor, setEquipmentLabor] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
// Image Viewer states
const [isVisible, setIsVisible] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
const fetchDetails = useCallback(async (isRefreshing = false) => {
try {
if (!isRefreshing) setIsLoading(true);
const paramsData = JSON.stringify({ id: params.id });
const [activityRes, subactivitiesRes] = await Promise.all([
api.post('/activity/get-activity-data', { params: paramsData }),
api.get('/subactivity/get-subactivities')
]);
if (activityRes.data?.success) {
const data = activityRes.data;
setActivityData(data.activity);
setPhotos(data.attachments || []);
setOperatorLabor(data.operator_labor || []);
setSubcontractorLabor(data.subcontractor_labor || []);
setOtherOperatorLabor(data.other_operator_labor || []);
setEquipmentLabor(data.materials || []);
if (subactivitiesRes.data?.success) {
const subactivities = subactivitiesRes.data.subactivities;
const match = subactivities.find((s: any) => s.id === data.activity.id_subactivity);
if (match) {
setPlaceName(match.label);
} else {
setPlaceName('Cantiere Non Specificato');
}
}
} else {
alert.showAlert('error', 'Errore', 'Impossibile caricare i dettagli dell\'attività.');
}
} catch (error) {
console.error('Errore nel recupero del dettaglio attività:', error);
alert.showAlert('error', 'Errore', 'Si è verificato un errore di rete.');
} finally {
setIsLoading(false);
setRefreshing(false);
}
}, [params.id]);
useFocusEffect(
useCallback(() => {
if (params.id) {
fetchDetails(true);
}
}, [params.id, fetchDetails])
);
const onRefresh = () => {
setRefreshing(true);
fetchDetails(true);
};
if (isLoading && !refreshing) {
return <LoadingScreen />;
}
// Calculate dimensions for the grid layout of photos
const windowWidth = Dimensions.get('window').width;
const padding = 40;
const gap = 8;
const itemSize = (windowWidth - padding - (gap * 2)) / 3;
const imageSource = photos.map(photo => ({ uri: photo.uri }));
const renderLaborSection = (title: string, icon: React.ReactNode, laborData: any[]) => {
if (!laborData || laborData.length === 0) return null;
return (
<View className="bg-white rounded-3xl p-5 shadow-sm border border-gray-100 mb-4">
<View className="flex-row items-center border-b border-gray-50 pb-3 mb-3">
<View className="bg-blue-50 p-2 rounded-xl mr-3">
{icon}
</View>
<Text className="text-[#082963] font-bold text-lg">{title}</Text>
</View>
<View className="gap-3">
{laborData.map((labor, idx) => (
<View key={idx} className="flex-row justify-between items-center bg-gray-50 p-3 rounded-2xl">
<Text className="text-gray-800 font-medium flex-1 mr-2" numberOfLines={3}>
{labor.name}
</Text>
<View className="flex-row items-center gap-2">
<View className="bg-white px-3 py-1.5 rounded-xl border border-gray-200">
<Text className="text-[#1071C2] font-bold">
{labor.hours}h {labor.minutes}m
</Text>
</View>
{labor.sync && (
<CheckCircle2 size={16} color="#0F9D58" />
)}
</View>
</View>
))}
</View>
</View>
);
};
return (
<View className="flex-1 bg-gray-50">
<StatusBar style="dark" />
{/* Header */}
<View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100">
<SafeAreaView edges={['top']} className='pt-5'>
<View className='flex-row items-center justify-between px-2'>
<TouchableOpacity onPress={() => router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100 w-12 items-center justify-center">
<ChevronLeft size={28} color="#4b5563" pointerEvents="none" />
</TouchableOpacity>
<Text
className="text-xl font-bold text-gray-800 leading-tight uppercase flex-1 text-center"
numberOfLines={1}
ellipsizeMode="tail"
>
Dettaglio Attività
</Text>
<TouchableOpacity onPress={() => router.push(`/activity/add?id=${params.id}`)} className="p-2 -mr-2 active:bg-gray-100 rounded-full w-12 items-center justify-center">
<Pencil size={20} color="#082963" />
</TouchableOpacity>
</View>
</SafeAreaView>
</View>
<ScrollView
contentContainerStyle={{ padding: 20 }}
showsVerticalScrollIndicator={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#1071C2']} />}
>
{/* General Info */}
<View className="bg-white rounded-3xl p-5 shadow-sm border border-gray-100 mb-4">
<View className="flex-row items-center mb-4">
<View className="bg-blue-50 p-3 rounded-2xl mr-4">
<MapPin size={24} color="#082963" />
</View>
<View className="flex-1">
<Text className="text-gray-400 text-sm font-bold uppercase mb-1">Cantiere</Text>
<Text className="text-[#082963] font-bold text-md leading-tight">
{placeName}
</Text>
</View>
</View>
<View className="flex-row items-center mb-4 pt-4 border-t border-gray-50">
<View className="bg-blue-50 p-3 rounded-2xl mr-4">
<CalendarIcon size={24} color="#082963" />
</View>
<View className="flex-1">
<Text className="text-gray-400 text-sm font-bold uppercase mb-1">Data</Text>
<Text className="text-[#082963] font-bold text-md leading-tight">
{activityData?.date ? new Date(activityData.date).toLocaleDateString('it-IT') : '-'}
</Text>
</View>
</View>
<View className="pt-4 border-t border-gray-50">
<View className="flex-row items-center gap-2 mb-2">
<TextAlignStart size={20} color="#9ca3af" className="mr-2" />
<Text className="text-gray-400 text-xs font-bold uppercase">Descrizione</Text>
</View>
<Text className="text-gray-700 text-md font-medium leading-relaxed">
{activityData?.description || 'Nessuna descrizione.'}
</Text>
</View>
</View>
{/* Labor and Materials Sections */}
{renderLaborSection('Operai', <HardHat size={24} color="#082963" />, operatorLabor)}
{renderLaborSection('Subappaltatori', <Briefcase size={24} color="#082963" />, subcontractorLabor)}
{renderLaborSection('Operai Distaccati', <Users size={24} color="#082963" />, otherOperatorLabor)}
{renderLaborSection('Attrezzature', <Wrench size={24} color="#082963" />, equipmentLabor)}
{/* Photos */}
<View className="mt-4 mb-2 flex-row items-center justify-between">
<Text className="text-lg font-bold text-[#082963] ml-2">Allegati</Text>
</View>
{photos.length === 0 ? (
<View className="bg-white p-8 rounded-3xl border border-gray-100 items-center justify-center border-dashed mb-4">
<ImageIcon size={48} color="#d1d5db" />
<Text className="text-gray-400 font-medium text-center mt-4">Nessun allegato presente per questa attività.</Text>
</View>
) : (
<View className="flex-row flex-wrap" style={{ gap: gap }}>
{photos.map((item, index) => (
<TouchableOpacity
key={index}
activeOpacity={0.8}
onPress={() => {
setCurrentIndex(index);
setIsVisible(true);
}}
style={{ width: itemSize }}
className="mb-2"
>
<View className="bg-gray-100 rounded-2xl overflow-hidden shadow-sm border border-gray-200 aspect-square items-center justify-center">
<Image
source={{ uri: item.uri }}
style={{ width: '100%', height: '100%' }}
contentFit="cover"
transition={200}
/>
</View>
</TouchableOpacity>
))}
</View>
)}
</ScrollView>
<ImageView
images={imageSource}
imageIndex={currentIndex}
visible={isVisible}
onRequestClose={() => setIsVisible(false)}
swipeToCloseEnabled={true}
doubleTapToZoomEnabled={true}
presentationStyle="overFullScreen"
/>
</View>
);
}