Skip to content

Commit

Permalink
feat: implement bus rental details layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nxnaxx committed Dec 5, 2024
1 parent 281dfe4 commit a99cb3f
Show file tree
Hide file tree
Showing 16 changed files with 654 additions and 21 deletions.
1 change: 0 additions & 1 deletion src/components/inputField/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const InputFieldWrapper = styled.div<{ isError: boolean }>`
align-items: center;
gap: 0.8rem;
width: 100%;
min-width: 24rem;
height: 4rem;
margin-bottom: 0.8rem;
padding: 0 1.6rem;
Expand Down
1 change: 1 addition & 0 deletions src/components/subHeader/SubHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const SubHeaderContainer = styled.div<{ isTransparent: boolean }>`
grid-template-columns: 24px 1fr 24px;
align-items: center;
position: fixed;
z-index: 900;
max-width: ${({ theme }) => theme.maxWidth};
width: 100%;
height: 5.2rem;
Expand Down
28 changes: 28 additions & 0 deletions src/constants/FilterTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const REGIONS = [
'서울',
'경기',
'인천',
'강원',
'세종',
'천안',
'청주',
'대전',
'대구',
'경북',
'부산',
'울산',
'마산',
'창원',
'경남',
'광주',
'전북',
'전주',
'전남',
] as const;

export const CONCERT_SORT = ['최근 공연순', '인기순'] as const;
export const DATE_SORT = ['최신순', '오래된순', '마감순'] as const;

export type Region = (typeof REGIONS)[number];
export type ConcertSort = (typeof CONCERT_SORT)[number];
export type DateSort = (typeof DATE_SORT)[number];
35 changes: 35 additions & 0 deletions src/hooks/useRentalDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

import type { AllRentalDetail, RentalAccountResponse, RentalDetailResponse } from 'types';

// 추후 api 로직 분리
const fetchRentalDetails = (id: string) => {
return axios.get<RentalDetailResponse>(`/api/v1/rents/${id}`);
};

const fetchDepositAccount = (id: string) => {
return axios.get<RentalAccountResponse>(`/api/v1/rents/${id}/deposit-account`);
};

export const useRentalDetails = (id: string) => {
// 로그인 여부 (추후 수정)
const isLoggedIn = true;

const fetchDetails = async (): Promise<AllRentalDetail> => {
const detailPromise = await fetchRentalDetails(id);
const accountPromise = isLoggedIn ? fetchDepositAccount(id) : Promise.resolve(null);

const [detailResponse, accountResponse] = await Promise.all([detailPromise, accountPromise]);
const rentalDetails = detailResponse.data.result;
const depositAccount = accountResponse?.data.result.depositAccount ?? null;

return { ...rentalDetails, depositAccount };
};

return useQuery<AllRentalDetail>({
queryKey: ['rentalDetail', id],
queryFn: fetchDetails,
enabled: !!id,
});
};
46 changes: 45 additions & 1 deletion src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
import { http, HttpResponse } from 'msw';

export const handlers = [];
export const handlers = [
http.get('/api/v1/rents/1', () => {
return HttpResponse.json({
timeStamp: '2024-12-03T00:13:56.217Z',
code: '200',
message: 'Success',
result: {
concertName: 'DAY6 3RD WORLD TOUR, FOREVER YOUNG [인천]',
imageUrl: 'https://img1.newsis.com/2024/03/18/NISI20240318_0001504432_web.jpg',
title: '데이식스(DAY6) FOREVER YOUNG 콘서트 청주 차대절 🎸',
artistName: 'DAY6',
region: '청주',
boardingArea: '스타벅스 청주터미널점',
dropOffArea: '인스파이어리조트 (아레나)',
upTime: '09:00',
downTime: '23:00',
rentBoardingDates: ['2024-09-20', '2024-09-21', '2024-09-22'],
busSize: 'LARGE',
busType: 'DELUXE',
maxPassenger: 28,
roundPrice: 45000,
upTimePrice: 45000,
downTimePrice: 45000,
recruitmentCount: 25,
participants: [25, 12, 18],
endDate: '2024-12-26',
chatUrl: 'https://open.kakao.com/o/abcDeF',
refundType: 'ADDITIONAL_DEPOSIT',
information: `❗입금 후 폼 작성 부탁드립니다.❗\n\n 왕복, 편도 가격 동일합니다.
양도나 분할 탑승 신청자분들은 직접 짝 구해주시고 신청해주시길 바랍니다. (입금은 한분께서 일괄 입금 부탁드리며 오픈카톡을 통해 확인 내용 알려주시길 바랍니다.)\n
📌 개인이 진행하는 차대절이기 때문에 인원 미달, 13번 이후 입금자를 제외한 환불은 절대 불가하오며 이 부분을 숙지하지 못한 사항에 대해서 생기는 불이익은 책임지지 않습니다.이점 유의하시고 신청 바랍니다.`,
},
});
}),
http.get('/api/v1/rents/1/deposit-account', () => {
return HttpResponse.json({
timeStamp: '2024-12-03T02:38:22.994Z',
code: '200',
message: 'success',
result: {
depositAccount: '우리은행 1242264211943 김데식',
},
});
}),
];
199 changes: 181 additions & 18 deletions src/pages/busRentalDetail/BusRentalDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,186 @@
import styled from '@emotion/styled';
import { every } from 'lodash-es';
import { useParams } from 'react-router-dom';

import BusTime from './components/BusTime';
import DepositAccount from './components/DepositAccount';
import DrivingInfo from './components/DrivingInfo';
import ParticipantsStatus from './components/ParticipantsStatus';

import Badge from 'components/badge/Badge';
import BaseButton from 'components/buttons/BaseButton';
import SimpleChip from 'components/chips/SimpleChip';
import { useRentalDetails } from 'hooks/useRentalDetails';
import { BodyRegularText, TitleText1, TitleText2 } from 'styles/Typography';
import { formatDateWithDay, getDday } from 'utils/dateUtils';

const DetailContainer = styled.div`
position: relative;
`;

const ThumbnailContainer = styled.div`
position: relative;
width: 100%;
height: 0;
padding-top: 100%;
overflow: hidden;
`;

const ThumbnailImg = styled.img`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
`;

const ContentContainer = styled.div`
padding: 2.4rem;
`;

const Title = styled(TitleText1)`
margin: 1.6rem 0 0.8rem 0;
`;

const ConcertName = styled(BodyRegularText)`
margin-bottom: 1.6rem;
color: ${({ theme }) => theme.colors.dark[200]};
`;

const ChipWrapper = styled.div`
display: flex;
gap: 0.8rem;
margin-bottom: 1.6rem;
`;

const SectionWrapper = styled.div`
&:not(:last-of-type) {
margin-bottom: 3.2rem;
}
`;

const SectionTitle = styled(TitleText2)`
margin-bottom: 1.6rem;
color: ${({ theme }) => theme.colors.dark[50]};
`;

const Information = styled(BodyRegularText)`
color: ${({ theme }) => theme.colors.dark[200]};
white-space: pre-line;
`;

const BottomButtonWrapper = styled.div`
position: sticky;
bottom: 0;
left: 0;
width: 100%;
padding: 2.4rem;
background-color: ${({ theme }) => theme.colors.black};
`;

const InfoSection = ({ title, children }: { title: string; children: React.ReactNode }) => (
<SectionWrapper>
<SectionTitle>{title}</SectionTitle>
{children}
</SectionWrapper>
);

const BusRentalDetail = () => {
const { id } = useParams();
const { data: details, error, isLoading } = useRentalDetails(id as string);

if (isLoading) return <div>로딩중</div>;
if (error) return <div>Error 발생: {error.message}</div>;
if (!details) return <div>세부 정보가 존재하지 않습니다.</div>;

const {
imageUrl,
title,
concertName,
region,
artistName,
endDate,
rentBoardingDates,
recruitmentCount,
participants,
boardingArea,
dropOffArea,
busSize,
busType,
maxPassenger,
roundPrice,
upTimePrice,
downTimePrice,
depositAccount,
upTime,
downTime,
information,
} = details;

const dDay = getDday(endDate);
const rentDates = rentBoardingDates.map((date) => formatDateWithDay(date));
const busPrices = [roundPrice, upTimePrice, downTimePrice];

return (
<>
<div>BusRentalDetail</div>
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
<div style={{ color: 'white', padding: '4rem', margin: ' 2rem 0' }} />
</>
<DetailContainer>
<ThumbnailContainer>
<ThumbnailImg alt={title} src={imageUrl} />
</ThumbnailContainer>
<ContentContainer>
<SectionWrapper>
<Badge color={dDay > 3 ? 'gray' : 'red'} size="medium" variant="square">
{dDay === 0 ? `D-Day` : `D-${dDay}`}
</Badge>
<Title>{title}</Title>
<ConcertName>{concertName}</ConcertName>
<ChipWrapper>
<SimpleChip>{region}</SimpleChip>
<SimpleChip>{artistName}</SimpleChip>
</ChipWrapper>
<ParticipantsStatus
participants={participants}
recruitmentCount={recruitmentCount}
rentDates={rentDates}
/>
</SectionWrapper>
<InfoSection title="운행 정보">
<DrivingInfo
boardingArea={boardingArea}
busPrices={busPrices}
busSize={busSize}
busType={busType}
maxPassenger={maxPassenger}
rentDates={rentDates}
/>
</InfoSection>
<InfoSection title="입금 계좌">
<DepositAccount depositAccount={depositAccount} />
</InfoSection>
<InfoSection title="출발 시각">
<BusTime
boardingArea={boardingArea}
downTime={downTime}
dropOffArea={dropOffArea}
upTime={upTime}
/>
</InfoSection>
<InfoSection title="기타 안내 사항">
<Information>{information}</Information>
</InfoSection>
</ContentContainer>
<BottomButtonWrapper>
<BaseButton
color="primary"
isDisabled={every(participants, (participant) => participant === recruitmentCount)}
onClick={() => {}}
size="medium"
variant="fill"
>
폼 작성하기
</BaseButton>
</BottomButtonWrapper>
</DetailContainer>
);
};

Expand Down
Empty file.
51 changes: 51 additions & 0 deletions src/pages/busRentalDetail/components/BusTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styled from '@emotion/styled';

import { BodyRegularText } from 'styles/Typography';

interface BusTimeProps {
upTime: string;
downTime: string;
boardingArea: string;
dropOffArea: string;
}

const TimeContainer = styled.div``;

const TimeItem = styled.div`
display: flex;
&:not(:last-of-type) {
margin-bottom: 1.6rem;
}
`;

const Label = styled(BodyRegularText)`
margin-right: 1.6rem;
color: ${({ theme }) => theme.colors.dark[200]};
`;

const Time = styled(BodyRegularText)`
margin-right: 0.8rem;
color: ${({ theme }) => theme.colors.dark[200]};
`;

const BusTime = ({ upTime, downTime, boardingArea, dropOffArea }: BusTimeProps) => {
const timeData = [
{ label: '상행', time: upTime, area: boardingArea },
{ label: '하행', time: downTime, area: dropOffArea },
];

return (
<TimeContainer>
{timeData.map(({ label, time, area }) => (
<TimeItem>
<Label>{label}</Label>
<Time>{time}</Time>
<BodyRegularText>{area}</BodyRegularText>
</TimeItem>
))}
</TimeContainer>
);
};

export default BusTime;
Loading

0 comments on commit a99cb3f

Please sign in to comment.