Update tabBarIcon sizes, use FlatList for invoices and refactor QrScanModal layout

This commit is contained in:
2026-08-31 17:37:54 +02:00
parent ccbc8d62c5
commit 8ce26da0c5
4 changed files with 71 additions and 50 deletions
+7 -6
View File
@@ -43,35 +43,35 @@ export default function ProtectedLayout() {
name="index" name="index"
options={{ options={{
title: 'Home', title: 'Home',
tabBarIcon: ({ color, size }) => <Home pointerEvents="none" color={color} size={24} />, tabBarIcon: ({ color, size }) => <Home pointerEvents="none" color={color} size={28} />,
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="attendance/index" name="attendance/index"
options={{ options={{
title: 'Presenze', title: 'Presenze',
tabBarIcon: ({ color, size }) => <Clock pointerEvents="none" color={color} size={24} />, tabBarIcon: ({ color, size }) => <Clock pointerEvents="none" color={color} size={28} />,
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="journal" name="journal"
options={{ options={{
title: 'Giornale', title: 'Giornale',
tabBarIcon: ({ color, size }) => <ImageIcon pointerEvents="none" color={color} size={24} />, tabBarIcon: ({ color, size }) => <ImageIcon pointerEvents="none" color={color} size={28} />,
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="permits/index" name="permits/index"
options={{ options={{
title: 'Ferie', title: 'Ferie',
tabBarIcon: ({ color, size }) => <CalendarIcon pointerEvents="none" color={color} size={24} />, tabBarIcon: ({ color, size }) => <CalendarIcon pointerEvents="none" color={color} size={28} />,
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="invoice" name="invoice"
options={{ options={{
title: 'Fatture', title: 'Fatture',
tabBarIcon: ({ color, size }) => <FileText pointerEvents="none" color={color} size={24} />, tabBarIcon: ({ color, size }) => <FileText pointerEvents="none" color={color} size={28} />,
href: authState.user?.isAdmin ? undefined : null, href: authState.user?.isAdmin ? undefined : null,
}} }}
/> />
@@ -79,7 +79,7 @@ export default function ProtectedLayout() {
name="machine/index" name="machine/index"
options={{ options={{
title: 'Macchine', title: 'Macchine',
tabBarIcon: ({ color, size }) => <Car pointerEvents="none" color={color} size={24} />, tabBarIcon: ({ color, size }) => <Car pointerEvents="none" color={color} size={28} />,
href: !authState.user?.isAdmin ? undefined : null, href: !authState.user?.isAdmin ? undefined : null,
}} }}
/> />
@@ -88,6 +88,7 @@ export default function ProtectedLayout() {
options={{ options={{
href: null, href: null,
title: 'Profilo', title: 'Profilo',
tabBarStyle: { display: 'none' },
}} }}
/> />
</Tabs> </Tabs>
+13 -9
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { View, Text, ScrollView, TouchableOpacity, RefreshControl } from 'react-native'; import { View, Text, FlatList, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
@@ -112,22 +112,26 @@ export default function InvoiceScreen() {
<View className="flex-1 bg-gray-50 rounded-t-[2.5rem] overflow-hidden"> <View className="flex-1 bg-gray-50 rounded-t-[2.5rem] overflow-hidden">
{/* List */} {/* List */}
<ScrollView <FlatList
data={invoices}
keyExtractor={(item) => item.id.toString()}
className="flex-1" className="flex-1"
contentContainerStyle={{ padding: 20, paddingBottom: 100, gap: 16 }} contentContainerStyle={{ padding: 20, paddingBottom: 100, gap: 16 }}
showsVerticalScrollIndicator={true} showsVerticalScrollIndicator={true}
scrollIndicatorInsets={{ right: 1 }} scrollIndicatorInsets={{ right: 1 }}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#1071C2']} />} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#1071C2']} />}
> initialNumToRender={10}
{invoices.length === 0 ? ( maxToRenderPerBatch={10}
windowSize={5}
renderItem={({ item }) => (
<InvoiceCard item={item} onPress={handleInvoicePress} />
)}
ListEmptyComponent={
<View className="bg-white p-8 rounded-3xl border border-gray-100 items-center justify-center border-dashed mt-4"> <View className="bg-white p-8 rounded-3xl border border-gray-100 items-center justify-center border-dashed mt-4">
<Text className="text-gray-400 font-medium text-center">Nessuna fattura trovata</Text> <Text className="text-gray-400 font-medium text-center">Nessuna fattura trovata</Text>
</View> </View>
) : ( }
invoices.map((invoice) => ( />
<InvoiceCard key={invoice.id} item={invoice} onPress={handleInvoicePress} />
)))}
</ScrollView>
{/* FAB Filter */} {/* FAB Filter */}
<TouchableOpacity <TouchableOpacity
Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

After

Width:  |  Height:  |  Size: 121 KiB

+46 -30
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } 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, useWindowDimensions } 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';
import { X, ScanLine } from 'lucide-react-native'; import { X, ScanLine } from 'lucide-react-native';
@@ -14,18 +14,17 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
const [permission, requestPermission] = useCameraPermissions(); const [permission, requestPermission] = useCameraPermissions();
const [scanned, setScanned] = useState(false); const [scanned, setScanned] = useState(false);
const scanInProgress = useRef(false); const scanInProgress = useRef(false);
const { width, height } = Dimensions.get('window'); const { width, height } = useWindowDimensions();
const squareSize = Math.min(width * 0.8, height * 0.8, 400); const squareSize = Math.min(width * 0.8, height * 0.8, 400);
// Permission Handling and Reset Scanned State on Modal Open // Permission handling and scanned state reset on modal open
useEffect(() => { useEffect(() => {
if (visible) { if (!visible) return;
setScanned(false); setScanned(false);
scanInProgress.current = false; scanInProgress.current = false;
if (permission && !permission.granted && permission.canAskAgain) { if (permission && !permission.granted && permission.canAskAgain) {
requestPermission(); requestPermission();
} }
}
}, [visible, permission]); }, [visible, permission]);
const handleBarCodeScanned = ({ type, data }: { type: string; data: string }) => { const handleBarCodeScanned = ({ type, data }: { type: string; data: string }) => {
@@ -43,10 +42,6 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
return <View />; return <View />;
} }
if (!permission.granted && visible) {
requestPermission();
}
return ( return (
<Modal <Modal
visible={visible} visible={visible}
@@ -61,39 +56,60 @@ export default function QrScanModal({ visible, onClose, onScan }: QrScanModalPro
facing="back" facing="back"
onBarcodeScanned={scanned ? undefined : handleBarCodeScanned} onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
barcodeScannerSettings={{ barcodeScannerSettings={{
barcodeTypes: ["qr"], barcodeTypes: ['qr'],
}} }}
/> />
{/* Dark Overlay with Transparent "Hole" (Visually Simulated with Borders or Opacity) */} {/* Mask Overlay with transparent cutout */}
<SafeAreaView className="flex-1 justify-between bg-black/60 pt-8"> <View style={StyleSheet.absoluteFillObject} pointerEvents="none">
{/* Header Overlay */} <View className="flex-1 bg-black/60" />
<View className="items-center"> <View style={{ height: squareSize }} className="flex-row">
<Text className="text-white text-xl font-bold">Scansiona QR Code</Text> <View className="flex-1 bg-black/60" />
<Text className="text-gray-300 text-base mt-1">Inquadra il codice nel riquadro</Text>
</View>
{/* Central Area (Transparent for the camera) */} {/* Scanner Target Frame */}
<View className="items-center justify-center" style={{ height: squareSize }}> <View
<View style={{ width: squareSize, height: squareSize }} style={{ width: squareSize, height: squareSize }}
className="border-2 border-[#1071C2] bg-transparent relative justify-center items-center"> className="border-2 border-[#1071C2] justify-center items-center relative"
{/* Decorative Corners */} >
<View className="absolute top-0 left-0 w-6 h-6 border-l-4 border-t-4 border-[#1071C2]" /> <View className="absolute top-0 left-0 w-6 h-6 border-l-4 border-t-4 border-[#1071C2]" />
<View className="absolute top-0 right-0 w-6 h-6 border-r-4 border-t-4 border-[#1071C2]" /> <View className="absolute top-0 right-0 w-6 h-6 border-r-4 border-t-4 border-[#1071C2]" />
<View className="absolute bottom-0 left-0 w-6 h-6 border-l-4 border-b-4 border-[#1071C2]" /> <View className="absolute bottom-0 left-0 w-6 h-6 border-l-4 border-b-4 border-[#1071C2]" />
<View className="absolute bottom-0 right-0 w-6 h-6 border-r-4 border-b-4 border-[#1071C2]" /> <View className="absolute bottom-0 right-0 w-6 h-6 border-r-4 border-b-4 border-[#1071C2]" />
{/* Animated Scan Line or Icon */} {!scanned && <ScanLine color="#1071C2" size={40} />}
{!scanned && <ScanLine color="#1071C2" size={40} className="opacity-50" pointerEvents="none" />}
</View>
</View> </View>
{/* Footer Overlay */} <View className="flex-1 bg-black/60" />
<View className="items-center justify-end pb-12"> </View>
<TouchableOpacity onPress={onClose} className="bg-white/20 p-4 rounded-full"> <View className="flex-1 bg-black/60" />
<X color="white" size={32} pointerEvents="none" /> </View>
{/* UI Overlay */}
<SafeAreaView style={StyleSheet.absoluteFillObject} pointerEvents="box-none">
<View className="flex-1 justify-between pt-8">
{/* Header */}
<View className="items-center">
<Text className="text-white text-xl font-bold">
Scansiona QR Code
</Text>
<Text className="text-gray-300 text-base mt-1">
Inquadra il codice nel riquadro
</Text>
</View>
{/* Footer */}
<View className="items-center pb-12">
<TouchableOpacity
onPress={onClose}
className="bg-white/20 p-4 rounded-full"
>
<X color="white" size={32} />
</TouchableOpacity> </TouchableOpacity>
<Text className="text-white mt-4 font-medium">Chiudi</Text>
<Text className="text-white mt-4 font-medium">
Chiudi
</Text>
</View>
</View> </View>
</SafeAreaView> </SafeAreaView>
</View> </View>