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
+169
View File
@@ -0,0 +1,169 @@
import { useAlert } from '@/components/AlertComponent';
import FilterModal from '@/components/FilterModal';
import LoadingScreen from '@/components/LoadingScreen';
import QualityControlCard from '@/components/QualityControlCard';
import { StatusBar } from 'expo-status-bar';
import api from '@/utils/api';
import { ClipboardCheck, Filter, Plus } from 'lucide-react-native';
import React, { useCallback, useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Place, QualityControlItem } from '@/types/types';
import { useRouter, useFocusEffect } from 'expo-router';
export default function QualityControlScreen() {
const router = useRouter();
const alert = useAlert();
const [qualityControls, setQualityControls] = useState<QualityControlItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
// Filters state
const [isFilterVisible, setIsFilterVisible] = useState(false);
const [places, setPlaces] = useState<Place[]>([]);
const [filterRange, setFilterRange] = useState<{ startDate: string | null; endDate: string | null }>({ startDate: null, endDate: null });
const [filterPlace, setFilterPlace] = useState<any>(null);
const activeFiltersCount = (filterRange.startDate ? 1 : 0) + (filterPlace ? 1 : 0);
const fetchPlaces = async () => {
try {
const response = await api.get('/construction-site/get-construction-sites');
if (response.data?.success) {
setPlaces(response.data.constructionSites || []);
}
} catch (error) {
console.error('Errore nel recupero dei cantieri:', error);
}
};
const fetchQualityControls = async (currentRange = filterRange, currentPlace = filterPlace, isSilent = false) => {
try {
if (!refreshing && !isSilent) setIsLoading(true);
const rangeParam = currentRange.startDate ? currentRange : null;
// The API expects 'constructionSite' which is likely the ID. The filterPlace stores the ID or the Place object?
// According to PlaceFilter and InvoiceScreen, currentPlace is the selectedPlaceId.
const params = { range: rangeParam, constructionSite: currentPlace };
const response = await api.post('/quality-control/list', { params });
if (response.data?.success) {
setQualityControls(response.data.result || []);
}
} catch (error) {
console.error('Errore nel recupero dei controlli qualità:', error);
alert.showAlert('error', 'Errore', 'Impossibile recuperare i dati. Riprova più tardi.');
} finally {
setIsLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchPlaces();
}, []);
useFocusEffect(
useCallback(() => {
fetchQualityControls(filterRange, filterPlace, true);
}, [filterRange, filterPlace])
);
const onRefresh = () => {
setRefreshing(true);
fetchQualityControls();
};
if (isLoading && !refreshing) {
return <LoadingScreen />;
}
return (
<View className="flex-1 bg-primary-dark">
<StatusBar style="light" />
{/* Header */}
<SafeAreaView edges={['top']} className='pt-5'>
<View className="pb-6 px-6 z-10 flex-row justify-between items-center">
<View>
<Text className="text-white text-md font-semibold uppercase tracking-wider mb-1">Lista dei controlli di qualità</Text>
<Text className="text-yellow-400 text-3xl font-bold leading-tight">Controlli di Qualità</Text>
</View>
<View className="bg-white/10 p-4 rounded-full">
<ClipboardCheck size={32} color="white" pointerEvents="none" />
</View>
</View>
</SafeAreaView>
<View className="flex-1 bg-gray-50 rounded-t-[2.5rem] overflow-hidden">
<ScrollView
contentContainerStyle={{ paddingBottom: 160 }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#1071C2']} />
}
>
<View className="flex-1 p-5 items-center">
<View className="w-full mt-2">
{qualityControls.length === 0 ? (
<View className="bg-white p-6 rounded-3xl border border-gray-100 items-center justify-center border-dashed">
<Text className="text-gray-400 font-medium">Nessun controllo registrato</Text>
</View>
) : (
<View>
{qualityControls.map((item, index) => (
<QualityControlCard key={item.id || index} item={item} />
))}
</View>
)}
</View>
</View>
</ScrollView>
{/* FAB Add */}
<TouchableOpacity
onPress={() => router.push('/quality/add')}
className="absolute bottom-[6.5rem] right-6 w-16 h-16 bg-white border border-[#1071C2] rounded-full shadow-lg items-center justify-center active:scale-90"
>
<Plus size={32} color="#1071C2" pointerEvents="none" />
</TouchableOpacity>
{/* 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>
</View>
{/* Filter Modal */}
<FilterModal
visible={isFilterVisible}
showPlace={true}
showDate={true}
places={places}
currentRange={filterRange}
currentPlace={filterPlace}
onApply={(range, place) => {
setFilterRange(range);
setFilterPlace(place);
setIsFilterVisible(false);
fetchQualityControls(range, place);
}}
onReset={() => {
setFilterRange({ startDate: null, endDate: null });
setFilterPlace(null);
setIsFilterVisible(false);
fetchQualityControls({ startDate: null, endDate: null }, null);
}}
onClose={() => setIsFilterVisible(false)}
/>
</View>
);
}