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:
@@ -18,6 +18,10 @@ externalPort = 80
|
|||||||
localPort = 35345
|
localPort = 35345
|
||||||
externalPort = 3002
|
externalPort = 3002
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 38369
|
||||||
|
externalPort = 3001
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 41353
|
localPort = 41353
|
||||||
externalPort = 3000
|
externalPort = 3000
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ import { Label } from '@shared/schema';
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiRequest } from '@/lib/queryClient';
|
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 {
|
interface ProjectTask {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -31,6 +37,7 @@ interface Project {
|
|||||||
tasks: ProjectTask[];
|
tasks: ProjectTask[];
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
|
plannedEndDate?: Date;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +101,8 @@ export default function ProjectTemplate({
|
|||||||
const [isEditProjectOpen, setIsEditProjectOpen] = useState(false);
|
const [isEditProjectOpen, setIsEditProjectOpen] = useState(false);
|
||||||
const [newProjectName, setNewProjectName] = useState('');
|
const [newProjectName, setNewProjectName] = useState('');
|
||||||
const [newProjectDescription, setNewProjectDescription] = useState('');
|
const [newProjectDescription, setNewProjectDescription] = useState('');
|
||||||
|
const [newPlannedEndDate, setNewPlannedEndDate] = useState<Date | undefined>();
|
||||||
|
const [isPlannedEndDateCalendarOpen, setIsPlannedEndDateCalendarOpen] = useState(false);
|
||||||
const [isRestartProjectOpen, setIsRestartProjectOpen] = useState(false);
|
const [isRestartProjectOpen, setIsRestartProjectOpen] = useState(false);
|
||||||
const [restartingProject, setRestartingProject] = useState<Project | null>(null);
|
const [restartingProject, setRestartingProject] = useState<Project | null>(null);
|
||||||
const [newEndDate, setNewEndDate] = useState<Date | undefined>();
|
const [newEndDate, setNewEndDate] = useState<Date | undefined>();
|
||||||
@@ -162,12 +171,14 @@ export default function ProjectTemplate({
|
|||||||
description: newProjectDescription.trim() || undefined,
|
description: newProjectDescription.trim() || undefined,
|
||||||
status: 'planning',
|
status: 'planning',
|
||||||
tasks: projectTasks,
|
tasks: projectTasks,
|
||||||
|
plannedEndDate: newPlannedEndDate,
|
||||||
createdAt: new Date()
|
createdAt: new Date()
|
||||||
};
|
};
|
||||||
|
|
||||||
setProjects([...projects, newProject]);
|
setProjects([...projects, newProject]);
|
||||||
setNewProjectName('');
|
setNewProjectName('');
|
||||||
setNewProjectDescription('');
|
setNewProjectDescription('');
|
||||||
|
setNewPlannedEndDate(undefined);
|
||||||
setProjectTasks([]);
|
setProjectTasks([]);
|
||||||
setIsCreateProjectOpen(false);
|
setIsCreateProjectOpen(false);
|
||||||
console.log('Created new project:', newProject.name, 'with', projectTasks.length, 'tasks');
|
console.log('Created new project:', newProject.name, 'with', projectTasks.length, 'tasks');
|
||||||
@@ -650,7 +661,7 @@ export default function ProjectTemplate({
|
|||||||
setNewEndDate(date);
|
setNewEndDate(date);
|
||||||
setIsEndDateCalendarOpen(false);
|
setIsEndDateCalendarOpen(false);
|
||||||
}}
|
}}
|
||||||
disabled={(date) => date < new Date()}
|
disabled={(date) => date < getStartOfToday()}
|
||||||
initialFocus
|
initialFocus
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
@@ -700,6 +711,9 @@ export default function ProjectTemplate({
|
|||||||
{/* Create Project Dialog */}
|
{/* Create Project Dialog */}
|
||||||
<Dialog open={isCreateProjectOpen} onOpenChange={() => {
|
<Dialog open={isCreateProjectOpen} onOpenChange={() => {
|
||||||
setIsCreateProjectOpen(false);
|
setIsCreateProjectOpen(false);
|
||||||
|
setNewProjectName('');
|
||||||
|
setNewProjectDescription('');
|
||||||
|
setNewPlannedEndDate(undefined);
|
||||||
setProjectTasks([]);
|
setProjectTasks([]);
|
||||||
setTaskForm({});
|
setTaskForm({});
|
||||||
setIsEditingTask(false);
|
setIsEditingTask(false);
|
||||||
@@ -731,6 +745,46 @@ export default function ProjectTemplate({
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Tasks Section */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -980,6 +1034,48 @@ export default function ProjectTemplate({
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Tasks Section */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|||||||
Reference in New Issue
Block a user