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
This commit is contained in:
paul-nothaft
2025-09-11 12:36:38 +00:00
parent 5f866afbb1
commit 4ea8a7c6a1
5 changed files with 55 additions and 9 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ import CalendarView from './components/CalendarView';
import KanbanBoard from './components/KanbanBoard'; import KanbanBoard from './components/KanbanBoard';
import ProjectTemplate from './components/ProjectTemplate'; import ProjectTemplate from './components/ProjectTemplate';
import ThemeToggle from './components/ThemeToggle'; import ThemeToggle from './components/ThemeToggle';
import { Task } from './components/TaskCard'; import { Task } from '@shared/schema';
import { addDays, subDays } from 'date-fns'; import { addDays, subDays } from 'date-fns';
// Mock data for prototype // Mock data for prototype
+17 -4
View File
@@ -19,12 +19,21 @@ export default function TaskCard({ task, onPlay, onPause, onEdit, onStatusChange
const [isTimerRunning, setIsTimerRunning] = useState(task.isTracking); const [isTimerRunning, setIsTimerRunning] = useState(task.isTracking);
// Fetch labels to get the label color // Fetch labels to get the label color
const { data: labels = [] } = useQuery<Label[]>({ const { data: labels = [], isLoading: labelsLoading, error: labelsError } = useQuery<Label[]>({
queryKey: ['/api/labels'], queryKey: ['/api/labels'],
staleTime: 5 * 60 * 1000, // 5 minutes cache
refetchOnWindowFocus: false,
}); });
// Find the label for this task // Find the label for this task
const taskLabel = task.labelId ? labels.find(label => label.id === task.labelId) : null; const taskLabel = task.labelId && labels.length > 0 ? labels.find(label => label.id === task.labelId) : null;
// Debug logging - always log for tasks with labelId
if (task.labelId) {
console.log(`TaskCard Debug - Task: "${task.title}", LabelId: ${task.labelId}, Labels loading: ${labelsLoading}, Labels count: ${labels.length}, Labels error:`, labelsError);
console.log(`TaskCard Debug - Available labels:`, labels.map(l => ({ id: l.id, name: l.name, color: l.color })));
console.log(`TaskCard Debug - Found label:`, taskLabel);
}
const handleToggleTimer = () => { const handleToggleTimer = () => {
if (isTimerRunning) { if (isTimerRunning) {
@@ -68,8 +77,12 @@ export default function TaskCard({ task, onPlay, onPause, onEdit, onStatusChange
> >
{taskLabel && ( {taskLabel && (
<div <div
className="absolute left-0 top-2 bottom-2 w-1 rounded-r-sm" className="absolute left-0 top-2 bottom-2 w-1 rounded-r-sm z-10"
style={{ backgroundColor: taskLabel.color }} style={{
backgroundColor: taskLabel.color,
minWidth: '4px'
}}
data-testid={`accent-label-${task.id}`}
/> />
)} )}
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
+34 -2
View File
@@ -6,8 +6,9 @@ import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Calendar } from '@/components/ui/calendar'; import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { CalendarIcon, Plus } from 'lucide-react'; import { CalendarIcon, Plus, Tag } from 'lucide-react';
import { Task } from './TaskCard'; import { Task, Label } from '@shared/schema';
import { useQuery } from '@tanstack/react-query';
interface TaskCreationModalProps { interface TaskCreationModalProps {
isOpen: boolean; isOpen: boolean;
@@ -20,8 +21,14 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium'); const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium');
const [dueDate, setDueDate] = useState<Date | undefined>(); const [dueDate, setDueDate] = useState<Date | undefined>();
const [labelId, setLabelId] = useState<string | undefined>();
const [isCalendarOpen, setIsCalendarOpen] = useState(false); const [isCalendarOpen, setIsCalendarOpen] = useState(false);
// Fetch labels
const { data: labels = [] } = useQuery<Label[]>({
queryKey: ['/api/labels'],
});
const handleSave = () => { const handleSave = () => {
if (!title.trim()) return; if (!title.trim()) return;
@@ -30,6 +37,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
description: description.trim() || undefined, description: description.trim() || undefined,
priority, priority,
dueDate, dueDate,
labelId,
status: 'todo', status: 'todo',
timeTracked: 0, timeTracked: 0,
isTracking: false isTracking: false
@@ -43,6 +51,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
setDescription(''); setDescription('');
setPriority('medium'); setPriority('medium');
setDueDate(undefined); setDueDate(undefined);
setLabelId(undefined);
onClose(); onClose();
}; };
@@ -58,6 +67,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
setDescription(''); setDescription('');
setPriority('medium'); setPriority('medium');
setDueDate(undefined); setDueDate(undefined);
setLabelId(undefined);
onClose(); onClose();
}; };
@@ -109,6 +119,28 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
</Select> </Select>
</div> </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}> <Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
+1 -1
View File
@@ -5,7 +5,7 @@ import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Search, Filter, SortAsc, Calendar, ChevronLeft, ChevronRight } from 'lucide-react'; import { Search, Filter, SortAsc, Calendar, ChevronLeft, ChevronRight } from 'lucide-react';
import { Task } from './TaskCard'; import { Task } from '@shared/schema';
import TaskCard from './TaskCard'; import TaskCard from './TaskCard';
import TimeCompletionModal from './TimeCompletionModal'; import TimeCompletionModal from './TimeCompletionModal';
import { addDays, format, isSameDay, startOfToday } from 'date-fns'; import { addDays, format, isSameDay, startOfToday } from 'date-fns';
+2 -1
View File
@@ -1,4 +1,5 @@
import TaskCard, { Task } from '../TaskCard'; import TaskCard from '../TaskCard';
import { Task } from '@shared/schema';
const mockTask: Task = { const mockTask: Task = {
id: '1', id: '1',