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
This commit is contained in:
paul-nothaft
2025-09-11 15:02:02 +00:00
parent f8ca3a860d
commit dd453e1db1
4 changed files with 224 additions and 46 deletions
+200 -1
View File
@@ -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<Project | null>(null);
const [newEndDate, setNewEndDate] = useState<Date | undefined>();
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({
);
})}
</div>
{/* Closed Projects Section */}
<div className="space-y-4 mt-8">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Closed Projects</h3>
<Input
placeholder="Filter closed projects..."
value={closedProjectsFilter}
onChange={(e) => setClosedProjectsFilter(e.target.value)}
className="max-w-xs"
data-testid="input-filter-closed-projects"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{getFilteredClosedProjects().map((project) => (
<Card key={project.id} className="p-4 hover-elevate" data-testid={`closed-project-card-${project.id}`}>
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className="font-semibold text-sm" data-testid={`text-closed-project-name-${project.id}`}>
{project.name}
</h4>
{project.description && (
<p className="text-xs text-muted-foreground mt-1">
{project.description}
</p>
)}
</div>
<Badge variant="outline" className="text-xs bg-green-50 text-green-700 border-green-200">
Finished
</Badge>
</div>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{getTotalEstimatedHours(project)}h</span>
</div>
<div className="flex items-center gap-1">
<Badge variant="outline" className="text-xs">
{project.tasks.length} tasks
</Badge>
</div>
</div>
{project.endDate && (
<div className="text-xs text-muted-foreground">
Finished: {project.endDate.toLocaleDateString()}
</div>
)}
<Button
variant="outline"
size="sm"
onClick={() => handleRestartProject(project)}
className="w-full"
data-testid={`button-restart-${project.id}`}
>
<Play className="w-3 h-3 mr-2" />
Start Again
</Button>
</div>
</Card>
))}
{getFilteredClosedProjects().length === 0 && (
<div className="col-span-full text-center text-muted-foreground text-sm py-8">
{closedProjectsFilter ? 'No closed projects match your filter' : 'No closed projects yet'}
</div>
)}
</div>
</div>
</TabsContent>
{/* Restart Project Dialog */}
<Dialog open={isRestartProjectOpen} onOpenChange={setIsRestartProjectOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Restart Project</DialogTitle>
</DialogHeader>
{restartingProject && (
<div className="space-y-4 py-4">
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm font-medium">{restartingProject.name}</div>
<div className="text-xs text-muted-foreground">
{restartingProject.tasks.length} tasks {getTotalEstimatedHours(restartingProject)}h estimated
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Planned End Date</label>
<Popover open={isEndDateCalendarOpen} onOpenChange={setIsEndDateCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left"
data-testid="button-select-end-date"
>
<CalendarIcon className="mr-2 h-4 w-4" />
{newEndDate ? newEndDate.toLocaleDateString() : "Select planned end date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={newEndDate}
onSelect={(date) => {
setNewEndDate(date);
setIsEndDateCalendarOpen(false);
}}
disabled={(date) => date < new Date()}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="clearHistory"
checked={clearHistory}
onChange={(e) => setClearHistory(e.target.checked)}
className="rounded border-gray-300"
data-testid="checkbox-clear-history"
/>
<label htmlFor="clearHistory" className="text-sm">
Clear project history (remove all tasks and start fresh)
</label>
</div>
</div>
)}
<div className="flex gap-2">
<Button
variant="outline"
onClick={() => {
setIsRestartProjectOpen(false);
setRestartingProject(null);
setNewEndDate(undefined);
setClearHistory(false);
}}
className="flex-1"
>
Cancel
</Button>
<Button
onClick={handleConfirmRestart}
disabled={!newEndDate}
className="flex-1"
data-testid="button-confirm-restart"
>
Start Again
</Button>
</div>
</DialogContent>
</Dialog>
{/* Create Project Dialog */}
<Dialog open={isCreateProjectOpen} onOpenChange={setIsCreateProjectOpen}>
<DialogContent className="sm:max-w-md">