Improve task editing and project closing functionality
Refactors the project template component to enable inline task editing, implement temporary task IDs for new tasks, and update the handling of closing projects to duplicate them as templates. 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:
@@ -273,9 +273,14 @@ export default function ProjectTemplate({
|
||||
|
||||
const handleSaveTask = () => {
|
||||
if (editingTaskIndex !== null) {
|
||||
const currentTask = projectTasks[editingTaskIndex];
|
||||
const updatedTasks = [...projectTasks];
|
||||
|
||||
// Check if this is a new task (temporary ID) or existing task
|
||||
const isNewTask = currentTask.id.startsWith('temp-');
|
||||
|
||||
updatedTasks[editingTaskIndex] = {
|
||||
id: projectTasks[editingTaskIndex].id,
|
||||
id: isNewTask ? Date.now().toString() : currentTask.id,
|
||||
title: taskForm.title || 'New Task',
|
||||
description: taskForm.description || '',
|
||||
priority: taskForm.priority || 'medium',
|
||||
@@ -283,6 +288,7 @@ export default function ProjectTemplate({
|
||||
estimatedHours: taskForm.estimatedHours || 0,
|
||||
labelId: taskForm.labelId
|
||||
};
|
||||
|
||||
setProjectTasks(updatedTasks);
|
||||
}
|
||||
|
||||
@@ -296,14 +302,41 @@ export default function ProjectTemplate({
|
||||
};
|
||||
|
||||
const handleCancelTaskEdit = () => {
|
||||
// If we're editing a task that has a temporary ID (new task), remove it from the list
|
||||
if (editingTaskIndex !== null) {
|
||||
const currentTask = projectTasks[editingTaskIndex];
|
||||
if (currentTask.id.startsWith('temp-')) {
|
||||
const updatedTasks = projectTasks.filter((_, index) => index !== editingTaskIndex);
|
||||
setProjectTasks(updatedTasks);
|
||||
}
|
||||
}
|
||||
|
||||
setTaskForm({});
|
||||
setIsEditingTask(false);
|
||||
setEditingTaskIndex(null);
|
||||
};
|
||||
|
||||
const handleStartNewTask = () => {
|
||||
setTaskForm({});
|
||||
setEditingTaskIndex(null);
|
||||
// Create a temporary new task and add it to the list in edit mode
|
||||
const tempTask: ProjectTask = {
|
||||
id: 'temp-' + Date.now(),
|
||||
title: '',
|
||||
description: '',
|
||||
priority: 'medium',
|
||||
status: 'todo',
|
||||
estimatedHours: 0
|
||||
};
|
||||
|
||||
const newTasks = [...projectTasks, tempTask];
|
||||
setProjectTasks(newTasks);
|
||||
setEditingTaskIndex(newTasks.length - 1);
|
||||
setTaskForm({
|
||||
title: '',
|
||||
description: '',
|
||||
priority: 'medium',
|
||||
status: 'todo',
|
||||
estimatedHours: 0
|
||||
});
|
||||
setIsEditingTask(true);
|
||||
};
|
||||
|
||||
@@ -962,49 +995,149 @@ export default function ProjectTemplate({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Task List */}
|
||||
{/* Task List with Inline Editing */}
|
||||
<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}
|
||||
<Card key={task.id} className={`p-3 ${editingTaskIndex === index ? 'border-primary' : ''}`}>
|
||||
{editingTaskIndex === index ? (
|
||||
// Inline Edit Mode
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">Task Title</label>
|
||||
<Input
|
||||
value={taskForm.title || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, title: e.target.value})}
|
||||
placeholder="Enter task title"
|
||||
className="text-sm"
|
||||
data-testid={`input-inline-task-title-${index}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">Description</label>
|
||||
<Input
|
||||
value={taskForm.description || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, description: e.target.value})}
|
||||
placeholder="Enter task description"
|
||||
className="text-sm"
|
||||
data-testid={`input-inline-task-description-${index}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">Priority</label>
|
||||
<Select
|
||||
value={taskForm.priority || 'medium'}
|
||||
onValueChange={(value: ProjectTask['priority']) =>
|
||||
setTaskForm({...taskForm, priority: value})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="text-sm" data-testid={`select-inline-task-priority-${index}`}>
|
||||
<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-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">Status</label>
|
||||
<Select
|
||||
value={taskForm.status || 'todo'}
|
||||
onValueChange={(value: ProjectTask['status']) =>
|
||||
setTaskForm({...taskForm, status: value})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="text-sm" data-testid={`select-inline-task-status-${index}`}>
|
||||
<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-1 md:col-span-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">Estimated Hours</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={taskForm.estimatedHours || ''}
|
||||
onChange={(e) => setTaskForm({...taskForm, estimatedHours: Number(e.target.value)})}
|
||||
placeholder="0"
|
||||
min="0"
|
||||
className="text-sm max-w-32"
|
||||
data-testid={`input-inline-task-hours-${index}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 justify-end pt-2 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleCancelTaskEdit}
|
||||
data-testid={`button-cancel-inline-edit-${index}`}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleSaveTask}
|
||||
disabled={!taskForm.title?.trim()}
|
||||
data-testid={`button-save-inline-edit-${index}`}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</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>
|
||||
) : (
|
||||
// Display Mode
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
|
||||
@@ -1015,106 +1148,6 @@ export default function ProjectTemplate({
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user