feat: Implement routines feature with CRUD operations and child assignment
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m0s

- Add backend routines management with add, get, update, delete, and list functionalities.
- Create models for Routine, RoutineItem, RoutineSchedule, and RoutineExtension.
- Develop event types for routine confirmation and modification.
- Implement frontend components for routine assignment, confirmation dialog, and routine management views.
- Add unit tests for routine API and integration tests for routine CRUD flow.
- Create end-to-end test plan for routines feature covering parent and child interactions.
This commit is contained in:
2026-05-05 09:08:19 -04:00
parent 082097b4f9
commit eb775ba7d8
41 changed files with 2847 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
<template>
<ModalDialog :image-url="task.image_url" :title="'Schedule Chore'" :subtitle="task.name">
<ModalDialog :image-url="entity.image_url" :title="scheduleTitle" :subtitle="entity.name">
<!-- Enable/disable toggle row -->
<div class="schedule-toggle-row">
<span class="toggle-label">{{ scheduleEnabled ? 'Enabled' : 'Paused' }}</span>
@@ -165,8 +165,20 @@ import { ref, computed } from 'vue'
import ModalDialog from './ModalDialog.vue'
import TimePickerPopover from './TimePickerPopover.vue'
import DateInputField from './DateInputField.vue'
import { setChoreSchedule, deleteChoreSchedule, parseErrorResponse } from '@/common/api'
import type { ChildTask, ChoreSchedule, DayConfig } from '@/common/models'
import {
setChoreSchedule,
deleteChoreSchedule,
setRoutineSchedule,
deleteRoutineSchedule,
parseErrorResponse,
} from '@/common/api'
import type {
ChildTask,
ChildRoutine,
ChoreSchedule,
RoutineSchedule,
DayConfig,
} from '@/common/models'
interface TimeValue {
hour: number
@@ -174,11 +186,16 @@ interface TimeValue {
}
const props = defineProps<{
task: ChildTask
entity: ChildTask | ChildRoutine
entityType: 'task' | 'routine'
childId: string
schedule: ChoreSchedule | null
schedule: ChoreSchedule | RoutineSchedule | null
}>()
const scheduleTitle = computed(() =>
props.entityType === 'task' ? 'Schedule Chore' : 'Schedule Routine',
)
const emit = defineEmits<{
(e: 'saved'): void
(e: 'cancelled'): void
@@ -386,10 +403,20 @@ async function save() {
errorMsg.value = null
saving.value = true
const entityId = props.entity.id
const saveEntitySchedule = (payload: object) =>
props.entityType === 'task'
? setChoreSchedule(props.childId, entityId, payload)
: setRoutineSchedule(props.childId, entityId, payload)
const deleteEntitySchedule = () =>
props.entityType === 'task'
? deleteChoreSchedule(props.childId, entityId)
: deleteRoutineSchedule(props.childId, entityId)
let res: Response
if (mode.value === 'days' && selectedDays.value.size === 0) {
// 0 days = remove schedule entirely → chore becomes always active
res = await deleteChoreSchedule(props.childId, props.task.id)
// 0 days = remove schedule entirely so the item becomes always active
res = await deleteEntitySchedule()
} else if (mode.value === 'days') {
const day_configs: DayConfig[] = Array.from(selectedDays.value).map((day) => {
const override = exceptions.value.get(day)
@@ -399,7 +426,7 @@ async function save() {
minute: override?.minute ?? defaultTime.value.minute,
}
})
res = await setChoreSchedule(props.childId, props.task.id, {
res = await saveEntitySchedule({
mode: 'days',
enabled: scheduleEnabled.value,
day_configs,
@@ -408,7 +435,7 @@ async function save() {
default_has_deadline: hasDefaultDeadline.value,
})
} else {
res = await setChoreSchedule(props.childId, props.task.id, {
res = await saveEntitySchedule({
mode: 'interval',
enabled: scheduleEnabled.value,
interval_days: intervalDays.value,