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 { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import BottomNavigation from './components/BottomNavigation';
|
import BottomNavigation from './components/BottomNavigation';
|
||||||
@@ -98,6 +108,7 @@ function App() {
|
|||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false);
|
const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false);
|
||||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||||
|
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Fetch tasks and labels from server on mount
|
// Fetch tasks and labels from server on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -229,6 +240,44 @@ function App() {
|
|||||||
console.log('Task details saved:', updatedTask.title);
|
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) => {
|
const handleCreateFromTemplate = (templateId: string, startDate: Date, projectName: string) => {
|
||||||
console.log('Creating project from template:', { templateId, startDate, projectName });
|
console.log('Creating project from template:', { templateId, startDate, projectName });
|
||||||
// In a real app, this would create multiple tasks based on the template
|
// In a real app, this would create multiple tasks based on the template
|
||||||
@@ -242,6 +291,7 @@ function App() {
|
|||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
onTaskUpdate={handleTaskUpdate}
|
onTaskUpdate={handleTaskUpdate}
|
||||||
onTaskEdit={handleTaskClick}
|
onTaskEdit={handleTaskClick}
|
||||||
|
onTaskDelete={handleTaskDelete}
|
||||||
onStartTimer={startTimer}
|
onStartTimer={startTimer}
|
||||||
onStopTimer={stopTimer}
|
onStopTimer={stopTimer}
|
||||||
/>
|
/>
|
||||||
@@ -264,6 +314,7 @@ function App() {
|
|||||||
onTaskStatusChange={handleTaskStatusChange}
|
onTaskStatusChange={handleTaskStatusChange}
|
||||||
onTaskUpdate={handleTaskUpdate}
|
onTaskUpdate={handleTaskUpdate}
|
||||||
onTaskClick={handleTaskClick}
|
onTaskClick={handleTaskClick}
|
||||||
|
onTaskDelete={handleTaskDelete}
|
||||||
onStartTimer={startTimer}
|
onStartTimer={startTimer}
|
||||||
onStopTimer={stopTimer}
|
onStopTimer={stopTimer}
|
||||||
/>
|
/>
|
||||||
@@ -354,6 +405,30 @@ function App() {
|
|||||||
onSave={handleTaskDetailsSave}
|
onSave={handleTaskDetailsSave}
|
||||||
labels={labels}
|
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>
|
</div>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -6,6 +7,7 @@ import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
|||||||
import { Task, Label } from '@shared/schema';
|
import { Task, Label } from '@shared/schema';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { addDays, format, isSameDay, startOfWeek, subWeeks } from 'date-fns';
|
import { addDays, format, isSameDay, startOfWeek, subWeeks } from 'date-fns';
|
||||||
|
import { de, enUS } from 'date-fns/locale';
|
||||||
|
|
||||||
interface CalendarViewProps {
|
interface CalendarViewProps {
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
@@ -15,9 +17,13 @@ interface CalendarViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSelect }: CalendarViewProps) {
|
export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSelect }: CalendarViewProps) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [currentDate, setCurrentDate] = useState(new Date());
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
const [draggedTask, setDraggedTask] = useState<string | null>(null);
|
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
|
// Fetch labels to get label colors
|
||||||
const { data: labels = [] } = useQuery<Label[]>({
|
const { data: labels = [] } = useQuery<Label[]>({
|
||||||
queryKey: ['/api/labels'],
|
queryKey: ['/api/labels'],
|
||||||
@@ -71,7 +77,7 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar className="w-4 h-4 sm:w-5 sm:h-5 text-primary" />
|
<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">
|
<h2 className="text-base sm:text-lg font-semibold" data-testid="text-calendar-title">
|
||||||
Calendar
|
{t('calendar.title')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -129,12 +135,12 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
|||||||
<div className={`text-xs font-medium ${
|
<div className={`text-xs font-medium ${
|
||||||
isPreviousWeek ? 'text-muted-foreground/60' : 'text-muted-foreground'
|
isPreviousWeek ? 'text-muted-foreground/60' : 'text-muted-foreground'
|
||||||
}`}>
|
}`}>
|
||||||
{format(date, 'EEE')}
|
{format(date, 'EEE', { locale: dateLocale })}
|
||||||
</div>
|
</div>
|
||||||
<div className={`text-sm font-semibold ${
|
<div className={`text-sm font-semibold ${
|
||||||
isToday ? 'text-primary' : isPreviousWeek ? 'text-muted-foreground/60' : ''
|
isToday ? 'text-primary' : isPreviousWeek ? 'text-muted-foreground/60' : ''
|
||||||
}`}>
|
}`}>
|
||||||
{format(date, 'd')}
|
{format(date, 'd', { locale: dateLocale })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -178,7 +184,7 @@ export default function CalendarView({ tasks, onTaskDrop, onTaskClick, onDateSel
|
|||||||
|
|
||||||
{dayTasks.length > 3 && (
|
{dayTasks.length > 3 && (
|
||||||
<Badge variant="secondary" className="w-full justify-center text-xs">
|
<Badge variant="secondary" className="w-full justify-center text-xs">
|
||||||
+{dayTasks.length - 3} more
|
{t('calendar.moreItems', { count: dayTasks.length - 3 })}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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-4 text-xs text-muted-foreground">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-primary"></div>
|
<div className="w-3 h-3 rounded-full bg-primary"></div>
|
||||||
<span>Today</span>
|
<span>{t('calendar.today')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-muted"></div>
|
<div className="w-3 h-3 rounded-full bg-muted"></div>
|
||||||
<span>Weekend</span>
|
<span>{t('calendar.weekend')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-muted opacity-50"></div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -22,6 +23,7 @@ import { Task, Label } from '@shared/schema';
|
|||||||
import TaskCard from './TaskCard';
|
import TaskCard from './TaskCard';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { format, startOfWeek, endOfWeek, startOfMonth, endOfMonth, addWeeks, addMonths, isWithinInterval } from 'date-fns';
|
import { format, startOfWeek, endOfWeek, startOfMonth, endOfMonth, addWeeks, addMonths, isWithinInterval } from 'date-fns';
|
||||||
|
import { de, enUS } from 'date-fns/locale';
|
||||||
|
|
||||||
interface KanbanBoardProps {
|
interface KanbanBoardProps {
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
@@ -36,6 +38,7 @@ interface KanbanBoardProps {
|
|||||||
type ViewMode = 'traditional' | 'weekly' | 'monthly';
|
type ViewMode = 'traditional' | 'weekly' | 'monthly';
|
||||||
|
|
||||||
export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, onTaskClick, onTaskDelete, onStartTimer, onStopTimer }: KanbanBoardProps) {
|
export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, onTaskClick, onTaskDelete, onStartTimer, onStopTimer }: KanbanBoardProps) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [viewMode, setViewMode] = useState<ViewMode>('traditional');
|
const [viewMode, setViewMode] = useState<ViewMode>('traditional');
|
||||||
const [currentDate, setCurrentDate] = useState(new Date());
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
const [draggedTask, setDraggedTask] = useState<string | null>(null);
|
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 [selectedLabels, setSelectedLabels] = useState<string[]>([]);
|
||||||
const [selectedPriorities, setSelectedPriorities] = 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
|
// Fetch labels to get label colors
|
||||||
const { data: labels = [] } = useQuery<Label[]>({
|
const { data: labels = [] } = useQuery<Label[]>({
|
||||||
queryKey: ['/api/labels'],
|
queryKey: ['/api/labels'],
|
||||||
@@ -52,9 +58,9 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
});
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ id: 'todo', title: 'To Do', status: 'todo' as const },
|
{ id: 'todo', title: t('kanban.todo'), status: 'todo' as const },
|
||||||
{ id: 'inProgress', title: 'In Progress', status: 'inProgress' as const },
|
{ id: 'inProgress', title: t('kanban.inProgress'), status: 'inProgress' as const },
|
||||||
{ id: 'done', title: 'Done', status: 'done' as const }
|
{ id: 'done', title: t('kanban.done'), status: 'done' as const }
|
||||||
];
|
];
|
||||||
|
|
||||||
const getTasksForColumn = (status: Task['status']) => {
|
const getTasksForColumn = (status: Task['status']) => {
|
||||||
@@ -124,11 +130,11 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
|
|
||||||
const getPeriodTitle = () => {
|
const getPeriodTitle = () => {
|
||||||
if (viewMode === 'weekly') {
|
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') {
|
} 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
|
// Filter management functions
|
||||||
@@ -174,7 +180,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LayoutGrid className="w-4 h-4 sm:w-5 sm:h-5 text-primary" />
|
<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">
|
<h2 className="text-base sm:text-lg font-semibold" data-testid="text-kanban-title">
|
||||||
Board
|
{t('kanban.title')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</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 justify-between flex-wrap gap-3 sm:gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Filter className="w-4 h-4 text-muted-foreground" />
|
<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 && (
|
{hasActiveFilters && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary" className="text-xs">
|
||||||
{selectedLabels.length + selectedPriorities.length}
|
{selectedLabels.length + selectedPriorities.length}
|
||||||
@@ -232,7 +238,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
data-testid="button-filter-labels"
|
data-testid="button-filter-labels"
|
||||||
>
|
>
|
||||||
<Tag className="w-4 h-4 mr-2" />
|
<Tag className="w-4 h-4 mr-2" />
|
||||||
Labels
|
{t('kanban.labels')}
|
||||||
{selectedLabels.length > 0 && (
|
{selectedLabels.length > 0 && (
|
||||||
<Badge variant="secondary" className="ml-2 text-xs">
|
<Badge variant="secondary" className="ml-2 text-xs">
|
||||||
{selectedLabels.length}
|
{selectedLabels.length}
|
||||||
@@ -242,7 +248,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-56" align="start">
|
<PopoverContent className="w-56" align="start">
|
||||||
<div className="space-y-2">
|
<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 />
|
<Separator />
|
||||||
<div className="space-y-2 max-h-48 overflow-y-auto">
|
<div className="space-y-2 max-h-48 overflow-y-auto">
|
||||||
{labels.length > 0 ? (
|
{labels.length > 0 ? (
|
||||||
@@ -270,7 +276,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,7 +295,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
data-testid="button-filter-priorities"
|
data-testid="button-filter-priorities"
|
||||||
>
|
>
|
||||||
<AlertTriangle className="w-4 h-4 mr-2" />
|
<AlertTriangle className="w-4 h-4 mr-2" />
|
||||||
Priorities
|
{t('kanban.priorities')}
|
||||||
{selectedPriorities.length > 0 && (
|
{selectedPriorities.length > 0 && (
|
||||||
<Badge variant="secondary" className="ml-2 text-xs">
|
<Badge variant="secondary" className="ml-2 text-xs">
|
||||||
{selectedPriorities.length}
|
{selectedPriorities.length}
|
||||||
@@ -299,7 +305,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-48" align="start">
|
<PopoverContent className="w-48" align="start">
|
||||||
<div className="space-y-2">
|
<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 />
|
<Separator />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{priorities.map((priority) => (
|
{priorities.map((priority) => (
|
||||||
@@ -321,7 +327,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={`text-xs ${getPriorityColor(priority)}`}
|
className={`text-xs ${getPriorityColor(priority)}`}
|
||||||
>
|
>
|
||||||
{priority}
|
{t(`priority.${priority}`)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -344,7 +350,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
className="text-muted-foreground hover:text-foreground"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4 mr-2" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
Clear All
|
{t('kanban.clearAll')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -355,7 +361,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
<div className="mt-3 pt-3 border-t">
|
<div className="mt-3 pt-3 border-t">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<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) => {
|
{selectedLabels.map((labelId) => {
|
||||||
const label = labels.find(l => l.id === labelId);
|
const label = labels.find(l => l.id === labelId);
|
||||||
return label ? (
|
return label ? (
|
||||||
@@ -384,7 +390,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
className={`text-xs ${getPriorityColor(priority)}`}
|
className={`text-xs ${getPriorityColor(priority)}`}
|
||||||
data-testid={`active-filter-priority-${priority}`}
|
data-testid={`active-filter-priority-${priority}`}
|
||||||
>
|
>
|
||||||
{priority}
|
{t(`priority.${priority}`)}
|
||||||
<X
|
<X
|
||||||
className="w-3 h-3 ml-1 cursor-pointer"
|
className="w-3 h-3 ml-1 cursor-pointer"
|
||||||
onClick={() => togglePriority(priority)}
|
onClick={() => togglePriority(priority)}
|
||||||
@@ -402,15 +408,15 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="traditional" data-testid="tab-traditional">
|
<TabsTrigger value="traditional" data-testid="tab-traditional">
|
||||||
<LayoutGrid className="w-4 h-4 mr-2" />
|
<LayoutGrid className="w-4 h-4 mr-2" />
|
||||||
Traditional
|
{t('kanban.traditional')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="weekly" data-testid="tab-weekly">
|
<TabsTrigger value="weekly" data-testid="tab-weekly">
|
||||||
<CalendarIcon className="w-4 h-4 mr-2" />
|
<CalendarIcon className="w-4 h-4 mr-2" />
|
||||||
Weekly
|
{t('kanban.weekly')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="monthly" data-testid="tab-monthly">
|
<TabsTrigger value="monthly" data-testid="tab-monthly">
|
||||||
<CalendarIcon className="w-4 h-4 mr-2" />
|
<CalendarIcon className="w-4 h-4 mr-2" />
|
||||||
Monthly
|
{t('kanban.monthly')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
@@ -469,7 +475,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
|
|
||||||
{columnTasks.length === 0 && (
|
{columnTasks.length === 0 && (
|
||||||
<div className="text-center text-muted-foreground text-sm py-8">
|
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -533,7 +539,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
|
|
||||||
{columnTasks.length === 0 && (
|
{columnTasks.length === 0 && (
|
||||||
<div className="text-center text-muted-foreground text-sm py-8">
|
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -597,7 +603,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
|
|
||||||
{columnTasks.length === 0 && (
|
{columnTasks.length === 0 && (
|
||||||
<div className="text-center text-muted-foreground text-sm py-8">
|
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -23,6 +24,7 @@ interface TaskCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDelete, onStatusChange, isDragging }: TaskCardProps) {
|
export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDelete, onStatusChange, isDragging }: TaskCardProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Fetch labels to get the label color
|
// Fetch labels to get the label color
|
||||||
const { data: labels = [] } = useQuery<Label[]>({
|
const { data: labels = [] } = useQuery<Label[]>({
|
||||||
@@ -105,7 +107,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
className={`text-xs ${getPriorityColor(task.priority)}`}
|
className={`text-xs ${getPriorityColor(task.priority)}`}
|
||||||
data-testid={`badge-priority-${task.id}`}
|
data-testid={`badge-priority-${task.id}`}
|
||||||
>
|
>
|
||||||
{task.priority}
|
{t(`priority.${task.priority}`)}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<Badge
|
<Badge
|
||||||
@@ -113,7 +115,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
className={`text-xs ${getStatusColor(task.status)}`}
|
className={`text-xs ${getStatusColor(task.status)}`}
|
||||||
data-testid={`badge-status-${task.id}`}
|
data-testid={`badge-status-${task.id}`}
|
||||||
>
|
>
|
||||||
{task.status.replace(/([A-Z])/g, ' $1').toLowerCase()}
|
{t(`status.${task.status}`)}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{task.dueDate && (
|
{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'}
|
className={task.isTracking ? 'text-primary font-medium' : 'text-muted-foreground'}
|
||||||
>
|
>
|
||||||
{task.timeTracked > 0 ? formatTime(task.timeTracked) : '0m'}
|
{task.timeTracked > 0 ? formatTime(task.timeTracked) : '0m'}
|
||||||
{task.isTracking && ' (running)'}
|
{task.isTracking && ` (${t('taskCard.running')})`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -163,7 +165,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-edit-${task.id}`}
|
data-testid={`menu-edit-${task.id}`}
|
||||||
>
|
>
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
Edit Task
|
{t('taskCard.editTask')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -174,7 +176,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-timer-${task.id}`}
|
data-testid={`menu-timer-${task.id}`}
|
||||||
>
|
>
|
||||||
<Timer className="w-4 h-4 mr-2" />
|
<Timer className="w-4 h-4 mr-2" />
|
||||||
{task.isTracking ? 'Stop Timer' : 'Start Timer'}
|
{task.isTracking ? t('taskCard.stopTimer') : t('taskCard.startTimer')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
@@ -189,7 +191,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-complete-${task.id}`}
|
data-testid={`menu-complete-${task.id}`}
|
||||||
>
|
>
|
||||||
<CheckCircle className="w-4 h-4 mr-2" />
|
<CheckCircle className="w-4 h-4 mr-2" />
|
||||||
Mark as Done
|
{t('taskCard.markAsDone')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -203,7 +205,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-start-${task.id}`}
|
data-testid={`menu-start-${task.id}`}
|
||||||
>
|
>
|
||||||
<Play className="w-4 h-4 mr-2" />
|
<Play className="w-4 h-4 mr-2" />
|
||||||
Start Working
|
{t('taskCard.startWorking')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -217,7 +219,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-todo-${task.id}`}
|
data-testid={`menu-todo-${task.id}`}
|
||||||
>
|
>
|
||||||
<Clock className="w-4 h-4 mr-2" />
|
<Clock className="w-4 h-4 mr-2" />
|
||||||
Move to Todo
|
{t('taskCard.moveToTodo')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -231,7 +233,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-reopen-${task.id}`}
|
data-testid={`menu-reopen-${task.id}`}
|
||||||
>
|
>
|
||||||
<Play className="w-4 h-4 mr-2" />
|
<Play className="w-4 h-4 mr-2" />
|
||||||
Reopen Task
|
{t('taskCard.reopenTask')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -248,7 +250,7 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
|||||||
data-testid={`menu-delete-${task.id}`}
|
data-testid={`menu-delete-${task.id}`}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
Delete Task
|
{t('taskCard.deleteTask')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface TasksWithCalendarProps {
|
|||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
||||||
onTaskEdit?: (task: Task) => void;
|
onTaskEdit?: (task: Task) => void;
|
||||||
|
onTaskDelete?: (taskId: string) => void;
|
||||||
onStartTimer?: (taskId: string) => void;
|
onStartTimer?: (taskId: string) => void;
|
||||||
onStopTimer?: (taskId: string) => void;
|
onStopTimer?: (taskId: string) => void;
|
||||||
}
|
}
|
||||||
@@ -22,7 +23,7 @@ interface TasksWithCalendarProps {
|
|||||||
type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
|
type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
|
||||||
type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue';
|
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 [searchQuery, setSearchQuery] = useState('');
|
||||||
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
||||||
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
||||||
@@ -304,6 +305,10 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onS
|
|||||||
onTaskEdit?.(task);
|
onTaskEdit?.(task);
|
||||||
console.log(`Edit task ${task.title}`);
|
console.log(`Edit task ${task.title}`);
|
||||||
}}
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
onTaskDelete?.(task.id);
|
||||||
|
console.log(`Delete task ${task.title}`);
|
||||||
|
}}
|
||||||
onStatusChange={(newStatus) => {
|
onStatusChange={(newStatus) => {
|
||||||
if (newStatus === 'done') {
|
if (newStatus === 'done') {
|
||||||
handleTaskComplete(task);
|
handleTaskComplete(task);
|
||||||
|
|||||||
@@ -62,22 +62,50 @@
|
|||||||
"play": "Timer starten",
|
"play": "Timer starten",
|
||||||
"pause": "Timer pausieren",
|
"pause": "Timer pausieren",
|
||||||
"edit": "Aufgabe bearbeiten",
|
"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",
|
"overdue": "Überfällig",
|
||||||
"timeTracked": "{{hours}}Std {{minutes}}Min erfasst"
|
"timeTracked": "{{hours}}Std {{minutes}}Min erfasst"
|
||||||
},
|
},
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Kalenderansicht",
|
"title": "Kalender",
|
||||||
"today": "Heute",
|
"today": "Heute",
|
||||||
|
"weekend": "Wochenende",
|
||||||
|
"previousWeek": "Vorherige Woche",
|
||||||
|
"moreItems": "+{{count}} weitere",
|
||||||
"tasksDue": "{{count}} Aufgabe fällig",
|
"tasksDue": "{{count}} Aufgabe fällig",
|
||||||
"tasksDue_plural": "{{count}} Aufgaben fällig"
|
"tasksDue_plural": "{{count}} Aufgaben fällig"
|
||||||
},
|
},
|
||||||
"kanban": {
|
"kanban": {
|
||||||
"title": "Kanban-Board",
|
"title": "Board",
|
||||||
"todo": "Zu erledigen",
|
"todo": "Zu erledigen",
|
||||||
"inProgress": "In Bearbeitung",
|
"inProgress": "In Bearbeitung",
|
||||||
"done": "Erledigt",
|
"done": "Erledigt",
|
||||||
"taskCount": "{{count}} Aufgabe",
|
"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": {
|
"settings": {
|
||||||
"title": "Einstellungen",
|
"title": "Einstellungen",
|
||||||
@@ -145,5 +173,11 @@
|
|||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"loading": "Laden...",
|
"loading": "Laden...",
|
||||||
"error": "Ein Fehler ist aufgetreten"
|
"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",
|
"play": "Start timer",
|
||||||
"pause": "Pause timer",
|
"pause": "Pause timer",
|
||||||
"edit": "Edit task",
|
"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",
|
"overdue": "Overdue",
|
||||||
"timeTracked": "{{hours}}h {{minutes}}m tracked"
|
"timeTracked": "{{hours}}h {{minutes}}m tracked"
|
||||||
},
|
},
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Calendar View",
|
"title": "Calendar",
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
|
"weekend": "Weekend",
|
||||||
|
"previousWeek": "Previous Week",
|
||||||
|
"moreItems": "+{{count}} more",
|
||||||
"tasksDue": "{{count}} task due",
|
"tasksDue": "{{count}} task due",
|
||||||
"tasksDue_plural": "{{count}} tasks due"
|
"tasksDue_plural": "{{count}} tasks due"
|
||||||
},
|
},
|
||||||
"kanban": {
|
"kanban": {
|
||||||
"title": "Kanban Board",
|
"title": "Board",
|
||||||
"todo": "To Do",
|
"todo": "To Do",
|
||||||
"inProgress": "In Progress",
|
"inProgress": "In Progress",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"taskCount": "{{count}} task",
|
"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": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
@@ -145,5 +173,11 @@
|
|||||||
"close": "Close",
|
"close": "Close",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"error": "An error occurred"
|
"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