diff --git a/next.config.ts b/next.config.ts index 7673b1d..047e31f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,6 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ images: { domains: ['storage.googleapis.com'], // 여기에 에러에 나온 도메인 추가 }, diff --git a/src/apis/event.ts b/src/apis/event.ts index a65248d..6b5a297 100644 --- a/src/apis/event.ts +++ b/src/apis/event.ts @@ -1,4 +1,16 @@ -import { CategoryProps, CheckListType, LeftEventCount, MonthlyChecklistType } from "./event.type"; +import { + CategoryProps, + CheckListType, + LeftEventCount, + MonthlyChecklistType, + CalendarEvent, + CalendarResponseDTO, + TempEvent, + CreateEventRequest, + UpdateEventRequest, + FixEventRequest, + CheckStatusUpdate +} from "./event.type"; import instance from "./instance"; export const getMajorEventChecklist = async (params?: { year?: number; category?: string }): Promise => { @@ -45,4 +57,248 @@ export const getCountLeftEvent = async(): Promise=>{ console.error("남은 과행사 개수 조회 실패", error); throw error; } -} \ No newline at end of file +} + +// ========== Calendar 관련 API 함수들 ========== + +// 월별 달력 조회 +export const getCalendarEvents = async (year: number, month: number): Promise => { + try { + const response = await instance.get("/api/v1/major-event/calendar", { + params: { year, month } + }); + console.log("월별 달력 조회 성공", response); + return response.data; + } catch (error) { + console.error("월별 달력 조회 실패", error); + throw error; + } +}; + +// 임시 행사 저장 (Swagger 스펙에 맞게 구현) +export const saveTempEvent = async (eventData: CreateEventRequest): Promise => { + try { + const formData = new FormData(); + + // 필수 필드들 추가 (Swagger Create 스키마에 맞게) + formData.append("eventName", eventData.eventName); + formData.append("category", eventData.category); + formData.append("hostType", eventData.hostType); + formData.append("location", eventData.location); + formData.append("notice", eventData.notice); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time); + + // 이미지 파일들 추가 (cardNewsImages 필드명 사용) + eventData.cardNewsImages.forEach((image) => { + formData.append("cardNewsImages", image); + }); + + // FormData 내용 로깅 + console.log("📤 임시 행사 저장 FormData 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.post("/api/v1/major-event/temp", formData); + console.log("✅ 임시 행사 저장 성공", response); + return response.data; + } catch (error) { + console.error("❌ 임시 행사 저장 실패", error); + throw error; + } +}; + +// 임시 행사 수정 (Swagger 스펙에 맞게 구현) +export const updateTempEvent = async (tempEventId: number, eventData: UpdateEventRequest): Promise => { + try { + const formData = new FormData(); + + // 필수 필드들 추가 (Swagger Update 스키마에 맞게) + formData.append("eventName", eventData.eventName); + formData.append("category", eventData.category); + formData.append("hostType", eventData.hostType); + formData.append("location", eventData.location); + formData.append("notice", eventData.notice); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time); + formData.append("majorEventId", eventData.majorEventId.toString()); + + // 기존 이미지 URL들 추가 + eventData.existingImageUrls.forEach((url) => { + formData.append("existingImageUrls", url); + }); + + // 새 이미지 파일들 추가 + eventData.newImages.forEach((image) => { + formData.append("newImages", image); + }); + + // FormData 내용 로깅 + console.log("📤 임시 행사 수정 FormData 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.patch(`/api/v1/major-event/temp/${tempEventId}`, formData); + console.log("✅ 임시 행사 수정 성공", response); + return response.data; + } catch (error) { + console.error("❌ 임시 행사 수정 실패", error); + throw error; + } +}; + +// 임시 행사 삭제 +export const deleteTempEvent = async (tempEventId: number): Promise => { + try { + const response = await instance.delete(`/api/v1/major-event/temp/${tempEventId}`); + console.log("임시 행사 삭제 성공", response); + } catch (error) { + console.error("임시 행사 삭제 실패", error); + throw error; + } +}; + +// 임시 행사 최종 제출 (확정) +export const submitAllTempEvents = async (tempEventIds: number[]): Promise => { + try { + const response = await instance.post("/api/v1/major-event/fix", { + tempEventIds + }); + console.log("임시 행사 최종 제출 성공", response); + return response.data; + } catch (error) { + console.error("임시 행사 최종 제출 실패", error); + throw error; + } +}; + +// 확정된 행사 수정 (Swagger 스펙에 맞게 구현) +export const updateMajorEvent = async (majorEventId: number, eventData: UpdateEventRequest): Promise => { + try { + const formData = new FormData(); + + // 필수 필드들 추가 (Swagger Update 스키마에 맞게) + formData.append("eventName", eventData.eventName); + formData.append("category", eventData.category); + formData.append("hostType", eventData.hostType); + formData.append("location", eventData.location); + formData.append("notice", eventData.notice); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time); + formData.append("majorEventId", eventData.majorEventId.toString()); + + // 기존 이미지 URL들 추가 + eventData.existingImageUrls.forEach((url) => { + formData.append("existingImageUrls", url); + }); + + // 새 이미지 파일들 추가 + eventData.newImages.forEach((image) => { + formData.append("newImages", image); + }); + + // FormData 내용 로깅 + console.log("📤 확정된 행사 수정 FormData 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.put(`/api/v1/major-event/${majorEventId}`, formData); + console.log("✅ 확정된 행사 수정 성공", response); + return response.data; + } catch (error) { + console.error("❌ 확정된 행사 수정 실패", error); + throw error; + } +}; + +// 확정된 행사 삭제 +export const deleteMajorEvent = async (majorEventId: number): Promise => { + try { + const response = await instance.delete(`/api/v1/major-event/${majorEventId}`); + console.log("확정된 행사 삭제 성공", response); + } catch (error) { + console.error("확정된 행사 삭제 실패", error); + throw error; + } +}; + +// 체크리스트 항목 상태 변경 +export const updateChecklistItemStatus = async (itemId: number, isChecked: boolean): Promise => { + try { + const response = await instance.put(`/api/v1/major-event/checklists/${itemId}`, { + isChecked + }); + console.log("체크리스트 항목 상태 변경 성공", response); + return response.data; + } catch (error) { + console.error("체크리스트 항목 상태 변경 실패", error); + throw error; + } +}; + +// ========== Swagger 스펙에 맞는 새로운 임시 행사 저장 API ========== + +/** + * Swagger 스펙에 맞는 임시 행사 저장 API + * @param eventData - Create 스키마에 맞는 행사 데이터 + * @returns TempEventResponseDTO + */ +export const saveTempEventV2 = async (eventData: CreateEventRequest): Promise => { + try { + const formData = new FormData(); + + // Swagger Create 스키마의 모든 필수 필드 추가 + formData.append("eventName", eventData.eventName); + formData.append("category", eventData.category); + formData.append("hostType", eventData.hostType); + formData.append("location", eventData.location); + formData.append("notice", eventData.notice); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time); + + // 이미지 파일들 추가 (cardNewsImages 필드명 사용) + eventData.cardNewsImages.forEach((image) => { + formData.append("cardNewsImages", image); + }); + + // FormData 내용 로깅 + console.log("📤 Swagger 스펙 임시 행사 저장 FormData 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.post("/api/v1/major-event/temp", formData); + console.log("✅ Swagger 스펙 임시 행사 저장 성공", response); + return response.data; + } catch (error) { + console.error("❌ Swagger 스펙 임시 행사 저장 실패", error); + throw error; + } +}; \ No newline at end of file diff --git a/src/apis/event.type.ts b/src/apis/event.type.ts index 454512e..0433c07 100644 --- a/src/apis/event.type.ts +++ b/src/apis/event.type.ts @@ -9,14 +9,22 @@ export interface CheckListType{//연도별, 행사별 export type CategoryProps = "ALL"| "FRESHMAN_ORIENTATION"| - "MEETING" | + "FIRST_SEMESTER_OPENING_MEETING" | + "FIRST_SEMESTER_CLOSING_MEETING" | + "SECOND_SEMESTER_OPENING_MEETING" | + "SECOND_SEMESTER_CLOSING_MEETING" | "FACE_TO_FACE_MEETING" | - "SNACK_EVENT" | + "FIRST_SEMESTER_MIDTERM_SNACK" | + "FIRST_SEMESTER_FINAL_SNACK" | + "SECOND_SEMESTER_MIDTERM_SNACK" | + "SECOND_SEMESTER_FINAL_SNACK" | "MT" | "KICK_OFF" | - "SPORTS_DAY" | + "COLLEGE_SPORTS_DAY" | + "UNIVERSITY_SPORTS_DAY" | "FESTIVAL" | - "HOME_COMING_DAY" + "HOMECOMING_DAY" | + "ETC" // Tip 객체 타입 정의 export interface Tip { @@ -47,4 +55,82 @@ export interface MonthlyChecklistType { // 할일별 export interface LeftEventCount{ count: number; // 남은 과행사 개수 +} + +// Calendar 관련 타입 정의 +export interface CalendarEvent { + id: number; + eventName: string; + startDate: string; + endDate: string; + eventStatus: "TEMPORARY" | "FIXED"; +} + +// API 스키마에 맞는 CalendarResponseDTO 타입 정의 +export interface CalendarResponseDTO { + id: number; + eventName: string; + startDate: string; + endDate: string; + eventStatus: "TEMPORARY" | "FIXED"; + category: string; + location: string; + notice: string; + googleFormLink: string; + time: string; + cardNewsImageUrls: string[]; +} + +// 임시 행사 관련 타입 정의 +export interface TempEvent { + tempEventId: number; + eventName: string; + category: string; + startDate: string; + endDate: string; + time: string; + location: string; + notice: string; + googleFormLink: string; + cardNewsImageUrls: string[]; +} + +// Swagger 스펙에 맞는 행사 생성 요청 타입 (Create 스키마) +export interface CreateEventRequest { + eventName: string; + category: "FRESHMAN_ORIENTATION" | "FIRST_SEMESTER_OPENING_MEETING" | "FIRST_SEMESTER_CLOSING_MEETING" | "SECOND_SEMESTER_OPENING_MEETING" | "SECOND_SEMESTER_CLOSING_MEETING" | "FACE_TO_FACE_MEETING" | "FIRST_SEMESTER_MIDTERM_SNACK" | "FIRST_SEMESTER_FINAL_SNACK" | "SECOND_SEMESTER_MIDTERM_SNACK" | "SECOND_SEMESTER_FINAL_SNACK" | "MT" | "KICK_OFF" | "COLLEGE_SPORTS_DAY" | "UNIVERSITY_SPORTS_DAY" | "FESTIVAL" | "HOMECOMING_DAY" | "ETC"; + hostType: "COMPUTER_SCIENCE" | "ETC"; + location: string; + notice: string; + googleFormLink: string; + startDate: string; // YYYY-MM-DD 형식 + endDate: string; // YYYY-MM-DD 형식 + time: string; // HH:mm:ss 형식 + cardNewsImages: File[]; +} + +// Swagger 스펙에 맞는 행사 수정 요청 타입 (Update 스키마) +export interface UpdateEventRequest { + eventName: string; + category: "FRESHMAN_ORIENTATION" | "FIRST_SEMESTER_OPENING_MEETING" | "FIRST_SEMESTER_CLOSING_MEETING" | "SECOND_SEMESTER_OPENING_MEETING" | "SECOND_SEMESTER_CLOSING_MEETING" | "FACE_TO_FACE_MEETING" | "FIRST_SEMESTER_MIDTERM_SNACK" | "FIRST_SEMESTER_FINAL_SNACK" | "SECOND_SEMESTER_MIDTERM_SNACK" | "SECOND_SEMESTER_FINAL_SNACK" | "MT" | "KICK_OFF" | "COLLEGE_SPORTS_DAY" | "UNIVERSITY_SPORTS_DAY" | "FESTIVAL" | "HOMECOMING_DAY" | "ETC"; + hostType: "COMPUTER_SCIENCE" | "ETC"; + location: string; + notice: string; + googleFormLink: string; + startDate: string; // YYYY-MM-DD 형식 + endDate: string; // YYYY-MM-DD 형식 + time: string; // HH:mm:ss 형식 + majorEventId: number; + existingImageUrls: string[]; + newImages: File[]; +} + +// 행사 확정 요청 타입 +export interface FixEventRequest { + tempEventIds: number[]; +} + +// 체크리스트 상태 업데이트 타입 +export interface CheckStatusUpdate { + isChecked: boolean; } \ No newline at end of file diff --git a/src/apis/instance.ts b/src/apis/instance.ts index b4699c4..0e068a0 100644 --- a/src/apis/instance.ts +++ b/src/apis/instance.ts @@ -1,31 +1,199 @@ import axios from "axios"; const baseURL = process.env.NEXT_PUBLIC_API_URL; + +// API URL 디버깅 정보 +console.log("🔧 API 설정:", { + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + baseURL: baseURL, + NODE_ENV: process.env.NODE_ENV +}); + +// 토큰 관리 함수들 +const getAccessToken = () => { + if (typeof window !== "undefined") { + // localStorage에서 먼저 확인 + const localToken = localStorage.getItem("AccessToken"); + if (localToken) { + return localToken; + } + + // HttpOnly 쿠키는 JavaScript에서 접근할 수 없음 + // 서버에서 토큰을 Authorization 헤더로 직접 전달하거나 + // HttpOnly가 아닌 쿠키로 설정해야 함 + console.log("🍪 접근 가능한 쿠키:", document.cookie); + console.log("⚠️ AccessToken은 HttpOnly 쿠키로 설정되어 있어 JavaScript에서 접근할 수 없습니다."); + } + return null; +}; + +const setAccessToken = (token: string) => { + if (typeof window !== "undefined") { + localStorage.setItem("AccessToken", token); + } +}; + +const removeAccessToken = () => { + if (typeof window !== "undefined") { + localStorage.removeItem("AccessToken"); + } +}; + export const instance = axios.create({ baseURL, - timeout: 3000, + timeout: 10000, // 타임아웃을 10초로 증가 headers: { "Content-Type": "application/json", }, withCredentials: true, + maxRedirects: 5, // 리다이렉트 최대 5회까지 자동 따라가기 + validateStatus: function (status) { + // 302 리다이렉트도 정상 상태로 처리하여 자동 따라가기 + return status >= 200 && status < 400; + }, }); -// 응답 인터셉터 +// 요청 인터셉터 - 토큰 추가 및 요청 데이터 콘솔 출력 +instance.interceptors.request.use( + (config) => { + // 토큰 추가 + const token = getAccessToken(); + console.log("🔑 토큰 확인:", { + token: token ? `${token.substring(0, 20)}...` : "토큰 없음", + localStorage: typeof window !== "undefined" ? localStorage.getItem("AccessToken") : "N/A", + cookies: typeof window !== "undefined" ? document.cookie : "N/A" + }); + + if (token) { + // 이미 Bearer가 포함되어 있는지 확인 + if (token.startsWith('Bearer ')) { + config.headers.Authorization = token; + } else { + config.headers.Authorization = `Bearer ${token}`; + } + } else { + console.warn("⚠️ 토큰이 없습니다. 401 에러가 발생할 수 있습니다."); + } + + // FormData가 아닌 경우에만 Content-Type 설정 + if (!(config.data instanceof FormData)) { + config.headers["Content-Type"] = "application/json"; + } else { + // FormData인 경우 Content-Type을 제거하여 브라우저가 자동으로 설정하도록 함 + delete config.headers["Content-Type"]; + console.log("📤 FormData 요청 감지 - Content-Type을 자동 설정으로 변경"); + } + + console.log("🚀 API 요청:", { + url: config.url, + method: config.method?.toUpperCase(), + baseURL: config.baseURL, + data: config.data, + params: config.params, + headers: config.headers, + }); + return config; + }, + (error) => { + console.error("❌ 요청 에러:", error); + return Promise.reject(error); + } +); + +// 응답 인터셉터 - 응답 데이터 콘솔 출력 instance.interceptors.response.use( - (response) => response, + (response) => { + console.log("✅ API 응답:", { + url: response.config.url, + status: response.status, + statusText: response.statusText, + data: response.data, + }); + return response; + }, (error) => { + // 에러 정보를 안전하게 로깅 + const errorInfo = { + url: error.config?.url || 'unknown', + status: error.response?.status || 'no response', + statusText: error.response?.statusText || 'no response', + data: error.response?.data || null, + message: error.message || 'unknown error', + code: error.code || 'no code', + }; + + console.error("❌ API 에러:", errorInfo); + + // 네트워크 에러 처리 - 더 구체적인 감지 + const isNetworkError = + error.code === 'ECONNREFUSED' || + error.code === 'ENOTFOUND' || + error.code === 'ETIMEDOUT' || + error.code === 'ECONNABORTED' || + error.message.includes('Network Error') || + error.message.includes('Failed to fetch') || + error.message.includes('ERR_NETWORK') || + error.message.includes('ERR_CONNECTION_REFUSED') || + error.message.includes('ERR_CONNECTION_TIMED_OUT') || + error.message.includes('ERR_INTERNET_DISCONNECTED') || + (error.response === undefined && error.request !== undefined) || + (error.code === 'ERR_CANCELED' && error.message.includes('timeout')); + + if (isNetworkError) { + console.error("🔌 네트워크 연결 실패:", { + code: error.code, + message: error.message, + url: error.config?.url, + isFormData: error.config?.data instanceof FormData, + requestType: error.config?.data instanceof FormData ? 'multipart/form-data' : 'json' + }); + + // FormData 요청인 경우 특별한 처리 + if (error.config?.data instanceof FormData) { + console.error("📤 FormData 요청 실패 - 파일 업로드 중 네트워크 오류 발생"); + const entries = Array.from(error.config.data.entries()) as [string, FormDataEntryValue][]; + console.log("FormData 내용:", { + hasFiles: entries.some((entry) => entry[1] instanceof File), + fieldCount: entries.length + }); + } + + // 사용자에게 친화적인 메시지 표시 + if (typeof window !== "undefined") { + console.warn("서버에 연결할 수 없습니다. 네트워크 상태를 확인해주세요."); + } + } + + // 302 리다이렉트 처리 + if (error.response && error.response.status === 302) { + console.error("🔄 302 리다이렉트 발생:", { + url: error.config?.url, + baseURL: error.config?.baseURL, + fullURL: `${error.config?.baseURL}${error.config?.url}`, + redirectLocation: error.response.headers?.location + }); + + } + + // 401 에러 처리 if (error.response && error.response.status === 401) { - // 401 에러 발생 시 처리 - if ( - !error.response.data || - error.response.data.error !== "TOKEN_EXPIRED" - ) { - // 클라이언트 사이드에서만 실행 + console.warn("🔐 인증 실패: 토큰을 제거하고 로그인 페이지로 이동합니다."); + removeAccessToken(); + if (typeof window !== "undefined") { window.location.href = "/login"; } } + + // 500 에러 처리 + if (error.response && error.response.status >= 500) { + console.error("🚨 서버 내부 오류:", error.response.status); + } + return Promise.reject(error); } ); +// 토큰 관리 함수들 export +export { getAccessToken, setAccessToken, removeAccessToken }; + export default instance; diff --git a/src/app/calendar/addEvent/api/eventApi.ts b/src/app/calendar/addEvent/api/eventApi.ts index 9aa89f1..385a27f 100644 --- a/src/app/calendar/addEvent/api/eventApi.ts +++ b/src/app/calendar/addEvent/api/eventApi.ts @@ -1,14 +1,19 @@ // 이벤트 API 관련 함수들 +import instance from "@/apis/instance"; +// Swagger 스펙에 맞는 EventData 인터페이스 interface EventData { - category?: string; + category?: "FRESHMAN_ORIENTATION" | "FIRST_SEMESTER_OPENING_MEETING" | "FIRST_SEMESTER_CLOSING_MEETING" | "SECOND_SEMESTER_OPENING_MEETING" | "SECOND_SEMESTER_CLOSING_MEETING" | "FACE_TO_FACE_MEETING" | "FIRST_SEMESTER_MIDTERM_SNACK" | "FIRST_SEMESTER_FINAL_SNACK" | "SECOND_SEMESTER_MIDTERM_SNACK" | "SECOND_SEMESTER_FINAL_SNACK" | "MT" | "KICK_OFF" | "COLLEGE_SPORTS_DAY" | "UNIVERSITY_SPORTS_DAY" | "FESTIVAL" | "HOMECOMING_DAY" | "ETC"; eventName?: string; - startDate: string; - endDate: string; + startDate: string; // YYYY-MM-DD 형식 + endDate: string; // YYYY-MM-DD 형식 location: string; announcement: string; googleFormLink: string; cardNewsLink: string; + cardNewsImages?: File[]; // 이미지 파일들 추가 + hostType?: "COMPUTER_SCIENCE" | "ETC"; // 주최 유형 추가 + time?: string; // HH:mm:ss 형식 } interface ApiResponse { @@ -17,95 +22,265 @@ interface ApiResponse { message?: string; } -// API 기본 URL (환경변수로 관리하는 것이 좋습니다) -const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "/api"; +/** + * 에러 타입에 따른 구체적인 메시지 반환 + */ +function getErrorMessage(error: any): string { + // 서버 응답이 있는 경우 + if (error.response?.data?.message) { + return error.response.data.message; + } + + // 네트워크 에러 감지 (더 구체적으로) + const isNetworkError = + error.code === 'ECONNREFUSED' || + error.code === 'ENOTFOUND' || + error.code === 'ETIMEDOUT' || + error.code === 'ECONNABORTED' || + error.message.includes('Network Error') || + error.message.includes('Failed to fetch') || + error.message.includes('ERR_NETWORK') || + error.message.includes('ERR_CONNECTION_REFUSED') || + error.message.includes('ERR_CONNECTION_TIMED_OUT') || + error.message.includes('ERR_INTERNET_DISCONNECTED') || + (error.response === undefined && error.request !== undefined) || + (error.code === 'ERR_CANCELED' && error.message.includes('timeout')); + + if (isNetworkError) { + // FormData 요청인 경우 특별한 메시지 + if (error.config?.data instanceof FormData) { + return "파일 업로드 중 네트워크 오류가 발생했습니다. 네트워크 상태를 확인하고 다시 시도해주세요."; + } + return "서버에 연결할 수 없습니다. 네트워크 상태를 확인해주세요."; + } + + // HTTP 상태 코드별 처리 + if (error.response?.status === 302) { + return "API 서버 주소가 잘못 설정되었습니다. 관리자에게 문의해주세요."; + } else if (error.response?.status === 400) { + return "잘못된 요청입니다. 입력 정보를 확인해주세요."; + } else if (error.response?.status === 401) { + return "인증이 필요합니다. 다시 로그인해주세요."; + } else if (error.response?.status === 413) { + return "파일 크기가 너무 큽니다. 파일 크기를 줄여주세요."; + } else if (error.response?.status === 415) { + return "지원하지 않는 파일 형식입니다. 다른 파일을 선택해주세요."; + } else if (error.response?.status >= 500) { + return "서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요."; + } + + return "네트워크 오류가 발생했습니다."; +} /** - * 기존행사 생성 + * 기존행사 생성 (임시 행사 저장) - Swagger 스펙에 맞게 구현 */ export async function createPastEvent( eventData: EventData ): Promise> { try { - const response = await fetch(`${API_BASE_URL}/past-events`, { - method: "POST", + // FormData 생성 + const formData = new FormData(); + + // 기본 필드들 추가 (Swagger Create 스키마에 맞게) + formData.append("eventName", eventData.eventName || ""); + formData.append("category", eventData.category || "ETC"); + formData.append("hostType", eventData.hostType || "COMPUTER_SCIENCE"); + formData.append("location", eventData.location); + formData.append("notice", eventData.announcement); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time || "14:30:00"); // 기본 시간 설정 + + // 이미지 파일들 추가 + if (eventData.cardNewsImages && eventData.cardNewsImages.length > 0) { + eventData.cardNewsImages.forEach((image, index) => { + formData.append("cardNewsImages", image); + }); + } + + // 카테고리 값 특별 확인 + console.log("🔍 createPastEvent - 카테고리 값 확인:", { + eventDataCategory: eventData.category, + formDataCategory: formData.get("category") + }); + + // FormData 내용 확인 (보내기 전) + console.log("📤 FormData 전송 전 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.post("/api/v1/major-event/temp", formData, { headers: { - "Content-Type": "application/json", + // Content-Type을 명시적으로 설정하지 않음 - 브라우저가 자동으로 boundary 설정 }, - body: JSON.stringify(eventData), + timeout: 30000, // FormData 요청은 더 긴 타임아웃 설정 }); - - const result = await response.json(); + + // 응답 후 FormData 재확인 (보낸 후) + console.log("📥 FormData 전송 후 응답:", { + status: response.status, + data: response.data, + eventName: eventData.eventName, + category: eventData.category, + location: eventData.location, + imageCount: eventData.cardNewsImages?.length || 0 + }); + return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("기존행사 생성 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } /** - * 신규행사 생성 + * 신규행사 생성 (임시 행사 저장) - Swagger 스펙에 맞게 구현 */ export async function createNewEvent( eventData: EventData ): Promise> { try { - const response = await fetch(`${API_BASE_URL}/new-events`, { - method: "POST", + // FormData 생성 + const formData = new FormData(); + + // 기본 필드들 추가 (Swagger Create 스키마에 맞게) + formData.append("eventName", eventData.eventName || ""); + formData.append("category", eventData.category || "ETC"); + formData.append("hostType", eventData.hostType || "COMPUTER_SCIENCE"); + formData.append("location", eventData.location); + formData.append("notice", eventData.announcement); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time || "14:30:00"); // 기본 시간 설정 + + // 이미지 파일들 추가 + if (eventData.cardNewsImages && eventData.cardNewsImages.length > 0) { + eventData.cardNewsImages.forEach((image, index) => { + formData.append("cardNewsImages", image); + }); + } + + // FormData 내용 확인 (보내기 전) + console.log("📤 FormData 전송 전 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.post("/api/v1/major-event/temp", formData, { headers: { - "Content-Type": "application/json", + // Content-Type을 명시적으로 설정하지 않음 - 브라우저가 자동으로 boundary 설정 }, - body: JSON.stringify(eventData), + timeout: 30000, // FormData 요청은 더 긴 타임아웃 설정 }); - - const result = await response.json(); + + // 응답 후 FormData 재확인 (보낸 후) + console.log("📥 FormData 전송 후 응답:", { + status: response.status, + data: response.data, + eventName: eventData.eventName, + category: eventData.category, + location: eventData.location, + imageCount: eventData.cardNewsImages?.length || 0 + }); + return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("신규행사 생성 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } /** - * 기타행사 생성 + * 기타행사 생성 (임시 행사 저장) - Swagger 스펙에 맞게 구현 */ export async function createAnotherEvent( eventData: EventData ): Promise> { try { - const response = await fetch(`${API_BASE_URL}/another-events`, { - method: "POST", + // FormData 생성 + const formData = new FormData(); + + // 기본 필드들 추가 (Swagger Create 스키마에 맞게) + formData.append("eventName", eventData.eventName || ""); + formData.append("category", eventData.category || "ETC"); + formData.append("hostType", eventData.hostType || "COMPUTER_SCIENCE"); + formData.append("location", eventData.location); + formData.append("notice", eventData.announcement); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time || "14:30:00"); // 기본 시간 설정 + + // 이미지 파일들 추가 + if (eventData.cardNewsImages && eventData.cardNewsImages.length > 0) { + eventData.cardNewsImages.forEach((image, index) => { + formData.append("cardNewsImages", image); + }); + } + + // FormData 내용 확인 (보내기 전) + console.log("📤 FormData 전송 전 내용:"); + for (let [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(` ${key}: File(${value.name}, ${value.size}bytes, ${value.type})`); + } else { + console.log(` ${key}: ${value}`); + } + } + + const response = await instance.post("/api/v1/major-event/temp", formData, { headers: { - "Content-Type": "application/json", + // Content-Type을 명시적으로 설정하지 않음 - 브라우저가 자동으로 boundary 설정 }, - body: JSON.stringify(eventData), + timeout: 30000, // FormData 요청은 더 긴 타임아웃 설정 }); - - const result = await response.json(); + + // 응답 후 FormData 재확인 (보낸 후) + console.log("📥 FormData 전송 후 응답:", { + status: response.status, + data: response.data, + eventName: eventData.eventName, + category: eventData.category, + location: eventData.location, + imageCount: eventData.cardNewsImages?.length || 0 + }); + return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("기타행사 생성 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } @@ -119,112 +294,168 @@ export async function getEvent( ): Promise> { try { const endpoint = getEventEndpoint(eventType); - const response = await fetch(`${API_BASE_URL}/${endpoint}/${eventId}`); + const response = await instance.get(`/${endpoint}/${eventId}`); - const result = await response.json(); return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("이벤트 조회 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } /** - * 기존행사 수정 + * 기존행사 수정 - Swagger 스펙에 맞게 구현 */ export async function updatePastEvent( eventId: string, eventData: EventData ): Promise> { try { - const response = await fetch(`${API_BASE_URL}/past-events/${eventId}`, { - method: "PUT", + // FormData 생성 + const formData = new FormData(); + + // 기본 필드들 추가 (Swagger Update 스키마에 맞게) + formData.append("eventName", eventData.eventName || ""); + formData.append("category", eventData.category || "ETC"); + formData.append("hostType", eventData.hostType || "COMPUTER_SCIENCE"); + formData.append("location", eventData.location); + formData.append("notice", eventData.announcement); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time || "14:30:00"); // 기본 시간 설정 + + // 이미지 파일들 추가 + if (eventData.cardNewsImages && eventData.cardNewsImages.length > 0) { + eventData.cardNewsImages.forEach((image, index) => { + formData.append("cardNewsImages", image); + }); + } + + const response = await instance.put(`/api/v1/major-event/temp/${eventId}`, formData, { headers: { - "Content-Type": "application/json", + // Content-Type을 명시적으로 설정하지 않음 - 브라우저가 자동으로 boundary 설정 }, - body: JSON.stringify(eventData), + timeout: 30000, // FormData 요청은 더 긴 타임아웃 설정 }); - - const result = await response.json(); + return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("기존행사 수정 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } /** - * 신규행사 수정 + * 신규행사 수정 - Swagger 스펙에 맞게 구현 */ export async function updateNewEvent( eventId: string, eventData: EventData ): Promise> { try { - const response = await fetch(`${API_BASE_URL}/new-events/${eventId}`, { - method: "PUT", + // FormData 생성 + const formData = new FormData(); + + // 기본 필드들 추가 (Swagger Update 스키마에 맞게) + formData.append("eventName", eventData.eventName || ""); + formData.append("category", eventData.category || "ETC"); + formData.append("hostType", eventData.hostType || "COMPUTER_SCIENCE"); + formData.append("location", eventData.location); + formData.append("notice", eventData.announcement); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time || "14:30:00"); // 기본 시간 설정 + + // 이미지 파일들 추가 + if (eventData.cardNewsImages && eventData.cardNewsImages.length > 0) { + eventData.cardNewsImages.forEach((image, index) => { + formData.append("cardNewsImages", image); + }); + } + + const response = await instance.put(`/api/v1/major-event/temp/${eventId}`, formData, { headers: { - "Content-Type": "application/json", + // Content-Type을 명시적으로 설정하지 않음 - 브라우저가 자동으로 boundary 설정 }, - body: JSON.stringify(eventData), + timeout: 30000, // FormData 요청은 더 긴 타임아웃 설정 }); - - const result = await response.json(); + return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("신규행사 수정 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } /** - * 기타행사 수정 + * 기타행사 수정 - Swagger 스펙에 맞게 구현 */ export async function updateAnotherEvent( eventId: string, eventData: EventData ): Promise> { try { - const response = await fetch(`${API_BASE_URL}/another-events/${eventId}`, { - method: "PUT", + // FormData 생성 + const formData = new FormData(); + + // 기본 필드들 추가 (Swagger Update 스키마에 맞게) + formData.append("eventName", eventData.eventName || ""); + formData.append("category", eventData.category || "ETC"); + formData.append("hostType", eventData.hostType || "COMPUTER_SCIENCE"); + formData.append("location", eventData.location); + formData.append("notice", eventData.announcement); + formData.append("googleFormLink", eventData.googleFormLink); + formData.append("startDate", eventData.startDate); + formData.append("endDate", eventData.endDate); + formData.append("time", eventData.time || "14:30:00"); // 기본 시간 설정 + + // 이미지 파일들 추가 + if (eventData.cardNewsImages && eventData.cardNewsImages.length > 0) { + eventData.cardNewsImages.forEach((image, index) => { + formData.append("cardNewsImages", image); + }); + } + + const response = await instance.put(`/api/v1/major-event/temp/${eventId}`, formData, { headers: { - "Content-Type": "application/json", + // Content-Type을 명시적으로 설정하지 않음 - 브라우저가 자동으로 boundary 설정 }, - body: JSON.stringify(eventData), + timeout: 30000, // FormData 요청은 더 긴 타임아웃 설정 }); - - const result = await response.json(); + return { - success: response.ok, - data: result, - message: result.message, + success: true, + data: response.data, + message: response.data.message, }; - } catch (error) { + } catch (error: any) { console.error("기타행사 수정 실패:", error); return { success: false, - message: "네트워크 오류가 발생했습니다.", + message: getErrorMessage(error), }; } } diff --git a/src/app/calendar/addEvent/components/EventForm.tsx b/src/app/calendar/addEvent/components/EventForm.tsx index f50c514..444d9a0 100644 --- a/src/app/calendar/addEvent/components/EventForm.tsx +++ b/src/app/calendar/addEvent/components/EventForm.tsx @@ -113,6 +113,7 @@ export default function EventForm({ // 핸들러 함수들 const handleCategorySelect = (category: string) => { + console.log("🔍 EventForm - 받은 카테고리:", category); setEventData((prev) => ({ ...prev, category })); }; diff --git a/src/app/calendar/addEvent/components/step/Step1Category.tsx b/src/app/calendar/addEvent/components/step/Step1Category.tsx index 08e7931..cf2fbc3 100644 --- a/src/app/calendar/addEvent/components/step/Step1Category.tsx +++ b/src/app/calendar/addEvent/components/step/Step1Category.tsx @@ -14,29 +14,33 @@ interface Step1CategoryProps { } const categoryList = [ - { item: "새내기 배움터", sort: "기존행사" }, - { item: "1학기 개강총회", sort: "기존행사" }, - { item: "대면식", sort: "기존행사" }, - { item: "1학기 중간고사 간식행사", sort: "기존행사" }, - { item: "1학기 기말고사 간식행사", sort: "기존행사" }, - { item: "MT", sort: "기존행사" }, - { item: "1학기 종강총회", sort: "기존행사" }, - { item: "해오름식", sort: "기존행사" }, - { item: "공대 체전", sort: "기존행사" }, - { item: "왕산 체전", sort: "기존행사" }, - { item: "축제", sort: "기존행사" }, - { item: "2학기 개강총회", sort: "기존행사" }, - { item: "2학기 중간고사 간식행사", sort: "기존행사" }, - { item: "2학기 기말고사 간식행사", sort: "기존행사" }, - { item: "홈커밍 데이", sort: "기존행사" }, + { item: "새내기 배움터", sort: "FRESHMAN_ORIENTATION" }, + { item: "1학기 개강총회", sort: "FIRST_SEMESTER_OPENING_MEETING" }, + { item: "대면식", sort: "FACE_TO_FACE_MEETING" }, + { item: "1학기 중간고사 간식행사", sort: "FIRST_SEMESTER_MIDTERM_SNACK" }, + { item: "1학기 기말고사 간식행사", sort: "FIRST_SEMESTER_FINAL_SNACK" }, + { item: "MT", sort: "MT" }, + { item: "1학기 종강총회", sort: "FIRST_SEMESTER_CLOSING_MEETING" }, + { item: "해오름식", sort: "KICK_OFF" }, + { item: "공대 체전", sort: "COLLEGE_SPORTS_DAY" }, + { item: "왕산 체전", sort: "UNIVERSITY_SPORTS_DAY" }, + { item: "축제", sort: "FESTIVAL" }, + { item: "2학기 개강총회", sort: "SECOND_SEMESTER_OPENING_MEETING" }, + { item: "2학기 종강총회", sort: "SECOND_SEMESTER_CLOSING_MEETING" }, + { item: "2학기 중간고사 간식행사", sort: "SECOND_SEMESTER_MIDTERM_SNACK" }, + { item: "2학기 기말고사 간식행사", sort: "SECOND_SEMESTER_FINAL_SNACK" }, + { item: "홈커밍 데이", sort: "HOMECOMING_DAY" }, + { item: "기타", sort: "ETC" }, ]; export default function Step1Category({ onCategorySelect, selectedCategory, }: Step1CategoryProps) { - const handleCategorySelect = (selectedItem: string) => { - onCategorySelect(selectedItem); + const handleCategorySelect = (selectedEnum: string) => { + // Category 컴포넌트에서 이미 영문 enum 값을 전달함 + console.log("🔍 Step1Category - 받은 영문 enum:", selectedEnum); + onCategorySelect(selectedEnum); }; return ( @@ -50,7 +54,11 @@ export default function Step1Category({ - + cat.sort === selectedCategory)?.item : undefined} + /> ); } diff --git a/src/app/calendar/addEvent/components/step/Step6CardNews.tsx b/src/app/calendar/addEvent/components/step/Step6CardNews.tsx index df1f685..b03cc85 100644 --- a/src/app/calendar/addEvent/components/step/Step6CardNews.tsx +++ b/src/app/calendar/addEvent/components/step/Step6CardNews.tsx @@ -154,11 +154,13 @@ export default function Step6CardNews({ ) : (
- 업로드된 이미지 + {imagePreview && ( + 업로드된 이미지 + )}