Skip to content

Commit

Permalink
Merge pull request #14 from PlanIt-Project/dev
Browse files Browse the repository at this point in the history
Landing, Main, UserSchedule 페이지
  • Loading branch information
Hailey0930 authored Mar 13, 2024
2 parents 393d752 + 9994a77 commit ecbbd26
Show file tree
Hide file tree
Showing 21 changed files with 824 additions and 49 deletions.
3 changes: 2 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
Expand Down Expand Up @@ -29,6 +29,7 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="modal"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
Expand Down
Binary file added src/assets/icon_left-arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icon_right-arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
188 changes: 188 additions & 0 deletions src/components/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import * as S from "../styles/Calendar.styles";
import LeftArrow from "../assets/icon_left-arrow.png";
import RightArrow from "../assets/icon_right-arrow.png";
import { DAYS_OF_WEEK, TODAY } from "../constants/Calendar.constants";
import { ICalendarProps } from "../types/Calendar.types";
import { useState } from "react";

export default function Calendar({
selectedDay,
handleClickDay,
}: ICalendarProps) {
const [currentMonth, setCurrentMonth] = useState(TODAY);

const isSameDay = (toDay: Date, compareDay?: Date | null) => {
if (
toDay.getFullYear() === compareDay?.getFullYear() &&
toDay.getMonth() === compareDay?.getMonth() &&
toDay.getDate() === compareDay?.getDate()
) {
return true;
}
return false;
};

// 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 (
<S.CalendarDay key={index}>
<S.Day
className={`${dayClass} ${selectedDayClass}`}
onClick={() => {
if (isCurrentMonth) {
handleClickDay(day);
}
}}
>
{day.getDate()}
</S.Day>
</S.CalendarDay>
);
});
};

// 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 (
<S.Container>
<S.TopContainer>
<S.ArrowIcon onClick={handleMoveToPrevCalendar}>
<img src={LeftArrow} alt="왼쪽 화살표" />
</S.ArrowIcon>
<p>
{currentMonth.getFullYear()}{currentMonth.getMonth() + 1}
</p>
<S.ArrowIcon onClick={handleMoveToNextCalendar}>
<img src={RightArrow} alt="오른쪽 화살표" />
</S.ArrowIcon>
</S.TopContainer>

<S.WeekContainer>
{DAYS_OF_WEEK.map((week, index) => (
<S.Week key={index}>{week}</S.Week>
))}
</S.WeekContainer>

<S.CalendarContainer>
{calendarRows.map((day, index) => (
<S.CalendarRows key={index}>{day}</S.CalendarRows>
))}
</S.CalendarContainer>
</S.Container>
);
}
23 changes: 23 additions & 0 deletions src/components/modal/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as S from "../../styles/ConfirmModal.styles";
import { IConfirmModalProps } from "../../types/ConfirmModal.types";

export default function ConfirmModal({
contents,
setIsModalOpen,
}: IConfirmModalProps) {
const handleCloseModal = () => {
setIsModalOpen(false);
};

return (
<S.Background>
<S.ModalContainer>
<S.ModalContents>{contents}</S.ModalContents>
<S.ButtonContainer>
<S.CancelButton onClick={handleCloseModal}>닫기</S.CancelButton>
<S.ConfirmButton>취소</S.ConfirmButton>
</S.ButtonContainer>
</S.ModalContainer>
</S.Background>
);
}
15 changes: 15 additions & 0 deletions src/components/modal/ModalPortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ReactNode } from "react";
import ReactDom from "react-dom";

interface IModalPortalProps {
children: ReactNode;
}

export default function ModalPortal({ children }: IModalPortalProps) {
const el = document.getElementById("modal");

if (el) return ReactDom.createPortal(children, el);
else {
return <></>;
}
}
4 changes: 4 additions & 0 deletions src/constants/Calendar.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const TODAY = new Date();
TODAY.setHours(0, 0, 0, 0);

export const DAYS_OF_WEEK = ["일", "월", "화", "수", "목", "금", "토"];
4 changes: 2 additions & 2 deletions src/pages/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export default function Landing() {
<S.Container>
<S.TitleButtonContainer>
<S.Title>
<p>언제 어디서나</p>
<p>이용권 스케줄 관리를!</p>
<p>PT의 모든 것</p>
<p>PlanIT에서 쉽고 간편하게</p>
</S.Title>
<S.StartButton onClick={handleMoveToLogin}>Get Start</S.StartButton>
</S.TitleButtonContainer>
Expand Down
75 changes: 64 additions & 11 deletions src/pages/Main.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(null);

useEffect(() => {
window.addEventListener("resize", setVH);
setVH();
}, []);

const handleDragStart = (e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
if (trainerScrollRef.current) {
setIsDrag(true);
setStartX(e.pageX + trainerScrollRef.current.scrollLeft);
}
};

const handleDragEnd = () => {
setIsDrag(false);
};

const handleDragMove = (e: MouseEvent<HTMLDivElement>) => {
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 (
<S.Container>
<S.Banner></S.Banner>
<S.BottomContainer>
<S.HalfContainer>
<S.Title>이용 후기</S.Title>
<S.ContentsContainer></S.ContentsContainer>
</S.HalfContainer>
<S.HalfContainer>
<S.Title>이번주 예약 내역</S.Title>
<S.ContentsContainer></S.ContentsContainer>
</S.HalfContainer>
</S.BottomContainer>
<S.TrainerContainer>
<S.Title>트레이너 소개</S.Title>
<S.ContentsContainer
ref={trainerScrollRef}
onMouseDown={handleDragStart}
onMouseMove={isDrag ? throttleDragMove : undefined}
onMouseUp={handleDragEnd}
onMouseLeave={handleDragEnd}
>
{trainerList.map((trainer) => (
<S.Grid key={trainer.id}>
<S.ImageContainer>
<img src={trainer.imgUrl} />
</S.ImageContainer>
<S.InfoContainer>{trainer.info}</S.InfoContainer>
</S.Grid>
))}
</S.ContentsContainer>
</S.TrainerContainer>
</S.Container>
);
}
3 changes: 3 additions & 0 deletions src/pages/trainer/Reservation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function TrainerReservation() {
return <div>트레이너 예약 관리 페이지</div>;
}
3 changes: 3 additions & 0 deletions src/pages/user/Reservation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function UserReservation() {
return <div>유저 예약하기 페이지</div>;
}
Loading

0 comments on commit ecbbd26

Please sign in to comment.