taskeep-app/components/taskItem.tsx
2025-07-30 16:03:14 +02:00

146 lines
6.0 KiB
TypeScript

import { Alert, ColorSchemeName, Pressable, StyleSheet, useColorScheme, type ViewProps } from 'react-native';
import { toast } from '@backpackapp-io/react-native-toast';
import { Text } from '@react-navigation/elements';
import { useRouter } from 'expo-router';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useThemeColor } from '../hooks/useThemeColor';
import { Category } from '../models/category';
import { Task, TaskQuery } from '../models/task';
import { TaskRepository } from '../repositories/TaskRepository';
import { SQLiteDataService } from '../services/data/sqliteDataService';
import { GetTaskColor } from '../utils/colors';
import { ThemedView } from './ThemedView';
import ClockProgress from './clockProgress';
export type TaskItemProps = ViewProps & {
lightColor?: string;
darkColor?: string;
task: Task;
onUpdate: () => void;
categories: Category[];
};
const taskRepository = new TaskRepository(new SQLiteDataService<Task>(TaskQuery));
const adaptDaysToGoToUnit = (t: Task) => {
const timeLeft = t.lastDone! + (t.daysToRedo! * 24 * 60 * 60 * 1000) - Date.now();
if (timeLeft < 0) return 0; // Task is overdue
else if (timeLeft < 3 * 60 * 60 * 1000) return Math.ceil(timeLeft / (60 * 1000)) + " minutes";
else if (timeLeft < 24 * 60 * 60 * 1000) return Math.ceil(timeLeft / (3600 * 1000)) + " hours";
else if (timeLeft < 7 * 24 * 60 * 60 * 1000) return Math.ceil(timeLeft / (24 * 3600 * 1000)) + " days";
else if (timeLeft < 30 * 24 * 60 * 60 * 1000) return Math.ceil(timeLeft / (7 * 24 * 3600 * 1000)) + " weeks";
else return Math.ceil(timeLeft / (30 * 24 * 3600 * 1000)) + " months";
};
export function TaskItem({ task, categories, onUpdate, lightColor, darkColor, ...otherProps }: TaskItemProps) {
const { t } = useTranslation();
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
const router = useRouter();
const daysLeft = useRef(task.daysToRedo! * 24 * 3600 * 1000 - (Date.now() - task.lastDone!));
const colorScheme = useColorScheme();
const category = categories?.find((c) => {
return c.id === task?.category;
});
const LongPressHandler = () => {
//select edit or remove
console.log("Long pressed task:", task);
Alert.alert(
t('select_action'),
t('choose_action_for_task'),
[
{
text: t('edit'),
onPress: () => {
// Navigate to edit task form
console.log("Editing task:", task);
if (!task?.id) return;
router.push({ pathname: "./taskForm", params: { task: JSON.stringify(task) } });
}
},
{
text: t('remove'),
onPress: () => {
// Remove the task
console.log("Removing task:", task);
if (!task?.id) return;
taskRepository.delete(task.id).then(() => {
toast.success(t('task_removed_successfully'));
onUpdate?.();
}).catch((error) => {
toast.error(t('error_removing_task'));
console.error("Error removing task:", error);
});
},
style: "destructive"
},
{ text: t('cancel'), style: "cancel" }
]);
}
return <Pressable onLongPress={() => {
LongPressHandler();
}}>
<ThemedView style={[{ backgroundColor }, styles(colorScheme).taskItemMain, {
borderLeftColor: GetTaskColor(task!)
}]} {...otherProps}>
<ThemedView style={[{ backgroundColor }, styles(colorScheme).taskItemSubcontainer]}>
<Text style={styles(colorScheme).taskItemTitle}>
{task?.title}
</Text>
<Text style={styles(colorScheme).taskItemText}>
{category?.icon + " " + category?.title + " - " + (adaptDaysToGoToUnit(task) == 0 ? "Overdue" : adaptDaysToGoToUnit(task))}
</Text>
</ThemedView>
<Pressable style={styles(colorScheme).checkButton} onPress={() => {
taskRepository.update(task!.id!, { ...task, lastDone: Date.now() }).then(() => {
toast.success(t('task_updated_successfully'));
onUpdate?.();
}).catch((error) => {
toast.error(t('error_updating_task'));
console.error("Error updating task:", error);
});
}}>
<ClockProgress degree={parseInt(adaptDaysToGoToUnit(task).toString()) > 0 ? ((daysLeft.current / (24 * 3600 * 1000)) / task.daysToRedo!) * 360 : 0} width={40} height={40} color={GetTaskColor(task!)} />
</Pressable>
</ThemedView></Pressable>;
}
const styles = (theme: ColorSchemeName) => {
return StyleSheet.create({
taskItemMain: {
borderLeftWidth: 20,
borderColor: 'color-mix(in oklab, #a71e14 100%, white)',
flexDirection: 'row',
padding: 16,
borderRadius: 8,
marginBottom: 8,
backgroundColor: theme === 'dark' ? '#555' : '#f0f0f0',
justifyContent: 'space-between',
alignItems: 'center',
},
taskItemSubcontainer: {
flexDirection: 'column',
backgroundColor: 'transparent'
},
taskItemText: {
color: theme === 'dark' ? '#fff' : '#000',
fontSize: 15,
marginBottom: 4,
},
taskItemTitle: {
fontWeight: 'bold',
fontSize: 20,
marginBottom: 4,
color: theme === 'dark' ? '#fff' : '#000',
},
checkButton: {
width: 40,
height: 40,
}
})
}