feat: Add document download and upload. Add NFC support and enhance attendance and permits views

- Improved error message handling in LoginScreen for invalid credentials.
- Added new images: mariani-icon.png and mariani-splash.png.
- Updated AddDocumentModal to handle file extensions and improve UI.
- Enhanced CalendarWidget to support month change callbacks.
- Introduced NfcScanModal for NFC tag scanning with animations.
- Revamped QrScanModal to utilize camera for QR code scanning.
- Removed mock data from data.ts and streamlined Office data.
- Updated package dependencies for expo-camera and react-native-nfc-manager.
- Added utility function to parse seconds to time format.
- Refactored document upload logic to use FormData for server uploads.
This commit is contained in:
2026-01-19 18:10:31 +01:00
parent 325bfbe19f
commit 44d021891f
20 changed files with 882 additions and 299 deletions

View File

@ -1,40 +1,99 @@
import React from 'react';
import { View, Text, Modal, TouchableOpacity } from 'react-native';
import { QrCode } from 'lucide-react-native';
import React, { useState, useEffect } from 'react';
import { View, Text, Modal, TouchableOpacity, Vibration, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { CameraView, useCameraPermissions } from 'expo-camera';
import { X, ScanLine } from 'lucide-react-native';
interface QrScanModalProps {
visible: boolean;
onClose: () => void;
onScan: (data: string) => void;
}
export default function QrScanModal({ visible, onClose }: QrScanModalProps) {
export default function QrScanModal({ visible, onClose, onScan }: QrScanModalProps) {
const [permission, requestPermission] = useCameraPermissions();
const [scanned, setScanned] = useState(false);
// Gestione Permessi e Reset Stato
useEffect(() => {
if (visible) {
setScanned(false);
if (permission && !permission.granted && permission.canAskAgain) {
requestPermission();
}
}
}, [visible, permission]);
const handleBarCodeScanned = ({ type, data }: { type: string; data: string }) => {
if (scanned) return;
setScanned(true);
Vibration.vibrate();
console.log(`Bar code with type ${type} and data ${data} has been scanned!`);
onScan(data);
onClose();
};
if (!permission) {
return <View />;
}
if (!permission.granted && visible) {
requestPermission();
}
return (
<Modal
visible={visible}
transparent={true}
animationType="fade"
statusBarTranslucent
animationType="slide"
presentationStyle="fullScreen"
onRequestClose={onClose}
>
<View className="flex-1 bg-black/90 items-center justify-center p-4">
<View className="bg-white rounded-[2rem] p-8 w-full max-w-sm items-center shadow-2xl">
<QrCode color="#099499" size={80} />
<Text className="text-2xl font-bold mt-6 text-gray-800 text-center">Scansione in corso...</Text>
<Text className="text-gray-500 mt-3 text-center text-base px-4">
Inquadra il codice QR nel riquadro sottostante
</Text>
<View className="flex-1 bg-black">
{/* Camera Full Screen */}
<CameraView
style={StyleSheet.absoluteFillObject}
facing="back"
onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
barcodeScannerSettings={{
barcodeTypes: ["qr"],
}}
/>
{/* Viewfinder Simulata */}
<View className="mt-8 w-64 h-64 border-4 border-[#099499] rounded-3xl bg-gray-50 relative overflow-hidden items-center justify-center">
<View className="absolute top-0 w-full h-1 bg-red-500 shadow-[0_0_15px_rgba(239,68,68,0.8)]" />
<Text className="text-gray-400 text-sm">Camera Feed</Text>
{/* Overlay Oscuro con "buco" trasparente (Simulato visivamente con bordi o opacity) */}
<View className="flex-1">
{/* Header Overlay */}
<SafeAreaView className="flex-1 bg-black/60 items-center pt-8">
<Text className="text-white text-xl font-bold">Scansiona QR Code</Text>
<Text className="text-gray-300 text-base mt-1">Inquadra il codice nel riquadro</Text>
</SafeAreaView>
{/* Area Centrale (Trasparente per la camera) */}
<View className="flex-row h-[400px]">
<View className="flex-1 bg-black/60" />
<View className="w-[400px] h-[400px] border-2 border-[#099499] bg-transparent relative justify-center items-center">
{/* Angoli decorativi */}
<View className="absolute top-0 left-0 w-6 h-6 border-l-4 border-t-4 border-[#099499]" />
<View className="absolute top-0 right-0 w-6 h-6 border-r-4 border-t-4 border-[#099499]" />
<View className="absolute bottom-0 left-0 w-6 h-6 border-l-4 border-b-4 border-[#099499]" />
<View className="absolute bottom-0 right-0 w-6 h-6 border-r-4 border-b-4 border-[#099499]" />
{/* Linea scansione animata o icona */}
{!scanned && <ScanLine color="#099499" size={40} className="opacity-50" />}
</View>
<View className="flex-1 bg-black/60" />
</View>
<TouchableOpacity
onPress={onClose}
className="mt-10 bg-gray-100 rounded-2xl px-10 py-4 w-full active:bg-gray-200"
>
<Text className="text-gray-800 font-bold text-lg text-center">Annulla</Text>
</TouchableOpacity>
{/* Footer Overlay */}
<View className="flex-1 bg-black/60 items-center justify-end pb-12 px-6">
<TouchableOpacity
onPress={onClose}
className="bg-white/20 p-4 rounded-full"
>
<X color="white" size={32} />
</TouchableOpacity>
<Text className="text-white mt-4 font-medium">Chiudi</Text>
</View>
</View>
</View>
</Modal>