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 */}
-
-
- )}
-