Skip to content

Split Task due date into separate date and time fields #762

Description

@gaidheal1

Summary

The task detail modal's due date is currently a single type="datetime-local" input (frontend/src/components/TasksPanel/TasksPanel.tsx:160-171), backed by Task.due_at — a single DateTimeField (progression/models.py:1062) with no separate date/time concept. Split this into two independent inputs — a date field and a time field — with the following behavior:

  • Time set, no date set → default the date to today.
  • Date set, no time set → the time field should visibly show as unset in the UI, but the value persisted to due_at on the backend should be end-of-day, 23:59 (on the selected date), not midnight.

Current implementation

  • Frontend: one <input type="datetime-local">, converted via toDatetimeLocalValue/fromDatetimeLocalValue (frontend/src/utils/formatUtils.ts:106-126), committed on blur straight to due_at.
  • Backend: due_at = models.DateTimeField(null=True, blank=True) (progression/models.py:1062), exposed as-is through TaskSerializer (progression/serializers.py:355-384) with no split representation and no date/time-specific validation or defaulting.

Proposed approach

Frontend

  1. Replace the single datetime-local input with two inputs: type="date" and type="time".
  2. When the time input gets a value and the date input is empty, default the date input to today (local) before committing.
  3. When only the date input has a value, leave the time input visually empty/unset, but send 23:59 for that date as the effective time when constructing the due_at payload.
  4. Add/adjust helpers near toDatetimeLocalValue/fromDatetimeLocalValue in formatUtils.ts (or new ones) to convert between due_at and the split date/time pair, including the defaulting rules above. Update formatUtils.test.ts accordingly.

Backend

  • Decide whether the 23:59-when-date-only rule is enforced only on the frontend (simplest — it's already sending a full ISO datetime for due_at either way) or also defensively in TaskSerializer.validate. Frontend-only is likely sufficient since due_at remains a single DateTimeField; call this out explicitly in the PR either way so it's a deliberate choice, not an oversight.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status
    Staging review

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions