This commit is contained in:
2025-12-02 17:02:20 -05:00
parent f82ba25160
commit 6423d1c1a2
49 changed files with 2320 additions and 349 deletions

View File

@@ -1,24 +1,22 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { ref, onMounted, onBeforeUnmount, nextTick, computed } from 'vue'
import { defineProps, defineEmits } from 'vue'
import { getCachedImageUrl, revokeAllImageUrls } from '../../common/imageCache'
interface Task {
id: string
name: string
points: number
is_good: boolean
image_id: string | null // Ensure image can be null or hold an object URL
}
import type { Task } from '@/common/models'
const imageCacheName = 'images-v1'
const props = defineProps<{
title: string
taskIds: string[]
childId: string | number | null
isParentAuthenticated: boolean
filterType?: number | null
}>()
const emit = defineEmits<{
(e: 'points-updated', payload: { id: string; points: number }): void
(e: 'trigger-task', task: Task): void
}>()
const emit = defineEmits(['points-updated'])
const tasks = ref<Task[]>([])
const loading = ref(true)
@@ -27,7 +25,6 @@ const scrollWrapper = ref<HTMLDivElement | null>(null)
const taskRefs = ref<Record<string, HTMLElement | null>>({})
const lastCenteredTaskId = ref<string | null>(null)
const lastCenterTime = ref<number>(0)
const readyTaskId = ref<string | null>(null)
const fetchTasks = async () => {
@@ -60,7 +57,7 @@ const fetchImage = async (task: Task) => {
try {
const url = await getCachedImageUrl(task.image_id, imageCacheName)
task.image_id = url
task.image_url = url
} catch (err) {
console.error('Error fetching image for task', task.id, err)
}
@@ -84,20 +81,9 @@ const centerTask = async (taskId: string) => {
}
}
const triggerTask = async (taskId: string) => {
if (!props.isParentAuthenticated || !props.childId) return
try {
const resp = await fetch(`/api/child/${props.childId}/trigger-task`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId }),
})
if (!resp.ok) return
const data = await resp.json()
emit('points-updated', { id: data.id, points: data.points })
} catch (err) {
console.error('Failed to trigger task:', err)
}
const triggerTask = (taskId: string) => {
const task = tasks.value.find((t) => t.id === taskId)
if (task) emit('trigger-task', task)
}
const handleTaskClick = async (taskId: string) => {
@@ -115,16 +101,24 @@ const handleTaskClick = async (taskId: string) => {
// Center the task, but don't trigger
await centerTask(taskId)
lastCenteredTaskId.value = taskId
lastCenterTime.value = Date.now()
readyTaskId.value = taskId // <-- Add this line
readyTaskId.value = taskId
return
}
// If already centered and visible, trigger the task
// If already centered and visible, emit to parent
triggerTask(taskId)
readyTaskId.value = null
}
const filteredTasks = computed(() => {
if (props.filterType == 1) {
return tasks.value.filter((t) => t.is_good)
} else if (props.filterType == 2) {
return tasks.value.filter((t) => !t.is_good)
}
return tasks.value
})
onMounted(fetchTasks)
// revoke all created object URLs when component unmounts
@@ -135,16 +129,15 @@ onBeforeUnmount(() => {
<template>
<div class="task-list-container">
<h3>Tasks</h3>
<h3>{{ title }}</h3>
<div v-if="loading" class="loading">Loading tasks...</div>
<div v-else-if="error" class="error">Error: {{ error }}</div>
<div v-else-if="tasks.length === 0" class="empty">No tasks</div>
<div v-else-if="filteredTasks.length === 0" class="empty">No {{ title }}</div>
<div v-else class="scroll-wrapper" ref="scrollWrapper">
<div class="task-scroll">
<div
v-for="task in tasks"
v-for="task in filteredTasks"
:key="task.id"
class="task-card"
:class="{ good: task.is_good, bad: !task.is_good, ready: readyTaskId === task.id }"
@@ -152,7 +145,7 @@ onBeforeUnmount(() => {
@click="() => handleTaskClick(task.id)"
>
<div class="task-name">{{ task.name }}</div>
<img v-if="task.image_id" :src="task.image_id" alt="Task Image" class="task-image" />
<img v-if="task.image_url" :src="task.image_url" alt="Task Image" class="task-image" />
<div
class="task-points"
:class="{ 'good-points': task.is_good, 'bad-points': !task.is_good }"
@@ -177,7 +170,7 @@ onBeforeUnmount(() => {
}
.task-list-container h3 {
margin: 0 0 1rem 0;
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}