store menu source edit
This commit is contained in:
parent
76a69a6453
commit
87871709f2
210
src/services/menu.js
Normal file
210
src/services/menu.js
Normal file
@ -0,0 +1,210 @@
|
||||
// src/services/menu.js - 메뉴 관련 API 서비스
|
||||
import { menuApi, handleApiError, formatSuccessResponse } from './api.js'
|
||||
|
||||
/**
|
||||
* 메뉴 관련 API 서비스
|
||||
* 백엔드 Menu Controller와 연동 (포트 8082)
|
||||
*/
|
||||
class MenuService {
|
||||
/**
|
||||
* 메뉴 목록 조회
|
||||
* @param {number} storeId - 매장 ID
|
||||
* @returns {Promise<Object>} 메뉴 목록
|
||||
*/
|
||||
async getMenus(storeId) {
|
||||
try {
|
||||
console.log('=== 메뉴 목록 조회 API 호출 ===')
|
||||
console.log('매장 ID:', storeId)
|
||||
|
||||
if (!storeId) {
|
||||
throw new Error('매장 ID가 필요합니다')
|
||||
}
|
||||
|
||||
// GET /api/menu?storeId={storeId}
|
||||
const response = await menuApi.get('', {
|
||||
params: { storeId }
|
||||
})
|
||||
|
||||
console.log('메뉴 목록 조회 API 응답:', response.data)
|
||||
|
||||
if (response.data && response.data.status === 200) {
|
||||
return formatSuccessResponse(response.data.data, '메뉴 목록을 조회했습니다.')
|
||||
} else {
|
||||
throw new Error(response.data.message || '메뉴 목록을 찾을 수 없습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 목록 조회 실패:', error)
|
||||
|
||||
// 404 오류 또는 네트워크 오류 시 빈 배열 반환 (개발 중)
|
||||
if (error.response?.status === 404 ||
|
||||
error.code === 'ECONNREFUSED' ||
|
||||
error.message.includes('Network Error')) {
|
||||
console.warn('백엔드 미구현 또는 네트워크 오류 - 빈 메뉴 목록 반환')
|
||||
return formatSuccessResponse([], '메뉴 목록이 비어있습니다.')
|
||||
}
|
||||
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 상세 조회
|
||||
* @param {number} menuId - 메뉴 ID
|
||||
* @returns {Promise<Object>} 메뉴 상세 정보
|
||||
*/
|
||||
async getMenuDetail(menuId) {
|
||||
try {
|
||||
console.log('=== 메뉴 상세 조회 API 호출 ===')
|
||||
console.log('메뉴 ID:', menuId)
|
||||
|
||||
if (!menuId || menuId === 'undefined') {
|
||||
throw new Error('올바른 메뉴 ID가 필요합니다')
|
||||
}
|
||||
|
||||
const numericMenuId = parseInt(menuId)
|
||||
if (isNaN(numericMenuId)) {
|
||||
throw new Error('메뉴 ID는 숫자여야 합니다')
|
||||
}
|
||||
|
||||
// GET /api/menu/{menuId}
|
||||
const response = await menuApi.get(`/${numericMenuId}`)
|
||||
|
||||
console.log('메뉴 상세 조회 API 응답:', response.data)
|
||||
|
||||
if (response.data && response.data.status === 200) {
|
||||
return formatSuccessResponse(response.data.data, '메뉴 상세 정보를 조회했습니다.')
|
||||
} else {
|
||||
throw new Error(response.data.message || '메뉴를 찾을 수 없습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 상세 조회 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 등록
|
||||
* @param {Object} menuData - 메뉴 정보
|
||||
* @returns {Promise<Object>} 등록 결과
|
||||
*/
|
||||
async createMenu(menuData) {
|
||||
try {
|
||||
console.log('=== 메뉴 등록 API 호출 ===')
|
||||
console.log('요청 데이터:', menuData)
|
||||
|
||||
const requestData = {
|
||||
storeId: menuData.storeId,
|
||||
menuName: menuData.menuName,
|
||||
category: menuData.category,
|
||||
price: parseInt(menuData.price) || 0,
|
||||
description: menuData.description || ''
|
||||
}
|
||||
|
||||
console.log('백엔드 전송 데이터:', requestData)
|
||||
|
||||
// POST /api/menu/register
|
||||
const response = await menuApi.post('/register', requestData)
|
||||
|
||||
console.log('메뉴 등록 API 응답:', response.data)
|
||||
|
||||
if (response.data && response.data.status === 200) {
|
||||
return formatSuccessResponse(response.data.data, '메뉴가 성공적으로 등록되었습니다.')
|
||||
} else {
|
||||
throw new Error(response.data.message || '메뉴 등록에 실패했습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 등록 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 수정
|
||||
* @param {number} menuId - 메뉴 ID
|
||||
* @param {Object} menuData - 수정할 메뉴 정보
|
||||
* @returns {Promise<Object>} 수정 결과
|
||||
*/
|
||||
async updateMenu(menuId, menuData) {
|
||||
try {
|
||||
console.log('=== 메뉴 수정 API 호출 ===')
|
||||
console.log('메뉴 ID:', menuId)
|
||||
console.log('수정 데이터:', menuData)
|
||||
|
||||
if (!menuId || menuId === 'undefined') {
|
||||
throw new Error('올바른 메뉴 ID가 필요합니다')
|
||||
}
|
||||
|
||||
const numericMenuId = parseInt(menuId)
|
||||
if (isNaN(numericMenuId)) {
|
||||
throw new Error('메뉴 ID는 숫자여야 합니다')
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
menuName: menuData.menuName,
|
||||
category: menuData.category,
|
||||
price: parseInt(menuData.price) || 0,
|
||||
description: menuData.description || ''
|
||||
}
|
||||
|
||||
console.log('백엔드 전송 데이터:', requestData)
|
||||
|
||||
// PUT /api/menu/{menuId}
|
||||
const response = await menuApi.put(`/${numericMenuId}`, requestData)
|
||||
|
||||
console.log('메뉴 수정 API 응답:', response.data)
|
||||
|
||||
if (response.data && response.data.status === 200) {
|
||||
return formatSuccessResponse(response.data.data, '메뉴가 성공적으로 수정되었습니다.')
|
||||
} else {
|
||||
throw new Error(response.data.message || '메뉴 수정에 실패했습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 수정 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 삭제
|
||||
* @param {number} menuId - 메뉴 ID
|
||||
* @returns {Promise<Object>} 삭제 결과
|
||||
*/
|
||||
async deleteMenu(menuId) {
|
||||
try {
|
||||
console.log('=== 메뉴 삭제 API 호출 ===')
|
||||
console.log('메뉴 ID:', menuId)
|
||||
|
||||
if (!menuId || menuId === 'undefined') {
|
||||
throw new Error('올바른 메뉴 ID가 필요합니다')
|
||||
}
|
||||
|
||||
const numericMenuId = parseInt(menuId)
|
||||
if (isNaN(numericMenuId)) {
|
||||
throw new Error('메뉴 ID는 숫자여야 합니다')
|
||||
}
|
||||
|
||||
// DELETE /api/menu/{menuId}
|
||||
const response = await menuApi.delete(`/${numericMenuId}`)
|
||||
|
||||
console.log('메뉴 삭제 API 응답:', response.data)
|
||||
|
||||
if (response.data && response.data.status === 200) {
|
||||
return formatSuccessResponse(response.data.data, '메뉴가 성공적으로 삭제되었습니다.')
|
||||
} else {
|
||||
throw new Error(response.data.message || '메뉴 삭제에 실패했습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 삭제 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 싱글톤 인스턴스 생성 및 export
|
||||
export const menuService = new MenuService()
|
||||
export default menuService
|
||||
|
||||
// 디버깅을 위한 전역 노출 (개발 환경에서만)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
window.menuService = menuService
|
||||
}
|
||||
@ -80,51 +80,53 @@ export const useStoreStore = defineStore('store', {
|
||||
},
|
||||
|
||||
/**
|
||||
* 메뉴 목록 조회 - 추가된 메서드
|
||||
* 메뉴 목록 조회 - 실제 API 연동 (매장 ID 필요)
|
||||
*/
|
||||
async fetchMenus() {
|
||||
console.log('=== Store 스토어: 메뉴 목록 조회 시작 ===')
|
||||
|
||||
try {
|
||||
// 현재는 목업 데이터 반환 (추후 실제 API 연동 시 수정)
|
||||
const mockMenus = [
|
||||
{
|
||||
id: 1,
|
||||
name: '아메리카노',
|
||||
price: 4000,
|
||||
category: '커피',
|
||||
description: '진한 풍미의 아메리카노',
|
||||
imageUrl: '/images/americano.jpg',
|
||||
isAvailable: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '카페라떼',
|
||||
price: 4500,
|
||||
category: '커피',
|
||||
description: '부드러운 우유가 들어간 라떼',
|
||||
imageUrl: '/images/latte.jpg',
|
||||
isAvailable: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '치즈케이크',
|
||||
price: 6000,
|
||||
category: '디저트',
|
||||
description: '진한 크림치즈로 만든 케이크',
|
||||
imageUrl: '/images/cheesecake.jpg',
|
||||
isAvailable: false
|
||||
// 매장 정보에서 storeId 가져오기
|
||||
const storeId = this.storeInfo?.storeId
|
||||
if (!storeId) {
|
||||
console.warn('매장 ID가 없습니다. 매장 정보를 먼저 조회해주세요.')
|
||||
return { success: false, message: '매장 정보가 필요합니다', data: [] }
|
||||
}
|
||||
|
||||
// 메뉴 서비스 임포트
|
||||
const { menuService } = await import('@/services/menu')
|
||||
|
||||
console.log('메뉴 목록 API 호출, 매장 ID:', storeId)
|
||||
const result = await menuService.getMenus(storeId)
|
||||
|
||||
console.log('=== Store 스토어: 메뉴 API 응답 분석 ===')
|
||||
console.log('Result:', result)
|
||||
console.log('Result.success:', result.success)
|
||||
console.log('Result.data:', result.data)
|
||||
console.log('Result.message:', result.message)
|
||||
|
||||
if (result.success && result.data) {
|
||||
// 메뉴 목록이 있는 경우
|
||||
console.log('✅ 메뉴 목록 설정:', result.data)
|
||||
this.menus = result.data
|
||||
return { success: true, data: result.data }
|
||||
} else {
|
||||
// 메뉴가 없거나 조회 실패한 경우
|
||||
console.log('⚠️ 메뉴 목록 없음 또는 조회 실패')
|
||||
this.menus = []
|
||||
|
||||
if (result.message === '등록된 메뉴가 없습니다') {
|
||||
return { success: false, message: '등록된 메뉴가 없습니다', data: [] }
|
||||
} else {
|
||||
return { success: false, message: result.message || '메뉴 목록 조회에 실패했습니다', data: [] }
|
||||
}
|
||||
]
|
||||
|
||||
this.menus = mockMenus
|
||||
console.log('✅ 메뉴 목록 설정 완료:', mockMenus)
|
||||
|
||||
return { success: true, data: mockMenus }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 목록 조회 실패:', error)
|
||||
console.error('=== Store 스토어: 메뉴 목록 조회 실패 ===')
|
||||
console.error('Error:', error)
|
||||
|
||||
this.menus = []
|
||||
return { success: false, message: '메뉴 목록을 불러오는데 실패했습니다' }
|
||||
return { success: false, message: error.message || '메뉴 목록을 불러오는데 실패했습니다', data: [] }
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -1,233 +1,164 @@
|
||||
//* src/services/store.js - 매장 서비스 완전 수정
|
||||
import { storeApi, handleApiError, formatSuccessResponse } from './api.js'
|
||||
// src/store/store.js - StoreService 클래스의 getMenus 메서드 올바른 문법으로 수정
|
||||
|
||||
/**
|
||||
* 매장 관련 API 서비스
|
||||
* 백엔드 Store Controller와 연동 (포트 8082)
|
||||
* 메뉴 목록 조회 (수정된 버전 - storeId 파라미터 추가)
|
||||
* @param {number} storeId - 매장 ID (옵션, 없으면 목업 데이터 반환)
|
||||
* @returns {Promise<Object>} 메뉴 목록
|
||||
*/
|
||||
class StoreService {
|
||||
/**
|
||||
* 매장 등록 (STR-015: 매장 등록)
|
||||
* @param {Object} storeData - 매장 정보
|
||||
* @returns {Promise<Object>} 매장 등록 결과
|
||||
*/
|
||||
async registerStore(storeData) {
|
||||
try {
|
||||
console.log('=== 매장 등록 API 호출 ===')
|
||||
console.log('요청 데이터:', storeData)
|
||||
async getMenus(storeId) {
|
||||
try {
|
||||
console.log('=== 메뉴 목록 조회 API 호출 ===')
|
||||
console.log('매장 ID:', storeId)
|
||||
|
||||
// 백엔드 StoreCreateRequest에 맞는 형태로 변환
|
||||
const requestData = {
|
||||
storeName: storeData.storeName,
|
||||
businessType: storeData.businessType,
|
||||
address: storeData.address,
|
||||
phoneNumber: storeData.phoneNumber,
|
||||
businessHours: storeData.businessHours,
|
||||
closedDays: storeData.closedDays,
|
||||
seatCount: parseInt(storeData.seatCount) || 0,
|
||||
instaAccounts: storeData.instaAccounts || '',
|
||||
blogAccounts: storeData.blogAccounts || '',
|
||||
description: storeData.description || ''
|
||||
}
|
||||
|
||||
console.log('=== 각 필드 상세 검증 ===')
|
||||
console.log('storeName:', requestData.storeName, '(타입:', typeof requestData.storeName, ')')
|
||||
console.log('businessType:', requestData.businessType, '(타입:', typeof requestData.businessType, ')')
|
||||
console.log('address:', requestData.address, '(타입:', typeof requestData.address, ')')
|
||||
console.log('seatCount:', requestData.seatCount, '(타입:', typeof requestData.seatCount, ')')
|
||||
|
||||
console.log('백엔드 전송 데이터:', requestData)
|
||||
|
||||
const response = await storeApi.post('/register', requestData)
|
||||
|
||||
console.log('매장 등록 API 응답:', response.data)
|
||||
|
||||
// 백엔드 응답 구조에 맞게 처리
|
||||
if (response.data && (response.data.status === 200 || response.data.success !== false)) {
|
||||
return {
|
||||
success: true,
|
||||
message: response.data.message || '매장이 등록되었습니다.',
|
||||
data: response.data.data
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.data.message || '매장 등록에 실패했습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('매장 등록 실패:', error)
|
||||
|
||||
if (error.response) {
|
||||
console.error('응답 상태:', error.response.status)
|
||||
console.error('응답 데이터:', error.response.data)
|
||||
}
|
||||
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 매장 정보 조회 (STR-005: 매장 정보 관리)
|
||||
* @returns {Promise<Object>} 매장 정보
|
||||
*/
|
||||
async getStore() {
|
||||
try {
|
||||
console.log('=== 매장 정보 조회 API 호출 ===')
|
||||
|
||||
const response = await storeApi.get('/')
|
||||
|
||||
console.log('매장 정보 조회 API 응답:', response.data)
|
||||
|
||||
// 백엔드 응답 구조에 맞게 처리
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
success: true,
|
||||
message: '매장 정보를 조회했습니다.',
|
||||
data: response.data.data
|
||||
}
|
||||
} else if (response.data && response.data.data === null) {
|
||||
// 매장이 없는 경우
|
||||
return {
|
||||
success: false,
|
||||
message: '등록된 매장이 없습니다',
|
||||
data: null
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.data.message || '매장 정보를 찾을 수 없습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('매장 정보 조회 실패:', error)
|
||||
|
||||
// 404 오류 처리 (매장이 없음)
|
||||
if (error.response?.status === 404) {
|
||||
return {
|
||||
success: false,
|
||||
message: '등록된 매장이 없습니다',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
|
||||
// 500 오류 처리 (서버 내부 오류)
|
||||
if (error.response?.status === 500) {
|
||||
console.error('서버 내부 오류 - 백엔드 로그 확인 필요:', error.response?.data)
|
||||
return {
|
||||
success: false,
|
||||
message: '서버 오류가 발생했습니다. 관리자에게 문의하세요.',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 매장 정보 수정 (STR-010: 매장 수정)
|
||||
* @param {number} storeId - 매장 ID (현재는 사용하지 않음 - JWT에서 사용자 확인)
|
||||
* @param {Object} storeData - 수정할 매장 정보
|
||||
* @returns {Promise<Object>} 매장 수정 결과
|
||||
*/
|
||||
async updateStore(storeId, storeData) {
|
||||
try {
|
||||
console.log('=== 매장 정보 수정 API 호출 ===')
|
||||
console.log('요청 데이터:', storeData)
|
||||
|
||||
// 백엔드 StoreUpdateRequest에 맞는 형태로 변환
|
||||
const requestData = {
|
||||
storeName: storeData.storeName,
|
||||
businessType: storeData.businessType,
|
||||
address: storeData.address,
|
||||
phoneNumber: storeData.phoneNumber,
|
||||
businessHours: storeData.businessHours,
|
||||
closedDays: storeData.closedDays,
|
||||
seatCount: parseInt(storeData.seatCount) || 0,
|
||||
instaAccounts: storeData.instaAccounts || '',
|
||||
blogAccounts: storeData.blogAccounts || '',
|
||||
description: storeData.description || ''
|
||||
}
|
||||
|
||||
console.log('백엔드 전송 데이터:', requestData)
|
||||
|
||||
// PUT 요청 (storeId는 JWT에서 추출하므로 URL에 포함하지 않음)
|
||||
const response = await storeApi.put('/', requestData)
|
||||
|
||||
console.log('매장 정보 수정 API 응답:', response.data)
|
||||
|
||||
if (response.data && (response.data.status === 200 || response.data.success !== false)) {
|
||||
return {
|
||||
success: true,
|
||||
message: response.data.message || '매장 정보가 수정되었습니다.',
|
||||
data: response.data.data
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.data.message || '매장 정보 수정에 실패했습니다.')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('매장 정보 수정 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 매출 정보 조회 (STR-020: 대시보드)
|
||||
* @param {string} period - 조회 기간 (today, week, month, year)
|
||||
* @returns {Promise<Object>} 매출 정보
|
||||
*/
|
||||
async getSales(period = 'today') {
|
||||
try {
|
||||
// 현재는 목업 데이터 반환 (추후 실제 API 연동 시 수정)
|
||||
const mockSalesData = {
|
||||
todaySales: 150000,
|
||||
yesterdaySales: 120000,
|
||||
changeRate: 25.0,
|
||||
monthlyTarget: 3000000,
|
||||
achievementRate: 45.2
|
||||
}
|
||||
|
||||
return formatSuccessResponse(mockSalesData, '매출 정보를 조회했습니다.')
|
||||
} catch (error) {
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 목록 조회 (개발 예정)
|
||||
* @returns {Promise<Object>} 메뉴 목록
|
||||
*/
|
||||
async getMenus() {
|
||||
try {
|
||||
// 현재는 목업 데이터 반환 (추후 실제 API 연동 시 수정)
|
||||
// storeId가 없으면 목업 데이터 반환 (개발 중)
|
||||
if (!storeId) {
|
||||
console.warn('매장 ID가 없어서 목업 데이터 반환')
|
||||
const mockMenus = [
|
||||
{
|
||||
id: 1,
|
||||
menuId: 1, // id 대신 menuId 사용
|
||||
id: 1, // 호환성을 위해
|
||||
name: '아메리카노',
|
||||
menuName: '아메리카노', // 백엔드 형식
|
||||
price: 4000,
|
||||
category: '커피',
|
||||
description: '진한 풍미의 아메리카노',
|
||||
imageUrl: '/images/americano.jpg',
|
||||
isAvailable: true
|
||||
isAvailable: true,
|
||||
available: true // 백엔드 형식
|
||||
},
|
||||
{
|
||||
menuId: 2,
|
||||
id: 2,
|
||||
name: '카페라떼',
|
||||
menuName: '카페라떼',
|
||||
price: 4500,
|
||||
category: '커피',
|
||||
description: '부드러운 우유가 들어간 라떼',
|
||||
imageUrl: '/images/latte.jpg',
|
||||
isAvailable: true
|
||||
isAvailable: true,
|
||||
available: true
|
||||
}
|
||||
]
|
||||
|
||||
return formatSuccessResponse(mockMenus, '메뉴 목록을 조회했습니다.')
|
||||
} catch (error) {
|
||||
return handleApiError(error)
|
||||
return formatSuccessResponse(mockMenus, '목업 메뉴 목록을 조회했습니다.')
|
||||
}
|
||||
|
||||
// 실제 백엔드 API 호출
|
||||
try {
|
||||
// 메뉴 API import
|
||||
const { menuApi } = await import('./api.js')
|
||||
|
||||
// GET /api/menu?storeId={storeId}
|
||||
const response = await menuApi.get('', {
|
||||
params: { storeId }
|
||||
})
|
||||
|
||||
console.log('메뉴 목록 조회 API 응답:', response.data)
|
||||
|
||||
if (response.data && response.data.status === 200) {
|
||||
// 백엔드에서 받은 메뉴 데이터를 프론트엔드 형식으로 변환
|
||||
const menus = response.data.data.map(menu => ({
|
||||
menuId: menu.menuId,
|
||||
id: menu.menuId, // 호환성을 위해
|
||||
storeId: menu.storeId,
|
||||
menuName: menu.menuName,
|
||||
name: menu.menuName, // 호환성을 위해
|
||||
category: menu.category,
|
||||
price: menu.price,
|
||||
description: menu.description,
|
||||
available: menu.available !== undefined ? menu.available : true,
|
||||
isAvailable: menu.available !== undefined ? menu.available : true, // 호환성
|
||||
imageUrl: menu.imageUrl || '/images/menu-placeholder.png',
|
||||
createdAt: menu.createdAt,
|
||||
updatedAt: menu.updatedAt
|
||||
}))
|
||||
|
||||
return formatSuccessResponse(menus, '메뉴 목록을 조회했습니다.')
|
||||
} else {
|
||||
throw new Error(response.data.message || '메뉴 목록을 찾을 수 없습니다.')
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error('백엔드 API 호출 실패:', apiError)
|
||||
|
||||
// 백엔드 미구현이나 네트워크 오류 시 목업 데이터 반환
|
||||
if (apiError.response?.status === 404 ||
|
||||
apiError.code === 'ECONNREFUSED' ||
|
||||
apiError.message.includes('Network Error')) {
|
||||
console.warn('백엔드 미구현 - 목업 데이터 반환')
|
||||
|
||||
const mockMenus = [
|
||||
{
|
||||
menuId: 1,
|
||||
id: 1,
|
||||
storeId: storeId,
|
||||
name: '아메리카노',
|
||||
menuName: '아메리카노',
|
||||
price: 4000,
|
||||
category: '커피',
|
||||
description: '진한 풍미의 아메리카노',
|
||||
imageUrl: '/images/americano.jpg',
|
||||
isAvailable: true,
|
||||
available: true
|
||||
},
|
||||
{
|
||||
menuId: 2,
|
||||
id: 2,
|
||||
storeId: storeId,
|
||||
name: '카페라떼',
|
||||
menuName: '카페라떼',
|
||||
price: 4500,
|
||||
category: '커피',
|
||||
description: '부드러운 우유가 들어간 라떼',
|
||||
imageUrl: '/images/latte.jpg',
|
||||
isAvailable: true,
|
||||
available: true
|
||||
}
|
||||
]
|
||||
|
||||
return formatSuccessResponse(mockMenus, '목업 메뉴 목록을 조회했습니다. (백엔드 미구현)')
|
||||
}
|
||||
|
||||
throw apiError
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 목록 조회 실패:', error)
|
||||
return handleApiError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 싱글톤 인스턴스 생성 및 export
|
||||
export const storeService = new StoreService()
|
||||
export default storeService
|
||||
|
||||
// 디버깅을 위한 전역 노출 (개발 환경에서만)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
window.storeService = storeService
|
||||
// 만약 fetchMenus 메서드가 따로 필요하다면 다음과 같이 추가:
|
||||
/**
|
||||
* 메뉴 목록 조회 (fetchMenus 별칭)
|
||||
* @param {number} storeId - 매장 ID
|
||||
* @returns {Promise<Object>} 메뉴 목록
|
||||
*/
|
||||
async fetchMenus(storeId) {
|
||||
return await this.getMenus(storeId)
|
||||
}
|
||||
|
||||
// StoreService 클래스 전체 구조 예시:
|
||||
class StoreService {
|
||||
// ... 기존 메서드들 (registerStore, getStore, updateStore 등) ...
|
||||
|
||||
/**
|
||||
* 메뉴 목록 조회 (위의 getMenus 메서드)
|
||||
*/
|
||||
async getMenus(storeId) {
|
||||
// 위의 구현 내용
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 목록 조회 별칭
|
||||
*/
|
||||
async fetchMenus(storeId) {
|
||||
return await this.getMenus(storeId)
|
||||
}
|
||||
}
|
||||
|
||||
// 올바른 JavaScript 클래스 메서드 문법:
|
||||
// ❌ 잘못된 문법:
|
||||
// async function getMenus(storeId) { ... }
|
||||
// function async getMenus(storeId) { ... }
|
||||
|
||||
// ✅ 올바른 문법:
|
||||
// async getMenus(storeId) { ... }
|
||||
@ -163,7 +163,7 @@
|
||||
</v-card>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<!-- 메뉴 관리 탭 -->
|
||||
<!-- 메뉴 관리 탭 -->
|
||||
<v-tabs-window-item value="menu">
|
||||
<!-- 메뉴 관리 도구 -->
|
||||
<v-card class="mb-4">
|
||||
@ -518,6 +518,267 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 메뉴 상세 다이얼로그 -->
|
||||
<v-dialog
|
||||
v-model="showMenuDetailDialog"
|
||||
max-width="600"
|
||||
persistent
|
||||
>
|
||||
<v-card v-if="selectedMenu">
|
||||
<v-card-title class="pa-4">
|
||||
<div class="d-flex align-center justify-space-between w-100">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon class="mr-2" color="primary">mdi-food</v-icon>
|
||||
메뉴 상세 정보
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<!-- 상태 배지 -->
|
||||
<v-chip
|
||||
:color="selectedMenu.available ? 'success' : 'error'"
|
||||
size="small"
|
||||
variant="elevated"
|
||||
class="mr-2"
|
||||
>
|
||||
{{ selectedMenu.available ? '판매중' : '품절' }}
|
||||
</v-chip>
|
||||
|
||||
<!-- 추천 메뉴 배지 -->
|
||||
<v-chip
|
||||
v-if="selectedMenu.recommended"
|
||||
color="warning"
|
||||
size="small"
|
||||
variant="elevated"
|
||||
>
|
||||
<v-icon size="small" class="mr-1">mdi-star</v-icon>
|
||||
추천
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-0">
|
||||
<!-- 메뉴 이미지 -->
|
||||
<v-img
|
||||
:src="selectedMenu.imageUrl || selectedMenu.image || '/images/menu-placeholder.png'"
|
||||
:alt="selectedMenu.menuName"
|
||||
height="300"
|
||||
cover
|
||||
class="mb-4"
|
||||
>
|
||||
<template v-slot:placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height">
|
||||
<v-icon size="64" color="grey-lighten-2">mdi-food</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
</v-img>
|
||||
|
||||
<div class="pa-4">
|
||||
<!-- 메뉴 기본 정보 -->
|
||||
<div class="mb-4">
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<h2 class="text-h5 font-weight-bold">{{ selectedMenu.menuName }}</h2>
|
||||
<span class="text-h4 font-weight-bold text-primary">
|
||||
{{ formatCurrency(selectedMenu.price) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<v-chip
|
||||
:color="getCategoryColor(selectedMenu.category)"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
class="mb-3"
|
||||
>
|
||||
{{ selectedMenu.category }}
|
||||
</v-chip>
|
||||
|
||||
<p class="text-body-1 text-grey-darken-1 mb-0">
|
||||
{{ selectedMenu.description || '메뉴 설명이 없습니다.' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 메뉴 상세 정보 -->
|
||||
<v-divider class="my-4" />
|
||||
|
||||
<div class="mb-4">
|
||||
<h4 class="text-h6 font-weight-bold mb-3 d-flex align-center">
|
||||
<v-icon class="mr-2" color="primary">mdi-information</v-icon>
|
||||
상세 정보
|
||||
</h4>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div class="info-item">
|
||||
<v-icon class="mr-2" color="grey">mdi-tag</v-icon>
|
||||
<div>
|
||||
<div class="text-caption text-grey">카테고리</div>
|
||||
<div class="text-body-1">{{ selectedMenu.category }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<div class="info-item">
|
||||
<v-icon class="mr-2" color="grey">mdi-currency-krw</v-icon>
|
||||
<div>
|
||||
<div class="text-caption text-grey">가격</div>
|
||||
<div class="text-body-1">{{ formatCurrency(selectedMenu.price) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="grey"
|
||||
variant="outlined"
|
||||
@click="closeMenuDetail"
|
||||
>
|
||||
닫기
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-pencil"
|
||||
@click="editMenuFromDetail"
|
||||
>
|
||||
수정
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-delete"
|
||||
@click="deleteMenuFromDetail"
|
||||
>
|
||||
삭제
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 메뉴 등록/수정 다이얼로그 -->
|
||||
<v-dialog
|
||||
v-model="showMenuDialog"
|
||||
max-width="600"
|
||||
persistent
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title class="pa-4">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon class="mr-2" color="primary">mdi-food-plus</v-icon>
|
||||
{{ menuEditMode ? '메뉴 수정' : '메뉴 등록' }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-form ref="menuFormRef" v-model="formValid">
|
||||
<v-row>
|
||||
<!-- 메뉴명 -->
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="menuFormData.menuName"
|
||||
label="메뉴명 *"
|
||||
:rules="[v => !!v || '메뉴명을 입력해주세요']"
|
||||
prepend-inner-icon="mdi-food"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 카테고리 -->
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
v-model="menuFormData.category"
|
||||
label="카테고리 *"
|
||||
:items="menuCategories"
|
||||
:rules="[v => !!v || '카테고리를 선택해주세요']"
|
||||
prepend-inner-icon="mdi-tag"
|
||||
variant="outlined"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 가격 -->
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model.number="menuFormData.price"
|
||||
label="가격 *"
|
||||
type="number"
|
||||
:rules="[v => !!v || '가격을 입력해주세요', v => v > 0 || '가격은 0원보다 커야 합니다']"
|
||||
prepend-inner-icon="mdi-currency-krw"
|
||||
variant="outlined"
|
||||
:min="0"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 메뉴 설명 -->
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="menuFormData.description"
|
||||
label="메뉴 설명"
|
||||
prepend-inner-icon="mdi-text"
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
:rules="[v => !v || v.length <= 500 || '설명은 500자 이하로 입력해주세요']"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 메뉴 옵션 -->
|
||||
<v-col cols="12">
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<v-switch
|
||||
v-model="menuFormData.available"
|
||||
label="판매 중"
|
||||
color="success"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<v-switch
|
||||
v-model="menuFormData.recommended"
|
||||
label="추천 메뉴"
|
||||
color="warning"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="grey"
|
||||
variant="outlined"
|
||||
@click="cancelMenuForm"
|
||||
:disabled="saving"
|
||||
>
|
||||
취소
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="saveMenu"
|
||||
:loading="saving"
|
||||
:disabled="!formValid"
|
||||
>
|
||||
{{ menuEditMode ? '수정' : '등록' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 스낵바 -->
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
@ -532,7 +793,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useStoreStore } from '@/store/index'
|
||||
import { useStoreStore } from '@/store/index' // 올바른 경로로 수정
|
||||
|
||||
const storeStore = useStoreStore()
|
||||
|
||||
@ -544,6 +805,28 @@ const formValid = ref(false)
|
||||
const saving = ref(false)
|
||||
const storeFormRef = ref(null) // 폼 참조
|
||||
|
||||
// 메뉴 관리 관련 상태
|
||||
const menus = ref([])
|
||||
const menuSearch = ref('')
|
||||
const menuCategoryFilter = ref('전체')
|
||||
const menuCategories = ref(['커피', '음료', '디저트', '베이커리', '샐러드', '샌드위치'])
|
||||
const showMenuDialog = ref(false)
|
||||
const menuEditMode = ref(false)
|
||||
const menuFormRef = ref(null)
|
||||
const menuFormData = ref({
|
||||
menuName: '',
|
||||
category: '',
|
||||
price: 0,
|
||||
description: '',
|
||||
available: true,
|
||||
recommended: false,
|
||||
imageUrl: ''
|
||||
})
|
||||
|
||||
// 메뉴 상세 관련 상태
|
||||
const showMenuDetailDialog = ref(false)
|
||||
const selectedMenu = ref(null)
|
||||
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
message: '',
|
||||
@ -583,6 +866,27 @@ const weekDays = [
|
||||
// 컴퓨티드 속성
|
||||
const storeInfo = computed(() => storeStore.storeInfo || {})
|
||||
|
||||
// 메뉴 관련 컴퓨티드 속성
|
||||
const filteredMenus = computed(() => {
|
||||
let filtered = menus.value
|
||||
|
||||
// 검색 필터
|
||||
if (menuSearch.value) {
|
||||
const search = menuSearch.value.toLowerCase()
|
||||
filtered = filtered.filter(menu =>
|
||||
menu.menuName.toLowerCase().includes(search) ||
|
||||
menu.description.toLowerCase().includes(search)
|
||||
)
|
||||
}
|
||||
|
||||
// 카테고리 필터
|
||||
if (menuCategoryFilter.value && menuCategoryFilter.value !== '전체') {
|
||||
filtered = filtered.filter(menu => menu.category === menuCategoryFilter.value)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
// 유틸리티 함수들
|
||||
const formatClosedDays = (closedDays) => {
|
||||
if (!closedDays) return '미설정'
|
||||
@ -650,6 +954,161 @@ const addMenu = () => {
|
||||
showSnackbar('메뉴 관리 기능은 곧 업데이트 예정입니다', 'info')
|
||||
}
|
||||
|
||||
// 메뉴 관련 메서드들
|
||||
const openCreateMenuDialog = () => {
|
||||
console.log('메뉴 등록 다이얼로그 열기')
|
||||
menuEditMode.value = false
|
||||
resetMenuForm()
|
||||
showMenuDialog.value = true
|
||||
}
|
||||
|
||||
const editMenu = (menu) => {
|
||||
console.log('메뉴 수정:', menu)
|
||||
menuEditMode.value = true
|
||||
|
||||
// 백엔드에서 받은 메뉴 데이터 구조에 맞게 설정
|
||||
menuFormData.value = {
|
||||
id: menu.id || menu.menuId, // 메뉴 ID 추가
|
||||
menuName: menu.menuName || '',
|
||||
category: menu.category || '',
|
||||
price: menu.price || 0,
|
||||
description: menu.description || '',
|
||||
available: menu.available !== undefined ? menu.available : true,
|
||||
recommended: menu.recommended !== undefined ? menu.recommended : false,
|
||||
imageUrl: menu.imageUrl || menu.image || ''
|
||||
}
|
||||
|
||||
showMenuDialog.value = true
|
||||
}
|
||||
|
||||
const viewMenuDetail = async (menu) => {
|
||||
console.log('메뉴 상세 보기:', menu)
|
||||
|
||||
try {
|
||||
// 메뉴 서비스 임포트
|
||||
const { menuService } = await import('@/services/menu')
|
||||
|
||||
// 메뉴 상세 정보 조회
|
||||
const result = await menuService.getMenuDetail(menu.id)
|
||||
|
||||
if (result.success) {
|
||||
selectedMenu.value = result.data
|
||||
showMenuDetailDialog.value = true
|
||||
console.log('✅ 메뉴 상세 정보 로드:', result.data)
|
||||
} else {
|
||||
// API 실패 시 현재 메뉴 정보 사용
|
||||
selectedMenu.value = menu
|
||||
showMenuDetailDialog.value = true
|
||||
console.log('⚠️ 상세 정보 로드 실패, 기본 정보 사용:', menu)
|
||||
}
|
||||
} catch (error) {
|
||||
// 오류 발생 시 현재 메뉴 정보 사용
|
||||
console.error('메뉴 상세 정보 로드 실패:', error)
|
||||
selectedMenu.value = menu
|
||||
showMenuDetailDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const closeMenuDetail = () => {
|
||||
showMenuDetailDialog.value = false
|
||||
selectedMenu.value = null
|
||||
}
|
||||
|
||||
const confirmDeleteMenu = (menu) => {
|
||||
console.log('메뉴 삭제 확인:', menu)
|
||||
if (confirm(`'${menu.menuName}' 메뉴를 삭제하시겠습니까?`)) {
|
||||
deleteMenu(menu.id)
|
||||
}
|
||||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
menuSearch.value = ''
|
||||
menuCategoryFilter.value = '전체'
|
||||
}
|
||||
|
||||
const cancelMenuForm = () => {
|
||||
console.log('메뉴 폼 취소')
|
||||
showMenuDialog.value = false
|
||||
menuEditMode.value = false
|
||||
resetMenuForm()
|
||||
}
|
||||
|
||||
const resetMenuForm = () => {
|
||||
menuFormData.value = {
|
||||
id: null, // 메뉴 ID 초기화 추가
|
||||
menuName: '',
|
||||
category: '',
|
||||
price: 0,
|
||||
description: '',
|
||||
available: true,
|
||||
recommended: false,
|
||||
imageUrl: ''
|
||||
}
|
||||
|
||||
// 폼 validation 초기화
|
||||
if (menuFormRef.value) {
|
||||
menuFormRef.value.resetValidation()
|
||||
}
|
||||
}
|
||||
|
||||
const getCategoryColor = (category) => {
|
||||
const colors = {
|
||||
'커피': 'brown',
|
||||
'음료': 'blue',
|
||||
'디저트': 'pink',
|
||||
'베이커리': 'orange',
|
||||
'샐러드': 'green',
|
||||
'샌드위치': 'purple'
|
||||
}
|
||||
return colors[category] || 'grey'
|
||||
}
|
||||
|
||||
const formatCurrency = (amount) => {
|
||||
return new Intl.NumberFormat('ko-KR', {
|
||||
style: 'currency',
|
||||
currency: 'KRW'
|
||||
}).format(amount)
|
||||
}
|
||||
|
||||
const formatDateTime = (dateTimeString) => {
|
||||
if (!dateTimeString) return '-'
|
||||
|
||||
try {
|
||||
const date = new Date(dateTimeString)
|
||||
return new Intl.DateTimeFormat('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}).format(date)
|
||||
} catch (error) {
|
||||
return dateTimeString
|
||||
}
|
||||
}
|
||||
|
||||
// 메뉴 상세에서 수정 버튼 클릭
|
||||
const editMenuFromDetail = () => {
|
||||
if (selectedMenu.value) {
|
||||
// 상세 다이얼로그 닫기
|
||||
closeMenuDetail()
|
||||
|
||||
// 수정 다이얼로그 열기
|
||||
editMenu(selectedMenu.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 메뉴 상세에서 삭제 버튼 클릭
|
||||
const deleteMenuFromDetail = () => {
|
||||
if (selectedMenu.value) {
|
||||
// 상세 다이얼로그 닫기
|
||||
closeMenuDetail()
|
||||
|
||||
// 삭제 확인
|
||||
confirmDeleteMenu(selectedMenu.value)
|
||||
}
|
||||
}
|
||||
|
||||
const cancelForm = () => {
|
||||
console.log('폼 취소')
|
||||
showCreateDialog.value = false
|
||||
@ -765,13 +1224,9 @@ onMounted(async () => {
|
||||
if (result.success) {
|
||||
console.log('✅ 매장 정보 로드 완료:', result.data)
|
||||
|
||||
// 메뉴 관리 탭에서 사용할 메뉴 정보도 로드
|
||||
try {
|
||||
await storeStore.fetchMenus()
|
||||
console.log('메뉴 정보 로드 완료')
|
||||
} catch (menuError) {
|
||||
console.log('메뉴 정보 로드 실패 (개발 중이므로 무시):', menuError)
|
||||
}
|
||||
// 매장 정보가 있는 경우 메뉴 정보도 로드
|
||||
await loadMenus()
|
||||
|
||||
} else {
|
||||
if (result.message === '등록된 매장이 없습니다') {
|
||||
console.log('⚠️ 등록된 매장이 없음 - 등록 화면 표시')
|
||||
@ -786,6 +1241,120 @@ onMounted(async () => {
|
||||
showSnackbar('매장 정보를 불러오는 중 오류가 발생했습니다', 'error')
|
||||
}
|
||||
})
|
||||
|
||||
// 메뉴 데이터 로드 함수 - 실제 API 연동
|
||||
const loadMenus = async () => {
|
||||
try {
|
||||
console.log('메뉴 데이터 로드 시작')
|
||||
const result = await storeStore.fetchMenus()
|
||||
|
||||
if (result.success) {
|
||||
menus.value = result.data
|
||||
console.log('✅ 메뉴 데이터 로드 완료:', result.data)
|
||||
} else {
|
||||
console.log('메뉴 데이터 없음 또는 로드 실패:', result.message)
|
||||
menus.value = [] // 빈 배열로 설정 (빈 상태 UI 표시)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 데이터 로드 실패:', error)
|
||||
menus.value = [] // 빈 배열로 초기화
|
||||
}
|
||||
}
|
||||
|
||||
// 실제 메뉴 등록/수정 함수
|
||||
const saveMenu = async () => {
|
||||
if (!menuFormRef.value) {
|
||||
showSnackbar('폼 오류가 발생했습니다', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
const { valid } = await menuFormRef.value.validate()
|
||||
if (!valid) {
|
||||
showSnackbar('필수 정보를 모두 입력해주세요', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
// 메뉴 서비스 임포트
|
||||
const { menuService } = await import('@/services/menu')
|
||||
|
||||
let result
|
||||
|
||||
if (menuEditMode.value) {
|
||||
// 메뉴 수정 - PUT /api/menu/{menuId}
|
||||
const menuId = menuFormData.value.id
|
||||
if (!menuId) {
|
||||
showSnackbar('메뉴 ID가 없습니다', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('메뉴 수정 API 호출, 메뉴 ID:', menuId)
|
||||
result = await menuService.updateMenu(menuId, menuFormData.value)
|
||||
} else {
|
||||
// 새 메뉴 등록 - POST /api/menu/register
|
||||
const storeId = storeInfo.value.storeId
|
||||
if (!storeId) {
|
||||
showSnackbar('매장 정보를 찾을 수 없습니다', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// 메뉴 데이터 준비 (매장 ID 포함)
|
||||
const menuData = {
|
||||
...menuFormData.value,
|
||||
storeId: storeId
|
||||
}
|
||||
|
||||
console.log('메뉴 등록 API 호출, 매장 ID:', storeId)
|
||||
result = await menuService.registerMenu(menuData)
|
||||
}
|
||||
|
||||
console.log('메뉴 저장 결과:', result)
|
||||
|
||||
if (result.success) {
|
||||
showSnackbar(
|
||||
menuEditMode.value ? '메뉴가 수정되었습니다' : '메뉴가 등록되었습니다',
|
||||
'success'
|
||||
)
|
||||
showMenuDialog.value = false
|
||||
menuEditMode.value = false
|
||||
resetMenuForm()
|
||||
|
||||
// 메뉴 목록 새로고침
|
||||
await loadMenus()
|
||||
} else {
|
||||
showSnackbar(result.message || '저장에 실패했습니다', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 저장 중 오류:', error)
|
||||
showSnackbar('저장 중 오류가 발생했습니다', 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 실제 메뉴 삭제 함수
|
||||
const deleteMenu = async (menuId) => {
|
||||
try {
|
||||
// 메뉴 서비스 임포트
|
||||
const { menuService } = await import('@/services/menu')
|
||||
|
||||
console.log('메뉴 삭제:', menuId)
|
||||
const result = await menuService.deleteMenu(menuId)
|
||||
|
||||
if (result.success) {
|
||||
showSnackbar('메뉴가 삭제되었습니다', 'success')
|
||||
// 메뉴 목록 새로고침
|
||||
await loadMenus()
|
||||
} else {
|
||||
showSnackbar(result.message || '메뉴 삭제에 실패했습니다', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메뉴 삭제 실패:', error)
|
||||
showSnackbar('메뉴 삭제 중 오류가 발생했습니다', 'error')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -811,9 +1380,57 @@ onMounted(async () => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 메뉴 카드 스타일 */
|
||||
.menu-card {
|
||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.position-relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.position-absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.top-0 {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.info-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user