From dd453e1db1468d3a64d6b3a6b489bedfae712958 Mon Sep 17 00:00:00 2001 From: paul-nothaft <40865108-paul-nothaft@users.noreply.replit.com> Date: Thu, 11 Sep 2025 15:02:02 +0000 Subject: [PATCH] Improve task management by adding project restart functionality and filtering closed projects Enhances the task management app by introducing a "restart project" feature with options to clear history or reset tasks, refining calendar and Kanban views to use a Monday start, and enabling filtering for closed projects. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/RVJYTwu --- client/src/App.tsx | 55 ++---- client/src/components/CalendarView.tsx | 8 +- client/src/components/KanbanBoard.tsx | 6 +- client/src/components/ProjectTemplate.tsx | 201 +++++++++++++++++++++- 4 files changed, 224 insertions(+), 46 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index d79034b..aac0f35 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -29,6 +29,7 @@ const initialTasks: Task[] = [ timeTracked: 0, isTracking: false, projectId: 'project-1', + notes: null, labelId: '2e8c3aa0-278f-4220-b2c5-aa109b4279b7' // Urgent label }, { @@ -41,16 +42,20 @@ const initialTasks: Task[] = [ timeTracked: 120, isTracking: true, projectId: 'project-2', + notes: null, labelId: 'cb44bed1-8ba3-43fe-9498-bb28e483ed1f' // Work label }, { id: '3', title: 'Team standup meeting', + description: null, status: 'todo', priority: 'low', - dueDate: undefined, // Make unscheduled + dueDate: null, timeTracked: 0, isTracking: false, + projectId: null, + notes: null, labelId: '274f0ba4-a133-471a-bbe9-8189aa3b0106' // Personal label }, { @@ -59,21 +64,25 @@ const initialTasks: Task[] = [ description: 'Users unable to login with special characters in password', status: 'todo', priority: 'high', - dueDate: undefined, // Make unscheduled + dueDate: null, timeTracked: 30, isTracking: false, projectId: 'project-2', + notes: null, labelId: 'c5eccac9-7919-4eb1-bb50-badacad6b1c1' // Study label }, { id: '5', title: 'Deploy new features', + description: null, status: 'done', priority: 'medium', dueDate: subDays(new Date(), 2), timeTracked: 120, isTracking: false, - projectId: 'project-1' + projectId: 'project-1', + notes: null, + labelId: null } ]; @@ -86,14 +95,15 @@ function App() { const task: Task = { id: Date.now().toString(), title: newTask.title || '', - description: newTask.description, + description: newTask.description || null, status: 'todo', priority: newTask.priority || 'medium', - dueDate: newTask.dueDate, + dueDate: newTask.dueDate || null, timeTracked: 0, isTracking: false, - projectId: newTask.projectId, - labelId: newTask.labelId + projectId: newTask.projectId || null, + notes: newTask.notes || null, + labelId: newTask.labelId || null }; setTasks(prev => [...prev, task]); console.log('Task created:', task); @@ -155,37 +165,6 @@ function App() { /> ); - case 'settings': - return ( -
-
-

Settings

- - -
-
-

Dark Mode

-

Toggle between light and dark themes

-
- -
-
- - -
-

About TaskFlow

-

- A personal task management app with calendar scheduling, kanban boards, and project templates. -

-
- Version 1.0.0 • Built with React and Tailwind CSS -
-
-
-
-
- ); - default: return (
diff --git a/client/src/components/CalendarView.tsx b/client/src/components/CalendarView.tsx index dac863d..ed523c0 100644 --- a/client/src/components/CalendarView.tsx +++ b/client/src/components/CalendarView.tsx @@ -24,10 +24,10 @@ export default function CalendarView({ tasks, onTaskDrop, onDateSelect }: Calend refetchOnWindowFocus: false, }); - // Get 6 weeks: 1 previous week (grayed out) + current week + 4 future weeks - const currentWeekStart = startOfWeek(currentDate); + // Get 5 weeks: 1 previous week (grayed out) + current week + 3 future weeks + const currentWeekStart = startOfWeek(currentDate, { weekStartsOn: 1 }); // Monday = 1 const startDate = subWeeks(currentWeekStart, 1); // Start from 1 week before current week - const dates = Array.from({ length: 42 }, (_, i) => addDays(startDate, i)); + const dates = Array.from({ length: 35 }, (_, i) => addDays(startDate, i)); const getTasksForDate = (date: Date) => { return tasks.filter(task => @@ -69,7 +69,7 @@ export default function CalendarView({ tasks, onTaskDrop, onDateSelect }: Calend

- 6-Week Calendar View + 5-Week Calendar View

diff --git a/client/src/components/KanbanBoard.tsx b/client/src/components/KanbanBoard.tsx index 0ed76a4..02efa4a 100644 --- a/client/src/components/KanbanBoard.tsx +++ b/client/src/components/KanbanBoard.tsx @@ -39,8 +39,8 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate }: let filteredTasks = tasks.filter(task => task.status === status); if (viewMode === 'weekly') { - const weekStart = startOfWeek(currentDate); - const weekEnd = endOfWeek(currentDate); + const weekStart = startOfWeek(currentDate, { weekStartsOn: 1 }); // Monday = 1 + const weekEnd = endOfWeek(currentDate, { weekStartsOn: 1 }); filteredTasks = filteredTasks.filter(task => task.dueDate && isWithinInterval(task.dueDate, { start: weekStart, end: weekEnd }) ); @@ -87,7 +87,7 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate }: const getPeriodTitle = () => { if (viewMode === 'weekly') { - return `Week of ${format(startOfWeek(currentDate), 'MMM d')}`; + return `Week of ${format(startOfWeek(currentDate, { weekStartsOn: 1 }), 'MMM d')}`; } else if (viewMode === 'monthly') { return format(currentDate, 'MMMM yyyy'); } diff --git a/client/src/components/ProjectTemplate.tsx b/client/src/components/ProjectTemplate.tsx index 7616a90..1d9fa54 100644 --- a/client/src/components/ProjectTemplate.tsx +++ b/client/src/components/ProjectTemplate.tsx @@ -9,7 +9,6 @@ import { Calendar } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Plus, Calendar as CalendarIcon, Clock, Copy, Play, Tag, Edit, Trash2, Palette } from 'lucide-react'; -import { Task } from './TaskCard'; import { Label } from '@shared/schema'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiRequest } from '@/lib/queryClient'; @@ -95,6 +94,12 @@ export default function ProjectTemplate({ const [isEditProjectOpen, setIsEditProjectOpen] = useState(false); const [newProjectName, setNewProjectName] = useState(''); const [newProjectDescription, setNewProjectDescription] = useState(''); + const [isRestartProjectOpen, setIsRestartProjectOpen] = useState(false); + const [restartingProject, setRestartingProject] = useState(null); + const [newEndDate, setNewEndDate] = useState(); + const [clearHistory, setClearHistory] = useState(false); + const [isEndDateCalendarOpen, setIsEndDateCalendarOpen] = useState(false); + const [closedProjectsFilter, setClosedProjectsFilter] = useState(''); // Label management state const [isLabelDialogOpen, setIsLabelDialogOpen] = useState(false); @@ -197,6 +202,43 @@ export default function ProjectTemplate({ const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; + + const handleRestartProject = (project: Project) => { + setRestartingProject(project); + setIsRestartProjectOpen(true); + }; + + const handleConfirmRestart = () => { + if (restartingProject && newEndDate) { + const restartedProject: Project = { + ...restartingProject, + status: 'planning', + startDate: new Date(), + endDate: newEndDate, + tasks: clearHistory ? [] : restartingProject.tasks.map(task => ({ ...task, status: 'todo' as const })) + }; + + setProjects(projects.map(p => + p.id === restartingProject.id ? restartedProject : p + )); + + setIsRestartProjectOpen(false); + setRestartingProject(null); + setNewEndDate(undefined); + setClearHistory(false); + console.log('Project restarted:', restartedProject.name, 'Clear history:', clearHistory); + } + }; + + const getFilteredClosedProjects = () => { + const closedProjects = projects.filter(project => project.status === 'finished'); + if (!closedProjectsFilter) return closedProjects; + + return closedProjects.filter(project => + project.name.toLowerCase().includes(closedProjectsFilter.toLowerCase()) || + (project.description && project.description.toLowerCase().includes(closedProjectsFilter.toLowerCase())) + ); + }; // Label management functions const handleSaveLabel = () => { @@ -378,8 +420,165 @@ export default function ProjectTemplate({ ); })}
+ + {/* Closed Projects Section */} +
+
+

Closed Projects

+ setClosedProjectsFilter(e.target.value)} + className="max-w-xs" + data-testid="input-filter-closed-projects" + /> +
+ +
+ {getFilteredClosedProjects().map((project) => ( + +
+
+
+

+ {project.name} +

+ {project.description && ( +

+ {project.description} +

+ )} +
+ + + Finished + +
+ +
+
+ + {getTotalEstimatedHours(project)}h +
+
+ + {project.tasks.length} tasks + +
+
+ + {project.endDate && ( +
+ Finished: {project.endDate.toLocaleDateString()} +
+ )} + + +
+
+ ))} + + {getFilteredClosedProjects().length === 0 && ( +
+ {closedProjectsFilter ? 'No closed projects match your filter' : 'No closed projects yet'} +
+ )} +
+
+ {/* Restart Project Dialog */} + + + + Restart Project + + {restartingProject && ( +
+
+
{restartingProject.name}
+
+ {restartingProject.tasks.length} tasks • {getTotalEstimatedHours(restartingProject)}h estimated +
+
+ +
+ + + + + + + { + setNewEndDate(date); + setIsEndDateCalendarOpen(false); + }} + disabled={(date) => date < new Date()} + initialFocus + /> + + +
+ +
+ setClearHistory(e.target.checked)} + className="rounded border-gray-300" + data-testid="checkbox-clear-history" + /> + +
+
+ )} +
+ + +
+
+
+ {/* Create Project Dialog */}