Add missing translations for task management features
Integrates i18next for internationalization, adding translated strings for task details, search, filters, and calendar views in both 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/E3NLe8e
This commit is contained in:
@@ -10,6 +10,7 @@ import { Separator } from '@/components/ui/separator';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Clock, Timer, FileText, Plus, Edit2, Save, X, Calendar, Tag } from 'lucide-react';
|
||||
import { Task, Label } from '@shared/schema';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface TimeEntry {
|
||||
id: string;
|
||||
@@ -33,6 +34,8 @@ export default function TaskDetailsModal({
|
||||
onSave,
|
||||
labels = []
|
||||
}: TaskDetailsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Edit form state
|
||||
const [editedTitle, setEditedTitle] = useState('');
|
||||
const [editedDescription, setEditedDescription] = useState('');
|
||||
@@ -73,7 +76,7 @@ export default function TaskDetailsModal({
|
||||
id: 'tracked-time',
|
||||
date: new Date(),
|
||||
timeSpent: task.timeTracked,
|
||||
description: 'Previously tracked time'
|
||||
description: t('taskDetails.previouslyTrackedTime')
|
||||
}]);
|
||||
} else {
|
||||
setTimeEntries([]);
|
||||
@@ -237,7 +240,7 @@ export default function TaskDetailsModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5" />
|
||||
Task Details
|
||||
{t('taskDetails.title')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -257,14 +260,14 @@ export default function TaskDetailsModal({
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="outline" className={`text-xs ${getPriorityColor(task.priority)}`}>
|
||||
{task.priority} priority
|
||||
{t(`taskDetails.priorityValue.${task.priority}`)}
|
||||
</Badge>
|
||||
<Badge variant="outline" className={`text-xs ${getStatusColor(task.status)}`}>
|
||||
{task.status}
|
||||
{t(`taskDetails.statusValue.${task.status}`)}
|
||||
</Badge>
|
||||
{getTotalTrackedTime() > 0 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{formatMinutesToHours(getTotalTrackedTime())} tracked
|
||||
{formatMinutesToHours(getTotalTrackedTime())} {t('taskDetails.tracked')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -275,41 +278,41 @@ export default function TaskDetailsModal({
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="edit" data-testid="tab-edit">
|
||||
<Edit2 className="w-4 h-4 mr-2" />
|
||||
Edit
|
||||
{t('taskDetails.editTab')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="notes" data-testid="tab-notes">
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
Notes
|
||||
{t('taskDetails.notesTab')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="time" data-testid="tab-time">
|
||||
<Clock className="w-4 h-4 mr-2" />
|
||||
Time Tracking
|
||||
{t('taskDetails.timeTab')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Edit Tab */}
|
||||
<TabsContent value="edit" className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<h4 className="font-medium mb-4">Edit Task</h4>
|
||||
<h4 className="font-medium mb-4">{t('taskDetails.editTask')}</h4>
|
||||
<div className="space-y-4">
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">Title</label>
|
||||
<label className="text-sm font-medium block mb-2">{t('taskDetails.titleLabel')}</label>
|
||||
<Input
|
||||
value={editedTitle}
|
||||
onChange={(e) => setEditedTitle(e.target.value)}
|
||||
placeholder="Task title"
|
||||
placeholder={t('taskDetails.titlePlaceholder')}
|
||||
data-testid="input-task-title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">Description</label>
|
||||
<label className="text-sm font-medium block mb-2">{t('taskDetails.description')}</label>
|
||||
<Textarea
|
||||
value={editedDescription}
|
||||
onChange={(e) => setEditedDescription(e.target.value)}
|
||||
placeholder="Task description"
|
||||
placeholder={t('taskDetails.descriptionPlaceholder')}
|
||||
rows={3}
|
||||
data-testid="input-task-description"
|
||||
/>
|
||||
@@ -318,29 +321,29 @@ export default function TaskDetailsModal({
|
||||
{/* Status and Priority */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">Status</label>
|
||||
<label className="text-sm font-medium block mb-2">{t('taskDetails.status')}</label>
|
||||
<Select value={editedStatus} onValueChange={(value) => setEditedStatus(value as 'todo' | 'inProgress' | 'done')}>
|
||||
<SelectTrigger data-testid="select-status">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="todo">To Do</SelectItem>
|
||||
<SelectItem value="inProgress">In Progress</SelectItem>
|
||||
<SelectItem value="done">Done</SelectItem>
|
||||
<SelectItem value="todo">{t('taskDetails.statusValue.todo')}</SelectItem>
|
||||
<SelectItem value="inProgress">{t('taskDetails.statusValue.inProgress')}</SelectItem>
|
||||
<SelectItem value="done">{t('taskDetails.statusValue.done')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">Priority</label>
|
||||
<label className="text-sm font-medium block mb-2">{t('taskDetails.priority')}</label>
|
||||
<Select value={editedPriority} onValueChange={(value) => setEditedPriority(value as 'low' | 'medium' | 'high')}>
|
||||
<SelectTrigger data-testid="select-priority">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Low</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="high">High</SelectItem>
|
||||
<SelectItem value="low">{t('taskDetails.priorityValue.low')}</SelectItem>
|
||||
<SelectItem value="medium">{t('taskDetails.priorityValue.medium')}</SelectItem>
|
||||
<SelectItem value="high">{t('taskDetails.priorityValue.high')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -348,13 +351,13 @@ export default function TaskDetailsModal({
|
||||
|
||||
{/* Label */}
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">Label</label>
|
||||
<label className="text-sm font-medium block mb-2">{t('taskDetails.label')}</label>
|
||||
<Select value={editedLabelId || 'none'} onValueChange={(value) => setEditedLabelId(value === 'none' ? null : value)}>
|
||||
<SelectTrigger data-testid="input-label-selector">
|
||||
<SelectValue placeholder="Select a label" />
|
||||
<SelectValue placeholder={t('taskDetails.selectLabel')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">No Label</SelectItem>
|
||||
<SelectItem value="none">{t('taskDetails.noLabel')}</SelectItem>
|
||||
{labels.map((label) => (
|
||||
<SelectItem key={label.id} value={label.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -372,7 +375,7 @@ export default function TaskDetailsModal({
|
||||
|
||||
{/* Due Date */}
|
||||
<div>
|
||||
<label className="text-sm font-medium block mb-2">Due Date</label>
|
||||
<label className="text-sm font-medium block mb-2">{t('taskDetails.dueDate')}</label>
|
||||
<Input
|
||||
type="date"
|
||||
value={editedDueDate}
|
||||
@@ -389,7 +392,7 @@ export default function TaskDetailsModal({
|
||||
data-testid="button-save-task-edit"
|
||||
>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
Save Changes
|
||||
{t('taskDetails.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,7 +403,7 @@ export default function TaskDetailsModal({
|
||||
<TabsContent value="notes" className="space-y-4">
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-medium">Task Notes</h4>
|
||||
<h4 className="font-medium">{t('taskDetails.taskNotes')}</h4>
|
||||
{!isEditingNotes && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -409,7 +412,7 @@ export default function TaskDetailsModal({
|
||||
data-testid="button-edit-notes"
|
||||
>
|
||||
<Edit2 className="w-4 h-4 mr-1" />
|
||||
Edit
|
||||
{t('taskDetails.edit')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -419,7 +422,7 @@ export default function TaskDetailsModal({
|
||||
<Textarea
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
placeholder="Add your notes for this task..."
|
||||
placeholder={t('taskDetails.notesPlaceholder')}
|
||||
rows={4}
|
||||
data-testid="textarea-task-notes"
|
||||
/>
|
||||
@@ -430,7 +433,7 @@ export default function TaskDetailsModal({
|
||||
data-testid="button-save-notes"
|
||||
>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
Save
|
||||
{t('taskDetails.save')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -442,7 +445,7 @@ export default function TaskDetailsModal({
|
||||
data-testid="button-cancel-notes"
|
||||
>
|
||||
<X className="w-4 h-4 mr-1" />
|
||||
Cancel
|
||||
{t('taskDetails.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -454,7 +457,7 @@ export default function TaskDetailsModal({
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground italic">
|
||||
No notes added yet. Click "Edit" to add notes.
|
||||
{t('taskDetails.noNotesYet')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -468,7 +471,7 @@ export default function TaskDetailsModal({
|
||||
<Card className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium">Total Time Tracked</h4>
|
||||
<h4 className="font-medium">{t('taskDetails.totalTimeTracked')}</h4>
|
||||
<p className="text-2xl font-mono text-primary" data-testid="text-total-time">
|
||||
{formatMinutesToHours(getTotalTrackedTime())}
|
||||
</p>
|
||||
@@ -479,7 +482,7 @@ export default function TaskDetailsModal({
|
||||
data-testid="button-add-time"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
Add Time
|
||||
{t('taskDetails.addTime')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -489,7 +492,7 @@ export default function TaskDetailsModal({
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-medium">Add Time Entry</h4>
|
||||
<h4 className="font-medium">{t('taskDetails.addTimeEntry')}</h4>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -504,11 +507,11 @@ export default function TaskDetailsModal({
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="clock" data-testid="tab-clock-picker">
|
||||
<Clock className="w-4 h-4 mr-2" />
|
||||
Clock
|
||||
{t('taskDetails.clock')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="manual" data-testid="tab-manual-input">
|
||||
<Timer className="w-4 h-4 mr-2" />
|
||||
Manual
|
||||
{t('taskDetails.manual')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
|
||||
@@ -219,16 +219,16 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
|
||||
{/* Title Bar */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold" data-testid="text-task-list-title">
|
||||
My Tasks
|
||||
{t('taskList.title')}
|
||||
</h2>
|
||||
<Badge variant="secondary" data-testid="badge-task-count">
|
||||
{filteredAndSortedTasks.length} tasks
|
||||
{t('taskList.taskCount', { count: filteredAndSortedTasks.length })}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search tasks..."
|
||||
placeholder={t('taskList.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
setSearchQuery(e.target.value);
|
||||
@@ -251,11 +251,11 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All ({getFilterCount('all')})</SelectItem>
|
||||
<SelectItem value="todo">To Do ({getFilterCount('todo')})</SelectItem>
|
||||
<SelectItem value="inProgress">In Progress ({getFilterCount('inProgress')})</SelectItem>
|
||||
<SelectItem value="done">Done ({getFilterCount('done')})</SelectItem>
|
||||
<SelectItem value="overdue">Overdue ({getFilterCount('overdue')})</SelectItem>
|
||||
<SelectItem value="all">{t('taskList.filter.all')} ({getFilterCount('all')})</SelectItem>
|
||||
<SelectItem value="todo">{t('taskList.filter.todo')} ({getFilterCount('todo')})</SelectItem>
|
||||
<SelectItem value="inProgress">{t('taskList.filter.inProgress')} ({getFilterCount('inProgress')})</SelectItem>
|
||||
<SelectItem value="done">{t('taskList.filter.done')} ({getFilterCount('done')})</SelectItem>
|
||||
<SelectItem value="overdue">{t('taskList.filter.overdue')} ({getFilterCount('overdue')})</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@@ -270,10 +270,10 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="dueDate">Due Date</SelectItem>
|
||||
<SelectItem value="priority">Priority</SelectItem>
|
||||
<SelectItem value="title">Title</SelectItem>
|
||||
<SelectItem value="status">Status</SelectItem>
|
||||
<SelectItem value="dueDate">{t('taskList.sort.dueDate')}</SelectItem>
|
||||
<SelectItem value="priority">{t('taskList.sort.priority')}</SelectItem>
|
||||
<SelectItem value="title">{t('taskList.sort.title')}</SelectItem>
|
||||
<SelectItem value="status">{t('taskList.sort.status')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -286,15 +286,15 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
|
||||
<div className="text-center py-8">
|
||||
<p className="text-muted-foreground" data-testid="text-no-tasks">
|
||||
{searchQuery || filterBy !== 'all'
|
||||
? 'No unscheduled tasks match your filters'
|
||||
: 'No unscheduled tasks. Drag tasks to the calendar below to schedule them!'
|
||||
? t('taskList.noMatchingTasks')
|
||||
: t('taskList.noUnscheduledTasks')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-sm font-medium text-muted-foreground mb-2">
|
||||
Unscheduled Tasks - Drag to calendar below to schedule
|
||||
{t('taskList.unscheduledTasksLabel')}
|
||||
</div>
|
||||
{unscheduledTasks.map((task) => (
|
||||
<div
|
||||
@@ -342,7 +342,7 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-primary" />
|
||||
<h3 className="text-sm font-semibold" data-testid="text-calendar-title">
|
||||
Next {visibleDays} Days
|
||||
{t('taskList.calendarTitle', { count: visibleDays })}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user