diff --git a/backend/alembic/versions/015_add_todo_due_time.py b/backend/alembic/versions/015_add_todo_due_time.py new file mode 100644 index 0000000..9d5f5da --- /dev/null +++ b/backend/alembic/versions/015_add_todo_due_time.py @@ -0,0 +1,24 @@ +"""Add due_time column to todos + +Revision ID: 015 +Revises: 014 +Create Date: 2026-02-23 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "015" +down_revision = "014" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("todos", sa.Column("due_time", sa.Time(), nullable=True)) + + +def downgrade() -> None: + op.drop_column("todos", "due_time") diff --git a/backend/app/models/todo.py b/backend/app/models/todo.py index 2a828ad..62e4273 100644 --- a/backend/app/models/todo.py +++ b/backend/app/models/todo.py @@ -1,6 +1,6 @@ -from sqlalchemy import String, Text, Boolean, Date, Integer, ForeignKey, func +from sqlalchemy import String, Text, Boolean, Date, Time, Integer, ForeignKey, func from sqlalchemy.orm import Mapped, mapped_column, relationship -from datetime import datetime, date +from datetime import datetime, date, time from typing import Optional from app.database import Base @@ -13,6 +13,7 @@ class Todo(Base): description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) priority: Mapped[str] = mapped_column(String(20), default="medium") due_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True) + due_time: Mapped[Optional[time]] = mapped_column(Time, nullable=True) completed: Mapped[bool] = mapped_column(Boolean, default=False) completed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) diff --git a/backend/app/schemas/todo.py b/backend/app/schemas/todo.py index 47ac7fa..a36ffb3 100644 --- a/backend/app/schemas/todo.py +++ b/backend/app/schemas/todo.py @@ -1,5 +1,5 @@ from pydantic import BaseModel, ConfigDict -from datetime import datetime, date +from datetime import datetime, date, time from typing import Optional, Literal TodoPriority = Literal["none", "low", "medium", "high"] @@ -10,6 +10,7 @@ class TodoCreate(BaseModel): description: Optional[str] = None priority: TodoPriority = "medium" due_date: Optional[date] = None + due_time: Optional[time] = None category: Optional[str] = None recurrence_rule: Optional[str] = None project_id: Optional[int] = None @@ -20,6 +21,7 @@ class TodoUpdate(BaseModel): description: Optional[str] = None priority: Optional[TodoPriority] = None due_date: Optional[date] = None + due_time: Optional[time] = None completed: Optional[bool] = None category: Optional[str] = None recurrence_rule: Optional[str] = None @@ -32,6 +34,7 @@ class TodoResponse(BaseModel): description: Optional[str] priority: str due_date: Optional[date] + due_time: Optional[time] completed: bool completed_at: Optional[datetime] category: Optional[str] diff --git a/frontend/src/components/todos/TodoForm.tsx b/frontend/src/components/todos/TodoForm.tsx index 5ef9318..29913ef 100644 --- a/frontend/src/components/todos/TodoForm.tsx +++ b/frontend/src/components/todos/TodoForm.tsx @@ -29,17 +29,28 @@ export default function TodoForm({ todo, onClose }: TodoFormProps) { description: todo?.description || '', priority: todo?.priority || 'medium', due_date: todo?.due_date || '', + due_time: todo?.due_time ? todo.due_time.slice(0, 5) : '', category: todo?.category || '', recurrence_rule: todo?.recurrence_rule || '', }); const mutation = useMutation({ mutationFn: async (data: typeof formData) => { + // Convert empty strings to null for optional fields + const payload = { + title: data.title, + description: data.description || null, + priority: data.priority, + due_date: data.due_date || null, + due_time: data.due_time || null, + category: data.category || null, + recurrence_rule: data.recurrence_rule || null, + }; if (todo) { - const response = await api.put(`/todos/${todo.id}`, data); + const response = await api.put(`/todos/${todo.id}`, payload); return response.data; } else { - const response = await api.post('/todos', data); + const response = await api.post('/todos', payload); return response.data; } }, @@ -104,18 +115,6 @@ export default function TodoForm({ todo, onClose }: TodoFormProps) { -