diff --git a/src/components/TaskList/ActiveFilters.tsx b/src/components/TaskList/ActiveFilters.tsx index 3fb29b3..451b65c 100644 --- a/src/components/TaskList/ActiveFilters.tsx +++ b/src/components/TaskList/ActiveFilters.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; export interface ActiveFiltersProps { importanceFilter: string; @@ -9,43 +10,67 @@ export interface ActiveFiltersProps { onClearDeadlineFilter: () => void; } -export const ActiveFilters = ({ - importanceFilter, - deadlineFilter, - onClearImportanceFilter, - onClearDeadlineFilter +export const ActiveFilters = ({ + importanceFilter, + deadlineFilter, + onClearImportanceFilter, + onClearDeadlineFilter }: ActiveFiltersProps) => { - + + const { t } = useTranslation(); + + // Mappa i filtri italiani ai valori tradotti + const getTranslatedFilter = (filter: string, type: 'importance' | 'deadline'): string => { + if (type === 'importance') { + switch (filter) { + case 'Alta': return t('taskList.filters.high'); + case 'Media': return t('taskList.filters.medium'); + case 'Bassa': return t('taskList.filters.low'); + default: return filter; + } + } else { + switch (filter) { + case 'Oggi': return t('taskList.filters.today'); + case 'Domani': return t('taskList.filters.tomorrow'); + case 'Dopodomani': return t('taskList.filters.dayAfterTomorrow'); + case 'Fra 3 giorni': return t('taskList.filters.in3Days'); + case 'Fra 7 giorni': return t('taskList.filters.in7Days'); + case 'Senza scadenza': return t('taskList.filters.noDeadline'); + default: return filter; + } + } + }; + // Se non c'è nessun filtro attivo, non renderizzare il componente if (importanceFilter === 'Tutte' && deadlineFilter === 'Tutte') { return null; } - + return ( - {importanceFilter !== 'Tutte' && ( - - {importanceFilter} + {getTranslatedFilter(importanceFilter, 'importance')} )} - + {deadlineFilter !== 'Tutte' && ( - - {deadlineFilter} + {getTranslatedFilter(deadlineFilter, 'deadline')} )} diff --git a/src/components/TaskList/CompletedTasksButton.tsx b/src/components/TaskList/CompletedTasksButton.tsx new file mode 100644 index 0000000..023b88a --- /dev/null +++ b/src/components/TaskList/CompletedTasksButton.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { TouchableOpacity, Text, StyleSheet, View } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +interface CompletedTasksButtonProps { + count: number; + onPress: () => void; +} + +export const CompletedTasksButton: React.FC = ({ count, onPress }) => { + const insets = useSafeAreaInsets(); + + if (count === 0) return null; + + return ( + + + + + {count} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + bottom: 20, + left: 20, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.08, + shadowRadius: 12, + elevation: 3, + }, + inner: { + backgroundColor: '#000000', + width: 56, + height: 56, + borderRadius: 28, + alignItems: 'center', + justifyContent: 'center', + gap: 2, + }, + text: { + fontSize: 16, + fontWeight: '500', + color: '#ffffff', + fontFamily: 'System', + }, +}); diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx new file mode 100644 index 0000000..237852c --- /dev/null +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -0,0 +1,248 @@ +import React from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Modal, + FlatList, + Alert, +} from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { useTranslation } from 'react-i18next'; +import { Task as TaskType } from './types'; + +interface CompletedTasksModalProps { + visible: boolean; + onClose: () => void; + tasks: TaskType[]; + renderTask: (item: TaskType, index: number) => JSX.Element; + onDeleteAll: () => void; +} + +export const CompletedTasksModal: React.FC = ({ + visible, + onClose, + tasks, + renderTask, + onDeleteAll, +}) => { + const { t } = useTranslation(); + + const handleDeleteAll = () => { + Alert.alert( + t('completedTasks.deleteAll.title'), + t('completedTasks.deleteAll.message'), + [ + { text: t('common.buttons.cancel'), style: 'cancel' }, + { + text: t('common.buttons.delete'), + style: 'destructive', + onPress: () => { + onDeleteAll(); + onClose(); + }, + }, + ] + ); + }; + + return ( + + + + + + {t('taskList.sections.completed')} + + {t('completedTasks.count', { count: tasks.length })} + + + + {tasks.length > 0 && ( + + + + {t('completedTasks.deleteAll.label')} + + + )} + + + + + + + {tasks.length > 0 && ( + + + {Array.from({ length: 28 }).map((_, i) => ( + + ))} + + + {t('completedTasks.comingSoon')} + + + )} + + + `completed-${item.id || item.task_id || index}`} + renderItem={({ item, index }) => ( + + {renderTask(item, index)} + + )} + ListEmptyComponent={ + + + + {t('taskList.sections.emptyCompleted')} + + + } + contentContainerStyle={styles.scrollContentContainer} + showsVerticalScrollIndicator={false} + /> + + + + + ); +}; + +const styles = StyleSheet.create({ + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.4)', + justifyContent: 'flex-end', + }, + formContainer: { + width: '100%', + height: '92%', + backgroundColor: '#ffffff', + borderTopLeftRadius: 24, + borderTopRightRadius: 24, + overflow: 'hidden', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: -4, + }, + shadowOpacity: 0.08, + shadowRadius: 12, + elevation: 8, + }, + formHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 24, + borderBottomWidth: 1, + borderBottomColor: '#e1e5e9', + backgroundColor: '#ffffff', + }, + formTitle: { + fontSize: 24, + fontWeight: '300', + color: '#000000', + fontFamily: 'System', + letterSpacing: -0.5, + }, + formSubtitle: { + fontSize: 16, + fontWeight: '400', + color: '#999999', + fontFamily: 'System', + marginTop: 4, + }, + headerActions: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + deleteAllButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#FFF8F8', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 16, + borderWidth: 1.5, + borderColor: '#FFD6D6', + gap: 8, + }, + deleteAllText: { + fontSize: 15, + fontWeight: '500', + color: '#FF5252', + fontFamily: 'System', + }, + closeButton: { + width: 36, + height: 36, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', + }, + tasksContainer: { + flex: 1, + width: '100%', + }, + scrollContentContainer: { + paddingBottom: 48, + flexGrow: 1, + }, + emptyContainer: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 80, + }, + emptyText: { + fontSize: 17, + fontWeight: '400', + color: '#999999', + fontFamily: 'System', + marginTop: 16, + textAlign: 'center', + }, + chartPlaceholder: { + paddingHorizontal: 24, + paddingVertical: 24, + borderBottomWidth: 1, + borderBottomColor: '#e1e5e9', + alignItems: 'center', + }, + chartGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + width: 240, + gap: 4, + marginBottom: 16, + opacity: 0.3, + }, + gridCell: { + width: 26, + height: 26, + borderRadius: 6, + backgroundColor: '#000000', + }, + chartPlaceholderTitle: { + fontSize: 15, + fontWeight: '400', + color: '#999999', + fontFamily: 'System', + letterSpacing: 0.5, + textTransform: 'uppercase', + }, +}); diff --git a/src/components/TaskList/FilterModal.tsx b/src/components/TaskList/FilterModal.tsx index 6f70c04..78c636e 100644 --- a/src/components/TaskList/FilterModal.tsx +++ b/src/components/TaskList/FilterModal.tsx @@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react'; import { View, Text, Modal, TouchableOpacity, ScrollView, Animated, PanResponder, Dimensions } from 'react-native'; import { styles } from './styles'; import { FilterChip } from './FilterChip'; +import { useTranslation } from 'react-i18next'; export interface FilterModalProps { visible: boolean; @@ -26,6 +27,8 @@ export const FilterModal = ({ ordineScadenza, setOrdineScadenza }: FilterModalProps) => { + + const { t } = useTranslation(); const panY = useRef(new Animated.Value(0)).current; @@ -99,8 +102,8 @@ export const FilterModal = ({ - Filtra task - {t('taskList.filters.title')} + @@ -112,27 +115,27 @@ export const FilterModal = ({ {/* Filtro per importanza */} - Importanza + {t('taskList.filters.importance')} setFiltroImportanza("Tutte")} /> setFiltroImportanza("Alta")} color="#000000" /> setFiltroImportanza("Media")} color="#333333" /> setFiltroImportanza("Bassa")} color="#666666" @@ -142,44 +145,44 @@ export const FilterModal = ({ {/* Filtro per scadenza */} - Scadenza - {t('taskList.filters.deadline')} + setFiltroScadenza("Tutte")} /> setFiltroScadenza("Oggi")} /> setFiltroScadenza("Domani")} /> setFiltroScadenza("Dopodomani")} /> setFiltroScadenza("Fra 3 giorni")} /> setFiltroScadenza("Fra 7 giorni")} /> setFiltroScadenza("Senza scadenza")} color="#999999" @@ -189,7 +192,7 @@ export const FilterModal = ({ {/* Ordine di visualizzazione */} - Ordina per scadenza + {t('taskList.filters.sortByDeadline')} - Più Recente + {t('taskList.filters.mostRecent')} - Più Vecchio + {t('taskList.filters.oldest')} @@ -228,11 +231,11 @@ export const FilterModal = ({ - - Applica Filtri + {t('taskList.filters.apply')} diff --git a/src/components/TaskList/TaskListContainer.tsx b/src/components/TaskList/TaskListContainer.tsx index 2fd0b6f..005eae3 100644 --- a/src/components/TaskList/TaskListContainer.tsx +++ b/src/components/TaskList/TaskListContainer.tsx @@ -1,20 +1,20 @@ -import React, { useState, useEffect, useMemo, useRef, useCallback, useLayoutEffect } from 'react'; -import { View, ScrollView, Alert, Animated, Easing } from 'react-native'; +import React, { useState, useEffect, useMemo, useCallback, useLayoutEffect } from 'react'; +import { View, ScrollView, Alert, TouchableOpacity } from 'react-native'; import { useTranslation } from 'react-i18next'; import { useNavigation } from '@react-navigation/native'; import { Ionicons } from '@expo/vector-icons'; -import { TouchableOpacity } from 'react-native'; import { styles } from './styles'; import { Task as TaskType, globalTasksRef } from './types'; import eventEmitter, { EVENTS } from '../../utils/eventEmitter'; import { ActiveFilters } from './ActiveFilters'; import { FilterModal } from './FilterModal'; -import { TaskSection } from './TaskSection'; import { AddTaskButton } from './AddTaskButton'; import { filterTasksByDay } from './TaskUtils'; import AddTask from '../Task/AddTask'; import { recurringTaskService, RecurringTask, CreateRecurringTaskPayload } from '../../services/recurringTaskService'; -import { LoadingState, EmptyState, SectionHeader } from '../UI/foundation'; +import { LoadingState, EmptyState } from '../UI/foundation'; +import { CompletedTasksButton } from './CompletedTasksButton'; +import { CompletedTasksModal } from './CompletedTasksModal'; export interface TaskListContainerProps { categoryName: string; @@ -48,14 +48,7 @@ export const TaskListContainer = ({ const [isLoading, setIsLoading] = useState(true); const [modalVisible, setModalVisible] = useState(false); const [formVisible, setFormVisible] = useState(false); - - // Stati per le sezioni collassabili - const [todoSectionExpanded, setTodoSectionExpanded] = useState(true); - const [completedSectionExpanded, setCompletedSectionExpanded] = useState(false); // Collapsed by default to hide them - - // Animated values per le animazioni di altezza - const todoSectionHeight = useRef(new Animated.Value(1)).current; - const completedSectionHeight = useRef(new Animated.Value(0)).current; + const [completedTasksModalVisible, setCompletedTasksModalVisible] = useState(false); const navigation = useNavigation(); @@ -64,7 +57,7 @@ export const TaskListContainer = ({ title: categoryName, headerRight: () => ( setModalVisible(true)} > @@ -324,7 +317,13 @@ export const TaskListContainer = ({ // Separiamo i task in completati e non completati const completedTasks = useMemo(() => { - return tasks.filter(task => task.status === "Completato"); + const filtered = tasks.filter(task => task.status === "Completato"); + console.log('[TASK_LIST_CONTAINER] completedTasks calcolati:', { + totalTasks: tasks.length, + completedCount: filtered.length, + completedTasks: filtered.map(t => ({ id: t.id, title: t.title, status: t.status })) + }); + return filtered; }, [tasks]); const incompleteTasks = useMemo(() => { @@ -525,15 +524,29 @@ export const TaskListContainer = ({ } }; - // Funzione di animazione per le sezioni - const toggleSection = (isExpanded: boolean, setExpanded: React.Dispatch>, heightValue: Animated.Value) => { - setExpanded(!isExpanded); - Animated.timing(heightValue, { - toValue: isExpanded ? 0 : 1, - duration: 300, - easing: Easing.inOut(Easing.ease), - useNativeDriver: false - }).start(); + // Funzione per eliminare tutti i task completati + const handleDeleteAllCompleted = async () => { + try { + // Elimina tutti i task completati in parallelo + await Promise.all( + completedTasks.map(task => taskService.deleteTask(task.id || task.task_id)) + ); + + // Aggiorna lo stato locale + setTasks(prevTasks => + prevTasks.filter(task => task.status !== "Completato") + ); + + // Aggiorna la referenza globale + if (globalTasksRef.tasks[categoryName]) { + globalTasksRef.tasks[categoryName] = globalTasksRef.tasks[categoryName].filter( + task => task.status !== "Completato" + ); + } + } catch (error) { + console.error("Errore nell'eliminazione dei task completati:", error); + Alert.alert(t('itemDetailModal.error'), t('taskDelete.deleteAllCompletedError')); + } }; return ( @@ -597,44 +610,48 @@ export const TaskListContainer = ({ )} - {/* Sezione task completati (collapsabile in basso) */} - {completedTasks.length > 0 && ( - toggleSection(completedSectionExpanded, setCompletedSectionExpanded, completedSectionHeight)} - renderTask={(item, index) => { - // Ensure every completed task has a valid ID - const taskId = item.id || item.task_id || `completed_fallback_${Date.now()}_${index}_${Math.random().toString(36).substr(2, 9)}`; - return ( - - ); + {/* Spazio per i pulsanti */} + + + )} + + {/* Bottone task completati */} + { + console.log('[TASK_LIST_CONTAINER] Bottone task completati premuto, completedTasks:', completedTasks); + setCompletedTasksModalVisible(true); + }} + /> + + {/* Modal task completati */} + setCompletedTasksModalVisible(false)} + tasks={completedTasks} + renderTask={(item, index) => { + const taskId = item.id || item.task_id || `completed_fallback_${Date.now()}_${index}_${Math.random().toString(36).substr(2, 9)}`; + return ( + - )} + ); + }} + onDeleteAll={handleDeleteAllCompleted} + /> - {/* Spazio per il pulsante flottante */} - - - )} -