diff --git a/src/App.tsx b/src/App.tsx index 2f45c335..2ee94504 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,7 +19,7 @@ import { CouponRegistration } from './pages/coupon-registration'; import { Main } from './pages/main'; import Room from './pages/room-management'; import RoomRegistration from './pages/room-registration'; -import { RoomUpdate } from './pages/room-update'; +import RoomUpdate from './pages/room-update'; import { RootLayout } from './layout'; import './App.less'; import { RoomLayout } from '@components/room/room-layout'; @@ -90,10 +90,12 @@ function App() { element={} /> - } - /> + }> + } + /> + } /> diff --git a/src/api/room/index.ts b/src/api/room/index.ts index 178c1142..d245e5d5 100644 --- a/src/api/room/index.ts +++ b/src/api/room/index.ts @@ -1,12 +1,11 @@ import { Response } from '@/types/api'; import { instance } from '..'; import { - RoomListData, + RoomListResponseData, RoomData, RoomPostResponseData, AccommodationData, } from './type'; -import { useParams } from 'react-router-dom'; export const ROOM_API = { addRoom: (data: RoomData, accommodationId: string) => @@ -16,10 +15,10 @@ export const ROOM_API = { data, }, ), - // getRoom: (params: RoomAddParams) => - // instance.get>('/api/rooms/list/{accommodationId}?pageSize={pageSize}&pageNum={pageNum}', { - // data: params, - // }), + getRoomList: (accommodationId: string) => + instance.get>( + `/api/rooms/list/${accommodationId}?pageSize={pageSize}&pageNum={pageNum}`, + ), // editRoom: (params: RoomEditParams) => // instance.patch>('/api/rooms/{roomId}', params), }; diff --git a/src/api/room/type.ts b/src/api/room/type.ts index 0630cbc8..ac332301 100644 --- a/src/api/room/type.ts +++ b/src/api/room/type.ts @@ -1,4 +1,19 @@ -export type RoomListData = { +export type onFinishValues = { + 'room-name': string; + price: string; + defaultCapacity: number; + maxCapacity: number; + checkInTime: moment.Moment; + checkOutTime: moment.Moment; + count: number; +}; + +export type RoomListResponseData = { + pageNum: number; + pageSize: number; + totalPages: number; + totalElements: number; + isLast: boolean; rooms: RoomData[]; }; diff --git a/src/assets/data/roomListData.json b/src/assets/data/roomListData.json new file mode 100644 index 00000000..62815262 --- /dev/null +++ b/src/assets/data/roomListData.json @@ -0,0 +1,121 @@ +{ + "message": "성공적으로 객실 목록을 조회 했습니다.", + "data": { + "pageNum": 1, + "pageSize": 5, + "totalPages": 2, + "totalElements": 8, + "isLast": false, + "rooms": [ + { + "id": 286, + "name": "봄", + "basePrice": 100000, + "discountPrice": 80000, + "defaultCapacity": 2, + "maxCapacity": 6, + "checkInTime": "15:00", + "checkOutTime": "12:00", + "status": "SELLING", + "count": 5, + "images": [ + { + "url": "pension_room2_2.webp" + }, + { + "url": "pension_room2_1.webp" + } + ], + "coupons": [ + { + "id": 56, + "name": "10% 할인", + "price": 90000 + }, + { + "id": 57, + "name": "2000원 할인", + "price": 80000 + } + ], + "options": { + "airCondition": true, + "tv": true, + "internet": true + } + }, + { + "id": 286, + "name": "봄", + "basePrice": 100000, + "discountPrice": 80000, + "defaultCapacity": 2, + "maxCapacity": 6, + "checkInTime": "15:00", + "checkOutTime": "12:00", + "count": 5, + "images": [ + { + "fileName": "pension_room2_2.webp" + }, + { + "fileName": "pension_room2_1.webp" + } + ], + "coupons": [ + { + "id": 56, + "name": "10% 할인", + "price": 90000 + }, + { + "id": 57, + "name": "2000원 할인", + "price": 80000 + } + ], + "options": { + "airCondition": true, + "tv": true, + "internet": true + } + }, + { + "id": 286, + "name": "봄", + "basePrice": 100000, + "discountPrice": 80000, + "defaultCapacity": 2, + "maxCapacity": 6, + "checkInTime": "15:00", + "checkOutTime": "12:00", + "count": 5, + "images": [ + { + "fileName": "pension_room2_2.webp" + }, + { + "fileName": "pension_room2_1.webp" + } + ], + "coupons": [ + { + "id": 56, + "name": "10% 할인", + "price": 90000 + }, + { + "id": 57, + "name": "2000원 할인", + "price": 80000 + } + ], + "options": { + "airCondition": true, + "tv": true, + "internet": true + } + } + ] + } +} diff --git a/src/components/init/ButtonContainer.tsx b/src/components/init/ButtonContainer.tsx index c489ad4e..783806c6 100644 --- a/src/components/init/ButtonContainer.tsx +++ b/src/components/init/ButtonContainer.tsx @@ -14,6 +14,7 @@ export const ButtonContainer = ({ isValid, }: ButtonContainerProps) => { const navigate = useNavigate(); + const handlePreviousClick = () => { if (window.location.pathname === ROUTES.INIT_ACCOMMODATION_REGISTRATION) navigate(ROUTES.INIT); diff --git a/src/components/room/room-buttons/index.tsx b/src/components/room/room-buttons/index.tsx index f4eb5ad0..13343696 100644 --- a/src/components/room/room-buttons/index.tsx +++ b/src/components/room/room-buttons/index.tsx @@ -17,21 +17,16 @@ export const ButtonContainer = ({ return ( - {buttonStyle === 'register' && ( + { <> 이전 - - {buttonStyle === 'register' ? '등록 요청' : '수정 요청'} + + {buttonStyle === 'register' ? '등록 요청' : '수정 완료'} - )} + } ); }; @@ -39,17 +34,9 @@ export const ButtonContainer = ({ export const StyledWrapper = styled.div` width: 100%; - display: ${(props) => - props.$buttonStyle === 'register' || props.$buttonStyle === 'edit' - ? 'grid' - : 'block'}; - grid-template-columns: ${(props) => - props.$buttonStyle === 'register' - ? '1fr 2.5fr' - : props.$buttonStyle === 'edit' - ? 'auto' - : 'none'}; - gap: ${(props) => (props.$buttonStyle === 'register' ? '10px' : '0')}; + display: grid; + grid-template-columns: 1fr 2.5fr; + gap: 10px; `; export const StyledButton = styled(Button)` diff --git a/src/components/room/room-buttons/type.ts b/src/components/room/room-buttons/type.ts index 947d7f2b..e32c332e 100644 --- a/src/components/room/room-buttons/type.ts +++ b/src/components/room/room-buttons/type.ts @@ -1,8 +1,8 @@ export type ButtonContainerProps = { - buttonStyle: 'register' | 'edit'; + buttonStyle: 'register' | 'update'; isValid: boolean; }; export type ButtonContainerStyledWrapperProps = { - $buttonStyle: 'register' | 'edit'; + $buttonStyle: 'register' | 'update'; }; diff --git a/src/components/room/room-layout/index.tsx b/src/components/room/room-layout/index.tsx index f80ba1b9..7aa23524 100644 --- a/src/components/room/room-layout/index.tsx +++ b/src/components/room/room-layout/index.tsx @@ -20,12 +20,16 @@ export const RoomLayout = () => { }, }; - const currentRoute = Object.keys(routeConfig).find( - (route) => location.pathname === route, + const currentRouteKey = Object.keys(routeConfig).find((routeKey) => + location.pathname.includes(routeKey), ); - const { pageTitle = '객실 추가 등록' } = - routeConfig[currentRoute as keyof typeof routeConfig] || {}; + const currentRouteConfig = + routeConfig[currentRouteKey as keyof typeof routeConfig]; + + const pageTitle = currentRouteConfig + ? currentRouteConfig.pageTitle + : '객실 추가 등록'; return ( { + const [isOnSale, setIsOnSale] = useState(true); + + const handleInputChange = (isOnSale: boolean) => { + setIsOnSale(!isOnSale); + return; + }; + + return ( + + + + 객실 상태 + + + + + + + + + {!isOnSale ? '판매 중' : '판매 중지'} + + + + + ); +}; + +const StyledInputWrapper = styled.div` + margin-bottom: 35px; + + .ant-form-item-control { + width: 100%; + } + + .ant-input { + font-size: 16px; + } +`; + +const StyledTextBoxWrapper = styled.div` + margin-bottom: 24px; + margin-right: 12px; + + &:last-child { + margin-right: 0; + } +`; + +const StyledRow = styled.div` + height: 30px; + display: flex; + justify-content: center; + align-items: center; +`; + +const StyledDesc = styled.div` + display: flex; + align-items: center; + margin-bottom: 16px; +`; + +const StyledSwitch = styled(Switch)` + margin-right: 7px; +`; diff --git a/src/hooks/room/useAddRoom.ts b/src/hooks/room/useAddRoom.ts deleted file mode 100644 index 641834fe..00000000 --- a/src/hooks/room/useAddRoom.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useAddRoom } from '@queries/room'; - -// export const postRoomData = () => { -// useAddRoom(); -// onSuccess: (response) => { -// console.log('postRoomData, hooks, success'); -// }, -// }; diff --git a/src/layout.tsx b/src/layout.tsx index d5b01448..8dc77bef 100644 --- a/src/layout.tsx +++ b/src/layout.tsx @@ -7,16 +7,20 @@ import { colors } from '@/constants/colors'; export const RootLayout = () => { const location = useLocation(); const currentRoute = location.pathname; + const isRoomRegistrationRoute = currentRoute.includes( + ROUTES.ROOM_REGISTRATION, + ); + const isRoomUpdateRoute = currentRoute.includes(ROUTES.ROOM_UPDATE); + + const shouldApplyGrayBackground = + isRoomRegistrationRoute || isRoomUpdateRoute; return ( Header diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index cbb9e6ab..cb9a6359 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -25,7 +25,7 @@ import { buyCouponResolver, getCouponRoomListResolver, } from './coupon-registration'; -import { postRoomResolver } from './room'; +import { postRoomResolver, getRoomListResolver } from './room'; const email = 'ivegaeul@naver.com'; const verificationCode = '020924'; @@ -70,4 +70,8 @@ export const handlers = [ http.delete('/api/points/charges/1', deleteOrderCancelResolver), http.post(`/api/rooms/${accommodationId}`, postRoomResolver), + http.get( + `/api/rooms/list/${accommodationId}?pageSize={pageSize}&pageNum={pageNum}`, + getRoomListResolver, + ), ]; diff --git a/src/mocks/room/index.ts b/src/mocks/room/index.ts index e3e14e40..542d0ae8 100644 --- a/src/mocks/room/index.ts +++ b/src/mocks/room/index.ts @@ -1,6 +1,11 @@ import { HttpResponse } from 'msw'; import roomData from '@assets/data/roomData.json'; +import roomListData from '@assets/data/roomListData.json'; export const postRoomResolver = () => { return HttpResponse.json(roomData, { status: 200 }); }; + +export const getRoomListResolver = () => { + return HttpResponse.json(roomListData, { status: 200 }); +}; diff --git a/src/pages/room-management/index.tsx b/src/pages/room-management/index.tsx index ec7d7e7a..2e41dcb9 100644 --- a/src/pages/room-management/index.tsx +++ b/src/pages/room-management/index.tsx @@ -2,21 +2,49 @@ import RoomCard from '../../components/room/room-card'; import { Card, Button, Row } from 'antd'; import { TextBox } from '@components/text-box'; import styled from 'styled-components'; -import { colors } from '@/constants/colors'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useGetRoomList } from '@queries/room'; +import { ROUTES } from '@/constants/routes'; const RoomManagement = () => { + const navigate = useNavigate(); + const { accommodationId: tempAccommodationId } = useParams(); + const accommodationId = tempAccommodationId || ''; + const { data, isLoading, error } = useGetRoomList(accommodationId); + + console.log(data); + // const { getList } = useGetRoomList(accommodationId as string, { + // onSuccess() { + // console.log(data) + // // message.success({ + // // content: '등록되었습니다', + // // className: 'coupon-message', + // // }); + // // navigate(`/${accommodationId}${ROUTES.ROOM}`); + // // setSelectedImages([]); + // // setSelectedOptions({ + // // airCondition: false, + // // tv: false, + // // internet: false, + // // }); + // } + // }); + return ( - - - 객실 관리 - - - - + 객실추가 + + + + 객실 관리 - - + navigate(`/${accommodationId}/room/registration`)} + > + + 객실추가 + + + ); @@ -36,11 +64,15 @@ const StyledTitleButton = styled(Row)` margin-bottom: 16px; `; -const StyledAddRoomButton = styled(Button)` - border-radius: 2px; - background: ${colors.primary}; - color: white; +const StyledButton = styled(Button)` + font-size: 18px; + font-weight: 700; + display: flex; align-items: center; - justify-content: center; + margin-top: 2px; +`; + +const StyledFixedTitle = styled.div` + // position: fixed; `; diff --git a/src/pages/room-registration/index.tsx b/src/pages/room-registration/index.tsx index ea3e8c1a..30ef610e 100644 --- a/src/pages/room-registration/index.tsx +++ b/src/pages/room-registration/index.tsx @@ -9,19 +9,23 @@ import { PriceContainer } from '@components/room/price-container'; import { CapacityContainer } from '@components/room/capacity-container'; import { CountContainer } from '@components/room/num-of-rooms-container'; import { TimeContainer } from '@components/room/time-container'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { checkedRoomOptions, selectedInitRoomFilesState, } from '@stores/init/atoms'; -import { RoomData } from '@api/room/type'; +import { RoomData, onFinishValues } from '@api/room/type'; import { useAddRoom } from '@queries/room'; import { useNavigate, useParams } from 'react-router-dom'; +import { capacityHasError, priceHasError } from '@stores/room/atoms'; +import { useState, useEffect } from 'react'; import { ROUTES } from '@/constants/routes'; import { AxiosError } from 'axios'; const RoomRegistration = () => { - const isValid = true; + const navigate = useNavigate(); + const [isValid, setIsValid] = useState(false); + const roomOptions = { tv: 'TV', airCondition: '에어컨', @@ -29,7 +33,6 @@ const RoomRegistration = () => { }; const { accommodationId } = useParams(); - const navigate = useNavigate(); const [form] = Form.useForm(); const { mutate } = useAddRoom(accommodationId as string, { @@ -38,8 +41,8 @@ const RoomRegistration = () => { content: '등록되었습니다', }); navigate(`/${accommodationId}${ROUTES.ROOM}`); - setSelectedInitRoomFiles([]); - setSelectedInitRoomOptions({ + setSelectedImages([]); + setSelectedOptions({ airCondition: false, tv: false, internet: false, @@ -51,13 +54,18 @@ const RoomRegistration = () => { }, }); - const [selectedImages, setSelectedInitRoomFiles] = useRecoilState( + const [selectedImages, setSelectedImages] = useRecoilState( selectedInitRoomFilesState, ); - const [selectedOptions, setSelectedInitRoomOptions] = + const [selectedOptions, setSelectedOptions] = useRecoilState(checkedRoomOptions); - const onFinish = (value: any) => { + const [sameRoomName, setSameRoomName] = useState(false); + const [recoilUpdated, setRecoilUpdated] = useState(false); + const priceError = useRecoilValue(priceHasError); + const capacityError = useRecoilValue(capacityHasError); + + const onFinish = (value: onFinishValues) => { const data: RoomData = { name: value['room-name'], price: parseInt(value['price'].replace(',', '')), @@ -69,26 +77,63 @@ const RoomRegistration = () => { options: selectedOptions, images: selectedImages, }; + setRecoilUpdated(true); mutate(data); }; - // async (formData: RoomData) => { - // try { - // //postRoomData(formData); - // //const res = await axios.post('', formData); - // const res = useAddRoom; - // console.log('Success:', res.data); - // } catch (error) { - // console.log(error); - // } - // }; + + const areFormFieldsValid = () => { + const values = form.getFieldsValue(); + + const conditions = + values['room-name'] && + values['price'] && + values['checkInTime'] && + values['checkOutTime'] && + selectedImages.length !== 0; + + console.log( + values['room-name'], + values['price'], + values['checkInTime'], + values['checkOutTime'], + selectedImages.length, + ); + + return ( + !form.getFieldsError().some(({ errors }) => errors.length) && + conditions && + !priceError && + !capacityError + ); + }; + + useEffect(() => { + setIsValid(areFormFieldsValid()); + }, [ + form, + selectedImages, + selectedOptions, + priceError, + capacityError, + recoilUpdated, + ]); + + const handleFormValuesChange = () => { + setIsValid(areFormFieldsValid()); + }; return ( -
+ diff --git a/src/pages/room-update/index.tsx b/src/pages/room-update/index.tsx index d98c1217..d96fa673 100644 --- a/src/pages/room-update/index.tsx +++ b/src/pages/room-update/index.tsx @@ -1,3 +1,158 @@ -export const RoomUpdate = () => { - return
room update page
; +import { colors } from '@/constants/colors'; +import { styled } from 'styled-components'; +import { Form, message } from 'antd'; +import { ButtonContainer } from '@components/room/room-buttons'; +import { CheckBoxContainer } from '@components/init/CheckBoxContainer'; +import { ImageUploadContainer } from '@components/init/ImageUploadContainer'; +import { NameContainer } from '@components/init/NameContainer'; +import { PriceContainer } from '@components/room/price-container'; +import { CapacityContainer } from '@components/room/capacity-container'; +import { CountContainer } from '@components/room/num-of-rooms-container'; +import { TimeContainer } from '@components/room/time-container'; +import { StatusContainer } from '@components/room/status-container'; +import { useRecoilState } from 'recoil'; +import { + checkedRoomOptions, + selectedInitRoomFilesState, +} from '@stores/init/atoms'; +import { RoomData } from '@api/room/type'; +import { useAddRoom } from '@queries/room'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { ROUTES } from '@/constants/routes'; +import { AxiosError } from 'axios'; + +const RoomUpdate = () => { + const navigate = useNavigate(); + const [isValid, setIsValid] = useState(false); + + const roomOptions = { + tv: 'TV', + airCondition: '에어컨', + internet: '인터넷', + }; + + const { accommodationId } = useParams(); + + const [form] = Form.useForm(); + const { mutate } = useAddRoom(accommodationId as string, { + onSuccess() { + message.success({ + content: '등록되었습니다', + className: 'coupon-message', + }); + navigate(`/${accommodationId}${ROUTES.ROOM}`); + setSelectedRoomFiles([]); + setSelectedRoomOptions({ + airCondition: false, + tv: false, + internet: false, + }); + }, + onError(error) { + if (error instanceof AxiosError) + message.error('요청에 실패했습니다 잠시 후 다시 시도해주세요'); + }, + }); + + const [selectedImages, setSelectedRoomFiles] = useRecoilState( + selectedInitRoomFilesState, + ); + const [selectedOptions, setSelectedRoomOptions] = + useRecoilState(checkedRoomOptions); + + const onFinish = (value: any) => { + const data: RoomData = { + name: value['room-name'], + price: parseInt(value['price'].replace(',', '')), + defaultCapacity: value.defaultCapacity, + maxCapacity: value.maxCapacity, + checkInTime: value['checkInTime'].format('HH:mm'), + checkOutTime: value['checkOutTime'].format('HH:mm'), + amount: value.count, + options: selectedOptions, + images: selectedImages, + }; + mutate(data); + }; + + const areFormFieldsValid = () => { + const values = form.getFieldsValue(); + + const conditions = + values['room-name'] && + values['price'] && + values['checkInTime'] && + values['checkOutTime'] && + selectedImages.length !== 0; + + return ( + !form.getFieldsError().some(({ errors }) => errors.length) && conditions + ); + }; + + useEffect(() => { + setIsValid(areFormFieldsValid()); + }, [form, selectedImages, selectedOptions]); + + const handleFormValuesChange = () => { + setIsValid(areFormFieldsValid()); + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; + +export default RoomUpdate; + +const StyledWrapper = styled.div` + border-radius: 8px; + width: 100%; + padding: 40px; + background-color: ${(props) => props.color}; +`; + +const StyledInputWrapper = styled.div` + margin-bottom: 48px; +`; + +const StyledCenterVertically = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin-top: 0px; +`; diff --git a/src/queries/room/index.ts b/src/queries/room/index.ts index ad94de97..6aa14fd7 100644 --- a/src/queries/room/index.ts +++ b/src/queries/room/index.ts @@ -6,7 +6,11 @@ import { UseMutationOptions, } from '@tanstack/react-query'; import { Response } from '@/types/api'; -import { RoomData, RoomPostResponseData } from '@api/room/type'; +import { + RoomData, + RoomPostResponseData, + RoomListResponseData, +} from '@api/room/type'; import { ROOM_API } from '@api/room'; export const useAddRoom = ( @@ -25,3 +29,20 @@ export const useAddRoom = ( ...options, }); }; + +export const useGetRoomList = ( + accommodationId: string, + options?: UseQueryOptions< + AxiosResponse>, + AxiosError, + RoomListResponseData + >, +) => { + return useQuery< + AxiosResponse>, + AxiosError, + RoomListResponseData + >(['getRoomList'], () => ROOM_API.getRoomList(accommodationId), { + ...options, + }); +};