Add edit, delete, and deactivate actions to categories view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<div class="d-flex align-center mb-4">
|
<div class="d-flex align-center mb-4">
|
||||||
<h1 class="text-h4">Categories</h1>
|
<h1 class="text-h4">Categories</h1>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn color="primary" prepend-icon="mdi-plus" @click="showDialog = true">Add Category</v-btn>
|
<v-btn color="primary" prepend-icon="mdi-plus" @click="openCreateDialog">Add Category</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
@@ -11,22 +11,41 @@
|
|||||||
<template #prepend="{ item }">
|
<template #prepend="{ item }">
|
||||||
<v-chip :color="item.type === 'Income' ? 'success' : item.type === 'Transfer' ? 'info' : 'error'" size="x-small">{{ item.type }}</v-chip>
|
<v-chip :color="item.type === 'Income' ? 'success' : item.type === 'Transfer' ? 'info' : 'error'" size="x-small">{{ item.type }}</v-chip>
|
||||||
</template>
|
</template>
|
||||||
|
<template #append="{ item }">
|
||||||
|
<v-btn icon="mdi-pencil" size="x-small" variant="text" @click.stop="openEditDialog(item)" />
|
||||||
|
<v-btn v-if="!item.isSystem" icon="mdi-delete" size="x-small" variant="text" color="error" @click.stop="openDeleteDialog(item)" />
|
||||||
|
</template>
|
||||||
</v-treeview>
|
</v-treeview>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<v-dialog v-model="showDialog" max-width="400">
|
<v-dialog v-model="showDialog" max-width="400">
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>Add Category</v-card-title>
|
<v-card-title>{{ editingCategory ? 'Edit Category' : 'Add Category' }}</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-text-field v-model="form.name" label="Category Name" />
|
<v-text-field v-model="form.name" label="Category Name" />
|
||||||
<v-select v-model="form.type" label="Type" :items="['Income','Expense','Transfer']" />
|
<v-select v-model="form.type" label="Type" :items="['Income','Expense','Transfer']" :disabled="!!editingCategory" />
|
||||||
<v-select v-model="form.parentId" label="Parent Category" :items="parentOptions" item-title="name" item-value="id" clearable />
|
<v-select v-model="form.parentId" label="Parent Category" :items="parentOptions" item-title="name" item-value="id" clearable />
|
||||||
|
<v-switch v-if="editingCategory" v-model="form.isActive" label="Active" color="primary" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn @click="showDialog = false">Cancel</v-btn>
|
<v-btn @click="showDialog = false">Cancel</v-btn>
|
||||||
<v-btn color="primary" @click="saveCategory">Create</v-btn>
|
<v-btn color="primary" @click="saveCategory">{{ editingCategory ? 'Save' : 'Create' }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-dialog v-model="showDeleteDialog" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>Delete Category</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
Are you sure you want to delete <strong>{{ deleteTarget?.name }}</strong>? This action cannot be undone.
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="showDeleteDialog = false">Cancel</v-btn>
|
||||||
|
<v-btn color="error" @click="confirmDelete">Delete</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@@ -41,22 +60,64 @@ import type { CategoryTree, Category } from '@/types'
|
|||||||
const categoryTree = ref<CategoryTree[]>([])
|
const categoryTree = ref<CategoryTree[]>([])
|
||||||
const allCategories = ref<Category[]>([])
|
const allCategories = ref<Category[]>([])
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const form = ref({ name: '', type: 'Expense', parentId: null as string | null })
|
const editingCategory = ref<Category | null>(null)
|
||||||
|
const showDeleteDialog = ref(false)
|
||||||
|
const deleteTarget = ref<CategoryTree | null>(null)
|
||||||
|
const form = ref({ name: '', type: 'Expense', parentId: null as string | null, isActive: true })
|
||||||
|
|
||||||
const parentOptions = computed(() => allCategories.value.filter(c => !c.parentId))
|
const parentOptions = computed(() => allCategories.value.filter(c => !c.parentId))
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const [tree, all] = await Promise.all([categoriesApi.getTree(), categoriesApi.getAll()])
|
await reloadCategories()
|
||||||
categoryTree.value = tree.data
|
|
||||||
allCategories.value = all.data
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function saveCategory() {
|
async function reloadCategories() {
|
||||||
await categoriesApi.create(form.value)
|
|
||||||
const [tree, all] = await Promise.all([categoriesApi.getTree(), categoriesApi.getAll()])
|
const [tree, all] = await Promise.all([categoriesApi.getTree(), categoriesApi.getAll()])
|
||||||
categoryTree.value = tree.data
|
categoryTree.value = tree.data
|
||||||
allCategories.value = all.data
|
allCategories.value = all.data
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreateDialog() {
|
||||||
|
editingCategory.value = null
|
||||||
|
form.value = { name: '', type: 'Expense', parentId: null, isActive: true }
|
||||||
|
showDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditDialog(item: CategoryTree) {
|
||||||
|
const cat = allCategories.value.find(c => c.id === item.id)
|
||||||
|
if (!cat) return
|
||||||
|
editingCategory.value = cat
|
||||||
|
form.value = { name: cat.name, type: cat.type, parentId: cat.parentId, isActive: cat.isActive }
|
||||||
|
showDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDeleteDialog(item: CategoryTree) {
|
||||||
|
deleteTarget.value = item
|
||||||
|
showDeleteDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCategory() {
|
||||||
|
if (editingCategory.value) {
|
||||||
|
await categoriesApi.update(editingCategory.value.id, {
|
||||||
|
name: form.value.name,
|
||||||
|
parentId: form.value.parentId,
|
||||||
|
sortOrder: editingCategory.value.sortOrder,
|
||||||
|
isActive: form.value.isActive,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await categoriesApi.create(form.value)
|
||||||
|
}
|
||||||
|
await reloadCategories()
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
form.value = { name: '', type: 'Expense', parentId: null }
|
editingCategory.value = null
|
||||||
|
form.value = { name: '', type: 'Expense', parentId: null, isActive: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDelete() {
|
||||||
|
if (!deleteTarget.value) return
|
||||||
|
await categoriesApi.delete(deleteTarget.value.id)
|
||||||
|
await reloadCategories()
|
||||||
|
showDeleteDialog.value = false
|
||||||
|
deleteTarget.value = null
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user