diff --git a/src/App.jsx b/src/App.jsx
index ca25782..a9b26f3 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -20,6 +20,7 @@ import TheraExerciseAddPage from "./pages/Therapist/TheraExerciseAddPage.jsx";
import TheraMakeAssignPage from "./pages/Therapist/TheraMakeAssignPage.jsx";
import styled from "styled-components";
import "./App.scss";
+import { ReducerContext } from "./reducer/context.js";
const Container = styled.div`
margin-top: 60px;
@@ -29,46 +30,54 @@ const Container = styled.div`
function App() {
return (
-
-
-
-
- } />
- } />
- } />
- } />
- }
- />
- } />
- } />
- } />
- } />
- }
- />
- }
- />
- } />
- }
- />
- } />
- } />
- }
- />
- } />
- } />
-
-
-
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ }
+ />
+ } />
+ }
+ />
+ }
+ />
+ } />
+ }
+ />
+ }
+ />
+ } />
+
+
+
+
);
}
diff --git a/src/assets/images/role/role_doctor.png b/src/assets/images/role/role_doctor.png
new file mode 100644
index 0000000..600c03e
Binary files /dev/null and b/src/assets/images/role/role_doctor.png differ
diff --git a/src/assets/images/role/role_patient.png b/src/assets/images/role/role_patient.png
new file mode 100644
index 0000000..3e77933
Binary files /dev/null and b/src/assets/images/role/role_patient.png differ
diff --git a/src/assets/images/role/role_therapist.png b/src/assets/images/role/role_therapist.png
new file mode 100644
index 0000000..d6c0322
Binary files /dev/null and b/src/assets/images/role/role_therapist.png differ
diff --git a/src/components/Chart/ChartSummary.jsx b/src/components/Chart/ChartSummary.jsx
new file mode 100644
index 0000000..8caa0c6
--- /dev/null
+++ b/src/components/Chart/ChartSummary.jsx
@@ -0,0 +1,92 @@
+import styled from "styled-components";
+import { useState, useEffect } from "react";
+import { userLogin } from "../../librarys/dummy-api";
+
+const Container = styled.div`
+ width: 380px;
+ height: 200px;
+ 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;
+ flex-direction: column;
+`;
+
+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-top: 0px;
+`;
+
+const Row = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 5px;
+`;
+
+const Label = styled.span`
+ color: #000000;
+ font-size: 16px;
+`;
+
+const Value = styled.span`
+ color: #908b8b;
+`;
+
+const ChartSummary = ({ ...props }) => {
+ const [patientInfo, setPatientInfo] = useState({});
+
+ useEffect(() => {
+ async function fetchPatientInfo() {
+ const doctorInfo = await userLogin("doctor", "123456");
+ const patient = doctorInfo?.patient;
+
+ if (patient) {
+ setPatientInfo(patient);
+ }
+ }
+
+ fetchPatientInfo();
+ }, []);
+
+ return (
+
+ 차트 정보
+
+
+
+ {patientInfo.diseaseCode}
+
+
+
+ {patientInfo.recentVisitDate}
+
+
+
+ {patientInfo.nextReservationDate}
+
+
+
+ {patientInfo.assignedTherapist}
+
+
+ );
+};
+
+export default ChartSummary;
diff --git a/src/components/Input/InputArea.jsx b/src/components/Input/InputArea.jsx
index 3a0d4dc..292ccd7 100644
--- a/src/components/Input/InputArea.jsx
+++ b/src/components/Input/InputArea.jsx
@@ -11,15 +11,23 @@ const Item = styled.textarea`
border: 1px solid #bbbbbb;
padding-left: 12px;
resize: none;
+ overflow: hidden;
&:focus {
outline: none;
}
`;
-function InputArea({ value, onInput, className }) {
+function InputArea({ value, onInput, disabled, className }) {
return (
-
+
);
}
@@ -27,6 +35,11 @@ InputArea.propTypes = {
value: PropTypes.string,
onInput: PropTypes.func,
className: PropTypes.string,
+ disabled: PropTypes.bool,
+};
+
+InputArea.defaultProps = {
+ disabled: false,
};
export default InputArea;
diff --git a/src/components/Input/InputAreaContainer.jsx b/src/components/Input/InputAreaContainer.jsx
index b1fce12..e1c0f07 100644
--- a/src/components/Input/InputAreaContainer.jsx
+++ b/src/components/Input/InputAreaContainer.jsx
@@ -1,6 +1,7 @@
import styled from "styled-components";
import PropTypes from "prop-types";
import InputArea from "./InputArea";
+import classNames from "classnames";
const Input = styled(InputArea)`
width: 100% !important;
@@ -17,11 +18,11 @@ const Label = styled.p`
margin-bottom: 6px;
`;
-function InputAreaContainer({ label, value, onInput, ...props }) {
+function InputAreaContainer({ label, value, onInput, disabled, ...props }) {
return (
-
+
);
}
@@ -30,6 +31,7 @@ InputAreaContainer.propTypes = {
label: PropTypes.string,
value: PropTypes.string,
onInput: PropTypes.func,
+ disabled: PropTypes.bool,
};
export default InputAreaContainer;
diff --git a/src/components/Pagination/Pagination.jsx b/src/components/Pagination/Pagination.jsx
index 4e815e1..0fa2599 100644
--- a/src/components/Pagination/Pagination.jsx
+++ b/src/components/Pagination/Pagination.jsx
@@ -66,7 +66,7 @@ const StyledReactPaginate = styled(ReactPaginate)`
function Pagination({}) {
const [state, dispatch] = useContext(ReducerContext);
- const { totalPage } = state;
+ const { totalPage } = state || {};
const handlePageClick = (data) => {
console.log(data);
diff --git a/src/components/Reservation/ReservationItem.jsx b/src/components/Reservation/ReservationItem.jsx
new file mode 100644
index 0000000..1ee6cd6
--- /dev/null
+++ b/src/components/Reservation/ReservationItem.jsx
@@ -0,0 +1,200 @@
+import { useMemo } from "react";
+import styled from "styled-components";
+import PropTypes from "prop-types";
+import Button from "../Button/Button.jsx";
+
+import PatientIcon from "../../assets/images/role/role_patient.png";
+import DoctorIcon from "../../assets/images/role/role_doctor.png";
+import TherapistIcon from "../../assets/images/role/role_therapist.png";
+
+import { MdLocalHospital, MdCalendarMonth, MdPerson } from "react-icons/md";
+import { DAYJS_FORMAT, ROLE_TYPE } from "../../librarys/type.js";
+import dayjs from "dayjs";
+import classNames from "classnames";
+import { useDispatch } from "react-redux";
+import { show } from "../../redux/modalSlice.js";
+
+const Container = styled.div`
+ height: 110px;
+ padding: 0 24px;
+ border: 1px solid #bbbbbb;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ gap: 18px;
+`;
+
+const Image = styled.img`
+ width: 72px;
+ height: 72px;
+ background-color: #d9d9d9;
+ border-radius: 36px;
+ object-fit: contain;
+ overflow: hidden;
+`;
+
+const Info = styled.div`
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ font-size: 16px;
+ font-weight: 400;
+`;
+
+const Big = styled.span`
+ margin-right: 8px;
+ font-size: 22px;
+ font-weight: 800;
+`;
+
+const Line = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 24px;
+`;
+
+const Item = styled.div`
+ width: ${(props) => props.width || "auto"};
+ vertical-align: middle;
+
+ &.user {
+ display: none;
+ }
+
+ & > svg {
+ width: 18px;
+ height: 18px;
+ margin-right: 8px;
+ vertical-align: middle;
+ }
+`;
+
+const ButtonContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`;
+
+const Btn = styled(Button)`
+ width: 160px;
+ height: 32px;
+ font-size: 14px;
+ font-weight: 500;
+`;
+
+/*
+1. Close !isOpen && !isDone 아직 열리지 않은 예약
+2. Open isOpen && !isDone 열려서 들어갈 수 있는 예약
+3. Done isOpen && isDone 완료된 예약
+*/
+
+const dummyText = `그러나 한 시와 강아지, 가을 보고, 새워 까닭입니다. 까닭이요, 이름을 옥 별들을 많은 까닭입니다. 그리워 동경과 둘 이런 이런 계절이 거외다. 나의 오면 언덕 하나 무덤 이런 아직 있습니다. 자랑처럼 하나 무성할 패, 까닭입니다. 하나의 별 사람들의 너무나 별 피어나듯이 당신은 북간도에 봅니다.그러나 한 시와 강아지, 가을 보고, 새워 까닭입니다. 까닭이요, 이름을 옥 별들을 많은 까닭입니다. 그리워 동경과 둘 이런 이런 계절이 거외다. 나의 오면 언덕 하나 무덤 이런 아직 있습니다. 자랑처럼 하나 무성할 패, 까닭입니다. 하나의 별 사람들의 너무나 별 피어나듯이 당신은 북간도에 봅니다.`;
+
+const notReadyText = `아직 비대면 진료 요약이 생성되지 않았습니다.`;
+
+const ReservationItem = ({ name, role, dept, date, index }) => {
+ const dispatch = useDispatch();
+
+ const image = useMemo(() => {
+ switch (role) {
+ case "ADMIN_DOCTOR":
+ return DoctorIcon;
+ case "ADMIN_THERAPIST":
+ return TherapistIcon;
+ default:
+ return PatientIcon;
+ }
+ }, [role]);
+
+ const roleDisplay = useMemo(() => {
+ const find = ROLE_TYPE.find((item) => item.key === role);
+
+ if (find) return find.value;
+ return "환자";
+ }, [role]);
+
+ const fullDate = useMemo(
+ () => dayjs(date).add(index * 30, "minute"),
+ [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;
+
+ function firstButton() {
+ if (isDone) {
+ return 종료되었습니다;
+ } else if (isOpen) {
+ return 입장;
+ } else {
+ return 예약 시간이 아닙니다;
+ }
+ }
+
+ function showDetail() {
+ dispatch(
+ show({
+ id: "reservation_detail",
+ props: {
+ chartDetail: null,
+ description: dummyText,
+ aiSummary: notReadyText,
+ },
+ }),
+ );
+ }
+
+ return (
+
+
+
+
+ -
+ {name}님
+
+
+
+ -
+
+ {roleDisplay}
+
+ -
+
+ {fullDate.format(DAYJS_FORMAT)}
+
+
+
+ -
+
+ {dept}
+
+
+
+
+ {firstButton()}
+
+ 상세 정보
+
+
+
+ );
+};
+
+ReservationItem.propTypes = {
+ name: PropTypes.string,
+ role: PropTypes.string,
+ date: PropTypes.string,
+ dept: PropTypes.string,
+ index: PropTypes.number,
+};
+
+ReservationItem.defaultProps = {
+ role: "USER",
+};
+
+export default ReservationItem;
diff --git a/src/components/Reservation/ReservationList.jsx b/src/components/Reservation/ReservationList.jsx
new file mode 100644
index 0000000..07bf9a7
--- /dev/null
+++ b/src/components/Reservation/ReservationList.jsx
@@ -0,0 +1,76 @@
+import styled from "styled-components";
+import ReservationItem from "./ReservationItem";
+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 { getAdminReservationList } from "../../librarys/api/reservation.js";
+import ReservationModal from "./ReservationModal.jsx";
+
+const List = styled.div`
+ margin: 28px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 28px;
+`;
+
+const ReservationList = () => {
+ const [state, dispatch] = useReducer(
+ reservationListReducer,
+ intialReservationListState,
+ );
+ const { list, page } = state;
+
+ useEffect(() => {
+ (async () => {
+ const data = await getAdminReservationList("ldh", page);
+ dispatch({
+ type: "data",
+ payload: data,
+ });
+ })();
+ }, [page]);
+
+ return (
+
+
+
+
+
+ {list.map((item) => (
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default ReservationList;
diff --git a/src/components/Reservation/ReservationModal.jsx b/src/components/Reservation/ReservationModal.jsx
new file mode 100644
index 0000000..36b2f09
--- /dev/null
+++ b/src/components/Reservation/ReservationModal.jsx
@@ -0,0 +1,60 @@
+import styled from "styled-components";
+import PropTypes from "prop-types";
+
+import Modal from "../Common/Modal.jsx";
+
+import { 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";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 28px;
+`;
+
+const Chart = styled(ChartSummary)`
+ margin-top: 16px;
+ width: 100%;
+`;
+
+const Input = styled(InputAreaContainer)`
+ width: 100%;
+`;
+
+const ButtonContainer = styled.div`
+ margin-top: 8px;
+ margin-bottom: 32px;
+ display: flex;
+ gap: 24px;
+`;
+
+const id = "reservation_detail";
+
+const ReservationModal = () => {
+ const value = useSelector(selectProps(id));
+ const { description, aiSummary } = value || {};
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+ReservationModal.propTypes = {};
+
+export default ReservationModal;
diff --git a/src/index.scss b/src/index.scss
index dd1b9b0..1d92363 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -13,4 +13,9 @@
html {
}
+body {
+ // 가로 스크롤 생기면 이중 스크롤 생기는 문제 수정
+ overflow-y: hidden;
+}
+
@import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css);
diff --git a/src/librarys/api/reservation.js b/src/librarys/api/reservation.js
new file mode 100644
index 0000000..428c864
--- /dev/null
+++ b/src/librarys/api/reservation.js
@@ -0,0 +1,12 @@
+import { getSpringAxios } from "./axios.js";
+
+export async function getAdminReservationList(id, page = undefined) {
+ const axios = getSpringAxios();
+
+ const params = {
+ page,
+ };
+
+ const response = await axios.get("/reservation-admin/" + id, { params });
+ return response.data;
+}
diff --git a/src/librarys/type.js b/src/librarys/type.js
index 346f347..8415af5 100644
--- a/src/librarys/type.js
+++ b/src/librarys/type.js
@@ -1,8 +1,8 @@
export const ROLE_TYPE = [
- { key: "VISITOR", value: 0 },
- { key: "USER", value: 1 },
- { key: "ADMIN_DOCTOR", value: 2 },
- { key: "ADMIN_THERAPIST", value: 3 },
+ { key: "VISITOR", value: "방문자" },
+ { key: "USER", value: "환자" },
+ { key: "ADMIN_DOCTOR", value: "전문의" },
+ { key: "ADMIN_THERAPIST", value: "재활치료사" },
];
export const CATEGORY_TYPE = [
@@ -11,3 +11,5 @@ export const CATEGORY_TYPE = [
{ key: "KNEE", value: "무릎" },
{ key: "THIGH", value: "허벅지" },
];
+
+export const DAYJS_FORMAT = "YYYY/MM/DD HH:mm";
diff --git a/src/pages/Doctor/DoctorUntactReservePage.jsx b/src/pages/Doctor/DoctorUntactReservePage.jsx
index 8b9c2d7..f3a5368 100644
--- a/src/pages/Doctor/DoctorUntactReservePage.jsx
+++ b/src/pages/Doctor/DoctorUntactReservePage.jsx
@@ -1,28 +1,12 @@
-import styled from "styled-components";
import BackButton from "../../components/Button/BackButton";
-import DoctorUntactFullList from "../../components/DoctorDashBoard/DoctorUntactFullList";
-
-const PageContainer = styled.div`
- display: flex;
- flex-direction: column;
- height: 100vh;
-`;
-
-const CenteredContainer = styled.div`
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- flex: 1;
-`;
+import PageContainer from "../../components/Common/PageContainer";
+import ReservationList from "../../components/Reservation/ReservationList.jsx";
const DoctorUntactReservePage = () => {
return (
-
-
-
-
+
+
);
};
diff --git a/src/reducer/reservation-list.js b/src/reducer/reservation-list.js
new file mode 100644
index 0000000..a5a1ca1
--- /dev/null
+++ b/src/reducer/reservation-list.js
@@ -0,0 +1,26 @@
+export const intialReservationListState = {
+ list: [],
+ page: 1,
+ totalPage: 1,
+};
+
+export function reservationListReducer(state, action) {
+ switch (action.type) {
+ case "page":
+ return {
+ ...state,
+ page: action.payload,
+ };
+ case "data":
+ return {
+ list: action.payload.dtoList || [],
+ page: action.payload.page || 1,
+ totalPage: action.payload.end || 1,
+ };
+ default:
+ console.error(
+ "[ReservationListReducer] Undefined action: " + action.type,
+ );
+ return state;
+ }
+}
diff --git a/src/reducer/video-list.js b/src/reducer/video-list.js
index a91d1f8..e4c1816 100644
--- a/src/reducer/video-list.js
+++ b/src/reducer/video-list.js
@@ -1,12 +1,9 @@
-import { CATEGORY_TYPE } from "../librarys/type.js";
-
export const intialVideoListState = {
query: "",
category: null,
list: [],
page: 1,
- totalItems: 80,
- itemsPerPage: 10,
+ totalPage: 1,
};
export function videoListReducer(state, action) {