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

@@ -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 { 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');

View File

@@ -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) => {
</TouchableOpacity>
</View>
<DateTimePicker
<AppDatePicker
mode="range"
startDate={range.startDate}
endDate={range.endDate}
onChange={(params) => setRange(params)}
timeZone='Europe/Rome'
locale='it'
styles={{
...defaultStyles,
selected: { backgroundColor: '#099499' }
}}
/>
<TouchableOpacity

View File

@@ -1,12 +1,13 @@
import { useAlert } from '@/components/AlertComponent';
import React, { useState } from 'react';
import { View, Text, Modal, TouchableOpacity, TextInput, ScrollView } from 'react-native';
import DateTimePicker, { DateType, useDefaultStyles } from 'react-native-ui-datepicker';
import { View, Text, Modal, TouchableOpacity, TextInput, ScrollView, Platform } from 'react-native';
import { TimeOffRequestType } from '@/types/types';
import { X } from 'lucide-react-native';
import { TimePickerModal } from './TimePickerModal';
import api from '@/utils/api';
import { formatPickerDate } from '@/utils/dateTime';
import { AppDatePicker } from '@/components/AppDatePicker';
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
interface RequestPermitModalProps {
visible: boolean;
@@ -16,7 +17,6 @@ interface RequestPermitModalProps {
}
export default function RequestPermitModal({ visible, types, onClose, onSubmit }: RequestPermitModalProps) {
const defaultStyles = useDefaultStyles('light');
const alert = useAlert();
const [type, setType] = useState<TimeOffRequestType>(types[0]); // Default to first type
const [date, setDate] = useState<string | null>();
@@ -116,143 +116,142 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
</TouchableOpacity>
</View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 40 }}>
<View className="space-y-6">
{/* Permit Type */}
<View className='mb-6'>
<Text className="text-lg font-bold text-gray-700 mb-3">Tipologia Assenza</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ paddingHorizontal: 0, gap: 12 }}
>
{types.map((t) => (
<TouchableOpacity
key={t.id}
onPress={() => 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'
}`}
>
<Text className={`text-sm font-bold ${type?.id === t.id ? 'text-[#099499]' : 'text-gray-500'}`}>
{t.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
<KeyboardAvoidingView
behavior={"padding"}
keyboardVerticalOffset={100}
className='flex-1 mh-600'
>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 40 }}>
<View className="space-y-6">
{/* Permit Type */}
<View className='mb-6'>
<Text className="text-lg font-bold text-gray-700 mb-3">Tipologia Assenza</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ paddingHorizontal: 0, gap: 12 }}
>
{types.map((t) => (
<TouchableOpacity
key={t.id}
onPress={() => 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'
}`}
>
<Text className={`text-sm font-bold ${type?.id === t.id ? 'text-[#099499]' : 'text-gray-500'}`}>
{t.name}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* Date and Time Selection */}
{type?.time_required === 0 ? (
<DateTimePicker
mode="range"
startDate={range.startDate}
endDate={range.endDate}
onChange={(params) => {
setRange({
startDate: params.startDate ? formatPickerDate(params.startDate) : null,
endDate: params.endDate ? formatPickerDate(params.endDate) : null
})
}}
locale='it'
styles={{
...defaultStyles,
selected: { backgroundColor: '#099499' },
}}
/>
) : (
<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>
{/* Date and Time Selection */}
{type?.time_required === 0 ? (
<AppDatePicker
mode="range"
startDate={range.startDate}
endDate={range.endDate}
onChange={(params) => {
setRange({
startDate: params.startDate ? formatPickerDate(params.startDate) : null,
endDate: params.endDate ? formatPickerDate(params.endDate) : null
})
}}
/>
) : (
<AppDatePicker
mode="single"
date={date}
onChange={({ date }) => setDate(date ? formatPickerDate(date) : null)}
/>
)}
{/* 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..."
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 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"
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>
{/* 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>
</ScrollView>
</ScrollView>
</KeyboardAvoidingView>
</View>
</View>
</Modal>