diff --git a/client/src/components/TaskDetailsModal.tsx b/client/src/components/TaskDetailsModal.tsx index 4f7cfca..36679e1 100644 --- a/client/src/components/TaskDetailsModal.tsx +++ b/client/src/components/TaskDetailsModal.tsx @@ -13,7 +13,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import { cn } from "@/lib/utils"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useQuery } from '@tanstack/react-query'; -import { Clock, Timer, FileText, Plus, Edit2, Save, X, Calendar as CalendarIcon, Tag, Link2, Check, LayoutList, Trash2, ArrowRight } from 'lucide-react'; +import { Clock, Timer, FileText, Plus, Edit2, Save, X, Calendar as CalendarIcon, Tag, Link2, Check, LayoutList, Trash2, ArrowRight, CheckCircle2 } from 'lucide-react'; import { Calendar } from '@/components/ui/calendar'; import { format } from 'date-fns'; import { de, enUS } from 'date-fns/locale'; @@ -75,7 +75,7 @@ export default function TaskDetailsModal({ const [notes, setNotes] = useState(''); const [isEditingNotes, setIsEditingNotes] = useState(false); - // Time reporting state + // Time reporting state const [isAddingTime, setIsAddingTime] = useState(false); const [hours, setHours] = useState(0); const [minutes, setMinutes] = useState(0); @@ -83,6 +83,13 @@ export default function TaskDetailsModal({ const [timeDescription, setTimeDescription] = useState(''); const [activeTimeTab, setActiveTimeTab] = useState('clock'); + // Mark as done flow state + const [showTimeRequiredDialog, setShowTimeRequiredDialog] = useState(false); + const [markDoneHours, setMarkDoneHours] = useState(0); + const [markDoneMinutes, setMarkDoneMinutes] = useState(0); + const [markDoneManualHours, setMarkDoneManualHours] = useState(''); + const [markDoneTimeTab, setMarkDoneTimeTab] = useState('clock'); + // Time entries state - simulate from timeTracked for display const [timeEntries, setTimeEntries] = useState([]); @@ -130,9 +137,86 @@ export default function TaskDetailsModal({ setMinutes(0); setManualHours(''); setTimeDescription(''); + setShowTimeRequiredDialog(false); + setMarkDoneHours(0); + setMarkDoneMinutes(0); + setMarkDoneManualHours(''); onClose(); }; + // Handle Mark as Done button click + const handleMarkAsDone = () => { + if (!task) return; + + // Check if time has been tracked + if (!task.timeTracked || task.timeTracked === 0) { + // Show dialog to enter time first + setShowTimeRequiredDialog(true); + setMarkDoneHours(0); + setMarkDoneMinutes(0); + setMarkDoneManualHours(''); + setMarkDoneTimeTab('clock'); + } else { + // Time already tracked, mark as done directly + completeTaskAsDone(); + } + }; + + // Complete the task as done + const completeTaskAsDone = () => { + if (!task) return; + + const updatedTask: Task = { + ...task, + title: editedTitle, + description: editedDescription || null, + status: 'done', + priority: editedPriority, + labelId: editedLabelId, + dueDate: editedDueDate ? new Date(editedDueDate) : null, + startDate: editedStartDate ? new Date(editedStartDate) : null, + estimatedDuration: editedEstimatedDuration ?? null, + dependencies: editedDependencies + }; + onSave(updatedTask); + handleClose(); + }; + + // Handle saving time and then marking as done + const handleSaveTimeAndMarkDone = () => { + if (!task) return; + + let totalMinutes = 0; + + if (markDoneTimeTab === 'clock') { + totalMinutes = markDoneHours * 60 + markDoneMinutes; + } else { + const parsedHours = parseFloat(markDoneManualHours) || 0; + totalMinutes = Math.round(parsedHours * 60); + } + + if (totalMinutes <= 0) return; + + // Create updated task with time and done status + const updatedTask: Task = { + ...task, + title: editedTitle, + description: editedDescription || null, + status: 'done', + priority: editedPriority, + labelId: editedLabelId, + dueDate: editedDueDate ? new Date(editedDueDate) : null, + startDate: editedStartDate ? new Date(editedStartDate) : null, + estimatedDuration: editedEstimatedDuration ?? null, + dependencies: editedDependencies, + timeTracked: (task.timeTracked || 0) + totalMinutes + }; + + onSave(updatedTask); + setShowTimeRequiredDialog(false); + handleClose(); + }; + const handleSaveTaskEdit = () => { if (!task) return; @@ -322,6 +406,7 @@ export default function TaskDetailsModal({ const isSubtask = !!task.parentTaskId; return ( + <> @@ -717,7 +802,7 @@ export default function TaskDetailsModal({ - {/* Save Button */} + {/* Save and Mark as Done Buttons */}
+ {editedStatus !== 'done' && ( + + )}
@@ -1013,6 +1110,139 @@ export default function TaskDetailsModal({
-
+ + + {/* Time Required Dialog - shown when marking as done without time */} + + + + + + {t('taskDetails.timeRequired', 'Time Required')} + + + +
+

+ {t('taskDetails.timeRequiredDesc', 'Please enter the time spent on this task before marking it as done.')} +

+ + + + + + {t('taskDetails.clock', 'Clock')} + + + + {t('taskDetails.manual', 'Manual')} + + + + +
+
+
+ {formatTime(markDoneHours, markDoneMinutes)} +
+
+ +
+ {/* Hours Picker */} +
+ +
+ {Array.from({ length: 13 }, (_, i) => ( + + ))} +
+
+ + {/* Minutes Picker */} +
+ +
+ {Array.from({ length: 12 }, (_, i) => i * 5).map((minute) => ( + + ))} +
+
+
+
+
+ + +
+
+
+ {(() => { + const parsedHours = parseFloat(markDoneManualHours) || 0; + const h = Math.floor(parsedHours); + const m = Math.round((parsedHours - h) * 60); + return formatTime(h, m); + })()} +
+
+ +
+ + setMarkDoneManualHours(e.target.value)} + /> +

+ {t('taskDetails.timeExamples', 'Examples: 0.5 = 30min, 1.25 = 1h 15min')} +

+
+
+
+
+ +
+ + +
+
+
+
+ ); } \ No newline at end of file diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json index 857c468..a2a219e 100644 --- a/client/src/i18n/locales/de.json +++ b/client/src/i18n/locales/de.json @@ -197,7 +197,15 @@ "durationUnits": { "m": "Min", "h": "Std" - } + }, + "markAsDone": "Als erledigt markieren", + "timeRequired": "Zeitangabe erforderlich", + "timeRequiredDesc": "Bitte geben Sie die für diese Aufgabe aufgewendete Zeit ein, bevor Sie sie als erledigt markieren.", + "hours": "Stunden", + "minutes": "Minuten", + "hoursDecimal": "Stunden (dezimal)", + "timeExamples": "Beispiele: 0.5 = 30 Min, 1.25 = 1 Std 15 Min", + "saveAndMarkDone": "Speichern & Als erledigt markieren" }, "taskCard": { "play": "Timer starten", diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index 6678dcd..9874a64 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -195,7 +195,15 @@ "durationUnits": { "m": "m", "h": "h" - } + }, + "markAsDone": "Mark as Done", + "timeRequired": "Time Required", + "timeRequiredDesc": "Please enter the time spent on this task before marking it as done.", + "hours": "Hours", + "minutes": "Minutes", + "hoursDecimal": "Hours (decimal)", + "timeExamples": "Examples: 0.5 = 30min, 1.25 = 1h 15min", + "saveAndMarkDone": "Save & Mark as Done" }, "taskCard": { "play": "Start timer",