From ae61dd0375b03671e68a5756898174e9d86bbd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Tue, 3 Dec 2024 01:06:08 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A5=B4=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=AA=A8=EB=B0=94=EC=9D=BC=EB=B7=B0?= =?UTF-8?q?=20=EA=B0=9C=EB=B0=9C=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Schedule.jsx | 7 -- src/pages/SchedulePage.jsx | 210 +++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 7 deletions(-) delete mode 100644 src/pages/Schedule.jsx create mode 100644 src/pages/SchedulePage.jsx diff --git a/src/pages/Schedule.jsx b/src/pages/Schedule.jsx deleted file mode 100644 index 980b0e5..0000000 --- a/src/pages/Schedule.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const Schedule = () => { - return <></>; -}; - -export default Schedule; diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx new file mode 100644 index 0000000..12e03af --- /dev/null +++ b/src/pages/SchedulePage.jsx @@ -0,0 +1,210 @@ +import React, { useState, useRef } from "react"; + +const generateTimeSlots = () => { + const timeSlots = []; + for (let hour = 0; hour < 24; hour++) { + for (let min = 0; min < 60; min += 15) { + timeSlots.push( + `${hour.toString().padStart(2, "0")}:${min.toString().padStart(2, "0")}` + ); + } + } + return timeSlots; +}; + +const days = ["월", "화", "수", "목", "금", "토", "일"]; + +const SchedulePage = () => { + const timeSlots = generateTimeSlots(); + const [schedules, setSchedules] = useState([]); + const isDragging = useRef(false); + const [draggedSlots, setDraggedSlots] = useState([]); + const [inputTitle, setInputTitle] = useState(""); + const [isEditing, setIsEditing] = useState(false); + const [editMode, setEditMode] = useState(false); + + const handleMouseDown = (slotIndex) => { + if (!editMode) return; + isDragging.current = true; + + if (draggedSlots.includes(slotIndex)) { + setDraggedSlots((prev) => prev.filter((slot) => slot !== slotIndex)); + return; + } + + setDraggedSlots((prev) => [...new Set([...prev, slotIndex])]); + + const schedule = schedules.find((s) => s.time_idx === slotIndex); + if (schedule) { + setInputTitle(schedule.title); + setIsEditing(true); + } else { + setInputTitle(""); + setIsEditing(false); + } + }; + + const handleMouseEnter = (slotIndex) => { + if (!editMode || !isDragging.current) return; + setDraggedSlots((prev) => [...new Set([...prev, slotIndex])]); + }; + + const handleMouseUp = () => { + isDragging.current = false; + }; + + const handleMouseLeave = () => { + isDragging.current = false; + }; + + const handleSave = () => { + if (!inputTitle.trim()) return; + + const newSchedules = [...schedules]; + draggedSlots.forEach((slotIndex) => { + const existingIndex = newSchedules.findIndex( + (s) => s.time_idx === slotIndex + ); + if (existingIndex === -1) { + newSchedules.push({ + id: Date.now(), + time_idx: slotIndex, + title: inputTitle, + is_fixed: false, + }); + } else { + newSchedules[existingIndex].title = inputTitle; + } + }); + + setSchedules(newSchedules); + setDraggedSlots([]); + setInputTitle(""); + setIsEditing(false); + }; + + const handleDelete = () => { + const newSchedules = schedules.filter( + (s) => !draggedSlots.includes(s.time_idx) + ); + setSchedules(newSchedules); + setDraggedSlots([]); + setInputTitle(""); + setIsEditing(false); + }; + + return ( + <div className="min-h-screen bg-gray-50"> + {/* Edit Mode Toggle */} + <div className="flex items-center justify-between p-4 bg-white shadow"> + <h1 className="text-lg font-bold mobile:text-base">Schedule</h1> + <label className="flex items-center space-x-3"> + <span className="text-lg font-bold mobile:text-sm">Edit Mode</span> + <div + className={`relative w-12 h-6 rounded-full cursor-pointer transition-colors ${ + editMode ? "bg-gradient-pink" : "bg-gray-300" + }`} + onClick={() => setEditMode((prev) => !prev)} + > + <div + className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${ + editMode ? "translate-x-6" : "" + }`} + ></div> + </div> + </label> + </div> + + {/* Sticky Container (Edit Mode가 on일 때만 표시) */} + {editMode && ( + <div className="sticky top-0 flex flex-col gap-2 p-4 bg-white shadow tablet:flex-row tablet:justify-between"> + <input + type="text" + value={inputTitle} + onChange={(e) => setInputTitle(e.target.value)} + placeholder="Enter schedule title" + className="flex-1 p-2 border rounded" + /> + <div className="flex space-x-2"> + <button + onClick={handleSave} + className="flex-1 px-4 py-2 text-white rounded bg-gradient-pink disabled:opacity-50" + disabled={!draggedSlots.length} + > + {isEditing ? "Update" : "Save"} + </button> + <button + onClick={handleDelete} + className="flex-1 px-4 py-2 text-white rounded bg-gradient-purple disabled:opacity-50" + disabled={!draggedSlots.length} + > + Delete + </button> + </div> + </div> + )} + + {/* Schedule Grid */} + <div className="p-4"> + <div className="overflow-auto scrollbar-hide"> + <div className="tablet:w-[960px] tablet:min-w-[800px] grid grid-cols-[64px,repeat(7,1fr)] gap-2"> + {/* Header */} + <div className="min-w-[54px] p-2 font-bold text-center bg-gray-200 select-none"> + Time + </div> + {days.map((day) => ( + <div + key={day} + className="p-2 font-bold text-center bg-gray-200 select-none" + > + {day} + </div> + ))} + + {/* Time Slots */} + {timeSlots.map((time, rowIndex) => ( + <React.Fragment key={rowIndex}> + {/* Time Column */} + <div className="min-w-[54px] p-2 text-sm font-bold text-center bg-gray-100 select-none"> + {time} + </div> + {days.map((_, colIndex) => { + const slotIndex = colIndex * timeSlots.length + rowIndex; + const isSelected = draggedSlots.includes(slotIndex); + const schedule = schedules.find( + (s) => s.time_idx === slotIndex + ); + + return ( + <div + key={slotIndex} + className={`p-2 border rounded cursor-pointer ${ + editMode + ? isSelected + ? "bg-tertiary-300" + : schedule + ? "bg-primary-500 text-white" + : "bg-white" + : schedule + ? "bg-primary-500 text-white" + : "bg-gray-50" + }`} + onMouseDown={() => handleMouseDown(slotIndex)} + onMouseEnter={() => handleMouseEnter(slotIndex)} + onMouseUp={handleMouseUp} + onMouseLeave={handleMouseLeave} + > + {schedule && !isSelected ? schedule.title : ""} + </div> + ); + })} + </React.Fragment> + ))} + </div> + </div> + </div> + </div> + ); +}; + +export default SchedulePage; -- GitLab From 6bafaeddff21d974c002dae6b30b3cfe39a66320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Thu, 5 Dec 2024 16:58:39 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20schedul=20api=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EA=B4=80=EB=A6=AC=20=ED=95=A8=EC=88=98=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/api/schedule.js | 157 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/api/schedule.js diff --git a/.gitignore b/.gitignore index 4d29575..8692cf6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/src/api/schedule.js b/src/api/schedule.js new file mode 100644 index 0000000..bb36ad4 --- /dev/null +++ b/src/api/schedule.js @@ -0,0 +1,157 @@ +// api.js +const baseURL = process.env.REACT_APP_BACKEND_BASE_URL; + +// Fetch all schedules +export const fetchAllSchedules = async () => { + try { + const response = await fetch(`${baseURL}/api/schedule/all`, { + method: "GET", + credentials: "include", // Include credentials for session-based authentication + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error("Failed to fetch schedules."); + } + + return result.data.schedules; + } catch (error) { + console.error("Error fetching schedules:", error); + throw error; + } +}; + +// Fetch schedule by time index +export const fetchScheduleByTimeIndex = async (timeIdx) => { + try { + const response = await fetch(`${baseURL}/api/schedule/${timeIdx}`, { + method: "GET", + credentials: "include", // Include credentials for session-based authentication + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + if (response.status === 404) { + const error = await response.json(); + throw new Error(error.error.message || "Schedule not found."); + } + throw new Error(`Error: ${response.status}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error("Failed to fetch schedule."); + } + + return result.data.schedule; + } catch (error) { + console.error(`Error fetching schedule with timeIdx ${timeIdx}:`, error); + throw error; + } +}; + +// Create a new schedule +export const createSchedule = async (scheduleData) => { + try { + const response = await fetch(`${baseURL}/api/schedule`, { + method: "POST", + credentials: "include", // Include credentials for session-based authentication + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(scheduleData), + }); + + if (!response.ok) { + if (response.status === 400) { + const error = await response.json(); + throw new Error(error.error.message || "Failed to create schedule."); + } + throw new Error(`Error: ${response.status}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error("Failed to create schedule."); + } + + return result.data.schedule; + } catch (error) { + console.error("Error creating schedule:", error); + throw error; + } +}; + +// Update an existing schedule +export const updateSchedule = async (scheduleData) => { + try { + const response = await fetch(`${baseURL}/api/schedule`, { + method: "PUT", + credentials: "include", // Include credentials for session-based authentication + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(scheduleData), + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error("Failed to update schedule."); + } + + return result.data.schedule; + } catch (error) { + console.error("Error updating schedule:", error); + throw error; + } +}; + +// Delete a schedule +export const deleteSchedule = async (title) => { + try { + const response = await fetch(`${baseURL}/api/schedule`, { + method: "DELETE", + credentials: "include", // Include credentials for session-based authentication + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ title }), + }); + + if (!response.ok) { + if (response.status === 404) { + const error = await response.json(); + throw new Error(error.error.message || "Schedule not found."); + } + throw new Error(`Error: ${response.status}`); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error("Failed to delete schedule."); + } + + return result.data; + } catch (error) { + console.error("Error deleting schedule:", error); + throw error; + } +}; -- GitLab From f45e8d89909dcaab117eab92af2948c70f2c489b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Thu, 5 Dec 2024 16:59:37 +0900 Subject: [PATCH 03/11] =?UTF-8?q?rename:=20timetable=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 4 ++-- src/pages/SchedulePage.jsx | 2 +- src/pages/TimeTablePage.jsx | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 src/pages/TimeTablePage.jsx diff --git a/src/App.js b/src/App.js index 689e5d3..43a9c0d 100644 --- a/src/App.js +++ b/src/App.js @@ -4,13 +4,13 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import LoginPage from "./pages/LoginPage"; import SignupPage from "./pages/SignUpPage"; import HomePage from "./pages/HomePage"; -import TimeTablePage from "./pages/TimeTablePage"; import ChattingListPage from "./pages/Chatting/ChattingListPage"; import MyPage from "./pages/Mypage"; import HeaderNav from "./components/layout/HeaderNav"; import Footer from "./components/layout/Footer"; import BodyLayout from "./components/layout/BodyLayout"; import HeaderLogoBar from "./components/layout/HeaderLogoBar"; +import SchedulePage from "./pages/SchedulePage"; const App = () => { return ( @@ -21,7 +21,7 @@ const App = () => { <BodyLayout> <Routes> <Route path="/" element={<HomePage />} /> - <Route path="/timetable" element={<TimeTablePage />} /> + <Route path="/timetable" element={<SchedulePage />} /> <Route path="/chattinglist" element={<ChattingListPage />} /> <Route path="/mypage" element={<MyPage />} /> <Route path="/login" element={<LoginPage />} /> diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 12e03af..ec88ddf 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -147,7 +147,7 @@ const SchedulePage = () => { {/* Schedule Grid */} <div className="p-4"> <div className="overflow-auto scrollbar-hide"> - <div className="tablet:w-[960px] tablet:min-w-[800px] grid grid-cols-[64px,repeat(7,1fr)] gap-2"> + <div className="w-[100vw] tablet:w-[960px] grid grid-cols-[64px,repeat(7,1fr)] gap-2"> {/* Header */} <div className="min-w-[54px] p-2 font-bold text-center bg-gray-200 select-none"> Time diff --git a/src/pages/TimeTablePage.jsx b/src/pages/TimeTablePage.jsx deleted file mode 100644 index 059584d..0000000 --- a/src/pages/TimeTablePage.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const TimeTablePage = () => { - return <></>; -}; - -export default TimeTablePage; -- GitLab From a6aec49bfc383fbbc02541048b810f39fd031ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Thu, 5 Dec 2024 17:51:38 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20schedule=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 288 +++++++++++++++++++++---------------- 1 file changed, 168 insertions(+), 120 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index ec88ddf..2d85714 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -1,4 +1,5 @@ -import React, { useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; +// import { createSchedule } from "./api"; // API 호출 주석 처리 const generateTimeSlots = () => { const timeSlots = []; @@ -14,132 +15,186 @@ const generateTimeSlots = () => { const days = ["월", "화", "수", "목", "금", "토", "일"]; +const dummySchedules = [ + { + id: 1, + user_id: 1, + title: "알고리즘 스터디", + is_fixed: true, + time_indices: [36, 37, 38, 39], + createdAt: "2024-12-02T09:52:00.000Z", + updatedAt: "2024-12-02T09:52:00.000Z", + }, + { + id: 5, + user_id: 1, + title: "웹시설 팀플", + is_fixed: true, + time_indices: [165, 166, 167, 255, 256, 257], + createdAt: "2024-12-02T09:54:53.000Z", + updatedAt: "2024-12-02T09:54:53.000Z", + }, +]; + const SchedulePage = () => { const timeSlots = generateTimeSlots(); const [schedules, setSchedules] = useState([]); - const isDragging = useRef(false); - const [draggedSlots, setDraggedSlots] = useState([]); - const [inputTitle, setInputTitle] = useState(""); - const [isEditing, setIsEditing] = useState(false); - const [editMode, setEditMode] = useState(false); - - const handleMouseDown = (slotIndex) => { - if (!editMode) return; - isDragging.current = true; - - if (draggedSlots.includes(slotIndex)) { - setDraggedSlots((prev) => prev.filter((slot) => slot !== slotIndex)); - return; - } + const [isEditMode, setIsEditMode] = useState(false); + const [selectedSchedule, setSelectedSchedule] = useState(null); + const [selectedSlots, setSelectedSlots] = useState([]); + const [newTitle, setNewTitle] = useState(""); + const [isFixed, setIsFixed] = useState(true); - setDraggedSlots((prev) => [...new Set([...prev, slotIndex])]); + useEffect(() => { + setSchedules(dummySchedules); + }, []); - const schedule = schedules.find((s) => s.time_idx === slotIndex); - if (schedule) { - setInputTitle(schedule.title); - setIsEditing(true); - } else { - setInputTitle(""); - setIsEditing(false); - } - }; + const handleSlotClick = (timeIdx) => { + if (!isEditMode) return; - const handleMouseEnter = (slotIndex) => { - if (!editMode || !isDragging.current) return; - setDraggedSlots((prev) => [...new Set([...prev, slotIndex])]); - }; + const slotInSchedule = schedules.find((s) => + s.time_indices.includes(timeIdx) + ); - const handleMouseUp = () => { - isDragging.current = false; - }; + if (slotInSchedule) { + if (selectedSlots.length === 0) { + setSelectedSchedule(slotInSchedule); + } + return; + } - const handleMouseLeave = () => { - isDragging.current = false; + // Toggle slot selection for new schedule creation + if (selectedSlots.includes(timeIdx)) { + setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx)); + } else { + setSelectedSlots((prev) => [...prev, timeIdx]); + } }; - const handleSave = () => { - if (!inputTitle.trim()) return; + const handleCreateSchedule = async () => { + if (!newTitle.trim() || selectedSlots.length === 0) { + alert("Title must not be empty and at least one slot must be selected."); + return; + } - const newSchedules = [...schedules]; - draggedSlots.forEach((slotIndex) => { - const existingIndex = newSchedules.findIndex( - (s) => s.time_idx === slotIndex - ); - if (existingIndex === -1) { - newSchedules.push({ - id: Date.now(), - time_idx: slotIndex, - title: inputTitle, - is_fixed: false, - }); - } else { - newSchedules[existingIndex].title = inputTitle; - } - }); + const newSchedule = { + title: newTitle, + is_fixed: isFixed, + time_indices: selectedSlots, + }; - setSchedules(newSchedules); - setDraggedSlots([]); - setInputTitle(""); - setIsEditing(false); - }; + try { + // API 호출 준비가 되었을 때 사용: + // await createSchedule(newSchedule); - const handleDelete = () => { - const newSchedules = schedules.filter( - (s) => !draggedSlots.includes(s.time_idx) - ); - setSchedules(newSchedules); - setDraggedSlots([]); - setInputTitle(""); - setIsEditing(false); + // 임시로 더미 데이터에 추가 + setSchedules((prev) => [ + ...prev, + { ...newSchedule, id: Date.now(), user_id: 1 }, + ]); + setSelectedSlots([]); + setNewTitle(""); + setIsFixed(true); + alert("Schedule created successfully!"); + } catch (error) { + console.error("Failed to create schedule:", error); + } }; return ( - <div className="min-h-screen bg-gray-50"> - {/* Edit Mode Toggle */} + <div className="min-h-screen bg-grayscale-50"> + {/* Toggle View/Edit Mode */} <div className="flex items-center justify-between p-4 bg-white shadow"> - <h1 className="text-lg font-bold mobile:text-base">Schedule</h1> - <label className="flex items-center space-x-3"> - <span className="text-lg font-bold mobile:text-sm">Edit Mode</span> + <h1 className="heading-1 text-primary-500">Schedule</h1> + <label className="flex items-center space-x-3 cursor-pointer"> + <span className="title-1 text-secondary-500">Edit Mode</span> <div - className={`relative w-12 h-6 rounded-full cursor-pointer transition-colors ${ - editMode ? "bg-gradient-pink" : "bg-gray-300" + className={`relative w-12 h-6 rounded-full transition-colors ${ + isEditMode ? "bg-primary-500" : "bg-grayscale-300" }`} - onClick={() => setEditMode((prev) => !prev)} + onClick={() => { + setIsEditMode((prev) => !prev); + setSelectedSlots([]); + setSelectedSchedule(null); + }} > <div className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${ - editMode ? "translate-x-6" : "" + isEditMode ? "translate-x-6" : "" }`} ></div> </div> </label> </div> - {/* Sticky Container (Edit Mode가 on일 때만 표시) */} - {editMode && ( - <div className="sticky top-0 flex flex-col gap-2 p-4 bg-white shadow tablet:flex-row tablet:justify-between"> - <input - type="text" - value={inputTitle} - onChange={(e) => setInputTitle(e.target.value)} - placeholder="Enter schedule title" - className="flex-1 p-2 border rounded" - /> - <div className="flex space-x-2"> - <button - onClick={handleSave} - className="flex-1 px-4 py-2 text-white rounded bg-gradient-pink disabled:opacity-50" - disabled={!draggedSlots.length} - > - {isEditing ? "Update" : "Save"} - </button> - <button - onClick={handleDelete} - className="flex-1 px-4 py-2 text-white rounded bg-gradient-purple disabled:opacity-50" - disabled={!draggedSlots.length} - > - Delete - </button> + {/* Sticky Container in Edit Mode */} + {isEditMode && ( + <div className="fixed bottom-0 left-0 right-0 flex items-center justify-center w-screen"> + <div + className={`transform transition-transform w-full max-w-[760px] tablet:rounded-2xl bg-primary-100 p-6 text-center shadow-lg`} + > + {selectedSlots.length === 0 && selectedSchedule ? ( + <> + <h3 className="mb-2 heading-2 text-primary-500"> + Schedule Details + </h3> + <p className="mb-1 body-1"> + <strong>Title:</strong> {selectedSchedule.title} + </p> + <p className="mb-1 body-1"> + <strong>Fixed:</strong>{" "} + {selectedSchedule.is_fixed ? "Yes" : "No"} + </p> + <p className="body-1"> + <strong>Time Indices:</strong>{" "} + {selectedSchedule.time_indices.join(", ")} + </p> + </> + ) : ( + <> + <h3 className="mb-4 heading-2 text-primary-500"> + Create New Schedule + </h3> + <input + type="text" + value={newTitle} + onChange={(e) => setNewTitle(e.target.value)} + placeholder="Enter title" + className="w-full p-2 mb-4 border rounded shadow-input-box" + /> + <div className="flex items-center justify-center mb-4 space-x-4"> + <label className="flex items-center space-x-2"> + <input + type="radio" + name="is_fixed" + value={true} + checked={isFixed === true} + onChange={() => setIsFixed(true)} + /> + <span className="body-1">Fixed</span> + </label> + <label className="flex items-center space-x-2"> + <input + type="radio" + name="is_fixed" + value={false} + checked={isFixed === false} + onChange={() => setIsFixed(false)} + /> + <span className="body-1">Flexible</span> + </label> + </div> + <p className="mb-4 body-1"> + Selected Slots: {selectedSlots.join(", ")} + </p> + <button + onClick={handleCreateSchedule} + className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + > + Add Schedule + </button> + </> + )} </div> </div> )} @@ -149,13 +204,13 @@ const SchedulePage = () => { <div className="overflow-auto scrollbar-hide"> <div className="w-[100vw] tablet:w-[960px] grid grid-cols-[64px,repeat(7,1fr)] gap-2"> {/* Header */} - <div className="min-w-[54px] p-2 font-bold text-center bg-gray-200 select-none"> + <div className="min-w-[54px] p-2 font-bold text-center bg-grayscale-200 select-none"> Time </div> {days.map((day) => ( <div key={day} - className="p-2 font-bold text-center bg-gray-200 select-none" + className="p-2 font-bold text-center select-none bg-grayscale-200" > {day} </div> @@ -165,36 +220,29 @@ const SchedulePage = () => { {timeSlots.map((time, rowIndex) => ( <React.Fragment key={rowIndex}> {/* Time Column */} - <div className="min-w-[54px] p-2 text-sm font-bold text-center bg-gray-100 select-none"> + <div className="min-w-[54px] p-2 font-bold text-center bg-grayscale-100 select-none"> {time} </div> {days.map((_, colIndex) => { const slotIndex = colIndex * timeSlots.length + rowIndex; - const isSelected = draggedSlots.includes(slotIndex); - const schedule = schedules.find( - (s) => s.time_idx === slotIndex + const isSelected = selectedSlots.includes(slotIndex); + const schedule = schedules.find((s) => + s.time_indices.includes(slotIndex) ); return ( <div key={slotIndex} - className={`p-2 border rounded cursor-pointer ${ - editMode - ? isSelected - ? "bg-tertiary-300" - : schedule - ? "bg-primary-500 text-white" - : "bg-white" - : schedule - ? "bg-primary-500 text-white" - : "bg-gray-50" + className={`p-2 border rounded ${ + schedule + ? "bg-primary-300 text-white cursor-not-allowed" + : isSelected + ? "bg-primary-100 border-primary-300" + : "bg-grayscale-50 cursor-pointer" }`} - onMouseDown={() => handleMouseDown(slotIndex)} - onMouseEnter={() => handleMouseEnter(slotIndex)} - onMouseUp={handleMouseUp} - onMouseLeave={handleMouseLeave} + onClick={() => handleSlotClick(slotIndex)} > - {schedule && !isSelected ? schedule.title : ""} + {schedule ? schedule.title : ""} </div> ); })} -- GitLab From 1709d38c6dbde71fe2b4f03b252baab211c5ddef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Thu, 5 Dec 2024 17:57:48 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A4=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 70 ++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 2d85714..87132e3 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -// import { createSchedule } from "./api"; // API 호출 주석 처리 +// import { createSchedule, deleteSchedule, fetchAllSchedules } from "./api"; // API 호출 주석 유지 const generateTimeSlots = () => { const timeSlots = []; @@ -46,6 +46,18 @@ const SchedulePage = () => { const [isFixed, setIsFixed] = useState(true); useEffect(() => { + // const initializeSchedules = async () => { + // try { + // const data = await fetchAllSchedules(); + // setSchedules(data); + // } catch (error) { + // console.error("Failed to load schedules", error); + // } + // }; + + // initializeSchedules(); + + // 현재는 더미 데이터로 초기화 setSchedules(dummySchedules); }, []); @@ -63,7 +75,6 @@ const SchedulePage = () => { return; } - // Toggle slot selection for new schedule creation if (selectedSlots.includes(timeIdx)) { setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx)); } else { @@ -71,33 +82,26 @@ const SchedulePage = () => { } }; - const handleCreateSchedule = async () => { - if (!newTitle.trim() || selectedSlots.length === 0) { - alert("Title must not be empty and at least one slot must be selected."); - return; - } - - const newSchedule = { - title: newTitle, - is_fixed: isFixed, - time_indices: selectedSlots, - }; + const handleDeleteSchedule = async () => { + if (!selectedSchedule) return; try { + const body = { title: selectedSchedule.title }; + // API 호출 준비가 되었을 때 사용: - // await createSchedule(newSchedule); + // await deleteSchedule(body); + + // const updatedSchedules = await fetchAllSchedules(); + // setSchedules(updatedSchedules); - // 임시로 더미 데이터에 추가 - setSchedules((prev) => [ - ...prev, - { ...newSchedule, id: Date.now(), user_id: 1 }, - ]); - setSelectedSlots([]); - setNewTitle(""); - setIsFixed(true); - alert("Schedule created successfully!"); + // 임시로 삭제 처리 + setSchedules((prev) => + prev.filter((s) => s.title !== selectedSchedule.title) + ); + setSelectedSchedule(null); + alert("Schedule deleted successfully!"); } catch (error) { - console.error("Failed to create schedule:", error); + console.error("Failed to delete schedule:", error); } }; @@ -149,6 +153,22 @@ const SchedulePage = () => { <strong>Time Indices:</strong>{" "} {selectedSchedule.time_indices.join(", ")} </p> + <div className="flex justify-center mt-4 space-x-4"> + <button + className="px-4 py-2 font-bold text-white rounded bg-gradient-purple" + onClick={() => + alert("Edit functionality not implemented yet") + } + > + Edit + </button> + <button + className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + onClick={handleDeleteSchedule} + > + Delete + </button> + </div> </> ) : ( <> @@ -188,8 +208,8 @@ const SchedulePage = () => { Selected Slots: {selectedSlots.join(", ")} </p> <button - onClick={handleCreateSchedule} className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + onClick={() => alert("Schedule created")} > Add Schedule </button> -- GitLab From 0d82ce722c1ea82c8bf163e3c903a4f5446580c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Fri, 6 Dec 2024 00:27:49 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A5=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 66 ++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 87132e3..8a9f684 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from "react"; +import Label from "../components/Label"; // import { createSchedule, deleteSchedule, fetchAllSchedules } from "./api"; // API 호출 주석 유지 const generateTimeSlots = () => { @@ -82,6 +83,36 @@ const SchedulePage = () => { } }; + const handleCreateSchedule = async () => { + try { + const scheduleData = { + title: newTitle, + is_fixed: isFixed, + time_indices: selectedSlots, + }; + + // API 호출 부분 (주석 처리) + // const newSchedule = await createSchedule(scheduleData); + + // 임시로 더미 데이터에 추가 + const newSchedule = { + ...scheduleData, + id: Date.now(), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + setSchedules((prev) => [...prev, newSchedule]); + setSelectedSlots([]); + setNewTitle(""); + setIsFixed(true); + alert("Schedule created successfully!"); + } catch (error) { + console.error("Failed to create schedule:", error); + alert("Failed to create schedule."); + } + }; + const handleDeleteSchedule = async () => { if (!selectedSchedule) return; @@ -133,9 +164,9 @@ const SchedulePage = () => { {/* Sticky Container in Edit Mode */} {isEditMode && ( - <div className="fixed bottom-0 left-0 right-0 flex items-center justify-center w-screen"> + <div className="fixed bottom-0 right-0 flex items-center justify-center w-screen"> <div - className={`transform transition-transform w-full max-w-[760px] tablet:rounded-2xl bg-primary-100 p-6 text-center shadow-lg`} + className={`transform transition-transform w-full max-w-[768px] tablet:rounded-2xl bg-primary-100 p-6 text-center shadow-lg`} > {selectedSlots.length === 0 && selectedSchedule ? ( <> @@ -147,12 +178,16 @@ const SchedulePage = () => { </p> <p className="mb-1 body-1"> <strong>Fixed:</strong>{" "} - {selectedSchedule.is_fixed ? "Yes" : "No"} - </p> - <p className="body-1"> - <strong>Time Indices:</strong>{" "} - {selectedSchedule.time_indices.join(", ")} + {selectedSchedule.is_fixed ? "고정" : "유동"} </p> + <div className="mb-4 body-1"> + <span>Time Indices:</span> + {selectedSchedule.time_indices.map((time_idx) => ( + <Label key={time_idx} theme="indigo" size="sm"> + {time_idx} + </Label> + ))} + </div> <div className="flex justify-center mt-4 space-x-4"> <button className="px-4 py-2 font-bold text-white rounded bg-gradient-purple" @@ -191,7 +226,7 @@ const SchedulePage = () => { checked={isFixed === true} onChange={() => setIsFixed(true)} /> - <span className="body-1">Fixed</span> + <span className="body-1">고정</span> </label> <label className="flex items-center space-x-2"> <input @@ -201,15 +236,20 @@ const SchedulePage = () => { checked={isFixed === false} onChange={() => setIsFixed(false)} /> - <span className="body-1">Flexible</span> + <span className="body-1">유동</span> </label> </div> - <p className="mb-4 body-1"> - Selected Slots: {selectedSlots.join(", ")} - </p> + <div className="mb-4 body-1"> + <span>Selected Slots:</span> + {selectedSlots.map((time_idx) => ( + <Label key={time_idx} theme="indigo" size="sm"> + {time_idx} + </Label> + ))} + </div> <button className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" - onClick={() => alert("Schedule created")} + onClick={() => handleCreateSchedule()} > Add Schedule </button> -- GitLab From 08c27311e7be908eb08567db6f314e0a1149855f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Fri, 6 Dec 2024 00:58:02 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A4=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 80 +++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 8a9f684..b796046 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -41,6 +41,7 @@ const SchedulePage = () => { const timeSlots = generateTimeSlots(); const [schedules, setSchedules] = useState([]); const [isEditMode, setIsEditMode] = useState(false); + const [isUpdateMode, setIsUpdateMode] = useState(false); const [selectedSchedule, setSelectedSchedule] = useState(null); const [selectedSlots, setSelectedSlots] = useState([]); const [newTitle, setNewTitle] = useState(""); @@ -62,7 +63,7 @@ const SchedulePage = () => { setSchedules(dummySchedules); }, []); - const handleSlotClick = (timeIdx) => { + const handleSlotClick = async (timeIdx) => { if (!isEditMode) return; const slotInSchedule = schedules.find((s) => @@ -81,6 +82,58 @@ const SchedulePage = () => { } else { setSelectedSlots((prev) => [...prev, timeIdx]); } + + // API 호출 준비가 되었을 때 사용: + // try { + // const response = await fetchScheduleByTimeIndex(timeIdx); + // if (response && response.data && response.data.schedule) { + // setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정 + // } else { + // console.error("No schedule found for time index:", timeIdx); + // } + // } catch (error) { + // console.error("Failed to fetch schedule for time index:", timeIdx, error); + // } + }; + + const handleEditSchedule = () => { + setIsUpdateMode(true); + setSchedules((prev) => + prev.filter((s) => s.title !== selectedSchedule.title) + ); + setSelectedSlots(selectedSchedule.time_indices); + setNewTitle(selectedSchedule.title); + setIsFixed(selectedSchedule.is_fixed); + }; + + const handleUpdateSchedule = async () => { + try { + const scheduleData = { + originalTitle: selectedSchedule.title, + title: newTitle, + is_fixed: isFixed, + time_indices: selectedSlots, + }; + // API 호출 부분 (주석 처리) + // const newSchedule = await updateSchedule(scheduleData); + // 임시로 더미 데이터에 추가 + const newSchedule = { + ...scheduleData, + id: Date.now(), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + setSchedules((prev) => [...prev, newSchedule]); + setSelectedSchedule(newSchedule); + setSelectedSlots([]); + setNewTitle(""); + setIsFixed(true); + alert("Schedule update successfully!"); + } catch (error) { + console.error("Failed to update schedule:", error); + alert("Failed to update schedule."); + } }; const handleCreateSchedule = async () => { @@ -191,9 +244,7 @@ const SchedulePage = () => { <div className="flex justify-center mt-4 space-x-4"> <button className="px-4 py-2 font-bold text-white rounded bg-gradient-purple" - onClick={() => - alert("Edit functionality not implemented yet") - } + onClick={handleEditSchedule} > Edit </button> @@ -247,12 +298,21 @@ const SchedulePage = () => { </Label> ))} </div> - <button - className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" - onClick={() => handleCreateSchedule()} - > - Add Schedule - </button> + {isUpdateMode ? ( + <button + className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + onClick={() => handleUpdateSchedule()} + > + Update Schedule + </button> + ) : ( + <button + className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + onClick={() => handleCreateSchedule()} + > + Add Schedule + </button> + )} </> )} </div> -- GitLab From 0dae8af52cb815dc7b9dc0f3279a1a9beaebd119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Fri, 6 Dec 2024 01:33:24 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A5=B4=20upd?= =?UTF-8?q?ate=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index b796046..a68d221 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -116,6 +116,7 @@ const SchedulePage = () => { }; // API 호출 부분 (주석 처리) // const newSchedule = await updateSchedule(scheduleData); + // 임시로 더미 데이터에 추가 const newSchedule = { ...scheduleData, @@ -133,6 +134,8 @@ const SchedulePage = () => { } catch (error) { console.error("Failed to update schedule:", error); alert("Failed to update schedule."); + } finally { + setIsUpdateMode(false); } }; @@ -217,7 +220,7 @@ const SchedulePage = () => { {/* Sticky Container in Edit Mode */} {isEditMode && ( - <div className="fixed bottom-0 right-0 flex items-center justify-center w-screen"> + <div className="fixed bottom-0 right-0 flex items-center justify-center w-full "> <div className={`transform transition-transform w-full max-w-[768px] tablet:rounded-2xl bg-primary-100 p-6 text-center shadow-lg`} > @@ -258,9 +261,15 @@ const SchedulePage = () => { </> ) : ( <> - <h3 className="mb-4 heading-2 text-primary-500"> - Create New Schedule - </h3> + {isUpdateMode ? ( + <h3 className="mb-4 heading-2 text-primary-500"> + Update Schedule + </h3> + ) : ( + <h3 className="mb-4 heading-2 text-primary-500"> + Create New Schedule + </h3> + )} <input type="text" value={newTitle} -- GitLab From b1d88b24fd4a9e99e3cbc17cc52be7e34f62fd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Fri, 6 Dec 2024 01:52:01 +0900 Subject: [PATCH 09/11] =?UTF-8?q?design:=20=EC=8A=A4=EC=BC=80=EC=A5=B4=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=EB=B0=8F=20=EC=88=98=EC=A0=95=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=ED=86=A0=EA=B8=80=20=EC=83=89=EC=83=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index a68d221..3fd46fe 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -196,9 +196,9 @@ const SchedulePage = () => { <div className="min-h-screen bg-grayscale-50"> {/* Toggle View/Edit Mode */} <div className="flex items-center justify-between p-4 bg-white shadow"> - <h1 className="heading-1 text-primary-500">Schedule</h1> + <h1 className="heading-1">Schedule</h1> <label className="flex items-center space-x-3 cursor-pointer"> - <span className="title-1 text-secondary-500">Edit Mode</span> + <span className="title-1 text-primary-500">Edit Mode</span> <div className={`relative w-12 h-6 rounded-full transition-colors ${ isEditMode ? "bg-primary-500" : "bg-grayscale-300" -- GitLab From 6349e1e51bb0b984fc9140c8eb7bc7a948fe56f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Fri, 6 Dec 2024 02:15:29 +0900 Subject: [PATCH 10/11] =?UTF-8?q?feat:=20=EC=84=A0=ED=83=9D=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20=EB=B0=8F?= =?UTF-8?q?=20=EC=95=88=EB=82=B4=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 121 +++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 3fd46fe..a39e13a 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -66,6 +66,19 @@ const SchedulePage = () => { const handleSlotClick = async (timeIdx) => { if (!isEditMode) return; + // API 호출 준비가 되었을 때 사용: + // try { + // const response = await fetchScheduleByTimeIndex(timeIdx); + // if (response && response.data && response.data.schedule) { + // setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정 + // } else { + // console.error("No schedule found for time index:", timeIdx); + // } + // } catch (error) { + // console.error("Failed to fetch schedule for time index:", timeIdx, error); + // } + + //임시 코드 const slotInSchedule = schedules.find((s) => s.time_indices.includes(timeIdx) ); @@ -82,18 +95,13 @@ const SchedulePage = () => { } else { setSelectedSlots((prev) => [...prev, timeIdx]); } + }; - // API 호출 준비가 되었을 때 사용: - // try { - // const response = await fetchScheduleByTimeIndex(timeIdx); - // if (response && response.data && response.data.schedule) { - // setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정 - // } else { - // console.error("No schedule found for time index:", timeIdx); - // } - // } catch (error) { - // console.error("Failed to fetch schedule for time index:", timeIdx, error); - // } + const handleCancelSchedule = () => { + setSelectedSlots([]); + setNewTitle(""); + setIsFixed(true); + setSelectedSchedule(null); }; const handleEditSchedule = () => { @@ -130,10 +138,10 @@ const SchedulePage = () => { setSelectedSlots([]); setNewTitle(""); setIsFixed(true); - alert("Schedule update successfully!"); + alert("스케줄을 수정했습니다.!"); } catch (error) { - console.error("Failed to update schedule:", error); - alert("Failed to update schedule."); + console.error("스케줄 수정에 실패했습니다.:", error); + alert("스케줄 수정에 실패했습니다."); } finally { setIsUpdateMode(false); } @@ -162,10 +170,10 @@ const SchedulePage = () => { setSelectedSlots([]); setNewTitle(""); setIsFixed(true); - alert("Schedule created successfully!"); + alert("스케줄이 추가되었습니다!"); } catch (error) { - console.error("Failed to create schedule:", error); - alert("Failed to create schedule."); + console.error("스케줄 삭제에 실패했습니다:", error); + alert("스케줄 추가에 실패했습니다."); } }; @@ -185,10 +193,11 @@ const SchedulePage = () => { setSchedules((prev) => prev.filter((s) => s.title !== selectedSchedule.title) ); + setSelectedSchedule(null); - alert("Schedule deleted successfully!"); + alert("스케줄이 삭제되었습니다."); } catch (error) { - console.error("Failed to delete schedule:", error); + console.error("스케줄 삭제에 실패했습니다:", error); } }; @@ -225,49 +234,49 @@ const SchedulePage = () => { className={`transform transition-transform w-full max-w-[768px] tablet:rounded-2xl bg-primary-100 p-6 text-center shadow-lg`} > {selectedSlots.length === 0 && selectedSchedule ? ( - <> - <h3 className="mb-2 heading-2 text-primary-500"> - Schedule Details - </h3> - <p className="mb-1 body-1"> - <strong>Title:</strong> {selectedSchedule.title} - </p> - <p className="mb-1 body-1"> - <strong>Fixed:</strong>{" "} - {selectedSchedule.is_fixed ? "고정" : "유동"} - </p> - <div className="mb-4 body-1"> - <span>Time Indices:</span> - {selectedSchedule.time_indices.map((time_idx) => ( - <Label key={time_idx} theme="indigo" size="sm"> - {time_idx} - </Label> - ))} + <div className="flex flex-col items-center justify-center w-full"> + <h3 className="mb-2 heading-2 text-primary-500">스케줄 정보</h3> + <div className="flex flex-col items-start w-1/2"> + <p className="mb-1 body-1"> + <strong>제목:</strong> {selectedSchedule.title} + </p> + <p className="mb-1 body-1"> + <strong>스케줄 타입:</strong>{" "} + {selectedSchedule.is_fixed ? "고정" : "유동"} + </p> + <div className="mb-4 body-1"> + <strong>선택된 시간:</strong>{" "} + {selectedSchedule.time_indices.map((time_idx) => ( + <Label key={time_idx} theme="indigo" size="sm"> + {time_idx} + </Label> + ))} + </div> </div> <div className="flex justify-center mt-4 space-x-4"> <button className="px-4 py-2 font-bold text-white rounded bg-gradient-purple" onClick={handleEditSchedule} > - Edit + 수정하기 </button> <button className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" onClick={handleDeleteSchedule} > - Delete + 삭제하기 </button> </div> - </> + </div> ) : ( <> {isUpdateMode ? ( <h3 className="mb-4 heading-2 text-primary-500"> - Update Schedule + 스케줄 수정하기 </h3> ) : ( <h3 className="mb-4 heading-2 text-primary-500"> - Create New Schedule + 새 스케줄 만들기 </h3> )} <input @@ -286,7 +295,7 @@ const SchedulePage = () => { checked={isFixed === true} onChange={() => setIsFixed(true)} /> - <span className="body-1">고정</span> + <span className="body-1">고정 스케줄</span> </label> <label className="flex items-center space-x-2"> <input @@ -296,11 +305,11 @@ const SchedulePage = () => { checked={isFixed === false} onChange={() => setIsFixed(false)} /> - <span className="body-1">유동</span> + <span className="body-1">유동 스케줄</span> </label> </div> <div className="mb-4 body-1"> - <span>Selected Slots:</span> + <span>선택된 시간:</span> {selectedSlots.map((time_idx) => ( <Label key={time_idx} theme="indigo" size="sm"> {time_idx} @@ -312,15 +321,23 @@ const SchedulePage = () => { className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" onClick={() => handleUpdateSchedule()} > - Update Schedule + 수정 완료 </button> ) : ( - <button - className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" - onClick={() => handleCreateSchedule()} - > - Add Schedule - </button> + <div className="flex justify-center mt-4 space-x-4"> + <button + className="px-4 py-2 font-bold text-white rounded bg-tertiary-900" + onClick={() => handleCancelSchedule()} + > + 취소 + </button> + <button + className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + onClick={() => handleCreateSchedule()} + > + 추가 + </button> + </div> )} </> )} -- GitLab From eab6aed7fb2edef435716ccec3eaca105dbdbadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr> Date: Fri, 6 Dec 2024 02:26:30 +0900 Subject: [PATCH 11/11] =?UTF-8?q?feat:=20=EC=8A=A4=EC=BC=80=EC=A4=84=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20api=20=EC=A0=81=EC=9A=A9=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 175 +++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 84 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index a39e13a..5fa5f28 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -1,6 +1,12 @@ import React, { useEffect, useState } from "react"; import Label from "../components/Label"; -// import { createSchedule, deleteSchedule, fetchAllSchedules } from "./api"; // API 호출 주석 유지 +import { + createSchedule, + deleteSchedule, + fetchScheduleByTimeIndex, + fetchAllSchedules, + updateSchedule, +} from "../api/schedule"; const generateTimeSlots = () => { const timeSlots = []; @@ -16,26 +22,26 @@ const generateTimeSlots = () => { const days = ["월", "화", "수", "목", "금", "토", "일"]; -const dummySchedules = [ - { - id: 1, - user_id: 1, - title: "알고리즘 스터디", - is_fixed: true, - time_indices: [36, 37, 38, 39], - createdAt: "2024-12-02T09:52:00.000Z", - updatedAt: "2024-12-02T09:52:00.000Z", - }, - { - id: 5, - user_id: 1, - title: "웹시설 팀플", - is_fixed: true, - time_indices: [165, 166, 167, 255, 256, 257], - createdAt: "2024-12-02T09:54:53.000Z", - updatedAt: "2024-12-02T09:54:53.000Z", - }, -]; +// const dummySchedules = [ +// { +// id: 1, +// user_id: 1, +// title: "알고리즘 스터디", +// is_fixed: true, +// time_indices: [36, 37, 38, 39], +// createdAt: "2024-12-02T09:52:00.000Z", +// updatedAt: "2024-12-02T09:52:00.000Z", +// }, +// { +// id: 5, +// user_id: 1, +// title: "웹시설 팀플", +// is_fixed: true, +// time_indices: [165, 166, 167, 255, 256, 257], +// createdAt: "2024-12-02T09:54:53.000Z", +// updatedAt: "2024-12-02T09:54:53.000Z", +// }, +// ]; const SchedulePage = () => { const timeSlots = generateTimeSlots(); @@ -48,53 +54,54 @@ const SchedulePage = () => { const [isFixed, setIsFixed] = useState(true); useEffect(() => { - // const initializeSchedules = async () => { - // try { - // const data = await fetchAllSchedules(); - // setSchedules(data); - // } catch (error) { - // console.error("Failed to load schedules", error); - // } - // }; + // API + const initializeSchedules = async () => { + try { + const data = await fetchAllSchedules(); + setSchedules(data); + } catch (error) { + console.error("Failed to load schedules", error); + } + }; - // initializeSchedules(); + initializeSchedules(); - // 현재는 더미 데이터로 초기화 - setSchedules(dummySchedules); + // 임시 코드 + // setSchedules(dummySchedules); }, []); const handleSlotClick = async (timeIdx) => { if (!isEditMode) return; - // API 호출 준비가 되었을 때 사용: - // try { - // const response = await fetchScheduleByTimeIndex(timeIdx); - // if (response && response.data && response.data.schedule) { - // setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정 - // } else { - // console.error("No schedule found for time index:", timeIdx); - // } - // } catch (error) { - // console.error("Failed to fetch schedule for time index:", timeIdx, error); - // } - - //임시 코드 - const slotInSchedule = schedules.find((s) => - s.time_indices.includes(timeIdx) - ); - - if (slotInSchedule) { - if (selectedSlots.length === 0) { - setSelectedSchedule(slotInSchedule); + // API + try { + const response = await fetchScheduleByTimeIndex(timeIdx); + if (response && response.data && response.data.schedule) { + setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정 + } else { + console.error("No schedule found for time index:", timeIdx); } - return; + } catch (error) { + console.error("Failed to fetch schedule for time index:", timeIdx, error); } - if (selectedSlots.includes(timeIdx)) { - setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx)); - } else { - setSelectedSlots((prev) => [...prev, timeIdx]); - } + // 임시 코드 + // const slotInSchedule = schedules.find((s) => + // s.time_indices.includes(timeIdx) + // ); + + // if (slotInSchedule) { + // if (selectedSlots.length === 0) { + // setSelectedSchedule(slotInSchedule); + // } + // return; + // } + + // if (selectedSlots.includes(timeIdx)) { + // setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx)); + // } else { + // setSelectedSlots((prev) => [...prev, timeIdx]); + // } }; const handleCancelSchedule = () => { @@ -122,16 +129,16 @@ const SchedulePage = () => { is_fixed: isFixed, time_indices: selectedSlots, }; - // API 호출 부분 (주석 처리) - // const newSchedule = await updateSchedule(scheduleData); - - // 임시로 더미 데이터에 추가 - const newSchedule = { - ...scheduleData, - id: Date.now(), - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; + // API + const newSchedule = await updateSchedule(scheduleData); + + // 임시 코드 + // const newSchedule = { + // ...scheduleData, + // id: Date.now(), + // createdAt: new Date().toISOString(), + // updatedAt: new Date().toISOString(), + // }; setSchedules((prev) => [...prev, newSchedule]); setSelectedSchedule(newSchedule); @@ -155,16 +162,16 @@ const SchedulePage = () => { time_indices: selectedSlots, }; - // API 호출 부분 (주석 처리) - // const newSchedule = await createSchedule(scheduleData); + // API + const newSchedule = await createSchedule(scheduleData); - // 임시로 더미 데이터에 추가 - const newSchedule = { - ...scheduleData, - id: Date.now(), - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; + // 임시코드 + // const newSchedule = { + // ...scheduleData, + // id: Date.now(), + // createdAt: new Date().toISOString(), + // updatedAt: new Date().toISOString(), + // }; setSchedules((prev) => [...prev, newSchedule]); setSelectedSlots([]); @@ -184,15 +191,15 @@ const SchedulePage = () => { const body = { title: selectedSchedule.title }; // API 호출 준비가 되었을 때 사용: - // await deleteSchedule(body); + await deleteSchedule(body); - // const updatedSchedules = await fetchAllSchedules(); - // setSchedules(updatedSchedules); + const updatedSchedules = await fetchAllSchedules(); + setSchedules(updatedSchedules); - // 임시로 삭제 처리 - setSchedules((prev) => - prev.filter((s) => s.title !== selectedSchedule.title) - ); + // 임시코드 + // setSchedules((prev) => + // prev.filter((s) => s.title !== selectedSchedule.title) + // ); setSelectedSchedule(null); alert("스케줄이 삭제되었습니다."); -- GitLab