diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 01c851dfa41010f0e3e897ca147bb131c287c2ee..d07252424be88fbde75afc6a183f179e801093b9 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -22,70 +22,74 @@ 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", - }, - { - id: 11, - user_id: 1, - title: "점심약속", - is_fixed: false, - time_indices: [240, 241, 242], - createdAt: "2024-12-02T09:54:53.000Z", - updatedAt: "2024-12-02T09:54:53.000Z", - }, - { - id: 14, - user_id: 1, - title: "롤 5:5", - is_fixed: true, - time_indices: [302, 303, 304, 305, 306, 307], - createdAt: "2024-12-02T09:54:53.000Z", - updatedAt: "2024-12-02T09:54:53.000Z", - }, - { - id: 20, - user_id: 1, - title: "토트넘 vs 첼시 경기", - is_fixed: true, - time_indices: [13, 14, 15, 16, 17, 18], - 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", +// }, +// { +// id: 11, +// user_id: 1, +// title: "점심약속", +// is_fixed: false, +// time_indices: [240, 241, 242], +// createdAt: "2024-12-02T09:54:53.000Z", +// updatedAt: "2024-12-02T09:54:53.000Z", +// }, +// { +// id: 14, +// user_id: 1, +// title: "롤 5:5", +// is_fixed: true, +// time_indices: [302, 303, 304, 305, 306, 307], +// createdAt: "2024-12-02T09:54:53.000Z", +// updatedAt: "2024-12-02T09:54:53.000Z", +// }, +// { +// id: 20, +// user_id: 1, +// title: "토트넘 vs 첼시 경기", +// is_fixed: true, +// time_indices: [13, 14, 15, 16, 17, 18], +// createdAt: "2024-12-02T09:54:53.000Z", +// updatedAt: "2024-12-02T09:54:53.000Z", +// }, +// { +// id: 26, +// user_id: 1, +// title: "아침 구보", +// is_fixed: true, +// time_indices: [34, 35, 130, 131, 226, 227, 322, 323, 418, 419, 514, 515, 610, 611], +// createdAt: "2024-12-02T09:54:53.000Z", +// updatedAt: "2024-12-02T09:54:53.000Z", +// }, +// ]; const colorClasses = [ - 'bg-indigo-300', - 'bg-purple-300', - 'bg-pink-300', - 'bg-blue-300', - 'bg-green-300', - 'bg-yellow-300', - 'bg-red-300', - 'bg-orange-300', - 'bg-teal-300', - 'bg-cyan-300', - 'bg-rose-300', - 'bg-violet-300', - 'bg-fuchsia-300', - 'bg-emerald-300', - 'bg-lime-300' + 'bg-indigo-300 hover:bg-indigo-400', + 'bg-purple-300 hover:bg-purple-400', + 'bg-pink-300 hover:bg-pink-400', + 'bg-blue-300 hover:bg-blue-400', + 'bg-green-300 hover:bg-green-400', + 'bg-yellow-300 hover:bg-yellow-400', + 'bg-red-300 hover:bg-red-400', + 'bg-orange-300 hover:bg-orange-400', + 'bg-teal-300 hover:bg-teal-400', + 'bg-cyan-300 hover:bg-cyan-400', ]; const SchedulePage = () => { const timeSlots = generateTimeSlots(); @@ -97,13 +101,21 @@ const SchedulePage = () => { const [newTitle, setNewTitle] = useState(""); const [isFixed, setIsFixed] = useState(true); const [titleColorMap, setTitleColorMap] = useState(new Map()); + const [showAllTimeSlot, setShowAllTimeSlot] = useState(false); useEffect(() => { // API const initializeSchedules = async () => { try { const data = await fetchAllSchedules(); - setSchedules(data); + + // 스케줄 병합을 위해서 사용 + const sortedSchedules = [...data].sort((a, b) => { + const aMin = Math.min(...a.time_indices); + const bMin = Math.min(...b.time_indices); + return aMin - bMin; + }); + setSchedules(sortedSchedules); } catch (error) { console.error("Failed to load schedules", error); } @@ -111,8 +123,8 @@ const SchedulePage = () => { initializeSchedules(); - // 임시 코드 - setSchedules(dummySchedules); + // 임시 코드 + // setSchedules(dummySchedules); }, []); useEffect(() => { @@ -278,13 +290,47 @@ const SchedulePage = () => { const timeSlotIndex = timeIndex % 96; const hour = Math.floor(timeSlotIndex / 4); const minute = (timeSlotIndex % 4) * 15; - const day = days[dayIndex]; const time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; - return `${day} ${time}`; }; + // 스케줄 통합해서 보여주기 + const renderTimeSlot = (slotIndex, rowIndex, colIndex) => { + const schedule = schedules.find((s) => s.time_indices.includes(slotIndex)); + const isSelected = selectedSlots.includes(slotIndex); + + const isFirstSlot = schedule && + !schedule.time_indices.includes(slotIndex - 1) && + Math.floor((slotIndex - 1) / 96) === Math.floor(slotIndex / 96); + + const isLastSlot = schedule && + !schedule.time_indices.includes(slotIndex + 1) && + Math.floor((slotIndex + 1) / 96) === Math.floor(slotIndex / 96); + + return ( + <div + key={slotIndex} + className={`p-2 border ${schedule + ? `${getColorForTitle(schedule.title)} text-white + ${!isFirstSlot && !isLastSlot ? 'border-t-0 border-b-0' : ''} + ${!isFirstSlot ? 'border-t-0' : ''} + ${!isLastSlot ? 'border-b-0' : ''}` + : isSelected + ? "bg-primary-100 border-primary-300" + : "bg-grayscale-50" + } cursor-pointer`} + onClick={() => handleSlotClick(slotIndex)} + > + {isFirstSlot ? schedule?.title : ""} + </div> + ); + }; + + const filterTimeSlots = (time) => { + const hour = parseInt(time.split(":")[0]); + return showAllTimeSlot || (hour >= 8 && hour <= 18); + }; return ( <div className="min-h-screen bg-grayscale-50"> @@ -311,11 +357,23 @@ const SchedulePage = () => { </div> </label> </div> - + + {/* 더보기 버튼 */} + <div className="flex justify-center mt-4"> + <Label + theme="indigo" + size="lg" + className="cursor-pointer" + onClick={() => setShowAllTimeSlot(!showAllTimeSlot)} + > + {showAllTimeSlot ? "시간 접기" : "전체 시간 보기"} + </Label> + </div> + {/* Schedule Grid */} <div className="p-4 pb-[210px]"> <div className="overflow-auto scrollbar-hide"> - <div className="w-[100vw] tablet:w-[960px] grid grid-cols-[64px,repeat(7,1fr)] gap-2"> + <div className="w-[100vw] tablet:w-[960px] grid grid-cols-[64px,repeat(7,1fr)] gap-0"> {/* Header */} <div className="min-w-[54px] p-2 font-bold text-center bg-grayscale-200 select-none"> Time @@ -325,43 +383,27 @@ const SchedulePage = () => { {day} </div> ))} - + {/* Time Slots */} - {timeSlots.map((time, rowIndex) => ( - <React.Fragment key={rowIndex}> - {/* Time Column */} - <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 = selectedSlots.includes(slotIndex); - const schedule = schedules.find((s) => - s.time_indices.includes(slotIndex) - ); - - return ( - <div - key={slotIndex} - className={`p-2 border rounded ${ - schedule - ? `${getColorForTitle(schedule.title)} text-white` - : isSelected - ? "bg-primary-100 border-primary-300" - : "bg-grayscale-50" - } cursor-pointer`} - onClick={() => handleSlotClick(slotIndex)} - > - {schedule ? schedule.title : ""} - </div> - ); - })} - </React.Fragment> - ))} + {timeSlots.map((time, rowIndex) => { + if (!filterTimeSlots(time)) return null; + + return ( + <React.Fragment key={rowIndex}> + <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; + return renderTimeSlot(slotIndex, rowIndex, colIndex); + })} + </React.Fragment> + ); + })} </div> </div> </div> - + {/* Sticky Container in Edit Mode */} {isEditMode && ( <div className="fixed bottom-0 right-0 flex items-center justify-center w-full z-10">