This commit is contained in:
SeoJHeasdw 2025-06-11 14:44:16 +09:00
parent 2e28c5a9df
commit c052e3300f
5 changed files with 951 additions and 0 deletions

156
src/services/api.js Normal file
View File

@ -0,0 +1,156 @@
//* src/services/api.js
import axios from 'axios'
// 런타임 환경 설정에서 API URL 가져오기
const getApiUrls = () => {
const config = window.__runtime_config__ || {}
return {
GATEWAY_URL: config.GATEWAY_URL || 'http://20.1.2.3',
MEMBER_URL: config.MEMBER_URL || 'http://20.1.2.3/api/member',
AUTH_URL: config.AUTH_URL || 'http://20.1.2.3/api/auth',
STORE_URL: config.STORE_URL || 'http://20.1.2.3/api/store',
CONTENT_URL: config.CONTENT_URL || 'http://20.1.2.3/api/content',
RECOMMEND_URL: config.RECOMMEND_URL || 'http://20.1.2.3/api/recommendation',
}
}
// Axios 인스턴스 생성
const createApiInstance = (baseURL) => {
const instance = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
})
// 요청 인터셉터 - JWT 토큰 자동 추가
instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
},
)
// 응답 인터셉터 - 토큰 갱신 및 에러 처리
instance.interceptors.response.use(
(response) => {
return response
},
async (error) => {
const originalRequest = error.config
// 401 에러이고 토큰 갱신을 시도하지 않은 경우
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
try {
const refreshToken = localStorage.getItem('refreshToken')
if (refreshToken) {
const refreshResponse = await axios.post(`${getApiUrls().AUTH_URL}/refresh`, {
refreshToken,
})
const { accessToken } = refreshResponse.data.data
localStorage.setItem('accessToken', accessToken)
// 원래 요청에 새 토큰으로 재시도
originalRequest.headers.Authorization = `Bearer ${accessToken}`
return instance(originalRequest)
}
} catch (refreshError) {
// 토큰 갱신 실패 시 로그아웃 처리
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
window.location.href = '/login'
}
}
return Promise.reject(error)
},
)
return instance
}
// API 인스턴스들 생성
const apiUrls = getApiUrls()
export const memberApi = createApiInstance(apiUrls.MEMBER_URL)
export const authApi = createApiInstance(apiUrls.AUTH_URL)
export const storeApi = createApiInstance(apiUrls.STORE_URL)
export const contentApi = createApiInstance(apiUrls.CONTENT_URL)
export const recommendApi = createApiInstance(apiUrls.RECOMMEND_URL)
// 기본 API 인스턴스 (Gateway URL 사용)
export const api = createApiInstance(apiUrls.GATEWAY_URL)
// 공통 에러 핸들러
export const handleApiError = (error) => {
const response = error.response
if (!response) {
return {
success: false,
message: '네트워크 연결을 확인해주세요.',
code: 'NETWORK_ERROR',
}
}
const status = response.status
const data = response.data
switch (status) {
case 400:
return {
success: false,
message: data?.message || '잘못된 요청입니다.',
code: 'BAD_REQUEST',
}
case 401:
return {
success: false,
message: '인증이 필요합니다.',
code: 'UNAUTHORIZED',
}
case 403:
return {
success: false,
message: '접근 권한이 없습니다.',
code: 'FORBIDDEN',
}
case 404:
return {
success: false,
message: '요청하신 정보를 찾을 수 없습니다.',
code: 'NOT_FOUND',
}
case 500:
return {
success: false,
message: '서버 오류가 발생했습니다.',
code: 'SERVER_ERROR',
}
default:
return {
success: false,
message: data?.message || '알 수 없는 오류가 발생했습니다.',
code: 'UNKNOWN_ERROR',
}
}
}
// 성공 응답 포맷터
export const formatSuccessResponse = (data, message = '성공적으로 처리되었습니다.') => {
return {
success: true,
message,
data,
}
}

205
src/services/auth.js Normal file
View File

@ -0,0 +1,205 @@
//* src/services/auth.js
import { memberApi, authApi, handleApiError, formatSuccessResponse } from './api.js'
/**
* 인증 관련 API 서비스
* 유저스토리: USR-005, USR-010, USR-020, USR-030, USR-035, USR-040
*/
class AuthService {
/**
* 로그인 (USR-005: 정상 로그인)
* @param {Object} credentials - 로그인 정보
* @param {string} credentials.userId - 사용자 ID
* @param {string} credentials.password - 비밀번호
* @returns {Promise<Object>} 로그인 결과
*/
async login(credentials) {
try {
const response = await authApi.post('/login', {
userId: credentials.userId,
password: credentials.password,
})
const { accessToken, refreshToken, expiresIn } = response.data.data
// 토큰 저장
localStorage.setItem('accessToken', accessToken)
localStorage.setItem('refreshToken', refreshToken)
localStorage.setItem('tokenExpiresIn', expiresIn.toString())
localStorage.setItem('userId', credentials.userId)
return formatSuccessResponse(response.data.data, '로그인되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 로그아웃 (USR-020: 로그아웃)
* @returns {Promise<Object>} 로그아웃 결과
*/
async logout() {
try {
const refreshToken = localStorage.getItem('refreshToken')
if (refreshToken) {
await authApi.post('/logout', { refreshToken })
}
// 로컬 스토리지 정리
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
localStorage.removeItem('tokenExpiresIn')
localStorage.removeItem('userId')
return formatSuccessResponse(null, '로그아웃되었습니다.')
} catch (error) {
// 로그아웃은 실패해도 로컬 데이터는 정리
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
localStorage.removeItem('tokenExpiresIn')
localStorage.removeItem('userId')
return handleApiError(error)
}
}
/**
* 토큰 갱신
* @returns {Promise<Object>} 토큰 갱신 결과
*/
async refreshToken() {
try {
const refreshToken = localStorage.getItem('refreshToken')
if (!refreshToken) {
throw new Error('리프레시 토큰이 없습니다.')
}
const response = await authApi.post('/refresh', { refreshToken })
const { accessToken, refreshToken: newRefreshToken, expiresIn } = response.data.data
localStorage.setItem('accessToken', accessToken)
localStorage.setItem('refreshToken', newRefreshToken)
localStorage.setItem('tokenExpiresIn', expiresIn.toString())
return formatSuccessResponse(response.data.data, '토큰이 갱신되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 회원가입 (USR-030: 회원등록)
* @param {Object} memberData - 회원가입 정보
* @param {string} memberData.userId - 사용자 ID
* @param {string} memberData.password - 비밀번호
* @param {string} memberData.name - 이름
* @param {string} memberData.businessNumber - 사업자 번호
* @param {string} memberData.email - 이메일
* @returns {Promise<Object>} 회원가입 결과
*/
async register(memberData) {
try {
const response = await memberApi.post('/register', {
userId: memberData.userId,
password: memberData.password,
name: memberData.name,
businessNumber: memberData.businessNumber,
email: memberData.email,
})
return formatSuccessResponse(response.data.data, '회원가입이 완료되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* ID 중복 확인 (USR-035: 중복ID검사)
* @param {string} userId - 확인할 사용자 ID
* @returns {Promise<Object>} 중복 확인 결과
*/
async checkDuplicate(userId) {
try {
const response = await memberApi.get(`/check-duplicate?userId=${userId}`)
const { isDuplicate } = response.data.data
if (isDuplicate) {
return {
success: false,
message: '이미 사용 중인 ID입니다.',
data: { isDuplicate: true },
}
}
return formatSuccessResponse({ isDuplicate: false }, '사용 가능한 ID입니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 비밀번호 유효성 검증 (USR-040: 암호유효성 검사)
* @param {string} password - 검증할 비밀번호
* @returns {Promise<Object>} 유효성 검증 결과
*/
async validatePassword(password) {
try {
const response = await memberApi.post('/validate-password', { password })
const { isValid, errors } = response.data.data
if (!isValid) {
return {
success: false,
message: '비밀번호가 규칙에 맞지 않습니다.',
data: { isValid: false, errors },
}
}
return formatSuccessResponse({ isValid: true, errors: [] }, '사용 가능한 비밀번호입니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 현재 로그인 상태 확인
* @returns {boolean} 로그인 여부
*/
isAuthenticated() {
const token = localStorage.getItem('accessToken')
const expiresIn = localStorage.getItem('tokenExpiresIn')
if (!token || !expiresIn) {
return false
}
// 토큰 만료 시간 확인 (여유를 두고 5분 전에 만료로 처리)
const now = Date.now()
const expiryTime = parseInt(expiresIn) - 5 * 60 * 1000
return now < expiryTime
}
/**
* 현재 사용자 정보 가져오기
* @returns {Object|null} 사용자 정보
*/
getCurrentUser() {
const userId = localStorage.getItem('userId')
const token = localStorage.getItem('accessToken')
if (!userId || !token || !this.isAuthenticated()) {
return null
}
return { userId }
}
}
export const authService = new AuthService()
export default authService

248
src/services/content.js Normal file
View File

@ -0,0 +1,248 @@
//* src/services/content.js
import { contentApi, handleApiError, formatSuccessResponse } from './api.js'
/**
* 마케팅 콘텐츠 관련 API 서비스
* 유저스토리: CON-005, CON-015, CON-020, CON-025, CON-030
*/
class ContentService {
/**
* SNS 게시물 생성 (CON-005: SNS 게시물 생성)
* @param {Object} contentData - SNS 콘텐츠 생성 정보
* @returns {Promise<Object>} 생성된 SNS 콘텐츠
*/
async generateSnsContent(contentData) {
try {
const response = await contentApi.post('/sns/generate', {
targetType: contentData.targetType, // 메뉴, 매장, 이벤트
platform: contentData.platform, // 인스타그램, 네이버블로그
images: contentData.images,
eventName: contentData.eventName,
startDate: contentData.startDate,
endDate: contentData.endDate,
toneAndManner: contentData.toneAndManner, // 친근함, 전문적, 유머러스, 고급스러운
promotionType: contentData.promotionType, // 할인정보, 이벤트정보, 신메뉴알림, 없음
emotionIntensity: contentData.emotionIntensity, // 차분함, 보통, 열정적, 과장된
requirements: contentData.requirements || '',
})
return formatSuccessResponse(response.data.data, 'SNS 게시물이 생성되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* SNS 게시물 저장
* @param {Object} saveData - 저장할 SNS 콘텐츠 정보
* @returns {Promise<Object>} 저장 결과
*/
async saveSnsContent(saveData) {
try {
const response = await contentApi.post('/sns/save', {
title: saveData.title,
content: saveData.content,
hashtags: saveData.hashtags,
images: saveData.images,
platform: saveData.platform,
status: saveData.status || 'DRAFT',
publishSchedule: saveData.publishSchedule,
})
return formatSuccessResponse(response.data.data, 'SNS 게시물이 저장되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 홍보 포스터 생성 (CON-015: 홍보 포스터 생성)
* @param {Object} posterData - 포스터 생성 정보
* @returns {Promise<Object>} 생성된 포스터
*/
async generatePoster(posterData) {
try {
const response = await contentApi.post('/poster/generate', {
targetType: posterData.targetType,
images: posterData.images,
eventName: posterData.eventName,
startDate: posterData.startDate,
endDate: posterData.endDate,
photoStyle: posterData.photoStyle, // 모던, 클래식, 감성적
promotionType: posterData.promotionType,
emotionIntensity: posterData.emotionIntensity,
sizes: posterData.sizes || ['1:1', '9:16', '16:9'], // SNS용, 스토리용, 블로그용
})
return formatSuccessResponse(response.data.data, '홍보 포스터가 생성되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 홍보 포스터 저장
* @param {Object} saveData - 저장할 포스터 정보
* @returns {Promise<Object>} 저장 결과
*/
async savePoster(saveData) {
try {
const response = await contentApi.post('/poster/save', {
title: saveData.title,
images: saveData.images,
posterSizes: saveData.posterSizes,
targetType: saveData.targetType,
eventName: saveData.eventName,
status: saveData.status || 'DRAFT',
})
return formatSuccessResponse(response.data.data, '홍보 포스터가 저장되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 콘텐츠 목록 조회 (CON-020: 마케팅 콘텐츠 이력)
* @param {Object} filters - 필터링 옵션
* @returns {Promise<Object>} 콘텐츠 목록
*/
async getContents(filters = {}) {
try {
const queryParams = new URLSearchParams()
if (filters.contentType) queryParams.append('contentType', filters.contentType)
if (filters.platform) queryParams.append('platform', filters.platform)
if (filters.period) queryParams.append('period', filters.period)
if (filters.sortBy) queryParams.append('sortBy', filters.sortBy || 'latest')
if (filters.page) queryParams.append('page', filters.page)
if (filters.size) queryParams.append('size', filters.size || 20)
if (filters.search) queryParams.append('search', filters.search)
const response = await contentApi.get(`/?${queryParams.toString()}`)
return formatSuccessResponse(response.data.data, '콘텐츠 목록을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 진행 중인 콘텐츠 조회
* @param {string} period - 조회 기간
* @returns {Promise<Object>} 진행 중인 콘텐츠 목록
*/
async getOngoingContents(period = 'month') {
try {
const response = await contentApi.get(`/ongoing?period=${period}`)
return formatSuccessResponse(response.data.data, '진행 중인 콘텐츠를 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 콘텐츠 상세 조회
* @param {number} contentId - 콘텐츠 ID
* @returns {Promise<Object>} 콘텐츠 상세 정보
*/
async getContentDetail(contentId) {
try {
const response = await contentApi.get(`/${contentId}`)
return formatSuccessResponse(response.data.data, '콘텐츠 상세 정보를 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 콘텐츠 수정
* @param {number} contentId - 콘텐츠 ID
* @param {Object} updateData - 수정할 콘텐츠 정보
* @returns {Promise<Object>} 수정 결과
*/
async updateContent(contentId, updateData) {
try {
const response = await contentApi.put(`/${contentId}`, {
title: updateData.title,
content: updateData.content,
hashtags: updateData.hashtags,
startDate: updateData.startDate,
endDate: updateData.endDate,
status: updateData.status,
})
return formatSuccessResponse(response.data.data, '콘텐츠가 수정되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 콘텐츠 삭제 (CON-025: 콘텐츠 삭제)
* @param {number} contentId - 콘텐츠 ID
* @returns {Promise<Object>} 삭제 결과
*/
async deleteContent(contentId) {
try {
await contentApi.delete(`/${contentId}`)
return formatSuccessResponse(null, '콘텐츠가 삭제되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 다중 콘텐츠 삭제
* @param {number[]} contentIds - 삭제할 콘텐츠 ID 배열
* @returns {Promise<Object>} 삭제 결과
*/
async deleteContents(contentIds) {
try {
const deletePromises = contentIds.map((contentId) => this.deleteContent(contentId))
await Promise.all(deletePromises)
return formatSuccessResponse(null, `${contentIds.length}개의 콘텐츠가 삭제되었습니다.`)
} catch (error) {
return handleApiError(error)
}
}
/**
* 콘텐츠 재생성
* @param {number} contentId - 원본 콘텐츠 ID
* @param {Object} regenerateOptions - 재생성 옵션
* @returns {Promise<Object>} 재생성된 콘텐츠
*/
async regenerateContent(contentId, regenerateOptions = {}) {
try {
const response = await contentApi.post(`/${contentId}/regenerate`, regenerateOptions)
return formatSuccessResponse(response.data.data, '콘텐츠가 재생성되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 콘텐츠 발행 상태 변경
* @param {number} contentId - 콘텐츠 ID
* @param {string} status - 변경할 상태 (DRAFT, PUBLISHED, ARCHIVED)
* @returns {Promise<Object>} 상태 변경 결과
*/
async updateContentStatus(contentId, status) {
try {
const response = await contentApi.patch(`/${contentId}/status`, { status })
return formatSuccessResponse(response.data.data, '콘텐츠 상태가 변경되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
}
export const contentService = new ContentService()
export default contentService

154
src/services/recommend.js Normal file
View File

@ -0,0 +1,154 @@
//* src/services/recommend.js
import { recommendApi, handleApiError, formatSuccessResponse } from './api.js'
/**
* AI 추천 관련 API 서비스
* 유저스토리: REC-005
*/
class RecommendService {
/**
* AI 마케팅 생성 (REC-005: AI 마케팅 방법 추천)
* @param {Object} requestData - 마케팅 요청 정보
* @returns {Promise<Object>} 생성된 마케팅
*/
async generateMarketingTips(requestData = {}) {
try {
const response = await recommendApi.post('/marketing-tips', {
storeId: requestData.storeId,
includeWeather: requestData.includeWeather !== false, // 기본값 true
includeTrends: requestData.includeTrends !== false, // 기본값 true
maxTips: requestData.maxTips || 3,
tipType: requestData.tipType || 'general', // general, menu, marketing, operation
})
return formatSuccessResponse(response.data.data, 'AI 마케팅 팁이 생성되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 날씨 기반 메뉴 추천
* @param {number} storeId - 매장 ID
* @returns {Promise<Object>} 날씨 기반 메뉴 추천
*/
async getWeatherBasedMenuRecommendation(storeId) {
try {
const response = await recommendApi.get(`/weather-menu/${storeId}`)
return formatSuccessResponse(response.data.data, '날씨 기반 메뉴 추천을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 매출 예측 추천
* @param {number} storeId - 매장 ID
* @param {string} period - 예측 기간 (day, week, month)
* @returns {Promise<Object>} 매출 예측 정보
*/
async getSalesPrediction(storeId, period = 'day') {
try {
const response = await recommendApi.get(`/sales-prediction/${storeId}?period=${period}`)
return formatSuccessResponse(response.data.data, '매출 예측 정보를 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 인기 메뉴 추천
* @param {number} storeId - 매장 ID
* @param {string} period - 분석 기간
* @returns {Promise<Object>} 인기 메뉴 추천
*/
async getPopularMenuRecommendation(storeId, period = 'month') {
try {
const response = await recommendApi.get(`/popular-menu/${storeId}?period=${period}`)
return formatSuccessResponse(response.data.data, '인기 메뉴 추천을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 마케팅 전략 추천
* @param {number} storeId - 매장 ID
* @param {string} strategyType - 전략 유형 (sales_boost, customer_retention, cost_reduction)
* @returns {Promise<Object>} 마케팅 전략 추천
*/
async getMarketingStrategy(storeId, strategyType = 'sales_boost') {
try {
const response = await recommendApi.get(`/marketing-strategy/${storeId}?type=${strategyType}`)
return formatSuccessResponse(response.data.data, '마케팅 전략 추천을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 통합 AI 추천 (매출 예측 + 메뉴 추천 + 마케팅 전략)
* @param {number} storeId - 매장 ID
* @returns {Promise<Object>} 통합 AI 추천 정보
*/
async getComprehensiveRecommendation(storeId) {
try {
const response = await recommendApi.get(`/comprehensive-recommendation/${storeId}`)
return formatSuccessResponse(response.data.data, '통합 AI 추천을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 추천 기록 조회
* @param {Object} filters - 필터링 옵션
* @returns {Promise<Object>} 추천 기록 목록
*/
async getRecommendationHistory(filters = {}) {
try {
const queryParams = new URLSearchParams()
if (filters.type) queryParams.append('type', filters.type)
if (filters.startDate) queryParams.append('startDate', filters.startDate)
if (filters.endDate) queryParams.append('endDate', filters.endDate)
if (filters.page) queryParams.append('page', filters.page)
if (filters.size) queryParams.append('size', filters.size || 20)
const response = await recommendApi.get(`/history?${queryParams.toString()}`)
return formatSuccessResponse(response.data.data, '추천 기록을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 추천 피드백 제공
* @param {number} recommendationId - 추천 ID
* @param {Object} feedback - 피드백 정보
* @returns {Promise<Object>} 피드백 제공 결과
*/
async provideFeedback(recommendationId, feedback) {
try {
const response = await recommendApi.post(`/feedback/${recommendationId}`, {
rating: feedback.rating, // 1-5 점수
useful: feedback.useful, // true/false
comment: feedback.comment || '',
appliedSuggestions: feedback.appliedSuggestions || [],
})
return formatSuccessResponse(response.data.data, '피드백이 제공되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
}
export const recommendService = new RecommendService()
export default recommendService

188
src/services/store.js Normal file
View File

@ -0,0 +1,188 @@
//* src/services/store.js
import { storeApi, handleApiError, formatSuccessResponse } from './api.js'
/**
* 매장 관련 API 서비스
* 유저스토리: STR-005, STR-010, STR-015, STR-020, STR-025, STR-030, STR-035, STR-040
*/
class StoreService {
/**
* 매장 등록 (STR-015: 매장 등록)
* @param {Object} storeData - 매장 정보
* @returns {Promise<Object>} 매장 등록 결과
*/
async registerStore(storeData) {
try {
const response = await storeApi.post('/register', {
storeName: storeData.storeName,
storeImage: storeData.storeImage,
businessType: storeData.businessType,
address: storeData.address,
phoneNumber: storeData.phoneNumber,
businessNumber: storeData.businessNumber,
instaAccount: storeData.instaAccount,
naverBlogAccount: storeData.naverBlogAccount,
operatingHours: storeData.operatingHours,
closedDays: storeData.closedDays,
seatCount: storeData.seatCount,
})
return formatSuccessResponse(response.data.data, '매장이 등록되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 매장 정보 조회 (STR-005: 매장 정보 관리)
* @returns {Promise<Object>} 매장 정보
*/
async getStore() {
try {
const response = await storeApi.get('/')
return formatSuccessResponse(response.data.data, '매장 정보를 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 매장 정보 수정 (STR-010: 매장 수정)
* @param {number} storeId - 매장 ID
* @param {Object} storeData - 수정할 매장 정보
* @returns {Promise<Object>} 매장 수정 결과
*/
async updateStore(storeId, storeData) {
try {
const response = await storeApi.put(`/${storeId}`, storeData)
return formatSuccessResponse(response.data.data, '매장 정보가 수정되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 매출 정보 조회 (STR-020: 대시보드)
* @param {string} period - 조회 기간 (today, week, month, year)
* @returns {Promise<Object>} 매출 정보
*/
async getSales(period = 'today') {
try {
const response = await storeApi.get(`/sales?period=${period}`)
return formatSuccessResponse(response.data.data, '매출 정보를 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 메뉴 등록 (STR-030: 메뉴 등록)
* @param {Object} menuData - 메뉴 정보
* @returns {Promise<Object>} 메뉴 등록 결과
*/
async registerMenu(menuData) {
try {
const response = await storeApi.post('/menu/register', {
menuName: menuData.menuName,
menuCategory: menuData.menuCategory,
menuImage: menuData.menuImage,
price: menuData.price,
description: menuData.description,
isPopular: menuData.isPopular || false,
isRecommended: menuData.isRecommended || false,
})
return formatSuccessResponse(response.data.data, '메뉴가 등록되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 메뉴 목록 조회 (STR-025: 메뉴 조회)
* @param {Object} filters - 필터링 옵션
* @returns {Promise<Object>} 메뉴 목록
*/
async getMenus(filters = {}) {
try {
const queryParams = new URLSearchParams()
if (filters.category) queryParams.append('category', filters.category)
if (filters.sortBy) queryParams.append('sortBy', filters.sortBy)
if (filters.isPopular !== undefined) queryParams.append('isPopular', filters.isPopular)
const response = await storeApi.get(`/menu?${queryParams.toString()}`)
return formatSuccessResponse(response.data.data, '메뉴 목록을 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 메뉴 수정 (STR-035: 메뉴 수정)
* @param {number} menuId - 메뉴 ID
* @param {Object} menuData - 수정할 메뉴 정보
* @returns {Promise<Object>} 메뉴 수정 결과
*/
async updateMenu(menuId, menuData) {
try {
const response = await storeApi.put(`/menu/${menuId}`, menuData)
return formatSuccessResponse(response.data.data, '메뉴가 수정되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 메뉴 삭제 (STR-040: 메뉴 삭제)
* @param {number} menuId - 메뉴 ID
* @returns {Promise<Object>} 메뉴 삭제 결과
*/
async deleteMenu(menuId) {
try {
await storeApi.delete(`/menu/${menuId}`)
return formatSuccessResponse(null, '메뉴가 삭제되었습니다.')
} catch (error) {
return handleApiError(error)
}
}
/**
* 다중 메뉴 삭제
* @param {number[]} menuIds - 삭제할 메뉴 ID 배열
* @returns {Promise<Object>} 삭제 결과
*/
async deleteMenus(menuIds) {
try {
const deletePromises = menuIds.map((menuId) => this.deleteMenu(menuId))
await Promise.all(deletePromises)
return formatSuccessResponse(null, `${menuIds.length}개의 메뉴가 삭제되었습니다.`)
} catch (error) {
return handleApiError(error)
}
}
/**
* 매장 통계 정보 조회
* @returns {Promise<Object>} 매장 통계
*/
async getStoreStatistics() {
try {
const response = await storeApi.get('/statistics')
return formatSuccessResponse(response.data.data, '매장 통계를 조회했습니다.')
} catch (error) {
return handleApiError(error)
}
}
}
export const storeService = new StoreService()
export default storeService