From 64bc46ce3e1e9c46fe632591be97a894ff8f4d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=8C=80=ED=9D=AC?= <joedaehui@ajou.ac.kr> Date: Sun, 8 Dec 2024 15:57:27 +0900 Subject: [PATCH 1/6] =?UTF-8?q?stlye:=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=B3=84=EB=A1=9C=20=EB=8B=A4=EB=A5=B8=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 80 +++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 35a53ba..221a813 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -41,8 +41,52 @@ const days = ["월", "화", "수", "목", "금", "토", "일"]; // 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 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' +]; const SchedulePage = () => { const timeSlots = generateTimeSlots(); const [schedules, setSchedules] = useState([]); @@ -52,6 +96,17 @@ const SchedulePage = () => { const [selectedSlots, setSelectedSlots] = useState([]); const [newTitle, setNewTitle] = useState(""); const [isFixed, setIsFixed] = useState(true); + const [titleColorMap, setTitleColorMap] = useState(new Map()); + + const getColorForTitle = (title) => { + if (!titleColorMap.has(title)) { + const newColor = colorClasses[titleColorMap.size % colorClasses.length]; + setTitleColorMap(prev => new Map(prev).set(title, newColor)); + return newColor; + } + return titleColorMap.get(title); + }; + useEffect(() => { // API @@ -70,6 +125,19 @@ const SchedulePage = () => { // setSchedules(dummySchedules); }, []); + useEffect(() => { + const newColorMap = new Map(); + schedules.forEach(schedule => { + if (!newColorMap.has(schedule.title)) { + newColorMap.set( + schedule.title, + colorClasses[newColorMap.size % colorClasses.length] + ); + } + }); + setTitleColorMap(newColorMap); + }, [schedules]); + const handleSlotClick = async (timeIdx) => { if (!isEditMode) return; @@ -216,7 +284,7 @@ const SchedulePage = () => { <div className={`relative w-12 h-6 rounded-full transition-colors ${ isEditMode ? "bg-primary-500" : "bg-grayscale-300" - }`} + }`} onClick={() => { setIsEditMode((prev) => !prev); setSelectedSlots([]); @@ -226,7 +294,7 @@ const SchedulePage = () => { <div className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${ isEditMode ? "translate-x-6" : "" - }`} + }`} ></div> </div> </label> @@ -386,11 +454,11 @@ const SchedulePage = () => { key={slotIndex} className={`p-2 border rounded ${ schedule - ? "bg-primary-300 text-white cursor-not-allowed" + ? `${getColorForTitle(schedule.title)} text-white` : isSelected - ? "bg-primary-100 border-primary-300" - : "bg-grayscale-50 cursor-pointer" - }`} + ? "bg-primary-100 border-primary-300" + : "bg-grayscale-50 cursor-pointer" + }`} onClick={() => handleSlotClick(slotIndex)} > {schedule ? schedule.title : ""} -- GitLab From 2f623b4591d71d8c31012a06a686a8b518be6a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=8C=80=ED=9D=AC?= <joedaehui@ajou.ac.kr> Date: Sun, 8 Dec 2024 16:02:55 +0900 Subject: [PATCH 2/6] =?UTF-8?q?style:=20=EC=8B=9C=EA=B0=84=20=EC=9D=B8?= =?UTF-8?q?=EB=8D=B1=EC=8A=A4=20=EC=8B=A4=EC=A0=9C=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 132 ++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 60 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 221a813..802de20 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -22,53 +22,53 @@ 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", + } +]; const colorClasses = [ 'bg-indigo-300', @@ -98,16 +98,6 @@ const SchedulePage = () => { const [isFixed, setIsFixed] = useState(true); const [titleColorMap, setTitleColorMap] = useState(new Map()); - const getColorForTitle = (title) => { - if (!titleColorMap.has(title)) { - const newColor = colorClasses[titleColorMap.size % colorClasses.length]; - setTitleColorMap(prev => new Map(prev).set(title, newColor)); - return newColor; - } - return titleColorMap.get(title); - }; - - useEffect(() => { // API const initializeSchedules = async () => { @@ -122,7 +112,7 @@ const SchedulePage = () => { initializeSchedules(); // 임시 코드 - // setSchedules(dummySchedules); + setSchedules(dummySchedules); }, []); useEffect(() => { @@ -274,6 +264,28 @@ const SchedulePage = () => { } }; + const getColorForTitle = (title) => { + if (!titleColorMap.has(title)) { + const newColor = colorClasses[titleColorMap.size % colorClasses.length]; + setTitleColorMap(prev => new Map(prev).set(title, newColor)); + return newColor; + } + return titleColorMap.get(title); + }; + + const convertIndexToTime = (timeIndex) => { + const dayIndex = Math.floor(timeIndex / 96); + 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}`; + }; + + return ( <div className="min-h-screen bg-grayscale-50"> {/* Toggle View/Edit Mode */} @@ -321,7 +333,7 @@ const SchedulePage = () => { <strong>선택된 시간:</strong>{" "} {selectedSchedule.time_indices.map((time_idx) => ( <Label key={time_idx} theme="indigo" size="sm"> - {time_idx} + {convertIndexToTime(time_idx)} </Label> ))} </div> @@ -385,7 +397,7 @@ const SchedulePage = () => { <span>선택된 시간:</span> {selectedSlots.map((time_idx) => ( <Label key={time_idx} theme="indigo" size="sm"> - {time_idx} + {convertIndexToTime(time_idx)} </Label> ))} </div> -- GitLab From 1f0133b66cb6e1a87835394e1a37be14c7dfa915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=8C=80=ED=9D=AC?= <joedaehui@ajou.ac.kr> Date: Sun, 8 Dec 2024 16:29:11 +0900 Subject: [PATCH 3/6] =?UTF-8?q?style:=20edit=20mode=20container=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20sticky=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 115 ++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 60 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 802de20..01c851d 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -296,7 +296,7 @@ const SchedulePage = () => { <div className={`relative w-12 h-6 rounded-full transition-colors ${ isEditMode ? "bg-primary-500" : "bg-grayscale-300" - }`} + }`} onClick={() => { setIsEditMode((prev) => !prev); setSelectedSlots([]); @@ -306,18 +306,66 @@ const SchedulePage = () => { <div className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${ isEditMode ? "translate-x-6" : "" - }`} + }`} ></div> </div> </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"> + {/* Header */} + <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 select-none bg-grayscale-200"> + {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> + ))} + </div> + </div> + </div> + {/* Sticky Container in Edit Mode */} {isEditMode && ( - <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`} - > + <div className="fixed bottom-0 right-0 flex items-center justify-center w-full z-10"> + <div className="transform transition-transform w-full max-w-[768px] tablet:rounded-2xl bg-primary-100/90 backdrop-blur-sm p-6 text-center shadow-lg"> {selectedSlots.length === 0 && selectedSchedule ? ( <div className="flex flex-col items-center justify-center w-full"> <h3 className="mb-2 heading-2 text-primary-500">스케줄 정보</h3> @@ -429,59 +477,6 @@ const SchedulePage = () => { </div> </div> )} - - {/* Schedule Grid */} - <div className="p-4"> - <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-grayscale-200 select-none"> - Time - </div> - {days.map((day) => ( - <div - key={day} - className="p-2 font-bold text-center select-none bg-grayscale-200" - > - {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> - ))} - </div> - </div> - </div> </div> ); }; -- GitLab From b15f63c406627313cf5408652edabcbd6794cfd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=8C=80=ED=9D=AC?= <joedaehui@ajou.ac.kr> Date: Sun, 8 Dec 2024 19:01:33 +0900 Subject: [PATCH 4/6] =?UTF-8?q?style:=20=EC=8A=A4=EC=BC=80=EC=A4=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EB=8D=94=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 246 ++++++++++++++++++++++--------------- 1 file changed, 144 insertions(+), 102 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index 01c851d..d072524 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"> -- GitLab From f0b4997cd54cc752f3ed82a88b231e7de1cfe40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EB=8C=80=ED=9D=AC?= <joedaehui@ajou.ac.kr> Date: Sun, 8 Dec 2024 20:28:59 +0900 Subject: [PATCH 5/6] =?UTF-8?q?style:=20cursor-not-allowed=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index d072524..f352172 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -312,7 +312,7 @@ const SchedulePage = () => { <div key={slotIndex} className={`p-2 border ${schedule - ? `${getColorForTitle(schedule.title)} text-white + ? `${getColorForTitle(schedule.title)} text-white cursor-not-allowed ${!isFirstSlot && !isLastSlot ? 'border-t-0 border-b-0' : ''} ${!isFirstSlot ? 'border-t-0' : ''} ${!isLastSlot ? 'border-b-0' : ''}` -- GitLab From c38f1a8b36e2615dac8bb78fa8c717a0c73d9b3f 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: Sun, 8 Dec 2024 20:40:56 +0900 Subject: [PATCH 6/6] =?UTF-8?q?design:=20=EC=8A=A4=EC=BC=80=EB=91=98=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SchedulePage.jsx | 156 ++++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx index d072524..5febb0c 100644 --- a/src/pages/SchedulePage.jsx +++ b/src/pages/SchedulePage.jsx @@ -7,6 +7,7 @@ import { fetchAllSchedules, updateSchedule, } from "../api/schedule"; +import Button from "../components/Button"; const generateTimeSlots = () => { const timeSlots = []; @@ -80,16 +81,16 @@ const days = ["월", "화", "수", "목", "금", "토", "일"]; // ]; const colorClasses = [ - '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', + "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(); @@ -123,13 +124,13 @@ const SchedulePage = () => { initializeSchedules(); - // 임시 코드 + // 임시 코드 // setSchedules(dummySchedules); }, []); useEffect(() => { const newColorMap = new Map(); - schedules.forEach(schedule => { + schedules.forEach((schedule) => { if (!newColorMap.has(schedule.title)) { newColorMap.set( schedule.title, @@ -279,7 +280,7 @@ const SchedulePage = () => { const getColorForTitle = (title) => { if (!titleColorMap.has(title)) { const newColor = colorClasses[titleColorMap.size % colorClasses.length]; - setTitleColorMap(prev => new Map(prev).set(title, newColor)); + setTitleColorMap((prev) => new Map(prev).set(title, newColor)); return newColor; } return titleColorMap.get(title); @@ -291,7 +292,9 @@ const SchedulePage = () => { 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')}`; + const time = `${hour.toString().padStart(2, "0")}:${minute + .toString() + .padStart(2, "0")}`; return `${day} ${time}`; }; @@ -300,26 +303,29 @@ const SchedulePage = () => { const schedule = schedules.find((s) => s.time_indices.includes(slotIndex)); const isSelected = selectedSlots.includes(slotIndex); - const isFirstSlot = schedule && + const isFirstSlot = + schedule && !schedule.time_indices.includes(slotIndex - 1) && Math.floor((slotIndex - 1) / 96) === Math.floor(slotIndex / 96); - const isLastSlot = schedule && + 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 + 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`} + } cursor-pointer`} onClick={() => handleSlotClick(slotIndex)} > {isFirstSlot ? schedule?.title : ""} @@ -336,9 +342,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">Schedule</h1> + <h1 className="heading-2">내 시간표</h1> <label className="flex items-center space-x-3 cursor-pointer"> - <span className="title-1 text-primary-500">Edit Mode</span> + <span className="title-1 text-primary-500">수정 모드</span> <div className={`relative w-12 h-6 rounded-full transition-colors ${ isEditMode ? "bg-primary-500" : "bg-grayscale-300" @@ -360,14 +366,14 @@ const SchedulePage = () => { {/* 더보기 버튼 */} <div className="flex justify-center mt-4"> - <Label - theme="indigo" - size="lg" - className="cursor-pointer" + <Button + theme="white" + size="sm" + className="w-full mx-4 hover:bg-grayscale-50" onClick={() => setShowAllTimeSlot(!showAllTimeSlot)} > {showAllTimeSlot ? "시간 접기" : "전체 시간 보기"} - </Label> + </Button> </div> {/* Schedule Grid */} @@ -379,7 +385,10 @@ const SchedulePage = () => { Time </div> {days.map((day) => ( - <div key={day} className="p-2 font-bold text-center select-none bg-grayscale-200"> + <div + key={day} + className="p-2 font-bold text-center select-none bg-grayscale-200" + > {day} </div> ))} @@ -406,7 +415,7 @@ const SchedulePage = () => { {/* Sticky Container in Edit Mode */} {isEditMode && ( - <div className="fixed bottom-0 right-0 flex items-center justify-center w-full z-10"> + <div className="fixed bottom-0 right-0 z-10 flex items-center justify-center w-full"> <div className="transform transition-transform w-full max-w-[768px] tablet:rounded-2xl bg-primary-100/90 backdrop-blur-sm p-6 text-center shadow-lg"> {selectedSlots.length === 0 && selectedSchedule ? ( <div className="flex flex-col items-center justify-center w-full"> @@ -458,35 +467,50 @@ const SchedulePage = () => { 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" + placeholder="스케줄 제목" + className="w-full p-2 px-4 mb-4 border rounded-full 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">고정 스케줄</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">유동 스케줄</span> - </label> + <div className="flex items-center space-x-4"> + <label className="flex items-center cursor-pointer"> + <input + type="radio" + name="is_fixed" + value={true} + checked={isFixed === true} + onChange={() => setIsFixed(true)} + className="hidden peer" + /> + <div className="flex items-center justify-center w-5 h-5 border-2 border-gray-400 rounded-full peer-checked:border-tertiary-500 peer-checked:bg-tertiary-500"> + <div className="w-2.5 h-2.5 bg-white rounded-full peer-checked:bg-white"></div> + </div> + <span className="ml-2 text-sm font-medium peer-checked:text-tertiary-500"> + 고정 스케줄 + </span> + </label> + + <label className="flex items-center cursor-pointer"> + <input + type="radio" + name="is_fixed" + value={false} + checked={isFixed === false} + onChange={() => setIsFixed(false)} + className="hidden peer" + /> + <div className="flex items-center justify-center w-5 h-5 border-2 border-gray-400 rounded-full peer-checked:border-primary-500 peer-checked:bg-primary-500"> + <div className="w-2.5 h-2.5 bg-white rounded-full peer-checked:bg-white"></div> + </div> + <span className="ml-2 text-sm font-medium peer-checked:text-primary-500"> + 유동 스케줄 + </span> + </label> + </div> </div> - <div className="mb-4 body-1"> - <span>선택된 시간:</span> + <span className="heading-4">선택된 시간</span> + <div className="flex flex-wrap gap-1 p-2 m-2 border rounded-lg body-1 border-primary-500"> {selectedSlots.map((time_idx) => ( - <Label key={time_idx} theme="indigo" size="sm"> + <Label key={time_idx} theme="solid" size="sm"> {convertIndexToTime(time_idx)} </Label> ))} @@ -499,19 +523,23 @@ const SchedulePage = () => { 수정 완료 </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" + <div className="flex justify-center w-full mt-4 space-x-2"> + <Button + theme="indigo" + size="md" + className="flex-1" onClick={() => handleCancelSchedule()} > 취소 - </button> - <button - className="px-4 py-2 font-bold text-white rounded bg-gradient-pink" + </Button> + <Button + theme="pink" + size="md" + className="flex-1" onClick={() => handleCreateSchedule()} > 추가 - </button> + </Button> </div> )} </> -- GitLab