From 1b0a3bd18706a72f8e4f3385524c348a9de9f497 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:14:13 +0900 Subject: [PATCH 01/11] =?UTF-8?q?chore(httpClient):=20reserve=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/httpClient/httpClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apis/httpClient/httpClient.ts b/src/apis/httpClient/httpClient.ts index fe66e3a2..bb25cce1 100644 --- a/src/apis/httpClient/httpClient.ts +++ b/src/apis/httpClient/httpClient.ts @@ -170,4 +170,5 @@ export default { image: new HttpClient("api/image/save", axiosConfig), meal: new HttpClient("api/meal", axiosConfig), calender: new HttpClient("api/calender", axiosConfig), + reserve: new HttpClient("api/ber", axiosConfig), }; From 01833ee4e44272f58cd474a5770052ac58f46989 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:14:23 +0900 Subject: [PATCH 02/11] =?UTF-8?q?chore(data):=20emptyReserve=20dto=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/emptyReserve.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/assets/data/emptyReserve.ts diff --git a/src/assets/data/emptyReserve.ts b/src/assets/data/emptyReserve.ts new file mode 100644 index 00000000..63dcfa49 --- /dev/null +++ b/src/assets/data/emptyReserve.ts @@ -0,0 +1,22 @@ +const emptyReserve = { + reservedBerNumber: [], + berResList: [ + { + id: 0, + berNumber: 0, + reservation: "2000-03-01", + reservationUsersName: "", + user: { + id: 0, + name: "", + nickname: "", + profileImage: "", + grade: 0, + class_number: 0, + student_number: 0, + }, + }, + ], +}; + +export default emptyReserve; From 77157b914d3544395d779bd1dd4e95fbba553366 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:14:27 +0900 Subject: [PATCH 03/11] =?UTF-8?q?chore(data):=20emptyReserve=20dto=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/data/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/assets/data/index.ts b/src/assets/data/index.ts index f03fb369..a519e3f8 100644 --- a/src/assets/data/index.ts +++ b/src/assets/data/index.ts @@ -4,3 +4,4 @@ export { default as emptyClassLevel } from "./emptyClassLevel"; export { default as emptyTimetable } from "./emptyTimetable"; export { default as emptyInputPost } from "./emptyInputPost"; export { default as emptyMealList } from "./emptyMealList"; +export { default as emptyReserve } from "./emptyReserve"; From bc1b0fdfcd24fe8a9067c6aece2d383a22e40c53 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:14:38 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix(Select):=20number=ED=83=80=EC=9E=85?= =?UTF-8?q?=EB=8F=84=20=EB=93=A4=EC=96=B4=EC=98=AC=EC=88=98=EC=9E=88?= =?UTF-8?q?=EA=B2=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/atoms/Select.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/atoms/Select.tsx b/src/components/atoms/Select.tsx index ab4bec5e..7c6beafa 100644 --- a/src/components/atoms/Select.tsx +++ b/src/components/atoms/Select.tsx @@ -9,8 +9,8 @@ interface ISelectProps { label: string; width?: string; handler: - | React.Dispatch> - | ((props: string) => void); + | React.Dispatch> + | ((props: string | number) => void); } const Select = ({ @@ -22,7 +22,7 @@ const Select = ({ }: ISelectProps) => { const [isHover, setIsHover] = React.useState(true); - const toggleHandler = (option: string) => { + const toggleHandler = (option: string | number) => { handler(option); setIsHover(true); }; From 703ff96674681f4e2eb2c6bf53d030d085b0f0a6 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:14:54 +0900 Subject: [PATCH 05/11] =?UTF-8?q?fix(JoinCheckBox):=20=E3=85=A0=E3=85=A0?= =?UTF-8?q?=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Aside/JoinCheckBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/Aside/JoinCheckBox.tsx b/src/components/common/Aside/JoinCheckBox.tsx index 7decf41b..fc89df60 100644 --- a/src/components/common/Aside/JoinCheckBox.tsx +++ b/src/components/common/Aside/JoinCheckBox.tsx @@ -69,7 +69,7 @@ const CheckButton = styled.button` background-color: ${color.content}; &:after { - content: "입사 완료"; + content: "미완성 기능"; } } `; From b33acce394b22015e33fdb5aaa54efdf9546ae8c Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:15:11 +0900 Subject: [PATCH 06/11] =?UTF-8?q?chore(cosntant):=20reserve=20key=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/key.constant.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/key.constant.ts b/src/constants/key.constant.ts index 05701628..6293a309 100644 --- a/src/constants/key.constant.ts +++ b/src/constants/key.constant.ts @@ -8,6 +8,7 @@ const KEY = { BAMBOO_ADMIN: "useBambooAdmin", MEAL: "useMeal", CALENDER: "useCalender", + RESERVE: "useReserve", } as const; export default KEY; From 61a0a6b3c2f64e1e206819293a7358956921c527 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:15:35 +0900 Subject: [PATCH 07/11] =?UTF-8?q?chore(interface):=20reserve=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interfaces/createReserve.interface.ts | 5 +++++ src/interfaces/index.ts | 3 +++ src/interfaces/reserve.interface.ts | 15 +++++++++++++++ src/interfaces/reserveList.interface.ts | 6 ++++++ 4 files changed, 29 insertions(+) create mode 100644 src/interfaces/createReserve.interface.ts create mode 100644 src/interfaces/reserve.interface.ts create mode 100644 src/interfaces/reserveList.interface.ts diff --git a/src/interfaces/createReserve.interface.ts b/src/interfaces/createReserve.interface.ts new file mode 100644 index 00000000..844d37f9 --- /dev/null +++ b/src/interfaces/createReserve.interface.ts @@ -0,0 +1,5 @@ +export default interface ICreateReserve { + berNumber: number; + reservationTime: string; + reservationUsersName: string; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index cad3e3e5..155e9291 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -18,3 +18,6 @@ export type { default as IInputPost } from "./inputPost.interface"; export type { default as IRecomment } from "./recomment.interface"; export type { default as ICalender } from "./calender.interface"; export type { default as IPlan } from "./plan.interface"; +export type { default as IReserve } from "./reserve.interface"; +export type { default as IReserveList } from "./reserveList.interface"; +export type { default as ICreateReserve } from "./createReserve.interface"; diff --git a/src/interfaces/reserve.interface.ts b/src/interfaces/reserve.interface.ts new file mode 100644 index 00000000..5b578042 --- /dev/null +++ b/src/interfaces/reserve.interface.ts @@ -0,0 +1,15 @@ +export default interface IReserve { + id: number; + berNumber: number; + reservation: string; + reservationUsersName: string; + user: { + id: number; + name: string; + nickname: string; + profileImage: string; + grade: number; + class_number: number; + student_number: number; + }; +} diff --git a/src/interfaces/reserveList.interface.ts b/src/interfaces/reserveList.interface.ts new file mode 100644 index 00000000..e9ef4bf0 --- /dev/null +++ b/src/interfaces/reserveList.interface.ts @@ -0,0 +1,6 @@ +import IReserve from "./reserve.interface"; + +export default interface IReserveList { + reservedBerNumber: Array; + berResList: Array; +} From 1c92c2c0fe1cfd1afbe33c50cc73d5b591f64e24 Mon Sep 17 00:00:00 2001 From: Ubinquitous Date: Wed, 4 Oct 2023 19:16:05 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat(reserve):=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=ED=8D=BC=EB=B8=94=EB=A6=AC=EC=8B=B1=20=EB=B0=8F=20?= =?UTF-8?q?API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/reserve/index.tsx | 80 +++++--- src/page/reserve/layouts/ReserveBox.tsx | 29 --- .../reserve/layouts/ReserveCategories.tsx | 32 ++++ src/page/reserve/layouts/ReserveJoinBox.tsx | 72 +++++--- src/page/reserve/layouts/ReserveList.tsx | 28 +++ src/page/reserve/layouts/ReserveListItem.tsx | 58 ++++++ src/page/reserve/layouts/ReserveMap.tsx | 173 ++++++++++-------- 7 files changed, 316 insertions(+), 156 deletions(-) delete mode 100644 src/page/reserve/layouts/ReserveBox.tsx create mode 100644 src/page/reserve/layouts/ReserveCategories.tsx create mode 100644 src/page/reserve/layouts/ReserveList.tsx create mode 100644 src/page/reserve/layouts/ReserveListItem.tsx diff --git a/src/page/reserve/index.tsx b/src/page/reserve/index.tsx index 5f424608..b168a97f 100644 --- a/src/page/reserve/index.tsx +++ b/src/page/reserve/index.tsx @@ -1,27 +1,60 @@ import { DesktopIcon } from "@/assets/icons"; import { Column } from "@/components/Flex"; import { Aside } from "@/components/common"; -import { flex, font } from "@/styles"; +import { color, flex, font } from "@/styles"; +import { emptyReserve } from "@/assets/data"; +import { IReserveList } from "@/interfaces"; import React from "react"; +import dayjs from "dayjs"; import styled from "styled-components"; -import ReserveBox from "./layouts/ReserveBox"; +import { useRecoilValue } from "recoil"; +import { reserveViewTypeStore } from "@/store/reserveViewType.store"; import ReserveJoinBox from "./layouts/ReserveJoinBox"; +import ReserveCategories from "./layouts/ReserveCategories"; +import ReserveMap from "./layouts/ReserveMap"; +import { useReserveListQuery } from "./services/query.service"; +import ReserveList from "./layouts/ReserveList"; const ReservePage = () => { + const reserveViewType = useRecoilValue(reserveViewTypeStore); + const [date, setDate] = React.useState(dayjs().format("YYYY-MM-DD")); + const [reserve, setReserve] = React.useState(emptyReserve); + const { data, isSuccess, refetch } = useReserveListQuery({ date }); + + React.useEffect(() => { + refetch(); + }, [date]); + + React.useEffect(() => { + if (isSuccess) setReserve(data); + }, [data, isSuccess]); + return ( - + - <ReserveBox /> - <ReserveJoinBox /> + <ReserveCategories /> + <Column gap="8px"> + <StyledTitle>조회할 날짜를 입력하세요</StyledTitle> + <StyledInputDate + type="date" + onChange={(e) => setDate(e.target.value)} + value={date} + /> + </Column> + {reserveViewType === "신청하기" && ( + <ReservationBox> + <ReserveMap reservedList={reserve?.reservedBerNumber} /> + <ReserveJoinBox date={date} /> + </ReservationBox> + )} + {reserveViewType === "목록 보기" && ( + <ReserveList reserveList={reserve?.berResList} /> + )} </Column> <Aside /> </Container> - <ResponsiveBox> - <DesktopIcon /> - <ResponsiveText /> - </ResponsiveBox> </Layout> ); }; @@ -35,10 +68,6 @@ const Container = styled.div` width: 76%; display: flex; gap: 8px; - - @media screen and (max-width: 1136px) { - display: none; - } `; const Title = styled.span` @@ -49,21 +78,24 @@ const Title = styled.span` } `; -const ResponsiveBox = styled.div` - height: 70vh; - ${flex.COLUMN_CENTER}; +const ReservationBox = styled.div` + width: fit-content; + height: fit-content; + padding: 10px 20px; + ${flex.COLUMN}; gap: 12px; - display: none; +`; - @media screen and (max-width: 1136px) { - display: flex; - } +const StyledInputDate = styled.input` + width: fit-content; + padding: 6px 16px; + ${font.p2}; + background-color: ${color.white}; `; -const ResponsiveText = styled.span` - &:after { - content: "데스크톱 모드로 이용해주세요"; - } +const StyledTitle = styled.span` + ${font.p2}; + font-weight: 500; `; export default ReservePage; diff --git a/src/page/reserve/layouts/ReserveBox.tsx b/src/page/reserve/layouts/ReserveBox.tsx deleted file mode 100644 index d7ce56b0..00000000 --- a/src/page/reserve/layouts/ReserveBox.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { color, flex } from "@/styles"; -import React from "react"; -import styled from "styled-components"; -import ReserveMap from "./ReserveMap"; - -const ReserveBox = () => { - return ( - <Container> - <ReserveMapBox> - <ReserveMap /> - </ReserveMapBox> - </Container> - ); -}; - -const Container = styled.div` - width: 100%; - display: flex; - justify-content: space-between; -`; - -const ReserveMapBox = styled.div` - width: 100%; - ${flex.CENTER}; - background-color: ${color.white}; - padding: 20px; -`; - -export default ReserveBox; diff --git a/src/page/reserve/layouts/ReserveCategories.tsx b/src/page/reserve/layouts/ReserveCategories.tsx new file mode 100644 index 00000000..78ca0b06 --- /dev/null +++ b/src/page/reserve/layouts/ReserveCategories.tsx @@ -0,0 +1,32 @@ +import { Category } from "@/components/atoms"; +import { reserveViewTypeStore } from "@/store/reserveViewType.store"; +import React from "react"; +import { useRecoilState } from "recoil"; +import styled from "styled-components"; + +const ReserveCategories = () => { + const [checked, setChecked] = useRecoilState(reserveViewTypeStore); + + return ( + <CategoryBox> + {["신청하기", "목록 보기"].map((title) => ( + <Category + key={title} + id={title} + name="reserve" + checked={checked === title} + onClick={() => setChecked(title)} + label={title} + /> + ))} + </CategoryBox> + ); +}; + +const CategoryBox = styled.div` + display: flex; + gap: 8px; + flex-wrap: wrap; +`; + +export default ReserveCategories; diff --git a/src/page/reserve/layouts/ReserveJoinBox.tsx b/src/page/reserve/layouts/ReserveJoinBox.tsx index e1fdf901..2d48dba9 100644 --- a/src/page/reserve/layouts/ReserveJoinBox.tsx +++ b/src/page/reserve/layouts/ReserveJoinBox.tsx @@ -1,25 +1,31 @@ import { XIcon } from "@/assets/icons"; import CheckIcon from "@/assets/icons/CheckIcon"; import { Column, Row } from "@/components/Flex"; -import { Select } from "@/components/atoms"; +import { roomStore } from "@/store/room.store"; import { color, flex, font } from "@/styles"; import React from "react"; +import { toast } from "react-toastify"; +import { useRecoilState } from "recoil"; import styled, { css } from "styled-components"; +import { useCreateReserveMutation } from "../services/mutation.service"; const noticeTexts = [ "1. 사용 가능 시간은 9시 10분부터 10시 20분까지입니다.", "2. 너무 시끄럽게 떠들 경우 퇴실처리될 수 있습니다.", "3. 퇴실시, 소지품을 잘 챙기고 정리정돈 및 청소를 하고 나와야 합니다.", - "4. 예약 후 노쇼나, 위 사항을 어길 경우 예약에 페널티가 있을 수 있습니다.", + "4. 예약 후 노쇼나, 위의 사항들을 어길 경우 페널티가 발생할 수 있습니다.", ]; -const reserveOptions = ["베르 1실", "베르 2실", "베르 3실", "베르 4실"]; +interface IReserveJoinBoxProps { + date: string; +} -const ReserveJoinBox = () => { - const [room, setRoom] = React.useState("베르 1실"); +const ReserveJoinBox = ({ date }: IReserveJoinBoxProps) => { + const [room, setRoom] = useRecoilState(roomStore); const [inputStudent, setInputStudent] = React.useState<string>(""); const [students, setStudents] = React.useState<Array<string>>([]); const [isChecked, setIsChecked] = React.useState(false); + const { mutate } = useCreateReserveMutation(); const handleStudentInputChange = ( e: React.KeyboardEvent<HTMLInputElement>, @@ -36,20 +42,27 @@ const ReserveJoinBox = () => { setStudents(students.filter((x) => x !== student)); }; + const handleCreateReserveClick = () => { + if (!room) return toast.error("베르실을 선택해주세요."); + if (!isChecked) return toast.error("숙지 사항에 동의해주세요."); + + mutate({ + berNumber: room, + reservationTime: date, + reservationUsersName: students.join(", "), + }); + setStudents([]); + setRoom(0); + }; + return ( <Container> <Column gap="6px"> - <ReserveSelectTitle>베르실</ReserveSelectTitle> - <Select - options={reserveOptions} - defaultOption={room} - label="" - handler={setRoom} - width="100px" - /> + <ReserveSelectTitle>선택된 베르실</ReserveSelectTitle> + <StyledTitle>{!room ? `선택하세요` : `베르 ${room}실`}</StyledTitle> </Column> <Column gap="16px"> - <ReserveSelectTitle>학번/이름</ReserveSelectTitle> + <ReserveSelectTitle>팀원 학번/이름 입력</ReserveSelectTitle> <Row alignItems="center" gap="12px"> <StyledInput value={inputStudent} @@ -70,21 +83,23 @@ const ReserveJoinBox = () => { </StudentListItem> ))} </StudentList> - <ReserveSelectTitle>숙지 사항</ReserveSelectTitle> - <NoticeTextList> - {noticeTexts.map((noticeText) => ( - <NoticeTextListItem key={noticeText}> - {noticeText} - </NoticeTextListItem> - ))} - </NoticeTextList> + <Column gap="6px"> + <ReserveSelectTitle>숙지 사항</ReserveSelectTitle> + <NoticeTextList> + {noticeTexts.map((noticeText) => ( + <NoticeTextListItem key={noticeText}> + {noticeText} + </NoticeTextListItem> + ))} + </NoticeTextList> + </Column> <CheckBox onClick={() => setIsChecked(!isChecked)}> <NoticeCheckText /> <CheckButton isChecked={isChecked}> {isChecked && <CheckIcon />} </CheckButton> </CheckBox> - <SubmitButton /> + <SubmitButton onClick={handleCreateReserveClick} /> </Column> </Container> ); @@ -120,7 +135,7 @@ const InfomationText = styled.span` color: ${color.gray}; &:after { - content: "사용하는 모든 학생을 추가해주세요. 엔터 키를 눌러 추가할 수 있어요."; + content: "본인을 제외한 사용하는 모든 학생을 추가해주세요. 엔터 키를 눌러 추가할 수 있어요."; } `; @@ -149,11 +164,13 @@ const NoticeTextList = styled.ul` `; const NoticeTextListItem = styled.li` - ${font.p3}; + ${font.p2}; color: ${color.gray}; + font-weight: 500; `; const CheckBox = styled.div` + width: fit-content; ${flex.HORIZONTAL}; gap: 8px; cursor: pointer; @@ -194,4 +211,9 @@ const SubmitButton = styled.button` } `; +const StyledTitle = styled.span` + ${font.p1}; + font-weight: 600; +`; + export default ReserveJoinBox; diff --git a/src/page/reserve/layouts/ReserveList.tsx b/src/page/reserve/layouts/ReserveList.tsx new file mode 100644 index 00000000..dfb49eed --- /dev/null +++ b/src/page/reserve/layouts/ReserveList.tsx @@ -0,0 +1,28 @@ +import { IReserve } from "@/interfaces"; +import { flex } from "@/styles"; +import React from "react"; +import styled from "styled-components"; +import ReserveListItem from "./ReserveListItem"; + +interface IReserveListProps { + reserveList: Array<IReserve>; +} + +const ReserveList = ({ reserveList }: IReserveListProps) => { + return ( + <Container> + {reserveList.map((reserve) => ( + <ReserveListItem reserve={reserve} /> + ))} + </Container> + ); +}; + +const Container = styled.div` + width: 100%; + height: 100%; + ${flex.COLUMN}; + gap: 12px; +`; + +export default ReserveList; diff --git a/src/page/reserve/layouts/ReserveListItem.tsx b/src/page/reserve/layouts/ReserveListItem.tsx new file mode 100644 index 00000000..83ef8415 --- /dev/null +++ b/src/page/reserve/layouts/ReserveListItem.tsx @@ -0,0 +1,58 @@ +import useUser from "@/hooks/useUser"; +import { IReserve } from "@/interfaces"; +import { color, font } from "@/styles"; +import React from "react"; +import styled from "styled-components"; +import { useDeleteReserveMutation } from "../services/mutation.service"; + +interface IReserveListItemProps { + reserve: IReserve; +} + +const ReserveListItem = ({ reserve }: IReserveListItemProps) => { + const { user } = useUser(); + const { mutate } = useDeleteReserveMutation(); + + const handleReserveDeleteClick = () => { + mutate(reserve.id); + }; + + return ( + <Container> + <StyledTitle>베르 {reserve.berNumber}실 예약</StyledTitle> + <StyledTitle> · {reserve.user.name}님</StyledTitle> + <StyledTitle>| {reserve.reservationUsersName}</StyledTitle> + {reserve.user.id === user.id && ( + <ReserveCancelButton onClick={handleReserveDeleteClick} /> + )} + </Container> + ); +}; + +const Container = styled.div` + width: 100%; + height: fit-content; + padding: 20px 30px; + background-color: ${color.white}; + display: flex; + gap: 4px; +`; + +const StyledTitle = styled.div` + ${font.p2}; +`; + +const ReserveCancelButton = styled.button` + margin-left: auto; + width: fit-content; + padding: 6px 10px; + border-radius: 4px; + background-color: ${color.primary_red}; + color: ${color.white}; + + &:after { + content: "예약 취소"; + } +`; + +export default ReserveListItem; diff --git a/src/page/reserve/layouts/ReserveMap.tsx b/src/page/reserve/layouts/ReserveMap.tsx index 26773f59..592fd73f 100644 --- a/src/page/reserve/layouts/ReserveMap.tsx +++ b/src/page/reserve/layouts/ReserveMap.tsx @@ -1,95 +1,112 @@ -import { flex, font } from "@/styles"; +import { Column, Row } from "@/components/Flex"; +import { roomStore } from "@/store/room.store"; +import { color, flex, font } from "@/styles"; import React from "react"; -import styled from "styled-components"; +import { useRecoilState } from "recoil"; +import styled, { css } from "styled-components"; + +interface IReserveMapProps { + reservedList: Array<number>; +} + +const ReserveMap = ({ reservedList }: IReserveMapProps) => { + const [room, setRoom] = useRecoilState(roomStore); + + const handleRoomButtonClick = (roomNumber: number) => { + if (reservedList.includes(roomNumber)) return; + setRoom(roomNumber); + }; -const ReserveMap = () => { return ( - <Container> - <Title /> - <svg - width="590" - height="220" - viewBox="0 0 590 220" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <rect - x="310.215" - y="0.273438" - width="148.74" - height="60" - fill="#F2F3F7" - /> - <rect - x="155.11" - y="0.273438" - width="148.74" - height="60" - fill="#F2F3F7" - /> - <rect - x="0.00463867" - y="0.273438" - width="148.74" - height="60" - fill="#F2F3F7" - /> - <rect - x="0.00463867" - y="65.0811" - width="458.951" - height="154.352" - fill="#F9F9F9" - /> - <rect - x="465.321" - y="65.0811" - width="123.95" - height="154.352" - fill="#d9d9d9" + <Column gap="8px"> + <Row gap="8px"> + <CommonRoom + isReserved={reservedList.includes(1)} + isClicked={room === 1} + onClick={() => handleRoomButtonClick(1)} /> - <rect - x="465.321" - y="0.273438" - width="123.95" - height="60" - fill="#F9F9F9" + <CommonRoom + isReserved={reservedList.includes(2)} + isClicked={room === 2} + onClick={() => handleRoomButtonClick(2)} /> - <path - d="M196.542 136.331C196.536 140.057 195.428 143.27 191.005 145.546L190.171 144.384C192.222 143.324 193.5 142.094 194.232 140.624L190.445 140.979L190.226 139.681L194.704 139.407C194.875 138.826 194.977 138.211 195.025 137.562H190.869V136.331H196.542ZM196.515 141.185V139.94H199.058V135.114H200.576V147.474H199.058V141.185H196.515ZM213.605 142.087V143.317H210.953V147.515H209.435V143.317H206.55V147.515H205.019V143.317H202.312V142.087H213.605ZM203.693 140.651V135.743H212.224V140.651H203.693ZM205.183 139.435H210.707V136.946H205.183V139.435ZM224.734 135.114V147.474H223.23V135.114H224.734ZM215.136 144.37V136.317H216.64V143.071C218.37 143.044 220.215 142.894 222.123 142.511L222.314 143.782C220.14 144.233 218.124 144.377 216.203 144.37H215.136ZM233.429 136.181V137.425H228.74V139.626H232.937V140.829H228.74V143.372C230.763 143.365 232.308 143.29 234.085 143.003L234.236 144.233C232.288 144.541 230.551 144.603 228.275 144.603H227.236V136.181H233.429ZM235.316 147.474V135.114H236.847V147.474H235.316ZM252.597 135.894V136.974H242.507V135.894H246.814V134.964H248.332V135.894H252.597ZM241.947 142.237V141.144H246.814V140.474C244.606 140.378 243.464 139.886 243.464 138.915C243.464 137.842 244.866 137.35 247.566 137.343C250.266 137.35 251.674 137.842 251.681 138.915C251.674 139.879 250.533 140.378 248.332 140.474V141.144H253.212V142.237H241.947ZM243.273 143.905V142.839H251.804V145.56H244.832V146.284H252.146V147.364H243.287V144.548H250.259V143.905H243.273ZM245.091 138.915C245.091 139.318 245.83 139.517 247.566 139.517C249.302 139.517 250.068 139.318 250.082 138.915C250.068 138.512 249.302 138.334 247.566 138.341C245.83 138.334 245.091 138.512 245.091 138.915Z" - fill="black" + <CommonRoom + isReserved={reservedList.includes(3)} + isClicked={room === 3} + onClick={() => handleRoomButtonClick(3)} /> - <path - d="M58.1885 28.4336C58.1787 30.0107 58.9697 31.6025 60.3369 32.2617L59.6729 33.1309C58.7158 32.6475 58.0273 31.7148 57.6514 30.5918C57.2656 31.793 56.543 32.7891 55.5322 33.2969L54.8682 32.4277C56.2891 31.7539 57.0996 30.0645 57.1045 28.4336V27.0273H58.1885V28.4336ZM60.8643 35.1426V26.3145H61.9482V29.7715H63.3057V30.6992H61.9482V35.1426H60.8643ZM71.7236 30.5625V31.4316H63.6768V30.5625H65.5518V29.4443C64.8828 29.1611 64.5068 28.7168 64.5068 28.1406C64.5068 27.0811 65.7422 26.4609 67.6904 26.4609C69.6338 26.4609 70.8691 27.0811 70.874 28.1406C70.8691 28.7217 70.4883 29.166 69.8193 29.4443V30.5625H71.7236ZM64.6143 33.541C64.6143 32.5254 65.7617 31.959 67.6904 31.9688C69.5898 31.959 70.7422 32.5254 70.7471 33.541C70.7422 34.5664 69.5898 35.123 67.6904 35.123C65.7617 35.123 64.6143 34.5664 64.6143 33.541ZM65.6396 28.1406C65.6299 28.6777 66.3867 28.9658 67.6904 28.9609C68.9893 28.9658 69.751 28.6777 69.751 28.1406C69.751 27.5986 68.9893 27.2959 67.6904 27.3008C66.3867 27.2959 65.6299 27.5986 65.6396 28.1406ZM65.7178 33.541C65.7129 34.0391 66.4062 34.3076 67.6904 34.3027C68.9551 34.3076 69.6533 34.0391 69.6631 33.541C69.6533 33.0479 68.9551 32.7939 67.6904 32.7891C66.4062 32.7939 65.7129 33.0479 65.7178 33.541ZM66.6455 30.5625H68.7354V29.7227C68.418 29.7666 68.0664 29.791 67.6904 29.791C67.3145 29.791 66.9629 29.7666 66.6455 29.7227V30.5625ZM81.6064 26.3145V29.7617H82.915V30.6699H81.6064V35.123H80.5225V26.3145H81.6064ZM74.6436 32.7109C76.792 31.6367 77.9541 30.1426 78.1689 28.1309H75.1221V27.2324H79.2725C79.2676 29.8252 78.1934 32.1055 75.2588 33.5508L74.6436 32.7109ZM90.4736 28.6094V29.4883H84.3799V26.4707H85.4639V28.6094H90.4736ZM83.335 31.2168V30.3379H91.3916V31.2168H83.335ZM84.2725 33.4824C84.2725 32.4424 85.4297 31.8564 87.3486 31.8516C89.2529 31.8564 90.4004 32.4424 90.4053 33.4824C90.4004 34.5176 89.2529 35.1035 87.3486 35.1035C85.4297 35.1035 84.2725 34.5176 84.2725 33.4824ZM85.376 33.4824C85.3711 33.9902 86.0645 34.2686 87.3486 34.2734C88.6133 34.2686 89.3115 33.9902 89.3213 33.4824C89.3115 32.96 88.6133 32.7061 87.3486 32.7012C86.0645 32.7061 85.3711 32.96 85.376 33.4824Z" - fill="#00BC00" + <Wall /> + </Row> + <Row gap="8px"> + <CommunityHall /> + <LongRoom + isReserved={reservedList.includes(4)} + isClicked={room === 4} + onClick={() => handleRoomButtonClick(4)} /> - <path - d="M213.294 28.4336C213.284 30.0107 214.075 31.6025 215.443 32.2617L214.779 33.1309C213.822 32.6475 213.133 31.7148 212.757 30.5918C212.371 31.793 211.649 32.7891 210.638 33.2969L209.974 32.4277C211.395 31.7539 212.205 30.0645 212.21 28.4336V27.0273H213.294V28.4336ZM215.97 35.1426V26.3145H217.054V29.7715H218.411V30.6992H217.054V35.1426H215.97ZM226.829 30.5625V31.4316H218.782V30.5625H220.657V29.4443C219.989 29.1611 219.613 28.7168 219.613 28.1406C219.613 27.0811 220.848 26.4609 222.796 26.4609C224.74 26.4609 225.975 27.0811 225.98 28.1406C225.975 28.7217 225.594 29.166 224.925 29.4443V30.5625H226.829ZM219.72 33.541C219.72 32.5254 220.867 31.959 222.796 31.9688C224.696 31.959 225.848 32.5254 225.853 33.541C225.848 34.5664 224.696 35.123 222.796 35.123C220.867 35.123 219.72 34.5664 219.72 33.541ZM220.745 28.1406C220.736 28.6777 221.492 28.9658 222.796 28.9609C224.095 28.9658 224.857 28.6777 224.857 28.1406C224.857 27.5986 224.095 27.2959 222.796 27.3008C221.492 27.2959 220.736 27.5986 220.745 28.1406ZM220.823 33.541C220.819 34.0391 221.512 34.3076 222.796 34.3027C224.061 34.3076 224.759 34.0391 224.769 33.541C224.759 33.0479 224.061 32.7939 222.796 32.7891C221.512 32.7939 220.819 33.0479 220.823 33.541ZM221.751 30.5625H223.841V29.7227C223.524 29.7666 223.172 29.791 222.796 29.791C222.42 29.791 222.069 29.7666 221.751 29.7227V30.5625ZM236.712 26.3145V29.7617H238.021V30.6699H236.712V35.123H235.628V26.3145H236.712ZM229.749 32.7109C231.898 31.6367 233.06 30.1426 233.275 28.1309H230.228V27.2324H234.378C234.373 29.8252 233.299 32.1055 230.365 33.5508L229.749 32.7109ZM245.579 28.6094V29.4883H239.486V26.4707H240.57V28.6094H245.579ZM238.441 31.2168V30.3379H246.497V31.2168H238.441ZM239.378 33.4824C239.378 32.4424 240.535 31.8564 242.454 31.8516C244.359 31.8564 245.506 32.4424 245.511 33.4824C245.506 34.5176 244.359 35.1035 242.454 35.1035C240.535 35.1035 239.378 34.5176 239.378 33.4824ZM240.482 33.4824C240.477 33.9902 241.17 34.2686 242.454 34.2734C243.719 34.2686 244.417 33.9902 244.427 33.4824C244.417 32.96 243.719 32.7061 242.454 32.7012C241.17 32.7061 240.477 32.96 240.482 33.4824Z" - fill="#00BC00" - /> - <path - d="M369.458 28.4336C369.448 30.0107 370.239 31.6025 371.606 32.2617L370.942 33.1309C369.985 32.6475 369.296 31.7148 368.92 30.5918C368.535 31.793 367.812 32.7891 366.801 33.2969L366.137 32.4277C367.558 31.7539 368.369 30.0645 368.374 28.4336V27.0273H369.458V28.4336ZM372.133 35.1426V26.3145H373.217V29.7715H374.575V30.6992H373.217V35.1426H372.133ZM382.993 30.5625V31.4316H374.946V30.5625H376.821V29.4443C376.152 29.1611 375.776 28.7168 375.776 28.1406C375.776 27.0811 377.011 26.4609 378.959 26.4609C380.903 26.4609 382.138 27.0811 382.143 28.1406C382.138 28.7217 381.757 29.166 381.088 29.4443V30.5625H382.993ZM375.883 33.541C375.883 32.5254 377.031 31.959 378.959 31.9688C380.859 31.959 382.011 32.5254 382.016 33.541C382.011 34.5664 380.859 35.123 378.959 35.123C377.031 35.123 375.883 34.5664 375.883 33.541ZM376.909 28.1406C376.899 28.6777 377.656 28.9658 378.959 28.9609C380.258 28.9658 381.02 28.6777 381.02 28.1406C381.02 27.5986 380.258 27.2959 378.959 27.3008C377.656 27.2959 376.899 27.5986 376.909 28.1406ZM376.987 33.541C376.982 34.0391 377.675 34.3076 378.959 34.3027C380.224 34.3076 380.922 34.0391 380.932 33.541C380.922 33.0479 380.224 32.7939 378.959 32.7891C377.675 32.7939 376.982 33.0479 376.987 33.541ZM377.915 30.5625H380.004V29.7227C379.687 29.7666 379.335 29.791 378.959 29.791C378.583 29.791 378.232 29.7666 377.915 29.7227V30.5625ZM392.875 26.3145V29.7617H394.184V30.6699H392.875V35.123H391.792V26.3145H392.875ZM385.913 32.7109C388.061 31.6367 389.223 30.1426 389.438 28.1309H386.391V27.2324H390.542C390.537 29.8252 389.462 32.1055 386.528 33.5508L385.913 32.7109ZM401.743 28.6094V29.4883H395.649V26.4707H396.733V28.6094H401.743ZM394.604 31.2168V30.3379H402.661V31.2168H394.604ZM395.542 33.4824C395.542 32.4424 396.699 31.8564 398.618 31.8516C400.522 31.8564 401.669 32.4424 401.674 33.4824C401.669 34.5176 400.522 35.1035 398.618 35.1035C396.699 35.1035 395.542 34.5176 395.542 33.4824ZM396.645 33.4824C396.64 33.9902 397.333 34.2686 398.618 34.2734C399.882 34.2686 400.581 33.9902 400.59 33.4824C400.581 32.96 399.882 32.7061 398.618 32.7012C397.333 32.7061 396.64 32.96 396.645 33.4824Z" - fill="#00BC00" - /> - <path - d="M517.743 140.417C517.733 141.994 518.524 143.586 519.892 144.245L519.228 145.114C518.271 144.631 517.582 143.698 517.206 142.575C516.82 143.776 516.098 144.772 515.087 145.28L514.423 144.411C515.844 143.737 516.654 142.048 516.659 140.417V139.011H517.743V140.417ZM520.419 147.126V138.298H521.503V141.755H522.86V142.683H521.503V147.126H520.419ZM531.278 142.546V143.415H523.231V142.546H525.106V141.428C524.438 141.145 524.062 140.7 524.062 140.124C524.062 139.064 525.297 138.444 527.245 138.444C529.188 138.444 530.424 139.064 530.429 140.124C530.424 140.705 530.043 141.149 529.374 141.428V142.546H531.278ZM524.169 145.524C524.169 144.509 525.316 143.942 527.245 143.952C529.145 143.942 530.297 144.509 530.302 145.524C530.297 146.55 529.145 147.106 527.245 147.106C525.316 147.106 524.169 146.55 524.169 145.524ZM525.194 140.124C525.185 140.661 525.941 140.949 527.245 140.944C528.544 140.949 529.306 140.661 529.306 140.124C529.306 139.582 528.544 139.279 527.245 139.284C525.941 139.279 525.185 139.582 525.194 140.124ZM525.272 145.524C525.268 146.022 525.961 146.291 527.245 146.286C528.51 146.291 529.208 146.022 529.218 145.524C529.208 145.031 528.51 144.777 527.245 144.772C525.961 144.777 525.268 145.031 525.272 145.524ZM526.2 142.546H528.29V141.706C527.973 141.75 527.621 141.774 527.245 141.774C526.869 141.774 526.518 141.75 526.2 141.706V142.546ZM539.921 142.351V143.229H536.425V143.972C538.002 144.074 538.939 144.621 538.944 145.534C538.939 146.55 537.787 147.116 535.888 147.116C533.959 147.116 532.812 146.55 532.812 145.534C532.812 144.621 533.749 144.069 535.351 143.967V143.229H531.874V142.351H539.921ZM532.265 141.091C533.886 140.939 534.945 140.28 535.126 139.558H532.606V138.688H539.198V139.558H536.654C536.83 140.285 537.88 140.939 539.54 141.091L539.159 141.95C537.553 141.784 536.366 141.145 535.888 140.251C535.409 141.145 534.237 141.784 532.636 141.95L532.265 141.091ZM533.915 145.534C533.91 146.022 534.604 146.291 535.888 146.286C537.152 146.291 537.851 146.022 537.86 145.534C537.851 145.046 537.152 144.787 535.888 144.782C534.604 144.787 533.91 145.046 533.915 145.534Z" - fill="white" - /> - </svg> - </Container> + </Row> + </Column> ); }; -const Container = styled.div` - ${flex.COLUMN}; - gap: 12px; +const CommonRoom = styled.div<{ isClicked?: boolean; isReserved?: boolean }>` + width: 10vw; + height: 8vh; + cursor: pointer; + ${({ isReserved }) => + isReserved + ? css` + background-color: ${color.on_tertiary}; + cursor: default; + &:after { + content: "예약중"; + } + ` + : css` + background-color: ${color.white}; + color: ${color.green}; + &:after { + content: "예약 가능"; + } + `} + ${({ isClicked }) => + isClicked && + css` + background-color: ${color.primary_blue}; + color: ${color.white}; + &:after { + content: "선택중"; + } + `} + box-shadow: 4px 4px 15px 0 rgba(0, 0, 0, 0.05); + ${flex.CENTER}; + ${font.p3}; +`; + +const LongRoom = styled(CommonRoom)` + width: 13.3vw; + height: 12vw; +`; + +const Wall = styled.div` + width: 10vw; + height: 8vh; + background-color: ${color.light_gray}; + box-shadow: 4px 4px 15px 0 rgba(0, 0, 0, 0.05); + ${flex.CENTER}; + ${font.p3}; `; -const Title = styled.span` - ${font.H5}; +const CommunityHall = styled.div` + width: 100%; + height: 12vw; + background-color: ${color.light_gray}; + box-shadow: 4px 4px 15px 0 rgba(0, 0, 0, 0.03); + ${flex.CENTER}; + ${font.H6}; &:after { - content: "🗺️ 예약할 베르실을 선택해주세요"; + content: "커뮤니티 홀"; } `; From 5b073df036f6d2ee753d67e37633bb128367097e Mon Sep 17 00:00:00 2001 From: Ubinquitous <ubinquitous1@gmail.com> Date: Wed, 4 Oct 2023 19:16:20 +0900 Subject: [PATCH 09/11] =?UTF-8?q?chore(services):=20reserve=20API=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/reserve/services/api.service.ts | 17 +++++++++++ src/page/reserve/services/mutation.service.ts | 30 +++++++++++++++++++ src/page/reserve/services/query.service.ts | 14 +++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/page/reserve/services/api.service.ts create mode 100644 src/page/reserve/services/mutation.service.ts create mode 100644 src/page/reserve/services/query.service.ts diff --git a/src/page/reserve/services/api.service.ts b/src/page/reserve/services/api.service.ts new file mode 100644 index 00000000..9bd297d5 --- /dev/null +++ b/src/page/reserve/services/api.service.ts @@ -0,0 +1,17 @@ +import httpClient from "@/apis/httpClient"; +import { ICreateReserve } from "@/interfaces"; + +export const getReserveList = async (date: string) => { + const { data } = await httpClient.reserve.get({ params: { date } }); + return data; +}; + +export const createReserve = async (reserve: ICreateReserve) => { + const { data } = await httpClient.reserve.post(reserve); + return data; +}; + +export const deleteReserve = async (id: number) => { + const { data } = await httpClient.reserve.deleteById({ params: { id } }); + return data; +}; diff --git a/src/page/reserve/services/mutation.service.ts b/src/page/reserve/services/mutation.service.ts new file mode 100644 index 00000000..0f7d7838 --- /dev/null +++ b/src/page/reserve/services/mutation.service.ts @@ -0,0 +1,30 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { KEY } from "@/constants"; +import { ICreateReserve } from "@/interfaces"; +import { toast } from "react-toastify"; +import { createReserve, deleteReserve } from "./api.service"; + +export const useCreateReserveMutation = () => { + const queryClient = useQueryClient(); + + return useMutation( + async (reserve: ICreateReserve) => createReserve(reserve), + { + onSuccess: () => { + queryClient.invalidateQueries([KEY.RESERVE]); + toast.success("베르실 예약에 성공했어요!"); + }, + }, + ); +}; + +export const useDeleteReserveMutation = () => { + const queryClient = useQueryClient(); + + return useMutation(async (id: number) => deleteReserve(id), { + onSuccess: () => { + queryClient.invalidateQueries([KEY.RESERVE]); + toast.success("예약을 취소했어요."); + }, + }); +}; diff --git a/src/page/reserve/services/query.service.ts b/src/page/reserve/services/query.service.ts new file mode 100644 index 00000000..9ebdbd94 --- /dev/null +++ b/src/page/reserve/services/query.service.ts @@ -0,0 +1,14 @@ +import { KEY } from "@/constants"; +import { useQuery } from "@tanstack/react-query"; +import { getReserveList } from "./api.service"; + +interface IUseReserveListQueryProps { + date: string; +} + +export const useReserveListQuery = ({ date }: IUseReserveListQueryProps) => { + const { data, ...queryRest } = useQuery([KEY.RESERVE], async () => + getReserveList(date), + ); + return { data, ...queryRest }; +}; From ce086452a0e6b3ff73d11b56ff770bd59fe87da3 Mon Sep 17 00:00:00 2001 From: Ubinquitous <ubinquitous1@gmail.com> Date: Wed, 4 Oct 2023 19:16:37 +0900 Subject: [PATCH 10/11] =?UTF-8?q?chore(store):=20reserve=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20store=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/reserveViewType.store.ts | 6 ++++++ src/store/room.store.ts | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/store/reserveViewType.store.ts create mode 100644 src/store/room.store.ts diff --git a/src/store/reserveViewType.store.ts b/src/store/reserveViewType.store.ts new file mode 100644 index 00000000..a90e90bb --- /dev/null +++ b/src/store/reserveViewType.store.ts @@ -0,0 +1,6 @@ +import { atom } from "recoil"; + +export const reserveViewTypeStore = atom<string>({ + key: "reserveViewTypeStore", + default: "신청하기", +}); diff --git a/src/store/room.store.ts b/src/store/room.store.ts new file mode 100644 index 00000000..c942349e --- /dev/null +++ b/src/store/room.store.ts @@ -0,0 +1,6 @@ +import { atom } from "recoil"; + +export const roomStore = atom<number>({ + key: "roomStore", + default: 0, +}); From d7c22bd154acdffae53233cb6fde03a81707e29a Mon Sep 17 00:00:00 2001 From: Ubinquitous <ubinquitous1@gmail.com> Date: Wed, 4 Oct 2023 19:26:06 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix(Select):=20handler=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=97=90=EB=9F=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/atoms/Select.tsx | 4 ++-- src/page/reserve/index.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/atoms/Select.tsx b/src/components/atoms/Select.tsx index 7c6beafa..8a8083eb 100644 --- a/src/components/atoms/Select.tsx +++ b/src/components/atoms/Select.tsx @@ -9,7 +9,7 @@ interface ISelectProps { label: string; width?: string; handler: - | React.Dispatch<React.SetStateAction<string | number>> + | React.Dispatch<React.SetStateAction<string>> | ((props: string | number) => void); } @@ -22,7 +22,7 @@ const Select = ({ }: ISelectProps) => { const [isHover, setIsHover] = React.useState(true); - const toggleHandler = (option: string | number) => { + const toggleHandler = (option: string) => { handler(option); setIsHover(true); }; diff --git a/src/page/reserve/index.tsx b/src/page/reserve/index.tsx index b168a97f..d5108dd6 100644 --- a/src/page/reserve/index.tsx +++ b/src/page/reserve/index.tsx @@ -1,4 +1,3 @@ -import { DesktopIcon } from "@/assets/icons"; import { Column } from "@/components/Flex"; import { Aside } from "@/components/common"; import { color, flex, font } from "@/styles";