diff --git a/app.json b/app.json index f4a298a..8bd8a15 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,7 @@ { "expo": { - "name": "mariani_app", - "slug": "mariani_app", + "name": "Mariani", + "slug": "mariani-app", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/images/mariani-icon.png", @@ -10,7 +10,7 @@ "newArchEnabled": true, "ios": { "supportsTablet": true, - "bundleIdentifier": "com.anonymous.mariani-app" + "bundleIdentifier": "com.pcrt.mariani-app" }, "android": { "adaptiveIcon": { @@ -25,7 +25,7 @@ "android.permission.CAMERA", "android.permission.RECORD_AUDIO" ], - "package": "com.anonymous.mariani_app" + "package": "com.pcrt.mariani-app" }, "web": { "output": "static", diff --git a/app/(protected)/_layout.tsx b/app/(protected)/_layout.tsx index 87666aa..c185500 100644 --- a/app/(protected)/_layout.tsx +++ b/app/(protected)/_layout.tsx @@ -14,7 +14,6 @@ export default function ProtectedLayout() { return ; } - // TODO: Aggiungere padding per i dispositivi con notch/bottom bar return ( , }} /> - {/* // TODO: Rimuovere all'utente e mostrare solo a admin */} + {/* // TODO: Probably needs to be restricted to admin */} , }} /> - {/* TODO: Dovrebbe essere rimosso, va rivisto layout */} + {/* TODO: Should be removed - move tabs inside (tabs) and refactor _layout */} ('qr'); @@ -20,6 +21,7 @@ export default function AttendanceScreen() { const [refreshing, setRefreshing] = useState(false); const checkNfcAvailability = async () => { + // TODO: add env variable to disable NFC checks in development or if not needed // if (!ENABLE_NFC) return; try { const isSupported = await NfcManager.isSupported(); @@ -58,13 +60,13 @@ export default function AttendanceScreen() { }; const handleStartScan = async () => { - // Modalità QR Code + // Qr Code mode if (scannerType === 'qr') { setShowScanner(true); return; } - // Modalità NFC + // NFC mode if (scannerType === 'nfc') { try { const supported = await NfcManager.isSupported(); @@ -117,9 +119,11 @@ export default function AttendanceScreen() { return ( {/* Header */} - - Gestione Presenze - Registra i tuoi movimenti + + + Gestione Presenze + Registra i tuoi movimenti + - {/* TODO: item.time può essere null -> calcolare tempo da in e out? */} + {/* TODO: item.time can be null - calculate time from in and out? */} {item.time && ( diff --git a/app/(protected)/automation/index.tsx b/app/(protected)/automation/index.tsx index 8ac4449..cd7baf2 100644 --- a/app/(protected)/automation/index.tsx +++ b/app/(protected)/automation/index.tsx @@ -197,13 +197,6 @@ export default function AutomationScreen() { )} - - {/* alert('Aggiungi nuovo collegamento')} - className="absolute bottom-8 right-6 w-16 h-16 bg-[#099499] rounded-full shadow-lg items-center justify-center active:scale-90" - > - - */} ); } \ No newline at end of file diff --git a/app/(protected)/index.tsx b/app/(protected)/index.tsx index ba493c6..3d9eadb 100644 --- a/app/(protected)/index.tsx +++ b/app/(protected)/index.tsx @@ -6,6 +6,7 @@ import { AuthContext } from '@/utils/authContext'; import { ActivityItem } from '@/types/types'; import api from '@/utils/api'; import LoadingScreen from '@/components/LoadingScreen'; +import { SafeAreaView } from 'react-native-safe-area-context'; export default function HomeScreen() { const router = useRouter(); @@ -63,27 +64,29 @@ export default function HomeScreen() { return ( - {/* Banner Custom */} - - - - - Benvenuto - - {user?.name} {user?.surname} - - {user?.role} + + {/* Custom Banner */} + + + + + Benvenuto + + {user?.name} {user?.surname} + + {user?.role} + + + + router.push('/profile')}> + + - - router.push('/profile')}> - - - - + - {/* Contenuto Scrollabile */} + {/* Scrollable Content */} - {/* Warning Card */} + {/* Warning Card - Incomplete Attendance */} {incompleteAttendance && ( - - + router.push('/attendance')} + > + @@ -105,10 +111,7 @@ export default function HomeScreen() { {incompleteAttendance} - router.push('/attendance')} className="bg-orange-50 px-5 py-3 rounded-xl ml-2 active:bg-orange-100"> - Risolvi - - + )} {/* Quick Actions */} @@ -141,6 +144,7 @@ export default function HomeScreen() { Ultime Attività + {/* TODO: Could be expanded */} {/* Vedi tutto */} @@ -153,12 +157,12 @@ export default function HomeScreen() { - {/* Icona */} + {/* Icon */} - {/* Titolo e Sottotitolo */} + {/* Title and Subtitle */} - {/* Data */} + {/* Date */} {item.date_display} diff --git a/app/(protected)/permits/index.tsx b/app/(protected)/permits/index.tsx index af07baf..201a789 100644 --- a/app/(protected)/permits/index.tsx +++ b/app/(protected)/permits/index.tsx @@ -8,13 +8,14 @@ import CalendarWidget from '@/components/CalendarWidget'; import LoadingScreen from '@/components/LoadingScreen'; import api from '@/utils/api'; import { formatDate, formatTime } from '@/utils/dateTime'; +import { SafeAreaView } from 'react-native-safe-area-context'; // Icon Mapping const typeIcons: Record JSX.Element> = { - Ferie: (color) => , - Permesso: (color) => , - Malattia: (color) => , - Assenza: (color) => , + Ferie: (color) => , + Permesso: (color) => , + Malattia: (color) => , + Assenza: (color) => , }; export default function PermitsScreen() { @@ -33,7 +34,7 @@ export default function PermitsScreen() { // Fetch Permits const response = await api.get('/time-off-request/list'); setPermits(response.data); - + // Fetch Types const typesResponse = await api.get('/time-off-request/get-types'); setTypes(typesResponse.data); @@ -49,22 +50,21 @@ export default function PermitsScreen() { const filteredPermits = useMemo(() => { if (!permits.length) return []; - // Calcoliamo inizio e fine del mese visualizzato + // Calculate start and end of the current month const year = currentMonthDate.getFullYear(); const month = currentMonthDate.getMonth(); - + const startOfMonth = new Date(year, month, 1); - // Trucco JS: giorno 0 del mese successivo = ultimo giorno del mese corrente - const endOfMonth = new Date(year, month + 1, 0, 23, 59, 59); + // Day 0 of the next month = last day of the current month + const endOfMonth = new Date(year, month + 1, 0, 23, 59, 59); return permits.filter(item => { const itemStart = new Date(item.start_date?.toString() ?? ''); - // Se non c'è end_date, assumiamo sia un giorno singolo (quindi end = start) + // If there's no end_date, assume it's a single day (so end = start) const itemEnd = item.end_date ? new Date(item.end_date?.toString() ?? '') : new Date(item.start_date?.toString() ?? ''); - // FORMULA OVERLAP: - // Il permesso è visibile se inizia prima della fine del mese - // E finisce dopo l'inizio del mese. + // The permit is visible if it starts before the end of the month + // And ends after the start of the month. return itemStart <= endOfMonth && itemEnd >= startOfMonth; }); }, [permits, currentMonthDate]); @@ -92,11 +92,13 @@ export default function PermitsScreen() { /> {/* Header */} - - - Ferie e Permessi - Gestisci le tue assenze - + + + + Ferie e Permessi + Gestisci le tue assenze + + setCurrentMonthDate(date)} /> - {/* Lista Richieste Recenti */} + {/* Recent Requests List */} {filteredPermits.length === 0 ? ( Nessuna richiesta di permesso questo mese @@ -135,7 +137,7 @@ export default function PermitsScreen() { )} - {/* TODO: Aggiungere funzionalità per modificare/eliminare la richiesta? */} + {/* TODO: Add functionality to edit/remove the request */} {item.status ? 'Approvato' : 'In Attesa'} diff --git a/app/(protected)/profile/documents.tsx b/app/(protected)/profile/documents.tsx index f59420a..42a0e0d 100644 --- a/app/(protected)/profile/documents.tsx +++ b/app/(protected)/profile/documents.tsx @@ -1,5 +1,5 @@ import { useAlert } from '@/components/AlertComponent'; -import { ArrowLeft, Download, FileText, MapPin, Plus, Search, CalendarIcon } from 'lucide-react-native'; +import { Download, FileText, MapPin, Plus, Search, CalendarIcon, ChevronLeft } from 'lucide-react-native'; import React, { useEffect, useState } from 'react'; import { useRouter } from 'expo-router'; import { RangePickerModal } from '@/components/RangePickerModal'; @@ -11,6 +11,7 @@ import LoadingScreen from '@/components/LoadingScreen'; import { formatTimestamp, parseTimestamp } from '@/utils/dateTime'; import AddDocumentModal from '@/components/AddDocumentModal'; import { downloadAndShareDocument, uploadDocument } from '@/utils/documentUtils'; +import { SafeAreaView } from 'react-native-safe-area-context'; export default function DocumentsScreen() { const router = useRouter(); @@ -32,7 +33,7 @@ export default function DocumentsScreen() { try { if (!refreshing) setIsLoading(true); - // Fetch Documenti Utente + // Fetch User Documents const response = await api.get(`/attachment/get-user-attachments`); setDocuments(response.data); } catch (error) { @@ -53,24 +54,24 @@ export default function DocumentsScreen() { fetchUserDocuments(); }; - // Filtra Documenti in base a searchTerm e range + // Filter Documents based on searchTerm and range const filteredDocs = documents.filter(doc => { - // Filtro Testuale + // Text Filter const matchesSearch = doc.title.toLowerCase().includes(searchTerm.toLowerCase()); if (!matchesSearch) return false; - // Filtro Date Range + // Date Range Filter if (range.startDate || range.endDate) { const docDate = parseTimestamp(doc.updated_at); // doc.date è "DD/MM/YYYY" - // Controllo Data Inizio + // Start Date Check if (range.startDate) { - // dayjs(range.startDate).toDate() converte in oggetto Date JS standard + // dayjs(range.startDate).toDate() converts to standard JS Date object const start = dayjs(range.startDate).startOf('day').toDate(); if (docDate < start) return false; } - // Controllo Data Fine + // End Date Check if (range.endDate) { const end = dayjs(range.endDate).endOf('day').toDate(); if (docDate > end) return false; @@ -80,7 +81,7 @@ export default function DocumentsScreen() { return true; }); - // Gestione Caricamento Documento + // Document Upload Handling const handleUploadDocument = async (file: any, customTitle?: string) => { setIsUploading(true); try { @@ -105,14 +106,18 @@ export default function DocumentsScreen() { return ( {/* Header */} - - router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100"> - - - - Documenti - Gestisci i tuoi documenti - + + + + router.back()} className="p-2 rounded-full active:bg-gray-100"> + + + + Documenti + Gestisci i tuoi documenti + + + @@ -141,7 +146,7 @@ export default function DocumentsScreen() { - {/* Modale Unico per il Range */} + {/* Range Picker Modal */} setShowRangePicker(false)} @@ -149,7 +154,7 @@ export default function DocumentsScreen() { onApply={setRange} /> - {/* List */} + {/* Documents List */} downloadAndShareDocument(doc.mimetype, doc.title, doc.url)} + onPress={() => downloadAndShareDocument(doc.mimetype, doc.title, doc.url)} className="p-4 bg-gray-50 rounded-2xl active:bg-gray-100"> @@ -193,7 +198,7 @@ export default function DocumentsScreen() { - {/* Modale Caricamento Documento */} + {/* Document Upload Modal */} setShowUploadModal(false)} diff --git a/app/(protected)/profile/index.tsx b/app/(protected)/profile/index.tsx index 781f25e..5756967 100644 --- a/app/(protected)/profile/index.tsx +++ b/app/(protected)/profile/index.tsx @@ -3,55 +3,56 @@ import { ChevronLeft, FileText, LogOut, Mail, Settings, User } from 'lucide-reac import React, { useContext } from 'react'; import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { AuthContext } from '@/utils/authContext'; +import { SafeAreaView } from 'react-native-safe-area-context'; export default function ProfileScreen() { const authContext = useContext(AuthContext); const { user } = authContext; const router = useRouter(); - // Genera le iniziali dell'utente + // Generate user initials const initials = `${user?.name?.[0] ?? ''}${user?.surname?.[0] ?? ''}`.toUpperCase(); return ( - {/* --- SEZIONE HEADER (INVARIATA) --- */} - - - router.back()} - > - - - - - {initials} - - - Profilo - {user?.name} {user?.surname} + + {/* Header Section */} + + + router.back()} + > + + + + + {initials} + + + Profilo + {user?.name} {user?.surname} + - + - {/* Card info - Testi ingranditi */} + {/* Info Card - Enlarged Texts */} - {/* Titolo sezione ingrandito */} + {/* Section title */} Informazioni - {/* Icona leggermente più grande e container adattato */} - {/* Label e valore ingranditi */} Email {user?.email} @@ -69,7 +70,7 @@ export default function ProfileScreen() { - {/* Actions - Testi e Pulsanti ingranditi */} + {/* Actions */} Azioni @@ -86,7 +87,8 @@ export default function ProfileScreen() { Apri - console.log('Apri impostazioni')} className="bg-white p-4 rounded-3xl shadow-sm flex-row items-center justify-between border border-gray-100 mb-4"> + {/* TODO: Settings not implemented at the moment */} + {/* console.log('Apri impostazioni')} className="bg-white p-4 rounded-3xl shadow-sm flex-row items-center justify-between border border-gray-100 mb-4"> @@ -97,7 +99,7 @@ export default function ProfileScreen() { Apri - + */} diff --git a/app/(protected)/sites/[id].tsx b/app/(protected)/sites/[id].tsx index c951235..5ba46d8 100644 --- a/app/(protected)/sites/[id].tsx +++ b/app/(protected)/sites/[id].tsx @@ -11,6 +11,7 @@ import dayjs from 'dayjs'; import { formatTimestamp, parseTimestamp } from '@/utils/dateTime'; import { downloadAndShareDocument, uploadDocument } from '@/utils/documentUtils'; import AddDocumentModal from '@/components/AddDocumentModal'; +import { SafeAreaView } from 'react-native-safe-area-context'; export default function SiteDocumentsScreen() { const router = useRouter(); @@ -24,12 +25,12 @@ export default function SiteDocumentsScreen() { const [showUploadModal, setShowUploadModal] = useState(false); const [isUploading, setIsUploading] = useState(false); - // Fetch dei documenti del cantiere + // Fetch site documents const fetchSiteDocuments = useCallback(async (siteId: number, isRefreshing = false) => { try { if (!isRefreshing) setIsLoading(true); - // Fetch Documenti + // Fetch Documents const response = await api.get(`/attachment/get-site-attachments`, { params: { siteId } }); @@ -74,13 +75,13 @@ export default function SiteDocumentsScreen() { }); const [showRangePicker, setShowRangePicker] = useState(false); - // Filtraggio Documenti + // Filter documents based on search term and date range const filteredDocs = documents.filter(doc => { - // Filtro Testuale (su nome documento) + // Textual Filter (on document name) const matchesSearch = doc.title.toLowerCase().includes(searchTerm.toLowerCase()); if (!matchesSearch) return false; - // Filtro Date Range + // Date Range Filter if (range.startDate || range.endDate) { const docDate = parseTimestamp(doc.updated_at); if (range.startDate) { @@ -95,7 +96,7 @@ export default function SiteDocumentsScreen() { return true; }); - // Gestione Download e Condivisione Documento + // Handle Document Download and Share const handleDownloadAndShare = async (mimetype: string, fileName: string, fileUrl: string) => { try { await downloadAndShareDocument(mimetype, fileName, fileUrl); @@ -105,7 +106,7 @@ export default function SiteDocumentsScreen() { } }; - // Gestione Caricamento Documento + // Handle Document Upload const handleUploadDocument = async (file: any, customTitle?: string) => { setIsUploading(true); try { @@ -130,36 +131,40 @@ export default function SiteDocumentsScreen() { return ( {/* Header */} - - router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100"> - - + + + + router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100"> + + - - {/* Badge Codice Cantiere */} - {site.code && ( - - - {site.code} + + {/* Site Code Badge */} + {site.code && ( + + + {site.code} + + + )} + + {/* Site Name with Truncate */} + + {site.name} + + Archivio documenti - )} - - {/* Nome Cantiere con Truncate funzionante */} - - {site.name} - - - Archivio documenti - + + - {/* Search + Date Row (Spostato qui) */} + {/* Search + Date Row */} @@ -189,7 +194,7 @@ export default function SiteDocumentsScreen() { onApply={setRange} /> - {/* Lista Documenti */} + {/* Documents List */} - {/* FAB (Spostato qui) */} + {/* FAB */} setShowUploadModal(true)} className="absolute bottom-8 right-6 w-16 h-16 bg-[#099499] rounded-full shadow-lg items-center justify-center active:scale-90" @@ -230,13 +235,13 @@ export default function SiteDocumentsScreen() { - {/* Modale Caricamento Documento */} + {/* Upload Document Modal */} setShowUploadModal(false)} onUpload={handleUploadDocument} isUploading={isUploading} /> - + ); } \ No newline at end of file diff --git a/app/(protected)/sites/index.tsx b/app/(protected)/sites/index.tsx index 41e4684..e32545a 100644 --- a/app/(protected)/sites/index.tsx +++ b/app/(protected)/sites/index.tsx @@ -6,6 +6,7 @@ import api from '@/utils/api'; import LoadingScreen from '@/components/LoadingScreen'; import { ConstructionSite } from '@/types/types'; import { useAlert } from '@/components/AlertComponent'; +import { SafeAreaView } from 'react-native-safe-area-context'; export default function SitesScreen() { const [searchTerm, setSearchTerm] = useState(''); @@ -15,12 +16,12 @@ export default function SitesScreen() { const router = useRouter(); const alert = useAlert(); - // Fetch cantieri e documenti + // Fetch sites and documents const fetchConstructionSites = async () => { try { if (!refreshing) setIsLoading(true); - // Fetch Cantieri + // Fetch Sites and their attachments count const response = await api.get('/construction-site/sites-attachments'); setConstructionSites(response.data); } catch (error) { @@ -41,7 +42,7 @@ export default function SitesScreen() { fetchConstructionSites(); }; - // Filtriamo i cantieri in base alla ricerca + // Filter sites based on search term const filteredSites = constructionSites.filter(site => site.name.toLowerCase().includes(searchTerm.toLowerCase()) || site.code.toLowerCase().includes(searchTerm.toLowerCase()) @@ -54,9 +55,11 @@ export default function SitesScreen() { return ( {/* Header */} - - Cantieri - Seleziona un cantiere per vedere i documenti + + + Cantieri + Seleziona un cantiere per vedere i documenti + @@ -74,11 +77,10 @@ export default function SitesScreen() { /> - {/* Lista Cantieri */} - {/* TODO: Rimuovere lo ScrollIndicator? */} + {/* Sites List */} } @@ -88,8 +90,8 @@ export default function SitesScreen() { key={index} onPress={() => router.push({ pathname: '/(protected)/sites/[id]', - params: { - id: site.id , + params: { + id: site.id, siteData: JSON.stringify(site), } })} @@ -111,7 +113,6 @@ export default function SitesScreen() { - {/* Freccia al posto del download */} ))} diff --git a/app/_layout.tsx b/app/_layout.tsx index edb19b8..8f02740 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -3,18 +3,21 @@ import { AuthProvider } from '@/utils/authContext'; import { Stack } from 'expo-router'; import { AlertProvider } from '@/components/AlertComponent'; import { NetworkProvider } from '@/utils/networkProvider'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; export default function AppLayout() { return ( - - - - - - - - - - + + + + + + + + + + + + ); } \ No newline at end of file diff --git a/app/login.tsx b/app/login.tsx index b093933..79a2bb4 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -13,21 +13,19 @@ export default function LoginScreen() { const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); - // TODO: Riscrivere funzione per migliorare leggibilità e gestione errori + // Login Handler function const handleLogin = async () => { - // TODO: Implementa toast o messaggio di errore più user-friendly if (!username || !password) { alert.showAlert('error', 'Attenzione', 'Inserisci username e password'); return; } - setIsLoading(true); try { username.trim(); password.trim(); - // Esegui richiesta di login + // Execute login request const response = await api.post("/user/login", { username: username, password: password @@ -35,25 +33,24 @@ export default function LoginScreen() { const { token, user } = response.data; - console.log("Login successo, token ricevuto."); + console.log("Login riuscito. Token:", token); console.log("Dati utente:", user); - // Passiamo token e dati utente al context che gestirà salvataggio e redirect + // Pass token and user data to the context which will handle saving and redirect authContext.logIn(token, user); } catch (error: any) { console.error("Login Error:", error); let message = "Si è verificato un errore durante l'accesso."; if (error.response) { - // Errore dal server (es. 401 Credenziali errate) + // Server error (e.g., 401 Invalid credentials) if (error.response.status === 401) { - // TODO: Alert o Toast specifico per credenziali errate message = "Credenziali non valide." } else { message = `Errore Server: ${error.response.data.message || error.response.status}`; } } else if (error.request) { - // Server non raggiungibile + // Server not reachable message = "Impossibile contattare il server. Controlla la connessione."; } @@ -65,7 +62,7 @@ export default function LoginScreen() { return ( - {/* Header con Logo/Titolo */} + {/* Header with Logo/Title */} + {/* TODO: Implement password recovery functionality */} Password dimenticata? - {/* Tasto Login */} + {/* Login Button */} { if (!visible) { setSelectedFile(null); @@ -24,11 +24,11 @@ export default function AddDocumentModal({ visible, onClose, onUpload, isUploadi } }, [visible]); - // TODO: Considerare selezione multipla? + // TODO: Need to handle multiple file selection? const pickDocument = async () => { try { const result = await DocumentPicker.getDocumentAsync({ - type: '*/*', // Puoi limitare a 'application/pdf', 'image/*', ecc. + type: '*/*', // You can limit to 'application/pdf', 'image/*', etc. copyToCacheDirectory: true, }); @@ -55,7 +55,7 @@ export default function AddDocumentModal({ visible, onClose, onUpload, isUploadi if (!selectedFile) return; const fullTitle = customTitle ? `${customTitle}${fileExtension}` : selectedFile.name; - // Se il titolo custom è vuoto, usiamo il nome originale + // If customTitle is empty, we use the original file name onUpload(selectedFile, fullTitle); }; @@ -65,8 +65,7 @@ export default function AddDocumentModal({ visible, onClose, onUpload, isUploadi setFileExtension(''); }; - // Formatta dimensione file - // TODO: Spostare in utils? + // Format file size in a human-readable format const formatSize = (size?: number) => { if (!size) return '0 B'; const k = 1024; @@ -93,10 +92,8 @@ export default function AddDocumentModal({ visible, onClose, onUpload, isUploadi - {/* Body */} - - {/* Area Selezione File */} + {/* File Selection Area */} {!selectedFile ? ( PDF, Immagini, Word ) : ( - // Visualizzazione File Selezionato + // Selected File View @@ -128,7 +125,7 @@ export default function AddDocumentModal({ visible, onClose, onUpload, isUploadi )} - {/* Campo Rinomina (Visibile solo se c'è un file) */} + {/* Rename field (Visible only if a file is selected) */} {selectedFile && ( Rinomina File diff --git a/components/AlertComponent.tsx b/components/AlertComponent.tsx index e6dea51..6420efd 100644 --- a/components/AlertComponent.tsx +++ b/components/AlertComponent.tsx @@ -11,6 +11,7 @@ interface AlertContextData { const AlertContext = createContext({} as AlertContextData); +// TODO: Move this config to a separate file const ALERT_CONFIG = { success: { icon: CheckCircle, @@ -57,33 +58,33 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => { const { icon: Icon, color, bgColor, btnColor } = ALERT_CONFIG[type]; + // TODO: Need to refactor component styles return ( {children} - {/* IL COMPONENTE ALERT MODALE */} - {/* Backdrop scuro */} + {/* Dark Backdrop */} - {/* Contenitore Alert */} + {/* Alert Container */} - {/* Icona Cerchiata */} + {/* Icon Circle */} - {/* Testi */} + {/* Texts */} {title} @@ -92,10 +93,10 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => { {message} - {/* Pulsante OK */} + {/* OK Button */} Ok, ho capito diff --git a/components/CalendarWidget.tsx b/components/CalendarWidget.tsx index 9c7684a..400f4a5 100644 --- a/components/CalendarWidget.tsx +++ b/components/CalendarWidget.tsx @@ -12,7 +12,7 @@ interface CalendarWidgetProps { export default function CalendarWidget({ events, types, onMonthChange }: CalendarWidgetProps) { const [currentDate, setCurrentDate] = useState(new Date()); - // Helpers per il calendario + // Calendar helpers const daysInMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate(); const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1).getDay(); // 0 = Sun const adjustedFirstDay = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1; // 0 = Mon @@ -40,8 +40,8 @@ export default function CalendarWidget({ events, types, onMonthChange }: Calenda const dateStr = `${year}-${month}-${dayStr}`; return events.find(event => { - // Logica semplice: controlla se la data cade nel range - // Nota: per una logica perfetta sui range lunghi, servirebbe un controllo più approfondito + // Simple logic: check if the date falls within the range + // Note: for perfect logic on long ranges, a more thorough check would be needed if (!event.start_date) return false; if (event.timeOffRequestType.name === 'Permesso') return event.start_date === dateStr; const end = event.end_date || event.start_date; @@ -51,7 +51,7 @@ export default function CalendarWidget({ events, types, onMonthChange }: Calenda return ( - {/* Header Mese */} + {/* Month Header */} changeMonth(-1)} @@ -106,7 +106,7 @@ export default function CalendarWidget({ events, types, onMonthChange }: Calenda })} - {/* Legenda */} + {/* Legend */} {types.map((type) => ( diff --git a/components/DeviceCard.tsx b/components/DeviceCard.tsx index a3e53fe..49d5fff 100644 --- a/components/DeviceCard.tsx +++ b/components/DeviceCard.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { Text, TouchableOpacity, View } from 'react-native'; const DeviceCard = ({ device, onToggle }: { device: HaEntity; onToggle: (entityId: string) => void; }) => { + // Icon mapping based on entity domain const getIcon = () => { const domain = device.entity_id.split('.')[0]; @@ -30,6 +31,7 @@ const DeviceCard = ({ device, onToggle }: { device: HaEntity; onToggle: (entityI const isToggleable = ['light', 'switch', 'fan', 'input_boolean'].includes(device.entity_id.split('.')[0]); const isOn = device.state === 'on'; + // State mapping const getDeviceState = (state: string) => { switch (state) { case 'on': diff --git a/components/NfcScanModal.tsx b/components/NfcScanModal.tsx index cddced9..6fef9e1 100644 --- a/components/NfcScanModal.tsx +++ b/components/NfcScanModal.tsx @@ -9,8 +9,9 @@ interface NfcScanModalProps { onScan: (data: string) => void; } +// TODO: Needs to be tested on a real device export default function NfcScanModal({ visible, onClose, onScan }: NfcScanModalProps) { - // Animazione per l'effetto "pulsante" (Breathing) + // Breathing animation effect const scaleAnim = useRef(new Animated.Value(1)).current; const opacityAnim = useRef(new Animated.Value(0.3)).current; @@ -44,7 +45,7 @@ export default function NfcScanModal({ visible, onClose, onScan }: NfcScanModalP } }; - // Loop infinito di espansione e contrazione + // Animation loop for the pulsing effect const animationLoop = () => { Animated.loop( Animated.parallel([ @@ -97,11 +98,9 @@ export default function NfcScanModal({ visible, onClose, onScan }: NfcScanModalP statusBarTranslucent > - - {/* Card Principale */} - {/* Bottone Chiudi (Alto Destra) */} + {/* Close Button (Top Right) */} - {/* Area Animata NFC */} + {/* NFC Animated Area */} - {/* Cerchio Pulsante (Sfondo) */} + {/* Pulsing Circle (Background) */} - {/* Cerchio Fisso (Primo piano) */} + {/* Fixed Circle (Foreground) */} - {/* Testi */} + {/* Texts */} Pronto alla scansione @@ -139,7 +138,7 @@ export default function NfcScanModal({ visible, onClose, onScan }: NfcScanModalP Avvicina il retro del tuo smartphone al Tag NFC per registrare la presenza. - {/* Indicatore Visivo (Simulazione onde) */} + {/* Indicator (Wave Simulation) */} diff --git a/components/OfflineScreen.tsx b/components/OfflineScreen.tsx index 4ac288e..b59b02f 100644 --- a/components/OfflineScreen.tsx +++ b/components/OfflineScreen.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { WifiOff, RefreshCw } from 'lucide-react-native'; +import { WifiOff } from 'lucide-react-native'; interface OfflineScreenProps { onRetry: () => void; @@ -12,7 +12,7 @@ export default function OfflineScreen({ onRetry, isRetrying = false }: OfflineSc return ( - {/* Icona con cerchio di sfondo */} + {/* Icon */} @@ -25,7 +25,7 @@ export default function OfflineScreen({ onRetry, isRetrying = false }: OfflineSc Sembra che non ci sia connessione a internet.{'\n'}Controlla il Wi-Fi o i dati mobili e riprova. - {/* Pulsante Riprova */} + {/* Retry Button */} - {isRetrying ? 'Controllo...' : 'Riprova'} diff --git a/components/QrScanModal.tsx b/components/QrScanModal.tsx index 9da78ef..28a06b5 100644 --- a/components/QrScanModal.tsx +++ b/components/QrScanModal.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { View, Text, Modal, TouchableOpacity, Vibration, StyleSheet } from 'react-native'; +import { View, Text, Modal, TouchableOpacity, Vibration, StyleSheet, Dimensions } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { CameraView, useCameraPermissions } from 'expo-camera'; import { X, ScanLine } from 'lucide-react-native'; @@ -13,8 +13,10 @@ interface QrScanModalProps { export default function QrScanModal({ visible, onClose, onScan }: QrScanModalProps) { const [permission, requestPermission] = useCameraPermissions(); const [scanned, setScanned] = useState(false); + const { width, height } = Dimensions.get('window'); + const squareSize = Math.min(width * 0.8, height * 0.8, 400); - // Gestione Permessi e Reset Stato + // Permission Handling and Reset Scanned State on Modal Open useEffect(() => { if (visible) { setScanned(false); @@ -59,26 +61,25 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro }} /> - {/* Overlay Oscuro con "buco" trasparente (Simulato visivamente con bordi o opacity) */} - - + {/* Dark Overlay with Transparent "Hole" (Visually Simulated with Borders or Opacity) */} + {/* Header Overlay */} - + Scansiona QR Code Inquadra il codice nel riquadro - + - {/* Area Centrale (Trasparente per la camera) */} - + {/* Central Area (Transparent for the camera) */} + - - {/* Angoli decorativi */} + + {/* Decorative Corners */} - {/* Linea scansione animata o icona */} + {/* Animated Scan Line or Icon */} {!scanned && } @@ -94,7 +95,7 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro Chiudi - + ); diff --git a/components/RangePickerModal.tsx b/components/RangePickerModal.tsx index 3561c60..cc54be6 100644 --- a/components/RangePickerModal.tsx +++ b/components/RangePickerModal.tsx @@ -4,7 +4,7 @@ import DateTimePicker, { DateType, useDefaultStyles } from 'react-native-ui-date import { Check, X } from 'lucide-react-native'; export const RangePickerModal = ({ visible, onClose, onApply }: any) => { - const defaultStyles = useDefaultStyles(); + const defaultStyles = useDefaultStyles('light'); const [range, setRange] = useState<{ startDate: DateType; endDate: DateType; diff --git a/components/RequestPermitModal.tsx b/components/RequestPermitModal.tsx index 09905fc..d412fed 100644 --- a/components/RequestPermitModal.tsx +++ b/components/RequestPermitModal.tsx @@ -16,7 +16,7 @@ interface RequestPermitModalProps { } export default function RequestPermitModal({ visible, types, onClose, onSubmit }: RequestPermitModalProps) { - const defaultStyles = useDefaultStyles(); + const defaultStyles = useDefaultStyles('light'); const alert = useAlert(); const [type, setType] = useState(types[0]); // Default to first type const [date, setDate] = useState(); @@ -31,7 +31,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } const [endTime, setEndTime] = useState(''); const [message, setMessage] = useState(''); - // Funzione per resettare le selezioni di data + // Clean up function to reset all fields const clearCalendar = () => { setDate(null); setRange({ startDate: null, endDate: null }); @@ -39,7 +39,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } setType(types[0]); }; - // Funzione per validare la richiesta + // Function to validate the request function validateRequest(type: TimeOffRequestType, date: string | null | undefined, range: { startDate: string | null; endDate: string | null }, startTime: string, endTime: string, message: string): string | null { if (!type) return "Seleziona una tipologia di assenza."; @@ -57,7 +57,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } return null; } - // Funzione per inviare la richiesta alla API + // Function to send the request to the API const saveRequest = async (requestData: any) => { try { const response = await api.post('/time-off-request/save-request', requestData); @@ -73,7 +73,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } } }; - // Funzione per inviare la richiesta + // Function to submit the request const handleSubmit = async () => { const error = validateRequest(type, date, range, startTime, endTime, message); if (error) { @@ -92,7 +92,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } try { await saveRequest(requestData); - onSubmit(requestData); // TODO: Gestire risposta e controllare fetch in index? + onSubmit(requestData); onClose(); } catch (e) { alert.showAlert("error", "Errore", "Impossibile inviare la richiesta."); @@ -108,7 +108,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } > - {/* Header Modale */} + {/* Modal Header */} Nuova Richiesta @@ -118,7 +118,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } - {/* Tipologia */} + {/* Permit Type */} Tipologia Assenza ) : ( @@ -218,7 +218,8 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } /> )} - {/* TODO: Trasformare message in una select? - Predefinito per alcuni tipi */} + + {/* Reason field */} Motivo - {/* Azioni */} + {/* Actions */} { diff --git a/components/TimePickerModal.tsx b/components/TimePickerModal.tsx index 445e7c7..053c7fd 100644 --- a/components/TimePickerModal.tsx +++ b/components/TimePickerModal.tsx @@ -13,7 +13,7 @@ interface TimePickerModalProps { } export const TimePickerModal = ({ visible, initialDate, title, onConfirm, onClose }: TimePickerModalProps) => { - const defaultStyles = useDefaultStyles(); + const defaultStyles = useDefaultStyles('light'); const [selectedDate, setSelectedDate] = useState(initialDate || new Date()); const formatTime = (date?: DateType | null) => { @@ -36,7 +36,7 @@ export const TimePickerModal = ({ visible, initialDate, title, onConfirm, onClos - {/* Header con chiusura */} + {/* Header */} {title} @@ -56,7 +56,7 @@ export const TimePickerModal = ({ visible, initialDate, title, onConfirm, onClos onChange={(d) => setSelectedDate(d.date || new Date())} /> - {/* Bottone conferma */} + {/* Confirm Button */} =18.17" diff --git a/utils/api.ts b/utils/api.ts index e19bc99..f4ec75a 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -4,17 +4,17 @@ import * as SecureStore from 'expo-secure-store'; const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL; export const KEY_TOKEN = 'auth_key'; -// Crea un'istanza di axios +// Create an Axios instance with default configuration const api = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, - timeout: 10000, // 10 secondi timeout + timeout: 10000, // 10 seconds timeout }); -// Interceptor: Aggiunge il token a OGNI richiesta se esiste +// Interceptor: Adds the token to EVERY request if it exists api.interceptors.request.use( async (config) => { const token = await SecureStore.getItemAsync(KEY_TOKEN); @@ -29,7 +29,7 @@ api.interceptors.request.use( } ); -// Interceptor: Gestione errori globale (es. token scaduto) +// Interceptor: Global error handling (e.g., expired token) api.interceptors.response.use( (response) => response, async (error) => { @@ -38,9 +38,9 @@ api.interceptors.response.use( if (error.response) { console.error('[API ERROR]', error.response.status, error.response.data); - // Se riceviamo 401 (Unauthorized), potremmo voler fare il logout forzato + // If we receive 401 (Unauthorized), we might want to force logout if (error.response.status === 401) { - // TODO: Qui potresti emettere un evento per disconnettere l'utente + // TODO: Here you can add logic to redirect to login screen if needed await SecureStore.deleteItemAsync(KEY_TOKEN); } } else { diff --git a/utils/authContext.tsx b/utils/authContext.tsx index 9313b99..16d732c 100644 --- a/utils/authContext.tsx +++ b/utils/authContext.tsx @@ -58,15 +58,15 @@ export function AuthProvider({ children }: PropsWithChildren) { useEffect(() => { const initApp = async () => { try { - // 1. Recupero Token salvato + // Get saved Token from SecureStore const savedToken = await SecureStore.getItemAsync(KEY_TOKEN); if (savedToken) { console.log("Token trovato: ", savedToken); - // 2. Chiamata al backend per verificare il token e scaricare i dati utente - // Nota: api.ts aggiunge già l'header Authorization grazie all'interceptor (se configurato per leggere da SecureStore) - // Se il tuo api.ts legge da AsyncStorage, assicurati che siano allineati, altrimenti passalo a mano qui: + // Call backend to verify token and fetch user data + // Note: api.ts already adds the Authorization header thanks to the interceptor (if configured to read from SecureStore) + // If your api.ts reads from AsyncStorage, make sure they are aligned, otherwise pass it manually here: const response = await api.get("/user/info", { headers: { Authorization: `Bearer ${savedToken}` } }); @@ -75,13 +75,13 @@ export function AuthProvider({ children }: PropsWithChildren) { const userData = result.user; console.log("Sessione valida, dati utente caricati:", userData); - // 3. Mappatura dati (Backend -> Frontend) - // Il backend actionMe ritorna: { id, username, role } + // Data mapping (Backend -> Frontend) + // The backend actionMe returns: { id, username, role } const loadedUser: UserData = { id: userData.id, username: userData.username, role: userData.role, - // Gestiamo i campi opzionali se il backend non li manda ancora + // Handle optional fields if the backend doesn't send them yet name: userData.name, surname: userData.surname || '', email: userData.email || '', @@ -96,7 +96,7 @@ export function AuthProvider({ children }: PropsWithChildren) { } catch (error: any) { console.error('Errore inizializzazione (Token scaduto o Server down):', error.message); - // Se il token non è valido, puliamo tutto + // If the token is not valid, clear everything await SecureStore.deleteItemAsync(KEY_TOKEN); setIsAuthenticated(false); setUser(null); @@ -109,7 +109,7 @@ export function AuthProvider({ children }: PropsWithChildren) { initApp(); }, []); - // Protezione rotte (opzionale, ma consigliata qui o nel Layout) + // Route protection (optional, but recommended here or in the Layout) useEffect(() => { if (!isReady) return; diff --git a/utils/dateTime.ts b/utils/dateTime.ts index 728ad08..9c17afb 100644 --- a/utils/dateTime.ts +++ b/utils/dateTime.ts @@ -1,9 +1,9 @@ import { DateType } from "react-native-ui-datepicker"; /** - * Trasforma una data da "YYYY-MM-DD" a "DD/MM/YYYY" - * @param dateStr stringa data in formato ISO "YYYY-MM-DD" - * @returns stringa formattata "DD/MM/YYYY" + * Transforms "YYYY-MM-DD" to "DD/MM/YYYY" + * @param dateStr string in ISO date format "YYYY-MM-DD" + * @returns formatted string "DD/MM/YYYY" */ export const formatDate = (dateStr: string | null | undefined): string => { if (!dateStr) return ''; @@ -12,9 +12,9 @@ export const formatDate = (dateStr: string | null | undefined): string => { }; /** - * Trasforma un'ora da "HH:MM:SS" a "HH:MM" - * @param timeStr stringa ora in formato "HH:MM:SS" - * @returns stringa formattata "HH:MM" + * Transforms time from "HH:MM:SS" to "HH:MM" + * @param timeStr string in time format "HH:MM:SS" + * @returns formatted string "HH:MM" */ export const formatTime = (timeStr: string | null | undefined): string => { if (!timeStr) return ''; @@ -23,9 +23,9 @@ export const formatTime = (timeStr: string | null | undefined): string => { }; /** - * Formatta una data per l'uso con un date picker, normalizzandola a mezzanotte - * @param d Data in formato DateType - * @returns stringa data in formato "YYYY-MM-DD" o null se l'input è null/undefined + * Formats a date for use with a date picker, normalizing it to midnight + * @param d Date in DateType format + * @returns string in "YYYY-MM-DD" format or null if input is null/undefined */ export const formatPickerDate = (d: DateType | null | undefined) => { if (!d) return null; @@ -41,9 +41,9 @@ export const formatPickerDate = (d: DateType | null | undefined) => { } /** - * Trasforma un timestamp in stringa "DD/MM/YYYY HH:mm:ss" - * @param timestamp stringa o oggetto Date - * @returns stringa formattata oppure vuota se input non valido + * Transforms a timestamp into a string "DD/MM/YYYY HH:mm:ss" + * @param timestamp string or Date object + * @returns formatted string or empty string if input is invalid */ export const formatTimestamp = (timestamp: string | Date | null | undefined): string => { if (!timestamp) return ''; @@ -52,7 +52,7 @@ export const formatTimestamp = (timestamp: string | Date | null | undefined): st if (isNaN(date.getTime())) return ''; const dd = String(date.getDate()).padStart(2, '0'); - const mm = String(date.getMonth() + 1).padStart(2, '0'); // mesi da 0 a 11 + const mm = String(date.getMonth() + 1).padStart(2, '0'); // months from 0 to 11 const yyyy = date.getFullYear(); const hh = String(date.getHours()).padStart(2, '0'); @@ -63,9 +63,9 @@ export const formatTimestamp = (timestamp: string | Date | null | undefined): st }; /** - * Converte un timestamp ISO in oggetto Date - * @param dateStr stringa data in formato ISO - * @returns oggetto Date corrispondente + * Converts an ISO timestamp to a Date object + * @param dateStr string in ISO date format + * @returns corresponding Date object */ export const parseTimestamp = (dateStr: string | undefined | null): Date => { if (!dateStr) return new Date(); diff --git a/utils/documentUtils.tsx b/utils/documentUtils.tsx index 14b3d75..7cc2ecf 100644 --- a/utils/documentUtils.tsx +++ b/utils/documentUtils.tsx @@ -1,16 +1,12 @@ import api from '@/utils/api'; import { Directory, File, Paths } from 'expo-file-system'; -import * as FileSystem from 'expo-file-system/legacy'; -import { StorageAccessFramework } from 'expo-file-system/legacy'; import * as Sharing from 'expo-sharing'; -import * as Linking from 'expo-linking'; -import { Platform } from 'react-native'; /** - * Gestisce l'upload di un documento verso il server usando FormData - * @param file File da caricare (deve avere almeno la proprietà 'uri') - * @param siteId ID del sito a cui associare il documento (null per registro generale) - * @param customTitle Titolo personalizzato per il documento (opzionale) + * Handles upload of a document through the server using FormData + * @param file File to upload (must have at least the 'uri' property) + * @param siteId ID of the site to associate the document with (null for general register) + * @param customTitle Custom title for the document (optional) */ export const uploadDocument = async ( file: any, @@ -64,10 +60,10 @@ export const uploadDocument = async ( }; /** - * Scarica un documento e offre di aprirlo/condividerlo (expo-sharing) - * @param attachmentId ID o URL relativo del documento - * @param fileName Nome con cui salvare il file - * @param fileUrl URL completo del file da scaricare + * Download and share a document (expo-sharing) + * @param attachmentId ID or relative URL of the document + * @param fileName Name to save the file as + * @param fileUrl Full URL of the file to download */ export const downloadAndShareDocument = async ( mimetype: string, @@ -75,7 +71,7 @@ export const downloadAndShareDocument = async ( fileUrl: string ): Promise => { try { - // TODO: Gestire meglio il download (attualmente si basa su expo-sharing) + // TODO: Download based on expo-sharing - some mime types may not be supported if (!fileUrl || !fileName) { throw new Error("Parametri mancanti per il download del documento."); } diff --git a/utils/haApi.ts b/utils/haApi.ts index 98735a6..8e8a21a 100644 --- a/utils/haApi.ts +++ b/utils/haApi.ts @@ -1,18 +1,18 @@ import axios, { AxiosError } from 'axios'; import { HaArea, HaEntity } from '@/types/types'; -// CONFIGURAZIONE +// HOME ASSISTANT API CONFIGURATION const HA_API_URL = process.env.EXPO_PUBLIC_HA_API_URL; const HA_TOKEN = process.env.EXPO_PUBLIC_HA_TOKEN; -// Crea un'istanza di axios per Home Assistant +// Create an axios instance for Home Assistant const haApi = axios.create({ baseURL: HA_API_URL, headers: { 'Authorization': `Bearer ${HA_TOKEN}`, 'Content-Type': 'application/json', }, - timeout: 5000, // 5 secondi di timeout + timeout: 5000, // 5 seconds timeout }); haApi.interceptors.request.use((config) => { @@ -24,7 +24,7 @@ haApi.interceptors.request.use((config) => { * Connection test */ export const testHaConnection = async (): Promise<{ success: boolean; message: string }> => { - // Controlla se le variabili d'ambiente sono caricate + // Check if environment variables are loaded if (!HA_API_URL || !HA_TOKEN) { console.error("Variabili d'ambiente per Home Assistant non trovate. Assicurati che EXPO_PUBLIC_HA_API_URL and EXPO_PUBLIC_HA_TOKEN siano definite nel file .env"); return { success: false, message: "Configurazione API per Home Assistant mancante." }; @@ -32,7 +32,7 @@ export const testHaConnection = async (): Promise<{ success: boolean; message: s try { const response = await haApi.get('/'); - // Se la risposta è OK, HA restituisce un JSON con 'message' + // If the response is OK, HA returns a JSON with 'message' if (response.status === 200 && response.data.message) { return { success: true, message: response.data.message }; } @@ -46,7 +46,7 @@ export const testHaConnection = async (): Promise<{ success: boolean; message: s } if (axiosError.response) { - // Errori con una risposta dal server (es. 401, 404) + // Errors with a server response (e.g., 401, 404) if (axiosError.response.status === 401) { return { success: false, message: "Errore 401: Token non autorizzato. Controlla il Long-Lived Token." }; } @@ -55,10 +55,10 @@ export const testHaConnection = async (): Promise<{ success: boolean; message: s } return { success: false, message: `Errore server: Status ${axiosError.response.status}` }; } else if (axiosError.request) { - // Errori di rete (la richiesta è partita ma non ha ricevuto risposta) + // Network errors (cannot receive a response) return { success: false, message: `Errore di rete: Impossibile raggiungere ${HA_API_URL}` }; } else { - // Errore generico + // Generic errors return { success: false, message: `Errore sconosciuto: ${axiosError.message}` }; } } @@ -92,7 +92,7 @@ export const getHaAreas = async (): Promise => { } catch (error) { console.error("Errore recupero aree:", error); - return []; // Restituisce un array vuoto in caso di errore + return []; // Return an empty array in case of error } }; @@ -120,7 +120,7 @@ export const getHaEntitiesByArea = async (areaId: string): Promise = } catch (error) { console.error(`Errore recupero entità per l'area ${areaId}:`, error); - return []; // Restituisce un array vuoto in caso di errore + return []; // Return an empty array in case of error } };