diff --git a/api/child_api.py b/api/child_api.py index 334dfe7..5488485 100644 --- a/api/child_api.py +++ b/api/child_api.py @@ -148,9 +148,6 @@ def set_child_tasks(id): if not isinstance(task_ids, list): return jsonify({'error': 'task_ids must be a list'}), 400 - # Deduplicate and drop falsy values - new_task_ids = [tid for tid in dict.fromkeys(task_ids) if tid] - ChildQuery = Query() result = child_db.search(ChildQuery.id == id) if not result: diff --git a/api/task_api.py b/api/task_api.py index 44aa5d9..d6434cf 100644 --- a/api/task_api.py +++ b/api/task_api.py @@ -38,7 +38,11 @@ def get_task(id): @task_api.route('/task/list', methods=['GET']) def list_tasks(): + ids_param = request.args.get('ids') tasks = task_db.all() + if ids_param: + ids = set(ids_param.split(',')) + tasks = [task for task in tasks if task.get('id') in ids] return jsonify({'tasks': tasks}), 200 @task_api.route('/task/', methods=['DELETE']) diff --git a/web/vue-app/src/components/child/ChildView.vue b/web/vue-app/src/components/child/ChildView.vue index e097a97..8b91980 100644 --- a/web/vue-app/src/components/child/ChildView.vue +++ b/web/vue-app/src/components/child/ChildView.vue @@ -2,8 +2,8 @@ import { ref, onMounted, onUnmounted } from 'vue' import { useRoute, useRouter } from 'vue-router' import ChildDetailCard from './ChildDetailCard.vue' -import ChildTaskList from '../task/ChildTaskList.vue' import ChildRewardList from '../reward/ChildRewardList.vue' +import ScrollingList from '../shared/ScrollingList.vue' import { eventBus } from '@/common/eventBus' import '@/assets/view-shared.css' import type { @@ -34,7 +34,7 @@ const showCancelDialog = ref(false) const dialogReward = ref(null) const childRewardListRef = ref() const childChoreListRef = ref() -const childHabitListRef = ref() +const childPenaltyListRef = ref() function handleTaskTriggered(event: Event) { const payload = event.payload as ChildTaskTriggeredEventPayload @@ -99,6 +99,7 @@ function handleChildModified(event: Event) { if (data) { child.value = data } + loading.value = false }) } catch (err) { console.warn('Failed to fetch child after EDIT operation:', err) @@ -137,6 +138,8 @@ function handleTaskModified(event: Event) { }) } catch (err) { console.warn('Failed to fetch child after EDIT operation:', err) + } finally { + loading.value = false } break @@ -168,6 +171,9 @@ const triggerTask = (task: Task) => { } } +const triggerReward = (reward: Reward) => {} +/* + const triggerReward = (reward: Reward, redeemable: boolean, pending: boolean) => { if ('speechSynthesis' in window && reward.name) { const utterString = @@ -184,7 +190,7 @@ const triggerReward = (reward: Reward, redeemable: boolean, pending: boolean) => dialogReward.value = reward showRewardDialog.value = true } -} +}*/ async function cancelPendingReward() { if (!child.value?.id || !dialogReward.value) return @@ -243,7 +249,6 @@ async function fetchChildData(id: string | number) { console.error(err) return null } finally { - loading.value = false } } @@ -253,7 +258,7 @@ function resetInactivityTimer() { if (inactivityTimer) clearTimeout(inactivityTimer) inactivityTimer = setTimeout(() => { router.push({ name: 'ChildrenListView' }) - }, 60000) // 60 seconds + }, 6000000) // 60 seconds } function setupInactivityListeners() { @@ -287,6 +292,7 @@ onMounted(async () => { tasks.value = data.tasks || [] rewards.value = data.rewards || [] } + loading.value = false }) } } @@ -318,24 +324,89 @@ onUnmounted(() => {
- - + :fetchBaseUrl="`/api/task/list?ids=${tasks.join(',')}`" + :ids="tasks" + itemKey="tasks" + imageField="image_id" + @trigger-item="triggerTask" + :getItemClass="(item) => ({ bad: !item.is_good, good: item.is_good })" + :filter-fn=" + (item) => { + return item.is_good + } + " + > + + + + + + + + { justify-content: center; margin: 2rem 0; } + +.item-points { + color: var(--item-points-color, #ffd166); + font-size: 1rem; + font-weight: 900; + text-shadow: var(--item-points-shadow); +} + +/* Mobile tweaks */ +@media (max-width: 480px) { + .item-points { + font-size: 0.78rem; + } +} + +:deep(.good) { + border-color: var(--list-item-border-good); + background: var(--list-item-bg-good); +} +:deep(.bad) { + border-color: var(--list-item-border-bad); + background: var(--list-item-bg-bad); +} diff --git a/web/vue-app/src/components/child/TaskAssignView.vue b/web/vue-app/src/components/child/TaskAssignView.vue index b167276..eb59d66 100644 --- a/web/vue-app/src/components/child/TaskAssignView.vue +++ b/web/vue-app/src/components/child/TaskAssignView.vue @@ -58,6 +58,7 @@ function goToCreateTask() { async function onSubmit() { const selectedIds = taskListRef.value?.selectedItems ?? [] try { + console.log('selectedIds:', selectedIds) const resp = await fetch(`/api/child/${childId}/set-tasks`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, diff --git a/web/vue-app/src/components/shared/ItemList.vue b/web/vue-app/src/components/shared/ItemList.vue index 9331bc5..6c676ef 100644 --- a/web/vue-app/src/components/shared/ItemList.vue +++ b/web/vue-app/src/components/shared/ItemList.vue @@ -40,6 +40,7 @@ const fetchItems = async () => { console.log('Fetched data:', data) let itemList = data[props.itemKey || 'items'] || [] if (props.filterFn) itemList = itemList.filter(props.filterFn) + const initiallySelected: string[] = [] await Promise.all( itemList.map(async (item: any) => { if (props.imageFields) { @@ -48,6 +49,7 @@ const fetchItems = async () => { try { item[`${field.replace('_id', '_url')}`] = await getCachedImageUrl(item[field]) } catch { + console.error('Error fetching image for item', item.id) item[`${field.replace('_id', '_url')}`] = null } } @@ -61,11 +63,14 @@ const fetchItems = async () => { } //for each item see it there is an 'assigned' field that is true. if so check the item's selectable checkbox if (props.selectable && item.assigned === true) { - selectedItems.value.push(item.id) + initiallySelected.push(item.id) } }), ) items.value = itemList + if (props.selectable) { + selectedItems.value = initiallySelected + } } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to fetch items' items.value = [] @@ -109,7 +114,7 @@ const handleDelete = (item: any) => { type="checkbox" class="list-checkbox" v-model="selectedItems" - :value="item" + :value="item.id" @click.stop />