Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/app/components/AddTodoForm/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -20,6 +24,12 @@ const AddTodoForm: React.FC = () => {
onChange={(e) => setTodoText(e.target.value)}
placeholder="Add a new task..."
/>
<input
type="date"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
placeholder="Due date (optional)"
/>
<button type="submit">Add</button>
</form>
);
Expand Down
15 changes: 12 additions & 3 deletions src/app/components/AddTodoForm/style.css
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
84 changes: 81 additions & 3 deletions src/app/components/TodoItem/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TodoItemProps> = ({
Expand All @@ -21,7 +22,8 @@ const TodoItem: React.FC<TodoItemProps> = ({
onStartTracking,
onPauseTracking,
onUpdateTime,
onUpdateText
onUpdateText,
onUpdateDueDate
}) => {
const [displayTime, setDisplayTime] = useState(todo.totalTimeSpent);
const [isEditingTime, setIsEditingTime] = useState(false);
Expand All @@ -30,6 +32,10 @@ const TodoItem: React.FC<TodoItemProps> = ({
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;
Expand Down Expand Up @@ -75,6 +81,30 @@ const TodoItem: React.FC<TodoItemProps> = ({
}
};

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<HTMLInputElement>) => {
if (e.key === 'Enter') {
saveDueDateEdit();
} else if (e.key === 'Escape') {
cancelDueDateEdit();
}
};

useEffect(() => {
if (!todo.isTracking) {
setDisplayTime(todo.totalTimeSpent);
Expand Down Expand Up @@ -109,6 +139,18 @@ const TodoItem: React.FC<TodoItemProps> = ({
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 (
<div className={`todo-item ${getStatusColor()} ${todo.status === 'Done' ? 'completed' : ''}`}>
<div className='todo-header'>
Expand All @@ -123,7 +165,7 @@ const TodoItem: React.FC<TodoItemProps> = ({
</select>
</div>
<div className="time-tracking">
{todo.status === 'In Progress' && !isEditingTime && !isEditingText && (
{todo.status === 'In Progress' && !isEditingTime && !isEditingText && !isEditingDueDate && (
<>
{!todo.isTracking ? (
<button
Expand Down Expand Up @@ -182,7 +224,7 @@ const TodoItem: React.FC<TodoItemProps> = ({
</span>
)}
</div>
{todo.status === 'Done' && !isEditingText && !isEditingTime && (
{todo.status === 'Done' && !isEditingText && !isEditingTime && !isEditingDueDate && (
<button
className="delete-button"
onClick={() => onDelete(todo.id)}
Expand Down Expand Up @@ -217,6 +259,42 @@ const TodoItem: React.FC<TodoItemProps> = ({
{todo.text}
</span>
)}
<div className="todo-due-date">
{isEditingDueDate ? (
<div className="due-date-edit">
<input
type="date"
value={editedDueDate}
onChange={(e) => setEditedDueDate(e.target.value)}
onKeyDown={handleDueDateKeyDown}
className="due-date-input"
autoFocus
/>
<button onClick={saveDueDateEdit} className="due-date-save-btn">✓</button>
<button onClick={cancelDueDateEdit} className="due-date-cancel-btn">✗</button>
</div>
) : (
<>
{todo.dueDate ? (
<span
className={`due-date ${isDueDateOverdue(todo.dueDate) ? 'overdue' : ''} editable`}
onDoubleClick={startDueDateEdit}
title="Double-click to edit due date"
>
Due: {formatDueDate(todo.dueDate)}
</span>
) : (
<button
className="add-due-date-btn"
onClick={startDueDateEdit}
title="Add due date"
>
+ Add Due Date
</button>
)}
</>
)}
</div>
</div>
</div>
);
Expand Down
86 changes: 86 additions & 0 deletions src/app/components/TodoItem/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
5 changes: 4 additions & 1 deletion src/app/components/TodoList/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const TodoList: React.FC = () => {
startTimeTracking,
pauseTimeTracking,
updateTodoTime,
updateTodoText
updateTodoText,
updateTodoDueDate
} = useTodoContext();

const activeTodos = todos.filter(todo => todo.status !== 'Done');
Expand All @@ -38,6 +39,7 @@ const TodoList: React.FC = () => {
onPauseTracking={pauseTimeTracking}
onUpdateTime={updateTodoTime}
onUpdateText={updateTodoText}
onUpdateDueDate={updateTodoDueDate}
/>
))}
</div>
Expand All @@ -61,6 +63,7 @@ const TodoList: React.FC = () => {
onPauseTracking={pauseTimeTracking}
onUpdateTime={updateTodoTime}
onUpdateText={updateTodoText}
onUpdateDueDate={updateTodoDueDate}
/>
))}
</div>
Expand Down
12 changes: 10 additions & 2 deletions src/app/context/TodoContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,16 @@ export const TodoProvider: React.FC<TodoProviderProps> = ({ children }) => {
return success;
};

const addTodo = (text: string) => {
const addTodo = (text: string, dueDate?: Date) => {
const newTodo: Todo = {
id: uuidv4(),
number: getNextTicketNumber(),
text,
status: 'Open',
timeLogs: [],
isTracking: false,
totalTimeSpent: 0
totalTimeSpent: 0,
dueDate
};

updateTodos([...todos, newTodo]);
Expand Down Expand Up @@ -170,6 +171,12 @@ export const TodoProvider: React.FC<TodoProviderProps> = ({ 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
Expand Down Expand Up @@ -229,6 +236,7 @@ export const TodoProvider: React.FC<TodoProviderProps> = ({ children }) => {
pauseTimeTracking,
updateTodoTime,
updateTodoText,
updateTodoDueDate,
createNewFile,
openExistingFile,
changeFile,
Expand Down
4 changes: 3 additions & 1 deletion src/app/types/TodoTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Todo {
timeLogs: TimeLog[];
isTracking: boolean;
totalTimeSpent: number;
dueDate?: Date;
}

interface TodoProviderProps {
Expand All @@ -29,14 +30,15 @@ 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;
startTimeTracking: (id: string) => void;
pauseTimeTracking: (id: string) => void;
updateTodoTime: (id: string, newTimeInMs: number) => void;
updateTodoText: (id: string, newText: string) => void;
updateTodoDueDate: (id: string, dueDate?: Date) => void;
createNewFile: () => Promise<boolean>;
openExistingFile: () => Promise<boolean>;
changeFile: () => Promise<boolean>;
Expand Down