diff --git a/src/components/DoctorDashBoard/DoctorReserve.jsx b/src/components/DoctorDashBoard/DoctorReserve.jsx deleted file mode 100644 index 1a9b72c..0000000 --- a/src/components/DoctorDashBoard/DoctorReserve.jsx +++ /dev/null @@ -1,97 +0,0 @@ -import styled from "styled-components"; -import IconDate from "../../assets/icons/icondate.png"; -import { useState } from "react"; - -const CardContainer = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 15px; - border: 1px solid #e0e0e0; - border-radius: 10px; - width: 320px; - height: 120px; - background-color: #ffffff; - margin-bottom: 20px; -`; - -const DateContainer = styled.div` - display: flex; - align-items: center; - margin-top: 5px; -`; - -const UserName = styled.div` - font-size: 25px; - font-weight: bold; - display: flex; - align-items: baseline; - - span { - font-size: 15px; - margin-left: 5px; - font-weight: 300; - } -`; - -const Icon = styled.img` - width: 18px; - height: 18px; - margin-right: 5px; -`; - -const InfoText = styled.span` - font-size: 14px; - font-weight: 300; - margin-left: 10px; -`; - -const Button = styled.button` - width: 120px; - height: 28px; - background-color: ${(props) => - props.cancelButton ? "#F3F3F3" : props.clicked ? "#888888" : "#3592FF"}; - color: ${(props) => - props.cancelButton ? "#000000" : props.clicked ? "#444444" : "#FEFDFD"}; - border: ${(props) => (props.cancelButton ? "1px solid #BBBBBB" : "none")}; - border-radius: 10px; - border: none; - cursor: pointer; - font-size: 14px; - -`; - -const ButtonContainer = styled.div` - display: flex; - justify-content: space-between; - width: 100%; - margin-top: 5px; -`; - -const DoctorReserve = () => { - const [buttonState, setButtonState] = useState(false); - - const toggleButtonState = () => { - setButtonState(!buttonState); - }; - - return ( - - - 오소현 - - - - 2023/08/30 16:00 - - - - - - - ); -}; - -export default DoctorReserve; diff --git a/src/components/DoctorDashBoard/DoctorUntactList.jsx b/src/components/DoctorDashBoard/DoctorUntactList.jsx deleted file mode 100644 index aa16654..0000000 --- a/src/components/DoctorDashBoard/DoctorUntactList.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import styled from "styled-components"; -import DoctorReserve from "./DoctorReserve"; - -const Container = styled.div` - width: 800px; - height: 555px; - margin: 0 auto; - padding: 20px; - border: 1px solid #0064ff; - border-radius: 10px; - background-color: #ffffff; - font-family: "Spoqa Han Sans Neo", "sans-serif"; - position: relative; - display: flex; - flex-wrap: wrap; - gap: 0px 80px; -`; - -const Title = styled.h1` - font-size: 28px; - font-weight: bold; - color: #333; - display: inline-block; -`; - -const Divider = styled.hr` - width: 100%; - height: 1px; - background-color: #d9d9d9; - border: none; - margin-bottom: 20px; -`; - -const DoctorUntactList = () => { - return ( - - 비대면 진료 예약 목록 - - - - - - - - - ); -}; - -export default DoctorUntactList; diff --git a/src/components/Pagination/Pagination.jsx b/src/components/Pagination/Pagination.jsx index 0fa2599..aee34a1 100644 --- a/src/components/Pagination/Pagination.jsx +++ b/src/components/Pagination/Pagination.jsx @@ -69,7 +69,6 @@ function Pagination({}) { const { totalPage } = state || {}; const handlePageClick = (data) => { - console.log(data); dispatch({ type: "page", payload: data.selected + 1, diff --git a/src/components/Reservation/ReservationCreateModal.jsx b/src/components/Reservation/ReservationCreateModal.jsx index b936421..bd473ef 100644 --- a/src/components/Reservation/ReservationCreateModal.jsx +++ b/src/components/Reservation/ReservationCreateModal.jsx @@ -7,13 +7,16 @@ import ToggleButton from "../Button/ToggleButton.jsx"; import Button from "../Button/Button.jsx"; import CalenderMonth from "../Calender/CalenderMonth.jsx"; import { ReducerContext } from "../../reducer/context.js"; -import { useEffect, useReducer, useState } from "react"; +import { useEffect, useMemo, useReducer, useState } from "react"; import { intialReserveCreateState, reserveCreateReducer, } from "../../reducer/reservation-create.js"; import dayjs from "dayjs"; -import { createReservation } from "../../librarys/api/reservation.js"; +import { + createReservation, + getAdminReservationTime, +} from "../../librarys/api/reservation.js"; const Container = styled.div` display: flex; @@ -72,14 +75,29 @@ export const ReservationCreateModal = () => { ); const [times, setTimes] = useState(createTimes()); - const { index, available, description } = state; + const { adminId, index, disabledTime, description, year, month, date } = + state; + const serverTime = useMemo( + () => [year, month + 1, date].join("-"), + [year, month, date], + ); useEffect(() => { dispatch({ - type: "available", - payload: [true, true, true, false, false], + type: "disabledTime", + payload: [36, 37, 38, 39, 40], }); - }, []); + + (async () => { + const response = await getAdminReservationTime(adminId, serverTime); + const payload = response.map((item) => item.index); + + dispatch({ + type: "disabledTime", + payload, + }); + })(); + }, [serverTime]); function onSelect(id) { dispatch({ @@ -100,7 +118,7 @@ export const ReservationCreateModal = () => { // state.adminId, // "ldh", // state.description, - // [state.year, state.month + 1, state.date].join("-"), + // serverTime, // state.index, // ); console.log(state); @@ -123,7 +141,7 @@ export const ReservationCreateModal = () => { onSelect(item.index)} > {item.value} diff --git a/src/components/Reservation/ReservationInfoModal.jsx b/src/components/Reservation/ReservationInfoModal.jsx index 8b6666f..31c2f87 100644 --- a/src/components/Reservation/ReservationInfoModal.jsx +++ b/src/components/Reservation/ReservationInfoModal.jsx @@ -3,12 +3,14 @@ import PropTypes from "prop-types"; import Modal from "../Common/Modal.jsx"; -import { selectProps } from "../../redux/modalSlice.js"; +import { hide, selectProps } from "../../redux/modalSlice.js"; import { useSelector } from "react-redux"; import ModalTitleText from "../Common/ModalTitleText.jsx"; import ChartSummary from "../Chart/ChartSummary.jsx"; import InputAreaContainer from "../Input/InputAreaContainer.jsx"; import Button from "../Button/Button.jsx"; +import { removeReservation } from "../../librarys/api/reservation.js"; +import { useDispatch } from "react-redux"; const Container = styled.div` display: flex; @@ -36,8 +38,17 @@ const ButtonContainer = styled.div` const id = "reservation_detail"; const ReservationInfoModal = () => { + const dispatch = useDispatch(); const value = useSelector(selectProps(id)); - const { description, aiSummary } = value || {}; + const { reservationId, description, aiSummary } = value || {}; + + async function onCancelButtonClick() { + const response = await removeReservation(reservationId); + 1; + alert("예약이 성공적으로 취소되었습니다."); + + dispatch(hide(id)); + } return ( @@ -48,7 +59,7 @@ const ReservationInfoModal = () => { - + diff --git a/src/components/Reservation/ReservationItem.jsx b/src/components/Reservation/ReservationItem.jsx index 4f1248a..695da38 100644 --- a/src/components/Reservation/ReservationItem.jsx +++ b/src/components/Reservation/ReservationItem.jsx @@ -95,6 +95,12 @@ const dummyText = `그러나 한 시와 강아지, 가을 보고, 새워 까닭 const notReadyText = `아직 비대면 진료 요약이 생성되지 않았습니다.`; +const buttonStyleList = { + normal: { type: "primary", text: "입장" }, + complete: { type: "disabled", text: "종료되었습니다" }, + notReady: { type: "disabled", text: "예약 시간이 아닙니다" }, +}; + const ReservationItem = ({ id, name, role, dept, date, index }) => { const dispatch = useDispatch(); const navigate = useNavigate(); @@ -125,28 +131,33 @@ const ReservationItem = ({ id, name, role, dept, date, index }) => { const time = useMemo(() => dayjs().diff(fullDate, "minute"), [fullDate]); const isUser = useMemo(() => classNames({ user: role === "USER" }), [role]); - const isOpen = time >= -10; - const isDone = time > 30; + const isRoomOpen = time >= -10; + const isReservationDone = time > 30; - function firstButton() { - if (isDone) { - return 종료되었습니다; - } else if (isOpen) { - return ( - navigate("/untact/meeting/" + id)}> - 입장 - - ); + const buttonStyle = useMemo(() => { + if (isReservationDone) { + return buttonStyleList.complete; + } else if (isRoomOpen) { + return buttonStyleList.normal; } else { - return 예약 시간이 아닙니다; + return buttonStyleList.notReady; } + }, [isRoomOpen, isReservationDone]); + + function onJoinButtonClick() { + if (isReservationDone || !isRoomOpen) { + return; + } + + navigate("/untact/meeting/" + id); } - function showDetail() { + function onInfoButtonClick() { dispatch( show({ id: "reservation_detail", props: { + reservationId: 999, chartDetail: null, description: dummyText, aiSummary: notReadyText, @@ -182,8 +193,10 @@ const ReservationItem = ({ id, name, role, dept, date, index }) => { - {firstButton()} - + + {buttonStyle.text} + + 상세 정보 diff --git a/src/components/Reservation/ReservationList.jsx b/src/components/Reservation/ReservationList.jsx index 8cbec9f..b6430a1 100644 --- a/src/components/Reservation/ReservationList.jsx +++ b/src/components/Reservation/ReservationList.jsx @@ -9,7 +9,6 @@ import { } from "../../reducer/reservation-list.js"; import BlockContainer from "../Common/BlockContainer.jsx"; import TitleText from "../Common/TitleText.jsx"; -import { getAdminReservationList } from "../../librarys/api/reservation.js"; import ReservationInfoModal from "./ReservationInfoModal.jsx"; import { useSelector } from "react-redux"; import { selectEmail, selectRole } from "../../redux/userSlice.js"; diff --git a/src/components/Reservation/ReservationMiniItem.jsx b/src/components/Reservation/ReservationMiniItem.jsx new file mode 100644 index 0000000..a1e9a15 --- /dev/null +++ b/src/components/Reservation/ReservationMiniItem.jsx @@ -0,0 +1,139 @@ +import { useMemo } from "react"; +import styled from "styled-components"; +import PropTypes from "prop-types"; +import Button from "../Button/Button.jsx"; + +import { MdCalendarMonth } from "react-icons/md"; +import { DAYJS_FORMAT } from "../../librarys/type.js"; +import dayjs from "dayjs"; +import classNames from "classnames"; +import { useDispatch } from "react-redux"; +import { show } from "../../redux/modalSlice.js"; +import { useNavigate } from "react-router-dom"; + +const Container = styled.div` + width: 320px; + height: 120px; + padding: 12px 24px; + border: 1px solid #bbbbbb; + border-radius: 10px; + display: flex; + flex-direction: column; + gap: 8px; +`; + +const Row = styled.div` + display: flex; + width: 100%; + align-items: center; + font-size: 14px; +`; + +const Icon = styled(MdCalendarMonth)` + margin-right: 8px; +`; + +const Big = styled.span` + margin-right: 4px; + font-size: 22px; + font-weight: 800; +`; + +const Btn = styled(Button)` + width: 120px; + height: 28px; + padding: 0px 8px; + font-size: 12px; + font-weight: 500; + margin-right: 32px; +`; + +/* +1. Close !isOpen && !isDone 아직 열리지 않은 예약 +2. Open isOpen && !isDone 열려서 들어갈 수 있는 예약 +3. Done isOpen && isDone 완료된 예약 +*/ + +const dummyText = `그러나 한 시와 강아지, 가을 보고, 새워 까닭입니다. 까닭이요, 이름을 옥 별들을 많은 까닭입니다. 그리워 동경과 둘 이런 이런 계절이 거외다. 나의 오면 언덕 하나 무덤 이런 아직 있습니다. 자랑처럼 하나 무성할 패, 까닭입니다. 하나의 별 사람들의 너무나 별 피어나듯이 당신은 북간도에 봅니다.그러나 한 시와 강아지, 가을 보고, 새워 까닭입니다. 까닭이요, 이름을 옥 별들을 많은 까닭입니다. 그리워 동경과 둘 이런 이런 계절이 거외다. 나의 오면 언덕 하나 무덤 이런 아직 있습니다. 자랑처럼 하나 무성할 패, 까닭입니다. 하나의 별 사람들의 너무나 별 피어나듯이 당신은 북간도에 봅니다.`; +const notReadyText = `아직 비대면 진료 요약이 생성되지 않았습니다.`; + +const buttonStyleList = { + normal: { type: "primary", text: "입장" }, + complete: { type: "disabled", text: "종료되었습니다" }, + notReady: { type: "disabled", text: "예약 시간이 아님" }, +}; + +const ReservationMiniItem = ({ id, name, date, index }) => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const fullDate = useMemo( + () => dayjs(date).add(index * 30, "minute"), + [date, index], + ); + + const time = useMemo(() => dayjs().diff(fullDate, "minute"), [fullDate]); + const isRoomOpen = time >= -10; + const isReservationDone = time > 30; + + const buttonStyle = useMemo(() => { + if (isReservationDone) { + return buttonStyleList.complete; + } else if (isRoomOpen) { + return buttonStyleList.normal; + } else { + return buttonStyleList.notReady; + } + }, [isRoomOpen, isReservationDone]); + + function onJoinButtonClick() { + if (isReservationDone || !isRoomOpen) { + return; + } + + navigate("/untact/meeting/" + id); + } + + function onInfoButtonClick() { + dispatch( + show({ + id: "reservation_detail", + props: { + reservationId: 999, + chartDetail: null, + description: dummyText, + aiSummary: notReadyText, + }, + }), + ); + } + + return ( + + + {name}님 + + + + {fullDate.format(DAYJS_FORMAT)} + + + + {buttonStyle.text} + + + 상세 정보 + + + + ); +}; + +ReservationMiniItem.propTypes = { + id: PropTypes.string, + name: PropTypes.string, + date: PropTypes.string, + index: PropTypes.number, +}; + +export default ReservationMiniItem; diff --git a/src/components/Reservation/ReservationMiniList.jsx b/src/components/Reservation/ReservationMiniList.jsx new file mode 100644 index 0000000..a2a0f1b --- /dev/null +++ b/src/components/Reservation/ReservationMiniList.jsx @@ -0,0 +1,75 @@ +import styled from "styled-components"; +import Pagination from "../Pagination/Pagination"; +import { ReducerContext } from "../../reducer/context.js"; +import { useEffect, useReducer } from "react"; +import { + intialReservationListState, + reservationListReducer, +} from "../../reducer/reservation-list.js"; +import BlockContainer from "../Common/BlockContainer.jsx"; +import TitleText from "../Common/TitleText.jsx"; +import ReservationInfoModal from "./ReservationInfoModal.jsx"; +import { useSelector } from "react-redux"; +import { selectEmail, selectRole } from "../../redux/userSlice.js"; +import { + getReservationListAdmin, + getReservationListUser, +} from "../../librarys/dummy-api.js"; +import ReservationMiniItem from "./ReservationMiniItem.jsx"; + +const List = styled.div` + margin: 28px 0; + display: grid; + grid-template-columns: 320px 320px; + flex-direction: column; + justify-content: center; + gap: 28px 80px; +`; + +const ReservationMiniList = () => { + const [state, dispatch] = useReducer( + reservationListReducer, + intialReservationListState, + ); + const { list, page } = state; + const id = useSelector(selectEmail); + const role = useSelector(selectRole); + + useEffect(() => { + (async () => { + let data; + if (role === "USER") { + data = await getReservationListUser(id, page); + } else { + data = await getReservationListAdmin(id, page); + } + + dispatch({ + type: "data", + payload: data, + }); + })(); + }, [page]); + + return ( + + + + + + {list.map((item) => ( + + ))} + + + + ); +}; + +export default ReservationMiniList; diff --git a/src/components/TherapistDashBoard/TheraReserve.jsx b/src/components/TherapistDashBoard/TheraReserve.jsx deleted file mode 100644 index 97eca1b..0000000 --- a/src/components/TherapistDashBoard/TheraReserve.jsx +++ /dev/null @@ -1,97 +0,0 @@ -import styled from "styled-components"; -import IconDate from "../../assets/icons/icondate.png"; -import { useState } from "react"; - -const CardContainer = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 15px; - border: 1px solid #e0e0e0; - border-radius: 10px; - width: 320px; - height: 120px; - background-color: #ffffff; - margin-bottom: 20px; -`; - -const DateContainer = styled.div` - display: flex; - align-items: center; - margin-top: 5px; -`; - -const UserName = styled.div` - font-size: 25px; - font-weight: bold; - display: flex; - align-items: baseline; - - span { - font-size: 15px; - margin-left: 5px; - font-weight: 300; - } -`; - -const Icon = styled.img` - width: 18px; - height: 18px; - margin-right: 5px; -`; - -const InfoText = styled.span` - font-size: 14px; - font-weight: 300; - margin-left: 10px; -`; - -const Button = styled.button` - width: 120px; - height: 28px; - background-color: ${(props) => - props.cancelButton ? "#F3F3F3" : props.clicked ? "#888888" : "#3592FF"}; - color: ${(props) => - props.cancelButton ? "#000000" : props.clicked ? "#444444" : "#FEFDFD"}; - border: ${(props) => (props.cancelButton ? "1px solid #BBBBBB" : "none")}; - border-radius: 10px; - border: none; - cursor: pointer; - font-size: 14px; - -`; - -const ButtonContainer = styled.div` - display: flex; - justify-content: space-between; - width: 100%; - margin-top: 5px; -`; - -const TheraReserve = () => { - const [buttonState, setButtonState] = useState(false); - - const toggleButtonState = () => { - setButtonState(!buttonState); - }; - - return ( - - - 오소현 - - - - 2023/08/30 16:00 - - - - - - - ); -}; - -export default TheraReserve; diff --git a/src/components/TherapistDashBoard/TheraUntactList.jsx b/src/components/TherapistDashBoard/TheraUntactList.jsx deleted file mode 100644 index 8b40842..0000000 --- a/src/components/TherapistDashBoard/TheraUntactList.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import styled from "styled-components"; -import TheraReserve from "./TheraReserve"; - -const Container = styled.div` - width: 800px; - height: 555px; - margin: 0 auto; - padding: 20px; - border: 1px solid #0064ff; - border-radius: 10px; - background-color: #ffffff; - font-family: "Spoqa Han Sans Neo", "sans-serif"; - position: relative; - display: flex; - flex-wrap: wrap; - gap: 0px 80px; -`; - -const Title = styled.h1` - font-size: 28px; - font-weight: bold; - color: #333; - display: inline-block; -`; - -const Divider = styled.hr` - width: 100%; - height: 1px; - background-color: #d9d9d9; - border: none; - margin-bottom: 20px; -`; - -const TheraUntactList = () => { - return ( - - 비대면 진료 예약 목록 - - - - - - - - - ); -}; - -export default TheraUntactList; diff --git a/src/librarys/api/reservation.js b/src/librarys/api/reservation.js index 8c8a6ae..7f7e806 100644 --- a/src/librarys/api/reservation.js +++ b/src/librarys/api/reservation.js @@ -1,3 +1,4 @@ +import { sleep } from "../util.js"; import { getSpringAxios } from "./axios.js"; export async function getAdminReservationList(id, page = undefined) { @@ -31,3 +32,17 @@ export async function createReservation( const response = await axios.post("/reservation/", data); return response.data; } + +export async function removeReservation(id) { + const axios = getSpringAxios(); + + const response = await axios.put("/reservation/" + id); + return response.data; +} + +export async function getAdminReservationTime(id, date) { + const axios = getSpringAxios(); + + const response = await axios.get(`/reserved-times/${id}/${date}`); + return response.data; +} diff --git a/src/librarys/util.js b/src/librarys/util.js index 77ddd07..47e1c18 100644 --- a/src/librarys/util.js +++ b/src/librarys/util.js @@ -17,3 +17,7 @@ export const throttle = (callback, delay) => { } }; }; + +export function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/pages/Doctor/DoctorDashBoardPage.jsx b/src/pages/Doctor/DoctorDashBoardPage.jsx index 26a54a3..37f4561 100644 --- a/src/pages/Doctor/DoctorDashBoardPage.jsx +++ b/src/pages/Doctor/DoctorDashBoardPage.jsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import DoctorDashHeader from "../../components/DoctorDashBoard/DoctorDashHeader"; import CardhButton from "../../components/Button/CardButton"; -import DoctorUntactList from "../../components/DoctorDashBoard/DoctorUntactList"; +import ReservationMiniList from "../../components/Reservation/ReservationMiniList.jsx"; const PageContainer = styled.div` display: flex; @@ -37,7 +37,7 @@ const DoctorDashBoardPage = () => { - + ); diff --git a/src/pages/Therapist/TheraDashBoardPage.jsx b/src/pages/Therapist/TheraDashBoardPage.jsx index f71f698..ac367f8 100644 --- a/src/pages/Therapist/TheraDashBoardPage.jsx +++ b/src/pages/Therapist/TheraDashBoardPage.jsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import TheraDashHeader from "../../components/TherapistDashBoard/TheraDashHeader"; import CardhButton from "../../components/Button/CardButton"; -import TheraUntactList from "../../components/TherapistDashBoard/TheraUntactList"; +import ReservationMiniList from "../../components/Reservation/ReservationMiniList.jsx"; const PageContainer = styled.div` display: flex; @@ -37,7 +37,7 @@ const TheraDashBoardPage = () => { - + ); diff --git a/src/reducer/reservation-create.js b/src/reducer/reservation-create.js index bb97e73..4b64538 100644 --- a/src/reducer/reservation-create.js +++ b/src/reducer/reservation-create.js @@ -21,10 +21,10 @@ function prevMonthState(state) { export const intialReserveCreateState = { ...getDate(), - available: [true, true, true, true, true], + disabledTime: [], index: null, description: "", - adminId: null, + adminId: "ldh", }; export function reserveCreateReducer(state, action) { @@ -54,10 +54,10 @@ export function reserveCreateReducer(state, action) { date: action.payload, index: null, }; - case "available": + case "disabledTime": return { ...state, - available: action.payload, + disabledTime: action.payload, }; case "index": return {