diff --git a/src/services/api.js b/src/services/api.js index 223b0f1..de8bf89 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -8,7 +8,7 @@ const getApiUrls = () => { GATEWAY_URL: config.GATEWAY_URL || 'http://20.1.2.3', AUTH_URL: 'http://localhost:8081/api/auth', MEMBER_URL: 'http://localhost:8081/api/member', - STORE_URL: config.STORE_URL || 'http://20.1.2.3/api/store', + STORE_URL: config.STORE_URL || 'http://localhost:8082/api/store', CONTENT_URL: config.CONTENT_URL || 'http://20.1.2.3/api/content', MENU_URL: config.MENU_URL || 'http://20.1.2.3/api/menu', SALES_URL: config.SALES_URL || 'http://20.1.2.3/api/sales', diff --git a/src/store/index.js b/src/store/index.js index ae9412c..a74e198 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,285 +1,153 @@ -//* src/store/index.js -/** - * Pinia 스토어 설정 - * 전역 상태 관리 - */ +//* src/store/index.js - Store 스토어 수정 (매장 조회 부분) import { defineStore } from 'pinia' -import authService from '@/services/auth' -import storeService from '@/services/store' - -// 인증 스토어 -export const useAuthStore = defineStore('auth', { - state: () => ({ - user: null, - token: localStorage.getItem('token'), - refreshToken: localStorage.getItem('refreshToken'), - isAuthenticated: false - }), - - getters: { - getUserInfo: (state) => state.user, - isLoggedIn: (state) => state.isAuthenticated && !!state.token - }, - - actions: { - async login(credentials) { - try { - const response = await authService.login(credentials) - this.setAuth(response.data) - return response - } catch (error) { - this.clearAuth() - throw error - } - }, - - async register(userData) { - try { - const response = await authService.register(userData) - return response - } catch (error) { - throw error - } - }, - - async logout() { - try { - if (this.token) { - await authService.logout() - } - } catch (error) { - console.error('로그아웃 오류:', error) - } finally { - this.clearAuth() - } - }, - - async refreshUserInfo() { - try { - const response = await authService.getUserInfo() - this.user = response.data - this.isAuthenticated = true - return response - } catch (error) { - this.clearAuth() - throw error - } - }, - - setAuth(authData) { - this.user = authData.user - this.token = authData.accessToken - this.refreshToken = authData.refreshToken - this.isAuthenticated = true - - localStorage.setItem('token', authData.accessToken) - localStorage.setItem('refreshToken', authData.refreshToken) - }, - - clearAuth() { - this.user = null - this.token = null - this.refreshToken = null - this.isAuthenticated = false - - localStorage.removeItem('token') - localStorage.removeItem('refreshToken') - } - } -}) - -// 앱 전역 스토어 -export const useAppStore = defineStore('app', { - state: () => ({ - loading: false, - snackbar: { - show: false, - message: '', - color: 'success', - timeout: 3000 - }, - notifications: [], - notificationCount: 0 - }), - - actions: { - setLoading(status) { - this.loading = status - }, - - showSnackbar(message, color = 'success', timeout = 3000) { - this.snackbar = { - show: true, - message, - color, - timeout - } - }, - - hideSnackbar() { - this.snackbar.show = false - }, - - addNotification(notification) { - this.notifications.unshift({ - id: Date.now(), - timestamp: new Date(), - ...notification - }) - this.notificationCount = this.notifications.length - }, - - clearNotifications() { - this.notifications = [] - this.notificationCount = 0 - } - } -}) - -// 매장 스토어 +// 매장 스토어에 추가할 fetchStoreInfo 메서드 export const useStoreStore = defineStore('store', { state: () => ({ storeInfo: null, - loading: false - }), - - getters: { - hasStoreInfo: (state) => !!state.storeInfo - }, - - actions: { - setStoreInfo(storeInfo) { - this.storeInfo = storeInfo - }, - - async fetchStoreInfo() { - try { - this.loading = true - const response = await storeService.getStore() // getStoreInfo가 아닌 getStore - this.storeInfo = response.data - return response - } catch (error) { - throw error - } finally { - this.loading = false - } - }, - - async registerStore(storeData) { - try { - this.loading = true - const response = await storeService.registerStore(storeData) - this.storeInfo = response.data - return response - } catch (error) { - throw error - } finally { - this.loading = false - } - }, - - async updateStore(storeId, storeData) { - try { - this.loading = true - const response = await storeService.updateStore(storeId, storeData) - this.storeInfo = response.data - return response - } catch (error) { - throw error - } finally { - this.loading = false - } - }, - - async createStoreInfo(storeData) { - try { - this.loading = true - const response = await storeService.createStoreInfo(storeData) - this.storeInfo = response.data - return response - } catch (error) { - throw error - } finally { - this.loading = false - } - } - } -}) - -// 메뉴 스토어 -export const useMenuStore = defineStore('menu', { - state: () => ({ - menus: [], loading: false, - totalCount: 0 + error: null }), getters: { - getMenuById: (state) => (id) => { - return state.menus.find(menu => menu.id === id) - }, - - getMenusByCategory: (state) => (category) => { - return state.menus.filter(menu => menu.category === category) - } + hasStoreInfo: (state) => !!state.storeInfo, + isLoading: (state) => state.loading }, actions: { - async fetchMenus() { + /** + * 매장 정보 조회 + */ + async fetchStoreInfo() { + console.log('=== Store 스토어: 매장 정보 조회 시작 ===') + this.loading = true + this.error = null + try { - this.loading = true - const response = await storeService.getMenus() - this.menus = response.data - this.totalCount = response.data.length - return response - } catch (error) { - throw error - } finally { - this.loading = false - } - }, - - async createMenu(menuData) { - try { - this.loading = true - const response = await storeService.createMenu(menuData) - this.menus.push(response.data) - this.totalCount++ - return response - } catch (error) { - throw error - } finally { - this.loading = false - } - }, - - async updateMenu(menuId, menuData) { - try { - this.loading = true - const response = await storeService.updateMenu(menuId, menuData) - const index = this.menus.findIndex(menu => menu.id === menuId) - if (index !== -1) { - this.menus[index] = response.data + // 스토어 서비스 임포트 + const { storeService } = await import('@/services/store') + + console.log('매장 정보 API 호출') + const result = await storeService.getStore() + + 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.storeInfo = result.data + return { success: true, data: result.data } + } else { + // 매장이 없거나 조회 실패한 경우 + console.log('⚠️ 매장 정보 없음 또는 조회 실패') + this.storeInfo = null + + if (result.message === '등록된 매장이 없습니다') { + return { success: false, message: '등록된 매장이 없습니다' } + } else { + return { success: false, message: result.message || '매장 정보 조회에 실패했습니다' } + } } - return response } catch (error) { - throw error + console.error('=== Store 스토어: 매장 정보 조회 실패 ===') + console.error('Error:', error) + + this.error = error.message + this.storeInfo = null + + // HTTP 상태 코드별 처리 + if (error.response?.status === 404) { + return { success: false, message: '등록된 매장이 없습니다' } + } + + if (error.response?.status >= 500) { + return { success: false, message: '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.' } + } + + if (error.response?.status === 401) { + return { success: false, message: '로그인이 필요합니다' } + } + + return { success: false, message: error.message || '매장 정보 조회에 실패했습니다' } } finally { this.loading = false } }, - - async deleteMenu(menuId) { + + /** + * 매장 등록 + */ + async registerStore(storeData) { + console.log('매장 등록 시작:', storeData) + this.loading = true + this.error = null + try { - this.loading = true - await storeService.deleteMenu(menuId) - this.menus = this.menus.filter(menu => menu.id !== menuId) - this.totalCount-- + const { storeService } = await import('@/services/store') + + const result = await storeService.registerStore(storeData) + + console.log('매장 등록 결과:', result) + + if (result.success) { + // 등록 성공 후 매장 정보 다시 조회 + await this.fetchStoreInfo() + return result + } else { + this.error = result.message + return result + } } catch (error) { - throw error + console.error('매장 등록 실패:', error) + this.error = error.message + return { success: false, message: error.message || '매장 등록에 실패했습니다' } } finally { this.loading = false } + }, + + /** + * 매장 정보 수정 + */ + async updateStore(storeId, storeData) { + console.log('매장 정보 수정 시작:', { storeId, storeData }) + this.loading = true + this.error = null + + try { + const { storeService } = await import('@/services/store') + + const result = await storeService.updateStore(storeId, storeData) + + console.log('매장 수정 결과:', result) + + if (result.success) { + // 수정 성공 후 매장 정보 다시 조회 + await this.fetchStoreInfo() + return result + } else { + this.error = result.message + return result + } + } catch (error) { + console.error('매장 수정 실패:', error) + this.error = error.message + return { success: false, message: error.message || '매장 수정에 실패했습니다' } + } finally { + this.loading = false + } + }, + + /** + * 매장 정보 초기화 + */ + clearStoreInfo() { + this.storeInfo = null + this.error = null + this.loading = false } } }) \ No newline at end of file diff --git a/src/store/store.js b/src/store/store.js index 138485c..45097a3 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -1,202 +1,266 @@ -//* src/store/store.js 수정 - 기존 구조 유지하고 API 연동만 추가 -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import storeService from '@/services/store' +//* src/services/store.js - 백엔드 API 연동 수정 +import { storeApi, handleApiError, formatSuccessResponse } from './api.js' -export const useStoreStore = defineStore('store', () => { - // 기존 상태들 유지 - const storeInfo = ref(null) - const menus = ref([]) - const salesData = ref(null) - const isLoading = ref(false) - - // 기존 computed 속성들 유지 - const hasStoreInfo = computed(() => !!storeInfo.value) - const menuCount = computed(() => menus.value?.length || 0) - - // fetchStoreInfo를 실제 API 호출로 수정 - const fetchStoreInfo = async () => { - if (import.meta.env.DEV) { - console.log('개발 모드: 매장 정보 API 호출 건너뛰기') - return { success: true } - } - - isLoading.value = true - +/** + * 매장 관련 API 서비스 + * 백엔드 Store Controller와 연동 (포트 8082) + */ +class StoreService { + /** + * 매장 등록 (STR-015: 매장 등록) + * @param {Object} storeData - 매장 정보 + * @returns {Promise} 매장 등록 결과 + */ + async registerStore(storeData) { try { - const result = await storeService.getStore() + console.log('매장 등록 API 호출 - 요청 데이터:', storeData) - if (result.success) { - storeInfo.value = result.data - return { success: true } - } else { - console.warn('매장 정보 조회 실패:', result.message) - return { success: false, error: result.message } - } - } catch (error) { - console.warn('매장 정보 조회 실패:', error) - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false - } - } - - // saveStoreInfo를 실제 API 호출로 수정 - const saveStoreInfo = async (storeData) => { - isLoading.value = true - - try { - let result - if (storeInfo.value) { - // 기존 매장 정보 수정 - result = await storeService.updateStore(storeData) - } else { - // 새 매장 등록 - result = await storeService.registerStore(storeData) + // 백엔드 StoreCreateRequest에 맞는 형태로 변환 + const requestData = { + storeName: storeData.storeName, + businessType: storeData.businessType, + address: storeData.address, + phoneNumber: storeData.phoneNumber, + businessHours: storeData.businessHours || `${storeData.openTime}-${storeData.closeTime}`, + closedDays: Array.isArray(storeData.holidays) ? storeData.holidays.join(',') : storeData.closedDays, + seatCount: parseInt(storeData.seatCount) || 0, + snsAccounts: { + instagram: storeData.instagramUrl || '', + blog: storeData.blogUrl || '' + }, + description: storeData.description || '' } - if (result.success) { - storeInfo.value = result.data - return { success: true, message: '매장 정보가 저장되었습니다.' } - } else { - return { success: false, error: result.message } - } - } catch (error) { - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false - } - } - - // fetchMenus를 실제 API 호출로 수정 - const fetchMenus = async () => { - if (!storeInfo.value?.storeId) { - console.warn('매장 ID가 없어 메뉴를 조회할 수 없습니다.') - return { success: false, error: '매장 정보가 필요합니다.' } - } - - isLoading.value = true - - try { - const result = await storeService.getMenus(storeInfo.value.storeId) + console.log('백엔드 전송 데이터:', requestData) - if (result.success) { - menus.value = result.data - return { success: true } - } else { - return { success: false, error: result.message } - } - } catch (error) { - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false - } - } - - // 메뉴 관련 메서드들 API 연동 추가 - const saveMenu = async (menuData) => { - isLoading.value = true - - try { - const result = await storeService.registerMenu(menuData) + const response = await storeApi.post('/register', requestData) - if (result.success) { - // 메뉴 목록 새로고침 - await fetchMenus() - return { success: true, message: '메뉴가 등록되었습니다.' } - } else { - return { success: false, error: result.message } - } - } catch (error) { - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false - } - } - - const updateMenu = async (menuId, menuData) => { - isLoading.value = true - - try { - const result = await storeService.updateMenu(menuId, menuData) + console.log('매장 등록 API 응답:', response.data) - if (result.success) { - // 메뉴 목록 새로고침 - await fetchMenus() - return { success: true, message: '메뉴가 수정되었습니다.' } + // 백엔드 응답 구조에 맞게 처리 + if (response.data.status === 200 || response.data.message?.includes('성공')) { + return formatSuccessResponse(response.data.data, response.data.message || '매장이 등록되었습니다.') } else { - return { success: false, error: result.message } + throw new Error(response.data.message || '매장 등록에 실패했습니다.') } } catch (error) { - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false + console.error('매장 등록 실패:', error) + return handleApiError(error) } } - const deleteMenu = async (menuId) => { - isLoading.value = true - + /** + * 매장 정보 조회 (STR-005: 매장 정보 관리) + * @returns {Promise} 매장 정보 + */ + async getStore() { try { - const result = await storeService.deleteMenu(menuId) + console.log('매장 정보 조회 API 호출') - if (result.success) { - // 메뉴 목록 새로고침 - await fetchMenus() - return { success: true, message: '메뉴가 삭제되었습니다.' } + const response = await storeApi.get('/') + + console.log('매장 정보 조회 API 응답:', response.data) + + // 백엔드 응답 구조에 맞게 처리 + if (response.data.status === 200 && response.data.data) { + return formatSuccessResponse(response.data.data, '매장 정보를 조회했습니다.') + } else if (response.data.data === null) { + // 매장이 없는 경우 + return { + success: false, + message: '등록된 매장이 없습니다', + data: null + } } else { - return { success: false, error: result.message } + throw new Error(response.data.message || '매장 정보를 찾을 수 없습니다.') } } catch (error) { - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false + 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) } } - // 매출 정보 조회 추가 - const fetchSalesData = async () => { - if (!storeInfo.value?.storeId) { - return { success: false, error: '매장 정보가 필요합니다.' } - } - - isLoading.value = true - + /** + * 매장 정보 수정 (STR-010: 매장 수정) + * @param {number} storeId - 매장 ID (현재는 사용하지 않음 - JWT에서 사용자 확인) + * @param {Object} storeData - 수정할 매장 정보 + * @returns {Promise} 매장 수정 결과 + */ + async updateStore(storeId, storeData) { try { - const result = await storeService.getSales(storeInfo.value.storeId) + console.log('매장 정보 수정 API 호출 - 요청 데이터:', storeData) - if (result.success) { - salesData.value = result.data - return { success: true } + // 백엔드 StoreUpdateRequest에 맞는 형태로 변환 + const requestData = { + storeName: storeData.storeName, + businessType: storeData.businessType, + address: storeData.address, + phoneNumber: storeData.phoneNumber, + businessHours: storeData.businessHours || `${storeData.openTime}-${storeData.closeTime}`, + closedDays: Array.isArray(storeData.holidays) ? storeData.holidays.join(',') : storeData.closedDays, + seatCount: parseInt(storeData.seatCount) || 0, + snsAccounts: { + instagram: storeData.instagramUrl || '', + blog: storeData.blogUrl || '' + }, + description: storeData.description || '' + } + + console.log('백엔드 전송 데이터:', requestData) + + // PUT 요청 (storeId는 JWT에서 추출하므로 URL에 포함하지 않음) + const response = await storeApi.put('/', requestData) + + console.log('매장 정보 수정 API 응답:', response.data) + + if (response.data.status === 200 || response.data.message?.includes('성공')) { + return formatSuccessResponse(response.data.data, response.data.message || '매장 정보가 수정되었습니다.') } else { - return { success: false, error: result.message } + throw new Error(response.data.message || '매장 정보 수정에 실패했습니다.') } } catch (error) { - return { success: false, error: '네트워크 오류가 발생했습니다.' } - } finally { - isLoading.value = false + console.error('매장 정보 수정 실패:', error) + return handleApiError(error) } } - return { - // 상태 - storeInfo, - menus, - salesData, - isLoading, - - // 컴퓨티드 - hasStoreInfo, - menuCount, - - // 메서드 - fetchStoreInfo, - saveStoreInfo, - fetchMenus, - saveMenu, - updateMenu, - deleteMenu, - fetchSalesData + /** + * 매출 정보 조회 (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) + } } -}) + /** + * 메뉴 등록 (STR-030: 메뉴 등록) + * @param {Object} menuData - 메뉴 정보 + * @returns {Promise} 메뉴 등록 결과 + */ + async registerMenu(menuData) { + try { + // 현재는 목업 처리 (추후 실제 API 연동 시 수정) + console.log('메뉴 등록 - 목업 처리:', menuData) + + const mockMenuResponse = { + id: Date.now(), + ...menuData, + createdAt: new Date().toISOString() + } + + return formatSuccessResponse(mockMenuResponse, '메뉴가 등록되었습니다.') + } catch (error) { + return handleApiError(error) + } + } + + /** + * 메뉴 목록 조회 (STR-025: 메뉴 조회) + * @returns {Promise} 메뉴 목록 + */ + async getMenus() { + try { + // 현재는 목업 데이터 반환 (추후 실제 API 연동 시 수정) + const mockMenus = [ + { + id: 1, + name: '김치찌개', + category: '찌개', + price: 8000, + description: '푸짐한 김치찌개', + imageUrl: '/images/menu-placeholder.png', + isPopular: true + }, + { + id: 2, + name: '제육볶음', + category: '볶음', + price: 12000, + description: '매콤한 제육볶음', + imageUrl: '/images/menu-placeholder.png', + isRecommended: true + } + ] + + return formatSuccessResponse(mockMenus, '메뉴 목록을 조회했습니다.') + } catch (error) { + return handleApiError(error) + } + } + + /** + * 메뉴 수정 (STR-035: 메뉴 수정) + * @param {number} menuId - 메뉴 ID + * @param {Object} menuData - 수정할 메뉴 정보 + * @returns {Promise} 메뉴 수정 결과 + */ + async updateMenu(menuId, menuData) { + try { + // 현재는 목업 처리 (추후 실제 API 연동 시 수정) + console.log('메뉴 수정 - 목업 처리:', { menuId, menuData }) + + const mockMenuResponse = { + id: menuId, + ...menuData, + updatedAt: new Date().toISOString() + } + + return formatSuccessResponse(mockMenuResponse, '메뉴가 수정되었습니다.') + } catch (error) { + return handleApiError(error) + } + } + + /** + * 메뉴 삭제 (STR-040: 메뉴 삭제) + * @param {number} menuId - 삭제할 메뉴 ID + * @returns {Promise} 메뉴 삭제 결과 + */ + async deleteMenu(menuId) { + try { + // 현재는 목업 처리 (추후 실제 API 연동 시 수정) + console.log('메뉴 삭제 - 목업 처리:', menuId) + + return formatSuccessResponse(null, '메뉴가 삭제되었습니다.') + } catch (error) { + return handleApiError(error) + } + } +} + +export const storeService = new StoreService() +export default storeService \ No newline at end of file diff --git a/src/views/StoreManagementView.vue b/src/views/StoreManagementView.vue index 7494f3f..5adaafe 100644 --- a/src/views/StoreManagementView.vue +++ b/src/views/StoreManagementView.vue @@ -1,4 +1,5 @@ -//* src/views/StoreManagementView.vue +//* src/views/StoreManagementView.vue - 데모 기능 제거 및 백엔드 연동 수정 + @@ -941,58 +247,24 @@ import { ref, computed, onMounted } from 'vue' import { useStoreStore } from '@/store/index' -/** - * AI 마케팅 서비스 - 매장 관리 페이지 - * 매장 정보 관리 및 메뉴 관리 기능 제공 - * 유저스토리: STR-005, STR-010, STR-015, STR-020, STR-025, STR-030, STR-035, STR-040 - */ - -// 스토어 const storeStore = useStoreStore() -// 탭 관리 +// 기존 상태들... const currentTab = ref('basic') - -// 매장 정보 관련 상태 const showCreateDialog = ref(false) const editMode = ref(false) const formValid = ref(false) const saving = ref(false) const imageInput = ref(null) +const storeForm = ref(null) -// 메뉴 관리 관련 상태 -const showMenuDialog = ref(false) -const editMenuMode = ref(false) -const menuFormValid = ref(false) -const savingMenu = ref(false) -const deletingMenu = ref(false) -const showDeleteMenuDialog = ref(false) -const deleteMenuTarget = ref(null) -const menuImageInput = ref(null) -const menuSearch = ref('') -const menuCategoryFilter = ref('전체') -const menuStatusFilter = ref('전체') - -// SNS 연동 관련 상태 -const snsCheckLoading = ref({ - instagram: false, - blog: false -}) -const showSnsResultDialog = ref(false) -const snsConnectionResult = ref({ - success: false, - platform: '', - message: '' -}) - -// 스낵바 상태 const snackbar = ref({ show: false, message: '', color: 'success' }) -// 폼 데이터 +// 기존 폼 데이터들... const formData = ref({ storeName: '', businessType: '', @@ -1011,892 +283,113 @@ const formData = ref({ imageUrl: '' }) -// 메뉴 폼 데이터 -const menuFormData = ref({ - menuName: '', - price: 0, - category: '', - description: '', - available: true, - recommended: false, - imageUrl: '' -}) - -// 메뉴 목록 (데모 데이터) -const menus = ref([ - { - id: 1, - menuName: '김치찌개', - category: '찌개류', - price: 8000, - description: '돼지고기와 신김치로 끓인 얼큰한 김치찌개', - available: true, - recommended: true, - imageUrl: '/images/kimchi-jjigae.jpg' - }, - { - id: 2, - menuName: '된장찌개', - category: '찌개류', - price: 7000, - description: '집된장으로 끓인 구수한 된장찌개', - available: true, - recommended: false, - imageUrl: '/images/doenjang-jjigae.jpg' - }, - { - id: 3, - menuName: '제육볶음', - category: '볶음류', - price: 12000, - description: '매콤달콤한 양념에 볶은 제육볶음', - available: false, - recommended: true, - imageUrl: '/images/jeyuk-bokkeum.jpg' - } -]) - // 컴퓨티드 속성 const storeInfo = computed(() => storeStore.storeInfo || {}) -const availableMenusCount = computed(() => - menus.value.filter(menu => menu.available).length -) - -const recommendedMenusCount = computed(() => - menus.value.filter(menu => menu.recommended).length -) - -const averagePrice = computed(() => { - if (menus.value.length === 0) return '0원' - const total = menus.value.reduce((sum, menu) => sum + menu.price, 0) - const average = Math.round(total / menus.value.length) - return formatCurrency(average) -}) - -const filteredMenus = computed(() => { - let filtered = menus.value - - // 검색 필터 - if (menuSearch.value) { - filtered = filtered.filter(menu => - menu.menuName.toLowerCase().includes(menuSearch.value.toLowerCase()) || - menu.description.toLowerCase().includes(menuSearch.value.toLowerCase()) - ) - } - - // 카테고리 필터 - if (menuCategoryFilter.value !== '전체') { - filtered = filtered.filter(menu => menu.category === menuCategoryFilter.value) - } - - // 상태 필터 - if (menuStatusFilter.value !== '전체') { - const isAvailable = menuStatusFilter.value === '판매중' - filtered = filtered.filter(menu => menu.available === isAvailable) - } - - return filtered -}) - -const menuCategories = computed(() => { - const categories = [...new Set(menus.value.map(menu => menu.category))] - return categories.sort() -}) - -// 선택 옵션 -const businessTypes = [ - '한식', '중식', '일식', '양식', '분식', '치킨', '피자', '버거', - '카페', '디저트', '술집', '기타' -] - -const daysOfWeek = [ - { title: '월요일', value: 'monday' }, - { title: '화요일', value: 'tuesday' }, - { title: '수요일', value: 'wednesday' }, - { title: '목요일', value: 'thursday' }, - { title: '금요일', value: 'friday' }, - { title: '토요일', value: 'saturday' }, - { title: '일요일', value: 'sunday' } -] - -// 유효성 검사 규칙 -const businessNumberRules = [ - v => !!v || '사업자등록번호를 입력해주세요', - v => /^\d{3}-\d{2}-\d{5}$/.test(v) || '올바른 사업자등록번호 형식이 아닙니다 (예: 123-45-67890)' -] - -const phoneRules = [ - v => !!v || '연락처를 입력해주세요', - v => /^[0-9-+\s()]+$/.test(v) || '올바른 연락처 형식이 아닙니다' -] - -const priceRules = [ - v => !!v || '가격을 입력해주세요', - v => v > 0 || '가격은 0보다 커야 합니다' -] - -// 유틸리티 함수 -const formatCurrency = (amount) => { - return new Intl.NumberFormat('ko-KR', { - style: 'currency', - currency: 'KRW' - }).format(amount) -} - -const formatHolidays = (holidays) => { - if (!holidays || holidays.length === 0) return '없음' - const dayNames = { - monday: '월요일', - tuesday: '화요일', - wednesday: '수요일', - thursday: '목요일', - friday: '금요일', - saturday: '토요일', - sunday: '일요일' - } - return holidays.map(day => dayNames[day]).join(', ') -} - -const getCategoryColor = (category) => { - const colors = { - '면류': 'orange', - '튀김류': 'amber', - '음료': 'blue', - '안주': 'green', - '디저트': 'pink', - '찌개류': 'red', - '볶음류': 'purple' - } - return colors[category] || 'grey' -} - -// 메서드 - -/** - * SNS 계정 연동 확인 - * @param {string} platform - SNS 플랫폼 (instagram, blog) - */ -const checkSnsConnection = async (platform) => { - console.log(`${platform} 연동 확인 시작`) +// 유틸리티 함수들 +const formatClosedDays = (closedDays) => { + if (!closedDays) return '미설정' - snsCheckLoading.value[platform] = true + if (typeof closedDays === 'string') { + return closedDays + } - try { - // 개발 모드에서는 빠른 시뮬레이션 - const delay = import.meta.env.DEV ? 1000 : 2000 - await new Promise(resolve => setTimeout(resolve, delay)) - - // 랜덤하게 성공/실패 결정 (실제로는 API 응답에 따라 결정) - const isSuccess = Math.random() > 0.3 - - snsConnectionResult.value.success = isSuccess - snsConnectionResult.value.platform = platform - - if (isSuccess) { - snsConnectionResult.value.message = platform === 'instagram' - ? '인스타그램 계정 연동이 확인되었습니다!' - : '네이버 블로그 연동이 확인되었습니다!' - } else { - snsConnectionResult.value.message = platform === 'instagram' - ? '인스타그램 계정을 찾을 수 없거나 연동할 수 없습니다. URL을 확인해주세요.' - : '네이버 블로그를 찾을 수 없거나 연동할 수 없습니다. URL을 확인해주세요.' + if (Array.isArray(closedDays)) { + const dayNames = { + 'monday': '월요일', + 'tuesday': '화요일', + 'wednesday': '수요일', + 'thursday': '목요일', + 'friday': '금요일', + 'saturday': '토요일', + 'sunday': '일요일' } - showSnsResultDialog.value = true - - } catch (error) { - console.error('SNS 연동 확인 중 오류:', error) - snsConnectionResult.value.success = false - snsConnectionResult.value.message = '연동 확인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.' - showSnsResultDialog.value = true - } finally { - snsCheckLoading.value[platform] = false - } -} - -/** - * 데모 매장 데이터 로드 (개발용) - */ -const loadDemoStoreData = () => { - console.log('데모 매장 데이터 로드') - - // 데모 매장 정보 설정 - const demoStoreInfo = { - id: 1, - storeName: '김씨네 분식점', - businessType: '분식', - ownerName: '김점주', - businessNumber: '123-45-67890', - address: '서울특별시 강남구 테헤란로 123', - phoneNumber: '02-1234-5678', - seatCount: 20, - instagramUrl: '@kimfood_bunsik', - blogUrl: 'blog.naver.com/kimfood123', - openTime: '09:00', - closeTime: '21:00', - holidays: ['sunday'], - deliveryAvailable: true, - takeoutAvailable: true, - imageUrl: '/images/store-demo.jpg' + return closedDays.map(day => dayNames[day] || day).join(', ') || '연중무휴' } - // 스토어에 데모 데이터 직접 설정 - storeStore.storeInfo = demoStoreInfo - - showSnackbar('데모 매장 정보가 로드되었습니다', 'success') + return '미설정' } -/** - * 기본 정보 수정 모드로 전환 - */ -const editBasicInfo = () => { - editMode.value = true +const getSnsAccount = (platform) => { + const snsAccounts = storeInfo.value.snsAccounts - // 현재 매장 정보를 폼 데이터에 복사 - Object.assign(formData.value, { - storeName: storeInfo.value.storeName || '', - businessType: storeInfo.value.businessType || '', - ownerName: storeInfo.value.ownerName || '', - businessNumber: storeInfo.value.businessNumber || '', - address: storeInfo.value.address || '', - phoneNumber: storeInfo.value.phoneNumber || '', - seatCount: storeInfo.value.seatCount || 0, - instagramUrl: storeInfo.value.instagramUrl || '', - blogUrl: storeInfo.value.blogUrl || '', - openTime: storeInfo.value.openTime || '09:00', - closeTime: storeInfo.value.closeTime || '21:00', - holidays: storeInfo.value.holidays || [], - deliveryAvailable: storeInfo.value.deliveryAvailable || false, - takeoutAvailable: storeInfo.value.takeoutAvailable || true, - imageUrl: storeInfo.value.imageUrl || '' - }) + if (!snsAccounts) return null - showCreateDialog.value = true -} - -/** - * 이미지 선택 - */ -const selectImage = () => { - imageInput.value?.click() -} - -/** - * 이미지 업로드 처리 - */ -const handleImageUpload = (event) => { - const file = event.target.files[0] - if (file) { - // 실제로는 서버에 업로드하고 URL을 받아옴 - const reader = new FileReader() - reader.onload = (e) => { - formData.value.imageUrl = e.target.result + if (typeof snsAccounts === 'string') { + try { + const parsed = JSON.parse(snsAccounts) + return parsed[platform] + } catch { + return null } - reader.readAsDataURL(file) } -} - -/** - * 매장 정보 저장 - */ -const saveStoreInfo = async () => { - if (!storeForm.value?.validate()) return - - console.log('매장 정보 저장:', storeFormData.value) - try { - const result = await storeStore.saveStoreInfo(storeFormData.value) - - if (result.success) { - appStore.showSnackbar(result.message || '매장 정보가 저장되었습니다', 'success') - showStoreForm.value = false - } else { - appStore.showSnackbar(result.error || '저장에 실패했습니다', 'error') - } - } catch (error) { - console.error('매장 정보 저장 실패:', error) - appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error') + if (typeof snsAccounts === 'object') { + return snsAccounts[platform] } -} - -/** - * 다이얼로그 닫기 - */ -const closeDialog = () => { - showCreateDialog.value = false - editMode.value = false - // 폼 데이터 초기화 - Object.assign(formData.value, { - storeName: '', - businessType: '', - ownerName: '', - businessNumber: '', - address: '', - phoneNumber: '', - seatCount: 0, - instagramUrl: '', - blogUrl: '', - openTime: '09:00', - closeTime: '21:00', - holidays: [], - deliveryAvailable: false, - takeoutAvailable: true, - imageUrl: '' - }) + return null } -// 메뉴 관리 메서드 - -/** - * 메뉴 등록 다이얼로그 열기 - */ -const openCreateMenuDialog = () => { - editMenuMode.value = false - resetMenuForm() - showMenuDialog.value = true -} - -/** - * 메뉴 수정 - */ -const editMenu = (menu) => { - editMenuMode.value = true - Object.assign(menuFormData.value, menu) - showMenuDialog.value = true -} - -/** - * 메뉴 삭제 확인 - */ -const confirmDeleteMenu = (menu) => { - deleteMenuTarget.value = menu - showDeleteMenuDialog.value = true -} - -/** - * 메뉴 이미지 선택 - */ -const selectMenuImage = () => { - menuImageInput.value?.click() -} - -/** - * 메뉴 이미지 업로드 처리 - */ -const handleMenuImageUpload = (event) => { - const file = event.target.files[0] - if (file) { - const reader = new FileReader() - reader.onload = (e) => { - menuFormData.value.imageUrl = e.target.result - } - reader.readAsDataURL(file) - } -} - -/** - * 메뉴 저장 - */ -const saveMenu = async () => { - if (!menuForm.value?.validate()) return - - console.log('메뉴 저장:', menuFormData.value) - - try { - let result - if (isMenuEdit.value && editingMenuId.value) { - result = await storeStore.updateMenu(editingMenuId.value, menuFormData.value) - } else { - result = await storeStore.saveMenu(menuFormData.value) - } - - if (result.success) { - appStore.showSnackbar(result.message || '메뉴가 저장되었습니다', 'success') - showMenuForm.value = false - resetMenuForm() - } else { - appStore.showSnackbar(result.error || '저장에 실패했습니다', 'error') - } - } catch (error) { - console.error('메뉴 저장 실패:', error) - appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error') - } -} - -/** - * 메뉴 삭제 - */ -const deleteMenu = async (menuId) => { - try { - const result = await storeStore.deleteMenu(menuId) - - if (result.success) { - appStore.showSnackbar(result.message || '메뉴가 삭제되었습니다', 'success') - } else { - appStore.showSnackbar(result.error || '삭제에 실패했습니다', 'error') - } - } catch (error) { - console.error('메뉴 삭제 실패:', error) - appStore.showSnackbar('네트워크 오류가 발생했습니다', 'error') - } -} - -/** - * 메뉴 다이얼로그 닫기 - */ -const closeMenuDialog = () => { - showMenuDialog.value = false - editMenuMode.value = false - resetMenuForm() -} - -/** - * 메뉴 폼 초기화 - */ -const resetMenuForm = () => { - Object.assign(menuFormData.value, { - menuName: '', - price: 0, - category: '', - description: '', - available: true, - recommended: false, - imageUrl: '' - }) -} - -/** - * 필터 초기화 - */ -const clearFilters = () => { - menuSearch.value = '' - menuCategoryFilter.value = '전체' - menuStatusFilter.value = '전체' -} - -/** - * 스낵바 표시 - */ const showSnackbar = (message, color = 'success') => { snackbar.value.message = message snackbar.value.color = color snackbar.value.show = true } -// 메뉴 상세 다이얼로그 관련 상태 (기존 상태들에 추가) -const showMenuDetailDialog = ref(false) -const selectedMenuDetail = ref(null) - -// 메뉴 관리 메서드에 추가할 함수들 - /** - * 메뉴 상세보기 - */ -const viewMenuDetail = (menu) => { - selectedMenuDetail.value = { ...menu } - showMenuDetailDialog.value = true -} - -/** - * 상세화면에서 수정 모드로 전환 - */ -const editFromDetail = () => { - showMenuDetailDialog.value = false - editMenuMode.value = true - Object.assign(menuFormData.value, selectedMenuDetail.value) - showMenuDialog.value = true -} - -/** - * 컴포넌트 마운트 시 실행 + * 컴포넌트 마운트 시 실행 (데모 기능 제거, 백엔드 연동만) */ onMounted(async () => { - console.log('StoreManagementView 마운트됨') + console.log('=== StoreManagementView 마운트됨 ===') try { - // 매장 정보 로드 - if (!storeStore.hasStoreInfo) { - await storeStore.fetchStoreInfo() + // 매장 정보 조회 + const result = await storeStore.fetchStoreInfo() + + console.log('매장 정보 조회 결과:', result) + + if (result.success) { + console.log('✅ 매장 정보 로드 완료:', result.data) + showSnackbar('매장 정보를 불러왔습니다', 'success') + } else { + if (result.message === '등록된 매장이 없습니다') { + console.log('⚠️ 등록된 매장이 없음 - 등록 화면 표시') + // 매장이 없는 경우는 정상적인 상황이므로 에러 메시지 표시하지 않음 + } else { + console.warn('❌ 매장 정보 조회 실패:', result.message) + showSnackbar(result.message || '매장 정보를 불러오는데 실패했습니다', 'error') + } } - - // 메뉴 목록 로드 - await storeStore.fetchMenus() - } catch (error) { - console.warn('매장 관리 데이터 로드 실패 (개발 중이므로 무시):', error) + console.error('매장 정보 조회 중 예외 발생:', error) + showSnackbar('매장 정보를 불러오는 중 오류가 발생했습니다', 'error') } }) + +// 기존 메서드들 유지 (데모 데이터 로드 함수만 제거) +// loadDemoStoreData 함수 제거 + +// 나머지 함수들은 기존과 동일하게 유지... \ No newline at end of file