Add swipe-to-delete functionality for requests and improve QR Code scanning
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
EXPO_PUBLIC_API_URL=[YOUR_API_URL]
|
EXPO_PUBLIC_API_URL=[YOUR_API_URL]
|
||||||
EXPO_PUBLIC_HA_API_URL=[YOUR_HOME_ASSISTANT_API_URL]
|
EXPO_PUBLIC_HA_API_URL=[YOUR_HOME_ASSISTANT_API_URL]
|
||||||
EXPO_PUBLIC_HA_TOKEN=[YOUR_HOME_ASSISTANT_TOKEN]
|
EXPO_PUBLIC_HA_TOKEN=[YOUR_HOME_ASSISTANT_TOKEN]
|
||||||
|
EXPO_PUBLIC_ENABLE_NFC=[true|false]
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ export default function AttendanceScreen() {
|
|||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
const checkNfcAvailability = async () => {
|
const checkNfcAvailability = async () => {
|
||||||
// TODO: add env variable to disable NFC checks in development or if not needed
|
if (process.env.EXPO_PUBLIC_ENABLE_NFC!=='true') return;
|
||||||
// if (!ENABLE_NFC) return;
|
|
||||||
try {
|
try {
|
||||||
const isSupported = await NfcManager.isSupported();
|
const isSupported = await NfcManager.isSupported();
|
||||||
if (isSupported) setScannerType('nfc');
|
if (isSupported) setScannerType('nfc');
|
||||||
@@ -252,4 +251,4 @@ export default function AttendanceScreen() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import RequestPermitModal from '@/components/RequestPermitModal';
|
|||||||
import { TimeOffRequest, TimeOffRequestType } from '@/types/types';
|
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 { Calendar as CalendarIcon, CalendarX, Clock, Plus, Thermometer, Trash2 } from 'lucide-react-native';
|
||||||
import React, { JSX, useEffect, useMemo, useState } from 'react';
|
import React, { JSX, useEffect, useMemo, 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';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
|
||||||
|
|
||||||
// Icon Mapping
|
// Icon Mapping
|
||||||
const typeIcons: Record<string, (color: string) => JSX.Element> = {
|
const typeIcons: Record<string, (color: string) => JSX.Element> = {
|
||||||
@@ -78,6 +79,73 @@ export default function PermitsScreen() {
|
|||||||
fetchPermits();
|
fetchPermits();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Funzione per eliminare una richiesta
|
||||||
|
const deletePermitRequest = async (id: number, itemRef?: React.ElementRef<typeof Swipeable> | null) => {
|
||||||
|
try {
|
||||||
|
itemRef?.close();
|
||||||
|
await api.post(`/time-off-request/delete-request`, {id: id});
|
||||||
|
// Optimistic update
|
||||||
|
setPermits(prevPermits => prevPermits.filter(p => p.id !== id));
|
||||||
|
alert.showAlert('success', 'Richiesta eliminata', 'La richiesta è stata eliminata con successo.');
|
||||||
|
// Refresh
|
||||||
|
fetchPermits();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Errore eliminazione richiesta:', error);
|
||||||
|
|
||||||
|
const errorMessage = error?.response?.data?.message || 'Impossibile eliminare la richiesta.';
|
||||||
|
alert.showAlert('error', 'Errore', errorMessage);
|
||||||
|
|
||||||
|
fetchPermits(); // Ripristina stato corretto
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dialogo di conferma
|
||||||
|
const confirmDelete = (item: TimeOffRequest, itemRef?: React.ElementRef<typeof Swipeable> | null) => {
|
||||||
|
const requestType = item.timeOffRequestType.name;
|
||||||
|
const dateRange = item.end_date
|
||||||
|
? `${formatDate(item.start_date?.toLocaleString())} - ${formatDate(item.end_date.toLocaleString())}`
|
||||||
|
: formatDate(item.start_date?.toLocaleString());
|
||||||
|
|
||||||
|
alert.showConfirm(
|
||||||
|
'Conferma eliminazione',
|
||||||
|
`Sei sicuro di voler eliminare questa richiesta?\n\n${requestType}\n${dateRange}`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Annulla',
|
||||||
|
style: 'cancel',
|
||||||
|
onPress: () => itemRef?.close()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Elimina',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => deletePermitRequest(item.id, itemRef)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renderizza pulsante DELETE al swipe
|
||||||
|
const renderRightActions = (
|
||||||
|
progress: any,
|
||||||
|
dragX: any,
|
||||||
|
item: TimeOffRequest,
|
||||||
|
swipeableRef: React.RefObject<React.ElementRef<typeof Swipeable> | null>
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => confirmDelete(item, swipeableRef.current)}
|
||||||
|
className="bg-red-500 justify-center items-center px-6 rounded-3xl ml-3"
|
||||||
|
activeOpacity={0.7}
|
||||||
|
style={{margin: 2}}
|
||||||
|
>
|
||||||
|
<View className="items-center gap-1">
|
||||||
|
<Trash2 size={24} color="white" strokeWidth={2.5} pointerEvents="none" />
|
||||||
|
<Text className="text-white font-bold text-sm">Elimina</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading && !refreshing) {
|
if (isLoading && !refreshing) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
@@ -119,32 +187,57 @@ export default function PermitsScreen() {
|
|||||||
) : (
|
) : (
|
||||||
<View className="gap-4">
|
<View className="gap-4">
|
||||||
<Text className="text-xl font-bold text-gray-800 px-1">Le tue richieste</Text>
|
<Text className="text-xl font-bold text-gray-800 px-1">Le tue richieste</Text>
|
||||||
{filteredPermits.map((item) => (
|
{filteredPermits.map((item) => {
|
||||||
<View key={item.id} className="bg-white p-5 rounded-3xl shadow-sm border border-gray-100 flex-row justify-between items-center">
|
const swipeableRef = React.createRef<React.ElementRef<typeof Swipeable>>();
|
||||||
<View className="flex-row items-center gap-4">
|
const canDelete = item.status === null; // Solo "In Attesa"
|
||||||
<View className={`p-4 rounded-2xl`} style={{ backgroundColor: item.timeOffRequestType.color ? `${item.timeOffRequestType.color}25` : '#E5E7EB' }}>
|
const cardContent = (
|
||||||
{typeIcons[item.timeOffRequestType.name]?.(item.timeOffRequestType.color)}
|
<View className="bg-white p-5 rounded-3xl shadow-sm border border-gray-100 flex-row justify-between items-center">
|
||||||
</View>
|
<View className="flex-row items-center gap-4">
|
||||||
<View>
|
<View className={`p-4 rounded-2xl`} style={{ backgroundColor: item.timeOffRequestType.color ? `${item.timeOffRequestType.color}25` : '#E5E7EB' }}>
|
||||||
<Text className="font-bold text-gray-800 text-lg">{item.timeOffRequestType.name}</Text>
|
{typeIcons[item.timeOffRequestType.name]?.(item.timeOffRequestType.color)}
|
||||||
<Text className="text-base text-gray-500 mt-0.5">
|
</View>
|
||||||
{formatDate(item.start_date?.toLocaleString())} {item.end_date ? `- ${formatDate(item.end_date.toLocaleString())}` : ''}
|
<View>
|
||||||
</Text>
|
<Text className="font-bold text-gray-800 text-lg">{item.timeOffRequestType.name}</Text>
|
||||||
{item.timeOffRequestType.name === 'Permesso' && (
|
<Text className="text-base text-gray-500 mt-0.5">
|
||||||
<Text className="text-sm text-orange-600 font-bold mt-1">
|
{formatDate(item.start_date?.toLocaleString())} {item.end_date ? `- ${formatDate(item.end_date.toLocaleString())}` : ''}
|
||||||
{formatTime(item.start_time)} - {formatTime(item.end_time)}
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
{item.timeOffRequestType.name === 'Permesso' && (
|
||||||
|
<Text className="text-sm text-orange-600 font-bold mt-1">
|
||||||
|
{formatTime(item.start_time)} - {formatTime(item.end_time)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className={`px-3 py-1.5 rounded-lg ${item.status===1 ? 'bg-green-100' : item.status===0 ? 'bg-red-100' : 'bg-yellow-100'}`}>
|
||||||
|
<Text className={`text-xs font-bold uppercase tracking-wide ${item.status===1 ? 'text-green-700' : item.status===0 ? 'text-red-700' : 'text-yellow-700'}`}>
|
||||||
|
{item.status===1 ? 'Approvata' : item.status===0 ? 'Rifiutata' : 'In Attesa'}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{/* TODO: Add functionality to edit/remove the request */}
|
);
|
||||||
<View className={`px-3 py-1.5 rounded-lg ${item.status ? 'bg-green-100' : 'bg-yellow-100'}`}>
|
|
||||||
<Text className={`text-xs font-bold uppercase tracking-wide ${item.status ? 'text-green-700' : 'text-yellow-700'}`}>
|
// Wrappa solo richieste "In Attesa" con Swipeable
|
||||||
{item.status ? 'Approvato' : 'In Attesa'}
|
if (canDelete) {
|
||||||
</Text>
|
return (
|
||||||
</View>
|
<Swipeable
|
||||||
</View>
|
key={item.id}
|
||||||
))}
|
ref={swipeableRef}
|
||||||
|
renderRightActions={(progress, dragX) =>
|
||||||
|
renderRightActions(progress, dragX, item, swipeableRef)
|
||||||
|
}
|
||||||
|
rightThreshold={40}
|
||||||
|
friction={2}
|
||||||
|
overshootFriction={8}
|
||||||
|
containerStyle={{padding: 2}}
|
||||||
|
>
|
||||||
|
{cardContent}
|
||||||
|
</Swipeable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Richieste approvate senza swipe
|
||||||
|
return <View key={item.id}>{cardContent}</View>;
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -159,4 +252,4 @@ export default function PermitsScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,25 @@ 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";
|
import { KeyboardProvider } from "react-native-keyboard-controller";
|
||||||
|
import {GestureHandlerRootView} from "react-native-gesture-handler";
|
||||||
|
|
||||||
export default function AppLayout() {
|
export default function AppLayout() {
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<KeyboardProvider>
|
<GestureHandlerRootView>
|
||||||
<NetworkProvider>
|
<KeyboardProvider>
|
||||||
<AuthProvider>
|
<NetworkProvider>
|
||||||
<AlertProvider>
|
<AuthProvider>
|
||||||
<Stack screenOptions={{ headerShown: false, animation: 'flip' }}>
|
<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>
|
||||||
</KeyboardProvider>
|
</NetworkProvider>
|
||||||
|
</KeyboardProvider>
|
||||||
|
</GestureHandlerRootView>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,21 @@ import { CheckCircle, XCircle, Info, AlertTriangle } from 'lucide-react-native';
|
|||||||
|
|
||||||
type AlertType = 'success' | 'error' | 'info' | 'warning';
|
type AlertType = 'success' | 'error' | 'info' | 'warning';
|
||||||
|
|
||||||
|
type ConfirmButtonStyle = 'default' | 'destructive' | 'cancel';
|
||||||
|
|
||||||
|
interface ConfirmButton {
|
||||||
|
text: string;
|
||||||
|
onPress: () => void;
|
||||||
|
style?: ConfirmButtonStyle;
|
||||||
|
}
|
||||||
|
|
||||||
interface AlertContextData {
|
interface AlertContextData {
|
||||||
showAlert: (type: AlertType, title: string, message: string) => void;
|
showAlert: (type: AlertType, title: string, message: string) => void;
|
||||||
|
showConfirm: (
|
||||||
|
title: string,
|
||||||
|
message: string,
|
||||||
|
buttons: [ConfirmButton, ConfirmButton]
|
||||||
|
) => void;
|
||||||
hideAlert: () => void;
|
hideAlert: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +57,29 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [type, setType] = useState<AlertType>('info');
|
const [type, setType] = useState<AlertType>('info');
|
||||||
|
const [isConfirmMode, setIsConfirmMode] = useState(false);
|
||||||
|
const [confirmButtons, setConfirmButtons] = useState<[ConfirmButton, ConfirmButton]>([
|
||||||
|
{ text: 'Annulla', onPress: () => {}, style: 'cancel' },
|
||||||
|
{ text: 'Conferma', onPress: () => {}, style: 'default' }
|
||||||
|
]);
|
||||||
|
|
||||||
const showAlert = (newType: AlertType, newTitle: string, newMessage: string) => {
|
const showAlert = (newType: AlertType, newTitle: string, newMessage: string) => {
|
||||||
setType(newType);
|
setType(newType);
|
||||||
setTitle(newTitle);
|
setTitle(newTitle);
|
||||||
setMessage(newMessage);
|
setMessage(newMessage);
|
||||||
|
setIsConfirmMode(false);
|
||||||
|
setVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showConfirm = (
|
||||||
|
newTitle: string,
|
||||||
|
newMessage: string,
|
||||||
|
buttons: [ConfirmButton, ConfirmButton]
|
||||||
|
) => {
|
||||||
|
setTitle(newTitle);
|
||||||
|
setMessage(newMessage);
|
||||||
|
setConfirmButtons(buttons);
|
||||||
|
setIsConfirmMode(true);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,7 +91,7 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
// TODO: Need to refactor component styles
|
// TODO: Need to refactor component styles
|
||||||
return (
|
return (
|
||||||
<AlertContext.Provider value={{ showAlert, hideAlert }}>
|
<AlertContext.Provider value={{ showAlert, showConfirm, hideAlert }}>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@@ -78,31 +109,67 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
{/* Alert Container */}
|
{/* Alert Container */}
|
||||||
<TouchableWithoutFeedback>
|
<TouchableWithoutFeedback>
|
||||||
<View className="bg-white w-full max-w-sm rounded-3xl p-6 items-center shadow-2xl">
|
<View className="bg-white w-full max-w-sm rounded-3xl p-6 items-center shadow-2xl">
|
||||||
|
|
||||||
{/* Icon Circle */}
|
{/* Icon Circle - Solo per alert normali */}
|
||||||
<View className={`${bgColor} p-4 rounded-full mb-4`}>
|
{!isConfirmMode && (
|
||||||
<Icon size={32} className={color} strokeWidth={2.5} pointerEvents="none" />
|
<View className={`${bgColor} p-4 rounded-full mb-4`}>
|
||||||
</View>
|
<Icon size={32} className={color} strokeWidth={2.5} pointerEvents="none" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Texts */}
|
{/* Texts */}
|
||||||
<Text className="text-xl font-bold text-gray-900 text-center mb-2">
|
<Text className="text-xl font-bold text-gray-900 text-center mb-2">
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-lg text-gray-500 text-center leading-relaxed mb-8">
|
<Text className="text-lg text-gray-500 text-center leading-relaxed mb-8">
|
||||||
{message}
|
{message}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* OK Button */}
|
{/* Buttons - Condizionale */}
|
||||||
<TouchableOpacity
|
{isConfirmMode ? (
|
||||||
onPress={hideAlert}
|
// Conferma: 2 bottoni orizzontali
|
||||||
className={`w-full py-3.5 rounded-3xl ${btnColor} active:opacity-90 shadow-sm`}
|
<View className="flex-row gap-3 w-full">
|
||||||
>
|
{confirmButtons.map((button, index) => {
|
||||||
<Text className="text-white text-center font-bold text-lg">
|
const isDestructive = button.style === 'destructive';
|
||||||
Ok, ho capito
|
const isCancel = button.style === 'cancel';
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={index}
|
||||||
|
onPress={() => {
|
||||||
|
hideAlert();
|
||||||
|
button.onPress();
|
||||||
|
}}
|
||||||
|
className={`flex-1 py-3.5 rounded-3xl ${
|
||||||
|
isDestructive
|
||||||
|
? 'bg-red-600'
|
||||||
|
: isCancel
|
||||||
|
? 'bg-gray-200'
|
||||||
|
: 'bg-[#099499]'
|
||||||
|
} active:opacity-90 shadow-sm`}
|
||||||
|
>
|
||||||
|
<Text className={`text-center font-bold text-lg ${
|
||||||
|
isCancel ? 'text-gray-700' : 'text-white'
|
||||||
|
}`}>
|
||||||
|
{button.text}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
// Alert normale: singolo bottone OK
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={hideAlert}
|
||||||
|
className={`w-full py-3.5 rounded-3xl ${btnColor} active:opacity-90 shadow-sm`}
|
||||||
|
>
|
||||||
|
<Text className="text-white text-center font-bold text-lg">
|
||||||
|
Ok, ho capito
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { View, Text, Modal, TouchableOpacity, Vibration, StyleSheet, Dimensions } from 'react-native';
|
import { View, Text, Modal, TouchableOpacity, Vibration, StyleSheet, Dimensions } from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { CameraView, useCameraPermissions } from 'expo-camera';
|
import { CameraView, useCameraPermissions } from 'expo-camera';
|
||||||
@@ -13,6 +13,7 @@ interface QrScanModalProps {
|
|||||||
export default function QrScanModal({ visible, onClose, onScan }: QrScanModalProps) {
|
export default function QrScanModal({ visible, onClose, onScan }: QrScanModalProps) {
|
||||||
const [permission, requestPermission] = useCameraPermissions();
|
const [permission, requestPermission] = useCameraPermissions();
|
||||||
const [scanned, setScanned] = useState(false);
|
const [scanned, setScanned] = useState(false);
|
||||||
|
const scanInProgress = useRef(false);
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
const squareSize = Math.min(width * 0.8, height * 0.8, 400);
|
const squareSize = Math.min(width * 0.8, height * 0.8, 400);
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
setScanned(false);
|
setScanned(false);
|
||||||
|
scanInProgress.current = false;
|
||||||
if (permission && !permission.granted && permission.canAskAgain) {
|
if (permission && !permission.granted && permission.canAskAgain) {
|
||||||
requestPermission();
|
requestPermission();
|
||||||
}
|
}
|
||||||
@@ -27,7 +29,9 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
|
|||||||
}, [visible, permission]);
|
}, [visible, permission]);
|
||||||
|
|
||||||
const handleBarCodeScanned = ({ type, data }: { type: string; data: string }) => {
|
const handleBarCodeScanned = ({ type, data }: { type: string; data: string }) => {
|
||||||
if (scanned) return;
|
if (scanInProgress.current) return;
|
||||||
|
scanInProgress.current = true;
|
||||||
|
|
||||||
setScanned(true);
|
setScanned(true);
|
||||||
Vibration.vibrate();
|
Vibration.vibrate();
|
||||||
console.log(`Bar code with type ${type} and data ${data} has been scanned!`);
|
console.log(`Bar code with type ${type} and data ${data} has been scanned!`);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 { AppDatePicker } from '@/components/AppDatePicker';
|
||||||
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller';
|
||||||
|
|
||||||
interface RequestPermitModalProps {
|
interface RequestPermitModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@@ -116,13 +116,19 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<KeyboardAvoidingView
|
<KeyboardAwareScrollView
|
||||||
behavior={"padding"}
|
bottomOffset={Platform.OS === 'ios' ? 50 : 80}
|
||||||
keyboardVerticalOffset={100}
|
disableScrollOnKeyboardHide={false}
|
||||||
className='flex-1 mh-600'
|
enabled={true}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{
|
||||||
|
paddingBottom: 70,
|
||||||
|
flexGrow: 1
|
||||||
|
}}
|
||||||
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 40 }}>
|
<View className="space-y-6">
|
||||||
<View className="space-y-6">
|
|
||||||
{/* Permit Type */}
|
{/* Permit Type */}
|
||||||
<View className='mb-6'>
|
<View className='mb-6'>
|
||||||
<Text className="text-lg font-bold text-gray-700 mb-3">Tipologia Assenza</Text>
|
<Text className="text-lg font-bold text-gray-700 mb-3">Tipologia Assenza</Text>
|
||||||
@@ -181,6 +187,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
|||||||
value={startTime}
|
value={startTime}
|
||||||
onChangeText={setStartTime}
|
onChangeText={setStartTime}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -194,6 +201,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
|||||||
value={endTime}
|
value={endTime}
|
||||||
onChangeText={setEndTime}
|
onChangeText={setEndTime}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -222,10 +230,12 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
|||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Scrivi qui il motivo..."
|
placeholder="Scrivi qui il motivo..."
|
||||||
placeholderTextColor="#9CA3AF"
|
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"
|
className="w-full px-3 py-3 bg-white font-bold text-gray-800 rounded-lg border border-orange-200"
|
||||||
textAlignVertical="top"
|
textAlignVertical="top"
|
||||||
value={message}
|
value={message}
|
||||||
onChangeText={setMessage}
|
onChangeText={setMessage}
|
||||||
|
multiline
|
||||||
|
numberOfLines={3}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -249,11 +259,10 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
|||||||
<Text className="text-white text-center font-bold text-lg">Invia Richiesta</Text>
|
<Text className="text-white text-center font-bold text-lg">Invia Richiesta</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</KeyboardAwareScrollView>
|
||||||
</KeyboardAvoidingView>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user