import React, { useState, useRef, useEffect } from "react"; import { Repeat } from "lucide-react"; import { Task } from "./WeeklyView"; interface GridTaskBlockProps { task: Task; date: Date; activeDate: Date; cellDuration: number; darkMode: boolean; isProtected: boolean; editingTaskId: string | null; setEditingTaskId: (id: string | null) => void; updateTask: (id: string, title: string) => void; updateTaskNotes: (id: string, notes: string) => void; updateTaskDuration: (id: string, duration: number) => void; toggleTask: (id: string) => void; deleteTask: (id: string) => void; toggleTaskRolling: (id: string) => void; setSelectedTaskForNotes: (task: Task) => void; setSelectedTaskForRecurrence: (task: Task) => void; handleDragStart: (e: React.DragEvent, task: Task) => void; handleDragEnd: (e: React.DragEvent) => void; getSlotHeight: (duration: number) => number; draggedTask: Task | null; addSubTask: (parentId: string, title: string) => void; toggleSubTask: (id: string) => void; updateSubTask: (id: string, title: string) => void; deleteSubTask: (id: string) => void; onSetEditingTaskId?: (id: string | null) => void; workingHoursStart: number; showTaskCheckboxes?: boolean; } export function GridTaskBlock({ task, date, activeDate, cellDuration, darkMode, isProtected, editingTaskId, setEditingTaskId, updateTask, updateTaskNotes, updateTaskDuration, toggleTask, deleteTask, toggleTaskRolling, setSelectedTaskForNotes, setSelectedTaskForRecurrence, handleDragStart, handleDragEnd, getSlotHeight, draggedTask, addSubTask, toggleSubTask, updateSubTask, deleteSubTask, onSetEditingTaskId, workingHoursStart, showTaskCheckboxes }: GridTaskBlockProps) { const [isNotesOpen, setIsNotesOpen] = useState(false); const [notesValue, setNotesValue] = useState(task.markdownContent || ""); const [isSubTasksOpen, setIsSubTasksOpen] = useState(false); const [isSubTaskInputOpen, setIsSubTaskInputOpen] = useState(false); const [newSubTaskTitle, setNewSubTaskTitle] = useState(""); const notesRef = useRef(null); const subTaskInputRef = useRef(null); // Resize State const [isResizing, setIsResizing] = useState(false); const [resizeHeight, setResizeHeight] = useState(null); const resizeStartY = useRef(0); const resizeStartHeight = useRef(0); // Focus notes when opened useEffect(() => { if (isNotesOpen && notesRef.current) { notesRef.current.focus(); } }, [isNotesOpen]); const handleNotesBlur = () => { if (notesValue !== task.markdownContent) { updateTaskNotes(task.id, notesValue); } }; const insertMarkdown = (prefix: string, suffix: string = "") => { if (!notesRef.current) return; const start = notesRef.current.selectionStart; const end = notesRef.current.selectionEnd; const text = notesValue; const before = text.substring(0, start); const selection = text.substring(start, end); const after = text.substring(end); const newText = `${before}${prefix}${selection}${suffix}${after}`; setNotesValue(newText); setTimeout(() => { if (notesRef.current) { notesRef.current.focus(); const newCursorPos = start + prefix.length + selection.length + suffix.length; notesRef.current.setSelectionRange(newCursorPos, newCursorPos); } }, 0); }; const pixelsPerMinute = getSlotHeight(cellDuration) / cellDuration; useEffect(() => { if (!task.startTime) return; const handleResizeMove = (e: MouseEvent) => { if (!isResizing) return; const deltaY = e.clientY - resizeStartY.current; let newHeight = resizeStartHeight.current + deltaY; const minHeight = 15 * pixelsPerMinute; if (newHeight < minHeight) newHeight = minHeight; setResizeHeight(newHeight); }; const handleResizeEnd = () => { if (!isResizing) return; setIsResizing(false); document.body.style.cursor = ""; if (resizeHeight !== null) { const newDurationMins = Math.round(resizeHeight / pixelsPerMinute / 15) * 15; updateTaskDuration(task.id, newDurationMins); } setResizeHeight(null); }; if (isResizing) { window.addEventListener("mousemove", handleResizeMove); window.addEventListener("mouseup", handleResizeEnd); } return () => { window.removeEventListener("mousemove", handleResizeMove); window.removeEventListener("mouseup", handleResizeEnd); }; }, [isResizing, resizeHeight, pixelsPerMinute, task.id, updateTaskDuration, task.startTime]); // Conditional rendering should only happen after hooks if (!task.startTime) return null; const [startHour, startMinute] = task.startTime.split(":").map(Number); const startMinutes = (startHour - workingHoursStart) * 60 + startMinute; const topOffset = startMinutes * pixelsPerMinute; const duration = task.duration || 15; const baseHeight = duration * pixelsPerMinute; const currentHeight = isResizing && resizeHeight !== null ? resizeHeight : baseHeight; const onResizeStart = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); setIsResizing(true); resizeStartY.current = e.clientY; resizeStartHeight.current = baseHeight; document.body.style.cursor = "ns-resize"; }; return (
handleDragStart(e, task)} onDragEnd={handleDragEnd} onClick={(e) => { e.stopPropagation(); if (editingTaskId !== task.id) toggleTask(task.id); }} >
{editingTaskId === task.id ? (
{ e.preventDefault(); const input = e.currentTarget.elements.namedItem("title") as HTMLInputElement; updateTask(task.id, input.value || \"\"); }} onClick={(e) => e.stopPropagation()} style={{ width: "100%", paddingRight: "20px" }} > updateTask(task.id, e.target.value)} onKeyDown={(e) => { if (e.key === "Escape") setEditingTaskId(null); if (e.key === "Enter") e.currentTarget.blur(); }} className="weekly-task-text" style={{ width: "100%", background: "transparent", border: "none", borderBottom: "1px solid var(--weekly-border)", outline: "none" }} />
) : ( { e.stopPropagation(); setEditingTaskId(task.id); }} > {showTaskCheckboxes && ( { e.stopPropagation(); toggleTask(task.id); }} onClick={(e) => e.stopPropagation()} className="task-checkbox flex-shrink-0" style={{ width: "12px", height: "12px", margin: 0, cursor: "pointer", position: "relative", top: "3px", left: "-2px", accentColor: "#FFF" }} /> )} {task.title} )}
e.stopPropagation()} > {!task.completed && ( )} { !task.completed && ( )}
{isNotesOpen && (
180 ? \"on-top\" : \"\"}`} onClick={(e) => e.stopPropagation()} style={{ top: topOffset > 180 ? \"auto\" : \"100%\", bottom: topOffset > 180 ? \"100%\" : \"auto\", marginTop: topOffset > 180 ? \"0\" : \"10px\", marginBottom: topOffset > 180 ? \"10px\" : \"0\", left: \"-10px\", right: \"-10px\", width: \"auto\", }} >
Markdown