Enable editing task details and tracking time within project views

Adds a modal for viewing and editing task notes and time entries, integrates task-level clicks for detail access, and introduces time tracking functionality.

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 22:03:27 +00:00
parent d08429e74b
commit 8b5727afb0
3 changed files with 642 additions and 5 deletions
+100 -4
View File
@@ -8,8 +8,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
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 { Plus, Calendar as CalendarIcon, Clock, Copy, Play, Tag, Edit, Trash2, Palette, FileText } from 'lucide-react';
import { Label } from '@shared/schema';
import TaskDetailsModal from '@/components/TaskDetailsModal';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiRequest } from '@/lib/queryClient';
@@ -19,6 +20,13 @@ const getStartOfToday = () => {
return new Date(today.getFullYear(), today.getMonth(), today.getDate());
};
interface TimeEntry {
id: string;
date: Date;
timeSpent: number; // in minutes
description?: string;
}
interface ProjectTask {
id: string;
title: string;
@@ -27,6 +35,9 @@ interface ProjectTask {
status: 'todo' | 'inProgress' | 'done';
estimatedHours?: number;
labelId?: string;
notes?: string;
timeTracked?: number; // in minutes
timeEntries?: TimeEntry[];
}
interface Project {
@@ -122,6 +133,10 @@ export default function ProjectTemplate({
const [labelName, setLabelName] = useState('');
const [labelColor, setLabelColor] = useState('#3B82F6');
// Task details modal state
const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false);
const [selectedTaskForDetails, setSelectedTaskForDetails] = useState<ProjectTask | null>(null);
const queryClient = useQueryClient();
// Fetch labels
@@ -362,6 +377,43 @@ export default function ProjectTemplate({
setIsEditingTask(true);
};
// Task details modal handlers
const handleOpenTaskDetails = (task: ProjectTask, isFromProject: boolean = false) => {
setSelectedTaskForDetails(task);
setIsTaskDetailsOpen(true);
};
const handleCloseTaskDetails = () => {
setSelectedTaskForDetails(null);
setIsTaskDetailsOpen(false);
};
const handleSaveTaskDetails = (updatedTask: ProjectTask) => {
// Update the task in projectTasks array
const taskIndex = projectTasks.findIndex(task => task.id === updatedTask.id);
if (taskIndex !== -1) {
const updatedTasks = [...projectTasks];
updatedTasks[taskIndex] = updatedTask;
setProjectTasks(updatedTasks);
console.log('Task updated:', updatedTask.title, 'with notes and time tracking');
}
// If we're in edit project mode, also update the selected project
if (selectedProject) {
const updatedProject = { ...selectedProject };
const projectTaskIndex = updatedProject.tasks.findIndex(task => task.id === updatedTask.id);
if (projectTaskIndex !== -1) {
updatedProject.tasks[projectTaskIndex] = updatedTask;
setSelectedProject(updatedProject);
// Update the projects array
setProjects(projects.map(p =>
p.id === selectedProject.id ? updatedProject : p
));
}
}
};
const handleSaveEditedProject = () => {
if (selectedProject) {
const updatedProject = {
@@ -1206,7 +1258,11 @@ export default function ProjectTemplate({
) : (
// Display Mode
<div className="flex items-center justify-between">
<div className="flex-1">
<div
className="flex-1 cursor-pointer hover-elevate rounded-md p-2 -m-2"
onClick={() => handleOpenTaskDetails(task)}
data-testid={`clickable-task-${index}`}
>
<h4 className="font-medium text-sm">{task.title}</h4>
{task.description && (
<p className="text-xs text-muted-foreground">{task.description}</p>
@@ -1223,22 +1279,53 @@ export default function ProjectTemplate({
{task.estimatedHours}h
</Badge>
) : null}
{task.timeTracked && task.timeTracked > 0 && (
<Badge variant="outline" className="text-xs bg-blue-50 text-blue-700 border-blue-200">
{Math.floor(task.timeTracked / 60)}h {task.timeTracked % 60}m tracked
</Badge>
)}
{task.notes && task.notes.trim() && (
<Badge variant="outline" className="text-xs bg-green-50 text-green-700 border-green-200">
<FileText className="w-3 h-3 mr-1" />
Notes
</Badge>
)}
</div>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleEditTask(index)}
onClick={(e) => {
e.stopPropagation();
handleOpenTaskDetails(task);
}}
data-testid={`button-details-task-${index}`}
title="View task details"
>
<FileText className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
handleEditTask(index);
}}
data-testid={`button-edit-task-edit-${index}`}
title="Edit task"
>
<Edit className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveTask(index)}
onClick={(e) => {
e.stopPropagation();
handleRemoveTask(index);
}}
data-testid={`button-remove-task-edit-${index}`}
title="Remove task"
>
<Trash2 className="w-3 h-3" />
</Button>
@@ -1422,6 +1509,15 @@ export default function ProjectTemplate({
)}
</TabsContent>
</Tabs>
{/* Task Details Modal */}
<TaskDetailsModal
isOpen={isTaskDetailsOpen}
onClose={handleCloseTaskDetails}
task={selectedTaskForDetails}
onSave={handleSaveTaskDetails}
labels={labels}
/>
</div>
);
}