Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b21d13d580 | |||
| 814163b07f | |||
| 8fe1c66d70 |
@@ -6,6 +6,16 @@ import { Toaster } from "@/components/ui/toaster";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
|
||||
// Components
|
||||
import BottomNavigation from './components/BottomNavigation';
|
||||
@@ -98,6 +108,7 @@ function App() {
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false);
|
||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null);
|
||||
|
||||
// Fetch tasks and labels from server on mount
|
||||
useEffect(() => {
|
||||
@@ -229,6 +240,44 @@ function App() {
|
||||
console.log('Task details saved:', updatedTask.title);
|
||||
};
|
||||
|
||||
const handleTaskDelete = async (taskId: string) => {
|
||||
setDeleteTaskId(taskId);
|
||||
};
|
||||
|
||||
const confirmTaskDelete = async () => {
|
||||
if (!deleteTaskId) return;
|
||||
|
||||
// Optimistic deletion - remove from UI immediately
|
||||
const taskToDelete = tasks.find(task => task.id === deleteTaskId);
|
||||
setTasks(prev => prev.filter(task => task.id !== deleteTaskId));
|
||||
|
||||
// Close task details modal if the deleted task was open
|
||||
if (selectedTask?.id === deleteTaskId) {
|
||||
handleTaskDetailsClose();
|
||||
}
|
||||
|
||||
setDeleteTaskId(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/tasks/${deleteTaskId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
// 404 is acceptable - task doesn't exist on backend (seeded task)
|
||||
if (!response.ok && response.status !== 404) {
|
||||
throw new Error('Failed to delete task');
|
||||
}
|
||||
|
||||
console.log('Task deleted:', deleteTaskId);
|
||||
} catch (error) {
|
||||
console.error('Error deleting task:', error);
|
||||
// Restore task on error
|
||||
if (taskToDelete) {
|
||||
setTasks(prev => [...prev, taskToDelete]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateFromTemplate = (templateId: string, startDate: Date, projectName: string) => {
|
||||
console.log('Creating project from template:', { templateId, startDate, projectName });
|
||||
// In a real app, this would create multiple tasks based on the template
|
||||
@@ -242,6 +291,7 @@ function App() {
|
||||
tasks={tasks}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskEdit={handleTaskClick}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
onStartTimer={startTimer}
|
||||
onStopTimer={stopTimer}
|
||||
/>
|
||||
@@ -264,6 +314,7 @@ function App() {
|
||||
onTaskStatusChange={handleTaskStatusChange}
|
||||
onTaskUpdate={handleTaskUpdate}
|
||||
onTaskClick={handleTaskClick}
|
||||
onTaskDelete={handleTaskDelete}
|
||||
onStartTimer={startTimer}
|
||||
onStopTimer={stopTimer}
|
||||
/>
|
||||
@@ -354,6 +405,30 @@ function App() {
|
||||
onSave={handleTaskDetailsSave}
|
||||
labels={labels}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<AlertDialog open={deleteTaskId !== null} onOpenChange={(open) => !open && setDeleteTaskId(null)}>
|
||||
<AlertDialogContent data-testid="dialog-delete-confirmation">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t('deleteConfirmation.title')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('deleteConfirmation.description')}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel data-testid="button-cancel-delete">
|
||||
{t('deleteConfirmation.cancel')}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmTaskDelete}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
data-testid="button-confirm-delete"
|
||||
>
|
||||
{t('deleteConfirmation.confirm')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
|
||||
<Toaster />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -6,6 +7,7 @@ import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
||||
import { Task, Label } from '@shared/schema';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { addDays, format, isSameDay, startOfWeek, subWeeks } from 'date-fns';
|
||||
import { de, enUS } from 'date-fns/locale';
|
||||
|
||||
interface CalendarViewProps {
|
||||
tasks: Task[];
|
||||
@@ -15,9 +17,13 @@ interface CalendarViewProps {
|
||||
}
|
||||
|
||||
export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSelect }: CalendarViewProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [draggedTask, setDraggedTask] = useState<string | null>(null);
|
||||
|
||||
// Get date-fns locale based on current language
|
||||
const dateLocale = i18n.language === 'de' ? de : enUS;
|
||||
|
||||
// Fetch labels to get label colors
|
||||
const { data: labels = [] } = useQuery<Label[]>({
|
||||
queryKey: ['/api/labels'],
|
||||
@@ -71,7 +77,7 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 sm:w-5 sm:h-5 text-primary" />
|
||||
<h2 className="text-base sm:text-lg font-semibold" data-testid="text-calendar-title">
|
||||
Calendar
|
||||
{t('calendar.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -129,12 +135,12 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
||||
<div className={`text-xs font-medium ${
|
||||
isPreviousWeek ? 'text-muted-foreground/60' : 'text-muted-foreground'
|
||||
}`}>
|
||||
{format(date, 'EEE')}
|
||||
{format(date, 'EEE', { locale: dateLocale })}
|
||||
</div>
|
||||
<div className={`text-sm font-semibold ${
|
||||
isToday ? 'text-primary' : isPreviousWeek ? 'text-muted-foreground/60' : ''
|
||||
}`}>
|
||||
{format(date, 'd')}
|
||||
{format(date, 'd', { locale: dateLocale })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -178,7 +184,7 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
||||
|
||||
{dayTasks.length > 3 && (
|
||||
<Badge variant="secondary" className="w-full justify-center text-xs">
|
||||
+{dayTasks.length - 3} more
|
||||
{t('calendar.moreItems', { count: dayTasks.length - 3 })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -191,15 +197,15 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-primary"></div>
|
||||
<span>Today</span>
|
||||
<span>{t('calendar.today')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-muted"></div>
|
||||
<span>Weekend</span>
|
||||
<span>{t('calendar.weekend')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-muted opacity-50"></div>
|
||||
<span>Previous Week</span>
|
||||
<span>{t('calendar.previousWeek')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -22,6 +23,7 @@ import { Task, Label } from '@shared/schema';
|
||||
import TaskCard from './TaskCard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { format, startOfWeek, endOfWeek, startOfMonth, endOfMonth, addWeeks, addMonths, isWithinInterval } from 'date-fns';
|
||||
import { de, enUS } from 'date-fns/locale';
|
||||
|
||||
interface KanbanBoardProps {
|
||||
tasks: Task[];
|
||||
@@ -36,6 +38,7 @@ interface KanbanBoardProps {
|
||||
type ViewMode = 'traditional' | 'weekly' | 'monthly';
|
||||
|
||||
export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, onTaskClick, onTaskDelete, onStartTimer, onStopTimer }: KanbanBoardProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('traditional');
|
||||
const [currentDate, setCurrentDate] = useState(new Date());
|
||||
const [draggedTask, setDraggedTask] = useState<string | null>(null);
|
||||
@@ -44,6 +47,9 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
const [selectedLabels, setSelectedLabels] = useState<string[]>([]);
|
||||
const [selectedPriorities, setSelectedPriorities] = useState<string[]>([]);
|
||||
|
||||
// Get date-fns locale based on current language
|
||||
const dateLocale = i18n.language === 'de' ? de : enUS;
|
||||
|
||||
// Fetch labels to get label colors
|
||||
const { data: labels = [] } = useQuery<Label[]>({
|
||||
queryKey: ['/api/labels'],
|
||||
@@ -52,9 +58,9 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ id: 'todo', title: 'To Do', status: 'todo' as const },
|
||||
{ id: 'inProgress', title: 'In Progress', status: 'inProgress' as const },
|
||||
{ id: 'done', title: 'Done', status: 'done' as const }
|
||||
{ id: 'todo', title: t('kanban.todo'), status: 'todo' as const },
|
||||
{ id: 'inProgress', title: t('kanban.inProgress'), status: 'inProgress' as const },
|
||||
{ id: 'done', title: t('kanban.done'), status: 'done' as const }
|
||||
];
|
||||
|
||||
const getTasksForColumn = (status: Task['status']) => {
|
||||
@@ -124,11 +130,11 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
|
||||
const getPeriodTitle = () => {
|
||||
if (viewMode === 'weekly') {
|
||||
return `Week of ${format(startOfWeek(currentDate, { weekStartsOn: 1 }), 'MMM d')}`;
|
||||
return t('kanban.weekOf', { date: format(startOfWeek(currentDate, { weekStartsOn: 1 }), 'MMM d', { locale: dateLocale }) });
|
||||
} else if (viewMode === 'monthly') {
|
||||
return format(currentDate, 'MMMM yyyy');
|
||||
return format(currentDate, 'MMMM yyyy', { locale: dateLocale });
|
||||
}
|
||||
return 'All Tasks';
|
||||
return t('kanban.allTasks');
|
||||
};
|
||||
|
||||
// Filter management functions
|
||||
@@ -174,7 +180,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="w-4 h-4 sm:w-5 sm:h-5 text-primary" />
|
||||
<h2 className="text-base sm:text-lg font-semibold" data-testid="text-kanban-title">
|
||||
Board
|
||||
{t('kanban.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -212,7 +218,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
<div className="flex items-center justify-between flex-wrap gap-3 sm:gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter className="w-4 h-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Filters</span>
|
||||
<span className="text-sm font-medium">{t('kanban.filters')}</span>
|
||||
{hasActiveFilters && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{selectedLabels.length + selectedPriorities.length}
|
||||
@@ -232,7 +238,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
data-testid="button-filter-labels"
|
||||
>
|
||||
<Tag className="w-4 h-4 mr-2" />
|
||||
Labels
|
||||
{t('kanban.labels')}
|
||||
{selectedLabels.length > 0 && (
|
||||
<Badge variant="secondary" className="ml-2 text-xs">
|
||||
{selectedLabels.length}
|
||||
@@ -242,7 +248,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-56" align="start">
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium">Filter by Labels</h4>
|
||||
<h4 className="text-sm font-medium">{t('kanban.filterByLabels')}</h4>
|
||||
<Separator />
|
||||
<div className="space-y-2 max-h-48 overflow-y-auto">
|
||||
{labels.length > 0 ? (
|
||||
@@ -270,7 +276,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No labels available</p>
|
||||
<p className="text-sm text-muted-foreground">{t('kanban.noLabelsAvailable')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -289,7 +295,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
data-testid="button-filter-priorities"
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4 mr-2" />
|
||||
Priorities
|
||||
{t('kanban.priorities')}
|
||||
{selectedPriorities.length > 0 && (
|
||||
<Badge variant="secondary" className="ml-2 text-xs">
|
||||
{selectedPriorities.length}
|
||||
@@ -299,7 +305,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-48" align="start">
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium">Filter by Priority</h4>
|
||||
<h4 className="text-sm font-medium">{t('kanban.filterByPriority')}</h4>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
{priorities.map((priority) => (
|
||||
@@ -321,7 +327,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
variant="secondary"
|
||||
className={`text-xs ${getPriorityColor(priority)}`}
|
||||
>
|
||||
{priority}
|
||||
{t(`priority.${priority}`)}
|
||||
</Badge>
|
||||
</label>
|
||||
</div>
|
||||
@@ -344,7 +350,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
Clear All
|
||||
{t('kanban.clearAll')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -355,7 +361,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
{hasActiveFilters && (
|
||||
<div className="mt-3 pt-3 border-t">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs text-muted-foreground">Active filters:</span>
|
||||
<span className="text-xs text-muted-foreground">{t('kanban.activeFilters')}</span>
|
||||
{selectedLabels.map((labelId) => {
|
||||
const label = labels.find(l => l.id === labelId);
|
||||
return label ? (
|
||||
@@ -384,7 +390,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
className={`text-xs ${getPriorityColor(priority)}`}
|
||||
data-testid={`active-filter-priority-${priority}`}
|
||||
>
|
||||
{priority}
|
||||
{t(`priority.${priority}`)}
|
||||
<X
|
||||
className="w-3 h-3 ml-1 cursor-pointer"
|
||||
onClick={() => togglePriority(priority)}
|
||||
@@ -402,15 +408,15 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="traditional" data-testid="tab-traditional">
|
||||
<LayoutGrid className="w-4 h-4 mr-2" />
|
||||
Traditional
|
||||
{t('kanban.traditional')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="weekly" data-testid="tab-weekly">
|
||||
<CalendarIcon className="w-4 h-4 mr-2" />
|
||||
Weekly
|
||||
{t('kanban.weekly')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="monthly" data-testid="tab-monthly">
|
||||
<CalendarIcon className="w-4 h-4 mr-2" />
|
||||
Monthly
|
||||
{t('kanban.monthly')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -469,7 +475,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
|
||||
{columnTasks.length === 0 && (
|
||||
<div className="text-center text-muted-foreground text-sm py-8">
|
||||
No tasks in {column.title.toLowerCase()}
|
||||
{t('kanban.noTasksInColumn', { column: column.title.toLowerCase() })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -533,7 +539,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
|
||||
{columnTasks.length === 0 && (
|
||||
<div className="text-center text-muted-foreground text-sm py-8">
|
||||
No tasks in {column.title.toLowerCase()} for this week
|
||||
{t('kanban.noTasksInColumnForWeek', { column: column.title.toLowerCase() })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -597,7 +603,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
||||
|
||||
{columnTasks.length === 0 && (
|
||||
<div className="text-center text-muted-foreground text-sm py-8">
|
||||
No tasks in {column.title.toLowerCase()} for this month
|
||||
{t('kanban.noTasksInColumnForMonth', { column: column.title.toLowerCase() })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -23,6 +24,7 @@ interface TaskCardProps {
|
||||
}
|
||||
|
||||
export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDelete, onStatusChange, isDragging }: TaskCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Fetch labels to get the label color
|
||||
const { data: labels = [] } = useQuery<Label[]>({
|
||||
@@ -105,7 +107,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
className={`text-xs ${getPriorityColor(task.priority)}`}
|
||||
data-testid={`badge-priority-${task.id}`}
|
||||
>
|
||||
{task.priority}
|
||||
{t(`priority.${task.priority}`)}
|
||||
</Badge>
|
||||
|
||||
<Badge
|
||||
@@ -113,7 +115,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
className={`text-xs ${getStatusColor(task.status)}`}
|
||||
data-testid={`badge-status-${task.id}`}
|
||||
>
|
||||
{task.status.replace(/([A-Z])/g, ' $1').toLowerCase()}
|
||||
{t(`status.${task.status}`)}
|
||||
</Badge>
|
||||
|
||||
{task.dueDate && (
|
||||
@@ -134,7 +136,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
className={task.isTracking ? 'text-primary font-medium' : 'text-muted-foreground'}
|
||||
>
|
||||
{task.timeTracked > 0 ? formatTime(task.timeTracked) : '0m'}
|
||||
{task.isTracking && ' (running)'}
|
||||
{task.isTracking && ` (${t('taskCard.running')})`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -163,7 +165,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-edit-${task.id}`}
|
||||
>
|
||||
<Edit className="w-4 h-4 mr-2" />
|
||||
Edit Task
|
||||
{t('taskCard.editTask')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
@@ -174,7 +176,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-timer-${task.id}`}
|
||||
>
|
||||
<Timer className="w-4 h-4 mr-2" />
|
||||
{task.isTracking ? 'Stop Timer' : 'Start Timer'}
|
||||
{task.isTracking ? t('taskCard.stopTimer') : t('taskCard.startTimer')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
@@ -189,7 +191,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-complete-${task.id}`}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Mark as Done
|
||||
{t('taskCard.markAsDone')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@@ -203,7 +205,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-start-${task.id}`}
|
||||
>
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
Start Working
|
||||
{t('taskCard.startWorking')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@@ -217,7 +219,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-todo-${task.id}`}
|
||||
>
|
||||
<Clock className="w-4 h-4 mr-2" />
|
||||
Move to Todo
|
||||
{t('taskCard.moveToTodo')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@@ -231,7 +233,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-reopen-${task.id}`}
|
||||
>
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
Reopen Task
|
||||
{t('taskCard.reopenTask')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@@ -248,7 +250,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
data-testid={`menu-delete-${task.id}`}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
Delete Task
|
||||
{t('taskCard.deleteTask')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -15,6 +15,7 @@ interface TasksWithCalendarProps {
|
||||
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;
|
||||
}
|
||||
@@ -22,7 +23,7 @@ interface TasksWithCalendarProps {
|
||||
type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
|
||||
type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue';
|
||||
|
||||
export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onStartTimer, onStopTimer }: TasksWithCalendarProps) {
|
||||
export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onTaskDelete, onStartTimer, onStopTimer }: TasksWithCalendarProps) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
||||
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
||||
@@ -304,6 +305,10 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onS
|
||||
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);
|
||||
|
||||
@@ -62,22 +62,50 @@
|
||||
"play": "Timer starten",
|
||||
"pause": "Timer pausieren",
|
||||
"edit": "Aufgabe bearbeiten",
|
||||
"editTask": "Aufgabe bearbeiten",
|
||||
"stopTimer": "Timer stoppen",
|
||||
"startTimer": "Timer starten",
|
||||
"markAsDone": "Als erledigt markieren",
|
||||
"startWorking": "Mit Arbeit beginnen",
|
||||
"moveToTodo": "Zu erledigen verschieben",
|
||||
"reopenTask": "Aufgabe erneut öffnen",
|
||||
"deleteTask": "Aufgabe löschen",
|
||||
"running": "läuft",
|
||||
"overdue": "Überfällig",
|
||||
"timeTracked": "{{hours}}Std {{minutes}}Min erfasst"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalenderansicht",
|
||||
"title": "Kalender",
|
||||
"today": "Heute",
|
||||
"weekend": "Wochenende",
|
||||
"previousWeek": "Vorherige Woche",
|
||||
"moreItems": "+{{count}} weitere",
|
||||
"tasksDue": "{{count}} Aufgabe fällig",
|
||||
"tasksDue_plural": "{{count}} Aufgaben fällig"
|
||||
},
|
||||
"kanban": {
|
||||
"title": "Kanban-Board",
|
||||
"title": "Board",
|
||||
"todo": "Zu erledigen",
|
||||
"inProgress": "In Bearbeitung",
|
||||
"done": "Erledigt",
|
||||
"taskCount": "{{count}} Aufgabe",
|
||||
"taskCount_plural": "{{count}} Aufgaben"
|
||||
"taskCount_plural": "{{count}} Aufgaben",
|
||||
"filters": "Filter",
|
||||
"labels": "Labels",
|
||||
"priorities": "Prioritäten",
|
||||
"filterByLabels": "Nach Labels filtern",
|
||||
"filterByPriority": "Nach Priorität filtern",
|
||||
"clearAll": "Alle löschen",
|
||||
"activeFilters": "Aktive Filter:",
|
||||
"noLabelsAvailable": "Keine Labels verfügbar",
|
||||
"traditional": "Traditionell",
|
||||
"weekly": "Wöchentlich",
|
||||
"monthly": "Monatlich",
|
||||
"allTasks": "Alle Aufgaben",
|
||||
"weekOf": "Woche vom {{date}}",
|
||||
"noTasksInColumn": "Keine Aufgaben in {{column}}",
|
||||
"noTasksInColumnForWeek": "Keine Aufgaben in {{column}} für diese Woche",
|
||||
"noTasksInColumnForMonth": "Keine Aufgaben in {{column}} für diesen Monat"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
@@ -145,5 +173,11 @@
|
||||
"close": "Schließen",
|
||||
"loading": "Laden...",
|
||||
"error": "Ein Fehler ist aufgetreten"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"title": "Aufgabe löschen",
|
||||
"description": "Möchten Sie diese Aufgabe wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Löschen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,22 +62,50 @@
|
||||
"play": "Start timer",
|
||||
"pause": "Pause timer",
|
||||
"edit": "Edit task",
|
||||
"editTask": "Edit Task",
|
||||
"stopTimer": "Stop Timer",
|
||||
"startTimer": "Start Timer",
|
||||
"markAsDone": "Mark as Done",
|
||||
"startWorking": "Start Working",
|
||||
"moveToTodo": "Move to Todo",
|
||||
"reopenTask": "Reopen Task",
|
||||
"deleteTask": "Delete Task",
|
||||
"running": "running",
|
||||
"overdue": "Overdue",
|
||||
"timeTracked": "{{hours}}h {{minutes}}m tracked"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendar View",
|
||||
"title": "Calendar",
|
||||
"today": "Today",
|
||||
"weekend": "Weekend",
|
||||
"previousWeek": "Previous Week",
|
||||
"moreItems": "+{{count}} more",
|
||||
"tasksDue": "{{count}} task due",
|
||||
"tasksDue_plural": "{{count}} tasks due"
|
||||
},
|
||||
"kanban": {
|
||||
"title": "Kanban Board",
|
||||
"title": "Board",
|
||||
"todo": "To Do",
|
||||
"inProgress": "In Progress",
|
||||
"done": "Done",
|
||||
"taskCount": "{{count}} task",
|
||||
"taskCount_plural": "{{count}} tasks"
|
||||
"taskCount_plural": "{{count}} tasks",
|
||||
"filters": "Filters",
|
||||
"labels": "Labels",
|
||||
"priorities": "Priorities",
|
||||
"filterByLabels": "Filter by Labels",
|
||||
"filterByPriority": "Filter by Priority",
|
||||
"clearAll": "Clear All",
|
||||
"activeFilters": "Active filters:",
|
||||
"noLabelsAvailable": "No labels available",
|
||||
"traditional": "Traditional",
|
||||
"weekly": "Weekly",
|
||||
"monthly": "Monthly",
|
||||
"allTasks": "All Tasks",
|
||||
"weekOf": "Week of {{date}}",
|
||||
"noTasksInColumn": "No tasks in {{column}}",
|
||||
"noTasksInColumnForWeek": "No tasks in {{column}} for this week",
|
||||
"noTasksInColumnForMonth": "No tasks in {{column}} for this month"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@@ -145,5 +173,11 @@
|
||||
"close": "Close",
|
||||
"loading": "Loading...",
|
||||
"error": "An error occurred"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"title": "Delete Task",
|
||||
"description": "Are you sure you want to delete this task? This action cannot be undone.",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Delete"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user