round 3
This commit is contained in:
@@ -1,17 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getCachedImageUrl, revokeAllImageUrls } from '../common/imageCache'
|
||||
import { isParentAuthenticated } from '../stores/auth'
|
||||
import ChildForm from './child/ChildForm.vue'
|
||||
|
||||
interface Child {
|
||||
id: string | number
|
||||
name: string
|
||||
age: number
|
||||
points?: number
|
||||
image_id: string | null
|
||||
}
|
||||
import { eventBus } from '@/common/eventBus'
|
||||
import type { Child, Event } from '@/common/models'
|
||||
|
||||
const router = useRouter()
|
||||
const children = ref<Child[]>([])
|
||||
@@ -27,19 +20,13 @@ const confirmDeleteVisible = ref(false)
|
||||
const deletingChildId = ref<string | number | null>(null)
|
||||
const deleting = ref(false)
|
||||
|
||||
const showEditDialog = ref(false)
|
||||
const editingChild = ref<Child | null>(null)
|
||||
|
||||
const openEditDialog = (child: Child, evt?: Event) => {
|
||||
const openChildEditor = (child: Child, evt?: Event) => {
|
||||
evt?.stopPropagation()
|
||||
editingChild.value = { ...child } // shallow copy for editing
|
||||
showEditDialog.value = true
|
||||
closeMenu()
|
||||
router.push({ name: 'ChildEditView', params: { id: child.id } })
|
||||
}
|
||||
|
||||
const closeEditDialog = () => {
|
||||
showEditDialog.value = false
|
||||
editingChild.value = null
|
||||
function handleServerChange(event: Event) {
|
||||
fetchChildren()
|
||||
}
|
||||
|
||||
// points update state
|
||||
@@ -85,12 +72,28 @@ const fetchChildren = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const createChild = () => {
|
||||
router.push({ name: 'CreateChild' })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
eventBus.on('child_update', handleServerChange)
|
||||
eventBus.on('task_update', handleServerChange)
|
||||
eventBus.on('reward_update', handleServerChange)
|
||||
eventBus.on('child_delete', handleServerChange)
|
||||
|
||||
await fetchChildren()
|
||||
// listen for outside clicks to auto-close any open kebab menu
|
||||
document.addEventListener('click', onDocClick, true)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
eventBus.off('child_update', handleServerChange)
|
||||
eventBus.off('task_update', handleServerChange)
|
||||
eventBus.off('reward_update', handleServerChange)
|
||||
eventBus.off('child_delete', handleServerChange)
|
||||
})
|
||||
|
||||
const shouldIgnoreNextCardClick = ref(false)
|
||||
|
||||
const onDocClick = (e: MouseEvent) => {
|
||||
@@ -194,11 +197,6 @@ const deletePoints = async (childId: string | number, evt?: Event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const gridColumns = computed(() => {
|
||||
const n = Math.min(children.value.length, 3)
|
||||
return `repeat(${n || 1}, minmax(var(--card-width, 289px), 1fr))`
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', onDocClick, true)
|
||||
revokeAllImageUrls()
|
||||
@@ -238,7 +236,7 @@ onBeforeUnmount(() => {
|
||||
<button
|
||||
class="menu-item"
|
||||
@mousedown.stop.prevent
|
||||
@click="openEditDialog(child, $event)"
|
||||
@click="openChildEditor(child, $event)"
|
||||
>
|
||||
Edit Child
|
||||
</button>
|
||||
@@ -269,18 +267,6 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChildForm
|
||||
v-if="showEditDialog"
|
||||
:child="editingChild"
|
||||
@close="closeEditDialog"
|
||||
@updated="
|
||||
async () => {
|
||||
closeEditDialog()
|
||||
await fetchChildren()
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- confirmation modal -->
|
||||
<div
|
||||
v-if="confirmDeleteVisible"
|
||||
@@ -308,6 +294,14 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Child button (FAB) -->
|
||||
<button v-if="isParentAuthenticated" class="fab" @click="createChild" aria-label="Add Child">
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||||
<circle cx="14" cy="14" r="14" fill="#667eea" />
|
||||
<path d="M14 8v12M8 14h12" stroke="#fff" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -525,4 +519,32 @@ h1 {
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Floating Action Button (FAB) */
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: #667eea;
|
||||
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: #5a67d8;
|
||||
}
|
||||
|
||||
.fab:active {
|
||||
background: #4c51bf;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user