Initial commit
This commit is contained in:
10
app/(protected)/profile/_layout.tsx
Normal file
10
app/(protected)/profile/_layout.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function ProfileLayout() {
|
||||
return (
|
||||
<Stack screenOptions={{headerShown: false}}>
|
||||
<Stack.Screen name="index" />
|
||||
<Stack.Screen name="documents" options={{ animation: 'slide_from_right' }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
205
app/(protected)/profile/documents.tsx
Normal file
205
app/(protected)/profile/documents.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import { useAlert } from '@/components/AlertComponent';
|
||||
import LoadingScreen from '@/components/LoadingScreen';
|
||||
import api from '@/utils/api';
|
||||
import { downloadAndShareDocument } from '@/utils/documentUtils';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { ChevronDown, ChevronLeft, Download, FileText, X } from 'lucide-react-native';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, RefreshControl, FlatList, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
export default function DocumentsScreen() {
|
||||
const router = useRouter();
|
||||
const alert = useAlert();
|
||||
const [documents, setDocuments] = useState<any[]>([]);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
||||
// Fetch document categories
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response = await api.get('/registry/get-categories');
|
||||
if (response.data?.success) {
|
||||
setCategories([{ label: 'Tutte le tipologie', value: null }, ...response.data.categories]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Errore nel recupero delle categorie:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch user documents based on selected category
|
||||
const fetchUserDocuments = async (filterValue: any = null) => {
|
||||
try {
|
||||
if (!refreshing) setIsLoading(true);
|
||||
const params = { filter: filterValue };
|
||||
const response = await api.get(`/registry/list`, { params });
|
||||
if (response.data?.success) {
|
||||
setDocuments(response.data.result || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Errore nel recupero dei documenti utente:', error);
|
||||
alert.showAlert('error', 'Errore', 'Impossibile recuperare i documenti. Riprova più tardi.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
setIsLoading(true);
|
||||
await fetchCategories();
|
||||
await fetchUserDocuments(selectedCategory);
|
||||
};
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
fetchUserDocuments(selectedCategory);
|
||||
};
|
||||
|
||||
const handleCategorySelect = (value: any) => {
|
||||
setSelectedCategory(value);
|
||||
fetchUserDocuments(value);
|
||||
setShowPicker(false);
|
||||
};
|
||||
|
||||
if (isLoading && !refreshing) {
|
||||
return (
|
||||
<LoadingScreen />
|
||||
);
|
||||
}
|
||||
|
||||
// Get label for the selected category or default text
|
||||
const selectedLabel = selectedCategory
|
||||
? categories.find(c => c.value === selectedCategory)?.label
|
||||
: 'Filtra per tipologia...';
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<StatusBar style="dark" />
|
||||
{/* Header */}
|
||||
<View className="bg-white px-4 pb-6 shadow-sm border-b border-gray-100">
|
||||
<SafeAreaView edges={['top']} className='pt-5'>
|
||||
<View className='flex-row items-center gap-4 mb-4'>
|
||||
<TouchableOpacity onPress={() => router.back()} className="p-2 rounded-full active:bg-gray-100">
|
||||
<ChevronLeft size={28} color="#4b5563" pointerEvents="none" />
|
||||
</TouchableOpacity>
|
||||
<View className="flex-1">
|
||||
<Text className="text-3xl font-bold text-gray-800">Documenti</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Select / Dropdown Trigger and Reset */}
|
||||
<View className="flex-row items-center mx-1 gap-3">
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowPicker(true)}
|
||||
className="flex-1 flex-row items-center justify-between bg-white px-5 py-3 rounded-2xl border border-gray-200 shadow-sm"
|
||||
>
|
||||
<Text className="text-gray-700 font-medium text-base flex-1 mr-2" numberOfLines={1}>
|
||||
{selectedLabel}
|
||||
</Text>
|
||||
<ChevronDown size={20} color="#6b7280" pointerEvents="none" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{selectedCategory !== null && (
|
||||
<TouchableOpacity
|
||||
onPress={() => handleCategorySelect(null)}
|
||||
className="bg-gray-50 p-3.5 rounded-2xl border border-gray-200 shadow-sm justify-center items-center"
|
||||
>
|
||||
<X size={22} color="#4b5563" pointerEvents="none" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
|
||||
<View className="p-5 flex-1 pt-4">
|
||||
{/* Documents List */}
|
||||
<FlatList
|
||||
data={documents}
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
contentContainerStyle={{ gap: 16, paddingBottom: 100 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#1071C2']} />
|
||||
}
|
||||
initialNumToRender={10}
|
||||
maxToRenderPerBatch={15}
|
||||
windowSize={5}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={({ item: doc }) => (
|
||||
<View className="bg-white p-5 rounded-3xl shadow-sm flex-row items-center justify-between border border-gray-100">
|
||||
<View className="flex-row items-center gap-5 flex-1">
|
||||
<View className="bg-blue-50 p-4 rounded-2xl flex-shrink-0">
|
||||
<FileText size={32} color="#1071C2" pointerEvents="none" />
|
||||
</View>
|
||||
<View className="flex-1 mr-2">
|
||||
<Text className="font-bold text-gray-800 text-base leading-tight uppercase" numberOfLines={3}>{doc.filename}</Text>
|
||||
<View className="flex-row items-center mt-2">
|
||||
<Text className="text-sm text-gray-400 font-bold">{doc.date}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => downloadAndShareDocument(doc.mimetype, doc.filename, doc.url)}
|
||||
className="p-4 bg-gray-50 rounded-2xl active:bg-gray-100 flex-shrink-0 border border-gray-100">
|
||||
<Download size={24} color="#1071C2" pointerEvents="none" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
ListEmptyComponent={() => (
|
||||
!isLoading ? (
|
||||
<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">Nessun documento trovato in questa categoria</Text>
|
||||
</View>
|
||||
) : null
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Modal Picker (Dropdown Custom) */}
|
||||
<Modal visible={showPicker} transparent={true} animationType="fade" onRequestClose={() => setShowPicker(false)}>
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={() => setShowPicker(false)}
|
||||
className="flex-1 bg-black/50 justify-end"
|
||||
>
|
||||
<View className="bg-white rounded-t-3xl p-5 max-h-[70%]" onStartShouldSetResponder={() => true}>
|
||||
<View className="flex-row justify-between items-center mb-4 border-b border-gray-100 pb-4">
|
||||
<Text className="text-xl font-bold text-gray-800">Filtra per tipologia</Text>
|
||||
<TouchableOpacity onPress={() => setShowPicker(false)} className="p-2 bg-gray-100 rounded-full">
|
||||
<X size={20} color="#4b5563" pointerEvents="none" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<FlatList
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ paddingBottom: 30 }}
|
||||
data={categories}
|
||||
keyExtractor={(item, index) => index.toString()}
|
||||
initialNumToRender={10}
|
||||
maxToRenderPerBatch={15}
|
||||
windowSize={5}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={({ item: cat }) => (
|
||||
<TouchableOpacity
|
||||
className={`py-4 px-4 rounded-xl flex-row justify-between items-center mb-2 ${selectedCategory === cat.value ? 'bg-blue-50' : ''}`}
|
||||
onPress={() => handleCategorySelect(cat.value)}
|
||||
>
|
||||
<Text className={`text-lg ${selectedCategory === cat.value ? 'font-bold text-primary-dark' : 'text-gray-700'}`}>
|
||||
{cat.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
108
app/(protected)/profile/index.tsx
Normal file
108
app/(protected)/profile/index.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { AuthContext } from '@/utils/authContext';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { ChevronLeft, FileText, LogOut, Mail, User } from 'lucide-react-native';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import React, { useContext } from 'react';
|
||||
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const authContext = useContext(AuthContext);
|
||||
const { user } = authContext;
|
||||
const router = useRouter();
|
||||
|
||||
// Generate user initials
|
||||
const initials = `${user?.firstName?.[0] ?? ''}${user?.lastName?.[0] ?? ''}`.toUpperCase();
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-primary-dark">
|
||||
<StatusBar style="light" />
|
||||
<SafeAreaView edges={['top']} className='pt-5'>
|
||||
{/* Header Section */}
|
||||
<View className="pb-6 px-4">
|
||||
<View className="flex-row justify-start items-center gap-4">
|
||||
<TouchableOpacity
|
||||
onPress={() => router.back()}
|
||||
>
|
||||
<ChevronLeft size={28} color="white" pointerEvents="none"/>
|
||||
</TouchableOpacity>
|
||||
<View className="flex-row items-center gap-4">
|
||||
<View className="w-16 h-16 rounded-full bg-white/20 items-center justify-center">
|
||||
<Text className="text-white font-bold text-2xl">{initials}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-gray-300 text-lg font-medium uppercase tracking-wider mb-1">Profilo</Text>
|
||||
<Text className="text-white text-2xl font-bold">{user?.firstName} {user?.lastName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
<ScrollView
|
||||
className="flex-1 bg-gray-50 rounded-t-[2.5rem] px-5 pt-8"
|
||||
contentContainerStyle={{ paddingBottom: 60, gap: 24 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* Info Card - Enlarged Texts */}
|
||||
<View className="bg-white p-7 rounded-3xl shadow-sm border border-gray-100">
|
||||
{/* Section title */}
|
||||
<Text className="text-2xl font-bold text-gray-800">Informazioni</Text>
|
||||
|
||||
<View className="mt-6 gap-5">
|
||||
<View className="flex-row items-center gap-5">
|
||||
<View className="w-14 h-14 bg-blue-50 rounded-2xl items-center justify-center">
|
||||
<Mail size={24} color="#1071C2" pointerEvents="none" />
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-lg text-gray-700 font-bold">Email</Text>
|
||||
<Text className="text-gray-500 text-base">{user?.email}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="flex-row items-center gap-5">
|
||||
<View className="w-14 h-14 bg-blue-50 rounded-2xl items-center justify-center">
|
||||
<User size={24} color="#1071C2" pointerEvents="none" />
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-lg text-gray-700 font-bold">Ruolo</Text>
|
||||
<Text className="text-gray-500 text-base capitalize">{user?.isAdmin ? 'Amministratore' : 'Utente'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Actions */}
|
||||
<View>
|
||||
<Text className="text-gray-800 text-2xl font-bold mb-5 px-1">Azioni</Text>
|
||||
|
||||
<TouchableOpacity onPress={() => router.push('/profile/documents')} className="bg-white p-4 rounded-3xl shadow-sm flex-row items-center justify-between border border-gray-100 mb-4">
|
||||
<View className="flex-row items-center gap-5">
|
||||
<View className="bg-blue-50 p-3.5 rounded-2xl">
|
||||
<FileText size={26} color="#1071C2" pointerEvents="none" />
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-lg text-gray-800 font-bold">I miei documenti</Text>
|
||||
<Text className="text-base text-gray-400 mt-0.5">Visualizza i tuoi documenti</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text className="text-primary text-base font-bold">Apri</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity onPress={authContext.logOut} className="bg-white p-4 rounded-3xl shadow-sm flex-row items-center justify-between border border-gray-100">
|
||||
<View className="flex-row items-center gap-5">
|
||||
<View className="bg-red-50 p-3.5 rounded-2xl">
|
||||
<LogOut size={26} color="#ef4444" pointerEvents="none" />
|
||||
</View>
|
||||
<View>
|
||||
<Text className="text-lg text-gray-800 font-bold">Esci</Text>
|
||||
<Text className="text-base text-gray-400 mt-0.5">Chiudi la sessione corrente</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text className="text-red-500 text-base font-bold">Esci</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user