From 07fa83cd7bad2f52ba1da8070953479bb2798794 Mon Sep 17 00:00:00 2001 From: halion Date: Wed, 12 Mar 2025 23:16:22 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EC=A0=95?= =?UTF-8?q?=EC=83=81=ED=99=94=20-=20All=20Class=EC=97=90=EC=84=9C=EB=8F=84?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=9D=84=EC=9B=8C=EC=A3=BC=EA=B8=B0=20(#211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: All Class에서도 기본 미리보기 띄워주기 * fix: 검색 기준 변경 - 충족 글자수 버그 수정 - All Class에서도 빈 문자열 검색 가능하도록 --- .../AddClass/CourseSearchDataList.tsx | 11 ++--------- .../timetable/LectureBottomSheet/AddClass/index.tsx | 8 ++------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/components/timetable/LectureBottomSheet/AddClass/CourseSearchDataList.tsx b/src/components/timetable/LectureBottomSheet/AddClass/CourseSearchDataList.tsx index eec08237..7ea00286 100644 --- a/src/components/timetable/LectureBottomSheet/AddClass/CourseSearchDataList.tsx +++ b/src/components/timetable/LectureBottomSheet/AddClass/CourseSearchDataList.tsx @@ -46,14 +46,7 @@ const CourseSearchDataList = forwardRef(({ year, semester if (hasNextPage && !isFetching) fetchNextPage() }) - if ((searchQuery.category === undefined || searchQuery.category === 'All Class') && !searchQuery.keyword) - // is initial state - return ( -
- Enter keywords to search (e.g., course name, professor name, or course number) -
- ) - + // TODO: Vanilla-extract & Pagination 컴포넌트 사용 로직으로 변경 if (searchData.length) return (
(({ year, semester {searchData.map((data, index) => ( ))} -
+
) diff --git a/src/components/timetable/LectureBottomSheet/AddClass/index.tsx b/src/components/timetable/LectureBottomSheet/AddClass/index.tsx index 66dcdeb6..ccf1af7e 100644 --- a/src/components/timetable/LectureBottomSheet/AddClass/index.tsx +++ b/src/components/timetable/LectureBottomSheet/AddClass/index.tsx @@ -69,12 +69,8 @@ const AddClass = ({ timetableId, year, semester }: AddClassProps) => { const handleSearchBoxOnSubmit = useCallback( (queryKeyword: string) => { - if ( - (queryKeyword.length === 0 && searchQuery.category !== undefined && searchQuery.category !== 'All Class') || - queryKeyword.length > 2 - ) { - // All Class가 아닌 Category에서 빈 글자 입력 - // 또는 2글자 이상 검색 + if (queryKeyword.length !== 1) { + // 글자수 충족 시 search({ ...searchQuery, keyword: queryKeyword }) } else toast.custom(() => ) From 2b340a9e5c1f78a588d8e8ce3797627b51675ebf Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Thu, 13 Mar 2025 09:29:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=ED=99=88=20banner=20api=20?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=94=EA=BE=B8=EA=B3=A0=20=EB=B0=94=EB=80=90=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=B4=EC=9A=94.=20(#210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 홈 banner api 를 바꾸고 바뀐 디자인으로 변경해요. * feat: home api 변경 * feat: 조작 버튼 없애기 --- src/domain/Banner/queries.ts | 3 ++ src/domain/Banner/useReadHomeBanner.ts | 18 +++++++ src/domain/HomeCalendar/queries.ts | 3 ++ src/features/HomeBanner/index.tsx | 75 +++++++++----------------- src/features/HomeBanner/style.css.ts | 57 ++++++-------------- 5 files changed, 63 insertions(+), 93 deletions(-) create mode 100644 src/domain/Banner/queries.ts create mode 100644 src/domain/Banner/useReadHomeBanner.ts create mode 100644 src/domain/HomeCalendar/queries.ts diff --git a/src/domain/Banner/queries.ts b/src/domain/Banner/queries.ts new file mode 100644 index 00000000..e8e4736c --- /dev/null +++ b/src/domain/Banner/queries.ts @@ -0,0 +1,3 @@ +export const BANNER_QUERY_KEY = { + banner: () => ['banner'] as const, +} diff --git a/src/domain/Banner/useReadHomeBanner.ts b/src/domain/Banner/useReadHomeBanner.ts new file mode 100644 index 00000000..d35b6376 --- /dev/null +++ b/src/domain/Banner/useReadHomeBanner.ts @@ -0,0 +1,18 @@ +import { queryOptions, useSuspenseQuery } from '@tanstack/react-query' + +import { useAsyncRead } from '@/common/hooks/useAsyncRead' +import { BANNER_QUERY_KEY } from '@/domain/Banner/queries' +import { kuKeyClient } from '@/packages/api' + +export const useQueryHomeBanner = () => { + const read = useAsyncRead(kuKeyClient.api.BannerApi.bannerGet) + return queryOptions({ + queryKey: BANNER_QUERY_KEY.banner(), + queryFn: () => read(), + }) +} + +export const useReadHomeBanner = () => { + const query = useQueryHomeBanner() + return useSuspenseQuery(query) +} diff --git a/src/domain/HomeCalendar/queries.ts b/src/domain/HomeCalendar/queries.ts new file mode 100644 index 00000000..5aff84bf --- /dev/null +++ b/src/domain/HomeCalendar/queries.ts @@ -0,0 +1,3 @@ +export const HOME_CALENDAR_QUERY_KEY = { + base: () => ['home-calendar'] as const, +} diff --git a/src/features/HomeBanner/index.tsx b/src/features/HomeBanner/index.tsx index 2db85a0c..9cc64344 100644 --- a/src/features/HomeBanner/index.tsx +++ b/src/features/HomeBanner/index.tsx @@ -2,26 +2,21 @@ import Autoplay from 'embla-carousel-autoplay' import useEmblaCarousel, { UseEmblaCarouselType } from 'embla-carousel-react' import { useCallback, useEffect, useState } from 'react' import { HiChevronLeft, HiChevronRight } from 'react-icons/hi' -import { useNavigate } from 'react-router-dom' import * as s from './style.css' -import { useGetBannerImages } from '@/api/hooks/calendar' -import KUkeyLogo from '@/assets/Ku-key_Big.png' -import { Button } from '@/ui/Button' +import { useReadHomeBanner } from '@/domain/Banner/useReadHomeBanner' import { Typography } from '@/ui/Typography' -import { useAuth } from '@/util/auth/useAuth' import { usePrevNextButtons } from '@/util/carousel-button' +import { useMediaQueryByName } from '@/util/hooks/useMediaQueryByName' const HomeBanner = () => { - const { authState } = useAuth() - const navigate = useNavigate() - const { data: banners } = useGetBannerImages() + const { data: banners } = useReadHomeBanner() - const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: 'center' }, [Autoplay()]) + const isMobile = useMediaQueryByName('smDown') + const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: 'center' }, isMobile ? [Autoplay()] : []) const [currentSlide, setCurrentSlide] = useState(0) - const logSlidesInView = useCallback((emblaApi: UseEmblaCarouselType[1]) => { if (emblaApi) { const currentSlideIndex = emblaApi.internalEngine().index.get() @@ -32,7 +27,8 @@ const HomeBanner = () => { const { onPrevButtonClick, onNextButtonClick, onButtonAutoplayClick } = usePrevNextButtons(emblaApi) useEffect(() => { - if (emblaApi) emblaApi.on('select', logSlidesInView) + if (!emblaApi) return + emblaApi.on('select', logSlidesInView) }, [emblaApi, logSlidesInView]) return ( @@ -41,49 +37,26 @@ const HomeBanner = () => {
{/* embla__container */}
- {banners?.map(({ imageUrl }, index) => ( -
- {`banner-img-${index}`} -
+ {banners?.map(({ imageUrl, id }, index) => ( + {`banner-img-${index}`} ))}
-
-
- {!authState && ( -
-
- KUkeyLogo - - For KU Exchange Students - -
-
- - -
-
- )} -
- onButtonAutoplayClick(onPrevButtonClick)} /> - - {currentSlide + 1}/{banners?.length} - - onButtonAutoplayClick(onNextButtonClick)} /> -
-
- - {currentSlide + 1}/ - - - {banners?.length} - -
-
+ +
+ onButtonAutoplayClick(onPrevButtonClick)} /> + + {currentSlide + 1}/{banners?.length} + + onButtonAutoplayClick(onNextButtonClick)} /> +
+
+ + {currentSlide + 1}/ + + + {banners?.length} +
) diff --git a/src/features/HomeBanner/style.css.ts b/src/features/HomeBanner/style.css.ts index d8167b13..39764aac 100644 --- a/src/features/HomeBanner/style.css.ts +++ b/src/features/HomeBanner/style.css.ts @@ -1,14 +1,16 @@ import { style } from '@vanilla-extract/css' import { f } from '@/style' -import { vars } from '@/theme/theme.css' export const Wrapper = style([ f.flex, f.wFull, f.hFull, f.pRelative, - { borderBottom: `1.5px solid ${vars.color.lightGray2}` }, + f.directionColumn, + f.alignCenter, + { gap: '2.5rem' }, + f.smDown({ gap: '0' }), ]) export const RelativeWrapper = style([ @@ -26,54 +28,25 @@ export const EmblaViewport = style([f.overflowHidden]) export const EmblaContainer = style([f.flex, { backfaceVisibility: 'hidden' }]) -export const EmblaSlide = style({ - flex: '0 0 100%', - minWidth: 0, - paddingLeft: '0', - width: '100%', -}) - -export const LoginWrapper = style([ - f.flex, - f.directionColumn, - f.alignEnd, - { width: '64.125rem', gap: '4.25rem' }, - f.smDown({ height: '100%', justifyContent: 'flex-end', padding: '0.875rem 1.25rem' }), -]) - -export const LoginBox = style([ - f.flex, - f.directionColumn, - f.alignCenter, +export const EmblaSlide = style([ + f.smDown({ borderRadius: 0, width: 'auto', height: 'auto' }), { - gap: '2.5rem', - padding: '2.5rem', - borderRadius: '30px', - border: `1px solid ${vars.color.lightGray2}`, - background: 'rgba(255, 255, 255, 0.80)', - backdropFilter: 'blur(2px)', + flex: '0 0 auto', + minWidth: 0, + marginLeft: '1.25rem', + display: 'block', + width: '50rem', + height: '25rem', + borderRadius: '20px', }, - f.smDown({ display: 'none' }), -]) - -export const LoginTitle = style([ - f.flex, - f.wFull, - f.directionColumn, - f.alignCenter, - { gap: '1.25rem', alignSelf: 'stretch', width: '23.4375rem' }, ]) -export const ButtonWrapper = style([f.flex, f.wFull, f.alignCenter, { gap: '0.9375rem' }]) - export const CarouselButton = style([ f.flexCenter, - f.pAbsolute, { - bottom: '2.5rem', + display: 'none', padding: '0.625rem 1.5rem', gap: '1.25rem', - alignSelf: 'center', borderRadius: '99px', backgroundColor: 'rgba(0, 0, 0, 0.30)', }, @@ -88,7 +61,7 @@ export const Icon = style([ export const MobileCarouselButton = style([ f.flexCenter, - f.smDown({ display: 'flex', alignItems: 'flex-end' }), + f.smDown({ display: 'none', alignItems: 'flex-end' }), { display: 'none', padding: '0.1875rem 0.5rem', From 66caa0c49b01060964fae71a6090754360a61703 Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Thu, 13 Mar 2025 09:30:01 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20home=EC=97=90=EC=84=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8A=94=20=EC=8B=9C=EA=B0=84=ED=91=9C,=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EA=B0=95=EC=9D=98=20api=20=EB=A5=BC=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=ED=95=B4=EC=9A=94.=20(#209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: home에서 사용될 api 를 작성해요. * feat: 현재의 스케줄로 스크롤 시켜요 * feat: 추천 강의평을 보여줘요. * feat: 빈 시간표 스케줄 대응 --- src/App.tsx | 2 +- .../Course/components/CourseItem/index.tsx | 22 ++--- .../Course/components/RateTag/index.tsx | 2 +- .../Course/components/SemesterTag/index.tsx | 5 +- .../hooks/useReadCourseRecommendation.ts | 21 ++++ src/domain/Course/queries.ts | 4 + .../Timetable/hooks/useReadTodayTimetable.ts | 20 ++++ src/domain/Timetable/queries.ts | 9 ++ .../components/FakeTimetable/index.tsx | 2 +- .../components/RecommendedLecture/index.tsx | 14 +-- .../components/Schedule/index.tsx | 97 ++++++++++++------- .../components/ScheduleItem/index.tsx | 68 +++++++------ .../components/ScheduleItem/style.css.ts | 4 +- .../hooks/useScrollToCurrentSchedule.ts | 27 ++++++ 14 files changed, 201 insertions(+), 96 deletions(-) create mode 100644 src/domain/Course/hooks/useReadCourseRecommendation.ts create mode 100644 src/domain/Course/queries.ts create mode 100644 src/domain/Timetable/hooks/useReadTodayTimetable.ts create mode 100644 src/features/HomeContents/hooks/useScrollToCurrentSchedule.ts diff --git a/src/App.tsx b/src/App.tsx index dc333e6e..1efc83ee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,7 @@ const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, - refetchOnMount: 'always', + refetchOnMount: true, refetchOnReconnect: true, refetchOnWindowFocus: true, staleTime: 1000 * 10, // 10 seconds diff --git a/src/domain/Course/components/CourseItem/index.tsx b/src/domain/Course/components/CourseItem/index.tsx index 14e478b7..34316171 100644 --- a/src/domain/Course/components/CourseItem/index.tsx +++ b/src/domain/Course/components/CourseItem/index.tsx @@ -2,24 +2,20 @@ import * as s from './style.css' import RateTag from '@/domain/Course/components/RateTag' import SemesterTag from '@/domain/Course/components/SemesterTag' +import { CommonCourseResponseDto } from '@/packages/api/ku-key/models' import { Typography } from '@/ui/Typography' +import { numberToSemester } from '@/util/timetableUtil' -type Props = { - title: string - professor: string - courseRate: string - semester: string - //TODO: 추천 강의 id 받아야 함. -} +type Props = Pick -const CourseItem = ({ title, professor, courseRate, semester }: Props) => { +const CourseItem = ({ courseName, professorName, totalRate, semester, year }: Props) => { + const semesterText = numberToSemester[Number(semester)] return (
- +
- 🍪 - +
@@ -28,14 +24,14 @@ const CourseItem = ({ title, professor, courseRate, semester }: Props) => { mobileTypography="miniTag1M" style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%' }} > - {title} + {courseName}
Prof. - {professor} + {professorName}
diff --git a/src/domain/Course/components/RateTag/index.tsx b/src/domain/Course/components/RateTag/index.tsx index 5e17bc43..945127e2 100644 --- a/src/domain/Course/components/RateTag/index.tsx +++ b/src/domain/Course/components/RateTag/index.tsx @@ -11,7 +11,7 @@ const RateTag = ({ rate }: Props) => { return (
- {rate} + {rate.toFixed(1)}
) diff --git a/src/domain/Course/components/SemesterTag/index.tsx b/src/domain/Course/components/SemesterTag/index.tsx index cc220562..1b645b19 100644 --- a/src/domain/Course/components/SemesterTag/index.tsx +++ b/src/domain/Course/components/SemesterTag/index.tsx @@ -4,13 +4,14 @@ import { Typography } from '@/ui/Typography' type Props = { semester: string + year: string } -const SemesterTag = ({ semester }: Props) => { +const SemesterTag = ({ semester, year }: Props) => { return (
- {semester} + {`${year} - ${semester}`}
) diff --git a/src/domain/Course/hooks/useReadCourseRecommendation.ts b/src/domain/Course/hooks/useReadCourseRecommendation.ts new file mode 100644 index 00000000..660ca7d0 --- /dev/null +++ b/src/domain/Course/hooks/useReadCourseRecommendation.ts @@ -0,0 +1,21 @@ +import { queryOptions, useSuspenseQuery } from '@tanstack/react-query' + +import { useAsyncRead } from '@/common/hooks/useAsyncRead' +import { COURSE_QUERY_KEY } from '@/domain/Course/queries' +import { kuKeyClient } from '@/packages/api' +import { CourseRecommendationGetRequestParams } from '@/packages/api/ku-key/api/course-api' + +type Props = CourseRecommendationGetRequestParams + +export const useQueryCourseRecommendation = ({ limit }: Props) => { + const read = useAsyncRead(kuKeyClient.api.CourseApi.courseRecommendationGet) + return queryOptions({ + queryKey: COURSE_QUERY_KEY.recommendation(limit), + queryFn: () => read({ limit }), + }) +} + +export const useReadCourseRecommendation = ({ limit }: Props) => { + const read = useQueryCourseRecommendation({ limit }) + return useSuspenseQuery(read) +} diff --git a/src/domain/Course/queries.ts b/src/domain/Course/queries.ts new file mode 100644 index 00000000..0ff10848 --- /dev/null +++ b/src/domain/Course/queries.ts @@ -0,0 +1,4 @@ +export const COURSE_QUERY_KEY = { + base: () => ['course'], + recommendation: (limit: number) => [...COURSE_QUERY_KEY.base(), 'recommendation', limit], +} diff --git a/src/domain/Timetable/hooks/useReadTodayTimetable.ts b/src/domain/Timetable/hooks/useReadTodayTimetable.ts new file mode 100644 index 00000000..fc253ba9 --- /dev/null +++ b/src/domain/Timetable/hooks/useReadTodayTimetable.ts @@ -0,0 +1,20 @@ +import { queryOptions, useSuspenseQuery } from '@tanstack/react-query' + +import { useAsyncRead } from '@/common/hooks/useAsyncRead' +import { TIMETABLE_QUERY_KEY } from '@/domain/Timetable/queries' +import { kuKeyClient } from '@/packages/api' +import { TimetableTodayGetRequestParams } from '@/packages/api/ku-key/api/timetable-api' + +type Props = TimetableTodayGetRequestParams +export const useQueryTodayTimetable = ({ semester, year }: Props) => { + const read = useAsyncRead(kuKeyClient.api.TimetableApi.timetableTodayGet) + return queryOptions({ + queryKey: TIMETABLE_QUERY_KEY.today({ semester, year }), + queryFn: () => read({ semester, year }), + }) +} + +export const useReadTodayTimetable = ({ semester, year }: Props) => { + const read = useQueryTodayTimetable({ semester, year }) + return useSuspenseQuery(read) +} diff --git a/src/domain/Timetable/queries.ts b/src/domain/Timetable/queries.ts index 775d8d96..ce0acf65 100644 --- a/src/domain/Timetable/queries.ts +++ b/src/domain/Timetable/queries.ts @@ -1,7 +1,10 @@ import { CourseSearchProps } from '@/domain/Timetable/hooks/useSearchCourse' +import { TimetableTodayGetRequestParams } from '@/packages/api/ku-key/api/timetable-api' export const TIMETABLE_QUERY_KEY = { + base: () => ['timetable'] as const, search: ({ year, semester, category, classification, keyword }: CourseSearchProps) => [ + ...TIMETABLE_QUERY_KEY.base(), 'courseSearchResult', year, semester, @@ -9,4 +12,10 @@ export const TIMETABLE_QUERY_KEY = { classification, keyword, ], + today: ({ year, semester }: TimetableTodayGetRequestParams) => [ + ...TIMETABLE_QUERY_KEY.base(), + 'today', + year, + semester, + ], } diff --git a/src/features/HomeContents/components/FakeTimetable/index.tsx b/src/features/HomeContents/components/FakeTimetable/index.tsx index 48d8ef88..44a1359b 100644 --- a/src/features/HomeContents/components/FakeTimetable/index.tsx +++ b/src/features/HomeContents/components/FakeTimetable/index.tsx @@ -14,7 +14,7 @@ const FakeTimetable = () => { >
diff --git a/src/features/HomeContents/components/RecommendedLecture/index.tsx b/src/features/HomeContents/components/RecommendedLecture/index.tsx index 2f6c5a9c..56ef1e02 100644 --- a/src/features/HomeContents/components/RecommendedLecture/index.tsx +++ b/src/features/HomeContents/components/RecommendedLecture/index.tsx @@ -1,9 +1,11 @@ import * as s from './style.css' import CourseItem from '@/domain/Course/components/CourseItem' +import { useReadCourseRecommendation } from '@/domain/Course/hooks/useReadCourseRecommendation' import { Typography } from '@/ui/Typography' const RecommendedLecture = () => { + const { data: courseRecommendation } = useReadCourseRecommendation({ limit: 4 }) return (
@@ -12,15 +14,9 @@ const RecommendedLecture = () => {
- - - - + {courseRecommendation.map(course => ( + + ))}
) diff --git a/src/features/HomeContents/components/Schedule/index.tsx b/src/features/HomeContents/components/Schedule/index.tsx index 39dfbf75..69018b6c 100644 --- a/src/features/HomeContents/components/Schedule/index.tsx +++ b/src/features/HomeContents/components/Schedule/index.tsx @@ -1,47 +1,72 @@ +import { useMemo, useRef } from 'react' + import * as s from './style.css' +import { useReadTodayTimetable } from '@/domain/Timetable/hooks/useReadTodayTimetable' import ScheduleItem from '@/features/HomeContents/components/ScheduleItem' +import { useScrollToCurrentSchedule } from '@/features/HomeContents/hooks/useScrollToCurrentSchedule' +import { TodayCourseDto } from '@/packages/api/ku-key/models' +import { getCurSemester } from '@/util/timetableUtil' const HomeContentsSchedule = () => { + const scrollRef = useRef(null) + + const { year, semester } = getCurSemester() + const { data: todayTimetable } = useReadTodayTimetable({ + semester: semester.toString(), + year: year.toString(), + }) + + const { courses = [], schedules = [] } = todayTimetable + + const normalizeScheduleItemAsCourse = schedules.map(schedule => ({ + ...schedule, + courseName: schedule.scheduleName, + professorName: '', + classroom: schedule.location, + startTime: schedule.startTime, + endTime: schedule.endTime, + })) + + const timeToTodayDate = (timeString: string) => { + const today = new Date() + const todayStr = today.toISOString().split('T')[0] // YYYY-MM-DD 형식 + const dateTimeStr = `${todayStr}T${timeString}` // YYYY-MM-DDT12:00:00 형식 + + return new Date(dateTimeStr) + } + + const todaySchedules = [...courses, ...normalizeScheduleItemAsCourse] + .filter( + (schedule): schedule is typeof schedule & { startTime: Date; endTime: Date } => + Boolean(schedule.startTime) && Boolean(schedule.endTime), + ) + .map(schedule => ({ + ...schedule, + startTime: timeToTodayDate(schedule.startTime), + endTime: timeToTodayDate(schedule.endTime), + })) + .sort((a, b) => a.startTime.getTime() - b.startTime.getTime()) + + const currentScheduleIndex = todaySchedules.findIndex(schedule => { + const now = new Date() + return now >= schedule.startTime && now <= schedule.endTime + }) + + useScrollToCurrentSchedule({ scrollRef, currentScheduleIndex }) + + const renderSchedule = useMemo(() => { + if (!todaySchedules.length) return
No Schedule for today!
+ return todaySchedules.map((schedule, index) => ( + + )) + }, [todaySchedules]) + return (
-
- - - - - +
+ {renderSchedule}
diff --git a/src/features/HomeContents/components/ScheduleItem/index.tsx b/src/features/HomeContents/components/ScheduleItem/index.tsx index af765dab..b7e5ad98 100644 --- a/src/features/HomeContents/components/ScheduleItem/index.tsx +++ b/src/features/HomeContents/components/ScheduleItem/index.tsx @@ -2,19 +2,19 @@ import { HiCheckCircle, HiClock, HiLocationMarker } from 'react-icons/hi' import * as s from './style.css' +import { TodayCourseDto } from '@/packages/api/ku-key/models' import { vars } from '@/theme/theme.css' import { Typography } from '@/ui/Typography' -type Props = { - title: string - professor: string - location: string +type Props = Omit & { startTime: Date endTime: Date + isLast?: boolean } -const ScheduleItem = ({ title, professor, location, startTime, endTime }: Props) => { +const ScheduleItem = ({ courseName, professorName, classroom, startTime, endTime, isLast }: Props) => { const startTimeText = startTime.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) + const endTimeText = endTime.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) const currentTime = new Date() @@ -25,41 +25,47 @@ const ScheduleItem = ({ title, professor, location, startTime, endTime }: Props) ) return ( -
+
-
-
-
-
+ {!isLast && ( +
+
+
+
+ )}
- {title} + {courseName} -
- - Prof. - - - {professor} - -
+ {professorName && ( +
+ + Prof. + + + {professorName} + +
+ )}
-
- - - {location} - -
+ {classroom && ( +
+ + + {classroom} + +
+ )}
diff --git a/src/features/HomeContents/components/ScheduleItem/style.css.ts b/src/features/HomeContents/components/ScheduleItem/style.css.ts index 993dce73..1fece0e3 100644 --- a/src/features/HomeContents/components/ScheduleItem/style.css.ts +++ b/src/features/HomeContents/components/ScheduleItem/style.css.ts @@ -5,7 +5,7 @@ import { vars } from '@/theme/theme.css' export const ItemWrapper = style([f.flex, f.alignStart, f.wFull, { gap: '1.25rem' }, f.smDown({ gap: '0.625rem' })]) -export const CheckWrapper = style([f.flex, f.alignCenter, f.directionColumn, f.hFull]) +export const CheckWrapper = style([f.flex, f.alignCenter, f.justifyStart, f.directionColumn, f.hFull]) export const Wrapper = style([ f.flex, @@ -26,7 +26,7 @@ export const Location = style([f.flex, f.directionRow, f.alignCenter, { gap: '0. export const IndicatorIcon = style([ f.smDown({ width: '1.125rem', height: '1.125rem' }), - { width: '2.25rem', height: '2.25rem', flex: 1 }, + { width: '2.25rem', height: '2.25rem' }, ]) export const Icon = style([ diff --git a/src/features/HomeContents/hooks/useScrollToCurrentSchedule.ts b/src/features/HomeContents/hooks/useScrollToCurrentSchedule.ts new file mode 100644 index 00000000..9b6a2157 --- /dev/null +++ b/src/features/HomeContents/hooks/useScrollToCurrentSchedule.ts @@ -0,0 +1,27 @@ +import { useEffect } from 'react' + +type Props = { + scrollRef: React.RefObject + currentScheduleIndex: number +} + +export const useScrollToCurrentSchedule = ({ scrollRef, currentScheduleIndex }: Props) => { + useEffect(() => { + if (!scrollRef.current || currentScheduleIndex === -1) return + + // 현재 스케줄 요소 찾기 + const currentScheduleElement = scrollRef.current.children[currentScheduleIndex] + + if (!currentScheduleElement) return + + // getBoundingClientRect() 메서드 사용 + const rect = currentScheduleElement.getBoundingClientRect() + const containerRect = scrollRef.current.getBoundingClientRect() + + // 컨테이너 내에서의 상대적 위치 계산 + const top = rect.top - containerRect.top + scrollRef.current.scrollTop + + // 스크롤 이동 + scrollRef.current.scrollTo({ top, behavior: 'smooth' }) + }, [currentScheduleIndex, scrollRef]) +}