오전 작업 내용
달력 디자인
달력에서 월을 넘기는 화살표 버튼 기능만 작동하고 년은 작동하지 않는 이유가 오늘부터 30일 동안만 예약할 수 있게 해 놓았기 때문에 예약을 할 수 없는 달이나 월은 아예 보이지 않는다 따라서 다음 연도나 전 연도로 넘어갈 수 있게 만들어 놓은 버튼은 실행이 안 되고, 예약할 수 있는 월까지만 넘어간다!
→ 여기에 있는 minDate와 maxDate를 주석처리하면 모든 연도와 월을 확인 가능하다!
import React from 'react';
import Calendar from 'react-calendar';
import styled from 'styled-components';
import { BiSolidCalendar } from 'react-icons/bi';
const CalendarWrapper = styled.div`
display: flex;
flex-direction: column;
margin-bottom: 20px;
width: 100%; /* 부모 컨테이너의 크기에 맞게 확장 */
`;
const StyledCalendar = styled(Calendar)`
width: 100%; /* 캘린더의 가로 크기를 부모 컨테이너에 맞춤 */
max-width: 100%; /* 캘린더의 최대 가로 크기를 부모 컨테이너에 맞춤 */
border: none;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
/* 연월 표시 부분 스타일 */
.react-calendar__navigation {
display: flex;
justify-content: space-between; /* 공간을 균등하게 배분하여 양쪽 끝으로 화살표 버튼 배치 */
align-items: center;
margin-bottom: 10px;
padding: 0 10px; /* 내비게이션 버튼이 가로 크기에 맞게 공간 확보 */
}
.react-calendar__navigation button {
color: #ff3d00;
background: none;
font-size: 1.5em; /* 내비게이션 버튼 글자 크기 */
font-weight: bold;
flex: 1; /* 버튼이 균등하게 공간을 차지하도록 설정 */
max-width: 33%; /* 각 버튼의 최대 너비를 33%로 설정하여 중앙의 월/년 텍스트와 조화를 이루도록 함 */
text-align: center; /* 텍스트를 중앙에 배치 */
}
/* 요일 이름 스타일 */
.react-calendar__month-view__weekdays {
font-size: 1.2em; /* 요일 이름 글자 크기 */
margin-bottom: 10px; /* 요일 이름과 날짜 사이 간격 */
text-align: center;
font-weight: bold;
color: #ff3d00;
}
/* 날짜 스타일 */
.react-calendar__tile {
font-size: 1.2em; /* 날짜 글자 크기 */
padding: 15px 0; /* 날짜 상하 간격을 넓힘 */
&:hover,
&:focus,
&.react-calendar__tile--active {
background: #ff3d00; /* 클릭하거나 포커스된 날짜의 배경색을 #ff3d00으로 변경 */
color: #ffffff; /* 클릭된 날짜의 텍스트 색상을 흰색으로 설정 */
border-radius: 10px; /* 모서리가 둥근 사각형으로 유지 */
}
.react-calendar__tile--now {
//background: #ffe4e1;
background: #ffcccb; /* 핑크색 배경 */
color: #d32f2f; /* 핑크색과 조화를 이루는 진한 빨간색 텍스트 */
border-radius: 10px; /* 모서리가 둥근 사각형으로 표시 */
}
.react-calendar__tile--active {
background: #ff3d00;
color: #ffffff;
border-radius: 7%;
}
.react-calendar__tile:hover {
background: #ffcccb;
border-radius: 7%;
}
.react-calendar__navigation button {
color: #ff3d00;
min-width: 44px;
background: none;
font-size: 1.2em;
margin-top: 8px;
}
.react-calendar__month-view__weekdays {
text-align: center;
font-weight: bold;
color: #ff3d00;
}
.react-calendar__month-view__days__day--weekend {
color: #d32f2f;
}
`;
const TitleWithIcon = styled.h2`
display: flex;
align-items: center;
margin: 30px 0px 15px 30px;
svg {
margin-right: 7px; /* 아이콘과 글씨 사이 간격 */
font-size: 1.1em; /* 아이콘 크기 */
margin-bottom: 5px;
}
h2 {
margin: 0; /* Remove default margin from h2 */
font-size: 0.8em; /* h2 글씨 크기(...선택해 주세요) */
}
`;
const CalendarForm = ({
startDate,
endDate,
availableDates,
onCalendarClick,
}) => {
return (
<CalendarWrapper>
<TitleWithIcon>
<BiSolidCalendar />
<h2>날짜를 선택해 주세요</h2>
</TitleWithIcon>
<StyledCalendar
onChange={onCalendarClick}
minDate={startDate}
maxDate={endDate}
tileDisabled={({ date }) =>
availableDates.findIndex(
(d) =>
date.getFullYear() === d.getFullYear() &&
date.getMonth() === d.getMonth() &&
date.getDate() === d.getDate(),
) === -1 &&
date.getTime() !== new Date().setHours(0, 0, 0, 0) // 오늘 날짜는 비활성화하지 않음
}
/>
</CalendarWrapper>
);
};
export default CalendarForm;
오후 작업 내용
시간 선택 버튼 간격 조정하기
예약자 정보 만들기
이건 네이버 예약 페이지의 폼인데 예약자 정보(예약자, 연락처, 이메일)도 예약 페이지에 추가해야 한다!
회원 가입한 정보가 칸에 미리 보여지게 구현하자!
예약자 정보 아이콘 고민 중
import { FaAddressBook } from "react-icons/fa";
import { PiAddressBookFill } from "react-icons/pi";
import { BsPersonLinesFill } from "react-icons/bs";
-> FaAddressBook으로 선택!
예약자 정보 추가
정보 입력하는 버튼은 컴포넌트에 따로 만들어서 적용했다.
정보 입력 버튼 안에 회원 정보가 미리 들어가 있게 기능 구현 하기 위해서
import UserInfoContext from '../../member/modules/UserInfoContext';
ReservationForm.js에 meber쪽 UserInfoContext 의존성을 추가했다.
* 참고
예약자 정보 입력하는 버튼에 넣어야 할 회원 정보들
회원명 name="userName" value={form.userName}
휴대전화번호 name="mobile" value={form.mobile}
이메일 name="email" value={form.email}
InfoInputBox.js
import React from 'react';
import styled from 'styled-components';
const InfoInputBox = styled.input`
border: 1px solid #ff3d00;
margin: 10px 10px 10px 20px; //상/우/하/좌
height: 50px;
width: 89%;
padding: 0 10px;
border-radius: 3px;
`;
export default React.memo(InfoInputBox);
ReservationForm.js
import React, { useContext } from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import UserInfoContext from '../../member/modules/UserInfoContext';
import InfoInputBox from './InfoInputBox';
import { IoIosTime, IoMdCheckmarkCircleOutline, IoMdNotificationsOff } from 'react-icons/io';
import { GoPersonFill } from 'react-icons/go';
import { FaAddressBook } from "react-icons/fa";
import { PiAddressBookFill } from "react-icons/pi";
import { BsPersonLinesFill } from "react-icons/bs";
import { BigButton } from '../../commons/components/Buttons';
import CalendarForm from './CalendarForm';
const FormBox = styled.form`
display: flex;
flex-direction: column;
`;
const TimeTableAndPerson = styled.div`
margin-left: 20px;
`;
const TitleWithIcon = styled.h2`
display: flex;
align-items: center;
margin-bottom: 15px; /* 아이콘+글씨 줄 아래 마진 */
margin-top: 30px; /* 아이콘+글씨 줄 위 마진 */
svg {
margin-right: 7px; /* 아이콘과 글씨 사이 간격 */
font-size: 1.1em; /* 아이콘 크기 */
}
h2 {
margin: 0; /* Remove default margin from h2 */
font-size: 0.8em; /* h2 글씨 크기(...선택해 주세요) */
}
`;
const Subtitle = styled.h3`
margin: 5px 0 15px 5px;
font-size: 0.9em;
color: #666;
`;
const Checktitle = styled.h3`
margin: 10px 0 20px 7px;
font-size: 1.2em;
`;
const LastCheckTitle = styled(Checktitle)`
margin: 40px 0 30px 7px;
`;
const TimeButton = styled.button`
background: ${({ isSelected }) => (isSelected ? '#ff3d00' : '#ffffff')};
color: ${({ isSelected }) => (isSelected ? '#ffffff' : '#ff3d00')};
border: 1px solid #ff3d00;
border-radius: 5px;
width: 130px;
padding: 10px 35px; /* 시간 버튼 가로, 세로 크기 */
margin: 5px 5px 20px 20px; //상/우/하/좌
font-size: 1.2em; // 시간 버튼 글자 크기
cursor: pointer;
transition: background 0.3s, color 0.3s;
&:hover {
background: #ff3d00;
color: #ffffff;
}
`;
const PersonButton = styled.button`
background: ${({ isSelected }) => (isSelected ? '#ff3d00' : '#ffffff')};
color: ${({ isSelected }) => (isSelected ? '#ffffff' : '#ff3d00')};
border: 1px solid #ff3d00;
border-radius: 50%;
width: 57px; // 인원 버튼 가로 크기
height: 57px; // 인원 버튼 세로 크기
display: flex;
align-items: center;
justify-content: center;
margin: 5px;
font-size: 1.2em;
cursor: pointer;
transition: background 0.3s, color 0.3s;
margin-bottom: 30px;
&:hover {
background: #ff3d00;
color: #ffffff;
}
`;
const PersonButtonsContainer = styled.div`
display: flex;
flex-wrap: wrap; /* Allows wrapping if needed */
gap: 10px; /* Space between buttons */
`;
const ReservationInfoBox = styled.dt`
font-size: 1.2em;
`;
const ReservationForm = ({
data,
form,
times,
onCalendarClick,
onTimeClick,
onChange,
onSubmit,
}) => {
const { availableDates } = data;
const startDate = availableDates[0];
const endDate = availableDates[availableDates.length - 1];
const {
states: { userInfo },
} = useContext(UserInfoContext);
const { t } = useTranslation();
const personOptions = [...new Array(10).keys()].map((i) => i + 1);
return (
<FormBox onSubmit={onSubmit} autoComplete="off">
<CalendarForm
startDate={startDate}
endDate={endDate}
availableDates={availableDates}
onCalendarClick={onCalendarClick}
/>
<TimeTableAndPerson>
{times?.length > 0 && (
<>
<TitleWithIcon>
<IoIosTime />
<h2>{t('시간을 선택해 주세요')}</h2>
</TitleWithIcon>
<div className="time-buttons">
{times.map((time) => (
<TimeButton
key={time}
isSelected={form.rTime === time}
onClick={() => onTimeClick(time)}
>
{time}
</TimeButton>
))}
</div>
<dl className="persons">
<TitleWithIcon>
<GoPersonFill />
<h2>{t('인원을 선택해 주세요')}</h2>
</TitleWithIcon>
<Subtitle>{t('1~10명까지 선택 가능합니다.')}</Subtitle>
<PersonButtonsContainer>
{personOptions.map((person) => (
<PersonButton
key={person}
isSelected={form.persons === person}
onClick={() =>
onChange({ target: { name: 'persons', value: person } })
}
>
{person}명
</PersonButton>
))}
</PersonButtonsContainer>
</dl>
<div>
<TitleWithIcon>
<FaAddressBook />
<h2>{t('예약자 정보')}</h2>
</TitleWithIcon>
<ReservationInfoBox>
<dl>
<dt>
{t('예약자')}
<InfoInputBox type="text" value="form.userName" />
</dt>
</dl>
<dl>
<dt>{t('연락처')}
<InfoInputBox type="text" value="form.mobile" />
</dt>
</dl>
<dl>
<dt>{t('이메일')}
<InfoInputBox type="text" value="form.email" />
</dt>
</dl>
</ReservationInfoBox>
<TitleWithIcon>
<IoMdCheckmarkCircleOutline />
<h2>{t('예약 시 확인해 주세요')}</h2>
</TitleWithIcon>
{[
'* 노쇼 방지를 위해 예약금과 함께 예약 신청을 받고 있습니다.',
'* 예약금은 식사 금액에서 차감합니다.',
'* 예약시간 15분 이상 늦을 시 자동 취소됩니다.(예약금 환불 X)',
'* 1인 1메뉴 주문 부탁드립니다.',
'* 외부 음식, 음료 반입 및 취식이 불가합니다.',
'* 인원 변경 시 방문 3시간 전까지 예약 수정 가능합니다.',
].map((item, index) => (
<Checktitle key={index}>{t(item)}</Checktitle>
))}
<LastCheckTitle>
{t(
'당일 취소 및 노쇼는 레스토랑뿐만 아니라 다른 고객님께도 피해가 될 수 있으므로 신중히 예약 부탁드립니다.',
)}
</LastCheckTitle>
</div>
<BigButton type="submit" color="jmt">
{t('예약하기')}
</BigButton>
</>
)}
</TimeTableAndPerson>
</FormBox>
);
};
export default React.memo(ReservationForm);
추가 작업해야 할 사항
- ReservationForm.js에 있는 내용들 다 따로 빼기
- 달력에 연도 움직이는 버튼 없애기
- 예약자 정보 텍스트와 아이콘 사이에 간격 위와 맞추기
'프로젝트 & 포트폴리오 > AI 기반 식당 예약 웹사이트 프로젝트' 카테고리의 다른 글
day 21 예약 필수 항목 결제 처리(예약 데이터 넘기기) + 오류 해결 + lang-common.js에서 공통 텍스트 관리 (0) | 2024.08.26 |
---|---|
day 21 회의 (0) | 2024.08.26 |
day 19 회의 (0) | 2024.08.22 |
p-4 day 18 예약 페이지 + 오류 수정 (0) | 2024.08.21 |
p-4 day 18 회의 (0) | 2024.08.21 |