374 lines
18 KiB
TypeScript
374 lines
18 KiB
TypeScript
import { useAlert } from '@/components/AlertComponent';
|
|
import { AppDatePicker } from '@/components/AppDatePicker';
|
|
import GenericDropdown from '@/components/GenericDropdown';
|
|
import FileAttachmentCard from '@/components/FileAttachmentCard';
|
|
import api from '@/utils/api';
|
|
import { formatDate, formatPickerDate } from '@/utils/dateTime';
|
|
import { DateType } from 'react-native-ui-datepicker';
|
|
import { useRouter } from 'expo-router';
|
|
import { StatusBar } from 'expo-status-bar';
|
|
import { Calendar, CheckSquare, ChevronLeft, Square } from 'lucide-react-native';
|
|
import React, { useEffect, useState } from 'react';
|
|
import { ActivityIndicator, KeyboardAvoidingView, Modal, Platform, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import * as DocumentPicker from 'expo-document-picker';
|
|
|
|
export default function AddQualityControlScreen() {
|
|
const router = useRouter();
|
|
const alert = useAlert();
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
|
|
// Form fields
|
|
const [date, setDate] = useState<DateType>(new Date());
|
|
const [subactivityId, setSubactivityId] = useState<number | null>(null);
|
|
const [workType, setWorkType] = useState<number | null>(null);
|
|
const [refDoc, setRefDoc] = useState<number | null>(null);
|
|
const [controlType, setControlType] = useState('');
|
|
const [instrument, setInstrument] = useState<number | null>(null);
|
|
const [result, setResult] = useState<number | null>(null);
|
|
|
|
// Attachments
|
|
const [attachments, setAttachments] = useState<DocumentPicker.DocumentPickerAsset[]>([]);
|
|
|
|
// Checkboxes
|
|
const [checkSegnaletica, setCheckSegnaletica] = useState(false);
|
|
const [checkSoggettiTerzi, setCheckSoggettiTerzi] = useState(false);
|
|
const [checkUtilizzoDPI, setCheckUtilizzoDPI] = useState(false);
|
|
const [checkControlloVisivo, setCheckControlloVisivo] = useState(false);
|
|
const [checkControlloDim, setCheckControlloDim] = useState(false);
|
|
const [checkConformita, setCheckConformita] = useState(false);
|
|
const [checkControlloFunz, setCheckControlloFunz] = useState(false);
|
|
|
|
// Lists
|
|
const [subactivities, setSubactivities] = useState<{id: number | string, label: string}[]>([]);
|
|
const [workTypeList, setWorkTypeList] = useState<{id: number, label: string}[]>([]);
|
|
const [refDocList, setRefDocList] = useState<{id: number, label: string}[]>([]);
|
|
const [instrumentList, setInstrumentList] = useState<{id: number, label: string}[]>([]);
|
|
|
|
const results = [
|
|
{ id: 1, label: 'Positivo' },
|
|
{ id: 0, label: 'Negativo' },
|
|
];
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
// Load Subactivities
|
|
const subRes = await api.get('/subactivity/get-subactivities');
|
|
if (subRes.data?.success) {
|
|
const mappedSubs = subRes.data.subactivities.map((s: any) => ({
|
|
id: s.id,
|
|
uuid: s.uuid,
|
|
label: s.label
|
|
}));
|
|
setSubactivities(mappedSubs);
|
|
}
|
|
|
|
// Load Related Tables
|
|
const relRes = await api.get('/quality-control/get-related-tables');
|
|
if (relRes.data?.success) {
|
|
setWorkTypeList(relRes.data.data.workType || []);
|
|
setRefDocList(relRes.data.data.refDocument || []);
|
|
setInstrumentList(relRes.data.data.instrument || []);
|
|
}
|
|
} catch (error) {
|
|
console.error('Errore nel caricamento dei dati iniziali:', error);
|
|
alert.showAlert('error', 'Errore', 'Impossibile caricare i dati per il form.');
|
|
}
|
|
};
|
|
|
|
const pickDocument = async () => {
|
|
try {
|
|
const result = await DocumentPicker.getDocumentAsync({
|
|
multiple: true,
|
|
copyToCacheDirectory: true,
|
|
});
|
|
if (!result.canceled && result.assets) {
|
|
setAttachments(prev => [...prev, ...result.assets]);
|
|
}
|
|
} catch (error) {
|
|
console.error('Errore durante la selezione del documento:', error);
|
|
alert.showAlert('error', 'Errore', 'Impossibile selezionare il documento.');
|
|
}
|
|
};
|
|
|
|
const removeAttachment = (index: number) => {
|
|
setAttachments(prev => prev.filter((_, i) => i !== index));
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
if (!subactivityId || !date || result === null) {
|
|
alert.showAlert('warning', 'Dati Mancanti', 'Compila tutti i campi obbligatori (Cantiere, Data, Esito).');
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
try {
|
|
const formattedDate = formatPickerDate(date);
|
|
const selectedSub = subactivities.find(s => s.id === subactivityId) as any;
|
|
const payload = {
|
|
subactivity_uuid: selectedSub?.uuid,
|
|
id_subactivity: subactivityId,
|
|
subactivity_id: subactivityId, // just in case
|
|
id_work_type: workType,
|
|
date: formattedDate,
|
|
id_ref_document: refDoc,
|
|
control_type: controlType,
|
|
id_instrument: instrument,
|
|
result: result,
|
|
check_segnaletica: checkSegnaletica ? 1 : 0,
|
|
check_soggetti_terzi: checkSoggettiTerzi ? 1 : 0,
|
|
check_utilizzo_dpi: checkUtilizzoDPI ? 1 : 0,
|
|
check_controllo_visivo: checkControlloVisivo ? 1 : 0,
|
|
check_controllo_dim: checkControlloDim ? 1 : 0,
|
|
check_conformita: checkConformita ? 1 : 0,
|
|
check_controllo_funz: checkControlloFunz ? 1 : 0,
|
|
};
|
|
|
|
const response = await api.post('/quality-control/add', { post: JSON.stringify(payload) });
|
|
if (response.data?.success) {
|
|
const qcId = response.data.id;
|
|
|
|
// Upload attachments if present
|
|
if (attachments.length > 0) {
|
|
for (let i = 0; i < attachments.length; i++) {
|
|
const file = attachments[i];
|
|
const formData = new FormData();
|
|
|
|
const fileName = file.name || `document_${i}`;
|
|
const fileType = file.mimeType || 'application/octet-stream';
|
|
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', 'QualityControl');
|
|
formData.append('model_id', qcId);
|
|
formData.append('method', 'put');
|
|
formData.append('name', fileName);
|
|
formData.append('type', fileType);
|
|
|
|
await api.post('/quality-control/upload', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
});
|
|
}
|
|
}
|
|
|
|
alert.showAlert('success', 'Salvato', 'Controllo di Qualità salvato con successo.');
|
|
router.back();
|
|
} else {
|
|
alert.showAlert('error', 'Errore', response.data?.message || 'Salvataggio non riuscito.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Errore durante il salvataggio:', error);
|
|
alert.showAlert('error', 'Errore', 'Impossibile completare il salvataggio.');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const CheckboxRow = ({ label, value, onChange }: { label: string, value: boolean, onChange: (v: boolean) => void }) => (
|
|
<TouchableOpacity
|
|
onPress={() => onChange(!value)}
|
|
className="flex-row items-center bg-white border border-gray-100 rounded-xl px-4 py-4 mb-3 active:bg-gray-50 shadow-sm"
|
|
>
|
|
{value ? <CheckSquare size={24} color="#1071C2" /> : <Square size={24} color="#9ca3af" />}
|
|
<Text className="ml-3 text-base text-gray-800 flex-1">{label}</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
|
|
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 gap-4 px-2'>
|
|
<TouchableOpacity onPress={() => router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100">
|
|
<ChevronLeft size={28} color="#4b5563" pointerEvents="none" />
|
|
</TouchableOpacity>
|
|
|
|
<Text className="text-xl font-bold text-gray-800 leading-tight uppercase flex-1 pr-4">
|
|
Nuovo Controllo
|
|
</Text>
|
|
</View>
|
|
</SafeAreaView>
|
|
</View>
|
|
|
|
<KeyboardAvoidingView
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'padding'}
|
|
className="flex-1"
|
|
>
|
|
<ScrollView
|
|
contentContainerStyle={{ padding: 24 }}
|
|
showsVerticalScrollIndicator={false}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
{/* Date */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3">Data <Text className="text-red-500">*</Text></Text>
|
|
<TouchableOpacity
|
|
onPress={() => setShowDatePicker(true)}
|
|
className="flex-row items-center bg-white border border-gray-200 rounded-2xl px-5 py-4 mb-6 active:bg-gray-50 shadow-sm"
|
|
>
|
|
<Text className="flex-1 text-base text-gray-800 font-medium">
|
|
{date ? formatDate(formatPickerDate(date)) : 'Seleziona data...'}
|
|
</Text>
|
|
<Calendar size={20} color="#6b7280" />
|
|
</TouchableOpacity>
|
|
|
|
{/* Subactivity / Cantiere */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3">Cantiere <Text className="text-red-500">*</Text></Text>
|
|
<View className="mb-6 shadow-sm">
|
|
<GenericDropdown
|
|
options={subactivities}
|
|
selectedId={subactivityId}
|
|
onSelect={(id) => setSubactivityId(id as number)}
|
|
placeholder="Seleziona cantiere..."
|
|
searchPlaceholder="Cerca cantiere..."
|
|
/>
|
|
</View>
|
|
|
|
{/* Work Type */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3">Tipologia Lavorazione</Text>
|
|
<View className="mb-6 shadow-sm">
|
|
<GenericDropdown
|
|
options={workTypeList}
|
|
selectedId={workType}
|
|
onSelect={setWorkType}
|
|
placeholder="Seleziona tipologia..."
|
|
showSearch={false}
|
|
/>
|
|
</View>
|
|
|
|
{/* Reference Document */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3">Documento di Riferimento</Text>
|
|
<View className="mb-6 shadow-sm">
|
|
<GenericDropdown
|
|
options={refDocList}
|
|
selectedId={refDoc}
|
|
onSelect={setRefDoc}
|
|
placeholder="Seleziona documento..."
|
|
showSearch={false}
|
|
/>
|
|
</View>
|
|
|
|
{/* Control Type */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3">Tipo di Controllo</Text>
|
|
<View className="bg-white rounded-2xl border border-gray-200 mb-6 shadow-sm">
|
|
<TextInput
|
|
className="px-5 py-4 text-base text-gray-800 font-medium"
|
|
placeholder="Inserisci tipo di controllo"
|
|
placeholderTextColor="#6a7282"
|
|
value={controlType}
|
|
onChangeText={setControlType}
|
|
/>
|
|
</View>
|
|
|
|
{/* Instrument */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3">Strumento Utilizzato</Text>
|
|
<View className="mb-8 shadow-sm">
|
|
<GenericDropdown
|
|
options={instrumentList}
|
|
selectedId={instrument}
|
|
onSelect={setInstrument}
|
|
placeholder="Seleziona strumento..."
|
|
showSearch={false}
|
|
/>
|
|
</View>
|
|
|
|
{/* Checkboxes */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-4 mt-2 border-t border-gray-200 pt-6">Checklist Controlli</Text>
|
|
<CheckboxRow label="Presenza e visibilità segnaletica" value={checkSegnaletica} onChange={setCheckSegnaletica} />
|
|
<CheckboxRow label="Presenza soggetti terzi" value={checkSoggettiTerzi} onChange={setCheckSoggettiTerzi} />
|
|
<CheckboxRow label="Corretto utilizzo DPI" value={checkUtilizzoDPI} onChange={setCheckUtilizzoDPI} />
|
|
<CheckboxRow label="Controllo visivo" value={checkControlloVisivo} onChange={setCheckControlloVisivo} />
|
|
<CheckboxRow label="Controllo Dimensionale/Elaborati" value={checkControlloDim} onChange={setCheckControlloDim} />
|
|
<CheckboxRow label="Controllo conformità mat. posato" value={checkConformita} onChange={setCheckConformita} />
|
|
<CheckboxRow label="Controllo funzionale" value={checkControlloFunz} onChange={setCheckControlloFunz} />
|
|
|
|
{/* Result */}
|
|
<Text className="text-lg font-bold text-primary-dark mb-3 mt-6 border-t border-gray-200 pt-6">Esito <Text className="text-red-500">*</Text></Text>
|
|
<View className="mb-10 shadow-sm">
|
|
<GenericDropdown
|
|
options={results}
|
|
selectedId={result}
|
|
onSelect={setResult}
|
|
placeholder="Seleziona esito..."
|
|
showSearch={false}
|
|
/>
|
|
</View>
|
|
|
|
{/* Attachments Section */}
|
|
<View className="mb-8">
|
|
<View className="flex-row items-center justify-between mb-4 border-t border-gray-200 pt-6">
|
|
<Text className="text-lg font-bold text-primary-dark">
|
|
Allegati <Text className="text-sm font-normal text-gray-500">({attachments.length})</Text>
|
|
</Text>
|
|
<TouchableOpacity activeOpacity={0.7} onPress={pickDocument}>
|
|
<Text className="text-[#1071C2] font-bold text-base uppercase">Aggiungi</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View className="mt-2">
|
|
{attachments.map((file, index) => (
|
|
<FileAttachmentCard
|
|
key={index}
|
|
file={file}
|
|
onRemove={() => removeAttachment(index)}
|
|
/>
|
|
))}
|
|
{attachments.length === 0 && (
|
|
<Text className="text-gray-400 font-medium text-center py-4 bg-white border border-gray-200 border-dashed rounded-2xl">
|
|
Nessun file allegato
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Save Button */}
|
|
<TouchableOpacity
|
|
onPress={handleSave}
|
|
disabled={isSubmitting || !subactivityId || !date || result === null}
|
|
className={`w-full py-4 rounded-2xl shadow-lg flex-row items-center justify-center ${(!subactivityId || !date || result === null || isSubmitting) ? 'bg-gray-300' : 'bg-[#1071C2] active:scale-[0.98]'}`}
|
|
>
|
|
{isSubmitting ? (
|
|
<ActivityIndicator color="white" />
|
|
) : (
|
|
<Text className="text-white text-lg font-bold uppercase">Salva</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
|
|
{/* Date Picker Modal */}
|
|
<Modal visible={showDatePicker} transparent animationType="fade">
|
|
<View className="flex-1 justify-center items-center bg-black/50">
|
|
<View className="bg-white rounded-3xl p-6 w-[90%] shadow-2xl">
|
|
<Text className="text-lg font-bold text-gray-800 mb-4">Seleziona Data</Text>
|
|
<AppDatePicker
|
|
mode="single"
|
|
date={date}
|
|
onChange={(d) => setDate(d.date || new Date())}
|
|
/>
|
|
<TouchableOpacity
|
|
onPress={() => setShowDatePicker(false)}
|
|
className="mt-6 w-full py-4 bg-[#1071C2] rounded-xl active:scale-[0.98]"
|
|
>
|
|
<Text className="text-white text-center font-bold text-lg">Conferma</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
</View>
|
|
);
|
|
}
|