Files
task-manager/client/src/components/TaskCreationModal.tsx
T
paul-nothaft 32d3126dc9 Add support for multiple languages in the application interface
Integrates `react-i18next` to enable language translations for UI elements across the BottomNavigation, TaskCreationModal, and TaskList components.

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/un1X8jl
2025-10-23 20:56:05 +00:00

194 lines
6.6 KiB
TypeScript

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { CalendarIcon, Plus, Tag } from 'lucide-react';
import { Task, Label } from '@shared/schema';
import { useQuery } from '@tanstack/react-query';
interface TaskCreationModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (task: Partial<Task>) => void;
}
export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreationModalProps) {
const { t } = useTranslation();
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium');
const [dueDate, setDueDate] = useState<Date | undefined>();
const [labelId, setLabelId] = useState<string | undefined>();
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
// Fetch labels
const { data: labels = [] } = useQuery<Label[]>({
queryKey: ['/api/labels'],
});
const handleSave = () => {
if (!title.trim()) return;
const newTask: Partial<Task> = {
title: title.trim(),
description: description.trim() || undefined,
priority,
dueDate,
labelId,
status: 'todo',
timeTracked: 0,
isTracking: false
};
onSave(newTask);
console.log('New task created:', newTask);
// Reset form
setTitle('');
setDescription('');
setPriority('medium');
setDueDate(undefined);
setLabelId(undefined);
onClose();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSave();
}
};
const handleClose = () => {
setTitle('');
setDescription('');
setPriority('medium');
setDueDate(undefined);
setLabelId(undefined);
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-md mx-4">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Plus className="w-4 h-4" />
{t('taskCreation.title')}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Input
placeholder={t('taskCreation.titlePlaceholder')}
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={handleKeyDown}
className="text-base"
data-testid="input-task-title"
autoFocus
/>
</div>
<div>
<Textarea
placeholder={t('taskCreation.descriptionPlaceholder')}
value={description}
onChange={(e) => setDescription(e.target.value)}
className="resize-none text-sm"
rows={3}
data-testid="input-task-description"
/>
</div>
<div className="flex gap-3">
<div className="flex-1">
<Select value={priority} onValueChange={(value: 'low' | 'medium' | 'high') => setPriority(value)}>
<SelectTrigger data-testid="select-task-priority">
<SelectValue placeholder={t('taskCreation.priority')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">{t('priority.low')}</SelectItem>
<SelectItem value="medium">{t('priority.medium')}</SelectItem>
<SelectItem value="high">{t('priority.high')}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex-1">
<Select value={labelId || 'none'} onValueChange={(value) => setLabelId(value === 'none' ? undefined : value)}>
<SelectTrigger data-testid="select-task-label">
<SelectValue placeholder={t('taskCreation.label')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">{t('taskCreation.noLabel')}</SelectItem>
{labels.map((label) => (
<SelectItem key={label.id} value={label.id}>
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded"
style={{ backgroundColor: label.color }}
/>
{label.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2"
data-testid="button-due-date"
>
<CalendarIcon className="w-4 h-4" />
{dueDate ? dueDate.toLocaleDateString() : t('taskCreation.dueDate')}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="end">
<Calendar
mode="single"
selected={dueDate}
onSelect={(date) => {
setDueDate(date);
setIsCalendarOpen(false);
}}
disabled={(date) => date < new Date()}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
<div className="flex gap-3 pt-2">
<Button
variant="outline"
onClick={handleClose}
className="flex-1"
data-testid="button-cancel"
>
{t('taskCreation.cancel')}
</Button>
<Button
onClick={handleSave}
disabled={!title.trim()}
className="flex-1"
data-testid="button-save-task"
>
{t('taskCreation.create')}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}