feat: Update assets and improve code comments across multiple components

- Updated favicon and various image assets.
- Enhanced comments.
- Adjusted styles and functionality in several components for improved user experience.
- Updated package-lock.json to reflect dependency updates.
This commit is contained in:
2026-02-06 12:56:34 +01:00
parent 7a6a7f5d35
commit 7c8ef45e5a
36 changed files with 325 additions and 317 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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();

View File

@@ -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<void> => {
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.");
}

View File

@@ -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<HaArea[]> => {
} 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<HaEntity[]> =
} 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
}
};