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:
12
utils/api.ts
12
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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user