박권수

feat. 회원 가입 시 병원 검색, 등록

......@@ -5,6 +5,7 @@ const Profile = require('../../models/profile');
const DoctorInfo = require('../../models/doctorInfo');
const Joi = require('joi');
const jwt = require('jsonwebtoken');
const axios = require('axios');
exports.register = async(ctx) => {
......@@ -66,6 +67,29 @@ exports.register = async(ctx) => {
};
exports.searchHospital = async ctx => {
const {
hospitalNm,
page,
} = ctx.query;
const pageSlice = 5;
const url = 'http://apis.data.go.kr/B551182/hospInfoService1/getHospBasisList1';
let queryParams = '?' + encodeURIComponent('ServiceKey') + '=' + process.env.SERVICE_KEY;
queryParams += '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent(page);
queryParams += '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent(pageSlice);
queryParams += '&' + encodeURIComponent('yadmNm') + '=' + encodeURIComponent(hospitalNm);
const result = await axios.get(url + queryParams);
ctx.status = 200;
ctx.body = {
totalPage : Math.ceil(result.data.response.body.totalCount / pageSlice),
hospitalList : result.data.response.body.items.item,
};
};
exports.doctorRegister = async ctx => {
const {
userId,
......
......@@ -12,6 +12,14 @@ const auth = new Router()
auth.post('/register', authCtrl.register)
/**
* 병원 검색
* url : http://localhost:4000/api/auth/hospital
* request parameter : hospitalNm
* return : xml type data
*/
auth.get('/hospital', authCtrl.searchHospital);
/**
* 회원가입 (email type) : 의사 회원가입
* url : http://localhost:4000/api/auth/register/doctor
* request parameter : userId, password, passwordCheck, doctorInfo
......
......@@ -11,6 +11,7 @@ exports.updateMedicineInfo = async() => {
//queryUrl을 return하는 함수 : 한 페이지에 100개의 item씩 요청할 수 있다.
const getQueryURL = (i) => {
const url = 'http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList';
// eslint-disable-next-line no-undef
const queryParams = '?' + encodeURIComponent('ServiceKey') + '=' + process.env.SERVICE_KEY;
const pageNum = '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent(i);
const numOfItem = '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent(100);
......@@ -24,6 +25,7 @@ const getItemsList = async(queryUrl) => {
let i = 1, getItem = null, items = null;
const result = [];
// eslint-disable-next-line no-constant-condition
while(true) {
getItem = await axios.get(queryUrl(i));
items = getItem.data.body.items;
......
......@@ -5,6 +5,15 @@ export default {
return client.post('/auth/register', Data);
},
searchHospital : (hospitalNm : string, page : number) => {
return client.get('/auth/hospital', {
params : {
hospitalNm,
page,
},
});
},
registerDoctor : (Data : any) => {
return client.post('/auth/register/doctor', Data);
},
......
import React, { useState, useEffect } from "react";
import { RouteComponentProps } from 'react-router-dom';
import { useRecoilValue } from "recoil";
import { useRecoilValue, useRecoilState } from "recoil";
import * as recoilUtil from '../../util/recoilUtil';
import validator from 'validator';
......@@ -11,7 +11,6 @@ import Header from '../../components/Header';
import RegisterPresenter from "./RegisterPresenter";
import { authApi } from '../../api';
import { resourceLimits } from "worker_threads";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
......@@ -20,6 +19,7 @@ interface RegisterProps extends RouteComponentProps {}
const RegisterContainer = (props : RegisterProps) => {
const token = useRecoilValue(recoilUtil.token);
const [loading, setLoading] = useRecoilState(recoilUtil.loading);
const [registerForm, setRegisterForm] = useState<any>({
userId : '',
......@@ -38,6 +38,12 @@ const RegisterContainer = (props : RegisterProps) => {
const [page, setPage] = useState<number>(1);
const [error, setError] = useState<string | null>(null);
const [searchHospital, setSearchHospital] = useState<boolean>(false);
const [hospitalNm, setHospitalNm] = useState<string>('');
const [hospitalSearchPage, setHospitalSearchPage] = useState<number>(1);
const [hospitalSearchPageList, setHospitalSearchPageList] = useState<number[]>([1]);
const [hospitalList, setHospitalList] = useState<any[]>([]);
const [selectHospital, setSelectHospital] = useState<any>(null);
const fetchData = async() => {
......@@ -46,7 +52,7 @@ const RegisterContainer = (props : RegisterProps) => {
if (result.statusText === 'OK') {
props.history.push('/');
}
}
}
};
const onCancleRegister = () => {
......@@ -126,23 +132,7 @@ const RegisterContainer = (props : RegisterProps) => {
};
const onSetHospitalNm = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
hospitalNm : e.target.value,
},
});
};
const onSetHospitalAddr = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
hospitalAddr : e.target.value,
},
});
setHospitalNm(e.target.value);
};
const onSetContact = (e : React.ChangeEvent<HTMLInputElement>) => {
......@@ -175,6 +165,49 @@ const RegisterContainer = (props : RegisterProps) => {
});
};
const onSearchHospital = async () => {
try {
setLoading(true);
setSearchHospital(true);
const result = await authApi.searchHospital(hospitalNm, hospitalSearchPage);
if(result.statusText === 'OK') {
setLoading(false);
setHospitalSearchPageList(new Array(result.data.totalPage).fill(null).map((item : null, index : number) => index + 1));
setHospitalList(result.data.hospitalList.length ? result.data.hospitalList : [result.data.hospitalList]);
}
} catch(e : any) {
setLoading(false);
Alert.onError('알 수 없는 에러로 검색에 실패했습니다.', () => null);
}
};
const onSetSearchPrevPage = () => {
//set Prev Page
const pageSlice = 5;
if(hospitalSearchPage > pageSlice) {
setHospitalSearchPage(Math.floor((hospitalSearchPage - 1) / pageSlice) * pageSlice);
}
};
const onSetSearchNextPage = () => {
//set Next Page
const pageSlice = 5;
if(hospitalSearchPage <= Math.floor((hospitalSearchPageList.length - 1) / pageSlice) * pageSlice) {
setHospitalSearchPage(Math.ceil(hospitalSearchPage / pageSlice) * pageSlice + 1);
}
};
const onCancelSearchHospital = () => {
Alert.onCheck('병원 등록이 취소됩니다. 계속하시겠습니까?', () => {
setSearchHospital(false);
setHospitalNm('');
setHospitalSearchPage(1);
setHospitalSearchPageList([1]);
setHospitalList([]);
setSelectHospital(null);
}, () => null);
};
const onSubmitButton = () => {
if(error) {
Alert.onError(error, () => null);
......@@ -192,20 +225,54 @@ const RegisterContainer = (props : RegisterProps) => {
if(result.data === 'Created') {
Alert.onSuccess('회원가입 성공, 관리자의 승인을 대기하세요.', () => props.history.push('/login'));
}
} catch(e) {
} catch(e : any) {
Alert.onError(e.response.data.error, () => null);
}
};
Alert.onCheck('입력하신 정보로 회원가입을 진행하시겠습니까?', onRegisterDoctor, () => null);
if(selectHospital) {
Alert.onCheck('입력하신 정보로 회원가입을 진행하시겠습니까?', onRegisterDoctor, () => null);
} else {
Alert.onError('검색 버튼을 눌러 병원을 선택해주세요.', () => null);
}
}
};
useEffect(() => {
validateRegisterForm();
}, [registerForm, page]);
useEffect(() => {
if(selectHospital) {
setHospitalNm(selectHospital.yadmNm);
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
hospitalNm : selectHospital.yadmNm,
hospitalAddr : selectHospital.addr,
},
});
} else {
setHospitalNm('');
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
hospitalNm : '',
hospitalAddr : '',
},
});
}
}, [selectHospital]);
useEffect(() => {
if(searchHospital) onSearchHospital();
}, [hospitalSearchPage]);
useEffect(() => {
fetchData();
......@@ -228,12 +295,27 @@ const RegisterContainer = (props : RegisterProps) => {
onSetPassword = {onSetPassword}
onSetPasswordCheck = {onSetPasswordCheck}
onSetDoctorLicense = {onSetDoctorLicense}
hospitalNm = {hospitalNm}
setHospitalNm = {setHospitalNm}
onSetHospitalNm = {onSetHospitalNm}
onSetHospitalAddr = {onSetHospitalAddr}
onSetContact = {onSetContact}
onSetDoctorType = {onSetDoctorType}
onSetDoctorNm = {onSetDoctorNm}
onSubmitButton = {onSubmitButton}
searchHospital = {searchHospital}
setSearchHospital = {setSearchHospital}
onSearchHospital = {onSearchHospital}
hospitalSearchPage = {hospitalSearchPage}
setHospitalSearchPage = {setHospitalSearchPage}
hospitalSearchPageList = {hospitalSearchPageList}
onSetSearchPrevPage = {onSetSearchPrevPage}
onSetSearchNextPage = {onSetSearchNextPage}
onCancelSearchHospital = {onCancelSearchHospital}
hospitalList = {hospitalList}
selectHospital = {selectHospital}
setSelectHospital = {setSelectHospital}
/>
</>
)
......
This diff is collapsed. Click to expand it.
import styled from 'styled-components';
import styled, { keyframes } from 'styled-components';
const ModalOn = keyframes `
0% {
background-color : rgba(52, 52, 52, .0);
}
20% {
background-color : rgba(52, 52, 52, .2);
}
40% {
background-color : rgba(52, 52, 52, .4);
}
60% {
background-color : rgba(52, 52, 52, .5);
}
80% {
background-color : rgba(52, 52, 52, .6);
}
100% {
background-color : rgba(52, 52, 52, .7);
}
`;
export const Container = styled.div `
......@@ -10,6 +33,274 @@ export const Container = styled.div `
align-items : center;
`;
export const ModalContainer = styled.div `
height : 100%;
width : 100%;
z-index : 99;
position : absolute;
display : flex;
flex-direction : column;
animation : ${ModalOn} .5s;
background-color : rgba(52, 52, 52, .7);
`;
export const ModalClsButtonWrapper = styled.div `
flex : 1;
display : flex;
justify-content : flex-end;
align-items : center;
padding : 0 20px;
border : none;
background-color : transprent;
`;
export const ModalClsButton = styled.button `
border : none;
background-color : transparent;
cursor : pointer;
color : #fff;
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
transition : .25s all;
&:hover {
opacity : .5;
}
`;
export const ModalClsButtonImg = styled.img `
height : 20px;
width : 20px;
margin : 0 10px 0 0;
`;
export const ModalClsButtonText = styled.div `
font-size : 18px;
font-weight : 700;
`;
export const ModalContentWrapper = styled.div `
flex : 8;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
border : none;
`;
export const ModalContent = styled.div `
width : 600px;
height : 400px;
background-color : #fff;
border : 1.2px solid #337DFF;
border-radius : 5px;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
`;
export const SearchTitle = styled.div `
font-weight : 600;
font-size : 20;
color : #337DFF;
`;
export const HospitalListWrapper = styled.div `
margin : 20px 0;
height : 200px;
width : 80%;
border : 1px solid #337DFF;
display : flex;
flex-direction : column;
`;
export const HospitalListInfo = styled.div `
height : 20px;
width : 100%;
border : none;
border-bottom : 1px solid #ddd;
display : flex;
flex-direction : row;
`;
export const HospitalListInfoEach = styled.div<{isLast : boolean}> `
flex : ${props => props.isLast ? '1' : '3'};
display : flex;
align-items : center;
justify-content : center;
font-size : 14px;
font-weight : 500;
color : #343434;
border : none;
border-right : ${props => props.isLast ? 'none' : '1px solid #ddd'};
padding : 3px 5px;
`;
export const HospitalListEach = styled.div `
min-height : 35px;
max-height : 35px;
width : 100%;
display : flex;
flex-direction : row;
border : none;
border-bottom : 1px solid #ddd;
`;
export const HospitalListEachInfo = styled.div<{isLast : boolean}> `
flex : ${props => props.isLast ? '1' : '3'};
display : flex;
font-size : 12px;
font-weight : 500;
justify-content : center;
align-items : center;
border : none;
border-right : ${props => props.isLast ? 'none' : '1px solid #ddd'};
padding : 3px 5px;
`;
export const CheckButton = styled.button `
border : none;
background-color : transparent;
height : 15px;
width : 15px;
display : flex;
flex-direction : row;
justify-content : center;
cursor : pointer;
transition : .25s all;
&:hover {
opacity : .5;
}
`;
export const CheckButtonImg = styled.img `
height : 15px;
width : 15px;
`;
export const PageWrapper = styled.div `
width : 50%;
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
gap : 2%;
`;
export const PageButton = styled.button<{isSelect : boolean}> `
height : 18px;
width : 18px;
display : flex;
align-items : center;
justify-content : center;
border : none;
border-radius : 4px;
background-color : ${props => props.isSelect ? '#337DFF' : 'transparent'};
color : ${props => props.isSelect ? '#fff' : '#343434'};
font-size : 12px;
font-weight : 600;
cursor : pointer;
transition : .25s all;
&:hover {
opacity : .7;
}
`;
export const PageArrowImg = styled.img `
height : 15px;
width : 15px;
`;
export const ModalButtonWrapper = styled.div `
margin : 20px 0 0 0;
width : 50%;
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
border : none;
gap : 10%;
`;
export const ModalButton = styled.div<{isCloseButton : boolean}> `
padding : 2.5% 10%;
cursor : pointer;
border : 1px solid ${props => props.isCloseButton ? '#343434' : '#337DFF'};
background-color : ${props => props.isCloseButton ? 'transparent' : '#337DFF'};
border-radius : 5px;
color : ${props => props.isCloseButton ? '#343434' : '#fff'};
font-weight : 600;
font-size : 16px;
transition : .25s all;
&:hover {
opacity : .7;
}
`
export const RegisterWrapper = styled.div `
width : 35%;
border : none;
......@@ -24,10 +315,9 @@ export const RegisterWrapper = styled.div `
padding : 30px 3px;
box-shadow: 0px 0px 10px #a0a0a0;
`;
export const RegisterBackButtonWrapper = styled.div `
width : 100%;
border : none;
......@@ -97,6 +387,18 @@ export const RegisterInputText = styled.div `
`;
export const RegisterInputWrapperForSearch = styled.div `
display : flex;
flex-direction : row;
justify-content : center;
width : 100%;
border : none;
background-color : transparent;
`;
export const RegisterInput = styled.input `
width : 80%;
padding : 5px 10px;
......@@ -111,6 +413,38 @@ export const RegisterInput = styled.input `
}
`;
export const RegisterInputSearchButton = styled.button `
position : absolute;
height : 25px;
width : 25px;
align-self : end;
margin : 0 0 1px 24%;
background-color : transparent;
border : none;
transition : .25s all;
&:hover {
opacity : .5;
}
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
cursor : pointer;
`;
export const RegisterInputSearchButtonImg = styled.img `
height : 20px;
width : 20px;
`;
export const RegisterButtonWrapper = styled.div `
margin : 20px 0 0 0;
......