From bb4e075b15482064609f28e2b5049f7dea1ac79a Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:15:08 +0200 Subject: [PATCH 1/9] feat: replace collapsible completed tasks section with small button and modal --- src/components/TaskList/ActiveFilters.tsx | 53 +++-- .../TaskList/CompletedTasksButton.tsx | 50 ++++ .../TaskList/CompletedTasksModal.tsx | 219 ++++++++++++++++++ src/components/TaskList/FilterModal.tsx | 47 ++-- src/components/TaskList/TaskListContainer.tsx | 122 +++++----- src/locales/en.json | 19 +- src/locales/it.json | 19 +- 7 files changed, 434 insertions(+), 95 deletions(-) create mode 100644 src/components/TaskList/CompletedTasksButton.tsx create mode 100644 src/components/TaskList/CompletedTasksModal.tsx 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..51e5dc5 --- /dev/null +++ b/src/components/TaskList/CompletedTasksButton.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { TouchableOpacity, Text, StyleSheet } from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import { colors, radius, elevation } from '../../theme/tokens'; + +interface CompletedTasksButtonProps { + count: number; + onPress: () => void; +} + +export const CompletedTasksButton: React.FC = ({ count, onPress }) => { + if (count === 0) return null; + + return ( + + + + {count} {count === 1 ? 'completato' : 'completati'} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + bottom: 20, + left: 20, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + paddingHorizontal: 14, + paddingVertical: 10, + borderRadius: radius.lg, + borderWidth: 1, + borderColor: colors.border, + ...elevation.sm, + gap: 6, + }, + text: { + fontSize: 14, + fontWeight: '500', + color: colors.textSecondary, + fontFamily: 'Inter_500Medium', + }, +}); \ No newline at end of file diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx new file mode 100644 index 0000000..fb40774 --- /dev/null +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -0,0 +1,219 @@ +import React, { useRef } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Modal, + ScrollView, + Animated, + Alert, +} from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import { colors, radius, elevation, spacing, typography } from '../../theme/tokens'; +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 slideAnim = useRef(new Animated.Value(1)).current; + + React.useEffect(() => { + if (visible) { + Animated.timing(slideAnim, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(); + } else { + Animated.timing(slideAnim, { + toValue: 1, + duration: 250, + useNativeDriver: true, + }).start(); + } + }, [visible, slideAnim]); + + const handleDeleteAll = () => { + Alert.alert( + 'Elimina tutti i task completati', + 'Sei sicuro di voler eliminare tutti i task completati? Questa azione non può essere annullata.', + [ + { text: 'Annulla', style: 'cancel' }, + { + text: 'Elimina', + style: 'destructive', + onPress: () => { + onDeleteAll(); + onClose(); + }, + }, + ] + ); + }; + + return ( + + + + e.stopPropagation()}> + + + + + + Task completati + + + + + + {tasks.length > 0 && ( + + + + Elimina tutti + + + )} + + + {tasks.length > 0 ? ( + tasks.map((item, index) => renderTask(item, index)) + ) : ( + + + Nessun task completato + + )} + + + + + + ); +}; + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.4)', + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: colors.surface, + borderTopLeftRadius: radius.xxl, + borderTopRightRadius: radius.xxl, + maxHeight: '85%', + ...elevation.lg, + }, + dragHandleContainer: { + alignItems: 'center', + paddingTop: spacing.md, + paddingBottom: spacing.sm, + }, + dragHandle: { + width: 40, + height: 4, + backgroundColor: colors.border, + borderRadius: 2, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: spacing.lg, + paddingBottom: spacing.md, + borderBottomWidth: 1, + borderBottomColor: colors.border, + }, + title: { + ...typography.title, + fontSize: 20, + fontWeight: '600', + }, + closeButton: { + width: 32, + height: 32, + borderRadius: radius.sm, + alignItems: 'center', + justifyContent: 'center', + }, + actionsContainer: { + paddingHorizontal: spacing.lg, + paddingTop: spacing.md, + paddingBottom: spacing.sm, + }, + deleteAllButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surfaceMuted, + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, + borderRadius: radius.md, + gap: 6, + alignSelf: 'flex-start', + }, + deleteAllText: { + fontSize: 14, + fontWeight: '500', + color: colors.danger, + fontFamily: 'Inter_500Medium', + }, + scrollContent: { + flex: 1, + }, + scrollContentContainer: { + paddingHorizontal: spacing.lg, + paddingBottom: spacing.xxl * 2, + }, + emptyContainer: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: spacing.xxxl * 2, + }, + emptyText: { + ...typography.body, + color: colors.textSecondary, + marginTop: spacing.md, + textAlign: 'center', + }, +}); \ No newline at end of file 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..d29a1fa 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(); @@ -525,15 +518,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("Errore", "Impossibile eliminare tutti i task completati. Riprova."); + } }; return ( @@ -597,44 +604,45 @@ 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 */} + 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 */} - - - )} - Date: Fri, 15 May 2026 21:18:35 +0200 Subject: [PATCH 2/9] feat: add translations for task list filters and center settings icon --- src/components/TaskList/TaskListContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TaskList/TaskListContainer.tsx b/src/components/TaskList/TaskListContainer.tsx index d29a1fa..71a7ba7 100644 --- a/src/components/TaskList/TaskListContainer.tsx +++ b/src/components/TaskList/TaskListContainer.tsx @@ -57,7 +57,7 @@ export const TaskListContainer = ({ title: categoryName, headerRight: () => ( setModalVisible(true)} > From 12c28445d2a49e52a676e1f61a4c0e702661d4b7 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:23:04 +0200 Subject: [PATCH 3/9] fix: correct animation interpolation in CompletedTasksModal to prevent NaN error --- .../TaskList/CompletedTasksModal.tsx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index fb40774..5dbf609 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useState } from 'react'; import { View, Text, @@ -7,12 +7,15 @@ import { Modal, ScrollView, Animated, + Dimensions, Alert, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import { colors, radius, elevation, spacing, typography } from '../../theme/tokens'; import { Task as TaskType } from './types'; +const { height: SCREEN_HEIGHT } = Dimensions.get('window'); + interface CompletedTasksModalProps { visible: boolean; onClose: () => void; @@ -28,24 +31,26 @@ export const CompletedTasksModal: React.FC = ({ renderTask, onDeleteAll, }) => { - const slideAnim = useRef(new Animated.Value(1)).current; + const slideAnim = useRef(new Animated.Value(0)).current; React.useEffect(() => { if (visible) { - Animated.timing(slideAnim, { + Animated.spring(slideAnim, { toValue: 0, - duration: 300, useNativeDriver: true, + tension: 65, + friction: 11, }).start(); } else { - Animated.timing(slideAnim, { - toValue: 1, - duration: 250, - useNativeDriver: true, - }).start(); + slideAnim.setValue(0); } }, [visible, slideAnim]); + const translateY = slideAnim.interpolate({ + inputRange: [0, 1], + outputRange: [0, SCREEN_HEIGHT], + }); + const handleDeleteAll = () => { Alert.alert( 'Elimina tutti i task completati', @@ -80,10 +85,7 @@ export const CompletedTasksModal: React.FC = ({ style={[ styles.modalContent, { - transform: [{ translateY: slideAnim.interpolate({ - inputRange: [0, 1], - outputRange: [0, '100%'], - }) }], + transform: [{ translateY }], }, ]} > From f0c56a993ee0d49a01f90dc3fa2b1073aa68436e Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:26:10 +0200 Subject: [PATCH 4/9] fix: add debug logging and minHeight to CompletedTasksModal for troubleshooting --- .../TaskList/CompletedTasksModal.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index 5dbf609..da43453 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -118,14 +118,20 @@ export const CompletedTasksModal: React.FC = ({ contentContainerStyle={styles.scrollContentContainer} showsVerticalScrollIndicator={false} > - {tasks.length > 0 ? ( - tasks.map((item, index) => renderTask(item, index)) - ) : ( - - - Nessun task completato - - )} + + {console.log('CompletedTasksModal - tasks:', tasks.length, tasks)} + {tasks.length > 0 ? ( + tasks.map((item, index) => { + console.log('Rendering task:', index, item.title, item.status); + return renderTask(item, index); + }) + ) : ( + + + Nessun task completato + + )} + @@ -145,6 +151,7 @@ const styles = StyleSheet.create({ borderTopLeftRadius: radius.xxl, borderTopRightRadius: radius.xxl, maxHeight: '85%', + minHeight: 200, ...elevation.lg, }, dragHandleContainer: { From d2cc1524fe66a06552b85ff598712ebf01bd3aec Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:28:35 +0200 Subject: [PATCH 5/9] fix: remove conflicting paddingHorizontal and add task wrapper for better layout --- .../TaskList/CompletedTasksModal.tsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index da43453..ddf821b 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -118,20 +118,22 @@ export const CompletedTasksModal: React.FC = ({ contentContainerStyle={styles.scrollContentContainer} showsVerticalScrollIndicator={false} > - - {console.log('CompletedTasksModal - tasks:', tasks.length, tasks)} - {tasks.length > 0 ? ( - tasks.map((item, index) => { - console.log('Rendering task:', index, item.title, item.status); - return renderTask(item, index); - }) - ) : ( - - - Nessun task completato - - )} - + {console.log('CompletedTasksModal - tasks:', tasks.length, tasks)} + {tasks.length > 0 ? ( + tasks.map((item, index) => { + console.log('Rendering task:', index, item.title, item.status); + return ( + + {renderTask(item, index)} + + ); + }) + ) : ( + + + Nessun task completato + + )} @@ -209,9 +211,9 @@ const styles = StyleSheet.create({ }, scrollContent: { flex: 1, + width: '100%', }, scrollContentContainer: { - paddingHorizontal: spacing.lg, paddingBottom: spacing.xxl * 2, }, emptyContainer: { From 12854bb846f921e7ab2fd0577ff18e35a187bcc3 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:31:48 +0200 Subject: [PATCH 6/9] fix: replace ScrollView with FlatList for better rendering performance --- .../TaskList/CompletedTasksModal.tsx | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index ddf821b..577ceca 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -5,7 +5,7 @@ import { TouchableOpacity, StyleSheet, Modal, - ScrollView, + FlatList, Animated, Dimensions, Alert, @@ -113,28 +113,28 @@ export const CompletedTasksModal: React.FC = ({ )} - - {console.log('CompletedTasksModal - tasks:', tasks.length, tasks)} - {tasks.length > 0 ? ( - tasks.map((item, index) => { - console.log('Rendering task:', index, item.title, item.status); + + `completed-${item.id || item.task_id || index}`} + renderItem={({ item, index }) => { + console.log('FlatList rendering task:', index, item.title); return ( - + {renderTask(item, index)} ); - }) - ) : ( - - - Nessun task completato - - )} - + }} + ListEmptyComponent={ + + + Nessun task completato + + } + contentContainerStyle={styles.scrollContentContainer} + showsVerticalScrollIndicator={false} + /> + @@ -209,12 +209,13 @@ const styles = StyleSheet.create({ color: colors.danger, fontFamily: 'Inter_500Medium', }, - scrollContent: { + tasksContainer: { flex: 1, width: '100%', }, scrollContentContainer: { paddingBottom: spacing.xxl * 2, + flexGrow: 1, }, emptyContainer: { alignItems: 'center', From fa756448f49fbf07b5ee1d627ac8eeca178fd2ee Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 10:37:59 +0200 Subject: [PATCH 7/9] feat: add debug logging and improve modal animation for completed tasks --- .../TaskList/CompletedTasksModal.tsx | 75 +++++++++---------- src/components/TaskList/TaskListContainer.tsx | 15 +++- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index 577ceca..af95c8e 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React from 'react'; import { View, Text, @@ -6,16 +6,13 @@ import { StyleSheet, Modal, FlatList, - Animated, - Dimensions, Alert, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; +import { useTranslation } from 'react-i18next'; import { colors, radius, elevation, spacing, typography } from '../../theme/tokens'; import { Task as TaskType } from './types'; -const { height: SCREEN_HEIGHT } = Dimensions.get('window'); - interface CompletedTasksModalProps { visible: boolean; onClose: () => void; @@ -31,34 +28,30 @@ export const CompletedTasksModal: React.FC = ({ renderTask, onDeleteAll, }) => { - const slideAnim = useRef(new Animated.Value(0)).current; - - React.useEffect(() => { - if (visible) { - Animated.spring(slideAnim, { - toValue: 0, - useNativeDriver: true, - tension: 65, - friction: 11, - }).start(); - } else { - slideAnim.setValue(0); - } - }, [visible, slideAnim]); + const { t } = useTranslation(); - const translateY = slideAnim.interpolate({ - inputRange: [0, 1], - outputRange: [0, SCREEN_HEIGHT], + console.log('[COMPLETED_TASKS_MODAL] Props ricevuti:', { + visible, + tasksCount: tasks.length, + tasks: tasks.map(t => ({ id: t.id, title: t.title, status: t.status })), + hasRenderTask: typeof renderTask === 'function' }); + // Disabilitato animazione per debug + // const slideAnim = useRef(new Animated.Value(1)).current; + // const translateY = slideAnim.interpolate({ + // inputRange: [0, 1], + // outputRange: [0, SCREEN_HEIGHT], + // }); + const handleDeleteAll = () => { Alert.alert( - 'Elimina tutti i task completati', - 'Sei sicuro di voler eliminare tutti i task completati? Questa azione non può essere annullata.', + t('categories.deleteModal.title'), + t('categories.deleteModal.confirmMessage') + ' ' + t('categories.deleteModal.warningMessage'), [ - { text: 'Annulla', style: 'cancel' }, + { text: t('common.buttons.cancel'), style: 'cancel' }, { - text: 'Elimina', + text: t('common.buttons.delete'), style: 'destructive', onPress: () => { onDeleteAll(); @@ -73,7 +66,7 @@ export const CompletedTasksModal: React.FC = ({ = ({ activeOpacity={1} onPress={onClose} > - + e.stopPropagation()}> - Task completati + {t('taskList.sections.completed')} @@ -108,20 +94,29 @@ export const CompletedTasksModal: React.FC = ({ onPress={handleDeleteAll} > - Elimina tutti + {t('taskActionMenu.delete')} )} + + {tasks.length === 0 ? "Nessun task" : `${tasks.length} task completati`} + + `completed-${item.id || item.task_id || index}`} renderItem={({ item, index }) => { - console.log('FlatList rendering task:', index, item.title); + console.log('[COMPLETED_TASKS_MODAL] FlatList renderItem chiamato:', { + index, + task: { id: item.id, title: item.title, status: item.status } + }); + const rendered = renderTask(item, index); + console.log('[COMPLETED_TASKS_MODAL] renderTask ritornato:', rendered != null ? 'jsx element' : 'null/undefined'); return ( - {renderTask(item, index)} + {rendered} ); }} @@ -136,7 +131,7 @@ export const CompletedTasksModal: React.FC = ({ /> - + ); diff --git a/src/components/TaskList/TaskListContainer.tsx b/src/components/TaskList/TaskListContainer.tsx index 71a7ba7..005eae3 100644 --- a/src/components/TaskList/TaskListContainer.tsx +++ b/src/components/TaskList/TaskListContainer.tsx @@ -317,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(() => { @@ -539,7 +545,7 @@ export const TaskListContainer = ({ } } catch (error) { console.error("Errore nell'eliminazione dei task completati:", error); - Alert.alert("Errore", "Impossibile eliminare tutti i task completati. Riprova."); + Alert.alert(t('itemDetailModal.error'), t('taskDelete.deleteAllCompletedError')); } }; @@ -612,7 +618,10 @@ export const TaskListContainer = ({ {/* Bottone task completati */} setCompletedTasksModalVisible(true)} + onPress={() => { + console.log('[TASK_LIST_CONTAINER] Bottone task completati premuto, completedTasks:', completedTasks); + setCompletedTasksModalVisible(true); + }} /> {/* Modal task completati */} From 1cafa74bb1918b8c1f2103f14166fddb4232ebd2 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 10:38:08 +0200 Subject: [PATCH 8/9] feat: add translation support and task count display to completed tasks modal --- src/components/TaskList/CompletedTasksModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index af95c8e..16a0a71 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -223,4 +223,4 @@ const styles = StyleSheet.create({ marginTop: spacing.md, textAlign: 'center', }, -}); \ No newline at end of file +}); From 0ce628a51a6d4f1239f12e23b49697dcfcc7d921 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 11:19:07 +0200 Subject: [PATCH 9/9] feat: redesign completed tasks button and modal with new UI --- .../TaskList/CompletedTasksButton.tsx | 50 ++-- .../TaskList/CompletedTasksModal.tsx | 270 ++++++++++-------- 2 files changed, 175 insertions(+), 145 deletions(-) diff --git a/src/components/TaskList/CompletedTasksButton.tsx b/src/components/TaskList/CompletedTasksButton.tsx index 51e5dc5..023b88a 100644 --- a/src/components/TaskList/CompletedTasksButton.tsx +++ b/src/components/TaskList/CompletedTasksButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { TouchableOpacity, Text, StyleSheet } from 'react-native'; -import { MaterialIcons } from '@expo/vector-icons'; -import { colors, radius, elevation } from '../../theme/tokens'; +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; @@ -9,18 +9,22 @@ interface CompletedTasksButtonProps { } export const CompletedTasksButton: React.FC = ({ count, onPress }) => { + const insets = useSafeAreaInsets(); + if (count === 0) return null; return ( - - - {count} {count === 1 ? 'completato' : 'completati'} - + + + + {count} + + ); }; @@ -30,21 +34,25 @@ const styles = StyleSheet.create({ position: 'absolute', bottom: 20, left: 20, - flexDirection: 'row', + 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', - backgroundColor: colors.surface, - paddingHorizontal: 14, - paddingVertical: 10, - borderRadius: radius.lg, - borderWidth: 1, - borderColor: colors.border, - ...elevation.sm, - gap: 6, + justifyContent: 'center', + gap: 2, }, text: { - fontSize: 14, + fontSize: 16, fontWeight: '500', - color: colors.textSecondary, - fontFamily: 'Inter_500Medium', + color: '#ffffff', + fontFamily: 'System', }, -}); \ No newline at end of file +}); diff --git a/src/components/TaskList/CompletedTasksModal.tsx b/src/components/TaskList/CompletedTasksModal.tsx index 16a0a71..237852c 100644 --- a/src/components/TaskList/CompletedTasksModal.tsx +++ b/src/components/TaskList/CompletedTasksModal.tsx @@ -8,9 +8,8 @@ import { FlatList, Alert, } from 'react-native'; -import { MaterialIcons } from '@expo/vector-icons'; +import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; -import { colors, radius, elevation, spacing, typography } from '../../theme/tokens'; import { Task as TaskType } from './types'; interface CompletedTasksModalProps { @@ -30,24 +29,10 @@ export const CompletedTasksModal: React.FC = ({ }) => { const { t } = useTranslation(); - console.log('[COMPLETED_TASKS_MODAL] Props ricevuti:', { - visible, - tasksCount: tasks.length, - tasks: tasks.map(t => ({ id: t.id, title: t.title, status: t.status })), - hasRenderTask: typeof renderTask === 'function' - }); - - // Disabilitato animazione per debug - // const slideAnim = useRef(new Animated.Value(1)).current; - // const translateY = slideAnim.interpolate({ - // inputRange: [0, 1], - // outputRange: [0, SCREEN_HEIGHT], - // }); - const handleDeleteAll = () => { Alert.alert( - t('categories.deleteModal.title'), - t('categories.deleteModal.confirmMessage') + ' ' + t('categories.deleteModal.warningMessage'), + t('completedTasks.deleteAll.title'), + t('completedTasks.deleteAll.message'), [ { text: t('common.buttons.cancel'), style: 'cancel' }, { @@ -69,158 +54,195 @@ export const CompletedTasksModal: React.FC = ({ animationType="slide" onRequestClose={onClose} > - - - e.stopPropagation()}> - - + + + + + {t('taskList.sections.completed')} + + {t('completedTasks.count', { count: tasks.length })} + - - - {t('taskList.sections.completed')} - - - - - - {tasks.length > 0 && ( - + + {tasks.length > 0 && ( - - {t('taskActionMenu.delete')} + + + {t('completedTasks.deleteAll.label')} + - - )} - - - {tasks.length === 0 ? "Nessun task" : `${tasks.length} task completati`} - + )} + + + + + - - `completed-${item.id || item.task_id || index}`} - renderItem={({ item, index }) => { - console.log('[COMPLETED_TASKS_MODAL] FlatList renderItem chiamato:', { - index, - task: { id: item.id, title: item.title, status: item.status } - }); - const rendered = renderTask(item, index); - console.log('[COMPLETED_TASKS_MODAL] renderTask ritornato:', rendered != null ? 'jsx element' : 'null/undefined'); - return ( - - {rendered} - - ); - }} - ListEmptyComponent={ - - - Nessun task completato - - } - contentContainerStyle={styles.scrollContentContainer} - showsVerticalScrollIndicator={false} - /> + {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({ - overlay: { + modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', justifyContent: 'flex-end', }, - modalContent: { - backgroundColor: colors.surface, - borderTopLeftRadius: radius.xxl, - borderTopRightRadius: radius.xxl, - maxHeight: '85%', - minHeight: 200, - ...elevation.lg, - }, - dragHandleContainer: { - alignItems: 'center', - paddingTop: spacing.md, - paddingBottom: spacing.sm, - }, - dragHandle: { - width: 40, - height: 4, - backgroundColor: colors.border, - borderRadius: 2, + 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, }, - header: { + formHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingHorizontal: spacing.lg, - paddingBottom: spacing.md, + padding: 24, borderBottomWidth: 1, - borderBottomColor: colors.border, + borderBottomColor: '#e1e5e9', + backgroundColor: '#ffffff', }, - title: { - ...typography.title, - fontSize: 20, - fontWeight: '600', + formTitle: { + fontSize: 24, + fontWeight: '300', + color: '#000000', + fontFamily: 'System', + letterSpacing: -0.5, }, - closeButton: { - width: 32, - height: 32, - borderRadius: radius.sm, - alignItems: 'center', - justifyContent: 'center', + formSubtitle: { + fontSize: 16, + fontWeight: '400', + color: '#999999', + fontFamily: 'System', + marginTop: 4, }, - actionsContainer: { - paddingHorizontal: spacing.lg, - paddingTop: spacing.md, - paddingBottom: spacing.sm, + headerActions: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, }, deleteAllButton: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.surfaceMuted, - paddingHorizontal: spacing.md, - paddingVertical: spacing.sm, - borderRadius: radius.md, - gap: 6, - alignSelf: 'flex-start', + backgroundColor: '#FFF8F8', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 16, + borderWidth: 1.5, + borderColor: '#FFD6D6', + gap: 8, }, deleteAllText: { - fontSize: 14, + fontSize: 15, fontWeight: '500', - color: colors.danger, - fontFamily: 'Inter_500Medium', + color: '#FF5252', + fontFamily: 'System', + }, + closeButton: { + width: 36, + height: 36, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', }, tasksContainer: { flex: 1, width: '100%', }, scrollContentContainer: { - paddingBottom: spacing.xxl * 2, + paddingBottom: 48, flexGrow: 1, }, emptyContainer: { alignItems: 'center', justifyContent: 'center', - paddingVertical: spacing.xxxl * 2, + paddingVertical: 80, }, emptyText: { - ...typography.body, - color: colors.textSecondary, - marginTop: spacing.md, + 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', + }, });