From cd758decb36a6c4a2a9916026ecd6493d80b6e11 Mon Sep 17 00:00:00 2001 From: HyerinJeon Date: Wed, 13 Mar 2024 09:16:55 +0900 Subject: [PATCH 1/7] Fix Landing page Button bug --- src/pages/Landing.tsx | 4 ++-- src/styles/Landing.styles.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index 493c855..aba2e21 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -21,8 +21,8 @@ export default function Landing() { -

언제 어디서나

-

이용권 스케줄 관리를!

+

PT의 모든 것

+

PlanIT에서 쉽고 간편하게

Get Start
diff --git a/src/styles/Landing.styles.ts b/src/styles/Landing.styles.ts index 670bd1d..94421d7 100644 --- a/src/styles/Landing.styles.ts +++ b/src/styles/Landing.styles.ts @@ -30,7 +30,8 @@ export const TitleButtonContainer = styled.div` height: 50%; transform: translate(0, 20%); - gap: 100px; + gap: 80px; + z-index: 2; `; export const Title = styled.div` @@ -47,7 +48,7 @@ export const Title = styled.div` } @media screen and (max-width: ${breakPoints.small}px) { - font-size: 3.5rem; + font-size: 3.3rem; } `; From a3f607e5649be8fd4be2c7b7b6942e650e07b952 Mon Sep 17 00:00:00 2001 From: HyerinJeon Date: Wed, 13 Mar 2024 10:57:45 +0900 Subject: [PATCH 2/7] Design & Feat horizontal scroll in Main Page --- src/pages/Main.tsx | 75 +++++++++++++++++++++++++++----- src/styles/Main.styles.ts | 90 +++++++++++++++++++++++++-------------- src/utils/throttle.ts | 13 ++++++ 3 files changed, 135 insertions(+), 43 deletions(-) create mode 100644 src/utils/throttle.ts diff --git a/src/pages/Main.tsx b/src/pages/Main.tsx index 4b7ed88..8421ae1 100644 --- a/src/pages/Main.tsx +++ b/src/pages/Main.tsx @@ -1,26 +1,79 @@ import * as S from "../styles/Main.styles"; -import { useEffect } from "react"; +import { MouseEvent, useEffect, useRef, useState } from "react"; import { setVH } from "../utils/setVH"; +import { throttle } from "../utils/throttle"; export default function Main() { + const trainerList = [ + { id: "1", imgUrl: "", info: "설명" }, + { id: "2", imgUrl: "", info: "설명" }, + { id: "3", imgUrl: "", info: "" }, + { id: "4", imgUrl: "", info: "" }, + { id: "5", imgUrl: "", info: "" }, + { id: "6", imgUrl: "", info: "" }, + { id: "7", imgUrl: "", info: "" }, + ]; + + const [isDrag, setIsDrag] = useState(false); + const [startX, setStartX] = useState(0); + + const trainerScrollRef = useRef(null); + useEffect(() => { window.addEventListener("resize", setVH); setVH(); }, []); + const handleDragStart = (e: MouseEvent) => { + e.preventDefault(); + if (trainerScrollRef.current) { + setIsDrag(true); + setStartX(e.pageX + trainerScrollRef.current.scrollLeft); + } + }; + + const handleDragEnd = () => { + setIsDrag(false); + }; + + const handleDragMove = (e: MouseEvent) => { + if (isDrag && trainerScrollRef.current) { + const { scrollWidth, clientWidth, scrollLeft } = trainerScrollRef.current; + + trainerScrollRef.current.scrollLeft = startX - e.pageX; + + if (scrollLeft === 0) { + setStartX(e.pageX); + } else if (scrollWidth <= clientWidth + scrollLeft) { + setStartX(e.pageX + scrollLeft); + } + } + }; + + const throttleDragMove = throttle(handleDragMove, 50); + return ( - - - 이용 후기 - - - - 이번주 예약 내역 - - - + + 트레이너 소개 + + {trainerList.map((trainer) => ( + + + + + {trainer.info} + + ))} + + ); } diff --git a/src/styles/Main.styles.ts b/src/styles/Main.styles.ts index 04d9e18..667888e 100644 --- a/src/styles/Main.styles.ts +++ b/src/styles/Main.styles.ts @@ -4,67 +4,93 @@ import { breakPoints } from "./breakPoints"; export const Container = styled.div` display: flex; flex-direction: column; + justify-content: space-between; width: 100%; height: calc((var(--vh, 1vh) * 100) - 90px); `; export const Banner = styled.div` width: 100%; - height: 35%; + height: 40%; border: 1px solid gray; - - @media screen and (max-width: ${breakPoints.medium}px) { - height: 27%; - } `; -export const BottomContainer = styled.div` - position: absolute; - bottom: 0; +export const TrainerContainer = styled.div` display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; width: 100%; height: 50%; + gap: 5%; +`; + +export const Title = styled.div` + font-size: var(--font-size-300); + font-weight: 700; + height: 5%; + padding: 0 5px; @media screen and (max-width: ${breakPoints.medium}px) { - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - height: 57%; - gap: 20px; + font-size: var(--font-size-400); + } + + @media screen and (max-width: ${breakPoints.small}px) { + font-size: var(--font-size-500); } `; -export const HalfContainer = styled.div` +export const ContentsContainer = styled.div` display: flex; - flex-direction: column; - justify-content: flex-end; - width: 48%; - height: 100%; - gap: 20px; + align-items: center; + height: 90%; + padding: 10px; + gap: 15px; + border: 1px solid gray; + overflow-x: scroll; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; - @media screen and (max-width: ${breakPoints.medium}px) { - width: 100%; - gap: 10px; + &::-webkit-scrollbar { + display: none; } `; -export const Title = styled.div` - font-size: var(--font-size-300); - font-weight: 700; +export const Grid = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + min-width: 400px; + height: 80%; + padding: 10px; + border: 1px solid gray; + border-radius: 10px; - @media screen and (max-width: ${breakPoints.medium}px) { - font-size: var(--font-size-400); + @media screen and (max-width: ${breakPoints.small}px) { + min-width: 300px; } +`; + +export const ImageContainer = styled.div` + width: 35%; + height: 50%; + border-radius: 50%; + overflow: hidden; + border: 1px solid gray; @media screen and (max-width: ${breakPoints.small}px) { - font-size: var(--font-size-500); + width: 25%; + height: 30%; } `; -export const ContentsContainer = styled.div` - width: 100%; +export const InfoContainer = styled.div` + width: 57%; height: 80%; + padding: 10px; + font-size: var(--font-size-400); border: 1px solid gray; + + @media screen and (max-width: ${breakPoints.small}px) { + width: 65%; + font-size: var(--font-size-500); + } `; diff --git a/src/utils/throttle.ts b/src/utils/throttle.ts new file mode 100644 index 0000000..fff406b --- /dev/null +++ b/src/utils/throttle.ts @@ -0,0 +1,13 @@ +export const throttle = (func: (args: any) => void, time: number) => { + let throttled: boolean = false; + + return (args: any) => { + if (!throttled) { + throttled = true; + setTimeout(() => { + func(args); + throttled = false; + }, time); + } + }; +}; From f92a3dee33abb8aee42df57bc919057205a4ae73 Mon Sep 17 00:00:00 2001 From: HyerinJeon Date: Wed, 13 Mar 2024 11:16:08 +0900 Subject: [PATCH 3/7] Feat router with UserReservation, TrainerReservation --- src/pages/trainer/Reservation.tsx | 3 +++ src/pages/user/Reservation.tsx | 3 +++ src/pages/user/Schedule.tsx | 8 +++++++- src/routes/router.tsx | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/pages/trainer/Reservation.tsx create mode 100644 src/pages/user/Reservation.tsx diff --git a/src/pages/trainer/Reservation.tsx b/src/pages/trainer/Reservation.tsx new file mode 100644 index 0000000..2bbdc4a --- /dev/null +++ b/src/pages/trainer/Reservation.tsx @@ -0,0 +1,3 @@ +export default function TrainerReservation() { + return
트레이너 예약 관리 페이지
; +} diff --git a/src/pages/user/Reservation.tsx b/src/pages/user/Reservation.tsx new file mode 100644 index 0000000..aaf8240 --- /dev/null +++ b/src/pages/user/Reservation.tsx @@ -0,0 +1,3 @@ +export default function UserReservation() { + return
유저 예약하기 페이지
; +} diff --git a/src/pages/user/Schedule.tsx b/src/pages/user/Schedule.tsx index 986beac..e2c12fb 100644 --- a/src/pages/user/Schedule.tsx +++ b/src/pages/user/Schedule.tsx @@ -1,3 +1,9 @@ +import * as S from "../../styles/Schedule.styles"; + export default function UserSchedule() { - return
유저 스케줄
; + return ( + + + + ); } diff --git a/src/routes/router.tsx b/src/routes/router.tsx index 7990caf..7a47325 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -7,6 +7,8 @@ import MyPage from "../pages/MyPage"; import Main from "../pages/Main"; import UserTicket from "../pages/user/Ticket"; import UserSchedule from "../pages/user/Schedule"; +import UserReservation from "../pages/user/Reservation"; +import TrainerReservation from "../pages/trainer/Reservation"; import TrainerSchedule from "../pages/trainer/Schedule"; import Account from "../pages/admin/Account"; import Trainer from "../pages/admin/Trainer"; @@ -37,12 +39,14 @@ export const router = createBrowserRouter([ children: [ { path: "/user/ticket", element: }, { path: "/user/schedule", element: }, + { path: "/user/reservation", element: }, ], }, { path: "/trainer", element: , children: [ + { path: "/trainer/reservation", element: }, { path: "/trainer/schedule", element: }, ], }, From 2142455248509ff6777198d11824bc04e16f3a99 Mon Sep 17 00:00:00 2001 From: HyerinJeon Date: Wed, 13 Mar 2024 14:00:13 +0900 Subject: [PATCH 4/7] Feat Calendar Component --- src/assets/icon_left-arrow.png | Bin 0 -> 2754 bytes src/assets/icon_right-arrow.png | Bin 0 -> 2272 bytes src/components/Calendar.tsx | 193 ++++++++++++++++++++++++++++++++ src/styles/Calendar.styles.ts | 112 ++++++++++++++++++ 4 files changed, 305 insertions(+) create mode 100644 src/assets/icon_left-arrow.png create mode 100644 src/assets/icon_right-arrow.png create mode 100644 src/components/Calendar.tsx create mode 100644 src/styles/Calendar.styles.ts diff --git a/src/assets/icon_left-arrow.png b/src/assets/icon_left-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..aafa5a63c7e3457dd2b7341256f6c79a8e2726c7 GIT binary patch literal 2754 zcmeHJ`BxKH6rRLJS)^D+5P{Ox4G>1b)5cUGse*t&u*|OE%VVk3q^L7_kH=_Hu-oAc20|JA;BS%qUsIf=l zjvhNsJ9#SpY{E~8$rmoBUP{Ygve=w!S=VpmqvKS5#agC>4szetqy;d4>2< zbxnQ4y(%952X=!b1mv(kNe<6GMS9kxw;Lz~M-}2X^W8)JFrE0To>`n+;5bSLK zjn@w?y-#BqNfcvo#8s!Xg)N?*DIWP`&Y8!h9-&XRXs^0xZ;`P0(zS{Lm-$=MGNc=N zq+eU77rXD{b`OtlK8cey#q|}}30#H8<-$YFTOaw5Lv8E; z%B0@kj|`=CKM^5L$x6ZEMSY~0B2ocutOmtVK1zm{^^)%4g0Eq3F;sgKjkZlN_9lT) zW@Pz(#HP}aqtxBikyNSr(}a3nzD{@J_XSA9FK|W=DXj#!Wt~LeW=2%)N0g`9Y$U>{ z69KCy80F#q6QQjDvrLONmn->g@ahI8Bd`tK%Uoh)Uoh1 zm11mIHpk(&eyUR3I>4-)buR10S6Pou;qdJ4`_C@LKS0c8!?`zyeF%Q4VPT8$jN`cc zHpMhLR@LZ>WdL@?D)H!h{N4c2OqzaG^ApI)k?&7f|6*|NR8wsgW2{jAsBZ*SRX%IJ z+0zJ#{Bs{x2jGzSZ8iT@>%))7BR`HNM^{^nn@;oS?=0=Ao4kozd1SlUVoj&19c1Sr z)W04JWB3f($bEz%U?vT?w-0=!sx-QtKpWZJX)5R@4kouBP&F{?%kf9L#~kxAX!6YG zEJ2NeL#=dHHSmS6RnnonfwZBz_U$T3s|X*rX>?z|uSzoE07+W=+zIl=xW(xLo?v0R zz(BC*vfxa+*2DG-OMuC4|F||&_w?!hZKj&q()pP0`}Iw=b)^%f%Tfg0$G)^Y>T(W! z6&u7s=VJ@JQIdn_eZgx4z1HY0!)$bCF)q z(&jLX8QZ#*qbBgSRvn(qYHY%~yyH39`+^_Ak)yS&H~meLWg%SvI+=@dcp1|Zy<<6Z zP7u|QEd1bP^vu3Ej2UQLi8&wP3aH$SWFEJFuk{?^2q;5l)+{YY!ttP^T7k(a{C1-l zxR6y?@0nOPFvdQU+RtH1Xv_ljiT=6hX$@w80a|2oK3cDyjfSaemQ#Y|c#1xsn(K(* z*rXnt=3exiW^XCjho!th}r^;^Z>TQn*mIEJiV$<{-fT&*{vZ41DTtEkrAk%x?}@zZ>|LF`2d&Q! zh(UE-u_~e08C&t`&f)rjqIg(8kRaQ*;2bQ2|D=?{Nz>* literal 0 HcmV?d00001 diff --git a/src/assets/icon_right-arrow.png b/src/assets/icon_right-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..4a6ee37f00b90a16ba28252cf7509d154dbc83c8 GIT binary patch literal 2272 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w@L)Zt|+Cx8@lv6E*A2M5RPhyD+MT+RZI z$YKTt(Mk|z6yFyG5|k`)jVKAuPb(=;EJ|f4FE7{2%*!rLPAo{(%P&fw{mw>;fq^kK zz$e7@|Ns9$^`n5MAuy9gL>y>ASxJyzFaskKGYcy_2PYS|kg$lPjI6w(l8UODx`vLf zzL}-9jf0E3hnJ6UNK|xOd_q!6YDQ*uPJTggS$SnubwhJ&M^A6x-V3(|32O0`N6=z zmhI`{7*cWT?d{6&DXbF55=~t1Xdj8a?9*$aTYh+Ik*2uHSyn?)mOFuk^UmMjU&>;0 z?&Ffr_pbjHoOl1|%$enF>(-r5JpKDT?_JsYuN5Uf9zEQrcly?=UV%cv!v_On@k^V+UEuPqo~?+q`0dsFXu=AUi$D|cR(Dp=6={({fh-P*Q! zSDw6G`~B^;pzy4B+kC_sPtO0hd=$z$$QM>S0hmU{zepBH+NlNE%@hdVqyQEx0T>dn1Z! zo?9Tvq{fL9a>N8r!?HYCDtQGZ3>yR!SR@$TKvcwX&KXK*Zc)g8jp5P-y?0~Yblqgq zvq;*}z}z=O_rX~+_cLM!4|@}KG{oOAh(2&xG;fA(!UfLFtb5)R9?D^~jx~sGSnl|c zNln}$VOs<5yXm?Qte>A2`>^jwproytM`EgW%coF zt>3?w7F5*RbDVtlUOn;Q-@h6ryZ6s;ZU6qgU-9PO`26A{PyT(KtnBvv|5?Gszxnlx zjz0NUuUF{$*SG%i{V(P~>i3`Re)WI9UXMo*|9k#T{`GqO{(nRf`M+lWTeklth`RRY z-}SH8A?8AjN2PwZM6V{&SfKSFQ|Df|P@pobU7qdNA?W@ASp08uM zaLK;v_m}Tn3+mtPihm)$ckQqA{O$j4TCb@8=J#s;-p{|%->(63SJi*}_G12CSs-_{ z*)RRlZESDz|86di`v0bOas9V_GcMRy+rOT__bC6@^KtwC-Hf(aRR1l^|Kx|e|l zuK)MY+Qhj2zxKZWnLoDK&s49^KF}z&|6X { + if ( + toDay.getFullYear() === compareDay?.getFullYear() && + toDay.getMonth() === compareDay?.getMonth() && + toDay.getDate() === compareDay?.getDate() + ) { + return true; + } + return false; + }; + + // NOTE 날짜 선택 + const handleClickDay = (day: Date) => { + setSelectedDay(day); + }; + + // NOTE 전 달로 이동 + const handleMoveToPrevCalendar = () => { + setCurrentMonth( + new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() - 1, + currentMonth.getDate(), + ), + ); + }; + + // NOTE 다음 달로 이동 + const handleMoveToNextCalendar = () => { + setCurrentMonth( + new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() + 1, + currentMonth.getDate(), + ), + ); + }; + + // NOTE 캘린더 날짜 세팅 + const buildCalendarDays = () => { + // 현재 월의 시작 요일 + const currentMonthStartDate = new Date( + currentMonth.getFullYear(), + currentMonth.getMonth(), + 1, + ).getDay(); + + // 현재 월의 마지막 날짜 + const currentMonthEndDate = new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() + 1, + 0, + ); + + // 이전 월의 마지막 날짜 + const prevMonthEndDate = new Date( + currentMonth.getFullYear(), + currentMonth.getMonth(), + 0, + ); + + // 다음 월의 첫 번째 날짜 + const nextMonthStartDate = new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() + 1, + 1, + ); + + // 이전 월의 마지막 날부터 현재 월의 첫 번째 요일까지의 날짜들을 담음 + const days = Array.from({ length: currentMonthStartDate }, (_, i) => { + return new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() - 1, + prevMonthEndDate.getDate() - i, + ); + }).reverse(); + + // 현재 월의 첫 번째 날부터 마지막 날까지의 날짜들을 추가 + days.push( + ...Array.from( + { length: currentMonthEndDate.getDate() }, + (_, i) => + new Date(currentMonth.getFullYear(), currentMonth.getMonth(), i + 1), + ), + ); + + // 남은 일수 계산 + const remainingDays = 7 - (days.length % 7); + // 남은 일수가 7보다 작으면 다음 달의 시작 날짜부터 remainingDays개수 만큼 날짜 추가 + if (remainingDays < 7) { + days.push( + ...Array.from( + { length: remainingDays }, + (_, i) => + new Date( + nextMonthStartDate.getFullYear(), + nextMonthStartDate.getMonth(), + i + 1, + ), + ), + ); + } + + return days; + }; + + // NOTE 날짜를 통해 el 생성 + const buildCalendarTag = (calendarDays: Date[]) => { + return calendarDays.map((day: Date, index: number) => { + const isCurrentMonth = day.getMonth() === currentMonth.getMonth(); + + const dayClass = !isCurrentMonth + ? "otherMonth" + : day < TODAY + ? "prevDay" + : "futureDay"; + + const selectedDayClass = isSameDay(day, selectedDay) ? "choiceDay" : ""; + + return ( + + { + if (isCurrentMonth) { + handleClickDay(day); + } + }} + > + {day.getDate()} + + + ); + }); + }; + + // NOTE 각 줄에 7일씩 나눠서 뿌리기 + const divideWeek = (calendarTags: JSX.Element[]) => { + return calendarTags.reduce( + (acc: JSX.Element[][], day: JSX.Element, i: number) => { + if (i % 7 === 0) acc.push([day]); + else acc[acc.length - 1].push(day); + return acc; + }, + [], + ); + }; + + const calendarDays = buildCalendarDays(); + const calendarTags = buildCalendarTag(calendarDays); + const calendarRows = divideWeek(calendarTags); + + return ( + + + + 왼쪽 화살표 + +

+ {currentMonth.getFullYear()}년 {currentMonth.getMonth() + 1}월 +

+ + 오른쪽 화살표 + +
+ + + {DAYS_OF_WEEK.map((week, index) => ( + {week} + ))} + + + + {calendarRows.map((day, index) => ( + {day} + ))} + +
+ ); +} diff --git a/src/styles/Calendar.styles.ts b/src/styles/Calendar.styles.ts new file mode 100644 index 0000000..0cac875 --- /dev/null +++ b/src/styles/Calendar.styles.ts @@ -0,0 +1,112 @@ +import styled from "styled-components"; +import { breakPoints } from "./breakPoints"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + justify-content: space-around; + width: 100%; + height: 100%; +`; + +export const TopContainer = styled.div` + display: flex; + justify-content: space-around; + align-items: center; + height: 50px; + font-size: var(--font-size-400); + font-weight: 700; +`; + +export const ArrowIcon = styled.div` + width: 25px; + height: 25px; + cursor: pointer; +`; + +export const WeekContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + height: 50px; + padding: 0 20px; + + @media screen and (max-width: ${breakPoints.medium}px) { + padding: 0 7px; + } + + @media screen and (max-width: ${breakPoints.small}px) { + padding: 0 5px; + } +`; + +export const Week = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + color: var(--main-color-500); + font-size: var(--font-size-400); + font-weight: 700; + + @media screen and (max-width: ${breakPoints.small}px) { + font-size: var(--font-size-500); + } +`; + +export const CalendarContainer = styled.div` + display: flex; + flex-direction: column; + padding: 0 20px; + gap: 10px; + + @media screen and (max-width: ${breakPoints.medium}px) { + padding: 0 7px; + } + + @media screen and (max-width: ${breakPoints.small}px) { + padding: 0 5px; + } +`; + +export const CalendarRows = styled.div` + display: flex; + align-items: center; +`; + +export const CalendarDay = styled.div` + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + width: 100%; + + @media screen and (max-width: ${breakPoints.medium}px) { + padding: 3px; + } + + @media screen and (max-width: ${breakPoints.small}px) { + padding: 0px; + } +`; + +export const Day = styled.div` + padding: 12px 15px; + font-size: var(--font-size-400); + border-radius: 50%; + cursor: pointer; + + @media screen and (max-width: ${breakPoints.small}px) { + font-size: var(--font-size-500); + } + + &.choiceDay { + background-color: var(--white-color-500); + font-weight: 700; + } + + &.otherMonth { + color: var(--white-color-500); + cursor: default; + } +`; From 10332edbdc9e4ecdc137602a7aa8e3d14a2dada3 Mon Sep 17 00:00:00 2001 From: HyerinJeon Date: Wed, 13 Mar 2024 16:01:53 +0900 Subject: [PATCH 5/7] Feat ConfirmModal with ModalPortal #10 --- public/index.html | 3 +- src/components/modal/ConfirmModal.tsx | 23 +++++++++++++ src/components/modal/ModalPortal.tsx | 15 +++++++++ src/styles/ConfirmModal.styles.ts | 47 +++++++++++++++++++++++++++ src/types/ConfirmModal.types.ts | 6 ++++ 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/components/modal/ConfirmModal.tsx create mode 100644 src/components/modal/ModalPortal.tsx create mode 100644 src/styles/ConfirmModal.styles.ts create mode 100644 src/types/ConfirmModal.types.ts diff --git a/public/index.html b/public/index.html index aa069f2..0331491 100644 --- a/public/index.html +++ b/public/index.html @@ -1,4 +1,4 @@ - + @@ -29,6 +29,7 @@
+