-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: react-datepicker 임시로 사용(데모데이때 빠르게 보여주기 위함) * chore: css-loader, style-loader 추가 데모데이때 사용하는 date-picker를 위해 임시로 설치 * feat(DateRangePicker): 임시로 사용할 DateRangePicker 컴포넌트 구현 * refactor(Input): maxCount, count optional로 변경 * chore(main): datepicker css 추가 * refactor: Place 내 name을 placeName으로 변경 * refactor(useTravelDays): onAddDay에 useCallback 추가 * feat(usePostTravelPlan): 여행 계획 post 요청 hook 구현 * feat(TravelogueRegisterPage): 여행 계획 등록 페이지 구현
- Loading branch information
1 parent
e0111a4
commit 74b3847
Showing
18 changed files
with
453 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
frontend/src/components/common/DateRangePicker/DateRangePicker.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import DatePicker from "react-datepicker"; | ||
|
||
import { ko } from "date-fns/locale"; | ||
|
||
import Input from "@components/common/Input/Input"; | ||
|
||
const DateRangePicker = ({ | ||
startDate, | ||
endDate, | ||
onChangeStartDate, | ||
onChangeEndDate, | ||
}: { | ||
startDate: Date | null; | ||
endDate: Date | null; | ||
onChangeStartDate: (date: Date | null) => void; | ||
onChangeEndDate: (date: Date | null) => void; | ||
}) => { | ||
const formatDate = (date: Date | null) => { | ||
if (!date) return ""; | ||
return date | ||
.toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit" }) | ||
.replace(/\. /g, "년 ") | ||
.replace(".", "일"); | ||
}; | ||
|
||
return ( | ||
<DatePicker | ||
selected={startDate} | ||
onChange={(dates) => { | ||
const [start, end] = dates; | ||
onChangeStartDate(start); | ||
onChangeEndDate(end); | ||
}} | ||
startDate={startDate as Date} | ||
endDate={endDate as Date} | ||
selectsRange={true} | ||
locale={ko} | ||
dateFormat="yyyy년 MM월 dd일" | ||
customInput={ | ||
<Input | ||
label="여행 기간" | ||
count={0} | ||
value={`${formatDate(startDate)} - ${formatDate(endDate)}`} | ||
readOnly | ||
/> | ||
} | ||
/> | ||
); | ||
}; | ||
|
||
export default DateRangePicker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
frontend/src/components/pages/travelPlanRegister/TravelPlanRegisterPage.styled.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import theme from "@styles/theme"; | ||
import { PRIMITIVE_COLORS, SPACING } from "@styles/tokens"; | ||
|
||
export const addButtonStyle = css` | ||
display: flex; | ||
width: 100%; | ||
height: 4rem; | ||
margin-bottom: 3.2rem; | ||
padding: 1.2rem 1.6rem; | ||
border: 1px solid ${theme.colors.border}; | ||
color: ${PRIMITIVE_COLORS.black}; | ||
font-weight: 700; | ||
font-size: 1.6rem; | ||
gap: ${SPACING.s}; | ||
border-radius: ${SPACING.s}; | ||
`; | ||
|
||
export const addTravelAddButtonStyle = css` | ||
display: flex; | ||
width: 100%; | ||
height: 4rem; | ||
padding: 1.2rem 1.6rem; | ||
border: 1px solid ${theme.colors.border}; | ||
color: ${PRIMITIVE_COLORS.black}; | ||
font-weight: 700; | ||
font-size: 1.6rem; | ||
gap: ${SPACING.s}; | ||
border-radius: ${SPACING.s}; | ||
`; | ||
|
||
export const Layout = styled.div` | ||
display: flex; | ||
padding: 1.6rem; | ||
flex-direction: column; | ||
gap: 3.2rem; | ||
`; | ||
|
||
export const AccordionRootContainer = styled.div` | ||
& > * { | ||
margin-bottom: 1.6rem; | ||
} | ||
`; | ||
|
||
export const PageInfoContainer = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.8rem; | ||
`; | ||
|
||
export const addDayButtonStyle = css` | ||
margin-top: 1.6rem; | ||
`; |
145 changes: 145 additions & 0 deletions
145
frontend/src/components/pages/travelPlanRegister/TravelPlanRegisterPage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { useEffect, useState } from "react"; | ||
import { useNavigate } from "react-router-dom"; | ||
|
||
import { usePostTravelPlan } from "@queries/usePostTravelPlan/usePostTravelPlan"; | ||
import { differenceInDays } from "date-fns"; | ||
|
||
import { | ||
Accordion, | ||
Button, | ||
DayContent, | ||
GoogleMapLoadScript, | ||
IconButton, | ||
Input, | ||
ModalBottomSheet, | ||
PageInfo, | ||
} from "@components/common"; | ||
import DateRangePicker from "@components/common/DateRangePicker/DateRangePicker"; | ||
|
||
import { useTravelDays } from "@hooks/pages/useTravelDays"; | ||
|
||
import * as S from "./TravelPlanRegisterPage.styled"; | ||
|
||
const MAX_TITLE_LENGTH = 20; | ||
|
||
const TravelPlanRegisterPage = () => { | ||
const [title, setTitle] = useState(""); | ||
const [startDate, setStartDate] = useState<Date | null>(null); | ||
const [endDate, setEndDate] = useState<Date | null>(null); | ||
|
||
const { travelDays, onAddDay, onAddPlace, onDeleteDay, onChangePlaceDescription, onDeletePlace } = | ||
useTravelDays(); | ||
|
||
useEffect(() => { | ||
if (startDate && endDate) { | ||
const dayDiff = differenceInDays(endDate, startDate) + 1; | ||
|
||
onAddDay(dayDiff); | ||
} | ||
}, [startDate, endDate, onAddDay]); | ||
|
||
const handleStartDateChange = (date: Date | null) => { | ||
setStartDate(date); | ||
}; | ||
|
||
const handleEndDateChange = (date: Date | null) => { | ||
setEndDate(date); | ||
}; | ||
|
||
const handleChangeTitle = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setTitle(e.target.value); | ||
}; | ||
|
||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
const handleOpenBottomSheet = () => { | ||
setIsOpen(true); | ||
}; | ||
|
||
const handleCloseBottomSheet = () => { | ||
setIsOpen(false); | ||
}; | ||
|
||
const navigate = useNavigate(); | ||
|
||
const handleConfirmBottomSheet = async () => { | ||
if (!startDate) return; | ||
|
||
const formattedStartDate = startDate.toISOString().split("T")[0]; // "YYYY-MM-DD" 형식으로 변환 | ||
|
||
handleAddTravelPlan( | ||
{ title, startDate: formattedStartDate, days: travelDays }, | ||
{ | ||
onSuccess: ({ data }) => { | ||
handleCloseBottomSheet(); | ||
navigate(`/travel-plan/${data.id}`); | ||
}, | ||
}, | ||
); | ||
handleCloseBottomSheet(); | ||
}; | ||
|
||
const { mutateAsync: handleAddTravelPlan } = usePostTravelPlan(); | ||
|
||
return ( | ||
<> | ||
<S.Layout> | ||
<PageInfo mainText="여행 계획 등록" /> | ||
<Input | ||
value={title} | ||
maxLength={MAX_TITLE_LENGTH} | ||
label="제목" | ||
count={title.length} | ||
maxCount={MAX_TITLE_LENGTH} | ||
onChange={handleChangeTitle} | ||
/> | ||
|
||
<DateRangePicker | ||
startDate={startDate} | ||
endDate={endDate} | ||
onChangeStartDate={handleStartDateChange} | ||
onChangeEndDate={handleEndDateChange} | ||
/> | ||
|
||
<S.AccordionRootContainer> | ||
<GoogleMapLoadScript libraries={["places", "maps"]}> | ||
<Accordion.Root> | ||
{travelDays.map((travelDay, dayIndex) => ( | ||
<DayContent | ||
key={`${travelDay}-${dayIndex}`} | ||
travelDay={travelDay} | ||
dayIndex={dayIndex} | ||
onAddPlace={onAddPlace} | ||
onDeletePlace={onDeletePlace} | ||
onDeleteDay={onDeleteDay} | ||
onChangePlaceDescription={onChangePlaceDescription} | ||
/> | ||
))} | ||
</Accordion.Root> | ||
<IconButton | ||
size="16" | ||
iconType="plus" | ||
position="left" | ||
css={[S.addButtonStyle, S.addDayButtonStyle]} | ||
onClick={() => onAddDay()} | ||
> | ||
일자 추가하기 | ||
</IconButton> | ||
</GoogleMapLoadScript> | ||
<Button variants="primary" onClick={handleOpenBottomSheet}> | ||
등록 | ||
</Button> | ||
</S.AccordionRootContainer> | ||
</S.Layout> | ||
<ModalBottomSheet | ||
isOpen={isOpen} | ||
mainText="여행 계획을 등록할까요?" | ||
subText="등록한 후에도 다시 여행 계획을 수정할 수 있어요." | ||
onClose={handleCloseBottomSheet} | ||
onConfirm={handleConfirmBottomSheet} | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
export default TravelPlanRegisterPage; |
Oops, something went wrong.