25. 예약 포털 (Vue3 + Firebase) - 카페 운영 대시보드
테이크아웃 전문 카페 운영 관리
온라인 주문을 받는 테이크 아웃 카페의 운영을 생각해 봅니다.
그랜파 개발자는 카페을 운영해본 적이 없습니다.
다만 개발자로서 운영에 필요한 기능일 것이란 추측으로 기능을 개발하고 있습니다.
그러므로 현실에서 카페의 온라인 주문 서비스를 개발, 오픈한다면,
카페 운영자와 인터뷰를 진행하면서 기능 정의를 하여 개발을 진행해야 할 것입니다.
테이크 아웃 커피점에서 온라인 주문을 받으면 여러 단계의 작업이 필요합니다.
원자재나 부재료의 소진 상태를 확인하여 판매 메뉴의 품절 여부를 항상 확인해야 합니다.
시스템이 접수한 주문을 확인하여
주문 내역에 따른 음료를 준비하고
준비된 주문은 고객의 혼선을 방지하기 위한 표시를 하고 포장을 합니다.
고객의 주문을 취소해야 하는 경우도 있을 것입니다.
그리고 고객에게 주문이 완료되었음을 알려 픽업이 가능하다는 것을 알려야 합니다.
고객에 매장으로 픽업하러 왔을 때 주문을 확인하여 전달합니다.
만약 행사를 하거나 쿠폰을 운영한다면 이것에 대한 추가적인 기능이 필요합니다.
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
1. 카페 운영 대시 보드(Operations Dashboard)
운영에 필요한 각종 관리 기능들을 모아둔 페이지를 ‘운영 대시보드’라 합시다.
운영에 필요한 각 기능들을 우선 다음과 같이 정리합니다.
- 메뉴 관리
- 메뉴 품절 관리
- 고객 주문 확인
- 고객 주문 완료 및 픽업 알림
- 고객 주문 취소
이들 관리 기능 외에도
매출 집계, 현황 등 매출 분석과
많이 팔리는 품목 조회 등 고객 데이터 분석 등
운영에 필요한 각종 리포팅 기능도 필요할 것입니다.
계속 운영 대시보드에 필요한 기능들이 추가되어갈 것입니다.
2. 카페 상점 보기 수정
상점 보기에 ‘운영 대시보드’ 관리 모드 링크를 추가합니다.
운영 대시보드에는 관리에 필요한 각 기능들에 대한 링크를 가지고 있습니다.
이미 개발한 메뉴 관리도 ‘운영 대시보드’로 이동합니다.
카페 상점 보기
- src/views/MyCompanies.vue
<!-- src/views/MyCompanies.vue-->
<template>
<v-container>
<v-card class="pa-4 mx-auto" max-width="1200">
<v-card-title class="pa-0 mb-4">내 업체 목록</v-card-title>
<v-row dense>
<v-col
v-for="company in companyStore.companies"
:key="company.id"
cols="12"
sm="6"
>
<v-card class="pa-3 h-100" outlined>
<v-card-title class="font-weight-bold pa-0">
{{ company.name }}
</v-card-title>
<v-card-subtitle class="pa-0 pt-1">
<strong>업종:</strong> {{ company.category }}
</v-card-subtitle>
<v-card-subtitle class="pa-0 pt-1">
<strong>영업시간:</strong>
{{ company?.openTime || '--' }} ~ {{ company?.closeTime || '--' }}
</v-card-subtitle>
<v-card-subtitle class="pa-0 pt-1">
<strong>주소:</strong> {{ company.address || '--' }}
</v-card-subtitle>
<v-card-subtitle class="pa-0 pt-1">
<strong>상세주소:</strong> {{ company.detailAddress || '--' }}
</v-card-subtitle>
<v-card-text class="pa-0 pt-2 multiline-subtitle">
<strong>소개:</strong> {{ company.description || '없음' }}
</v-card-text>
<v-card-actions class="pa-0 pt-3">
<v-btn
size="small"
color="primary"
@click="goToEdit(company.id)"
>
수정
</v-btn>
<v-spacer />
<v-btn
v-if="company.category === '서비스업'"
size="small"
color="secondary"
class="mt-1"
@click="goToRegisterService(company.id, company.name)"
>
서비스 관리
</v-btn>
<v-btn
v-if="company.category === '카페'"
size="small"
color="secondary"
class="mt-1"
@click="goToDashboard(company.id, company.name)"
>
운영 대시보드
</v-btn>
<v-btn
v-else
size="small"
color="secondary"
class="mt-1"
@click="goToReservationManagement(company.id, company.name)"
>
예약 관리
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card>
</v-container>
</template>
<script setup>
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useCompanyStore } from '@/stores/companyStore'
const companyStore = useCompanyStore()
const router = useRouter()
const goToEdit = (id) => {
router.push(`/edit-company/${id}`)
}
const goToRegisterService = (id, name) => {
router.push({
name: 'ServiceList',
params: { companyId: id },
query: { companyName: name }
})
}
const goToMenuManagement = (id, name) => {
router.push({
name: 'MenuList',
params: { companyId: id },
query: { companyName: name }
})
}
const goToDashboard = (id, name) => {
router.push({
name: 'OperationsDashboard',
query: { companyId: id, companyName: name }
})
}
const goToReservationManagement = (companyId, companyName) => {
router.push({
path: `/company-reservations/${companyId}`,
query: {
companyName: companyName,
},
})
}
onMounted(() => {
companyStore.fetchMyCompanies()
})
</script>
<style scoped>
.multiline-subtitle {
white-space: pre-line;
}
</style>
대시보드로 가기
const goToDashboard = (id, name) => {
router.push({
name: 'OperationsDashboard',
query: { companyId: id, companyName: name }
})
}
3. 카페 운영 대시보드
- src/views/OperationsDashboard.vue
<!-- src/views/OperationsDashboard.vue -->
<template>
<v-container>
<v-card>
<v-card-title class="text-h5 font-weight-bold">운영자 대시보드</v-card-title>
<v-divider />
<v-card-text>
<v-row dense>
<v-col
cols="12" sm="6" md="4"
v-for="link in dashboardLinks"
:key="link.title"
>
<v-card
class="pa-4 d-flex flex-column align-center justify-center"
hover
:to="!link.action && link.route ? link.route : undefined"
@click="link.action ? link.action() : null"
style="cursor: pointer;"
>
<v-icon :icon="link.icon" size="48" color="primary" class="mb-3" />
<div class="text-subtitle-1 font-weight-medium">{{ link.title }}</div>
<div class="text-body-2 text-grey-darken-1">{{ link.description }}</div>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const companyId = route.query.companyId
const companyName = route.query.companyName || ''
const goToMenuManager = () => {
if (!companyId || !companyName) {
console.error('Missing companyId or companyName')
alert('회사 정보를 찾을 수 없습니다.')
return
}
router.push({
name: 'MenuList',
params: { companyId },
query: { companyName }
})
}
const goToSoldOutManager = () => {
if (!companyId || !companyName) {
console.error('Missing companyId or companyName')
alert('회사 정보를 찾을 수 없습니다.')
return
}
router.push({
name: 'SoldOutManager',
query: { companyId, companyName }
})
}
const goToOrderManager = () => {
if (!companyId || !companyName) {
console.error('Missing companyId or companyName')
alert('회사 정보를 찾을 수 없습니다.')
return
}
router.push({
name: 'OrderManager',
query: { companyId, companyName }
})
}
const dashboardLinks = ref([
{
title: '메뉴 관리',
description: '메뉴 추가, 수정, 삭제',
icon: 'mdi-food',
action: goToMenuManager
},
{
title: '메뉴 품절 관리',
description: '품절 메뉴 설정 및 해제',
icon: 'mdi-cancel',
action: goToSoldOutManager
},
{
title: '고객 주문 확인',
description: '실시간 주문 목록 보기',
icon: 'mdi-clipboard-list',
action: goToOrderManager
},
{
title: '주문 완료 및 픽업 알림',
description: '주문 상태 업데이트 및 알림 전송',
route: '/admin/notify',
icon: 'mdi-bell-ring'
},
{
title: '고객 주문 취소 처리',
description: '고객 요청 주문 취소 승인',
route: '/admin/cancel',
icon: 'mdi-cancel'
}
])
</script>
매뉴 관리로 가기
const goToMenuManager = () => {
if (!companyId || !companyName) {
console.error('Missing companyId or companyName')
alert('회사 정보를 찾을 수 없습니다.')
return
}
router.push({
name: 'MenuList',
params: { companyId },
query: { companyName }
})
}
4. 메뉴 관리