Allow users to add and edit tasks within projects
Introduce state and handlers for managing project tasks, enabling users to add, edit, and remove tasks directly within the project creation and editing interfaces. 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/P2PZNJ9
This commit is contained in:
@@ -14,6 +14,10 @@ run = ["npm", "run", "start"]
|
||||
localPort = 5000
|
||||
externalPort = 80
|
||||
|
||||
[[ports]]
|
||||
localPort = 33729
|
||||
externalPort = 3001
|
||||
|
||||
[[ports]]
|
||||
localPort = 35345
|
||||
externalPort = 3002
|
||||
|
||||
@@ -101,6 +101,12 @@ export default function ProjectTemplate({
|
||||
const [isEndDateCalendarOpen, setIsEndDateCalendarOpen] = useState(false);
|
||||
const [closedProjectsFilter, setClosedProjectsFilter] = useState('');
|
||||
|
||||
// Task management state
|
||||
const [projectTasks, setProjectTasks] = useState<ProjectTask[]>([]);
|
||||
const [isEditingTask, setIsEditingTask] = useState(false);
|
||||
const [editingTaskIndex, setEditingTaskIndex] = useState<number | null>(null);
|
||||
const [taskForm, setTaskForm] = useState<Partial<ProjectTask>>({});
|
||||
|
||||
// Label management state
|
||||
const [isLabelDialogOpen, setIsLabelDialogOpen] = useState(false);
|
||||
const [editingLabel, setEditingLabel] = useState<Label | null>(null);
|
||||
@@ -155,20 +161,22 @@ export default function ProjectTemplate({
|
||||
name: newProjectName.trim(),
|
||||
description: newProjectDescription.trim() || undefined,
|
||||
status: 'planning',
|
||||
tasks: [],
|
||||
tasks: projectTasks,
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
setProjects([...projects, newProject]);
|
||||
setNewProjectName('');
|
||||
setNewProjectDescription('');
|
||||
setProjectTasks([]);
|
||||
setIsCreateProjectOpen(false);
|
||||
console.log('Created new project:', newProject.name);
|
||||
console.log('Created new project:', newProject.name, 'with', projectTasks.length, 'tasks');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditProject = (project: Project) => {
|
||||
setSelectedProject(project);
|
||||
setProjectTasks([...project.tasks]);
|
||||
setIsEditProjectOpen(true);
|
||||
};
|
||||
|
||||
@@ -239,6 +247,83 @@ export default function ProjectTemplate({
|
||||
(project.description && project.description.toLowerCase().includes(closedProjectsFilter.toLowerCase()))
|
||||
);
|
||||
};
|
||||
|
||||
// Task management functions
|
||||
const handleAddTask = () => {
|
||||
const newTask: ProjectTask = {
|
||||
id: Date.now().toString(),
|
||||
title: taskForm.title || 'New Task',
|
||||
description: taskForm.description || '',
|
||||
priority: taskForm.priority || 'medium',
|
||||
status: taskForm.status || 'todo',
|
||||
estimatedHours: taskForm.estimatedHours || 0,
|
||||
labelId: taskForm.labelId
|
||||
};
|
||||
|
||||
setProjectTasks([...projectTasks, newTask]);
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
};
|
||||
|
||||
const handleEditTask = (index: number) => {
|
||||
setEditingTaskIndex(index);
|
||||
setTaskForm({ ...projectTasks[index] });
|
||||
setIsEditingTask(true);
|
||||
};
|
||||
|
||||
const handleSaveTask = () => {
|
||||
if (editingTaskIndex !== null) {
|
||||
const updatedTasks = [...projectTasks];
|
||||
updatedTasks[editingTaskIndex] = {
|
||||
id: projectTasks[editingTaskIndex].id,
|
||||
title: taskForm.title || 'New Task',
|
||||
description: taskForm.description || '',
|
||||
priority: taskForm.priority || 'medium',
|
||||
status: taskForm.status || 'todo',
|
||||
estimatedHours: taskForm.estimatedHours || 0,
|
||||
labelId: taskForm.labelId
|
||||
};
|
||||
setProjectTasks(updatedTasks);
|
||||
}
|
||||
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
setEditingTaskIndex(null);
|
||||
};
|
||||
|
||||
const handleRemoveTask = (index: number) => {
|
||||
setProjectTasks(projectTasks.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCancelTaskEdit = () => {
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
setEditingTaskIndex(null);
|
||||
};
|
||||
|
||||
const handleStartNewTask = () => {
|
||||
setTaskForm({});
|
||||
setEditingTaskIndex(null);
|
||||
setIsEditingTask(true);
|
||||
};
|
||||
|
||||
const handleSaveEditedProject = () => {
|
||||
if (selectedProject) {
|
||||
const updatedProject = {
|
||||
...selectedProject,
|
||||
tasks: projectTasks
|
||||
};
|
||||
|
||||
setProjects(projects.map(p =>
|
||||
p.id === selectedProject.id ? updatedProject : p
|
||||
));
|
||||
|
||||
setSelectedProject(null);
|
||||
setProjectTasks([]);
|
||||
setIsEditProjectOpen(false);
|
||||
console.log('Updated project:', updatedProject.name, 'with', projectTasks.length, 'tasks');
|
||||
}
|
||||
};
|
||||
|
||||
// Label management functions
|
||||
const handleSaveLabel = () => {
|
||||
@@ -580,35 +665,218 @@ export default function ProjectTemplate({
|
||||
</Dialog>
|
||||
|
||||
{/* Create Project Dialog */}
|
||||
<Dialog open={isCreateProjectOpen} onOpenChange={setIsCreateProjectOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<Dialog open={isCreateProjectOpen} onOpenChange={() => {
|
||||
setIsCreateProjectOpen(false);
|
||||
setProjectTasks([]);
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
}}>
|
||||
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Project</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Project Name</label>
|
||||
<Input
|
||||
value={newProjectName}
|
||||
onChange={(e) => setNewProjectName(e.target.value)}
|
||||
placeholder="Enter project name"
|
||||
data-testid="input-project-name"
|
||||
/>
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Project Details */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Project Name</label>
|
||||
<Input
|
||||
value={newProjectName}
|
||||
onChange={(e) => setNewProjectName(e.target.value)}
|
||||
placeholder="Enter project name"
|
||||
data-testid="input-project-name"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description (Optional)</label>
|
||||
<Input
|
||||
value={newProjectDescription}
|
||||
onChange={(e) => setNewProjectDescription(e.target.value)}
|
||||
placeholder="Enter project description"
|
||||
data-testid="input-project-description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description (Optional)</label>
|
||||
<Input
|
||||
value={newProjectDescription}
|
||||
onChange={(e) => setNewProjectDescription(e.target.value)}
|
||||
placeholder="Enter project description"
|
||||
data-testid="input-project-description"
|
||||
/>
|
||||
|
||||
{/* Tasks Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">Project Tasks</h3>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleStartNewTask}
|
||||
data-testid="button-add-task"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Task
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Task List */}
|
||||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||||
{projectTasks.map((task, index) => (
|
||||
<Card key={task.id} className="p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium text-sm">{task.title}</h4>
|
||||
{task.description && (
|
||||
<p className="text-xs text-muted-foreground">{task.description}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{task.priority}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{task.status}
|
||||
</Badge>
|
||||
{task.estimatedHours ? (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{task.estimatedHours}h
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleEditTask(index)}
|
||||
data-testid={`button-edit-task-${index}`}
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveTask(index)}
|
||||
data-testid={`button-remove-task-${index}`}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{projectTasks.length === 0 && (
|
||||
<div className="text-center text-muted-foreground text-sm py-8 border-2 border-dashed border-muted rounded-lg">
|
||||
No tasks added yet. Click "Add Task" to get started.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Task Form */}
|
||||
{isEditingTask && (
|
||||
<Card className="p-4 border-primary">
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-semibold text-sm">
|
||||
{editingTaskIndex !== null ? 'Edit Task' : 'New Task'}
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Task Title</label>
|
||||
<Input
|
||||
value={taskForm.title || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, title: e.target.value})}
|
||||
placeholder="Enter task title"
|
||||
data-testid="input-task-title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description</label>
|
||||
<Input
|
||||
value={taskForm.description || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, description: e.target.value})}
|
||||
placeholder="Enter task description"
|
||||
data-testid="input-task-description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Priority</label>
|
||||
<Select
|
||||
value={taskForm.priority || 'medium'}
|
||||
onValueChange={(value: ProjectTask['priority']) =>
|
||||
setTaskForm({...taskForm, priority: value})
|
||||
}
|
||||
>
|
||||
<SelectTrigger data-testid="select-task-priority">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Low</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="high">High</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Status</label>
|
||||
<Select
|
||||
value={taskForm.status || 'todo'}
|
||||
onValueChange={(value: ProjectTask['status']) =>
|
||||
setTaskForm({...taskForm, status: value})
|
||||
}
|
||||
>
|
||||
<SelectTrigger data-testid="select-task-status">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="todo">To Do</SelectItem>
|
||||
<SelectItem value="inProgress">In Progress</SelectItem>
|
||||
<SelectItem value="done">Done</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Estimated Hours</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={taskForm.estimatedHours || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, estimatedHours: Number(e.target.value)})}
|
||||
placeholder="0"
|
||||
min="0"
|
||||
data-testid="input-task-hours"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancelTaskEdit}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={editingTaskIndex !== null ? handleSaveTask : handleAddTask}
|
||||
disabled={!taskForm.title?.trim()}
|
||||
className="flex-1"
|
||||
data-testid="button-save-task"
|
||||
>
|
||||
{editingTaskIndex !== null ? 'Save Changes' : 'Add Task'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsCreateProjectOpen(false)}
|
||||
onClick={() => {
|
||||
setIsCreateProjectOpen(false);
|
||||
setProjectTasks([]);
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
@@ -619,85 +887,258 @@ export default function ProjectTemplate({
|
||||
className="flex-1"
|
||||
data-testid="button-save-project"
|
||||
>
|
||||
Create Project
|
||||
Create Project ({projectTasks.length} tasks)
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Edit Project Dialog */}
|
||||
<Dialog open={isEditProjectOpen} onOpenChange={setIsEditProjectOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<Dialog open={isEditProjectOpen} onOpenChange={() => {
|
||||
setIsEditProjectOpen(false);
|
||||
setSelectedProject(null);
|
||||
setProjectTasks([]);
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
}}>
|
||||
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Project</DialogTitle>
|
||||
</DialogHeader>
|
||||
{selectedProject && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Project Name</label>
|
||||
<Input
|
||||
value={selectedProject.name}
|
||||
onChange={(e) => setSelectedProject({ ...selectedProject, name: e.target.value })}
|
||||
placeholder="Enter project name"
|
||||
data-testid="input-edit-project-name"
|
||||
/>
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Project Details */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Project Name</label>
|
||||
<Input
|
||||
value={selectedProject.name}
|
||||
onChange={(e) => setSelectedProject({ ...selectedProject, name: e.target.value })}
|
||||
placeholder="Enter project name"
|
||||
data-testid="input-edit-project-name"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description (Optional)</label>
|
||||
<Input
|
||||
value={selectedProject.description || ''}
|
||||
onChange={(e) => setSelectedProject({ ...selectedProject, description: e.target.value })}
|
||||
placeholder="Enter project description"
|
||||
data-testid="input-edit-project-description"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Status</label>
|
||||
<Select
|
||||
value={selectedProject.status}
|
||||
onValueChange={(value: Project['status']) =>
|
||||
setSelectedProject({ ...selectedProject, status: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger data-testid="select-project-status">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="planning">Planning</SelectItem>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="finished">Finished</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description (Optional)</label>
|
||||
<Input
|
||||
value={selectedProject.description || ''}
|
||||
onChange={(e) => setSelectedProject({ ...selectedProject, description: e.target.value })}
|
||||
placeholder="Enter project description"
|
||||
data-testid="input-edit-project-description"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Status</label>
|
||||
<Select
|
||||
value={selectedProject.status}
|
||||
onValueChange={(value: Project['status']) =>
|
||||
setSelectedProject({ ...selectedProject, status: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger data-testid="select-project-status">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="planning">Planning</SelectItem>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="finished">Finished</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* Tasks Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">Project Tasks</h3>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleStartNewTask}
|
||||
data-testid="button-add-task-edit"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Task
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Task List */}
|
||||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||||
{projectTasks.map((task, index) => (
|
||||
<Card key={task.id} className="p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium text-sm">{task.title}</h4>
|
||||
{task.description && (
|
||||
<p className="text-xs text-muted-foreground">{task.description}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{task.priority}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{task.status}
|
||||
</Badge>
|
||||
{task.estimatedHours ? (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{task.estimatedHours}h
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleEditTask(index)}
|
||||
data-testid={`button-edit-task-edit-${index}`}
|
||||
>
|
||||
<Edit className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveTask(index)}
|
||||
data-testid={`button-remove-task-edit-${index}`}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{projectTasks.length === 0 && (
|
||||
<div className="text-center text-muted-foreground text-sm py-8 border-2 border-dashed border-muted rounded-lg">
|
||||
No tasks in this project yet. Click "Add Task" to get started.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Task Form */}
|
||||
{isEditingTask && (
|
||||
<Card className="p-4 border-primary">
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-semibold text-sm">
|
||||
{editingTaskIndex !== null ? 'Edit Task' : 'New Task'}
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Task Title</label>
|
||||
<Input
|
||||
value={taskForm.title || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, title: e.target.value})}
|
||||
placeholder="Enter task title"
|
||||
data-testid="input-task-title-edit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description</label>
|
||||
<Input
|
||||
value={taskForm.description || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, description: e.target.value})}
|
||||
placeholder="Enter task description"
|
||||
data-testid="input-task-description-edit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Priority</label>
|
||||
<Select
|
||||
value={taskForm.priority || 'medium'}
|
||||
onValueChange={(value: ProjectTask['priority']) =>
|
||||
setTaskForm({...taskForm, priority: value})
|
||||
}
|
||||
>
|
||||
<SelectTrigger data-testid="select-task-priority-edit">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Low</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="high">High</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Status</label>
|
||||
<Select
|
||||
value={taskForm.status || 'todo'}
|
||||
onValueChange={(value: ProjectTask['status']) =>
|
||||
setTaskForm({...taskForm, status: value})
|
||||
}
|
||||
>
|
||||
<SelectTrigger data-testid="select-task-status-edit">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="todo">To Do</SelectItem>
|
||||
<SelectItem value="inProgress">In Progress</SelectItem>
|
||||
<SelectItem value="done">Done</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Estimated Hours</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={taskForm.estimatedHours || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, estimatedHours: Number(e.target.value)})}
|
||||
placeholder="0"
|
||||
min="0"
|
||||
data-testid="input-task-hours-edit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancelTaskEdit}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={editingTaskIndex !== null ? handleSaveTask : handleAddTask}
|
||||
disabled={!taskForm.title?.trim()}
|
||||
className="flex-1"
|
||||
data-testid="button-save-task-edit"
|
||||
>
|
||||
{editingTaskIndex !== null ? 'Save Changes' : 'Add Task'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setIsEditProjectOpen(false);
|
||||
setSelectedProject(null);
|
||||
setProjectTasks([]);
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (selectedProject) {
|
||||
setProjects(projects.map(p =>
|
||||
p.id === selectedProject.id ? selectedProject : p
|
||||
));
|
||||
setIsEditProjectOpen(false);
|
||||
setSelectedProject(null);
|
||||
console.log('Updated project:', selectedProject.name);
|
||||
}
|
||||
}}
|
||||
onClick={handleSaveEditedProject}
|
||||
disabled={!selectedProject?.name.trim()}
|
||||
className="flex-1"
|
||||
data-testid="button-update-project"
|
||||
>
|
||||
Update Project
|
||||
Update Project ({projectTasks.length} tasks)
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user