Initial commit

This commit is contained in:
2026-08-31 16:50:53 +02:00
commit a68c8864b0
80 changed files with 22222 additions and 0 deletions
+229
View File
@@ -0,0 +1,229 @@
import ConstructionSiteCard from '@/components/ConstructionSiteCard';
import FilterModal from '@/components/FilterModal';
import LoadingScreen from '@/components/LoadingScreen';
import api from '@/utils/api';
import { AuthContext } from '@/utils/authContext';
import { useRouter } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { FileText, Filter, QrCode, User, ShoppingBag, ChartNoAxesCombined } from 'lucide-react-native';
import React, { useContext, useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Client, ConstructionSite } from '@/types/types';
export default function HomeScreen() {
const router = useRouter();
const { user } = useContext(AuthContext);
const [isLoading, setIsLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
// Construction Sites & Filters state
const [constructionSites, setConstructionSites] = useState<ConstructionSite[]>([]);
const [clients, setClients] = useState<Client[]>([]);
const [filterClient, setFilterClient] = useState<any>(null);
const [isFilterVisible, setIsFilterVisible] = useState(false);
const activeFiltersCount = filterClient ? 1 : 0;
const fetchClients = async () => {
try {
const response = await api.get('/construction-site/get-clients');
if (response.data?.success) {
setClients(response.data.clients || []);
}
} catch (error) {
console.error('Errore nel recupero dei committenti:', error);
}
};
const fetchConstructionSites = async (clientId = filterClient) => {
try {
if (!refreshing) setIsLoading(true);
const params = { id_client: clientId };
const response = await api.post('/construction-site/list', { params });
if (response.data?.success) {
setConstructionSites(response.data.result || []);
}
} catch (error) {
console.error('Errore nel recupero dei cantieri:', error);
} finally {
setIsLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchClients();
fetchConstructionSites();
}, []);
const onRefresh = () => {
setRefreshing(true);
fetchConstructionSites();
};
if (isLoading && !refreshing) {
return (
<LoadingScreen />
);
}
return (
<View className="flex-1 bg-primary-dark">
<StatusBar style="light" />
<SafeAreaView edges={['top']} className='pt-5'>
{/* Custom Banner */}
<View className="pb-6 px-6 shadow-sm z-10">
<View className="flex-row justify-between items-start">
<View className="flex-row items-center gap-4 flex-1 mr-4">
<View className="flex-1">
<Text className="text-neutral-50 text-sm font-semibold uppercase tracking-wider mb-2">
Progeco Costruzioni Generali S.R.L.
</Text>
<Text className="text-white text-4xl font-bold leading-tight">
Ciao <Text className="text-yellow-400">{user?.firstName}</Text>
</Text>
</View>
</View>
<View className="flex-row gap-4 flex-shrink-0 items-center">
{/* Profile Avatar */}
<TouchableOpacity className="p-3 bg-white/10 rounded-full active:bg-white/20" onPress={() => router.push('/profile')}>
<User size={28} color="white" pointerEvents="none"/>
</TouchableOpacity>
</View>
</View>
</View>
</SafeAreaView>
{/* Scrollable Content */}
<View className="flex-1 bg-slate-50 rounded-t-[2.5rem] overflow-hidden">
<ScrollView
className="flex-1 px-5 pt-6"
contentContainerStyle={{ paddingBottom: 100, gap: 24 }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#1071C2']} />
}
>
{/* Quick Actions / Centro di Controllo */}
<View>
<Text className="text-slate-800 text-xl font-bold mb-4 px-1">
{user?.isAdmin ? 'Centro di Controllo' : 'Azioni Rapide'}
</Text>
{user?.isAdmin ? (
<View className="flex-row gap-5">
<TouchableOpacity
onPress={() => router.push('/dashboard')}
className="flex-1 bg-white p-6 rounded-3xl shadow-sm items-center justify-center gap-4 border border-slate-100 active:scale-[0.98]"
>
<View className="w-20 h-20 rounded-full bg-primary-50 items-center justify-center mb-1">
<ChartNoAxesCombined size={40} color="#1071C2" pointerEvents="none"/>
</View>
<Text className="text-lg font-bold text-slate-700 text-center">Dashboard</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => router.push('/invoice')}
className="flex-1 bg-white p-6 rounded-3xl shadow-sm items-center justify-center gap-4 border border-slate-100 active:scale-[0.98]"
>
<View className="w-20 h-20 rounded-full bg-primary-50 items-center justify-center mb-1">
<FileText size={40} color="#1071C2" pointerEvents="none"/>
</View>
<Text className="text-lg font-bold text-slate-700 text-center">Fatture</Text>
</TouchableOpacity>
</View>
) : (
<View className="flex-row gap-5">
<TouchableOpacity
onPress={() => router.push('/attendance?autoScan=true')}
className="flex-1 bg-white p-6 rounded-3xl shadow-sm items-center justify-center gap-4 border border-slate-100 active:scale-[0.98]"
>
<View className="w-20 h-20 rounded-full bg-primary-50 items-center justify-center mb-1">
<QrCode size={40} color="#1071C2" pointerEvents="none"/>
</View>
<Text className="text-lg font-bold text-slate-700 text-center">Nuova Presenza</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => router.push('/activity/add')}
className="flex-1 bg-white p-6 rounded-3xl shadow-sm items-center justify-center gap-4 border border-slate-100 active:scale-[0.98]"
>
<View className="w-20 h-20 rounded-full bg-primary-50 items-center justify-center mb-1">
<ShoppingBag size={40} color="#1071C2" pointerEvents="none"/>
</View>
<Text className="text-lg font-bold text-slate-700 text-center">Nuova{'\n'}Attività</Text>
</TouchableOpacity>
</View>
)}
</View>
{/* Cantieri */}
<View>
<View className="flex-row justify-between items-center px-1 mb-4">
<Text className="text-slate-800 text-xl font-bold">Lista Cantieri</Text>
</View>
<View className="gap-2">
{constructionSites.map((item, index) => (
<ConstructionSiteCard
key={index}
item={item}
onPress={() => router.push(`/construction-site/${item.id}`)}
/>
))}
{!isLoading && constructionSites.length === 0 && (
<View className="bg-white p-6 rounded-3xl border border-slate-200 items-center justify-center border-dashed">
<Text className="text-slate-400 font-medium text-center">Nessun cantiere trovato.</Text>
</View>
)}
{isLoading && constructionSites.length === 0 && (
<View className="bg-white p-5 rounded-3xl border border-slate-100 h-24 justify-center items-center">
<Text className="text-slate-400">Caricamento...</Text>
</View>
)}
</View>
</View>
</ScrollView>
{/* FAB Filter */}
<TouchableOpacity
onPress={() => setIsFilterVisible(true)}
className="absolute bottom-8 right-6 w-16 h-16 bg-[#1071C2] rounded-full shadow-lg items-center justify-center active:scale-90"
>
<Filter size={28} color="white" pointerEvents="none" />
{activeFiltersCount > 0 && (
<View className="absolute top-0 right-0 bg-red-500 w-6 h-6 rounded-full items-center justify-center border-2 border-white">
<Text className="text-white text-xs font-bold">{activeFiltersCount}</Text>
</View>
)}
</TouchableOpacity>
{/* Filter Modal */}
<FilterModal
visible={isFilterVisible}
clients={clients}
currentClient={filterClient}
showDate={false}
showPlace={false}
showClient={true}
onClose={() => setIsFilterVisible(false)}
onApply={(_, __, client) => {
setFilterClient(client);
setIsFilterVisible(false);
fetchConstructionSites(client);
}}
onReset={() => {
setFilterClient(null);
setIsFilterVisible(false);
fetchConstructionSites(null);
}}
/>
</View>
</View>
);
}