Files
task-manager/client/src/components/TaskCreationModal.tsx
T
paul-nothaft 4ea8a7c6a1 Add label functionality to tasks for better organization and tracking
Update TaskCard and TaskCreationModal components to support task labels, including UI elements for selection and display. Refactor schema imports to use '@shared/schema'.

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/vtpluZY
2025-09-11 12:36:38 +00:00

192 lines
6.3 KiB
TypeScript

import { useState } from 'react';
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 [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" />
New Task
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Input
placeholder="What needs to be done?"
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={handleKeyDown}
className="text-base"
data-testid="input-task-title"
autoFocus
/>
</div>
<div>
<Textarea
placeholder="Add a description (optional)"
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="Priority" />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">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="Label" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">No Label</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() : 'Due date'}
</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"
>
Cancel
</Button>
<Button
onClick={handleSave}
disabled={!title.trim()}
className="flex-1"
data-testid="button-save-task"
>
Create Task
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}