From 58fed6e6c5041e78d9108c91f9920bd0783c0145 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:45:35 +0000 Subject: [PATCH 1/2] Initial plan From d5c7f8dc06592e0e93184e8fbbb08c86ddbebbf1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:00:28 +0000 Subject: [PATCH 2/2] Complete DueDate implementation for TaskHub tasks Co-authored-by: tjarkpr <56918325+tjarkpr@users.noreply.github.com> --- src/app/components/AddTodoForm/component.tsx | 12 ++- src/app/components/AddTodoForm/style.css | 15 +++- src/app/components/TodoItem/component.tsx | 84 ++++++++++++++++++- src/app/components/TodoItem/style.css | 86 ++++++++++++++++++++ src/app/components/TodoList/component.tsx | 5 +- src/app/context/TodoContext.tsx | 12 ++- src/app/types/TodoTypes.ts | 4 +- 7 files changed, 207 insertions(+), 11 deletions(-) diff --git a/src/app/components/AddTodoForm/component.tsx b/src/app/components/AddTodoForm/component.tsx index 66cafdf..6ef0dc2 100644 --- a/src/app/components/AddTodoForm/component.tsx +++ b/src/app/components/AddTodoForm/component.tsx @@ -4,12 +4,16 @@ import './style.css'; const AddTodoForm: React.FC = () => { const [todoText, setTodoText] = useState(''); + const [dueDate, setDueDate] = useState(''); const { addTodo } = useTodoContext(); + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (todoText.trim()) { - addTodo(todoText); + const dueDateObj = dueDate ? new Date(dueDate) : undefined; + addTodo(todoText, dueDateObj); setTodoText(''); + setDueDate(''); } }; return ( @@ -20,6 +24,12 @@ const AddTodoForm: React.FC = () => { onChange={(e) => setTodoText(e.target.value)} placeholder="Add a new task..." /> + setDueDate(e.target.value)} + placeholder="Due date (optional)" + /> ); diff --git a/src/app/components/AddTodoForm/style.css b/src/app/components/AddTodoForm/style.css index 8673f17..387f7b2 100644 --- a/src/app/components/AddTodoForm/style.css +++ b/src/app/components/AddTodoForm/style.css @@ -1,11 +1,20 @@ .add-todo-form { display: flex; - justify-content: space-between; + gap: 8px; border-left: 4px solid var(--primary-color); padding: 5px; } -.add-todo-form input { +.add-todo-form input[type="text"] { + flex: 2; + padding: 10px; + border: none; + border-bottom: 1px solid var(--primary-color); + background-color: transparent; + color: var(--text-color); +} + +.add-todo-form input[type="date"] { flex: 1; padding: 10px; border: none; @@ -15,10 +24,10 @@ } .add-todo-form button { - margin-left: 5px; padding: 10px 15px; background-color: var(--primary-color); color: var(--secondary-color); border: none; border-radius: 4px; + flex-shrink: 0; } diff --git a/src/app/components/TodoItem/component.tsx b/src/app/components/TodoItem/component.tsx index e264a2e..60a71d3 100644 --- a/src/app/components/TodoItem/component.tsx +++ b/src/app/components/TodoItem/component.tsx @@ -11,6 +11,7 @@ interface TodoItemProps { onPauseTracking: (id: string) => void; onUpdateTime: (id: string, newTimeInMs: number) => void; onUpdateText: (id: string, newText: string) => void; + onUpdateDueDate: (id: string, dueDate?: Date) => void; } const TodoItem: React.FC = ({ @@ -21,7 +22,8 @@ const TodoItem: React.FC = ({ onStartTracking, onPauseTracking, onUpdateTime, - onUpdateText + onUpdateText, + onUpdateDueDate }) => { const [displayTime, setDisplayTime] = useState(todo.totalTimeSpent); const [isEditingTime, setIsEditingTime] = useState(false); @@ -30,6 +32,10 @@ const TodoItem: React.FC = ({ const [seconds, setSeconds] = useState(0); const [isEditingText, setIsEditingText] = useState(false); const [editedText, setEditedText] = useState(todo.text); + const [isEditingDueDate, setIsEditingDueDate] = useState(false); + const [editedDueDate, setEditedDueDate] = useState( + todo.dueDate ? todo.dueDate.toISOString().split('T')[0] : '' + ); const startTimeEdit = () => { if (todo.isTracking) return; @@ -75,6 +81,30 @@ const TodoItem: React.FC = ({ } }; + const startDueDateEdit = () => { + setEditedDueDate(todo.dueDate ? todo.dueDate.toISOString().split('T')[0] : ''); + setIsEditingDueDate(true); + }; + + const saveDueDateEdit = () => { + const newDueDate = editedDueDate ? new Date(editedDueDate) : undefined; + onUpdateDueDate(todo.id, newDueDate); + setIsEditingDueDate(false); + }; + + const cancelDueDateEdit = () => { + setEditedDueDate(todo.dueDate ? todo.dueDate.toISOString().split('T')[0] : ''); + setIsEditingDueDate(false); + }; + + const handleDueDateKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + saveDueDateEdit(); + } else if (e.key === 'Escape') { + cancelDueDateEdit(); + } + }; + useEffect(() => { if (!todo.isTracking) { setDisplayTime(todo.totalTimeSpent); @@ -109,6 +139,18 @@ const TodoItem: React.FC = ({ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; }; + const formatDueDate = (date: Date): string => { + return date.toLocaleDateString(); + }; + + const isDueDateOverdue = (date: Date): boolean => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const dueDate = new Date(date); + dueDate.setHours(0, 0, 0, 0); + return dueDate < today; + }; + return (
@@ -123,7 +165,7 @@ const TodoItem: React.FC = ({
- {todo.status === 'In Progress' && !isEditingTime && !isEditingText && ( + {todo.status === 'In Progress' && !isEditingTime && !isEditingText && !isEditingDueDate && ( <> {!todo.isTracking ? (
- {todo.status === 'Done' && !isEditingText && !isEditingTime && ( + {todo.status === 'Done' && !isEditingText && !isEditingTime && !isEditingDueDate && ( + +
+ ) : ( + <> + {todo.dueDate ? ( + + Due: {formatDueDate(todo.dueDate)} + + ) : ( + + )} + + )} + ); diff --git a/src/app/components/TodoItem/style.css b/src/app/components/TodoItem/style.css index fbe62d1..24d8a2a 100644 --- a/src/app/components/TodoItem/style.css +++ b/src/app/components/TodoItem/style.css @@ -192,4 +192,90 @@ .completed .todo-text { text-decoration: line-through; opacity: 0.7; +} + +.todo-due-date { + display: flex; + align-items: center; + gap: 8px; + margin-top: 4px; +} + +.due-date { + font-size: 0.9rem; + color: var(--text-color); + padding: 2px 6px; + border-radius: 4px; + background-color: var(--card-background); + border: 1px solid var(--border-color); +} + +.due-date.editable { + cursor: pointer; +} + +.due-date.editable:hover { + background-color: var(--primary-color); + color: var(--secondary-color); +} + +.due-date.overdue { + background-color: #ff4444; + color: white; + border-color: #ff4444; +} + +.add-due-date-btn { + font-size: 0.8rem; + padding: 2px 6px; + border: 1px dashed var(--border-color); + background-color: transparent; + color: var(--text-color); + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +.add-due-date-btn:hover { + background-color: var(--primary-color); + color: var(--secondary-color); + border-style: solid; +} + +.due-date-edit { + display: flex; + align-items: center; + gap: 4px; +} + +.due-date-input { + padding: 2px 4px; + border: none; + border-bottom: 1px solid var(--primary-color); + background-color: transparent; + color: var(--text-color); +} + +.due-date-save-btn, +.due-date-cancel-btn { + width: 20px; + height: 20px; + border-radius: 50%; + border: 1px solid var(--border-color); + background-color: var(--card-background); + cursor: pointer; + font-size: 0.7rem; + display: flex; + align-items: center; + justify-content: center; +} + +.due-date-save-btn:hover { + background-color: var(--primary-color); + color: var(--secondary-color); +} + +.due-date-cancel-btn:hover { + background-color: var(--primary-color); + color: var(--secondary-color); } \ No newline at end of file diff --git a/src/app/components/TodoList/component.tsx b/src/app/components/TodoList/component.tsx index 7514e00..304b91d 100644 --- a/src/app/components/TodoList/component.tsx +++ b/src/app/components/TodoList/component.tsx @@ -13,7 +13,8 @@ const TodoList: React.FC = () => { startTimeTracking, pauseTimeTracking, updateTodoTime, - updateTodoText + updateTodoText, + updateTodoDueDate } = useTodoContext(); const activeTodos = todos.filter(todo => todo.status !== 'Done'); @@ -38,6 +39,7 @@ const TodoList: React.FC = () => { onPauseTracking={pauseTimeTracking} onUpdateTime={updateTodoTime} onUpdateText={updateTodoText} + onUpdateDueDate={updateTodoDueDate} /> ))} @@ -61,6 +63,7 @@ const TodoList: React.FC = () => { onPauseTracking={pauseTimeTracking} onUpdateTime={updateTodoTime} onUpdateText={updateTodoText} + onUpdateDueDate={updateTodoDueDate} /> ))} diff --git a/src/app/context/TodoContext.tsx b/src/app/context/TodoContext.tsx index 69a2f8c..2ec378d 100644 --- a/src/app/context/TodoContext.tsx +++ b/src/app/context/TodoContext.tsx @@ -130,7 +130,7 @@ export const TodoProvider: React.FC = ({ children }) => { return success; }; - const addTodo = (text: string) => { + const addTodo = (text: string, dueDate?: Date) => { const newTodo: Todo = { id: uuidv4(), number: getNextTicketNumber(), @@ -138,7 +138,8 @@ export const TodoProvider: React.FC = ({ children }) => { status: 'Open', timeLogs: [], isTracking: false, - totalTimeSpent: 0 + totalTimeSpent: 0, + dueDate }; updateTodos([...todos, newTodo]); @@ -170,6 +171,12 @@ export const TodoProvider: React.FC = ({ children }) => { )); }; + const updateTodoDueDate = (id: string, dueDate?: Date) => { + updateTodos(todos.map(todo => + todo.id === id ? { ...todo, dueDate } : todo + )); + }; + const updateTodoTime = (id: string, newTimeInMs: number) => { updateTodos(todos.map(todo => todo.id === id ? { ...todo, totalTimeSpent: newTimeInMs } : todo @@ -229,6 +236,7 @@ export const TodoProvider: React.FC = ({ children }) => { pauseTimeTracking, updateTodoTime, updateTodoText, + updateTodoDueDate, createNewFile, openExistingFile, changeFile, diff --git a/src/app/types/TodoTypes.ts b/src/app/types/TodoTypes.ts index cfcf9cc..648e97d 100644 --- a/src/app/types/TodoTypes.ts +++ b/src/app/types/TodoTypes.ts @@ -15,6 +15,7 @@ interface Todo { timeLogs: TimeLog[]; isTracking: boolean; totalTimeSpent: number; + dueDate?: Date; } interface TodoProviderProps { @@ -29,7 +30,7 @@ interface SavedState { interface TodoContextType { todos: Todo[]; - addTodo: (text: string) => void; + addTodo: (text: string, dueDate?: Date) => void; removeTodo: (id: string) => void; toggleTodo: (id: string) => void; updateTodoStatus: (id: string, status: TodoStatus) => void; @@ -37,6 +38,7 @@ interface TodoContextType { pauseTimeTracking: (id: string) => void; updateTodoTime: (id: string, newTimeInMs: number) => void; updateTodoText: (id: string, newText: string) => void; + updateTodoDueDate: (id: string, dueDate?: Date) => void; createNewFile: () => Promise; openExistingFile: () => Promise; changeFile: () => Promise;