예약 포털 (Vue3 + Firebase) - 서비스 오픈까지
20. 동네 포털 (Vue 3 + Vuetify + Firebase) - 음료 Ice/Hot 옵션 관리
그랜파 개발자
2025. 6. 9. 12:17
아이스/핫 옵션 관리
대부분의 음료는 아이스(Ice), 핫(Hot)을 선택할 수 있습니다.
그러나 일부 음료의 경우 아이스만, 또는 핫만 가능할 수 있습니다.
그러므로 음료를 주문할 때 아이스 또는 핫을 선택할 수 있도록 메뉴를 구성해야 합니다.
아이스, 핫 선택 또는 아이스만, 또는 핫만 등록할 수 있습니다.
등록된 옵션은 리스에 나타나고
등록된 아이스/핫 옵션의 순서는 드래그로 변경할 수 있습니다.
옵션 옆에 있는 삭제 아이콘을 눌러 삭제할 수 있습니다.
https://github.com/inetsos/downtown
GitHub - inetsos/downtown: 동네 포털 - Vue 3 + Firebase
동네 포털 - Vue 3 + Firebase. Contribute to inetsos/downtown development by creating an account on GitHub.
github.com
vuedraggable는 Vue.js에서 드래그 앤 드롭(Drag & Drop) 기능을 쉽게 구현할 수 있도록 해 줍니다.
v-data-table은 Vuetify에서 제공하는 강력한 표 형식의 데이터 표시 컴포넌트입니다.
src/views/IceHotManagement.vue - Ice/Hot 옵션 관리
<!-- src/views/IceHotManagement.vue -->
<template>
<v-container>
<v-card class="pa-4 mx-auto" style="max-width: 600px;">
<v-card-title class="d-flex justify-space-between align-center">
<span>{{ companyName }} - 옵션 관리 (드래그로 순서 변경)</span>
<v-spacer />
<v-btn variant="text" class="mt-4" color="primary" @click="goToMenu">
메뉴로 가기
</v-btn>
</v-card-title>
<v-form @submit.prevent="addOption">
<v-row class="align-center">
<v-col cols="12" md="6">
<v-text-field v-model="newOptionName" label="옵션 이름 (예: Ice, Hot)" />
</v-col>
<v-col cols="12" md="6" class="d-flex justify-end">
<v-btn color="primary" type="submit" class="mt-2 mt-md-0">
등록
</v-btn>
</v-col>
</v-row>
</v-form>
<v-divider class="my-4" />
<!-- v-table with draggable rows -->
<v-table>
<thead>
<tr>
<th style="width: 40px;"></th>
<th>옵션명</th>
<th class="text-end" style="width: 80px;">순서</th>
<th style="width: 40px;"></th>
</tr>
</thead>
<draggable
tag="tbody"
v-model="options"
item-key="id"
handle=".drag-handle"
@end="saveOrder"
>
<template #item="{ element }">
<tr>
<td>
<v-icon class="drag-handle" color="grey darken-1" size="20" style="cursor: grab;">
mdi-drag
</v-icon>
</td>
<td>{{ element.name }}</td>
<td class="text-end">{{ element.sortOrder }}</td>
<td>
<v-icon
color="error"
class="cursor-pointer"
@click="confirmDelete(element.id)"
>
mdi-delete
</v-icon>
</td>
</tr>
</template>
</draggable>
</v-table>
</v-card>
</v-container>
</template>
<script setup>
import { onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import draggable from 'vuedraggable'
import { useIceHotManager } from '@/composables/useIceHotManager'
const router = useRouter()
const route = useRoute()
const companyId = route.params.companyId
const companyName = route.query.companyName || ''
const {
options,
newOptionName,
fetchOptions,
addOption,
deleteOption,
saveOrder,
} = useIceHotManager(companyId)
const goToMenu = () => {
router.push({ name: 'MenuList', params: { companyId }, query: { companyName } })
}
onMounted(fetchOptions)
function confirmDelete(id) {
if (window.confirm('정말 삭제하시겠습니까?')) {
deleteOption(id)
}
}
</script>
<style scoped>
.drag-handle {
cursor: grab;
}
</style>
src/composables/useIceHotManager.js - Ice/Hot 옵션 DB 연동
// src/composables/useIceHotManager.js
import { ref } from 'vue'
import { collection, getDocs, addDoc, deleteDoc, doc, updateDoc } from 'firebase/firestore'
import { db } from '@/firebase'
export function useIceHotManager(companyId) {
const options = ref([])
const newOptionName = ref('')
// 옵션 목록 불러오기
const fetchOptions = async () => {
const snap = await getDocs(collection(db, 'companies', companyId, 'icehotOptions'))
options.value = snap.docs
.map(doc => ({ id: doc.id, ...doc.data() }))
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
}
// 옵션 추가
const addOption = async () => {
if (!newOptionName.value.trim()) return
const maxSort = options.value.reduce((max, o) => Math.max(max, o.sortOrder ?? 0), 0)
await addDoc(collection(db, 'companies', companyId, 'icehotOptions'), {
name: newOptionName.value.trim(),
sortOrder: maxSort + 1,
})
newOptionName.value = ''
await fetchOptions()
}
// 옵션 삭제
const deleteOption = async (id) => {
await deleteDoc(doc(db, 'companies', companyId, 'icehotOptions', id))
await fetchOptions()
}
// 순서 저장
const saveOrder = async () => {
const batchUpdates = options.value.map((opt, index) =>
updateDoc(doc(db, 'companies', companyId, 'icehotOptions', opt.id), {
sortOrder: index,
})
)
await Promise.all(batchUpdates)
await fetchOptions()
}
return {
options,
newOptionName,
fetchOptions,
addOption,
deleteOption,
saveOrder,
}
}