From 6e8ef8a09126e00078acea9a894b3e4f71678c25 Mon Sep 17 00:00:00 2001 From: paul-nothaft <40865108-paul-nothaft@users.noreply.replit.com> Date: Thu, 11 Sep 2025 22:31:27 +0000 Subject: [PATCH] Add filtering options and improve task interactions on the board view Integrates label and priority filtering into the Kanban board, enhances task card clickability, and refactors UI components for better usability. 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/P2PZNJ9 --- .replit | 4 + client/src/components/KanbanBoard.tsx | 258 +++++++++++++++++++++++++- 2 files changed, 261 insertions(+), 1 deletion(-) diff --git a/.replit b/.replit index 61099cd..fdec5e9 100644 --- a/.replit +++ b/.replit @@ -18,6 +18,10 @@ externalPort = 80 localPort = 35345 externalPort = 3002 +[[ports]] +localPort = 40247 +externalPort = 3001 + [[ports]] localPort = 41353 externalPort = 3000 diff --git a/client/src/components/KanbanBoard.tsx b/client/src/components/KanbanBoard.tsx index 7bbbb19..e07eae1 100644 --- a/client/src/components/KanbanBoard.tsx +++ b/client/src/components/KanbanBoard.tsx @@ -3,7 +3,21 @@ import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { LayoutGrid, Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-react'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Separator } from '@/components/ui/separator'; +import { LayoutGrid, Calendar as CalendarIcon, ChevronLeft, ChevronRight, Filter, X, Tag, AlertTriangle } from 'lucide-react'; import { Task, Label } from '@shared/schema'; import TaskCard from './TaskCard'; import { useQuery } from '@tanstack/react-query'; @@ -25,6 +39,10 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o const [viewMode, setViewMode] = useState('traditional'); const [currentDate, setCurrentDate] = useState(new Date()); const [draggedTask, setDraggedTask] = useState(null); + + // Filter state + const [selectedLabels, setSelectedLabels] = useState([]); + const [selectedPriorities, setSelectedPriorities] = useState([]); // Fetch labels to get label colors const { data: labels = [] } = useQuery({ @@ -42,6 +60,21 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o const getTasksForColumn = (status: Task['status']) => { let filteredTasks = tasks.filter(task => task.status === status); + // Apply label filtering + if (selectedLabels.length > 0) { + filteredTasks = filteredTasks.filter(task => + task.labelId && selectedLabels.includes(task.labelId) + ); + } + + // Apply priority filtering + if (selectedPriorities.length > 0) { + filteredTasks = filteredTasks.filter(task => + selectedPriorities.includes(task.priority) + ); + } + + // Apply date filtering for weekly/monthly views if (viewMode === 'weekly') { const weekStart = startOfWeek(currentDate, { weekStartsOn: 1 }); // Monday = 1 const weekEnd = endOfWeek(currentDate, { weekStartsOn: 1 }); @@ -98,6 +131,40 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o return 'All Tasks'; }; + // Filter management functions + const toggleLabel = (labelId: string) => { + setSelectedLabels(prev => + prev.includes(labelId) + ? prev.filter(id => id !== labelId) + : [...prev, labelId] + ); + }; + + const togglePriority = (priority: string) => { + setSelectedPriorities(prev => + prev.includes(priority) + ? prev.filter(p => p !== priority) + : [...prev, priority] + ); + }; + + const clearAllFilters = () => { + setSelectedLabels([]); + setSelectedPriorities([]); + }; + + const hasActiveFilters = selectedLabels.length > 0 || selectedPriorities.length > 0; + + const priorities = ['high', 'medium', 'low']; + + const getPriorityColor = (priority: string) => { + switch (priority) { + case 'high': return 'bg-destructive text-destructive-foreground'; + case 'medium': return 'bg-yellow-500 text-white'; + default: return 'bg-muted text-muted-foreground'; + } + }; + return (
@@ -139,6 +206,195 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o )}
+ {/* Filter Bar */} + +
+
+ + Filters + {hasActiveFilters && ( + + {selectedLabels.length + selectedPriorities.length} + + )} +
+ +
+ {/* Labels Filter */} +
+ + + + + +
+

Filter by Labels

+ +
+ {labels.length > 0 ? ( + labels.map((label) => ( +
+ toggleLabel(label.id)} + data-testid={`checkbox-label-${label.id}`} + /> +
+
+ + +
+ + {/* Priorities Filter */} +
+ + + + + +
+

Filter by Priority

+ +
+ {priorities.map((priority) => ( +
+ togglePriority(priority)} + data-testid={`checkbox-priority-${priority}`} + /> + +
+ ))} +
+
+
+
+
+ + {/* Clear Filters */} + {hasActiveFilters && ( + <> + + + + )} +
+
+ + {/* Active Filters Display */} + {hasActiveFilters && ( +
+
+ Active filters: + {selectedLabels.map((labelId) => { + const label = labels.find(l => l.id === labelId); + return label ? ( + +
+ {label.name} + toggleLabel(labelId)} + /> + + ) : null; + })} + {selectedPriorities.map((priority) => ( + + {priority} + togglePriority(priority)} + /> + + ))} +
+
+ )} + + {/* View Mode Tabs */} setViewMode(value as ViewMode)}>