feat: add PendingRewardDialog, RewardConfirmDialog, and TaskConfirmDialog components
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 25s
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 25s
- Implemented PendingRewardDialog for handling pending reward requests. - Created RewardConfirmDialog for confirming reward redemption. - Developed TaskConfirmDialog for task confirmation with child name display. test: add unit tests for ChildView and ParentView components - Added comprehensive tests for ChildView including task triggering and SSE event handling. - Implemented tests for ParentView focusing on override modal and SSE event management. test: add ScrollingList component tests - Created tests for ScrollingList to verify item fetching, loading states, and custom item classes. - Included tests for two-step click interactions and edit button display logic. - Moved toward hashed passwords.
This commit is contained in:
@@ -10,6 +10,7 @@ from api.reward_status import RewardStatus
|
||||
from api.utils import send_event_for_current_user
|
||||
from db.db import child_db, task_db, reward_db, pending_reward_db
|
||||
from db.tracking import insert_tracking_event
|
||||
from db.child_overrides import get_override, delete_override, delete_overrides_for_child
|
||||
from events.types.child_modified import ChildModified
|
||||
from events.types.child_reward_request import ChildRewardRequest
|
||||
from events.types.child_reward_triggered import ChildRewardTriggered
|
||||
@@ -133,6 +134,12 @@ def delete_child(id):
|
||||
if not user_id:
|
||||
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
||||
ChildQuery = Query()
|
||||
|
||||
# Cascade delete overrides for this child
|
||||
deleted_count = delete_overrides_for_child(id)
|
||||
if deleted_count > 0:
|
||||
logger.info(f"Cascade deleted {deleted_count} overrides for child {id}")
|
||||
|
||||
if child_db.remove((ChildQuery.id == id) & (ChildQuery.user_id == user_id)):
|
||||
resp = send_event_for_current_user(Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_DELETE)))
|
||||
if resp:
|
||||
@@ -192,6 +199,17 @@ def set_child_tasks(id):
|
||||
|
||||
# Convert back to list if needed
|
||||
new_tasks = list(new_task_ids)
|
||||
|
||||
# Identify unassigned tasks and delete their overrides
|
||||
old_task_ids = set(child.tasks)
|
||||
unassigned_task_ids = old_task_ids - new_task_ids
|
||||
for task_id in unassigned_task_ids:
|
||||
# Only delete overrides for task entities
|
||||
override = get_override(id, task_id)
|
||||
if override and override.entity_type == 'task':
|
||||
delete_override(id, task_id)
|
||||
logger.info(f"Deleted override for unassigned task: child={id}, task={task_id}")
|
||||
|
||||
# Replace tasks with validated IDs
|
||||
child_db.update({'tasks': new_tasks}, ChildQuery.id == id)
|
||||
resp = send_event_for_current_user(Event(EventType.CHILD_TASKS_SET.value, ChildTasksSet(id, new_tasks)))
|
||||
@@ -246,8 +264,16 @@ def list_child_tasks(id):
|
||||
task = task_db.get((TaskQuery.id == tid) & ((TaskQuery.user_id == user_id) | (TaskQuery.user_id == None)))
|
||||
if not task:
|
||||
continue
|
||||
|
||||
# Check for override
|
||||
override = get_override(id, tid)
|
||||
custom_value = override.custom_value if override else None
|
||||
|
||||
ct = ChildTask(task.get('name'), task.get('is_good'), task.get('points'), task.get('image_id'), task.get('id'))
|
||||
child_tasks.append(ct.to_dict())
|
||||
ct_dict = ct.to_dict()
|
||||
if custom_value is not None:
|
||||
ct_dict['custom_value'] = custom_value
|
||||
child_tasks.append(ct_dict)
|
||||
|
||||
return jsonify({'tasks': child_tasks}), 200
|
||||
|
||||
@@ -372,11 +398,15 @@ def trigger_child_task(id):
|
||||
# Capture points before modification
|
||||
points_before = child.points
|
||||
|
||||
# Check for override
|
||||
override = get_override(id, task_id)
|
||||
points_value = override.custom_value if override else task.points
|
||||
|
||||
# update the child's points based on task type
|
||||
if task.is_good:
|
||||
child.points += task.points
|
||||
child.points += points_value
|
||||
else:
|
||||
child.points -= task.points
|
||||
child.points -= points_value
|
||||
child.points = max(child.points, 0)
|
||||
|
||||
# update the child in the database
|
||||
@@ -384,6 +414,15 @@ def trigger_child_task(id):
|
||||
|
||||
# Create tracking event
|
||||
entity_type = 'penalty' if not task.is_good else 'task'
|
||||
tracking_metadata = {
|
||||
'task_name': task.name,
|
||||
'is_good': task.is_good,
|
||||
'default_points': task.points
|
||||
}
|
||||
if override:
|
||||
tracking_metadata['custom_points'] = override.custom_value
|
||||
tracking_metadata['has_override'] = True
|
||||
|
||||
tracking_event = TrackingEvent.create_event(
|
||||
user_id=user_id,
|
||||
child_id=child.id,
|
||||
@@ -392,7 +431,7 @@ def trigger_child_task(id):
|
||||
action='activated',
|
||||
points_before=points_before,
|
||||
points_after=child.points,
|
||||
metadata={'task_name': task.name, 'is_good': task.is_good}
|
||||
metadata=tracking_metadata
|
||||
)
|
||||
insert_tracking_event(tracking_event)
|
||||
log_tracking_event(tracking_event)
|
||||
@@ -494,6 +533,9 @@ def set_child_rewards(id):
|
||||
result = child_db.search((ChildQuery.id == id) & (ChildQuery.user_id == user_id))
|
||||
if not result:
|
||||
return jsonify({'error': 'Child not found'}), 404
|
||||
|
||||
child = Child.from_dict(result[0])
|
||||
old_reward_ids = set(child.rewards)
|
||||
|
||||
# Optional: validate reward IDs exist in the reward DB
|
||||
RewardQuery = Query()
|
||||
@@ -501,6 +543,15 @@ def set_child_rewards(id):
|
||||
for rid in new_reward_ids:
|
||||
if reward_db.get((RewardQuery.id == rid) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None))):
|
||||
valid_reward_ids.append(rid)
|
||||
|
||||
# Identify unassigned rewards and delete their overrides
|
||||
new_reward_ids_set = set(valid_reward_ids)
|
||||
unassigned_reward_ids = old_reward_ids - new_reward_ids_set
|
||||
for reward_id in unassigned_reward_ids:
|
||||
override = get_override(id, reward_id)
|
||||
if override and override.entity_type == 'reward':
|
||||
delete_override(id, reward_id)
|
||||
logger.info(f"Deleted override for unassigned reward: child={id}, reward={reward_id}")
|
||||
|
||||
# Replace rewards with validated IDs
|
||||
child_db.update({'rewards': valid_reward_ids}, ChildQuery.id == id)
|
||||
@@ -553,8 +604,16 @@ def list_child_rewards(id):
|
||||
reward = reward_db.get((RewardQuery.id == rid) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None)))
|
||||
if not reward:
|
||||
continue
|
||||
|
||||
# Check for override
|
||||
override = get_override(id, rid)
|
||||
custom_value = override.custom_value if override else None
|
||||
|
||||
cr = ChildReward(reward.get('name'), reward.get('cost'), reward.get('image_id'), reward.get('id'))
|
||||
child_rewards.append(cr.to_dict())
|
||||
cr_dict = cr.to_dict()
|
||||
if custom_value is not None:
|
||||
cr_dict['custom_value'] = custom_value
|
||||
child_rewards.append(cr_dict)
|
||||
|
||||
return jsonify({'rewards': child_rewards}), 200
|
||||
|
||||
@@ -618,15 +677,19 @@ def trigger_child_reward(id):
|
||||
return jsonify({'error': 'Reward not found in reward database'}), 404
|
||||
reward: Reward = Reward.from_dict(reward_result[0])
|
||||
|
||||
# Check for override
|
||||
override = get_override(id, reward_id)
|
||||
cost_value = override.custom_value if override else reward.cost
|
||||
|
||||
# Check if child has enough points
|
||||
logger.info(f'Child {child.name} has {child.points} points, reward {reward.name} costs {reward.cost} points')
|
||||
if child.points < reward.cost:
|
||||
points_needed = reward.cost - child.points
|
||||
logger.info(f'Child {child.name} has {child.points} points, reward {reward.name} costs {cost_value} points')
|
||||
if child.points < cost_value:
|
||||
points_needed = cost_value - child.points
|
||||
return jsonify({
|
||||
'error': 'Insufficient points',
|
||||
'points_needed': points_needed,
|
||||
'current_points': child.points,
|
||||
'reward_cost': reward.cost
|
||||
'reward_cost': cost_value
|
||||
}), 400
|
||||
|
||||
# Remove matching pending reward requests for this child and reward
|
||||
@@ -641,11 +704,20 @@ def trigger_child_reward(id):
|
||||
points_before = child.points
|
||||
|
||||
# update the child's points based on reward cost
|
||||
child.points -= reward.cost
|
||||
child.points -= cost_value
|
||||
# update the child in the database
|
||||
child_db.update({'points': child.points}, ChildQuery.id == id)
|
||||
|
||||
# Create tracking event
|
||||
tracking_metadata = {
|
||||
'reward_name': reward.name,
|
||||
'reward_cost': reward.cost,
|
||||
'default_cost': reward.cost
|
||||
}
|
||||
if override:
|
||||
tracking_metadata['custom_cost'] = override.custom_value
|
||||
tracking_metadata['has_override'] = True
|
||||
|
||||
tracking_event = TrackingEvent.create_event(
|
||||
user_id=user_id,
|
||||
child_id=child.id,
|
||||
@@ -654,7 +726,7 @@ def trigger_child_reward(id):
|
||||
action='redeemed',
|
||||
points_before=points_before,
|
||||
points_after=child.points,
|
||||
metadata={'reward_name': reward.name, 'reward_cost': reward.cost}
|
||||
metadata=tracking_metadata
|
||||
)
|
||||
insert_tracking_event(tracking_event)
|
||||
log_tracking_event(tracking_event)
|
||||
@@ -702,15 +774,24 @@ def reward_status(id):
|
||||
RewardQuery = Query()
|
||||
statuses = []
|
||||
for reward_id in reward_ids:
|
||||
reward: Reward = Reward.from_dict(reward_db.get((RewardQuery.id == reward_id) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None))))
|
||||
if not reward:
|
||||
reward_dict = reward_db.get((RewardQuery.id == reward_id) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None)))
|
||||
if not reward_dict:
|
||||
continue
|
||||
points_needed = max(0, reward.cost - points)
|
||||
reward: Reward = Reward.from_dict(reward_dict)
|
||||
|
||||
# Check for override
|
||||
override = get_override(id, reward_id)
|
||||
cost_value = override.custom_value if override else reward.cost
|
||||
points_needed = max(0, cost_value - points)
|
||||
|
||||
#check to see if this reward id and child id is in the pending rewards db if so set its redeeming flag to true
|
||||
pending_query = Query()
|
||||
pending = pending_reward_db.get((pending_query.child_id == child.id) & (pending_query.reward_id == reward.id) & (pending_query.user_id == user_id))
|
||||
status = RewardStatus(reward.id, reward.name, points_needed, reward.cost, pending is not None, reward.image_id)
|
||||
statuses.append(status.to_dict())
|
||||
status = RewardStatus(reward.id, reward.name, points_needed, cost_value, pending is not None, reward.image_id)
|
||||
status_dict = status.to_dict()
|
||||
if override:
|
||||
status_dict['custom_value'] = override.custom_value
|
||||
statuses.append(status_dict)
|
||||
|
||||
statuses.sort(key=lambda s: (not s['redeeming'], s['cost']))
|
||||
return jsonify({'reward_status': statuses}), 200
|
||||
|
||||
Reference in New Issue
Block a user