Improve task interaction and display within the app

Introduces a TaskDetailsModal and integrates task click handlers into CalendarView and KanbanBoard components. Updates TaskDetailsModal to display and manage time tracking, and refactors prop types for task data.

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:13:15 +00:00
parent 717c1b5ff5
commit d69c81185b
5 changed files with 80 additions and 43 deletions
+31 -35
View File
@@ -8,19 +8,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Clock, Timer, FileText, Plus, Edit2, Save, X, Calendar } from 'lucide-react';
interface ProjectTask {
id: string;
title: string;
description?: string;
priority: 'low' | 'medium' | 'high';
status: 'todo' | 'inProgress' | 'done';
estimatedHours?: number;
labelId?: string;
notes?: string;
timeTracked?: number; // in minutes
timeEntries?: TimeEntry[];
}
import { Task, Label } from '@shared/schema';
interface TimeEntry {
id: string;
@@ -32,9 +20,9 @@ interface TimeEntry {
interface TaskDetailsModalProps {
isOpen: boolean;
onClose: () => void;
task: ProjectTask | null;
onSave: (updatedTask: ProjectTask) => void;
labels?: Array<{ id: string; name: string; color: string }>;
task: Task | null;
onSave: (updatedTask: Task) => void;
labels?: Label[];
}
export default function TaskDetailsModal({
@@ -56,13 +44,23 @@ export default function TaskDetailsModal({
const [timeDescription, setTimeDescription] = useState('');
const [activeTimeTab, setActiveTimeTab] = useState('clock');
// Time entries state
// Time entries state - simulate from timeTracked for display
const [timeEntries, setTimeEntries] = useState<TimeEntry[]>([]);
useEffect(() => {
if (task && isOpen) {
setNotes(task.notes || '');
setTimeEntries(task.timeEntries || []);
// Create a single time entry from timeTracked for display purposes
if (task.timeTracked > 0) {
setTimeEntries([{
id: 'tracked-time',
date: new Date(),
timeSpent: task.timeTracked,
description: 'Previously tracked time'
}]);
} else {
setTimeEntries([]);
}
// Reset time input state
setHours(0);
setMinutes(0);
@@ -119,7 +117,6 @@ export default function TaskDetailsModal({
const updatedTask = {
...task,
timeEntries: updatedTimeEntries,
timeTracked: updatedTimeTracked
};
@@ -137,7 +134,10 @@ export default function TaskDetailsModal({
};
const handleDeleteTimeEntry = (entryId: string) => {
if (!task) return;
if (!task || entryId === 'tracked-time') {
// Don't allow deleting the main tracked time entry
return;
}
const entryToDelete = timeEntries.find(entry => entry.id === entryId);
if (!entryToDelete) return;
@@ -147,7 +147,6 @@ export default function TaskDetailsModal({
const updatedTask = {
...task,
timeEntries: updatedTimeEntries,
timeTracked: updatedTimeTracked
};
@@ -177,7 +176,7 @@ export default function TaskDetailsModal({
};
const getTotalTrackedTime = () => {
return timeEntries.reduce((total, entry) => total + entry.timeSpent, 0);
return task?.timeTracked || 0;
};
const getPriorityColor = (priority: string) => {
@@ -231,11 +230,6 @@ export default function TaskDetailsModal({
<Badge variant="outline" className={`text-xs ${getStatusColor(task.status)}`}>
{task.status}
</Badge>
{task.estimatedHours && (
<Badge variant="outline" className="text-xs">
{task.estimatedHours}h estimated
</Badge>
)}
{getTotalTrackedTime() > 0 && (
<Badge variant="outline" className="text-xs">
{formatMinutesToHours(getTotalTrackedTime())} tracked
@@ -502,14 +496,16 @@ export default function TaskDetailsModal({
</p>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteTimeEntry(entry.id)}
data-testid={`button-delete-time-entry-${entry.id}`}
>
<X className="w-3 h-3" />
</Button>
{entry.id !== 'tracked-time' && (
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteTimeEntry(entry.id)}
data-testid={`button-delete-time-entry-${entry.id}`}
>
<X className="w-3 h-3" />
</Button>
)}
</div>
))}
</div>