From 4985cb980618b5b773a110ee2843f8fe894dd1f7 Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:17:12 +0900 Subject: [PATCH 1/7] =?UTF-8?q?Refactor:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Accounts/SignupComponents.jsx | 183 ++++++++++--------- src/components/Input/InputImage.jsx | 47 +++-- src/librarys/api/user.js | 4 +- src/librarys/util.js | 2 + src/reducer/user-register.js | 24 +++ 5 files changed, 160 insertions(+), 100 deletions(-) create mode 100644 src/reducer/user-register.js diff --git a/src/components/Accounts/SignupComponents.jsx b/src/components/Accounts/SignupComponents.jsx index 815eff2..c73b649 100644 --- a/src/components/Accounts/SignupComponents.jsx +++ b/src/components/Accounts/SignupComponents.jsx @@ -6,8 +6,16 @@ import InputImage from "../Input/InputImage.jsx"; import InputTextContainer from "../Input/InputTextContainer.jsx"; import Button from "../Button/Button.jsx"; import DropdownFilter from "../Dropdown/DropdownFilter.jsx"; -import { useState } from "react"; -import { userSignup } from "../../librarys/api/signup.js"; +import { useReducer, useState } from "react"; +import { + intialUserRegisterState, + userRegisterReducer, +} from "../../reducer/user-register.js"; +import { getByPath } from "../../librarys/util.js"; +import { createAccount } from "../../librarys/api/user.js"; +import { useDispatch } from "react-redux"; +import { getMyInfo, login } from "../../redux/userSlice.js"; +import { useNavigate } from "react-router-dom"; const Grid = styled.div` margin: 48px 70px; @@ -22,77 +30,88 @@ const RegisterButton = styled(Button)` justify-self: center; `; -const Signup = () => { - // 소속 병원 드롭다운 내용 - const hospitalItems = [ - { key: "춘천성심병원", value: "춘천성심병원" }, - { key: "동탄성심병원", value: "동탄성심병원" }, - { key: "강남성심병원", value: "강남성심병원" }, - { key: "한강성심병원", value: "한강성심병원" }, - { key: "강동성심병원", value: "강동성심병원" }, - { key: "한림성심병원", value: "한림성심병원" }, - ]; - - const handleSelectHospital = (hospital) => { - console.log("Selected Hospital: ", hospital.key); - setFormData({ ...formData, hospital: hospital.key }); - }; - - // 역할 버튼 상태 로직 - const [selectedRole, setSelectedRole] = useState(null); - - const handleSelectRole = (role) => { - setSelectedRole(role); - const staffRole = role === "doctor" ? "ROLE_DOCTOR" : "ROLE_THERAPIST"; - setFormData({ ...formData, staffRole: staffRole }); - }; +// 소속 병원 드롭다운 내용 +const hospitals = [ + "춘천성심병원", + "동탄성심병원", + "강남성심병원", + "한강성심병원", + "강동성심병원", + "한림성심병원", +].map((item) => ({ key: item, value: item })); - //이미지 업로드 api 연결 - const handleImageSelect = (file) => { - const formDataCopy = { ...formData }; - formDataCopy.file = file; - setFormData(formDataCopy); - }; +const Signup = () => { + const [state, dispatch] = useReducer( + userRegisterReducer, + intialUserRegisterState, + ); + const reduxDispatch = useDispatch(); + const navigate = useNavigate(); - const [formData, setFormData] = useState({ - mid: "", - password: "", - name: "", - hospital: "", - department: "", - email: "", - phone: "", - staffRole: "", - // fileName: '', - }); + const { id, password, name, hospital, department, email, phone, role } = + state; - const handleSubmit = async (e) => { - e.preventDefault(); + function setData(key, path) { + return (value) => { + if (path) { + value = getByPath(value, path); + } - // const dataToSubmit = new FormData(); - // Object.keys(formData).forEach(key => { - // if (key !== 'file') { - // dataToSubmit.append(key, formData[key]); - // } - // }); + dispatch({ + type: "field", + payload: { + key, + value, + }, + }); + }; + } - // if (formData.file) { - // dataToSubmit.append('file', formData.file); - // } + async function clickRegisterButton() { + if (id === "" || id.length < 3) { + alert("아이디는 4글자 이상으로 입력해주세요."); + return; + } + if (password === "" || password.length < 3) { + alert("비밀번호는 4글자 이상으로 입력해주세요."); + return; + } + if (name === "") { + alert("이름을 입력하세요."); + return; + } + if (hospital === "") { + alert("소속 병원을 입력하세요."); + return; + } + if (department === "") { + alert("소속 부서를 입력하세요."); + return; + } + if (email === "") { + alert("이메일을 입력하세요."); + return; + } + if (phone === "") { + alert("핸드폰 번호를 입력하세요."); + return; + } + console.log(state); try { - const response = await userSignup(formData); - console.log(response); + await createAccount(state); } catch (error) { console.error(error); + alert("회원가입에 실패했습니다."); + return; } - }; - const handleInputChange = (id) => { - return (e) => { - setFormData({ ...formData, [id]: e.target.value }); - }; - }; + alert(name + "님의 회원가입이 완료되었습니다!"); + const tokenResponse = await reduxDispatch(login({ id, password })); + await reduxDispatch(getMyInfo(tokenResponse.payload)); + + navigate("/"); + } return ( @@ -100,61 +119,61 @@ const Signup = () => { handleSelectRole("doctor")} + isSelected={role === "ROLE_DOCTOR"} + onSelectRole={() => setData("role")("ROLE_DOCTOR")} /> handleSelectRole("therapist")} + isSelected={role === "ROLE_THERAPIST"} + onSelectRole={() => setData("role")("ROLE_THERAPIST")} /> - 회원가입 + 회원가입 ); diff --git a/src/components/Input/InputImage.jsx b/src/components/Input/InputImage.jsx index a313d33..80c211c 100644 --- a/src/components/Input/InputImage.jsx +++ b/src/components/Input/InputImage.jsx @@ -1,5 +1,7 @@ -import { useMemo, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import styled from "styled-components"; +import PropTypes from "prop-types"; +import { createImage } from "../../librarys/api/image.js"; const UploadBox = styled.div` width: 154px; @@ -29,25 +31,34 @@ const ImagePreview = styled.img` object-fit: contain; `; -const InputImage = ({ onImageSelect, ...props }) => { +const InputImage = ({ onUpload, ...props }) => { const [preview, setPreview] = useState(null); - const [selectedFile, setSelectedFile] = useState(null); + const ref = useRef(null); - const handleImageChange = (e) => { + const handleInputChange = (e) => { const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onloadend = () => { - setPreview(reader.result); - setSelectedFile(file); - onImageSelect(file); - }; - reader.readAsDataURL(file); + + if (!file) { + return; } + + const reader = new FileReader(); + reader.onloadend = () => { + handleUpload(reader.result, file); + }; + reader.readAsDataURL(file); }; - const triggerFileInput = () => { - document.getElementById("imageInput").click(); + const handleUpload = async (image, file) => { + setPreview(image); + const response = await createImage(file); + onUpload(response.link); + }; + + const handleClick = () => { + if (ref) { + ref.current.click(); + } }; const content = useMemo(() => { @@ -59,11 +70,15 @@ const InputImage = ({ onImageSelect, ...props }) => { }, [preview]); return ( - + {content} - + ); }; +InputImage.propTypes = { + onUpload: PropTypes.func, +}; + export default InputImage; diff --git a/src/librarys/api/user.js b/src/librarys/api/user.js index 4d01b74..6c0251a 100644 --- a/src/librarys/api/user.js +++ b/src/librarys/api/user.js @@ -46,7 +46,7 @@ export async function createAccount(req) { const axios = getSpringAxios(); const body = { - mid: req.mid, + mid: req.id, name: req.name, password: req.password, hospital: req.hospital, @@ -54,7 +54,7 @@ export async function createAccount(req) { email: req.email, phone: req.phone, staffRole: req.role, - fileName: req.image, + // fileName: req.image || "", }; const response = await axios.post("/join", body); diff --git a/src/librarys/util.js b/src/librarys/util.js index 47e1c18..a05ea74 100644 --- a/src/librarys/util.js +++ b/src/librarys/util.js @@ -21,3 +21,5 @@ export const throttle = (callback, delay) => { export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } + +export const getByPath = (o, p) => p.split(".").reduce((a, v) => a[v], o); diff --git a/src/reducer/user-register.js b/src/reducer/user-register.js new file mode 100644 index 0000000..99012dc --- /dev/null +++ b/src/reducer/user-register.js @@ -0,0 +1,24 @@ +export const intialUserRegisterState = { + id: "", + password: "", + name: "", + hospital: "", + department: "", + email: "", + phone: "", + image: "", + role: "", +}; + +export function userRegisterReducer(state, action) { + switch (action.type) { + case "field": + return { + ...state, + [action.payload.key]: action.payload.value, + }; + default: + console.error("[UserRegisterReducer] Undefined action: " + action.type); + return state; + } +} From e3c18a281be0114f547a50bd3bff99ad99c2eea9 Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:17:26 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Chore:=20DnDList=EC=9D=98=20proptype?= =?UTF-8?q?=EC=9D=84=20object=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Common/DnDList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Common/DnDList.jsx b/src/components/Common/DnDList.jsx index 6faf040..53b7424 100644 --- a/src/components/Common/DnDList.jsx +++ b/src/components/Common/DnDList.jsx @@ -48,7 +48,7 @@ const DnDList = ({ id, data, dropping, dragging, removable }) => { DnDList.propTypes = { id: PropTypes.string.isRequired, - data: PropTypes.arrayOf(PropTypes.array).isRequired, + data: PropTypes.arrayOf(PropTypes.object).isRequired, dropping: PropTypes.bool, dragging: PropTypes.bool, removable: PropTypes.bool, From 8b8d594477b779ab46726977b363d95a2699c968 Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:17:43 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Chore:=20InputText=EC=9D=98=20Value?= =?UTF-8?q?=EB=A5=BC=20defaultValue=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Input/InputArea.jsx | 2 +- src/components/Input/InputText.jsx | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Input/InputArea.jsx b/src/components/Input/InputArea.jsx index 91d6838..ffe8555 100644 --- a/src/components/Input/InputArea.jsx +++ b/src/components/Input/InputArea.jsx @@ -30,7 +30,7 @@ function InputArea({ value, onInput, disabled, className }) { return ( + ); } @@ -27,4 +32,4 @@ InputText.propTypes = { className: PropTypes.string, }; -export default InputText; \ No newline at end of file +export default InputText; From 823b0eab8dc7cd3bf299600f35304883c48f6c33 Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:18:02 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Feat:=20Image=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/librarys/api/image.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/librarys/api/image.js diff --git a/src/librarys/api/image.js b/src/librarys/api/image.js new file mode 100644 index 0000000..6d0ac48 --- /dev/null +++ b/src/librarys/api/image.js @@ -0,0 +1,36 @@ +import { createFormData, getSpringAxios } from "./axios"; + +// get ~~ +// create ~~ +// modify ~~ +// delete ~~ + +export async function createImage(files) { + const axios = getSpringAxios(); + + const body = { + files, + }; + + const response = await axios.post("/upload", createFormData(body)); + + const data = { + uuid: response.data.uuid, + fileName: response.data.fileName, + link: response.data.link, + }; + + return data; +} + +export async function deleteImage(filePath) { + const axios = getSpringAxios(); + + const response = await axios.delete("/remove/" + filePath); + + const data = { + status: true, + message: response.data, + }; + return data; +} From 7b702ed5149dc0f5617fa1ead761fb15fa075abd Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:46:37 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Feat:=20=EA=B3=BC=EC=A0=9C=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=ED=8E=98=EC=9D=B4=EC=A7=80=20API=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TherapistDashBoard/TheraMakeAssign.jsx | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/components/TherapistDashBoard/TheraMakeAssign.jsx b/src/components/TherapistDashBoard/TheraMakeAssign.jsx index feaf160..a13255e 100644 --- a/src/components/TherapistDashBoard/TheraMakeAssign.jsx +++ b/src/components/TherapistDashBoard/TheraMakeAssign.jsx @@ -6,7 +6,7 @@ import { useEffect, useMemo, useReducer, useState } from "react"; import Pagination from "../Pagination/Pagination"; import { ReducerContext } from "../../reducer/context.js"; import BlockContainer from "../Common/BlockContainer.jsx"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { intialprogramAssignState, programAssignReducer, @@ -18,6 +18,9 @@ import { IoClose } from "react-icons/io5"; import Button from "../Button/Button.jsx"; import { getVideoList } from "../../librarys/api/video.js"; import DnDList from "../Common/DnDList.jsx"; +import { modifyProgram } from "../../librarys/api/program.js"; +import { useSelector } from "react-redux"; +import { selectId } from "../../redux/userSlice.js"; const InputArea = styled(InputAreaContainer)` margin-top: 28px; @@ -76,6 +79,8 @@ const TheraMakeAssign = () => { programAssignReducer, intialprogramAssignState, ); + const { id } = useParams(); + const adminId = useSelector(selectId); const { programList, @@ -101,6 +106,33 @@ const TheraMakeAssign = () => { }); }; + const handleSubmit = async () => { + if (assignDescription === "" || assignDescription.length < 4) { + alert("과제 설명을 4자 이상 적어주세요."); + return; + } + + if (assignList === null || assignList.length < 1) { + alert("환자에게 운동을 할당해주세요!"); + return; + } + + console.log(assignList); + + const response = await modifyProgram({ + adminId, + userId: id, + description: assignDescription, + list: assignList.map((item) => item.vno), + }); + + console.log(response); + + alert("과제 할당이 완료되었습니다."); + + navigate("/"); + }; + const handleDragEnd = (event) => { console.log(event); if (event.destination === null) { @@ -208,7 +240,9 @@ const TheraMakeAssign = () => { - + From 5fef48ff757187f5959e0bc8296a3778b7cc202a Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:47:29 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Refactor:=20UserSelectCard=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=20State=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationSelect.jsx | 19 +++++-- .../UserDashBoard/UserSelectCard.jsx | 56 +++++++++++-------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/src/components/Reservation/ReservationSelect.jsx b/src/components/Reservation/ReservationSelect.jsx index da69017..669d68d 100644 --- a/src/components/Reservation/ReservationSelect.jsx +++ b/src/components/Reservation/ReservationSelect.jsx @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; import styled from "styled-components"; import { UserSelectCard } from "../UserDashBoard/UserSelectCard"; -import { getDoctorData, userLogin } from "../../librarys/dummy-api"; import BlockContainer from "../Common/BlockContainer.jsx"; import TitleText from "../Common/TitleText.jsx"; import ReservationCreateModal from "./ReservationCreateModal.jsx"; @@ -19,8 +18,6 @@ const Text = styled.p` font-weight: 400; `; -const { doctor, therapist } = getDoctorData(); - const ReservationSelect = () => { return ( @@ -28,8 +25,20 @@ const ReservationSelect = () => { 진료를 희망하는 의료진을 선택해주세요. - - + + ); diff --git a/src/components/UserDashBoard/UserSelectCard.jsx b/src/components/UserDashBoard/UserSelectCard.jsx index e3bc95e..c30cab8 100644 --- a/src/components/UserDashBoard/UserSelectCard.jsx +++ b/src/components/UserDashBoard/UserSelectCard.jsx @@ -85,56 +85,64 @@ const MidSection = styled.div` background-color: rgba(0, 100, 255, 0.03); `; -export const UserSelectCard = ({ userType, userData }) => { +export const UserSelectCard = ({ + id, + role, + name, + image, + hospital, + department, +}) => { const dispatch = useDispatch(); - const [isModalOpen, setIsModalOpen] = useState(false); - const toggleModal = () => { - dispatch(show("reservation_create")); + const handleClick = () => { + dispatch( + show({ + id: "reservation_create", + props: id, + }), + ); }; - if (!userData) return null; - - const imageUrl = userType === "admin1" ? DoctorImage : TherapistImage; - const title = - userType === "admin1" ? "담당 전문의 프로필" : "담당 재활치료사 프로필"; - return ( <> - {title} + 담당 {role} 프로필 - + - {userData.name} + {name} - {" "} - {userType === "admin1" ? "전문의" : "재활치료사"} + {role} - {userData.workplace} + {hospital} - {userData.major} + {department} - + ); }; UserSelectCard.propTypes = { - userType: PropTypes.string.isRequired, - userData: PropTypes.shape({ - name: PropTypes.string, - workplace: PropTypes.string, - major: PropTypes.string, - }).isRequired, + id: PropTypes.string, + role: PropTypes.string, + name: PropTypes.string, + image: PropTypes.string, + hospital: PropTypes.string, + department: PropTypes.string, +}; + +UserSelectCard.defaultProps = { + image: DoctorImage, }; export default UserSelectCard; From b2d5d6229a5024f87ece93991073f9616e98771c Mon Sep 17 00:00:00 2001 From: PortalCube <35104213+PortalCube@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:52:56 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Chore:=20UserSelectCard=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserDashBoard/UserSelectCard.jsx | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/components/UserDashBoard/UserSelectCard.jsx b/src/components/UserDashBoard/UserSelectCard.jsx index c30cab8..86d3327 100644 --- a/src/components/UserDashBoard/UserSelectCard.jsx +++ b/src/components/UserDashBoard/UserSelectCard.jsx @@ -1,15 +1,13 @@ import { useState } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; -import Icondoctor from "../../assets/icons/icondoctor.png"; -import Iconmajor from "../../assets/icons/iconmajor.png"; -import Iconhospital from "../../assets/icons/iconhospital.png"; import DoctorImage from "../../assets/images/user/Odoctor.png"; -import TherapistImage from "../../assets/images/user/Otherapist.png"; -import { ReservationCreateModal } from "../Reservation/ReservationCreateModal"; import { useDispatch } from "react-redux"; import { show } from "../../redux/modalSlice.js"; +import { FaUser } from "react-icons/fa"; +import { MdLocalHospital, MdLocationOn } from "react-icons/md"; + const Card = styled.div` width: 280px; border: 1px solid #e1e1e1; @@ -18,7 +16,6 @@ const Card = styled.div` box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); text-align: center; margin: 20px; - font-family: "Spoqa Han Sans Neo", "sans-serif"; background-color: #ffffff; `; @@ -52,14 +49,18 @@ const Avatar = styled.img` const UserName = styled.span` font-size: 30px; font-weight: bold; - font-family: "Spoqa Han Sans Neo", "sans-serif"; `; const Info = styled.div` - font-family: "Spoqa Han Sans Neo", "sans-serif"; font-size: 12px; margin-bottom: 10px; text-align: left; + & > svg { + height: 16px; + width: 16px; + margin-right: 5px; + vertical-align: middle; + } `; const Button = styled.button` @@ -74,17 +75,6 @@ const Button = styled.button` font-size: 12px; `; -const Icon = styled.img` - height: 12px; - width: 12px; - margin-right: 5px; - vertical-align: middle; -`; - -const MidSection = styled.div` - background-color: rgba(0, 100, 255, 0.03); -`; - export const UserSelectCard = ({ id, role, @@ -109,21 +99,19 @@ export const UserSelectCard = ({ 담당 {role} 프로필 - - - - - {name} - + + + + {name} - {role} + {role} - {hospital} + {hospital} - {department} + {department}