Add swipe-to-delete functionality for requests and improve QR Code scanning
This commit is contained in:
@@ -4,8 +4,21 @@ import { CheckCircle, XCircle, Info, AlertTriangle } from 'lucide-react-native';
|
||||
|
||||
type AlertType = 'success' | 'error' | 'info' | 'warning';
|
||||
|
||||
type ConfirmButtonStyle = 'default' | 'destructive' | 'cancel';
|
||||
|
||||
interface ConfirmButton {
|
||||
text: string;
|
||||
onPress: () => void;
|
||||
style?: ConfirmButtonStyle;
|
||||
}
|
||||
|
||||
interface AlertContextData {
|
||||
showAlert: (type: AlertType, title: string, message: string) => void;
|
||||
showConfirm: (
|
||||
title: string,
|
||||
message: string,
|
||||
buttons: [ConfirmButton, ConfirmButton]
|
||||
) => void;
|
||||
hideAlert: () => void;
|
||||
}
|
||||
|
||||
@@ -44,11 +57,29 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [title, setTitle] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
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) => {
|
||||
setType(newType);
|
||||
setTitle(newTitle);
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -60,7 +91,7 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
// TODO: Need to refactor component styles
|
||||
return (
|
||||
<AlertContext.Provider value={{ showAlert, hideAlert }}>
|
||||
<AlertContext.Provider value={{ showAlert, showConfirm, hideAlert }}>
|
||||
{children}
|
||||
|
||||
<Modal
|
||||
@@ -78,31 +109,67 @@ export const AlertProvider = ({ children }: { children: ReactNode }) => {
|
||||
{/* Alert Container */}
|
||||
<TouchableWithoutFeedback>
|
||||
<View className="bg-white w-full max-w-sm rounded-3xl p-6 items-center shadow-2xl">
|
||||
|
||||
{/* Icon Circle */}
|
||||
<View className={`${bgColor} p-4 rounded-full mb-4`}>
|
||||
<Icon size={32} className={color} strokeWidth={2.5} pointerEvents="none" />
|
||||
</View>
|
||||
|
||||
{/* Icon Circle - Solo per alert normali */}
|
||||
{!isConfirmMode && (
|
||||
<View className={`${bgColor} p-4 rounded-full mb-4`}>
|
||||
<Icon size={32} className={color} strokeWidth={2.5} pointerEvents="none" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Texts */}
|
||||
<Text className="text-xl font-bold text-gray-900 text-center mb-2">
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
|
||||
<Text className="text-lg text-gray-500 text-center leading-relaxed mb-8">
|
||||
{message}
|
||||
</Text>
|
||||
|
||||
{/* OK Button */}
|
||||
<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>
|
||||
|
||||
{/* Buttons - Condizionale */}
|
||||
{isConfirmMode ? (
|
||||
// Conferma: 2 bottoni orizzontali
|
||||
<View className="flex-row gap-3 w-full">
|
||||
{confirmButtons.map((button, index) => {
|
||||
const isDestructive = button.style === 'destructive';
|
||||
const isCancel = button.style === 'cancel';
|
||||
|
||||
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>
|
||||
</TouchableWithoutFeedback>
|
||||
</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 { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera';
|
||||
@@ -13,6 +13,7 @@ interface QrScanModalProps {
|
||||
export default function QrScanModal({ visible, onClose, onScan }: QrScanModalProps) {
|
||||
const [permission, requestPermission] = useCameraPermissions();
|
||||
const [scanned, setScanned] = useState(false);
|
||||
const scanInProgress = useRef(false);
|
||||
const { width, height } = Dimensions.get('window');
|
||||
const squareSize = Math.min(width * 0.8, height * 0.8, 400);
|
||||
|
||||
@@ -20,6 +21,7 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setScanned(false);
|
||||
scanInProgress.current = false;
|
||||
if (permission && !permission.granted && permission.canAskAgain) {
|
||||
requestPermission();
|
||||
}
|
||||
@@ -27,7 +29,9 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
|
||||
}, [visible, permission]);
|
||||
|
||||
const handleBarCodeScanned = ({ type, data }: { type: string; data: string }) => {
|
||||
if (scanned) return;
|
||||
if (scanInProgress.current) return;
|
||||
scanInProgress.current = true;
|
||||
|
||||
setScanned(true);
|
||||
Vibration.vibrate();
|
||||
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 { formatPickerDate } from '@/utils/dateTime';
|
||||
import { AppDatePicker } from '@/components/AppDatePicker';
|
||||
import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
|
||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller';
|
||||
|
||||
interface RequestPermitModalProps {
|
||||
visible: boolean;
|
||||
@@ -116,13 +116,19 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<KeyboardAvoidingView
|
||||
behavior={"padding"}
|
||||
keyboardVerticalOffset={100}
|
||||
className='flex-1 mh-600'
|
||||
<KeyboardAwareScrollView
|
||||
bottomOffset={Platform.OS === 'ios' ? 50 : 80}
|
||||
disableScrollOnKeyboardHide={false}
|
||||
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 */}
|
||||
<View className='mb-6'>
|
||||
<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}
|
||||
onChangeText={setStartTime}
|
||||
editable={false}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -194,6 +201,7 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
||||
value={endTime}
|
||||
onChangeText={setEndTime}
|
||||
editable={false}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -222,10 +230,12 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit }
|
||||
<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"
|
||||
className="w-full px-3 py-3 bg-white font-bold text-gray-800 rounded-lg border border-orange-200"
|
||||
textAlignVertical="top"
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
multiline
|
||||
numberOfLines={3}
|
||||
/>
|
||||
</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>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user