feat: update app configuration and enhance UI components

This commit is contained in:
2026-02-06 18:06:48 +01:00
parent 7c8ef45e5a
commit 882cfc281d
19 changed files with 339 additions and 266 deletions

View File

@@ -58,6 +58,12 @@
"experiments": { "experiments": {
"typedRoutes": true, "typedRoutes": true,
"reactCompiler": true "reactCompiler": true
},
"extra": {
"router": {},
"eas": {
"projectId": "51cde1ca-e1b5-46c6-b9b4-0f17bf95693c"
}
} }
} }
} }

View File

@@ -1,13 +1,13 @@
import { useAlert } from '@/components/AlertComponent'; import { useAlert } from '@/components/AlertComponent';
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, ScrollView, RefreshControl } from 'react-native';
import { QrCode, CheckCircle2, Nfc } from 'lucide-react-native';
import QrScanModal from '@/components/QrScanModal';
import NfcScanModal from '@/components/NfcScanModal';
import LoadingScreen from '@/components/LoadingScreen'; import LoadingScreen from '@/components/LoadingScreen';
import NfcScanModal from '@/components/NfcScanModal';
import QrScanModal from '@/components/QrScanModal';
import { AttendanceRecord } from '@/types/types';
import api from '@/utils/api'; import api from '@/utils/api';
import { formatDate, formatTime, parseSecondsToTime } from '@/utils/dateTime'; import { formatDate, formatTime, parseSecondsToTime } from '@/utils/dateTime';
import { AttendanceRecord } from '@/types/types'; import { CheckCircle2, Nfc, QrCode } from 'lucide-react-native';
import React, { useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import NfcManager from 'react-native-nfc-manager'; import NfcManager from 'react-native-nfc-manager';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
@@ -120,7 +120,7 @@ export default function AttendanceScreen() {
<View className="flex-1 bg-gray-50"> <View className="flex-1 bg-gray-50">
{/* Header */} {/* Header */}
<View className="bg-white px-6 pb-6 shadow-sm border-b border-gray-100"> <View className="bg-white px-6 pb-6 shadow-sm border-b border-gray-100">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
<Text className="text-3xl font-bold text-gray-800 mb-1">Gestione Presenze</Text> <Text className="text-3xl font-bold text-gray-800 mb-1">Gestione Presenze</Text>
<Text className="text-base text-gray-500">Registra i tuoi movimenti</Text> <Text className="text-base text-gray-500">Registra i tuoi movimenti</Text>
</SafeAreaView> </SafeAreaView>

View File

@@ -6,6 +6,7 @@ import { useLocalSearchParams, useRouter } from 'expo-router';
import { ChevronLeft, ServerOff, WifiOff } from 'lucide-react-native'; import { ChevronLeft, ServerOff, WifiOff } from 'lucide-react-native';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function AutomationDetailScreen() { export default function AutomationDetailScreen() {
const router = useRouter(); const router = useRouter();
@@ -95,14 +96,18 @@ export default function AutomationDetailScreen() {
return ( return (
<View className="flex-1 bg-gray-50"> <View className="flex-1 bg-gray-50">
<View className="bg-white p-6 pt-16 shadow-sm flex-row justify-between items-center border-b border-gray-100"> <View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100">
<View className='flex-row items-center gap-4'> <SafeAreaView edges={['top']} className='pt-5'>
<TouchableOpacity onPress={() => router.back()} className='rounded-full active:bg-gray-100'> <View className="flex-row justify-between items-center">
<ChevronLeft size={28} color="#4b5563" /> <View className='flex-row items-center gap-4'>
</TouchableOpacity> <TouchableOpacity onPress={() => router.back()} className='rounded-full active:bg-gray-100'>
<Text className="text-2xl font-bold text-gray-800">{area.name}</Text> <ChevronLeft size={28} color="#4b5563" />
</View> </TouchableOpacity>
<View className={`ms-auto w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm bg-green-500`} /> <Text className="text-2xl font-bold text-gray-800">{area.name}</Text>
</View>
<View className={`ms-auto w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm bg-green-500`} />
</View>
</SafeAreaView>
</View> </View>
<ScrollView <ScrollView

View File

@@ -1,10 +1,11 @@
import LoadingScreen from '@/components/LoadingScreen';
import type { HaArea } from '@/types/types'; import type { HaArea } from '@/types/types';
import { getHaAreas, testHaConnection } from '@/utils/haApi'; import { getHaAreas, testHaConnection } from '@/utils/haApi';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { Archive, Briefcase, DoorOpen, Grid2X2, Lightbulb, Plus, Users, WifiOff } from 'lucide-react-native'; import { Archive, Briefcase, DoorOpen, Grid2X2, Lightbulb, Users, WifiOff } from 'lucide-react-native';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import LoadingScreen from '@/components/LoadingScreen'; import { SafeAreaView } from 'react-native-safe-area-context';
const AREA_STYLES = [ const AREA_STYLES = [
{ icon: Users, bgColor: 'bg-indigo-100' }, { icon: Users, bgColor: 'bg-indigo-100' },
@@ -70,11 +71,48 @@ export default function AutomationScreen() {
return <LoadingScreen />; return <LoadingScreen />;
} }
// TODO: Remove duplicated code
if (!connectionStatus.success) { if (!connectionStatus.success) {
return ( return (
<View className="flex-1 bg-gray-50"> <View className="flex-1 bg-gray-50">
<View className="bg-white pt-16 shadow-sm border-b border-gray-100"> <View className="bg-white shadow-sm border-b border-gray-100">
<View className="px-6 pb-4 flex-row justify-between items-center"> <SafeAreaView edges={['top']} className='pt-5'>
<View className="flex-row justify-between items-center px-6 pb-6">
<View>
<Text className="text-3xl font-bold text-gray-800 mb-1">Domotica</Text>
<Text className="text-base text-gray-500">Controlla gli ambienti</Text>
</View>
<View className={`px-4 py-2 rounded-xl border ${connectionStatus.success ? 'bg-green-100 border-green-200' : 'bg-red-100 border-red-200'}`}>
<Text className={`text-xs font-bold tracking-wide ${connectionStatus.success ? 'text-green-700' : 'text-red-700'}`}>
{connectionStatus.success ? 'ONLINE' : 'OFFLINE'}
</Text>
</View>
</View>
</SafeAreaView>
</View>
<ScrollView
contentContainerClassName="flex-grow items-center justify-center pb-[100px]"
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#099499']} tintColor={'#099499'} />}
>
<WifiOff size={48} color="#9ca3af" />
<Text className="text-lg text-gray-500 font-medium mt-4">Errore di connessione</Text>
<Text className="text-center text-gray-400 mt-2 px-10">
{connectionStatus.message}
</Text>
<TouchableOpacity onPress={onRefresh} className="mt-6 bg-[#099499] px-6 py-3 rounded-lg active:scale-95">
<Text className="text-white font-bold">Riprova</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
return (
<View className="flex-1 bg-gray-50">
<View className="bg-white shadow-sm border-b border-gray-100">
<SafeAreaView edges={['top']} className='pt-5'>
<View className="flex-row justify-between items-center px-6 pb-6">
<View> <View>
<Text className="text-3xl font-bold text-gray-800 mb-1">Domotica</Text> <Text className="text-3xl font-bold text-gray-800 mb-1">Domotica</Text>
<Text className="text-base text-gray-500">Controlla gli ambienti</Text> <Text className="text-base text-gray-500">Controlla gli ambienti</Text>
@@ -98,51 +136,7 @@ export default function AutomationScreen() {
))} ))}
</ScrollView> </ScrollView>
)} )}
</View> </SafeAreaView>
<ScrollView
contentContainerClassName="flex-grow items-center justify-center pb-[100px]"
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#099499']} tintColor={'#099499'} />}
>
<WifiOff size={48} color="#9ca3af" />
<Text className="text-lg text-gray-500 font-medium mt-4">Errore di connessione</Text>
<Text className="text-center text-gray-400 mt-2 px-10">
{connectionStatus.message}
</Text>
<TouchableOpacity onPress={onRefresh} className="mt-6 bg-[#099499] px-6 py-3 rounded-lg active:scale-95">
<Text className="text-white font-bold">Riprova</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
return (
<View className="flex-1 bg-gray-50">
<View className="bg-white pt-16 shadow-sm border-b border-gray-100">
<View className="px-6 pb-4 flex-row justify-between items-center">
<View>
<Text className="text-3xl font-bold text-gray-800 mb-1">Domotica</Text>
<Text className="text-base text-gray-500">Controlla gli ambienti</Text>
</View>
<View className={`px-4 py-2 rounded-xl border ${connectionStatus.success ? 'bg-green-100 border-green-200' : 'bg-red-100 border-red-200'}`}>
<Text className={`text-xs font-bold tracking-wide ${connectionStatus.success ? 'text-green-700' : 'text-red-700'}`}>
{connectionStatus.success ? 'ONLINE' : 'OFFLINE'}
</Text>
</View>
</View>
{connectionStatus.success && (
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerClassName="px-5 pb-4 gap-3">
{floors.map(floor => (
<TouchableOpacity
key={floor}
onPress={() => setSelectedFloor(floor)}
className={`px-5 py-2.5 rounded-xl ${selectedFloor === floor ? 'bg-[#099499]' : 'bg-gray-100 active:bg-gray-200'}`}
>
<Text className={`font-bold ${selectedFloor === floor ? 'text-white' : 'text-gray-600'}`}>{floor}</Text>
</TouchableOpacity>
))}
</ScrollView>
)}
</View> </View>
<ScrollView <ScrollView

View File

@@ -1,11 +1,11 @@
import { useRouter } from 'expo-router'; import LoadingScreen from '@/components/LoadingScreen';
import { AlertTriangle, CalendarDays, CheckCircle2, FileText, QrCode, User, CalendarClock, LayoutDashboard } from 'lucide-react-native';
import React, { useState, useContext, useEffect } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { AuthContext } from '@/utils/authContext';
import { ActivityItem } from '@/types/types'; import { ActivityItem } from '@/types/types';
import api from '@/utils/api'; import api from '@/utils/api';
import LoadingScreen from '@/components/LoadingScreen'; import { AuthContext } from '@/utils/authContext';
import { useRouter } from 'expo-router';
import { AlertTriangle, CalendarClock, CalendarDays, CheckCircle2, FileText, LayoutDashboard, QrCode, User } from 'lucide-react-native';
import React, { useContext, useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
export default function HomeScreen() { export default function HomeScreen() {
@@ -64,7 +64,7 @@ export default function HomeScreen() {
return ( return (
<View className="flex-1 bg-[#099499]"> <View className="flex-1 bg-[#099499]">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
{/* Custom Banner */} {/* Custom Banner */}
<View className="pb-6 px-6 shadow-sm z-10"> <View className="pb-6 px-6 shadow-sm z-10">
<View className="flex-row justify-between items-start"> <View className="flex-row justify-between items-start">

View File

@@ -1,13 +1,13 @@
import { useAlert } from '@/components/AlertComponent'; import { useAlert } from '@/components/AlertComponent';
import React, { JSX, useEffect, useMemo, useState } from 'react';
import { Calendar as CalendarIcon, CalendarX, Clock, Plus, Thermometer } from 'lucide-react-native';
import { ScrollView, Text, TouchableOpacity, View, ActivityIndicator, RefreshControl } from 'react-native';
import { TimeOffRequest, TimeOffRequestType } from '@/types/types';
import RequestPermitModal from '@/components/RequestPermitModal';
import CalendarWidget from '@/components/CalendarWidget'; import CalendarWidget from '@/components/CalendarWidget';
import LoadingScreen from '@/components/LoadingScreen'; import LoadingScreen from '@/components/LoadingScreen';
import RequestPermitModal from '@/components/RequestPermitModal';
import { TimeOffRequest, TimeOffRequestType } from '@/types/types';
import api from '@/utils/api'; import api from '@/utils/api';
import { formatDate, formatTime } from '@/utils/dateTime'; import { formatDate, formatTime } from '@/utils/dateTime';
import { Calendar as CalendarIcon, CalendarX, Clock, Plus, Thermometer } from 'lucide-react-native';
import React, { JSX, useEffect, useMemo, useState } from 'react';
import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
// Icon Mapping // Icon Mapping
@@ -93,7 +93,7 @@ export default function PermitsScreen() {
{/* Header */} {/* Header */}
<View className="bg-white px-6 pb-6 shadow-sm border-b border-gray-100 flex-row justify-between items-center"> <View className="bg-white px-6 pb-6 shadow-sm border-b border-gray-100 flex-row justify-between items-center">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
<View> <View>
<Text className="text-3xl font-bold text-gray-800 mb-1">Ferie e Permessi</Text> <Text className="text-3xl font-bold text-gray-800 mb-1">Ferie e Permessi</Text>
<Text className="text-base text-gray-500">Gestisci le tue assenze</Text> <Text className="text-base text-gray-500">Gestisci le tue assenze</Text>

View File

@@ -1,16 +1,16 @@
import AddDocumentModal from '@/components/AddDocumentModal';
import { useAlert } from '@/components/AlertComponent'; import { useAlert } from '@/components/AlertComponent';
import { Download, FileText, MapPin, Plus, Search, CalendarIcon, ChevronLeft } from 'lucide-react-native'; import LoadingScreen from '@/components/LoadingScreen';
import React, { useEffect, useState } from 'react';
import { useRouter } from 'expo-router';
import { RangePickerModal } from '@/components/RangePickerModal'; import { RangePickerModal } from '@/components/RangePickerModal';
import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { DocumentItem } from '@/types/types'; import { DocumentItem } from '@/types/types';
import api from '@/utils/api'; import api from '@/utils/api';
import dayjs from 'dayjs';
import LoadingScreen from '@/components/LoadingScreen';
import { formatTimestamp, parseTimestamp } from '@/utils/dateTime'; import { formatTimestamp, parseTimestamp } from '@/utils/dateTime';
import AddDocumentModal from '@/components/AddDocumentModal';
import { downloadAndShareDocument, uploadDocument } from '@/utils/documentUtils'; import { downloadAndShareDocument, uploadDocument } from '@/utils/documentUtils';
import dayjs from 'dayjs';
import { useRouter } from 'expo-router';
import { CalendarIcon, ChevronLeft, Download, FileText, MapPin, Plus, Search } from 'lucide-react-native';
import React, { useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
export default function DocumentsScreen() { export default function DocumentsScreen() {
@@ -107,7 +107,7 @@ export default function DocumentsScreen() {
<View className="flex-1 bg-gray-50"> <View className="flex-1 bg-gray-50">
{/* Header */} {/* Header */}
<View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100"> <View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
<View className='flex-row items-center gap-4'> <View className='flex-row items-center gap-4'>
<TouchableOpacity onPress={() => router.back()} className="p-2 rounded-full active:bg-gray-100"> <TouchableOpacity onPress={() => router.back()} className="p-2 rounded-full active:bg-gray-100">
<ChevronLeft size={28} color="#4b5563" /> <ChevronLeft size={28} color="#4b5563" />

View File

@@ -1,8 +1,8 @@
import { AuthContext } from '@/utils/authContext';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { ChevronLeft, FileText, LogOut, Mail, Settings, User } from 'lucide-react-native'; import { ChevronLeft, FileText, LogOut, Mail, User } from 'lucide-react-native';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { AuthContext } from '@/utils/authContext';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
export default function ProfileScreen() { export default function ProfileScreen() {
@@ -15,7 +15,7 @@ export default function ProfileScreen() {
return ( return (
<View className="flex-1 bg-[#099499]"> <View className="flex-1 bg-[#099499]">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
{/* Header Section */} {/* Header Section */}
<View className="pb-6 px-4"> <View className="pb-6 px-4">
<View className="flex-row justify-start items-center gap-4"> <View className="flex-row justify-start items-center gap-4">

View File

@@ -1,16 +1,16 @@
import AddDocumentModal from '@/components/AddDocumentModal';
import { useAlert } from '@/components/AlertComponent'; import { useAlert } from '@/components/AlertComponent';
import { Download, FileText, Plus, Search, Calendar as CalendarIcon, ChevronLeft } from 'lucide-react-native';
import React, { useCallback, useEffect, useState } from 'react';
import { RangePickerModal } from '@/components/RangePickerModal';
import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { ConstructionSite, DocumentItem } from '@/types/types';
import LoadingScreen from '@/components/LoadingScreen'; import LoadingScreen from '@/components/LoadingScreen';
import { RangePickerModal } from '@/components/RangePickerModal';
import { ConstructionSite, DocumentItem } from '@/types/types';
import api from '@/utils/api'; import api from '@/utils/api';
import dayjs from 'dayjs';
import { formatTimestamp, parseTimestamp } from '@/utils/dateTime'; import { formatTimestamp, parseTimestamp } from '@/utils/dateTime';
import { downloadAndShareDocument, uploadDocument } from '@/utils/documentUtils'; import { downloadAndShareDocument, uploadDocument } from '@/utils/documentUtils';
import AddDocumentModal from '@/components/AddDocumentModal'; import dayjs from 'dayjs';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Calendar as CalendarIcon, ChevronLeft, Download, FileText, Plus, Search } from 'lucide-react-native';
import React, { useCallback, useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
export default function SiteDocumentsScreen() { export default function SiteDocumentsScreen() {
@@ -132,7 +132,7 @@ export default function SiteDocumentsScreen() {
<View className="flex-1 bg-gray-50"> <View className="flex-1 bg-gray-50">
{/* Header */} {/* Header */}
<View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100"> <View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
<View className='flex-row items-center gap-4'> <View className='flex-row items-center gap-4'>
<TouchableOpacity onPress={() => router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100"> <TouchableOpacity onPress={() => router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100">
<ChevronLeft size={28} color="#4b5563" /> <ChevronLeft size={28} color="#4b5563" />

View File

@@ -1,11 +1,11 @@
import { useAlert } from '@/components/AlertComponent';
import LoadingScreen from '@/components/LoadingScreen';
import { ConstructionSite } from '@/types/types';
import api from '@/utils/api';
import { useRouter } from 'expo-router';
import { Building2, ChevronRight, MapPin, Search } from 'lucide-react-native'; import { Building2, ChevronRight, MapPin, Search } from 'lucide-react-native';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { useRouter } from 'expo-router';
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'; import { SafeAreaView } from 'react-native-safe-area-context';
export default function SitesScreen() { export default function SitesScreen() {
@@ -56,7 +56,7 @@ export default function SitesScreen() {
<View className="flex-1 bg-gray-50"> <View className="flex-1 bg-gray-50">
{/* Header */} {/* Header */}
<View className="bg-white px-6 pb-6 shadow-sm border-b border-gray-100"> <View className="bg-white px-6 pb-6 shadow-sm border-b border-gray-100">
<SafeAreaView edges={['top']} className='pt-4'> <SafeAreaView edges={['top']} className='pt-5'>
<Text className="text-3xl font-bold text-gray-800 mb-1">Cantieri</Text> <Text className="text-3xl font-bold text-gray-800 mb-1">Cantieri</Text>
<Text className="text-base text-gray-500">Seleziona un cantiere per vedere i documenti</Text> <Text className="text-base text-gray-500">Seleziona un cantiere per vedere i documenti</Text>
</SafeAreaView> </SafeAreaView>

View File

@@ -4,20 +4,23 @@ import { Stack } from 'expo-router';
import { AlertProvider } from '@/components/AlertComponent'; import { AlertProvider } from '@/components/AlertComponent';
import { NetworkProvider } from '@/utils/networkProvider'; import { NetworkProvider } from '@/utils/networkProvider';
import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SafeAreaProvider } from 'react-native-safe-area-context';
import { KeyboardProvider } from "react-native-keyboard-controller";
export default function AppLayout() { export default function AppLayout() {
return ( return (
<SafeAreaProvider> <SafeAreaProvider>
<NetworkProvider> <KeyboardProvider>
<AuthProvider> <NetworkProvider>
<AlertProvider> <AuthProvider>
<Stack screenOptions={{ headerShown: false }}> <AlertProvider>
<Stack.Screen name="(protected)" /> <Stack screenOptions={{ headerShown: false, animation: 'flip' }}>
<Stack.Screen name="login" /> <Stack.Screen name="(protected)" />
</Stack> <Stack.Screen name="login" />
</AlertProvider> </Stack>
</AuthProvider> </AlertProvider>
</NetworkProvider> </AuthProvider>
</NetworkProvider>
</KeyboardProvider>
</SafeAreaProvider> </SafeAreaProvider>
); );
} }

View File

@@ -3,7 +3,8 @@ import api from '@/utils/api';
import { AuthContext } from '@/utils/authContext'; import { AuthContext } from '@/utils/authContext';
import { Eye, EyeOff, Lock, LogIn, Mail } from 'lucide-react-native'; import { Eye, EyeOff, Lock, LogIn, Mail } from 'lucide-react-native';
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { Image, KeyboardAvoidingView, Platform, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { Image, Platform, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
export default function LoginScreen() { export default function LoginScreen() {
const alert = useAlert(); const alert = useAlert();
@@ -74,8 +75,9 @@ export default function LoginScreen() {
{/* Form Container */} {/* Form Container */}
<View className="flex-1 bg-white rounded-t-[2.5rem] px-8 pt-10 shadow-xl w-full"> <View className="flex-1 bg-white rounded-t-[2.5rem] px-8 pt-10 shadow-xl w-full">
<KeyboardAvoidingView <KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} behavior={"padding"}
className="flex-1" keyboardVerticalOffset={100}
className='flex-1 mh-600'
> >
<ScrollView showsVerticalScrollIndicator={false} className="h-full"> <ScrollView showsVerticalScrollIndicator={false} className="h-full">
<View className="gap-6 flex flex-col" style={{ gap: '1.5rem' }}> <View className="gap-6 flex flex-col" style={{ gap: '1.5rem' }}>

View File

@@ -0,0 +1,26 @@
import React from 'react';
import DateTimePicker, { useDefaultStyles } from 'react-native-ui-datepicker';
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
type AppDatePickerProps = React.ComponentProps<typeof DateTimePicker>;
export const AppDatePicker = (props: AppDatePickerProps) => {
const defaultStyles = useDefaultStyles('light');
return (
<DateTimePicker
{...props}
locale="it"
components={{
IconPrev: <ChevronLeft size={24} color="#1f2937" />,
IconNext: <ChevronRight size={24} color="#1f2937" />,
...props.components,
}}
styles={{
...defaultStyles,
selected: { backgroundColor: '#099499' },
...props.styles,
}}
/>
);
};

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { Modal, Text, TouchableOpacity, View, Animated, Easing, Vibration, Alert } from 'react-native'; import { Modal, Text, TouchableOpacity, View, Animated, Easing, Vibration, Alert } from 'react-native';
import { X, Radio, SmartphoneNfc } from 'lucide-react-native'; import { X, Radio, SmartphoneNfc } from 'lucide-react-native';
import NfcManager, { NfcTech } from 'react-native-nfc-manager'; import NfcManager, { Ndef, NfcTech } from 'react-native-nfc-manager';
interface NfcScanModalProps { interface NfcScanModalProps {
visible: boolean; visible: boolean;
@@ -31,9 +31,16 @@ export default function NfcScanModal({ visible, onClose, onScan }: NfcScanModalP
const tag = await NfcManager.getTag(); const tag = await NfcManager.getTag();
if (tag) { if (tag) {
console.log('NFC Tag Found:', tag); const record = tag.ndefMessage?.[0];
const payloadBytes = new Uint8Array(record.payload);
const text = Ndef.text.decodePayload(payloadBytes);
console.log('NFC text:', text || 'No NDEF payload');
Vibration.vibrate(); Vibration.vibrate();
// onScan(tag.id || JSON.stringify(tag)); if (!text) {
Alert.alert('Errore', 'Tag NFC non contiene dati validi.');
throw new Error('Empty NFC tag');
}
onScan(text);
onClose(); onClose();
} else { } else {
console.warn('Tag NFC vuoto o non formattato NDEF'); console.warn('Tag NFC vuoto o non formattato NDEF');

View File

@@ -1,10 +1,10 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Alert, Modal, Text, TouchableOpacity, View } from 'react-native'; import { Modal, Text, TouchableOpacity, View } from 'react-native';
import DateTimePicker, { DateType, useDefaultStyles } from 'react-native-ui-datepicker'; import { DateType } from 'react-native-ui-datepicker';
import { Check, X } from 'lucide-react-native'; import { Check, X } from 'lucide-react-native';
import { AppDatePicker } from './AppDatePicker';
export const RangePickerModal = ({ visible, onClose, onApply }: any) => { export const RangePickerModal = ({ visible, onClose, onApply }: any) => {
const defaultStyles = useDefaultStyles('light');
const [range, setRange] = useState<{ const [range, setRange] = useState<{
startDate: DateType; startDate: DateType;
endDate: DateType; endDate: DateType;
@@ -32,17 +32,12 @@ export const RangePickerModal = ({ visible, onClose, onApply }: any) => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<DateTimePicker <AppDatePicker
mode="range" mode="range"
startDate={range.startDate} startDate={range.startDate}
endDate={range.endDate} endDate={range.endDate}
onChange={(params) => setRange(params)} onChange={(params) => setRange(params)}
timeZone='Europe/Rome' timeZone='Europe/Rome'
locale='it'
styles={{
...defaultStyles,
selected: { backgroundColor: '#099499' }
}}
/> />
<TouchableOpacity <TouchableOpacity

View File

@@ -1,12 +1,13 @@
import { useAlert } from '@/components/AlertComponent'; import { useAlert } from '@/components/AlertComponent';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View, Text, Modal, TouchableOpacity, TextInput, ScrollView } from 'react-native'; import { View, Text, Modal, TouchableOpacity, TextInput, ScrollView, Platform } from 'react-native';
import DateTimePicker, { DateType, useDefaultStyles } from 'react-native-ui-datepicker';
import { TimeOffRequestType } from '@/types/types'; import { TimeOffRequestType } from '@/types/types';
import { X } from 'lucide-react-native'; import { X } from 'lucide-react-native';
import { TimePickerModal } from './TimePickerModal'; import { TimePickerModal } from './TimePickerModal';
import api from '@/utils/api'; import api from '@/utils/api';
import { formatPickerDate } from '@/utils/dateTime'; import { formatPickerDate } from '@/utils/dateTime';
import { AppDatePicker } from '@/components/AppDatePicker';
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
interface RequestPermitModalProps { interface RequestPermitModalProps {
visible: boolean; visible: boolean;
@@ -16,7 +17,6 @@ interface RequestPermitModalProps {
} }
export default function RequestPermitModal({ visible, types, onClose, onSubmit }: RequestPermitModalProps) { export default function RequestPermitModal({ visible, types, onClose, onSubmit }: RequestPermitModalProps) {
const defaultStyles = useDefaultStyles('light');
const alert = useAlert(); const alert = useAlert();
const [type, setType] = useState<TimeOffRequestType>(types[0]); // Default to first type const [type, setType] = useState<TimeOffRequestType>(types[0]); // Default to first type
const [date, setDate] = useState<string | null>(); const [date, setDate] = useState<string | null>();
@@ -116,143 +116,142 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 40 }}> <KeyboardAvoidingView
<View className="space-y-6"> behavior={"padding"}
{/* Permit Type */} keyboardVerticalOffset={100}
<View className='mb-6'> className='flex-1 mh-600'
<Text className="text-lg font-bold text-gray-700 mb-3">Tipologia Assenza</Text> >
<ScrollView <ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 40 }}>
horizontal <View className="space-y-6">
showsHorizontalScrollIndicator={false} {/* Permit Type */}
contentContainerStyle={{ paddingHorizontal: 0, gap: 12 }} <View className='mb-6'>
> <Text className="text-lg font-bold text-gray-700 mb-3">Tipologia Assenza</Text>
{types.map((t) => ( <ScrollView
<TouchableOpacity horizontal
key={t.id} showsHorizontalScrollIndicator={false}
onPress={() => setType(t)} contentContainerStyle={{ paddingHorizontal: 0, gap: 12 }}
className={`py-4 px-5 rounded-xl border-2 items-center justify-center ${type?.id === t.id ? 'border-[#099499] bg-teal-50' : 'border-gray-100 bg-white' >
}`} {types.map((t) => (
> <TouchableOpacity
<Text className={`text-sm font-bold ${type?.id === t.id ? 'text-[#099499]' : 'text-gray-500'}`}> key={t.id}
{t.name} onPress={() => setType(t)}
</Text> className={`py-4 px-5 rounded-xl border-2 items-center justify-center ${type?.id === t.id ? 'border-[#099499] bg-teal-50' : 'border-gray-100 bg-white'
</TouchableOpacity> }`}
))} >
</ScrollView> <Text className={`text-sm font-bold ${type?.id === t.id ? 'text-[#099499]' : 'text-gray-500'}`}>
</View> {t.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* Date and Time Selection */} {/* Date and Time Selection */}
{type?.time_required === 0 ? ( {type?.time_required === 0 ? (
<DateTimePicker <AppDatePicker
mode="range" mode="range"
startDate={range.startDate} startDate={range.startDate}
endDate={range.endDate} endDate={range.endDate}
onChange={(params) => { onChange={(params) => {
setRange({ setRange({
startDate: params.startDate ? formatPickerDate(params.startDate) : null, startDate: params.startDate ? formatPickerDate(params.startDate) : null,
endDate: params.endDate ? formatPickerDate(params.endDate) : null endDate: params.endDate ? formatPickerDate(params.endDate) : null
}) })
}} }}
locale='it' />
styles={{ ) : (
...defaultStyles, <AppDatePicker
selected: { backgroundColor: '#099499' }, mode="single"
}} date={date}
/> onChange={({ date }) => setDate(date ? formatPickerDate(date) : null)}
) : ( />
<DateTimePicker
mode="single"
date={date}
onChange={({ date }) => setDate(date ? formatPickerDate(date) : null)}
locale='it'
styles={{
...defaultStyles,
selected: { backgroundColor: '#099499' }
}}
/>
)}
<View className='flex-column bg-orange-50 rounded-xl border border-orange-100 mb-6'>
{type?.time_required === 1 && (
<View>
<View className="flex-row gap-4 p-4">
<View className="flex-1">
<Text className="text-sm font-bold text-orange-800 mb-2 uppercase">Dalle Ore</Text>
<TouchableOpacity onPress={() => setShowStartPicker(true)}>
<TextInput
placeholder="09:00"
className="w-full p-3 bg-white rounded-lg border border-orange-200 font-bold text-gray-800 text-center"
value={startTime}
onChangeText={setStartTime}
editable={false}
/>
</TouchableOpacity>
</View>
<View className="flex-1">
<Text className="text-sm font-bold text-orange-800 mb-2 uppercase">Alle Ore</Text>
<TouchableOpacity onPress={() => setShowEndPicker(true)}>
<TextInput
placeholder="18:00"
className="w-full p-3 bg-white rounded-lg border border-orange-200 font-bold text-gray-800 text-center"
value={endTime}
onChangeText={setEndTime}
editable={false}
/>
</TouchableOpacity>
</View>
</View>
<TimePickerModal
visible={showStartPicker}
initialDate={new Date()}
title="Seleziona Ora Inizio"
onConfirm={(time) => setStartTime(time)}
onClose={() => setShowStartPicker(false)}
/>
<TimePickerModal
visible={showEndPicker}
initialDate={new Date()}
title="Seleziona Ora Fine"
onConfirm={(time) => setEndTime(time)}
onClose={() => setShowEndPicker(false)}
/>
</View>
)} )}
{/* Reason field */} <View className='flex-column bg-orange-50 rounded-xl border border-orange-100 mb-6'>
<View className="p-4"> {type?.time_required === 1 && (
<Text className="text-sm font-bold text-orange-800 mb-2 uppercase">Motivo</Text> <View>
<TextInput <View className="flex-row gap-4 p-4">
placeholder="Scrivi qui il motivo..." <View className="flex-1">
className="w-full px-3 py-4 bg-white font-bold text-gray-800 rounded-lg border border-orange-200 text-gray-800" <Text className="text-sm font-bold text-orange-800 mb-2 uppercase">Dalle Ore</Text>
textAlignVertical="top" <TouchableOpacity onPress={() => setShowStartPicker(true)}>
value={message} <TextInput
onChangeText={setMessage} placeholder="09:00"
/> placeholderTextColor="#9CA3AF"
className="w-full p-3 bg-white rounded-lg border border-orange-200 font-bold text-gray-800 text-center"
value={startTime}
onChangeText={setStartTime}
editable={false}
/>
</TouchableOpacity>
</View>
<View className="flex-1">
<Text className="text-sm font-bold text-orange-800 mb-2 uppercase">Alle Ore</Text>
<TouchableOpacity onPress={() => setShowEndPicker(true)}>
<TextInput
placeholder="18:00"
placeholderTextColor="#9CA3AF"
className="w-full p-3 bg-white rounded-lg border border-orange-200 font-bold text-gray-800 text-center"
value={endTime}
onChangeText={setEndTime}
editable={false}
/>
</TouchableOpacity>
</View>
</View>
<TimePickerModal
visible={showStartPicker}
initialDate={new Date()}
title="Seleziona Ora Inizio"
onConfirm={(time) => setStartTime(time)}
onClose={() => setShowStartPicker(false)}
/>
<TimePickerModal
visible={showEndPicker}
initialDate={new Date()}
title="Seleziona Ora Fine"
onConfirm={(time) => setEndTime(time)}
onClose={() => setShowEndPicker(false)}
/>
</View>
)}
{/* Reason field */}
<View className="p-4">
<Text className="text-sm font-bold text-orange-800 mb-2 uppercase">Motivo</Text>
<TextInput
placeholder="Scrivi qui il motivo..."
placeholderTextColor="#9CA3AF"
className="w-full px-3 py-4 bg-white font-bold text-gray-800 rounded-lg border border-orange-200 text-gray-800"
textAlignVertical="top"
value={message}
onChangeText={setMessage}
/>
</View>
</View>
{/* Actions */}
<View className="flex-row gap-4">
<TouchableOpacity
onPress={() => {
clearCalendar();
onClose();
}}
className="flex-1 py-4 bg-gray-200 rounded-2xl shadow-sm active:bg-gray-300"
>
<Text className="text-gray-700 text-center font-bold text-lg">Annulla Richiesta</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={handleSubmit}
className="flex-1 py-4 bg-[#099499] rounded-2xl shadow-lg active:scale-[0.98]"
>
<Text className="text-white text-center font-bold text-lg">Invia Richiesta</Text>
</TouchableOpacity>
</View> </View>
</View> </View>
</ScrollView>
{/* Actions */} </KeyboardAvoidingView>
<View className="flex-row gap-4">
<TouchableOpacity
onPress={() => {
clearCalendar();
onClose();
}}
className="flex-1 py-4 bg-gray-200 rounded-2xl shadow-sm active:bg-gray-300"
>
<Text className="text-gray-700 text-center font-bold text-lg">Annulla Richiesta</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={handleSubmit}
className="flex-1 py-4 bg-[#099499] rounded-2xl shadow-lg active:scale-[0.98]"
>
<Text className="text-white text-center font-bold text-lg">Invia Richiesta</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</View> </View>
</View> </View>
</Modal> </Modal>

21
eas.json Normal file
View File

@@ -0,0 +1,21 @@
{
"cli": {
"version": ">= 16.32.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

16
package-lock.json generated
View File

@@ -40,6 +40,7 @@
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-native": "0.81.5", "react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0", "react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "1.18.5",
"react-native-nfc-manager": "^3.17.2", "react-native-nfc-manager": "^3.17.2",
"react-native-reanimated": "~4.1.1", "react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0", "react-native-safe-area-context": "~5.6.0",
@@ -11530,6 +11531,20 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-keyboard-controller": {
"version": "1.18.5",
"resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.18.5.tgz",
"integrity": "sha512-wbYN6Tcu3G5a05dhRYBgjgd74KqoYWuUmroLpigRg9cXy5uYo7prTMIvMgvLtARQtUF7BOtFggUnzgoBOgk0TQ==",
"license": "MIT",
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-reanimated": ">=3.0.0"
}
},
"node_modules/react-native-nfc-manager": { "node_modules/react-native-nfc-manager": {
"version": "3.17.2", "version": "3.17.2",
"resolved": "https://registry.npmjs.org/react-native-nfc-manager/-/react-native-nfc-manager-3.17.2.tgz", "resolved": "https://registry.npmjs.org/react-native-nfc-manager/-/react-native-nfc-manager-3.17.2.tgz",
@@ -11674,7 +11689,6 @@
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz",
"integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-arrow-functions": "^7.0.0-0",
"@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0",

View File

@@ -43,6 +43,7 @@
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-native": "0.81.5", "react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0", "react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "1.18.5",
"react-native-nfc-manager": "^3.17.2", "react-native-nfc-manager": "^3.17.2",
"react-native-reanimated": "~4.1.1", "react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0", "react-native-safe-area-context": "~5.6.0",