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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user