Files
mariani_mobile/app/(protected)/permits/index.tsx
leonardo b9807f6cc2 - Refactor Profile and Login screens to use AuthContext for user data
- Enhance RequestPermitModal with multiple time-off types and validation
- Implement CalendarWidget for visualizing time-off requests
- Improve API error handling and token management
- Add utility functions for consistent date and time formatting
- Clean up unused mock data and update types
2025-12-10 18:21:08 +01:00

139 lines
6.7 KiB
TypeScript

import React, { JSX, useEffect, useState } from 'react';
import { Calendar as CalendarIcon, CalendarX, Clock, Plus, Thermometer } from 'lucide-react-native';
import { Alert, ScrollView, Text, TouchableOpacity, View, ActivityIndicator, RefreshControl } from 'react-native';
import { TimeOffRequest, TimeOffRequestType } from '@/types/types';
import RequestPermitModal from '@/components/RequestPermitModal';
import CalendarWidget from '@/components/CalendarWidget';
import api from '@/utils/api';
import { formatDate, formatTime } from '@/utils/dateTime';
// Icon Mapping
const typeIcons: Record<string, (color: string) => JSX.Element> = {
Ferie: (color) => <CalendarIcon size={24} color={color} />,
Permesso: (color) => <Clock size={24} color={color} />,
Malattia: (color) => <Thermometer size={24} color={color} />,
Assenza: (color) => <CalendarX size={24} color={color} />,
};
export default function PermitsScreen() {
const [showModal, setShowModal] = useState(false);
const [permits, setPermits] = useState<TimeOffRequest[]>([]);
const [types, setTypes] = useState<TimeOffRequestType[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const fetchPermits = async () => {
try {
if (!refreshing) setIsLoading(true);
// Fetch Permits
const response = await api.get('/time-off-request/list');
setPermits(response.data);
// Fetch Types
const typesResponse = await api.get('/time-off-request/get-types');
setTypes(typesResponse.data);
} catch (error) {
console.error('Errore nel recupero dei permessi:', error);
Alert.alert('Errore', 'Impossibile recuperare i permessi. Riprova più tardi.');
} finally {
setIsLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchPermits();
}, []);
const onRefresh = () => {
setRefreshing(true);
fetchPermits();
};
// TODO: Migliorare schermata di caricamento -> spostarla in un componente a parte
if (isLoading && !refreshing) {
return (
<View className="flex-1 justify-center items-center bg-gray-50">
<ActivityIndicator size="large" color="#099499" />
<Text className="text-gray-500 mt-2">Caricamento...</Text>
</View>
);
}
return (
<View className="flex-1 bg-gray-50">
<RequestPermitModal
visible={showModal}
types={types}
onClose={() => setShowModal(false)}
onSubmit={(data) => { console.log('Richiesta:', data); fetchPermits(); }}
/>
{/* Header */}
<View className="bg-white p-6 pt-16 shadow-sm border-b border-gray-100 flex-row justify-between items-center">
<View>
<Text className="text-3xl font-bold text-gray-800 mb-1">Ferie e Permessi</Text>
<Text className="text-base text-gray-500">Gestisci le tue assenze</Text>
</View>
</View>
<ScrollView
contentContainerStyle={{ padding: 20, paddingBottom: 100, gap: 24 }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={['#099499']} />
}
>
{/* Calendar Widget */}
<CalendarWidget events={permits} types={types} />
{/* Lista Richieste Recenti */}
<View>
{permits.length === 0 ? (
<Text className="text-center text-gray-500 mt-8">Nessuna richiesta di permesso trovata.</Text>
) : (
<View className="gap-4">
<Text className="text-xl font-bold text-gray-800 px-1">Le tue richieste</Text>
{/* TODO: Aggiungere una paginazione con delle freccette affianco? */}
{permits.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">
<View className="flex-row items-center gap-4">
<View className={`p-4 rounded-2xl`} style={{ backgroundColor: item.timeOffRequestType.color ? `${item.timeOffRequestType.color}25` : '#E5E7EB' }}>
{typeIcons[item.timeOffRequestType.name]?.(item.timeOffRequestType.color)}
</View>
<View>
<Text className="font-bold text-gray-800 text-lg">{item.timeOffRequestType.name}</Text>
<Text className="text-base text-gray-500 mt-0.5">
{formatDate(item.start_date?.toLocaleString())} {item.end_date ? `- ${formatDate(item.end_date.toLocaleString())}` : ''}
</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 ? 'bg-green-100' : 'bg-yellow-100'}`}>
<Text className={`text-xs font-bold uppercase tracking-wide ${item.status ? 'text-green-700' : 'text-yellow-700'}`}>
{item.status ? 'Approvato' : 'In Attesa'}
</Text>
</View>
</View>
))}
</View>
)}
</View>
</ScrollView>
{/* FAB */}
<TouchableOpacity
onPress={() => setShowModal(true)}
className="absolute bottom-8 right-6 w-16 h-16 bg-[#099499] rounded-full shadow-lg items-center justify-center active:scale-90"
>
<Plus size={32} color="white" />
</TouchableOpacity>
</View>
);
}