Add planned end date for projects and improve task interaction

Adds a 'plannedEndDate' field to projects, making it editable. Enhances task interaction by allowing direct clicking on tasks in various views to open details and report time. Also, includes a helper function to ensure date selections respect the current day.

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
This commit is contained in:
paul-nothaft
2025-09-11 21:51:41 +00:00
parent b0a2f51927
commit 4d0d750042
2 changed files with 101 additions and 1 deletions
+4
View File
@@ -18,6 +18,10 @@ externalPort = 80
localPort = 35345
externalPort = 3002
[[ports]]
localPort = 38369
externalPort = 3001
[[ports]]
localPort = 41353
externalPort = 3000
+97 -1
View File
@@ -13,6 +13,12 @@ import { Label } from '@shared/schema';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiRequest } from '@/lib/queryClient';
// Helper function to get today's date at start of day for proper date comparison
const getStartOfToday = () => {
const today = new Date();
return new Date(today.getFullYear(), today.getMonth(), today.getDate());
};
interface ProjectTask {
id: string;
title: string;
@@ -31,6 +37,7 @@ interface Project {
tasks: ProjectTask[];
startDate?: Date;
endDate?: Date;
plannedEndDate?: Date;
createdAt: Date;
}
@@ -94,6 +101,8 @@ export default function ProjectTemplate({
const [isEditProjectOpen, setIsEditProjectOpen] = useState(false);
const [newProjectName, setNewProjectName] = useState('');
const [newProjectDescription, setNewProjectDescription] = useState('');
const [newPlannedEndDate, setNewPlannedEndDate] = useState<Date | undefined>();
const [isPlannedEndDateCalendarOpen, setIsPlannedEndDateCalendarOpen] = useState(false);
const [isRestartProjectOpen, setIsRestartProjectOpen] = useState(false);
const [restartingProject, setRestartingProject] = useState<Project | null>(null);
const [newEndDate, setNewEndDate] = useState<Date | undefined>();
@@ -162,12 +171,14 @@ export default function ProjectTemplate({
description: newProjectDescription.trim() || undefined,
status: 'planning',
tasks: projectTasks,
plannedEndDate: newPlannedEndDate,
createdAt: new Date()
};
setProjects([...projects, newProject]);
setNewProjectName('');
setNewProjectDescription('');
setNewPlannedEndDate(undefined);
setProjectTasks([]);
setIsCreateProjectOpen(false);
console.log('Created new project:', newProject.name, 'with', projectTasks.length, 'tasks');
@@ -650,7 +661,7 @@ export default function ProjectTemplate({
setNewEndDate(date);
setIsEndDateCalendarOpen(false);
}}
disabled={(date) => date < new Date()}
disabled={(date) => date < getStartOfToday()}
initialFocus
/>
</PopoverContent>
@@ -700,6 +711,9 @@ export default function ProjectTemplate({
{/* Create Project Dialog */}
<Dialog open={isCreateProjectOpen} onOpenChange={() => {
setIsCreateProjectOpen(false);
setNewProjectName('');
setNewProjectDescription('');
setNewPlannedEndDate(undefined);
setProjectTasks([]);
setTaskForm({});
setIsEditingTask(false);
@@ -731,6 +745,46 @@ export default function ProjectTemplate({
</div>
</div>
{/* Planned End Date */}
<div className="space-y-2">
<label className="text-sm font-medium">Planned End Date (Optional)</label>
<Popover open={isPlannedEndDateCalendarOpen} onOpenChange={setIsPlannedEndDateCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full md:w-80 justify-start text-left"
data-testid="button-select-planned-end-date"
>
<CalendarIcon className="mr-2 h-4 w-4" />
{newPlannedEndDate ? newPlannedEndDate.toLocaleDateString() : "Select planned end date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={newPlannedEndDate}
onSelect={(date) => {
setNewPlannedEndDate(date);
setIsPlannedEndDateCalendarOpen(false);
}}
disabled={(date) => date < getStartOfToday()}
initialFocus
/>
</PopoverContent>
</Popover>
{newPlannedEndDate && (
<Button
variant="ghost"
size="sm"
onClick={() => setNewPlannedEndDate(undefined)}
className="text-xs text-muted-foreground hover:text-destructive"
data-testid="button-clear-create-planned-end-date"
>
Clear date
</Button>
)}
</div>
{/* Tasks Section */}
<div className="space-y-4">
<div className="flex items-center justify-between">
@@ -980,6 +1034,48 @@ export default function ProjectTemplate({
</div>
</div>
{/* Planned End Date */}
<div className="space-y-2">
<label className="text-sm font-medium">Planned End Date (Optional)</label>
<Popover open={isPlannedEndDateCalendarOpen} onOpenChange={setIsPlannedEndDateCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full md:w-80 justify-start text-left"
data-testid="button-edit-planned-end-date"
>
<CalendarIcon className="mr-2 h-4 w-4" />
{selectedProject.plannedEndDate
? selectedProject.plannedEndDate.toLocaleDateString()
: "Select planned end date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={selectedProject.plannedEndDate}
onSelect={(date) => {
setSelectedProject({ ...selectedProject, plannedEndDate: date });
setIsPlannedEndDateCalendarOpen(false);
}}
disabled={(date) => date < getStartOfToday()}
initialFocus
/>
</PopoverContent>
</Popover>
{selectedProject.plannedEndDate && (
<Button
variant="ghost"
size="sm"
onClick={() => setSelectedProject({ ...selectedProject, plannedEndDate: undefined })}
className="text-xs text-muted-foreground hover:text-destructive"
data-testid="button-clear-planned-end-date"
>
Clear date
</Button>
)}
</div>
{/* Tasks Section */}
<div className="space-y-4">
<div className="flex items-center justify-between">