feat: Refactor ScheduleModal to support interval scheduling with date input and deadline toggle
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m30s

- Updated ChoreSchedule model to include anchor_date and interval_has_deadline.
- Refactored interval scheduling logic in scheduleUtils to use anchor_date.
- Introduced DateInputField component for selecting anchor dates in ScheduleModal.
- Enhanced ScheduleModal to include a stepper for interval days and a toggle for deadline.
- Updated tests for ScheduleModal and scheduleUtils to reflect new interval scheduling logic.
- Added DateInputField tests to ensure proper functionality and prop handling.
This commit is contained in:
2026-02-26 15:16:46 -05:00
parent 2403daa3f7
commit a197f8e206
12 changed files with 797 additions and 172 deletions

View File

@@ -126,42 +126,113 @@ PC: Implemented via a Tethered Popover with three clickable columns.
### Backend Models
`ChoreSchedule` changes:
- Remove `anchor_weekday: int = 0`
- Add `anchor_date: str = ""` — ISO date string (e.g. `"2026-02-25"`). Empty string means "use today" (backward compat for old DB records).
- Add `interval_has_deadline: bool = True` — when `False`, deadline is ignored ("Anytime").
- Change `interval_days` valid range from `[2, 7]` to `[1, 7]`.
`from_dict` defaults: `anchor_date` defaults to `""`, `interval_has_deadline` defaults to `True` for backward compat with existing DB records.
### Frontend Models
`ChoreSchedule` interface changes:
- Remove `anchor_weekday: number`
- Add `anchor_date: string`
- Add `interval_has_deadline: boolean`
---
## Frontend Design
- `DateInputField.vue` — new shared component at `frontend/vue-app/src/components/shared/DateInputField.vue`
- Props: `modelValue: string` (ISO date string), `min?: string` (ISO date, for disabling past dates), emits `update:modelValue`
- Wraps a native `<input type="date">` with styling matching the `TimePickerPopover` button: `--kebab-menu-border` border, `--modal-bg` background, `--secondary` text color
- Passes `min` to the native input so the browser disables past dates (no custom calendar needed)
- Fully scoped styles using CSS variables from `colors.css`
- `ScheduleModal.vue` — "Every X Days" section fully replaced; "Specific Days" section unchanged
---
## Backend Implementation
- `backend/models/chore_schedule.py`
- Remove `anchor_weekday: int = 0`
- Add `anchor_date: str = ""`
- Add `interval_has_deadline: bool = True`
- Update `from_dict` to default new fields for backward compat
- `backend/api/chore_schedule_api.py`
- Change `interval_days` validation from `[2, 7]` to `[1, 7]`
- Accept `anchor_date` (string, ISO format) instead of `anchor_weekday`
- Accept `interval_has_deadline` (boolean)
---
## Backend Tests
- [ ]
- [x] Update existing interval-mode tests to use `anchor_date` instead of `anchor_weekday`
- [x] Add test: `interval_days: 1` is now valid (was previously rejected)
- [x] Add test: `interval_has_deadline: false` is accepted and persisted
- [x] Add test: old DB records without `anchor_date` / `interval_has_deadline` load with correct defaults
---
## Frontend Implementation
- [ ]
- [x] Created `frontend/vue-app/src/components/shared/DateInputField.vue`
- Props: `modelValue: string` (ISO date), `min?: string`, emits `update:modelValue`
- Styled to match `TimePickerPopover` button (border, background, text color)
- Passes `min` to native `<input type="date">` to disable past dates
- Fully scoped styles using `colors.css` variables
- [x] Refactored `ScheduleModal.vue` — "Every X Days" section
- Removed `anchorWeekday` state; added `anchorDate: ref<string>` (default: today ISO) and `hasDeadline: ref<boolean>` (default: `true`)
- Changed `intervalDays` min from 2 → 1
- Replaced `<input type="number">` with a `` / value / `+` stepper, capped 17, styled with Phase 1 chip/button variables
- Replaced `<select>` anchor weekday with `DateInputField` (min = today's ISO date)
- Replaced `TimeSelector` with `TimePickerPopover` (exact reuse from Phase 1)
- Added "Anytime" toggle link below the deadline row; when active, hides `TimePickerPopover` and sets `hasDeadline = false`; when inactive, shows `TimePickerPopover` and sets `hasDeadline = true`
- Added "Next occurrence: [Weekday, Mon DD]" computed label (pure frontend, `Intl.DateTimeFormat`): starting from `anchorDate`, add `intervalDays` days repeatedly until result ≥ today; displayed as subtle italic label beneath the form rows (same style as Phase 1's "Default (HH:MM AM/PM)" label)
- Load logic: read `schedule.anchor_date` (default to today if empty), `schedule.interval_has_deadline`, `schedule.interval_days` (clamped to ≥1)
- Save logic: write `anchor_date`, `interval_has_deadline`; always write `interval_hour`/`interval_minute` (backend ignores them when `interval_has_deadline=false`)
- "Specific Days" mode left unchanged
---
## Frontend Tests
- [x] `DateInputField.vue`: renders the formatted date value; emits `update:modelValue` on change; `min` prop prevents selection of past dates
- [x] `ScheduleModal.vue` (Every X Days): stepper clamps to 17 at both ends; "Anytime" toggle hides the time picker and sets flag; restoring deadline shows the time picker; save payload contains `anchor_date`, `interval_has_deadline`, and correct `interval_days`; next occurrence label updates correctly when interval or anchor date changes; loading an existing schedule restores all fields including `anchor_date` and `interval_has_deadline`
---
## Future Considerations
- A fully custom calendar (bottom sheet on mobile, tethered popover on desktop) could replace `DateInputField` in a future phase for a more polished mobile experience.
- `TimePickerPopover` could similarly gain a bottom-sheet variant for mobile.
---
## Acceptance Criteria (Definition of Done)
### Backend
- [ ]
- [x] `anchor_weekday` removed; `anchor_date` (string) added with empty-string default for old records
- [x] `interval_has_deadline` (bool) added, defaults to `True` for old records
- [x] `interval_days` valid range updated to `[1, 7]`
- [x] All existing and new backend tests pass
### Frontend
- [ ]
- [x] New `DateInputField` component: styled native date input, respects `min`, emits ISO string
- [x] "Every X Days" mode shows ``/`+` stepper for interval (17), `DateInputField` for anchor date, `TimePickerPopover` for deadline
- [x] "Anytime" toggle clears the deadline (sets `interval_has_deadline = false`) and hides the time picker
- [x] "Next occurrence" label computes and displays the next date ≥ today based on anchor + interval
- [x] Past dates are disabled in the date input (via `min`)
- [x] Existing schedules load correctly — `anchor_date` restored, `interval_has_deadline` restored
- [x] Save payload is valid and consumed by the existing API unchanged
- [x] "Specific Days" mode is unchanged
- [x] Frontend component tests written and passing for `DateInputField` and the refactored `ScheduleModal` interval section