Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat[#99] 마이페이지/예약현황 모달창 구현 #214

Merged
merged 18 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f4314b2
feat: 예약 현황 모달에 CommonModal, useMultiState 적용 및 게임ID와 클릭한 날짜 Prop 전달
CheeseB Mar 28, 2024
e40831f
feat: 예약현황 모달에 탭 추가
CheeseB Mar 28, 2024
aa26529
refactor: 예약 현황 response 타입명 더 명확하게 변경
CheeseB Mar 28, 2024
ba6e9c7
feat: 모달에 일별 스케줄 정보 표시하는 기능 구현 (mock데이터)
CheeseB Mar 28, 2024
34b80b1
feat: 모달창 스타일 추가
CheeseB Mar 28, 2024
50fba75
feat: 예약 상세데이터 테스트를 위한 mock데이터 추가
CheeseB Mar 28, 2024
48ccdcb
feat: 예약 내역 카드 컴포넌트 추가
CheeseB Mar 28, 2024
9243217
feat: 모달 카드 스크롤 기능 추가
CheeseB Mar 28, 2024
6b1e192
feat: 이벤트 핸들러를 함수로 분리
CheeseB Mar 28, 2024
a103f4d
feat: 예약 카드 컴포넌트 리스트 렌더링 구현 (mock데이터)
CheeseB Mar 28, 2024
d49c1e1
feat: 예약 데이터 없을때 EmptyCard 보이도록 수정
CheeseB Mar 28, 2024
63a477c
feat: 모달 데이터 2개 넘으면 스크롤 보이도록 수정
CheeseB Mar 28, 2024
c9258dd
feat: 드롭다운 텍스트 넘치면 ellipse(...) 처리
CheeseB Mar 28, 2024
1919145
feat: 예약 승인/거절 누르면 confirmModal 열리도록 구현
CheeseB Mar 28, 2024
5d907fb
feat: 예약 현황 모달 태블릿 사이즈에서 꽉 차게 구현 (CommonModal 수정)
CheeseB Mar 28, 2024
ceef9e3
feat: 태블릿 이하에서 닫기 버튼 large 사이즈로 키우기
CheeseB Mar 28, 2024
55bfdb7
feat: 모달창 확인버튼 태블릿 이하에서 바닥에 붙게끔 수정
CheeseB Mar 28, 2024
14207ff
refactor: 코드리뷰 반영
CheeseB Mar 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/calendar/ListBody/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import classNames from 'classnames/bind';
import ListCard from '@/components/calendar/ListCard';
import EmptyCard from '@/components/layout/empty/EmptyCard';

import { MonthlySchedule } from '@/types';
import { MonthlyReservationResponse } from '@/types';

import styles from './ListBody.module.scss';

const cx = classNames.bind(styles);

type ListBodyProps = {
schedules?: MonthlySchedule[];
schedules?: MonthlyReservationResponse[];
onClick: (date: string) => void;
};

Expand Down
32 changes: 32 additions & 0 deletions src/components/calendar/ModalCard/ModalCard.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.modal-card {
&-container {
@include column-flexbox(start, stretch, 0.8rem);

padding: 1.2rem;
background-color: $black80;
border-radius: 0.8rem;
}

&-text {
@include column-flexbox(start, stretch, 0.4rem);

&-line {
@include flexbox(start, center, 0.8rem);
}

&-title {
@include text-style(14, $gray30);

display: inline-block;
width: 4rem;
}

&-value {
@include text-style(14, $white);
}
}

&-button {
@include flexbox(end, center, 0.8rem);
}
}
52 changes: 52 additions & 0 deletions src/components/calendar/ModalCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Fragment } from 'react';

import classNames from 'classnames/bind';

import Badge from '@/components/commons/Badge';
import { BaseButton } from '@/components/commons/buttons';

import { MyReservationsStatus } from '@/types';

import styles from './ModalCard.module.scss';

const cx = classNames.bind(styles);

type ModalCardProps = {
nickName: string;
headCount: number;
status: MyReservationsStatus;
onClickButton: (text: string) => void;
};

const ModalCard = ({ nickName, headCount, status, onClickButton }: ModalCardProps) => {
return (
<div className={cx('modal-card-container')}>
<div className={cx('modal-card-text')}>
<div className={cx('modal-card-text-line')}>
<span className={cx('modal-card-text-title')}>닉네임</span>
<span className={cx('modal-card-text-value')}>{nickName}</span>
</div>
<div className={cx('modal-card-text-line')}>
<span className={cx('modal-card-text-title')}>인원</span>
<span className={cx('modal-card-text-value')}>{headCount}명</span>
</div>
</div>
<div className={cx('modal-card-button')}>
{status === 'pending' ? (
<Fragment>
<BaseButton theme='outline' size='small' onClick={() => onClickButton('거절')}>
거절
</BaseButton>
<BaseButton theme='ghost' size='small' onClick={() => onClickButton('승인')}>
승인
</BaseButton>
</Fragment>
) : (
<Badge status={status} />
)}
</div>
</div>
);
};

export default ModalCard;
72 changes: 72 additions & 0 deletions src/components/calendar/ModalContents/ModalContents.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.schedule-modal {
&-container {
@include column-flexbox(start, stretch, 1.6rem);

@include responsive(T) {
justify-content: space-between;
height: 100%;
}
}

&-date {
@include column-flexbox(start, stretch, 0.8rem);

&-title {
@include text-style(16, $white, bold);
}

&-korean {
@include text-style(14, $gray10);
}
}

&-reservation {
@include column-flexbox(start, stretch, 0.8rem);

@include responsive(T) {
flex-grow: 1;
}

&-title {
@include flexbox(start, center, 0.4rem);
@include text-style(16, $white, bold);
}

&-count {
@include text-style(14, $primary, bold);
}

&-card {
@include column-flexbox(start, stretch, 0.8rem);

overflow-y: auto;
max-height: 23.2rem;

&::-webkit-scrollbar {
width: 0.6rem;
}

&::-webkit-scrollbar-track {
background: $opacity-white-5;
border-radius: 9.9rem;
}

&::-webkit-scrollbar-thumb {
background-color: $gray10;
border-radius: 9.9rem;
}

&.scroll {
padding-right: 0.8rem;
}
}
}

&-close {
@include responsive(T) {
margin: 0;
}

margin: 0 auto;
}
}
105 changes: 103 additions & 2 deletions src/components/calendar/ModalContents/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,112 @@
import { MouseEventHandler, useState } from 'react';

import classNames from 'classnames/bind';

import { getDateStringKR, getScheduleDropdownOption, getStatusCountByScheduleId } from '@/utils';

import ModalCard from '@/components/calendar/ModalCard';
import { BaseButton } from '@/components/commons/buttons';
import Dropdown from '@/components/commons/Dropdown';
import Tab from '@/components/commons/Tab';
import EmptyCard from '@/components/layout/empty/EmptyCard';
import reservationDetailMockDataConfirmed from '@/constants/mockData/reservationDetailMockDataConfirmed.json';
import reservationDetailMockDataDeclined from '@/constants/mockData/reservationDetailMockDataDeclined.json';
import reservationDetailMockDataPending from '@/constants/mockData/reservationDetailMockDataPending.json';
import reservationDetailMockDataPendingNoData from '@/constants/mockData/reservationDetailMockDataPendingNoData.json';
import DailyScheduleMockData from '@/constants/mockData/reservedScheduleMockData.json';
import { useDeviceType } from '@/hooks/useDeviceType';

import { DailyReservationResponse, DetailReservationResponse, MyReservationsStatus, StatusTabOptions } from '@/types';

import styles from './ModalContents.module.scss';

const cx = classNames.bind(styles);

const ModalContents = () => {
return <div className={cx('')}>ModalContents</div>;
type ModalContentsProps = {
gameId: number;
activeDate: string;
onClickCloseButton: MouseEventHandler<HTMLButtonElement>;
onClickCardButton: (text: string) => void;
};

const ModalContents = ({ gameId, activeDate, onClickCloseButton, onClickCardButton }: ModalContentsProps) => {
const DailyMockData: Record<string, DailyReservationResponse[]> = {
'2024-03-27': DailyScheduleMockData['2024-03-27'],
'2024-03-30': DailyScheduleMockData['2024-03-30'],
'2024-04-01': DailyScheduleMockData['2024-04-01'],
};

const ReservationMockData: Record<string, DetailReservationResponse> = {
'1-0-confirmed': reservationDetailMockDataConfirmed as DetailReservationResponse,
'1-0-declined': reservationDetailMockDataDeclined as DetailReservationResponse,
'1-0-pending': reservationDetailMockDataPending as DetailReservationResponse,
'1-1-pending': reservationDetailMockDataPendingNoData as DetailReservationResponse,
};

const dropdownOptions = getScheduleDropdownOption(DailyMockData[activeDate]);
const statusCountByScheduleId = getStatusCountByScheduleId(DailyMockData[activeDate]);

const [scheduleId, setScheduleId] = useState(dropdownOptions[0].value as number);

const statusCount = statusCountByScheduleId[scheduleId];

const statusTabOptions: StatusTabOptions[] = [
{ id: 'pending', text: '신청', count: statusCount.pending },
{ id: 'confirmed', text: '승인', count: statusCount.confirmed },
{ id: 'declined', text: '거절', count: statusCount.declined },
];

const [selectedStatus, setSelectedStatus] = useState<MyReservationsStatus>(statusTabOptions[0].id);

const currentDeviceType = useDeviceType();

const { totalCount, reservations } = ReservationMockData[`${gameId}-${scheduleId}-${selectedStatus}`];

const handleChangeTabId = (selectedId: string | number) => {
setSelectedStatus(selectedId as MyReservationsStatus);
};

const handleChangeScheduleId = (value: string | number) => {
setScheduleId(value as number);
};

return (
<div className={cx('schedule-modal-container')}>
<Tab items={statusTabOptions} size='small' selectedTabId={selectedStatus} onClick={handleChangeTabId} />
<div className={cx('schedule-modal-date')}>
<h3 className={cx('schedule-modal-date-title')}>예약 날짜</h3>
<span className={cx('schedule-modal-date-korean')}>{getDateStringKR(activeDate)}</span>
<Dropdown options={dropdownOptions} onChange={handleChangeScheduleId} color='yellow' isSmall />
</div>
<div className={cx('schedule-modal-reservation')}>
<h3 className={cx('schedule-modal-reservation-title')}>
<span>예약 내역</span>
<span className={cx('schedule-modal-reservation-count')}>{totalCount}</span>
</h3>
{totalCount !== 0 ? (
<ul className={cx('schedule-modal-reservation-card', { scroll: totalCount > 2 })}>
{reservations.map(({ nickname, headCount, status, id }) => (
<li key={`card-${id}`}>
<ModalCard
nickName={nickname}
headCount={headCount}
status={status as MyReservationsStatus}
onClickButton={onClickCardButton}
/>
</li>
))}
</ul>
) : (
<EmptyCard text='No Reservation' isSmall />
)}
</div>
<div className={cx('schedule-modal-close')}>
<BaseButton theme='outline' size={currentDeviceType === 'PC' ? 'medium' : 'large'} onClick={onClickCloseButton}>
닫기
</BaseButton>
</div>
</div>
);
};

export default ModalContents;
Loading