diff --git a/assets/css/main.css b/assets/css/main.css
index eff57b1..8ebb5c5 100644
--- a/assets/css/main.css
+++ b/assets/css/main.css
@@ -69,4 +69,10 @@ button {
.transaction-form-select {
@apply flex min-h-10 w-full items-center justify-between border-0 bg-transparent px-0 py-2 text-left text-sm font-semibold outline-none shadow-none focus:ring-0;
}
+
+ .nav-icon svg {
+ display: block;
+ height: 100%;
+ width: 100%;
+ }
}
diff --git a/docs/changelog.md b/docs/changelog.md
index dbdda33..46e7f55 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -61,3 +61,5 @@
- 기본 카테고리 초기값을 상위 카테고리 `기본 수입`, `기본 지출` 1개씩으로 단순화했다.
- 카테고리/자산 추가 바텀 시트의 닫기 버튼을 제거하고 추가 실패 안내를 표시하도록 했다.
- 개발 환경에서만 노출되는 테스트 로그인 버튼과 개발용 로그인 API를 추가했다.
+- 카테고리 상세 화면에 삭제 동작을 추가하고, 하위 카테고리나 거래가 연결된 카테고리는 삭제하지 못하도록 안내를 추가했다.
+- 하단 네비게이션과 PC 사이드바 SVG 아이콘을 마스크 방식 대신 인라인 SVG 렌더링으로 변경해 네모 박스로 보이는 문제를 수정했다.
diff --git a/layouts/default.vue b/layouts/default.vue
index bb5f6da..f3fc8be 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -21,7 +21,7 @@
:class="isActive(item.to) ? 'bg-red-100' : ''"
aria-hidden="true"
>
-
+
{{ item.label }}
@@ -50,7 +50,7 @@
:class="isActive(item.to) ? 'bg-red-50' : ''"
aria-hidden="true"
>
-
+
{{ item.label }}
@@ -68,18 +68,22 @@
diff --git a/pages/settings.vue b/pages/settings.vue
index 74eecf6..693f9e2 100644
--- a/pages/settings.vue
+++ b/pages/settings.vue
@@ -263,7 +263,11 @@
-
+
{{ categoryMessage }}
+
+
+
+
@@ -453,6 +457,7 @@ const categoryEditForm = reactive({
icon: 'circle',
color: '#6b7673'
})
+const categoryMessage = ref('')
const categoryIconOptions = CATEGORY_ICON_OPTIONS
const categoryColorOptions = CATEGORY_COLOR_OPTIONS
@@ -502,6 +507,7 @@ watch(selectedCategory, (category: any) => {
icon: category.icon || 'circle',
color: category.color || '#6b7673'
})
+ categoryMessage.value = ''
})
watch(selectedAsset, (asset: any) => {
@@ -520,6 +526,7 @@ function open(nextView: SettingsView) {
selectedCategoryId.value = ''
selectedAssetId.value = ''
assetMessage.value = ''
+ categoryMessage.value = ''
if (nextView === 'account') {
accountForm.name = session.value?.name || ''
accountMessage.value = ''
@@ -662,6 +669,7 @@ async function createCategory() {
async function saveCategory() {
if (!selectedCategory.value) return
+ categoryMessage.value = ''
await $fetch(`/api/categories/${selectedCategory.value.id}`, {
method: 'PATCH',
body: categoryEditForm
@@ -670,6 +678,30 @@ async function saveCategory() {
selectedCategoryId.value = ''
}
+async function deleteCategory() {
+ if (!selectedCategory.value) return
+ categoryMessage.value = ''
+ try {
+ await $fetch(`/api/categories/${selectedCategory.value.id}`, { method: 'DELETE' })
+ const deletedParentId = selectedCategory.value.parentId
+ await refreshCategories()
+ selectedCategoryId.value = ''
+ if (!deletedParentId) selectedParentId.value = ''
+ } catch (err: any) {
+ const status = err?.statusCode || err?.response?.status
+ const statusMessage = err?.statusMessage || err?.response?._data?.statusMessage || ''
+ if (status === 409 && statusMessage === 'Category has children') {
+ categoryMessage.value = '하위 카테고리가 있는 상위 카테고리는 삭제할 수 없습니다.'
+ return
+ }
+ if (status === 409) {
+ categoryMessage.value = '거래가 연결된 카테고리는 삭제할 수 없습니다.'
+ return
+ }
+ categoryMessage.value = '카테고리를 삭제하지 못했습니다.'
+ }
+}
+
async function createShareLink() {
await $fetch('/api/share-links', {
method: 'POST',
diff --git a/server/api/categories/[id].delete.ts b/server/api/categories/[id].delete.ts
new file mode 100644
index 0000000..e095b6a
--- /dev/null
+++ b/server/api/categories/[id].delete.ts
@@ -0,0 +1,26 @@
+import { getRouterParam, createError } from 'h3'
+import { prisma } from '../../utils/db'
+import { requireUser } from '../../utils/auth'
+
+export default defineEventHandler(async (event) => {
+ const user = await requireUser(event)
+ const id = getRouterParam(event, 'id')!
+ const category = await prisma.category.findFirst({ where: { id, userId: user.id } })
+ if (!category) throw createError({ statusCode: 404, statusMessage: 'Category not found' })
+
+ const [childCount, transactionCount] = await Promise.all([
+ prisma.category.count({ where: { parentId: id, userId: user.id } }),
+ prisma.transaction.count({ where: { categoryId: id, userId: user.id } })
+ ])
+
+ if (childCount) {
+ throw createError({ statusCode: 409, statusMessage: 'Category has children' })
+ }
+
+ if (transactionCount) {
+ throw createError({ statusCode: 409, statusMessage: 'Category has transactions' })
+ }
+
+ await prisma.category.delete({ where: { id } })
+ return { ok: true }
+})
diff --git a/todo.md b/todo.md
index 5e3a987..cafe604 100644
--- a/todo.md
+++ b/todo.md
@@ -27,9 +27,7 @@
## 4. 카테고리
-- 상위 카테고리와 하위 카테고리의 추가, 수정, 삭제 화면을 완성한다.
- 카테고리 정렬 순서 변경 기능을 추가한다.
-- 사용 중인 카테고리 삭제 시 처리 규칙을 정한다.
## 5. 자산