diff --git a/src/services/menu.js b/src/services/menu.js new file mode 100644 index 0000000..d911e78 --- /dev/null +++ b/src/services/menu.js @@ -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} 메뉴 목록 + */ + 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} 메뉴 상세 정보 + */ + 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} 등록 결과 + */ + 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} 수정 결과 + */ + 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} 삭제 결과 + */ + 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 +} \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index 266c5b2..da8b7ff 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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: [] } } }, diff --git a/src/store/store.js b/src/store/store.js index f20178e..d0739de 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -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} 메뉴 목록 */ -class StoreService { - /** - * 매장 등록 (STR-015: 매장 등록) - * @param {Object} storeData - 매장 정보 - * @returns {Promise} 매장 등록 결과 - */ - async registerStore(storeData) { - try { - console.log('=== 매장 등록 API 호출 ===') - console.log('요청 데이터:', storeData) - - // 백엔드 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} 매장 정보 - */ - 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} 매장 수정 결과 - */ - 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} 매출 정보 - */ - 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} 메뉴 목록 - */ - async getMenus() { - try { - // 현재는 목업 데이터 반환 (추후 실제 API 연동 시 수정) +async getMenus(storeId) { + try { + console.log('=== 메뉴 목록 조회 API 호출 ===') + console.log('매장 ID:', storeId) + + // 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 +// 만약 fetchMenus 메서드가 따로 필요하다면 다음과 같이 추가: +/** + * 메뉴 목록 조회 (fetchMenus 별칭) + * @param {number} storeId - 매장 ID + * @returns {Promise} 메뉴 목록 + */ +async fetchMenus(storeId) { + return await this.getMenus(storeId) +} -// 디버깅을 위한 전역 노출 (개발 환경에서만) -if (process.env.NODE_ENV === 'development') { - window.storeService = storeService -} \ No newline at end of file +// 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) { ... } \ No newline at end of file diff --git a/src/views/StoreManagementView.vue b/src/views/StoreManagementView.vue index adb6713..ee2ca70 100644 --- a/src/views/StoreManagementView.vue +++ b/src/views/StoreManagementView.vue @@ -163,7 +163,7 @@ - + @@ -518,6 +518,267 @@ + + + + +
+
+ mdi-food + 메뉴 상세 정보 +
+
+ + + {{ selectedMenu.available ? '판매중' : '품절' }} + + + + + mdi-star + 추천 + +
+
+
+ + + + + + + + + +
+ +
+
+

{{ selectedMenu.menuName }}

+ + {{ formatCurrency(selectedMenu.price) }} + +
+ + + {{ selectedMenu.category }} + + +

+ {{ selectedMenu.description || '메뉴 설명이 없습니다.' }} +

+
+ + + + +
+

+ mdi-information + 상세 정보 +

+ + + +
+ mdi-tag +
+
카테고리
+
{{ selectedMenu.category }}
+
+
+
+ + +
+ mdi-currency-krw +
+
가격
+
{{ formatCurrency(selectedMenu.price) }}
+
+
+
+
+
+
+
+ + + + + + + 닫기 + + + 수정 + + + 삭제 + + +
+
+ + + + + +
+ mdi-food-plus + {{ menuEditMode ? '메뉴 수정' : '메뉴 등록' }} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 취소 + + + {{ menuEditMode ? '수정' : '등록' }} + + +
+
+ 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') + } +} \ No newline at end of file