round 3
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user