Files
chore/frontend/vue-app/src/components/task/PenaltyView.vue
Ryan Kegel d7316bb00a
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s
feat: add chore, kindness, and penalty management components
- Implemented ChoreAssignView for assigning chores to children.
- Created ChoreConfirmDialog for confirming chore completion.
- Developed KindnessAssignView for assigning kindness acts.
- Added PenaltyAssignView for assigning penalties.
- Introduced ChoreEditView and ChoreView for editing and viewing chores.
- Created KindnessEditView and KindnessView for managing kindness acts.
- Developed PenaltyEditView and PenaltyView for managing penalties.
- Added TaskSubNav for navigation between chores, kindness acts, and penalties.
2026-02-28 11:25:56 -05:00

120 lines
3.0 KiB
Vue

<template>
<div class="penalty-view">
<MessageBlock v-if="countRef === 0" message="No penalties">
<span> <button class="round-btn" @click="create">Create</button> a penalty </span>
</MessageBlock>
<ItemList
v-else
ref="listRef"
fetchUrl="/api/penalty/list"
itemKey="tasks"
:itemFields="TASK_FIELDS"
imageField="image_id"
deletable
@clicked="(item: Task) => $router.push({ name: 'EditPenalty', params: { id: item.id } })"
@delete="confirmDelete"
@loading-complete="(count) => (countRef = count)"
:getItemClass="() => ({ bad: true })"
>
<template #item="{ item }">
<img v-if="item.image_url" :src="item.image_url" />
<span class="name">{{ item.name }}</span>
<span class="value">{{ item.points }} pts</span>
</template>
</ItemList>
<FloatingActionButton aria-label="Create Penalty" @click="create" />
<DeleteModal
:show="showConfirm"
message="Are you sure you want to delete this penalty?"
@confirm="deleteItem"
@cancel="showConfirm = false"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import ItemList from '../shared/ItemList.vue'
import MessageBlock from '@/components/shared/MessageBlock.vue'
import FloatingActionButton from '../shared/FloatingActionButton.vue'
import DeleteModal from '../shared/DeleteModal.vue'
import type { Task } from '@/common/models'
import { TASK_FIELDS } from '@/common/models'
import { eventBus } from '@/common/eventBus'
const $router = useRouter()
const showConfirm = ref(false)
const itemToDelete = ref<string | null>(null)
const listRef = ref()
const countRef = ref<number>(-1)
function handleModified() {
listRef.value?.refresh()
}
onMounted(() => {
eventBus.on('task_modified', handleModified)
})
onUnmounted(() => {
eventBus.off('task_modified', handleModified)
})
function confirmDelete(id: string) {
itemToDelete.value = id
showConfirm.value = true
}
const deleteItem = async () => {
const id =
typeof itemToDelete.value === 'object' && itemToDelete.value !== null
? (itemToDelete.value as any).id
: itemToDelete.value
if (!id) return
try {
const resp = await fetch(`/api/penalty/${id}`, { method: 'DELETE' })
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
} catch (err) {
console.error('Failed to delete penalty:', err)
} finally {
showConfirm.value = false
itemToDelete.value = null
}
}
const create = () => {
$router.push({ name: 'CreatePenalty' })
}
</script>
<style scoped>
.penalty-view {
display: flex;
flex-direction: column;
align-items: center;
flex: 1 1 auto;
width: 100%;
height: 100%;
padding: 0;
min-height: 0;
}
.name {
flex: 1;
text-align: left;
font-weight: 600;
}
.value {
min-width: 60px;
text-align: right;
font-weight: 600;
}
:deep(.bad) {
border-color: var(--list-item-border-bad);
background: var(--list-item-bg-bad);
}
</style>