Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { onBeforeUnmount, onMounted, ref, nextTick } from 'vue'
|
||||
import { onBeforeUnmount, onMounted, ref, nextTick, watch } from 'vue'
|
||||
import Sortable from 'sortablejs'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
@@ -11,6 +11,11 @@ const tags = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const newName = ref('')
|
||||
const reorderMode = ref(false)
|
||||
|
||||
function toggleReorder() {
|
||||
reorderMode.value = !reorderMode.value
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
@@ -88,17 +93,29 @@ async function removeTag(tag) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
watch(reorderMode, async (on) => {
|
||||
if (on) {
|
||||
await nextTick()
|
||||
initSortable()
|
||||
} else {
|
||||
if (sortable) { sortable.destroy(); sortable = null }
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(load)
|
||||
onBeforeUnmount(() => sortable?.destroy())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="tag-admin">
|
||||
<div class="top-row">
|
||||
<p class="hint">내가 등록한 태그는 나의 가계부 내역에서만 사용됩니다.</p>
|
||||
<button v-if="tags.length" type="button" class="text-btn reorder-btn" :class="{ active: reorderMode }" @click="toggleReorder">
|
||||
{{ reorderMode ? '완료' : '순서변경' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="reorderMode" class="hint sm">⠿ 손잡이를 끌어 순서를 바꾸세요. 끝나면 <b>완료</b>.</p>
|
||||
|
||||
<form class="new-tag" @submit.prevent="addTag">
|
||||
<input v-model="newName" type="text" placeholder="새 태그 이름 (예: 식비, 고정지출)" />
|
||||
@@ -110,10 +127,10 @@ onBeforeUnmount(() => sortable?.destroy())
|
||||
|
||||
<ul v-show="!loading && tags.length" ref="listEl" class="tag-list">
|
||||
<li v-for="t in tags" :key="t.id" :data-id="t.id" class="tag-row">
|
||||
<span class="drag-handle" title="드래그하여 순서 변경">≡</span>
|
||||
<input v-model="t.name" class="tag-name" @keyup.enter="saveTag(t)" />
|
||||
<IconBtn icon="check" title="저장" @click="saveTag(t)" />
|
||||
<IconBtn icon="trash" title="삭제" variant="danger" @click="removeTag(t)" />
|
||||
<span v-if="reorderMode" class="drag-handle" title="드래그하여 순서 변경">≡</span>
|
||||
<input v-model="t.name" class="tag-name" @keyup.enter="saveTag(t)" :disabled="reorderMode" />
|
||||
<IconBtn v-if="!reorderMode" icon="check" title="저장" @click="saveTag(t)" />
|
||||
<IconBtn v-if="!reorderMode" icon="trash" title="삭제" variant="danger" @click="removeTag(t)" />
|
||||
</li>
|
||||
</ul>
|
||||
<p v-if="!loading && !tags.length" class="msg">등록된 태그가 없습니다.</p>
|
||||
@@ -133,11 +150,37 @@ onBeforeUnmount(() => sortable?.destroy())
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.hint {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
margin: 0.5rem 0 1rem;
|
||||
}
|
||||
.hint.sm {
|
||||
margin-top: -0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.text-btn {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25rem 0.6rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.text-btn.active {
|
||||
border-color: hsla(160, 100%, 37%, 1);
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
}
|
||||
input {
|
||||
padding: 0.5rem 0.7rem;
|
||||
border: 1px solid var(--color-border);
|
||||
|
||||
@@ -48,7 +48,6 @@ const form = reactive({
|
||||
const submitting = ref(false)
|
||||
const formError = ref(null)
|
||||
|
||||
const DOW = ['', '월', '화', '수', '목', '금', '토', '일']
|
||||
|
||||
const TYPES = [
|
||||
{ value: 'EXPENSE', label: '지출', cls: 'chip-expense' },
|
||||
@@ -56,8 +55,6 @@ const TYPES = [
|
||||
{ value: 'TRANSFER', label: '이체', cls: 'chip-transfer' },
|
||||
]
|
||||
const FREQS = [
|
||||
{ value: 'DAILY', label: '매일' },
|
||||
{ value: 'WEEKLY', label: '매주' },
|
||||
{ value: 'MONTHLY', label: '매월' },
|
||||
{ value: 'YEARLY', label: '매년' },
|
||||
]
|
||||
@@ -102,21 +99,17 @@ function typeLabel(t) {
|
||||
return t === 'INCOME' ? '수입' : t === 'TRANSFER' ? '이체' : '지출'
|
||||
}
|
||||
function freqLabel(r) {
|
||||
if (r.frequency === 'DAILY') return '매일'
|
||||
if (r.frequency === 'WEEKLY') return `매주 ${DOW[r.dayOfWeek] || ''}요일`
|
||||
if (r.frequency === 'MONTHLY') return `매월 ${r.dayOfMonth}일`
|
||||
return `매년 ${r.monthOfYear}/${r.dayOfMonth}`
|
||||
}
|
||||
|
||||
function payKey(r) {
|
||||
if (r.frequency === 'DAILY') return 0
|
||||
if (r.frequency === 'WEEKLY') return r.dayOfWeek || 0
|
||||
if (r.frequency === 'YEARLY') return (r.monthOfYear || 0) * 100 + (r.dayOfMonth || 0)
|
||||
return r.dayOfMonth || 0
|
||||
}
|
||||
|
||||
const PERIODS = [
|
||||
{ key: 'MONTH', label: '월간 거래', freqs: ['DAILY', 'WEEKLY', 'MONTHLY'] },
|
||||
{ key: 'MONTH', label: '월간 거래', freqs: ['MONTHLY'] },
|
||||
{ key: 'YEAR', label: '년간 거래', freqs: ['YEARLY'] },
|
||||
]
|
||||
const grouped = computed(() =>
|
||||
@@ -391,11 +384,6 @@ onMounted(load)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label v-if="form.frequency === 'WEEKLY'">요일
|
||||
<select v-model.number="form.dayOfWeek" :disabled="submitting">
|
||||
<option v-for="d in 7" :key="d" :value="d">{{ DOW[d] }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label v-if="form.frequency === 'YEARLY'">월
|
||||
<input v-model.number="form.monthOfYear" type="number" min="1" max="12" :disabled="submitting" />
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user