feat: add chore, kindness, and penalty management components
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s

- Implemented ChoreAssignView for assigning chores to children.
- Created ChoreConfirmDialog for confirming chore completion.
- Developed KindnessAssignView for assigning kindness acts.
- Added PenaltyAssignView for assigning penalties.
- Introduced ChoreEditView and ChoreView for editing and viewing chores.
- Created KindnessEditView and KindnessView for managing kindness acts.
- Developed PenaltyEditView and PenaltyView for managing penalties.
- Added TaskSubNav for navigation between chores, kindness acts, and penalties.
This commit is contained in:
2026-02-28 11:25:56 -05:00
parent 65e987ceb6
commit d7316bb00a
61 changed files with 7364 additions and 647 deletions

View File

@@ -16,15 +16,15 @@ class ChildOverride(BaseModel):
"""
child_id: str
entity_id: str
entity_type: Literal['task', 'reward']
entity_type: Literal['task', 'reward', 'chore', 'kindness', 'penalty']
custom_value: int
def __post_init__(self):
"""Validate custom_value range and entity_type."""
if self.custom_value < 0 or self.custom_value > 10000:
raise ValueError("custom_value must be between 0 and 10000")
if self.entity_type not in ['task', 'reward']:
raise ValueError("entity_type must be 'task' or 'reward'")
if self.entity_type not in ['task', 'reward', 'chore', 'kindness', 'penalty']:
raise ValueError("entity_type must be 'task', 'reward', 'chore', 'kindness', or 'penalty'")
@classmethod
def from_dict(cls, d: dict):
@@ -52,7 +52,7 @@ class ChildOverride(BaseModel):
def create_override(
child_id: str,
entity_id: str,
entity_type: Literal['task', 'reward'],
entity_type: Literal['task', 'reward', 'chore', 'kindness', 'penalty'],
custom_value: int
) -> 'ChildOverride':
"""Factory method to create a new override."""

View File

@@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import Literal, Optional
from models.base import BaseModel
PendingEntityType = Literal['chore', 'reward']
PendingStatus = Literal['pending', 'approved', 'rejected']
@dataclass
class PendingConfirmation(BaseModel):
child_id: str
entity_id: str
entity_type: PendingEntityType
user_id: str
status: PendingStatus = "pending"
approved_at: Optional[str] = None # ISO 8601 UTC timestamp, set on approval
@classmethod
def from_dict(cls, d: dict):
return cls(
child_id=d.get('child_id'),
entity_id=d.get('entity_id'),
entity_type=d.get('entity_type'),
user_id=d.get('user_id'),
status=d.get('status', 'pending'),
approved_at=d.get('approved_at'),
id=d.get('id'),
created_at=d.get('created_at'),
updated_at=d.get('updated_at')
)
def to_dict(self):
base = super().to_dict()
base.update({
'child_id': self.child_id,
'entity_id': self.entity_id,
'entity_type': self.entity_type,
'user_id': self.user_id,
'status': self.status,
'approved_at': self.approved_at
})
return base

View File

@@ -1,20 +1,28 @@
from dataclasses import dataclass
from typing import Literal
from models.base import BaseModel
TaskType = Literal['chore', 'kindness', 'penalty']
@dataclass
class Task(BaseModel):
name: str
points: int
is_good: bool
type: TaskType
image_id: str | None = None
user_id: str | None = None
@classmethod
def from_dict(cls, d: dict):
# Support legacy is_good field for migration
task_type = d.get('type')
if task_type is None:
is_good = d.get('is_good', True)
task_type = 'chore' if is_good else 'penalty'
return cls(
name=d.get('name'),
points=d.get('points', 0),
is_good=d.get('is_good', True),
type=task_type,
image_id=d.get('image_id'),
user_id=d.get('user_id'),
id=d.get('id'),
@@ -27,8 +35,13 @@ class Task(BaseModel):
base.update({
'name': self.name,
'points': self.points,
'is_good': self.is_good,
'type': self.type,
'image_id': self.image_id,
'user_id': self.user_id
})
return base
@property
def is_good(self) -> bool:
"""Backward compatibility: chore and kindness are 'good', penalty is not."""
return self.type != 'penalty'

View File

@@ -4,8 +4,8 @@ from typing import Literal, Optional
from models.base import BaseModel
EntityType = Literal['task', 'reward', 'penalty']
ActionType = Literal['activated', 'requested', 'redeemed', 'cancelled']
EntityType = Literal['task', 'reward', 'penalty', 'chore', 'kindness']
ActionType = Literal['activated', 'requested', 'redeemed', 'cancelled', 'confirmed', 'approved', 'rejected', 'reset']
@dataclass