From dcac2742e9dc7c6022e990d0be2168fbbc3a32e1 Mon Sep 17 00:00:00 2001 From: Ryan Kegel Date: Wed, 14 Jan 2026 14:42:54 -0500 Subject: [PATCH] refactoring --- api/child_api.py | 74 ++++--- web/vue-app/src/assets/button-shared.css | 28 --- web/vue-app/src/assets/global.css | 8 +- web/vue-app/src/assets/list-shared.css | 69 ------ web/vue-app/src/assets/view-shared.css | 28 --- .../src/components/child/ChildDetailCard.vue | 2 +- .../src/components/child/ParentView.vue | 2 +- .../src/components/child/RewardAssignView.vue | 79 ++----- .../src/components/child/TaskAssignView.vue | 82 ++------ .../notification/NotificationList.vue | 1 - .../src/components/reward/ChildRewardList.vue | 1 - .../src/components/reward/RewardList.vue | 178 ---------------- .../src/components/reward/RewardView.vue | 79 +++---- .../components/shared/ChildrenListView.vue | 73 +++---- .../src/components/shared/DeleteModal.vue | 48 +++++ .../shared/FloatingActionButton.vue | 45 ++++ .../src/components/shared/ItemList.vue | 113 +++++++++- .../src/components/task/ChildTaskList.vue | 1 - web/vue-app/src/components/task/TaskList.vue | 196 ------------------ web/vue-app/src/components/task/TaskView.vue | 59 ++---- 20 files changed, 366 insertions(+), 800 deletions(-) delete mode 100644 web/vue-app/src/assets/list-shared.css delete mode 100644 web/vue-app/src/components/reward/RewardList.vue create mode 100644 web/vue-app/src/components/shared/DeleteModal.vue create mode 100644 web/vue-app/src/components/shared/FloatingActionButton.vue delete mode 100644 web/vue-app/src/components/task/TaskList.vue diff --git a/api/child_api.py b/api/child_api.py index d54f15b..9fe4ce6 100644 --- a/api/child_api.py +++ b/api/child_api.py @@ -1,3 +1,5 @@ +from time import sleep + from flask import Blueprint, request, jsonify from tinydb import Query @@ -137,6 +139,12 @@ def assign_task_to_child(id): def set_child_tasks(id): data = request.get_json() or {} task_ids = data.get('task_ids') + if 'type' not in data: + return jsonify({'error': 'type is required (good or bad)'}), 400 + task_type = data.get('type', 'good') + if task_type not in ['good', 'bad']: + return jsonify({'error': 'type must be either good or bad'}), 400 + is_good = task_type == 'good' if not isinstance(task_ids, list): return jsonify({'error': 'task_ids must be a list'}), 400 @@ -147,22 +155,25 @@ def set_child_tasks(id): result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 + child = Child.from_dict(result[0]) + new_task_ids = set(task_ids) - # Optional: validate task IDs exist in the task DB - TaskQuery = Query() - valid_task_ids = [] - for tid in new_task_ids: - if task_db.get(TaskQuery.id == tid): - valid_task_ids.append(tid) + # Add all existing child tasks of the opposite type + for task in task_db.all(): + if task['id'] in child.tasks and task['is_good'] != is_good: + new_task_ids.add(task['id']) + + # Convert back to list if needed + new_tasks = list(new_task_ids) # Replace tasks with validated IDs - child_db.update({'tasks': valid_task_ids}, ChildQuery.id == id) - resp = send_event_for_current_user(Event(EventType.CHILD_TASKS_SET.value, ChildTasksSet(id, valid_task_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))) if resp: return resp return jsonify({ 'message': f'Tasks set for child {id}.', - 'task_ids': valid_task_ids, - 'count': len(valid_task_ids) + 'task_ids': new_tasks, + 'count': len(new_tasks) }), 200 @@ -242,6 +253,10 @@ def list_all_tasks(id): result = child_db.search(ChildQuery.id == id) if not result: return jsonify({'error': 'Child not found'}), 404 + has_type = "type" in request.args + if has_type and request.args.get('type') not in ['good', 'bad']: + return jsonify({'error': 'type must be either good or bad'}), 400 + good = request.args.get('type', False) == 'good' child = result[0] assigned_ids = set(child.get('tasks', [])) @@ -249,8 +264,7 @@ def list_all_tasks(id): # Get all tasks from database all_tasks = task_db.all() - assigned_tasks = [] - assignable_tasks = [] + tasks = [] for task in all_tasks: if not task or not task.get('id'): @@ -263,18 +277,15 @@ def list_all_tasks(id): task.get('image_id'), task.get('id') ) + task_dict = ct.to_dict() + if has_type and task.get('is_good') != good: + continue - if task.get('id') in assigned_ids: - assigned_tasks.append(ct.to_dict()) - else: - assignable_tasks.append(ct.to_dict()) + task_dict.update({'assigned': task.get('id') in assigned_ids}) + tasks.append(task_dict) + tasks.sort(key=lambda t: (not t['assigned'], t['name'].lower())) - return jsonify({ - 'assigned_tasks': assigned_tasks, - 'assignable_tasks': assignable_tasks, - 'assigned_count': len(assigned_tasks), - 'assignable_count': len(assignable_tasks) - }), 200 + return jsonify({ 'tasks': tasks }), 200 @child_api.route('/child//trigger-task', methods=['POST']) @@ -341,9 +352,7 @@ def list_all_rewards(id): # Get all rewards from database all_rewards = reward_db.all() - - assigned_rewards = [] - assignable_rewards = [] + rewards = [] for reward in all_rewards: if not reward or not reward.get('id'): @@ -356,16 +365,15 @@ def list_all_rewards(id): reward.get('id') ) - if reward.get('id') in assigned_ids: - assigned_rewards.append(cr.to_dict()) - else: - assignable_rewards.append(cr.to_dict()) + reward_dict = cr.to_dict() + + reward_dict.update({'assigned': reward.get('id') in assigned_ids}) + rewards.append(reward_dict) + rewards.sort(key=lambda t: (not t['assigned'], t['name'].lower())) return jsonify({ - 'assigned_rewards': assigned_rewards, - 'assignable_rewards': assignable_rewards, - 'assigned_count': len(assigned_rewards), - 'assignable_count': len(assignable_rewards) + 'rewards': rewards, + 'rewards_count': len(rewards) }), 200 diff --git a/web/vue-app/src/assets/button-shared.css b/web/vue-app/src/assets/button-shared.css index e663148..6e14459 100644 --- a/web/vue-app/src/assets/button-shared.css +++ b/web/vue-app/src/assets/button-shared.css @@ -119,31 +119,3 @@ background: var(--sign-in-btn-hover-bg); color: var(--sign-in-btn-hover-color); } - -/* Floating Action Button (FAB) */ -.fab { - position: fixed; - bottom: 2rem; - right: 2rem; - background: var(--fab-bg); - color: #fff; - border: none; - border-radius: 50%; - width: 56px; - height: 56px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - cursor: pointer; - font-size: 24px; - z-index: 1300; -} - -.fab:hover { - background: var(--fab-hover-bg); -} - -.fab:active { - background: var(--fab-active-bg); -} diff --git a/web/vue-app/src/assets/global.css b/web/vue-app/src/assets/global.css index 65524c9..49029cb 100644 --- a/web/vue-app/src/assets/global.css +++ b/web/vue-app/src/assets/global.css @@ -34,10 +34,12 @@ --list-bg: #fff5; --list-item-bg: #f8fafc; - --list-item-border-good: #38c172; + --list-item-border-reward: #38c172; --list-item-border-bad: #e53e3e; - --list-item-bg-good: #f0fff4; - --list-item-bg-bad: #fff5f5; + --list-item-border-good: #00e1ff; + --list-item-bg-reward: #94ffb1; + --list-item-bg-bad: #ffc5c5; + --list-item-bg-good: #8dabfd; --list-image-bg: #eee; --delete-btn-hover-bg: #ffeaea; --delete-btn-hover-shadow: #ef444422; diff --git a/web/vue-app/src/assets/list-shared.css b/web/vue-app/src/assets/list-shared.css deleted file mode 100644 index a2b0808..0000000 --- a/web/vue-app/src/assets/list-shared.css +++ /dev/null @@ -1,69 +0,0 @@ -/* List container */ -.listbox { - flex: 1 1 auto; - max-width: 480px; - width: 100%; - max-height: calc(100vh - 4.5rem); - overflow-y: auto; - margin: 0.2rem 0 0 0; - display: flex; - flex-direction: column; - gap: 0.7rem; - background: var(--list-bg); - padding: 0.2rem 0.2rem 0.2rem; - border-radius: 12px; -} - -/* Delete button */ -.delete-btn { - background: transparent; - border: none; - border-radius: 50%; - padding: 0.15rem; - margin-left: 0.7rem; - cursor: pointer; - display: flex; - align-items: center; - transition: - background 0.15s, - box-shadow 0.15s; - width: 2rem; - height: 2rem; - opacity: 0.92; -} -.delete-btn:hover { - background: var(--delete-btn-hover-bg); - box-shadow: 0 0 0 2px var(--delete-btn-hover-shadow); - opacity: 1; -} -.delete-btn svg { - display: block; -} - -/* Checkbox */ -.list-checkbox { - margin-left: 1rem; - width: 1.2em; - height: 1.2em; - accent-color: var(--checkbox-accent); - cursor: pointer; -} - -/* Loading, error, empty states */ -.loading, -.empty { - margin: 1.2rem 0; - color: var(--list-loading-color); - font-size: 1.15rem; - font-weight: 600; - text-align: center; - line-height: 1.5; -} - -/* Separator (if needed) */ -.list-separator { - height: 0px; - background: #0000; - margin: 0rem 0.2rem; - border-radius: 0px; -} diff --git a/web/vue-app/src/assets/view-shared.css b/web/vue-app/src/assets/view-shared.css index 2121b6d..9cf1aff 100644 --- a/web/vue-app/src/assets/view-shared.css +++ b/web/vue-app/src/assets/view-shared.css @@ -94,34 +94,6 @@ font-size: 1rem; } -/* Floating Action Button (FAB) */ -.fab { - position: fixed; - bottom: 2rem; - right: 2rem; - background: var(--fab-bg); - color: #fff; - border: none; - border-radius: 50%; - width: 56px; - height: 56px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - cursor: pointer; - font-size: 24px; - z-index: 1300; -} - -.fab:hover { - background: var(--fab-hover-bg); -} - -.fab:active { - background: var(--fab-active-bg); -} - /* Responsive Adjustments */ @media (max-width: 900px) { .layout { diff --git a/web/vue-app/src/components/child/ChildDetailCard.vue b/web/vue-app/src/components/child/ChildDetailCard.vue index e8fe685..2c29829 100644 --- a/web/vue-app/src/components/child/ChildDetailCard.vue +++ b/web/vue-app/src/components/child/ChildDetailCard.vue @@ -1,5 +1,5 @@ - - - - diff --git a/web/vue-app/src/components/reward/RewardView.vue b/web/vue-app/src/components/reward/RewardView.vue index c516058..0b7ca47 100644 --- a/web/vue-app/src/components/reward/RewardView.vue +++ b/web/vue-app/src/components/reward/RewardView.vue @@ -1,46 +1,42 @@ + + diff --git a/web/vue-app/src/components/shared/FloatingActionButton.vue b/web/vue-app/src/components/shared/FloatingActionButton.vue new file mode 100644 index 0000000..14877d8 --- /dev/null +++ b/web/vue-app/src/components/shared/FloatingActionButton.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/web/vue-app/src/components/shared/ItemList.vue b/web/vue-app/src/components/shared/ItemList.vue index 89b1fdc..0f8305c 100644 --- a/web/vue-app/src/components/shared/ItemList.vue +++ b/web/vue-app/src/components/shared/ItemList.vue @@ -1,5 +1,5 @@ - - - - diff --git a/web/vue-app/src/components/task/TaskView.vue b/web/vue-app/src/components/task/TaskView.vue index 611fb12..42c30e4 100644 --- a/web/vue-app/src/components/task/TaskView.vue +++ b/web/vue-app/src/components/task/TaskView.vue @@ -1,11 +1,8 @@