Add a weekly task view to help users organize their week
Introduces a new "Week List" view accessible via the bottom navigation. This view displays tasks scheduled for the current week, allowing users to filter, sort, and manage them. The implementation includes new components for the week list view and integrates internationalization support for English and German. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/R3zkvYP
This commit is contained in:
@@ -25,6 +25,7 @@ import TasksWithCalendar from './components/TasksWithCalendar';
|
||||
import CalendarView from './components/CalendarView';
|
||||
import KanbanBoard from './components/KanbanBoard';
|
||||
import ProjectTemplate from './components/ProjectTemplate';
|
||||
import WeekListView from './components/WeekListView';
|
||||
import ThemeToggle from './components/ThemeToggle';
|
||||
import Settings from './pages/settings';
|
||||
import { Task, Label } from '@shared/schema';
|
||||
@@ -325,6 +326,18 @@ function App() {
|
||||
/>
|
||||
);
|
||||
|
||||
case 'weeklist':
|
||||
return (
|
||||
<WeekListView
|
||||
tasks={tasks}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskEdit={handleTaskClick}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
onStartTimer={startTimer}
|
||||
onStopTimer={stopTimer}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'templates':
|
||||
return (
|
||||
<ProjectTemplate
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Home, Calendar, LayoutGrid, Plus, Settings } from 'lucide-react';
|
||||
import { Home, Calendar, List, LayoutGrid, Plus, Settings } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface BottomNavigationProps {
|
||||
@@ -17,6 +17,7 @@ export default function BottomNavigation({ onTabChange, onCreateTask, activeTab
|
||||
const tabs = [
|
||||
{ id: 'tasks', label: t('navigation.tasks'), icon: Home },
|
||||
{ id: 'calendar', label: t('navigation.calendar'), icon: Calendar },
|
||||
{ id: 'weeklist', label: t('navigation.weekList'), icon: List },
|
||||
{ id: 'create', label: t('navigation.create'), icon: Plus, isCreate: true },
|
||||
{ id: 'kanban', label: t('navigation.kanban'), icon: LayoutGrid },
|
||||
{ id: 'settings', label: t('navigation.settings'), icon: Settings }
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
import { useState } from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Search, Filter, SortAsc, Calendar, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { Task, Label } from '@shared/schema';
|
||||
import TaskCard from './TaskCard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import TimeCompletionModal from './TimeCompletionModal';
|
||||
import { addDays, format, isSameDay, startOfWeek, endOfWeek, startOfDay, endOfDay, addWeeks } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface WeekListViewProps {
|
||||
tasks: Task[];
|
||||
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
||||
onTaskEdit?: (task: Task) => void;
|
||||
onTaskDelete?: (taskId: string) => void;
|
||||
onStartTimer?: (taskId: string) => void;
|
||||
onStopTimer?: (taskId: string) => void;
|
||||
}
|
||||
|
||||
type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
|
||||
type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue';
|
||||
|
||||
export default function WeekListView({ tasks, onTaskUpdate, onTaskEdit, onTaskDelete, onStartTimer, onStopTimer }: WeekListViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
||||
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
||||
const [selectedWeekStart, setSelectedWeekStart] = useState(startOfWeek(new Date(), { weekStartsOn: 1 }));
|
||||
const [completionModal, setCompletionModal] = useState<{ isOpen: boolean; task: Task | null }>({
|
||||
isOpen: false,
|
||||
task: null
|
||||
});
|
||||
|
||||
// Fetch labels to get label colors
|
||||
const { data: labels = [] } = useQuery<Label[]>({
|
||||
queryKey: ['/api/labels'],
|
||||
staleTime: 5 * 60 * 1000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
// Always show 7 days for the selected week (Monday to Sunday)
|
||||
const weekStart = startOfDay(selectedWeekStart);
|
||||
const weekEnd = endOfDay(endOfWeek(selectedWeekStart, { weekStartsOn: 1 }));
|
||||
const dates = Array.from({ length: 7 }, (_, i) => addDays(selectedWeekStart, i));
|
||||
|
||||
const isOverdue = (task: Task) => {
|
||||
if (!task.dueDate) return false;
|
||||
return task.dueDate < new Date() && task.status !== 'done';
|
||||
};
|
||||
|
||||
const isInCurrentWeek = (task: Task) => {
|
||||
if (!task.dueDate) return false;
|
||||
return task.dueDate >= weekStart && task.dueDate <= weekEnd;
|
||||
};
|
||||
|
||||
const filteredAndSortedTasks = tasks
|
||||
.filter(task => {
|
||||
// Only show tasks in the selected week
|
||||
if (!isInCurrentWeek(task)) return false;
|
||||
|
||||
// Search filter
|
||||
const matchesSearch = task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(task.description?.toLowerCase()?.includes(searchQuery.toLowerCase()) ?? false);
|
||||
|
||||
if (!matchesSearch) return false;
|
||||
|
||||
// Status filter
|
||||
switch (filterBy) {
|
||||
case 'overdue':
|
||||
return isOverdue(task);
|
||||
case 'all':
|
||||
return true;
|
||||
default:
|
||||
return task.status === filterBy;
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dueDate':
|
||||
if (!a.dueDate && !b.dueDate) return 0;
|
||||
if (!a.dueDate) return 1;
|
||||
if (!b.dueDate) return -1;
|
||||
return a.dueDate.getTime() - b.dueDate.getTime();
|
||||
|
||||
case 'priority':
|
||||
const priorityOrder = { high: 3, medium: 2, low: 1 };
|
||||
return priorityOrder[b.priority as keyof typeof priorityOrder] - priorityOrder[a.priority as keyof typeof priorityOrder];
|
||||
|
||||
case 'title':
|
||||
return a.title.localeCompare(b.title);
|
||||
|
||||
case 'status':
|
||||
const statusOrder = { todo: 1, inProgress: 2, done: 3 };
|
||||
return statusOrder[a.status as keyof typeof statusOrder] - statusOrder[b.status as keyof typeof statusOrder];
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
const getFilterCount = (filter: FilterOption) => {
|
||||
const weekTasks = tasks.filter(isInCurrentWeek);
|
||||
switch (filter) {
|
||||
case 'overdue':
|
||||
return weekTasks.filter(isOverdue).length;
|
||||
case 'all':
|
||||
return weekTasks.length;
|
||||
default:
|
||||
return weekTasks.filter(task => task.status === filter).length;
|
||||
}
|
||||
};
|
||||
|
||||
const getTasksForDate = (date: Date) => {
|
||||
return filteredAndSortedTasks.filter(task =>
|
||||
task.dueDate && isSameDay(task.dueDate, date)
|
||||
);
|
||||
};
|
||||
|
||||
const navigateCalendar = (direction: 'prev' | 'next') => {
|
||||
const newWeekStart = addWeeks(selectedWeekStart, direction === 'next' ? 1 : -1);
|
||||
setSelectedWeekStart(newWeekStart);
|
||||
console.log(`Navigating to week starting ${newWeekStart.toLocaleDateString()}`);
|
||||
};
|
||||
|
||||
const goToCurrentWeek = () => {
|
||||
setSelectedWeekStart(startOfWeek(new Date(), { weekStartsOn: 1 }));
|
||||
};
|
||||
|
||||
const handleTaskComplete = (task: Task) => {
|
||||
setCompletionModal({ isOpen: true, task });
|
||||
};
|
||||
|
||||
const handleTimeLogged = (timeSpent: number) => {
|
||||
if (completionModal.task) {
|
||||
onTaskUpdate?.(completionModal.task.id, {
|
||||
status: 'done',
|
||||
timeTracked: (completionModal.task.timeTracked || 0) + timeSpent,
|
||||
isTracking: false
|
||||
});
|
||||
}
|
||||
setCompletionModal({ isOpen: false, task: null });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pb-4">
|
||||
{/* Header and Search Filters */}
|
||||
<div className="sticky top-16 z-40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b pb-3 mb-4">
|
||||
<div className="space-y-3">
|
||||
{/* Title Bar */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold" data-testid="text-week-list-title">
|
||||
{t('weekList.title')}
|
||||
</h2>
|
||||
<Badge variant="secondary" data-testid="badge-week-task-count">
|
||||
{t('weekList.taskCount', { count: filteredAndSortedTasks.length })}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('weekList.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
setSearchQuery(e.target.value);
|
||||
console.log('Search query:', e.target.value);
|
||||
}}
|
||||
className="pl-10"
|
||||
data-testid="input-search-week-tasks"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Select value={filterBy} onValueChange={(value: FilterOption) => {
|
||||
setFilterBy(value);
|
||||
console.log('Filter changed to:', value);
|
||||
}}>
|
||||
<SelectTrigger className="flex-1" data-testid="select-filter-week">
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter className="w-4 h-4" />
|
||||
<SelectValue />
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">{t('weekList.filter.all')} ({getFilterCount('all')})</SelectItem>
|
||||
<SelectItem value="todo">{t('weekList.filter.todo')} ({getFilterCount('todo')})</SelectItem>
|
||||
<SelectItem value="inProgress">{t('weekList.filter.inProgress')} ({getFilterCount('inProgress')})</SelectItem>
|
||||
<SelectItem value="done">{t('weekList.filter.done')} ({getFilterCount('done')})</SelectItem>
|
||||
<SelectItem value="overdue">{t('weekList.filter.overdue')} ({getFilterCount('overdue')})</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={sortBy} onValueChange={(value: SortOption) => {
|
||||
setSortBy(value);
|
||||
console.log('Sort changed to:', value);
|
||||
}}>
|
||||
<SelectTrigger className="flex-1" data-testid="select-sort-week">
|
||||
<div className="flex items-center gap-2">
|
||||
<SortAsc className="w-4 h-4" />
|
||||
<SelectValue />
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="dueDate">{t('weekList.sort.dueDate')}</SelectItem>
|
||||
<SelectItem value="priority">{t('weekList.sort.priority')}</SelectItem>
|
||||
<SelectItem value="title">{t('weekList.sort.title')}</SelectItem>
|
||||
<SelectItem value="status">{t('weekList.sort.status')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Calendar Week Selector */}
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold" data-testid="text-week-range">
|
||||
{format(weekStart, 'MMM d')} - {format(weekEnd, 'MMM d, yyyy')}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigateCalendar('prev')}
|
||||
data-testid="button-prev-week-list"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
{t('weekList.previous')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={goToCurrentWeek}
|
||||
data-testid="button-today-week-list"
|
||||
>
|
||||
{t('weekList.today')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigateCalendar('next')}
|
||||
data-testid="button-next-week-list"
|
||||
>
|
||||
{t('weekList.next')}
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mini Calendar Bar */}
|
||||
<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(7, minmax(0, 1fr))' }}>
|
||||
{dates.map((date, index) => {
|
||||
const dayTasks = getTasksForDate(date);
|
||||
const isToday = isSameDay(date, new Date());
|
||||
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`p-2 rounded border-2 text-center ${
|
||||
isToday ? 'border-primary bg-primary/5' : isWeekend ? 'bg-muted/50' : 'border-border'
|
||||
}`}
|
||||
data-testid={`week-calendar-date-${format(date, 'yyyy-MM-dd')}`}
|
||||
>
|
||||
<div className={`text-xs font-medium ${isWeekend ? 'text-muted-foreground' : 'text-muted-foreground'}`}>
|
||||
{format(date, 'EEE')}
|
||||
</div>
|
||||
<div className={`text-sm font-semibold ${isToday ? 'text-primary' : ''}`}>
|
||||
{format(date, 'd')}
|
||||
</div>
|
||||
{dayTasks.length > 0 && (
|
||||
<Badge variant="secondary" className="mt-1 text-xs">
|
||||
{dayTasks.length}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Task List */}
|
||||
<div className="space-y-3">
|
||||
{filteredAndSortedTasks.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-muted-foreground" data-testid="text-no-week-tasks">
|
||||
{searchQuery || filterBy !== 'all'
|
||||
? t('weekList.noMatchingTasks')
|
||||
: t('weekList.noTasks')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-sm font-medium text-muted-foreground mb-2">
|
||||
{t('weekList.tasksFor', { start: format(weekStart, 'MMM d'), end: format(weekEnd, 'MMM d') })}
|
||||
</div>
|
||||
{filteredAndSortedTasks.map((task) => (
|
||||
<div key={task.id}>
|
||||
<TaskCard
|
||||
task={task}
|
||||
onStartTimer={() => onStartTimer?.(task.id)}
|
||||
onStopTimer={() => onStopTimer?.(task.id)}
|
||||
onEdit={() => {
|
||||
onTaskEdit?.(task);
|
||||
console.log(`Edit task ${task.title}`);
|
||||
}}
|
||||
onDelete={() => {
|
||||
onTaskDelete?.(task.id);
|
||||
console.log(`Delete task ${task.title}`);
|
||||
}}
|
||||
onStatusChange={(newStatus) => {
|
||||
if (newStatus === 'done') {
|
||||
handleTaskComplete(task);
|
||||
} else {
|
||||
onTaskUpdate?.(task.id, { status: newStatus });
|
||||
}
|
||||
console.log(`Task ${task.title} status changed to ${newStatus}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Time Completion Modal */}
|
||||
<TimeCompletionModal
|
||||
isOpen={completionModal.isOpen}
|
||||
onClose={() => setCompletionModal({ isOpen: false, task: null })}
|
||||
onSave={handleTimeLogged}
|
||||
taskTitle={completionModal.task?.title || ''}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
"navigation": {
|
||||
"tasks": "Aufgaben",
|
||||
"calendar": "Kalender",
|
||||
"weekList": "Woche",
|
||||
"create": "Erstellen",
|
||||
"kanban": "Kanban",
|
||||
"settings": "Einstellungen"
|
||||
@@ -30,6 +31,30 @@
|
||||
"status": "Status"
|
||||
}
|
||||
},
|
||||
"weekList": {
|
||||
"title": "Wochenaufgaben",
|
||||
"taskCount": "{{count}} Aufgaben",
|
||||
"searchPlaceholder": "Aufgaben suchen...",
|
||||
"noTasks": "Keine Aufgaben für diese Woche geplant",
|
||||
"noMatchingTasks": "Keine Aufgaben entsprechen deinen Filtern für diese Woche",
|
||||
"filter": {
|
||||
"all": "Alle",
|
||||
"todo": "Zu erledigen",
|
||||
"inProgress": "In Bearbeitung",
|
||||
"done": "Erledigt",
|
||||
"overdue": "Überfällig"
|
||||
},
|
||||
"sort": {
|
||||
"dueDate": "Fälligkeitsdatum",
|
||||
"priority": "Priorität",
|
||||
"title": "Titel",
|
||||
"status": "Status"
|
||||
},
|
||||
"previous": "Zurück",
|
||||
"today": "Heute",
|
||||
"next": "Weiter",
|
||||
"tasksFor": "Aufgaben für {{start}} - {{end}}"
|
||||
},
|
||||
"taskCreation": {
|
||||
"title": "Neue Aufgabe",
|
||||
"titlePlaceholder": "Was muss erledigt werden?",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"navigation": {
|
||||
"tasks": "Tasks",
|
||||
"calendar": "Calendar",
|
||||
"weekList": "Week",
|
||||
"create": "Create",
|
||||
"kanban": "Kanban",
|
||||
"settings": "Settings"
|
||||
@@ -30,6 +31,30 @@
|
||||
"status": "Status"
|
||||
}
|
||||
},
|
||||
"weekList": {
|
||||
"title": "Week Tasks",
|
||||
"taskCount": "{{count}} tasks",
|
||||
"searchPlaceholder": "Search tasks...",
|
||||
"noTasks": "No tasks scheduled for this week",
|
||||
"noMatchingTasks": "No tasks match your filters for this week",
|
||||
"filter": {
|
||||
"all": "All",
|
||||
"todo": "To Do",
|
||||
"inProgress": "In Progress",
|
||||
"done": "Done",
|
||||
"overdue": "Overdue"
|
||||
},
|
||||
"sort": {
|
||||
"dueDate": "Due Date",
|
||||
"priority": "Priority",
|
||||
"title": "Title",
|
||||
"status": "Status"
|
||||
},
|
||||
"previous": "Previous",
|
||||
"today": "Today",
|
||||
"next": "Next",
|
||||
"tasksFor": "Tasks for {{start}} - {{end}}"
|
||||
},
|
||||
"taskCreation": {
|
||||
"title": "New Task",
|
||||
"titlePlaceholder": "What needs to be done?",
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { Task } from '@shared/schema';
|
||||
import WeekListView from '@/components/WeekListView';
|
||||
import { queryClient } from '@/lib/queryClient';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useState } from 'react';
|
||||
import TaskDetailsModal from '@/components/TaskDetailsModal';
|
||||
|
||||
export default function WeekListPage() {
|
||||
const { toast } = useToast();
|
||||
const [editingTask, setEditingTask] = useState<Task | null>(null);
|
||||
|
||||
// Fetch all tasks
|
||||
const { data: tasks = [] } = useQuery<Task[]>({
|
||||
queryKey: ['/api/tasks'],
|
||||
});
|
||||
|
||||
// Update task mutation
|
||||
const updateTaskMutation = useMutation({
|
||||
mutationFn: async ({ taskId, updates }: { taskId: string; updates: Partial<Task> }) => {
|
||||
const response = await fetch(`/api/tasks/${taskId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates),
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to update task');
|
||||
return response.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/tasks'] });
|
||||
toast({
|
||||
title: 'Task updated',
|
||||
description: 'Task has been updated successfully',
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Error updating task:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to update task',
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Delete task mutation
|
||||
const deleteTaskMutation = useMutation({
|
||||
mutationFn: async (taskId: string) => {
|
||||
const response = await fetch(`/api/tasks/${taskId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to delete task');
|
||||
return response.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/tasks'] });
|
||||
toast({
|
||||
title: 'Task deleted',
|
||||
description: 'Task has been deleted successfully',
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Error deleting task:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to delete task',
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleTaskUpdate = (taskId: string, updates: Partial<Task>) => {
|
||||
updateTaskMutation.mutate({ taskId, updates });
|
||||
};
|
||||
|
||||
const handleTaskEdit = (task: Task) => {
|
||||
setEditingTask(task);
|
||||
};
|
||||
|
||||
const handleTaskDelete = (taskId: string) => {
|
||||
deleteTaskMutation.mutate(taskId);
|
||||
};
|
||||
|
||||
const handleStartTimer = (taskId: string) => {
|
||||
// Stop all other timers
|
||||
const runningTasks = tasks.filter(t => t.isTracking && t.id !== taskId);
|
||||
runningTasks.forEach(t => {
|
||||
updateTaskMutation.mutate({ taskId: t.id, updates: { isTracking: false } });
|
||||
});
|
||||
|
||||
// Start this timer
|
||||
updateTaskMutation.mutate({ taskId, updates: { isTracking: true } });
|
||||
};
|
||||
|
||||
const handleStopTimer = (taskId: string) => {
|
||||
updateTaskMutation.mutate({ taskId, updates: { isTracking: false } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-3 py-4 max-w-screen-2xl">
|
||||
<WeekListView
|
||||
tasks={tasks}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskEdit={handleTaskEdit}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
onStartTimer={handleStartTimer}
|
||||
onStopTimer={handleStopTimer}
|
||||
/>
|
||||
|
||||
{/* Task Edit Modal */}
|
||||
<TaskDetailsModal
|
||||
task={editingTask}
|
||||
isOpen={!!editingTask}
|
||||
onClose={() => setEditingTask(null)}
|
||||
onSave={(updatedTask: Task) => {
|
||||
if (editingTask) {
|
||||
handleTaskUpdate(editingTask.id, updatedTask);
|
||||
setEditingTask(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user