diff --git a/src/pages/Schedule.jsx b/src/pages/Schedule.jsx deleted file mode 100644 index 980b0e5bb47b8cef01e7094d47a3cdd2c503380f..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..12e03af3f889f9619d1faaf697f873242cfeb6d1 --- /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;