- {["일", "월", "화", "수", "목", "금", "토"].map((day) => (
-
{day}
- ))}
+ );
+ };
+
+ const renderEventContent = (eventInfo: EventContentArg) => {
+ return (
+
+ {eventInfo.event.title}
-
-
- {weeks.map((week, weekIndex) => (
-
- {week.map((day) => (
-
- {day.getDate()}
-
- ))}
-
- {renderSchedules(week)}
-
-
- ))}
-
+ );
+ };
+
+ return (
+
+
+
+ {" "}
+ {
+ const days = ["일", "월", "화", "수", "목", "금", "토"];
+ return days[date.getDay()];
+ }}
+ />
);
diff --git a/src/app/admin/(block)/calendar/components/add-schedule-form.tsx b/src/app/admin/(block)/calendar/components/schedule-form.tsx
similarity index 67%
rename from src/app/admin/(block)/calendar/components/add-schedule-form.tsx
rename to src/app/admin/(block)/calendar/components/schedule-form.tsx
index 439a770d..cdb970fc 100644
--- a/src/app/admin/(block)/calendar/components/add-schedule-form.tsx
+++ b/src/app/admin/(block)/calendar/components/schedule-form.tsx
@@ -3,6 +3,7 @@
import { useEffect, useState, useCallback } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
+import { getSequence } from "lib/get-sequence";
interface Schedule {
id?: number;
@@ -20,7 +21,17 @@ interface CalendarBlock {
schedule: Schedule[];
}
-export default function AddScheduleForm() {
+interface ScheduleFormProps {
+ mode: "add" | "edit";
+ initialData?: Schedule | null;
+ calendarBlockId?: number | null;
+}
+
+export default function ScheduleForm({
+ mode,
+ initialData,
+ calendarBlockId,
+}: ScheduleFormProps) {
const [startDate, setStartDate] = useState("");
const [startTime, setStartTime] = useState("");
const [endDate, setEndDate] = useState("");
@@ -29,20 +40,29 @@ export default function AddScheduleForm() {
const [url, setUrl] = useState("");
const [showStartTime, setShowStartTime] = useState(false);
const [showEndTime, setShowEndTime] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState
(null);
const [calendarBlock, setCalendarBlock] = useState(
null,
);
const router = useRouter();
+ useEffect(() => {
+ if (mode === "edit" && initialData) {
+ const startDateTime = new Date(initialData.dateStart);
+ const endDateTime = new Date(initialData.dateEnd);
+
+ setStartDate(startDateTime.toISOString().split("T")[0]);
+ setStartTime(startDateTime.toTimeString().slice(0, 5));
+ setEndDate(endDateTime.toISOString().split("T")[0]);
+ setEndTime(endDateTime.toTimeString().slice(0, 5));
+ setTitle(initialData.title);
+ setUrl(initialData.url || "");
+ }
+ }, [mode, initialData]);
+
const fetchCalendarBlock = useCallback(async () => {
- try {
- if (calendarBlock) {
- console.log("캘린더 블록이 이미 존재합니다.");
- return;
- }
+ if (mode === "edit") return;
+ try {
const token = sessionStorage.getItem("token");
if (!token) {
throw new Error("로그인이 필요합니다.");
@@ -62,36 +82,28 @@ export default function AddScheduleForm() {
}
const data = await response.json();
- console.log("Fetched data:", data);
-
- const existingCalendarBlock = data.data.find(
- (item: CalendarBlock) => item.type === 7,
- );
-
- if (existingCalendarBlock) {
- setCalendarBlock(existingCalendarBlock);
- console.log("Existing calendar (block) found:", existingCalendarBlock);
- } else {
- console.log("캘린더 블록이 없습니다.");
+ if (data.code === 200 && Array.isArray(data.data)) {
+ const existingCalendarBlock = data.data.find(
+ (item: CalendarBlock) => item.type === 7,
+ );
+ if (existingCalendarBlock) {
+ setCalendarBlock(existingCalendarBlock);
+ }
}
} catch (error) {
- console.error("Error fetching calendar (block):", error);
- setError("캘린더 블록 정보를 가져오는데 실패했습니다.");
+ console.error("Error fetching calendar block:", error);
}
- }, [calendarBlock]);
+ }, [mode]);
useEffect(() => {
- if (!calendarBlock) {
- fetchCalendarBlock();
- }
- }, [calendarBlock, fetchCalendarBlock]);
+ fetchCalendarBlock();
+ }, [fetchCalendarBlock]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- setIsLoading(true);
- setError(null);
- const schedule: Schedule = {
+ const newSchedule: Schedule = {
+ ...(mode === "edit" && initialData?.id ? { id: initialData.id } : {}),
title,
url: url || undefined,
dateStart: `${startDate}T${startTime}:00.000Z`,
@@ -100,83 +112,97 @@ export default function AddScheduleForm() {
try {
const token = sessionStorage.getItem("token");
- if (!token) {
- throw new Error("로그인이 필요합니다.");
- }
+ if (!token) throw new Error("인증 토큰이 없습니다. 다시 로그인해주세요.");
+
+ const listResponse = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/link/list`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ );
- console.log("Sending schedule data:", schedule);
+ if (!listResponse.ok) {
+ throw new Error("기존 일정을 불러오는데 실패했습니다.");
+ }
- if (calendarBlock) {
- console.log("Existing calendar (block) found:", calendarBlock);
- const updatedSchedule = [...calendarBlock.schedule, schedule];
+ const listData = await listResponse.json();
+ const existingCalendarBlock = listData.data.find(
+ (item: CalendarBlock) => item.type === 7,
+ );
- const updateResponse = await fetch(
- `${process.env.NEXT_PUBLIC_API_URL}/api/link/update`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- body: JSON.stringify({
- id: calendarBlock.id,
- type: 7,
- sequence: calendarBlock.sequence,
- style: calendarBlock.style,
- schedule: updatedSchedule,
- }),
- },
- );
+ let requestBody;
+ const blockId = mode === "edit" ? calendarBlockId : calendarBlock?.id;
- if (!updateResponse.ok) {
- const errorData = await updateResponse.json();
- throw new Error(errorData.message || "일정 추가에 실패했습니다.");
+ if (blockId) {
+ let updatedSchedules;
+ if (mode === "edit" && existingCalendarBlock) {
+ updatedSchedules = existingCalendarBlock.schedule.map(
+ (s: Schedule) => (s.id === initialData?.id ? newSchedule : s),
+ );
+ } else {
+ updatedSchedules = [
+ ...(existingCalendarBlock?.schedule || []),
+ newSchedule,
+ ];
}
- alert("일정이 성공적으로 추가되었습니다.");
+ requestBody = {
+ id: blockId,
+ type: 7,
+ sequence: existingCalendarBlock?.sequence || 1,
+ style: existingCalendarBlock?.style || 1,
+ schedule: updatedSchedules,
+ };
} else {
- console.log("No calendar (block) found, creating a new one.");
-
- const newBlock: Omit = {
+ // 새로운 캘린더 블록 생성 및 일정 추가
+ const nextSequence = await getSequence(token);
+ requestBody = {
type: 7,
- sequence: 8,
+ sequence: nextSequence + 1,
style: 1,
- schedule: [schedule],
+ schedule: [newSchedule],
};
+ }
- const createResponse = await fetch(
- `${process.env.NEXT_PUBLIC_API_URL}/api/link/add`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- body: JSON.stringify(newBlock),
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/link/${blockId ? "update" : "add"}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
},
- );
-
- if (!createResponse.ok) {
- const errorData = await createResponse.json();
- throw new Error(
- errorData.message || "캘린더 블록 생성에 실패했습니다.",
- );
- }
+ body: JSON.stringify(requestBody),
+ },
+ );
- alert("캘린더 블록과 일정이 성공적으로 추가되었습니다.");
+ if (!response.ok) {
+ throw new Error(
+ mode === "edit"
+ ? "일정 수정에 실패했습니다."
+ : "일정 추가에 실패했습니다.",
+ );
}
- await fetchCalendarBlock();
- router.push("/calendar");
+ const data = await response.json();
+ if (data.code === 200) {
+ alert(
+ mode === "edit"
+ ? "일정이 성공적으로 수정되었습니다."
+ : "일정이 성공적으로 추가되었습니다.",
+ );
+ router.push("/admin/calendar");
+ } else {
+ throw new Error("서버 응답 오류");
+ }
} catch (error) {
- console.error("Error adding schedule:", error);
- setError(
- error instanceof Error
- ? error.message
- : "알 수 없는 오류가 발생했습니다.",
+ console.error(
+ mode === "edit" ? "일정 수정 중 오류 발생" : "일정 추가 중 오류 발생",
+ error,
);
- } finally {
- setIsLoading(false);
+ alert(error instanceof Error ? error.message : "오류가 발생했습니다.");
}
};
@@ -349,13 +375,10 @@ export default function AddScheduleForm() {
type="submit"
style={{ backgroundColor: "#FFF1ED", color: "#FFB092" }}
className="button color w-full rounded-md px-4 py-2 text-white hover:bg-blue-600"
- disabled={isLoading}
>
- {isLoading ? "처리 중..." : "추가 완료"}
+ {mode === "edit" ? "수정 완료" : "추가 완료"}
-
- {error &&
{error}
}
);
}
diff --git a/src/app/admin/(block)/calendar/components/schedule-list.tsx b/src/app/admin/(block)/calendar/components/schedule-list.tsx
index 1c152acf..5f45d96c 100644
--- a/src/app/admin/(block)/calendar/components/schedule-list.tsx
+++ b/src/app/admin/(block)/calendar/components/schedule-list.tsx
@@ -1,5 +1,6 @@
import Image from "next/image";
-import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { useState, useEffect } from "react";
interface Schedule {
id: number;
@@ -9,43 +10,13 @@ interface Schedule {
dateEnd: string;
}
-const scheduleData: Schedule[] = [
- {
- id: 1,
- title: "일정 1",
- url: "https://naver.com/",
- dateStart: "2024-10-01T12:26:44.000Z",
- dateEnd: "2024-10-02T12:26:44.000Z",
- },
- {
- id: 2,
- title: "일정 2",
- url: "https://google.com/",
- dateStart: "2024-10-01T09:00:00.000Z",
- dateEnd: "2024-11-30T18:00:00.000Z",
- },
- {
- id: 3,
- title: "일정 3",
- url: "https://github.com/",
- dateStart: "2024-12-01T08:00:00.000Z",
- dateEnd: "2024-12-31T17:00:00.000Z",
- },
- {
- id: 4,
- title: "일정 4",
- url: "https://microsoft.com/",
- dateStart: "2025-01-01T10:00:00.000Z",
- dateEnd: "2025-01-31T16:00:00.000Z",
- },
- {
- id: 5,
- title: "일정 5",
- url: "https://apple.com/",
- dateStart: "2025-02-01T11:00:00.000Z",
- dateEnd: "2025-02-28T15:00:00.000Z",
- },
-];
+interface CalendarBlock {
+ id: number;
+ type: number;
+ sequence: number;
+ style: number;
+ schedule: Schedule[];
+}
const formatDate = (dateString: string) => {
const date = new Date(dateString);
@@ -80,9 +51,41 @@ const getScheduleStatus = (schedule: Schedule) => {
}
};
-function ScheduleItem({ schedule }: { schedule: Schedule }) {
+function EmptyState({ message }: { message: React.ReactNode }) {
+ return (
+
+ );
+}
+
+function ScheduleItem({
+ schedule,
+ onDelete,
+}: {
+ schedule: Schedule;
+ onDelete: (id: number) => void;
+}) {
+ const router = useRouter();
const status = getScheduleStatus(schedule);
+ const handleEdit = () => {
+ router.push(`/admin/calendar/manage?mode=edit&id=${schedule.id}`);
+ };
+
+ const handleClick = (url: string) => {
+ if (url) {
+ window.open(url, "_blank");
+ }
+ };
+
return (
@@ -91,17 +94,26 @@ function ScheduleItem({ schedule }: { schedule: Schedule }) {
>
{status.text}
-
+
schedule.url && handleClick(schedule.url)}
+ >
{formatDate(schedule.dateStart)} ~ {formatDate(schedule.dateEnd)}
-
{schedule.title}
+
{schedule.title}
-
@@ -114,11 +126,132 @@ function ScheduleItem({ schedule }: { schedule: Schedule }) {
export default function ScheduleList() {
const [isOpen, setIsOpen] = useState(true);
const [activeTab, setActiveTab] = useState<"current" | "past">("current");
+ const [schedules, setSchedules] = useState
([]);
+ const [calendarBlock, setCalendarBlock] = useState(
+ null,
+ );
+ const [error, setError] = useState(null);
+
const toggleOpen = () => setIsOpen(!isOpen);
+ useEffect(() => {
+ fetchSchedules();
+ }, []);
+
+ const fetchSchedules = async () => {
+ setError(null);
+ try {
+ const token = sessionStorage.getItem("token");
+ if (!token) {
+ throw new Error("로그인이 필요합니다.");
+ }
+
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/link/list`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ if (data.code === 200 && Array.isArray(data.data)) {
+ const foundCalendarBlock = data.data.find(
+ (item: CalendarBlock) => item.type === 7,
+ );
+
+ if (foundCalendarBlock) {
+ setCalendarBlock(foundCalendarBlock);
+ if (Array.isArray(foundCalendarBlock.schedule)) {
+ setSchedules(foundCalendarBlock.schedule);
+ } else {
+ setSchedules([]);
+ }
+ } else {
+ setSchedules([]);
+ }
+ } else {
+ throw new Error(`Unexpected data structure: ${JSON.stringify(data)}`);
+ }
+ } catch (err) {
+ console.error("Error fetching schedules:", err);
+ setError(
+ err instanceof Error ? err.message : "알 수 없는 오류가 발생했습니다.",
+ );
+ }
+ };
+
+ const handleDelete = async (scheduleId: number) => {
+ try {
+ if (!calendarBlock) {
+ throw new Error("캘린더 블록을 찾을 수 없습니다.");
+ }
+
+ const token = sessionStorage.getItem("token");
+ if (!token) {
+ throw new Error("로그인이 필요합니다.");
+ }
+
+ const updatedSchedules = schedules.filter(
+ (schedule) => schedule.id !== scheduleId,
+ );
+
+ const requestBody = {
+ id: calendarBlock.id,
+ type: 7,
+ sequence: calendarBlock.sequence,
+ style: calendarBlock.style,
+ schedule: updatedSchedules,
+ };
+
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/link/update`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify(requestBody),
+ },
+ );
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(
+ `Failed to delete schedule: ${JSON.stringify(errorData)}`,
+ );
+ }
+
+ const responseData = await response.json();
+
+ if (responseData.code === 200) {
+ setSchedules(updatedSchedules);
+ alert("일정이 성공적으로 삭제되었습니다.");
+ } else {
+ throw new Error(
+ `Failed to delete schedule. Server response: ${JSON.stringify(responseData)}`,
+ );
+ }
+ } catch (error) {
+ console.error("Error deleting schedule:", error);
+ alert(
+ error instanceof Error
+ ? error.message
+ : "일정 삭제 중 오류가 발생했습니다.",
+ );
+ }
+ };
+
const currentDate = new Date();
- const filteredSchedules = scheduleData.filter((schedule) => {
+ const filteredSchedules = schedules.filter((schedule) => {
const endDate = new Date(schedule.dateEnd);
return activeTab === "current"
? endDate >= currentDate
@@ -147,7 +280,7 @@ export default function ScheduleList() {
- {filteredSchedules.map((schedule) => (
-
- ))}
+ {filteredSchedules.length > 0 ? (
+ filteredSchedules.map((schedule) => (
+
+ ))
+ ) : (
+
+ 진행 중이거나 예정된 일정이 없습니다.
+
+ 일정을 추가하여 많은 방문자에게 알려보세요.
+ >
+ ) : (
+ <>
+ 지난 일정이 없습니다.
+
+ 일정을 추가하여 많은 방문자에게 알려보세요.
+ >
+ )
+ }
+ />
+ )}
)}
diff --git a/src/app/admin/(block)/calendar/manage/page.tsx b/src/app/admin/(block)/calendar/manage/page.tsx
new file mode 100644
index 00000000..5b13410e
--- /dev/null
+++ b/src/app/admin/(block)/calendar/manage/page.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import Image from "next/image";
+import ScheduleForm from "../components/schedule-form";
+
+interface Schedule {
+ id?: number;
+ title: string;
+ url?: string;
+ dateStart: string;
+ dateEnd: string;
+}
+
+interface CalendarBlockData {
+ type: number;
+ id: number;
+ schedule: Schedule[];
+}
+
+export default function ScheduleManagementPage() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const mode = searchParams.get("mode") || "add";
+ const [schedule, setSchedule] = useState(null);
+ const [calendarBlockId, setCalendarBlockId] = useState(null);
+
+ const handleClose = () => {
+ router.push("/admin/calendar");
+ };
+
+ useEffect(() => {
+ if (mode === "edit") {
+ const fetchSchedule = async () => {
+ const scheduleId = searchParams.get("id");
+
+ if (!scheduleId) {
+ alert("일정 ID가 필요합니다.");
+ router.push("/admin/calendar");
+ return;
+ }
+
+ try {
+ const token = sessionStorage.getItem("token");
+ if (!token) throw new Error("로그인이 필요합니다.");
+
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_API_URL}/api/link/list`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ },
+ );
+
+ if (!response.ok)
+ throw new Error("데이터를 불러오는데 실패했습니다.");
+
+ const data = await response.json();
+ if (data.code === 200 && Array.isArray(data.data)) {
+ const calendarBlock = data.data.find(
+ (block: CalendarBlockData) => block.type === 7,
+ );
+ if (calendarBlock) {
+ const foundSchedule = calendarBlock.schedule.find(
+ (s: Schedule) => s.id === Number(scheduleId),
+ );
+ if (foundSchedule) {
+ setSchedule(foundSchedule);
+ setCalendarBlockId(calendarBlock.id);
+ }
+ }
+ }
+ } catch (error) {
+ console.error("Error fetching schedule:", error);
+ alert("일정을 불러오는데 실패했습니다.");
+ router.push("/admin/calendar");
+ }
+ };
+
+ fetchSchedule();
+ }
+ }, [mode, router, searchParams]);
+
+ return (
+
+
+
+
+
+
+
+ {mode === "edit" ? "일정 수정하기" : "일정 추가하기"}
+
+
+ 입력하는 진행기간에 따라
+
+ 전체 일정이 최근 날짜 순서로 자동 정렬됩니다.
+
+
+
+ );
+}