From 882cfc281d546e5b00081054c8425a2b39a37f4b Mon Sep 17 00:00:00 2001 From: leonardo Date: Fri, 6 Feb 2026 18:06:48 +0100 Subject: [PATCH] feat: update app configuration and enhance UI components --- app.json | 6 + app/(protected)/attendance/index.tsx | 14 +- app/(protected)/automation/[id].tsx | 21 +- app/(protected)/automation/index.tsx | 92 ++++----- app/(protected)/index.tsx | 14 +- app/(protected)/permits/index.tsx | 12 +- app/(protected)/profile/documents.tsx | 16 +- app/(protected)/profile/index.tsx | 6 +- app/(protected)/sites/[id].tsx | 18 +- app/(protected)/sites/index.tsx | 12 +- app/_layout.tsx | 23 ++- app/login.tsx | 12 +- components/AppDatePicker.tsx | 26 +++ components/NfcScanModal.tsx | 13 +- components/RangePickerModal.tsx | 13 +- components/RequestPermitModal.tsx | 269 +++++++++++++------------- eas.json | 21 ++ package-lock.json | 16 +- package.json | 1 + 19 files changed, 339 insertions(+), 266 deletions(-) create mode 100644 components/AppDatePicker.tsx create mode 100644 eas.json diff --git a/app.json b/app.json index 8bd8a15..8872672 100644 --- a/app.json +++ b/app.json @@ -58,6 +58,12 @@ "experiments": { "typedRoutes": true, "reactCompiler": true + }, + "extra": { + "router": {}, + "eas": { + "projectId": "51cde1ca-e1b5-46c6-b9b4-0f17bf95693c" + } } } } diff --git a/app/(protected)/attendance/index.tsx b/app/(protected)/attendance/index.tsx index 235da06..61d02b4 100644 --- a/app/(protected)/attendance/index.tsx +++ b/app/(protected)/attendance/index.tsx @@ -1,13 +1,13 @@ 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 NfcScanModal from '@/components/NfcScanModal'; +import QrScanModal from '@/components/QrScanModal'; +import { AttendanceRecord } from '@/types/types'; import api from '@/utils/api'; 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 { SafeAreaView } from 'react-native-safe-area-context'; @@ -120,7 +120,7 @@ export default function AttendanceScreen() { {/* Header */} - + Gestione Presenze Registra i tuoi movimenti diff --git a/app/(protected)/automation/[id].tsx b/app/(protected)/automation/[id].tsx index 6292d7c..5fcf963 100644 --- a/app/(protected)/automation/[id].tsx +++ b/app/(protected)/automation/[id].tsx @@ -6,6 +6,7 @@ import { useLocalSearchParams, useRouter } from 'expo-router'; import { ChevronLeft, ServerOff, WifiOff } from 'lucide-react-native'; import React, { useCallback, useEffect, useState } from 'react'; import { RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; export default function AutomationDetailScreen() { const router = useRouter(); @@ -95,14 +96,18 @@ export default function AutomationDetailScreen() { return ( - - - router.back()} className='rounded-full active:bg-gray-100'> - - - {area.name} - - + + + + + router.back()} className='rounded-full active:bg-gray-100'> + + + {area.name} + + + + ; } + // TODO: Remove duplicated code if (!connectionStatus.success) { return ( - - + + + + + Domotica + Controlla gli ambienti + + + + {connectionStatus.success ? 'ONLINE' : 'OFFLINE'} + + + + + + + } + > + + Errore di connessione + + {connectionStatus.message} + + + Riprova + + + + ); + } + + return ( + + + + Domotica Controlla gli ambienti @@ -98,51 +136,7 @@ export default function AutomationScreen() { ))} )} - - } - > - - Errore di connessione - - {connectionStatus.message} - - - Riprova - - - - ); - } - - return ( - - - - - Domotica - Controlla gli ambienti - - - - {connectionStatus.success ? 'ONLINE' : 'OFFLINE'} - - - - {connectionStatus.success && ( - - {floors.map(floor => ( - setSelectedFloor(floor)} - className={`px-5 py-2.5 rounded-xl ${selectedFloor === floor ? 'bg-[#099499]' : 'bg-gray-100 active:bg-gray-200'}`} - > - {floor} - - ))} - - )} + - + {/* Custom Banner */} diff --git a/app/(protected)/permits/index.tsx b/app/(protected)/permits/index.tsx index 201a789..a697f94 100644 --- a/app/(protected)/permits/index.tsx +++ b/app/(protected)/permits/index.tsx @@ -1,13 +1,13 @@ 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 LoadingScreen from '@/components/LoadingScreen'; +import RequestPermitModal from '@/components/RequestPermitModal'; +import { TimeOffRequest, TimeOffRequestType } from '@/types/types'; import api from '@/utils/api'; 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'; // Icon Mapping @@ -93,7 +93,7 @@ export default function PermitsScreen() { {/* Header */} - + Ferie e Permessi Gestisci le tue assenze diff --git a/app/(protected)/profile/documents.tsx b/app/(protected)/profile/documents.tsx index 42a0e0d..8b32c3c 100644 --- a/app/(protected)/profile/documents.tsx +++ b/app/(protected)/profile/documents.tsx @@ -1,16 +1,16 @@ +import AddDocumentModal from '@/components/AddDocumentModal'; import { useAlert } from '@/components/AlertComponent'; -import { Download, FileText, MapPin, Plus, Search, CalendarIcon, ChevronLeft } from 'lucide-react-native'; -import React, { useEffect, useState } from 'react'; -import { useRouter } from 'expo-router'; +import LoadingScreen from '@/components/LoadingScreen'; import { RangePickerModal } from '@/components/RangePickerModal'; -import { RefreshControl, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { DocumentItem } from '@/types/types'; import api from '@/utils/api'; -import dayjs from 'dayjs'; -import LoadingScreen from '@/components/LoadingScreen'; import { formatTimestamp, parseTimestamp } from '@/utils/dateTime'; -import AddDocumentModal from '@/components/AddDocumentModal'; 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'; export default function DocumentsScreen() { @@ -107,7 +107,7 @@ export default function DocumentsScreen() { {/* Header */} - + router.back()} className="p-2 rounded-full active:bg-gray-100"> diff --git a/app/(protected)/profile/index.tsx b/app/(protected)/profile/index.tsx index 5756967..159eab2 100644 --- a/app/(protected)/profile/index.tsx +++ b/app/(protected)/profile/index.tsx @@ -1,8 +1,8 @@ +import { AuthContext } from '@/utils/authContext'; 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 { ScrollView, Text, TouchableOpacity, View } from 'react-native'; -import { AuthContext } from '@/utils/authContext'; import { SafeAreaView } from 'react-native-safe-area-context'; export default function ProfileScreen() { @@ -15,7 +15,7 @@ export default function ProfileScreen() { return ( - + {/* Header Section */} diff --git a/app/(protected)/sites/[id].tsx b/app/(protected)/sites/[id].tsx index 5ba46d8..b78b0f3 100644 --- a/app/(protected)/sites/[id].tsx +++ b/app/(protected)/sites/[id].tsx @@ -1,16 +1,16 @@ +import AddDocumentModal from '@/components/AddDocumentModal'; 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 { RangePickerModal } from '@/components/RangePickerModal'; +import { ConstructionSite, DocumentItem } from '@/types/types'; import api from '@/utils/api'; -import dayjs from 'dayjs'; import { formatTimestamp, parseTimestamp } from '@/utils/dateTime'; 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'; export default function SiteDocumentsScreen() { @@ -132,7 +132,7 @@ export default function SiteDocumentsScreen() { {/* Header */} - + router.back()} className="p-2 -ml-2 rounded-full active:bg-gray-100"> diff --git a/app/(protected)/sites/index.tsx b/app/(protected)/sites/index.tsx index e32545a..d6ed88e 100644 --- a/app/(protected)/sites/index.tsx +++ b/app/(protected)/sites/index.tsx @@ -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 React, { useEffect, useState } from 'react'; 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'; export default function SitesScreen() { @@ -56,7 +56,7 @@ export default function SitesScreen() { {/* Header */} - + Cantieri Seleziona un cantiere per vedere i documenti diff --git a/app/_layout.tsx b/app/_layout.tsx index 8f02740..63140bc 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -4,20 +4,23 @@ import { Stack } from 'expo-router'; import { AlertProvider } from '@/components/AlertComponent'; import { NetworkProvider } from '@/utils/networkProvider'; import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { KeyboardProvider } from "react-native-keyboard-controller"; export default function AppLayout() { return ( - - - - - - - - - - + + + + + + + + + + + + ); } \ No newline at end of file diff --git a/app/login.tsx b/app/login.tsx index 79a2bb4..2cb5a08 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -3,7 +3,8 @@ import api from '@/utils/api'; import { AuthContext } from '@/utils/authContext'; import { Eye, EyeOff, Lock, LogIn, Mail } from 'lucide-react-native'; 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() { const alert = useAlert(); @@ -15,7 +16,7 @@ export default function LoginScreen() { // Login Handler function const handleLogin = async () => { - if (!username || !password) { + if (!username || !password) { alert.showAlert('error', 'Attenzione', 'Inserisci username e password'); return; } @@ -41,7 +42,7 @@ export default function LoginScreen() { } catch (error: any) { console.error("Login Error:", error); let message = "Si รจ verificato un errore durante l'accesso."; - + if (error.response) { // Server error (e.g., 401 Invalid credentials) if (error.response.status === 401) { @@ -74,8 +75,9 @@ export default function LoginScreen() { {/* Form Container */} diff --git a/components/AppDatePicker.tsx b/components/AppDatePicker.tsx new file mode 100644 index 0000000..658a726 --- /dev/null +++ b/components/AppDatePicker.tsx @@ -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; + +export const AppDatePicker = (props: AppDatePickerProps) => { + const defaultStyles = useDefaultStyles('light'); + + return ( + , + IconNext: , + ...props.components, + }} + styles={{ + ...defaultStyles, + selected: { backgroundColor: '#099499' }, + ...props.styles, + }} + /> + ); +}; \ No newline at end of file diff --git a/components/NfcScanModal.tsx b/components/NfcScanModal.tsx index 6fef9e1..2493644 100644 --- a/components/NfcScanModal.tsx +++ b/components/NfcScanModal.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef } from 'react'; import { Modal, Text, TouchableOpacity, View, Animated, Easing, Vibration, Alert } from '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 { visible: boolean; @@ -31,9 +31,16 @@ export default function NfcScanModal({ visible, onClose, onScan }: NfcScanModalP const tag = await NfcManager.getTag(); 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(); - // 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(); } else { console.warn('Tag NFC vuoto o non formattato NDEF'); diff --git a/components/RangePickerModal.tsx b/components/RangePickerModal.tsx index cc54be6..b5b0b9d 100644 --- a/components/RangePickerModal.tsx +++ b/components/RangePickerModal.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; -import { Alert, Modal, Text, TouchableOpacity, View } from 'react-native'; -import DateTimePicker, { DateType, useDefaultStyles } from 'react-native-ui-datepicker'; +import { Modal, Text, TouchableOpacity, View } from 'react-native'; +import { DateType } from 'react-native-ui-datepicker'; import { Check, X } from 'lucide-react-native'; +import { AppDatePicker } from './AppDatePicker'; export const RangePickerModal = ({ visible, onClose, onApply }: any) => { - const defaultStyles = useDefaultStyles('light'); const [range, setRange] = useState<{ startDate: DateType; endDate: DateType; @@ -32,17 +32,12 @@ export const RangePickerModal = ({ visible, onClose, onApply }: any) => { - setRange(params)} timeZone='Europe/Rome' - locale='it' - styles={{ - ...defaultStyles, - selected: { backgroundColor: '#099499' } - }} /> (types[0]); // Default to first type const [date, setDate] = useState(); @@ -116,143 +116,142 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } - - - {/* Permit Type */} - - Tipologia Assenza - - {types.map((t) => ( - setType(t)} - 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' - }`} - > - - {t.name} - - - ))} - - + + + + {/* Permit Type */} + + Tipologia Assenza + + {types.map((t) => ( + setType(t)} + 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' + }`} + > + + {t.name} + + + ))} + + - {/* Date and Time Selection */} - {type?.time_required === 0 ? ( - { - setRange({ - startDate: params.startDate ? formatPickerDate(params.startDate) : null, - endDate: params.endDate ? formatPickerDate(params.endDate) : null - }) - }} - locale='it' - styles={{ - ...defaultStyles, - selected: { backgroundColor: '#099499' }, - }} - /> - ) : ( - setDate(date ? formatPickerDate(date) : null)} - locale='it' - styles={{ - ...defaultStyles, - selected: { backgroundColor: '#099499' } - }} - /> - )} - - - {type?.time_required === 1 && ( - - - - Dalle Ore - setShowStartPicker(true)}> - - - - - Alle Ore - setShowEndPicker(true)}> - - - - - - setStartTime(time)} - onClose={() => setShowStartPicker(false)} - /> - setEndTime(time)} - onClose={() => setShowEndPicker(false)} - /> - + {/* Date and Time Selection */} + {type?.time_required === 0 ? ( + { + setRange({ + startDate: params.startDate ? formatPickerDate(params.startDate) : null, + endDate: params.endDate ? formatPickerDate(params.endDate) : null + }) + }} + /> + ) : ( + setDate(date ? formatPickerDate(date) : null)} + /> )} - {/* Reason field */} - - Motivo - + + {type?.time_required === 1 && ( + + + + Dalle Ore + setShowStartPicker(true)}> + + + + + Alle Ore + setShowEndPicker(true)}> + + + + + + setStartTime(time)} + onClose={() => setShowStartPicker(false)} + /> + setEndTime(time)} + onClose={() => setShowEndPicker(false)} + /> + + )} + + {/* Reason field */} + + Motivo + + + + + {/* Actions */} + + { + clearCalendar(); + onClose(); + }} + className="flex-1 py-4 bg-gray-200 rounded-2xl shadow-sm active:bg-gray-300" + > + Annulla Richiesta + + + + Invia Richiesta + - - {/* Actions */} - - { - clearCalendar(); - onClose(); - }} - className="flex-1 py-4 bg-gray-200 rounded-2xl shadow-sm active:bg-gray-300" - > - Annulla Richiesta - - - - Invia Richiesta - - - - + + diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..fead124 --- /dev/null +++ b/eas.json @@ -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": {} + } +} diff --git a/package-lock.json b/package-lock.json index 0f80606..eab68c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-controller": "1.18.5", "react-native-nfc-manager": "^3.17.2", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", @@ -11530,6 +11531,20 @@ "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": { "version": "3.17.2", "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", "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", diff --git a/package.json b/package.json index 50bc9f2..6245914 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-controller": "1.18.5", "react-native-nfc-manager": "^3.17.2", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0",