박권수

Merge branch 'web'

Showing 71 changed files with 2567 additions and 669 deletions
......@@ -5,10 +5,20 @@
"version": "0.2.0",
"configurations": [
{
"name": "flutter_application_1",
"cwd": "frontend\\flutter_application_1",
"request": "launch",
"type": "dart"
}
"name": "Attach",
"port": 9229,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Attach to Chrome",
"port": 9222,
"request": "attach",
"type": "pwa-chrome",
"webRoot": "${workspaceFolder}"
},
]
}
\ No newline at end of file
......
const Koa = require('koa');
const cors = require('@koa/cors');
const Router = require('koa-router');
const cors = require('@koa/cors');
const bodyparser = require('koa-bodyparser');
const Mongoose = require('mongoose');
const api = require('./src/api');
const updateMedicineInfo = require('./src/lib/UpdatingMedicineInfo');
const MqttServer = require('./src/util/MqttServer');
const BatchSystem = require('./src/util/Batch');
// const FCM = require('./src/util/FCM');
require('dotenv').config();
// eslint-disable-next-line no-undef
......@@ -23,10 +25,10 @@ Mongoose.connect(MONGO_URL, {
useCreateIndex : true
}).then(() => {
console.log('\x1b[1;32mMongo DB is connected : ', MONGO_URL, '\x1b[0m');
// updateMedicineInfo.updateMedicineInfo();
BatchSystem.updateMedicineData();
}).catch(e => {
console.log(e);
})
});
app.use(bodyparser());
router.use('/api', api.routes());
......@@ -36,4 +38,7 @@ app.use(router.routes()).use(router.allowedMethods());
app.listen(SERVER_PORT, () => {
console.log('\x1b[1;36mPORT : ', SERVER_PORT, 'is connected\x1b[0m');
MqttServer.on();
})
\ No newline at end of file
// FCM.initializeFCM();
BatchSystem.removeQrCode();
BatchSystem.pushNotifyByDosage();
});
\ No newline at end of file
......
This diff could not be displayed because it is too large.
......@@ -17,11 +17,16 @@
"author": "박권수",
"license": "ISC",
"dependencies": {
"@google-cloud/storage": "^5.14.2",
"@koa/cors": "^3.1.0",
"firebase-admin": "^9.11.1",
"google-auth-library": "^7.10.0",
"koa-body": "^4.2.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"mqtt": "^4.2.6",
"node-cron": "^3.0.0"
"node-cron": "^3.0.0",
"qrcode": "^1.4.4"
},
"devDependencies": {
"eslint": "^7.32.0"
......
This diff is collapsed. Click to expand it.
const Router = require('koa-router')
const KoaBody = require('koa-body')({multipart : true});
const authCtrl = require('./auth.ctrl')
const auth = new Router()
/**
* 회원가입 (email type) : 환자 회원가입
* 로컬 회원가입 (email type) : 환자 회원가입
* url : http://localhost:4000/api/auth/register
* request parameter : userId, password, passwordCheck
* return : null
......@@ -12,28 +13,60 @@ const auth = new Router()
auth.post('/register', authCtrl.register)
/**
* 회원가입 (email type) : 의사 회원가입
* 병원 검색
* 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
* request parameter : userId, password, passwordCheck, doctorInfo(File)
* return : null
*/
auth.post('/register/doctor', authCtrl.doctorRegister)
auth.post('/register/doctor', KoaBody, authCtrl.doctorRegister)
/**
* 로그인 (email type)
* 로컬 로그인 (email type)
* url : http://localhost:4000/api/auth/login
* request parameter : userId, password
* return : userId
* return : token, userTypeCd
*/
auth.post('/login', authCtrl.login)
/**
* 소셜 회원가입(Google, Naver, Kakao)
* url : http://localhost:4000/api/auth/register/${socialType}
* request parameter : accessToken
* return : status
*/
auth.post('/register/social/:socialType', authCtrl.socialRegister);
/**
* 소셜 로그인(Google, Naver, Kakao)
* url : http://localhost:4000/api/auth/login/${socialType}
* request parameter
* return : token, userTypeCd
*/
auth.post('/login/social/:socialType', authCtrl.socialLogin);
/**
* 로그아웃
* url : http://localhost:4000/api/auth/logout
* request parameter : null
* return : null
*/
auth.post('/logout', authCtrl.logout)
auth.post('/logout', authCtrl.logout);
/**
* 회원 탈퇴
* url : http://localhost:4000/api/auth
* request parameter : password
* return : null
*/
auth.delete('/', authCtrl.secession);
/**
* 토큰이 유효한지 확인
......
/* eslint-disable no-undef */
//어플에서 약병 등록 및, 약병에 관한 정보 조회 = 여기서 mqtt통신으로 broker에 데이터를 요청한다.
const Bottle = require('../../models/bottle');
const Hub = require('../../models/hub');
const Medicine = require('../../models/medicine');
const User = require('../../models/user');
const DoctorInfo = require('../../models/doctorInfo');
const PatientInfo = require('../../models/patientInfo');
const TakeMedicineHist = require('../../models/takeMedicineHistory');
const BottleMedicine = require('../../models/bottleMedicine');
const Feedback = require('../../models/feedback');
const Mqtt = require('../../lib/MqttModule');
const Mqtt = require('../../util/MqttModule');
const jwt = require('jsonwebtoken');
//약병 등록
......@@ -19,6 +19,7 @@ exports.bottleConnect = async(ctx) => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -26,7 +27,7 @@ exports.bottleConnect = async(ctx) => {
return;
}
const { bottleId, hubId } = ctx.request.body;
const { bottleId, hubId, bottleNm } = ctx.request.body;
const isExistBottle = await Bottle.findByBottleId(bottleId);
if(isExistBottle) {
......@@ -53,7 +54,8 @@ exports.bottleConnect = async(ctx) => {
const newBottle = new Bottle({
bottleId,
hubId
hubId,
bottleNm,
});
const client = await Mqtt.mqttOn(hosting);
......@@ -73,6 +75,7 @@ exports.bottleDisconnect = async(ctx) => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -100,6 +103,7 @@ exports.bottleDisconnect = async(ctx) => {
const topic = 'bottle/' + bottleId + '/bts';
Mqtt.mqttUnsubscribe(client, topic);
await BottleMedicine.updateMany({ bottleId }, { useYn : 'N' });
await Bottle.deleteOne({ bottleId });
ctx.status = 204;
......@@ -114,6 +118,7 @@ exports.getBottleInfo = async(ctx) => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -146,21 +151,29 @@ exports.getBottleInfo = async(ctx) => {
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
if(bottleMedicine) {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
const doctorInfo = await DoctorInfo.findOne({ doctorId : bottleMedicine.doctorId });
const takeMedicineHist = await TakeMedicineHist
.find({ bmId : bottleMedicine._id })
.sort({ takeDate : 'desc' })
.populate('bmId');
.sort({ takeDate : 'desc' });
ctx.status = 200;
ctx.body = {
bottle,
medicine,
doctorInfo,
dailyDosage : bottleMedicine.dailyDosage,
totalDosage : bottleMedicine.totalDosage,
takeMedicineHist,
};
} else {
ctx.status = 200;
ctx.body = {
bottle,
medicine : null,
doctorInfo : null,
dailyDosage : null,
totalDosage : null,
takeMedicineHist : [],
}
}
......@@ -175,6 +188,7 @@ exports.getBottleFeedback = async ctx => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -213,7 +227,9 @@ exports.getBottleFeedback = async ctx => {
.populate('bmId');
ctx.status = 200;
ctx.body = feedbackList;
ctx.body = {
feedbackList
};
} else {
ctx.status = 404;
ctx.body = {
......@@ -231,6 +247,7 @@ exports.setMedicine = async(ctx) => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -239,7 +256,7 @@ exports.setMedicine = async(ctx) => {
}
const { bottleId } = ctx.params;
const { medicineId, dosage, doctorId } = ctx.request.body;
const { medicineId, doctorId, dailyDosage, totalDosage, } = ctx.request.body;
const bottle = await Bottle.findByBottleId(bottleId);
if(!bottle) {
......@@ -273,7 +290,8 @@ exports.setMedicine = async(ctx) => {
let bottleMedicine = new BottleMedicine({
bottleId,
medicineId,
dosage,
dailyDosage,
totalDosage,
});
if(doctorId !== undefined && doctorId !== null && doctorId !== '') {
......@@ -286,14 +304,109 @@ exports.setMedicine = async(ctx) => {
return;
}
bottleMedicine.setDoctorId(doctorId);
await bottleMedicine.setDoctorId(doctorId);
}
await BottleMedicine.updateMany({ bottleId }, { useYn : 'N '});
await bottleMedicine.save();
ctx.status = 200;
};
//약 무게 세팅
exports.setMedicineWeight = async ctx => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
ctx.status = 403;
return;
}
const { bottleId } = ctx.params;
bottleMedicine.save();
const bottle = await Bottle.findByBottleId(bottleId);
if(!bottle) {
ctx.status = 404;
ctx.body = {
error : '약병 찾을 수 없음.',
}
return;
}
const hub = await Hub.findByHubId(bottle.getHubId());
if(hub.getHub_UserId() !== userId) {
ctx.status = 403;
ctx.body = {
error : '해당 허브 권한 없음',
}
return;
}
//toDo : 약병에서 가져온 무게 데이터를 이용하여, bottleMedicine값을 갱신.
const client = await Mqtt.mqttOn(await hub.getHubHost());
const topic = 'bottle/' + bottleId + '/stb';
const message = 'weight';
await Mqtt.mqttPublishMessage(client, { topic, message });
// const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
// const { totalWeight, totalDosage } = bottleMedicine;
// if(totalDosage) bottleMedicine.setEachWeight(totalWeight / totalDosage);
ctx.status = 200;
};
//약병 이름 변경
exports.setBottleName = async ctx => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
ctx.status = 403;
return;
}
const { bottleId } = ctx.params;
const { bottleNm } = ctx.request.body;
const bottle = await Bottle.findByBottleId(bottleId);
if(!bottle) {
ctx.status = 404;
ctx.body = {
error : '약병 찾을 수 없음.',
}
return;
}
const hub = await Hub.findByHubId(bottle.getHubId());
if(hub.getHub_UserId() !== userId) {
ctx.status = 403;
ctx.body = {
error : '해당 허브 권한 없음',
}
return;
}
await bottle.setBottleNm(bottleNm);
await bottle.save();
ctx.status = 200;
};
// //비어있는 약병에 의사를 등록한다.
......@@ -344,6 +457,7 @@ exports.getHubsBottleList = async(ctx) => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -385,6 +499,7 @@ exports.getAllBottleList = async ctx => {
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
......@@ -402,7 +517,7 @@ exports.getAllBottleList = async ctx => {
ctx.status = 200;
ctx.body = {
bottleList
bottleList,
};
};
\ No newline at end of file
......
......@@ -44,6 +44,22 @@ bottle.get('/feedback/:bottleId', bottleCtrl.getBottleFeedback);
bottle.patch('/:bottleId', bottleCtrl.setMedicine);
/**
* 약병에 등록된 약의 무게 갱신
* request parameter : bottleid
* url : http://localhost:4000/api/bottle/weight/:bottleId
* return : null
*/
bottle.patch('/weight/:bottleId', bottleCtrl.setMedicineWeight);
/**
* 약병 이름 변경
* request parameter : bottleid, bottleNm
* url : http://localhost:4000/api/bottle/name/:bottleId
* return : null
*/
bottle.patch('/name/:bottleId', bottleCtrl.setBottleName);
/**
* 비어있는 약병에 전담의 등록
* request parameter : bottleId, doctorId
* url : http://localhost:4000/api/bottle/doctor/:bottleId
......
......@@ -9,8 +9,14 @@ const Feedback = require('../../models/feedback');
const Hub = require('../../models/hub');
const PatientInfo = require('../../models/patientInfo');
const DoctorInfo = require('../../models/doctorInfo');
const PrescribeInfo = require('../../models/prescribeInfo');
const jwt = require('jsonwebtoken');
const { uploadQrCode, getQrCodeUrl } = require('../../util/GoogleCloudStorage');
const QrCodeUtil = require('../../util/QrCodeUtil');
const { sendPushMessage } = require('../../util/FCM');
/**
* 현재 로그인한 유저의 의사 정보를 가져온다
......@@ -150,7 +156,8 @@ exports.getPatientDetail = async ctx => {
bottleId : bottle.bottleId,
useYn : 'Y',
});
reqUserBmList.push(bm);
if(bm) reqUserBmList.push(bm);
}));
const bottleList = await Promise.all(reqUserBmList.map(async bottleMedicine => {
......@@ -280,7 +287,7 @@ exports.writeReqPatientReport = async ctx => {
}
await patientInfo.updateInfo(info);
patientInfo.save();
await patientInfo.save();
ctx.status = 200;
......@@ -335,13 +342,31 @@ exports.writeReqBottleFeedback = async ctx => {
doctorId : userId,
feedback,
});
newFeedback.save();
await newFeedback.save();
//feedback 알람 보내기
const hub = await Hub.findOne({ hubId : bottle.hubId });
const patientProfile = await Profile.findOne({ userId : hub.userId });
if(patientProfile) {
sendPushMessage({
deviceToken : patientProfile.deviceToken,
title : '의사에게 새로운 알람이 도착했습니다.',
body : feedback,
});
}
ctx.status = 200;
};
exports.searchPatientById = async ctx => {
/**
* 이메일로 환자를 검색한다.
* @param {*} ctx
* @returns
*/
exports.searchPatientByContact = async ctx => {
const token = ctx.req.headers.authorization;
if (!token || !token.length) {
ctx.status = 401;
......@@ -359,22 +384,19 @@ exports.searchPatientById = async ctx => {
return;
}
const { patientId } = ctx.params;
const patient = await User.findByUserId(patientId);
if(!patient || patient.useYn !== 'Y') {
ctx.status = 404;
ctx.body = {
error : '존재하지 않는 회원',
};
return;
}
const { contact } = ctx.params;
const patientProfile = await Profile.findOne({ contact, useYn : 'Y' });
const patientProfile = await Profile.findOne({ userId : patientId });
const patientInfo = {
userId : patientProfile.userId,
userNm : patientProfile.userNm,
birth : patientProfile.birth,
contact: patientProfile.contact,
};
ctx.status = 200;
ctx.body = {
patientNm : patientProfile.userNm,
patientId,
patientInfo,
};
};
......@@ -427,8 +449,17 @@ exports.registerNewPatient = async ctx => {
useYn : 'W',
});
patientInfo.updateInfo('환자 등록 요청');
patientInfo.save();
await patientInfo.updateInfo('환자 등록 요청');
await patientInfo.save();
const profile = await Profile.findByUserId(patientId);
const { deviceToken } = profile;
sendPushMessage({
deviceToken,
title : '새로운 의사 등록 요청이 왔습니다.',
body : '어플리케이션을 실행해 확인하세요.',
});
ctx.status = 200;
......@@ -477,8 +508,113 @@ exports.removeReqPatient = async ctx => {
}
await patientInfo.setUseYn('N')
patientInfo.save();
await patientInfo.save();
ctx.status = 200;
};
/**
* 특정 환자에게 특정 약을 처방한다
* @param {*} ctx
* http methods : post
*/
exports.prescribeMedicine = async ctx => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || user.userTypeCd !== 'DOCTOR') {
ctx.status = 403;
ctx.body = {
error : '권한 없는 유저',
};
return;
}
const {
patientId,
medicineId,
dailyDosage,
totalDosage,
} = ctx.request.body;
const patientInfo = await PatientInfo.findOne({
patientId,
doctorId : userId,
useYn : 'Y',
})
if(!patientInfo) {
ctx.status = 403;
ctx.body = {
error : '권한 없는 환자',
};
return;
}
const medicine = await Medicine.findOne({ medicineId });
if(!medicine) {
ctx.status = 404;
ctx.body = {
error : '존재하지 않는 약',
};
return;
}
const qrCodeResult = await QrCodeUtil.generateQrCode_prescribe({
medicine,
dailyDosage,
totalDosage,
patientId,
doctorId : userId,
});
if(!qrCodeResult) {
ctx.status = 400;
ctx.body = {
error : 'QR 코드 생성 에러',
};
return;
}
const qrCodeUrl = await uploadQrCode(qrCodeResult);
if(!qrCodeUrl) {
ctx.status = 400;
ctx.body = {
error : 'QR 코드 생성 후 서버 업로드 에러',
};
return;
}
const prescribeInfo = new PrescribeInfo({
doctorId : userId,
patientId,
dailyDosage,
totalDosage,
medicineId,
qrCodeUrl,
});
await prescribeInfo.save();
//특이사항에 처방기록 저장
await patientInfo.updateInfo(`${medicine.name}, 하루 ${dailyDosage}회분 처방, 총 ${totalDosage}회분 처방`);
await patientInfo.save();
const { qrCodeFileName } = qrCodeResult;
const qrCode = await getQrCodeUrl({ qrCodeFileName });
ctx.status = 200;
ctx.body = {
qrCode,
};
};
\ No newline at end of file
......
......@@ -7,7 +7,7 @@ const doctor = new Router();
/**
* 현재 로그인한 유저(의사)의 정보를 가져옴.
* request parameter : token
* url : http://localhost:4000/doctor/
* url : http://localhost:4000/api/doctor/
* return : doctor's Info
*/
doctor.get('/', doctorCtrl.getDoctorsInfo);
......@@ -15,7 +15,7 @@ doctor.get('/', doctorCtrl.getDoctorsInfo);
/**
* 현재 로그인한 유저(의사)의 관리 환자 목록을 가져옴
* request parameter
* url : http://localhost:4000/doctor/patient
* url : http://localhost:4000/api/doctor/patient
* return : patient List
*/
doctor.get('/patient', doctorCtrl.getPatientList);
......@@ -23,7 +23,7 @@ doctor.get('/patient', doctorCtrl.getPatientList);
/**
* 현재 로그인한 유저(의사)의 관리 환자 상세 정보를 가져옴
* request parameter : patient Id
* url : http://localhost:4000/doctor/patient/:patientId
* url : http://localhost:4000/api/doctor/patient/:patientId
* return : patient Detail
*/
doctor.get('/patient/:patientId', doctorCtrl.getPatientDetail);
......@@ -31,7 +31,7 @@ doctor.get('/patient/:patientId', doctorCtrl.getPatientDetail);
/**
* 현재 로그인한 유저(의사)의 관리 약병 상세 정보를 가져옴
* request parameter : bottle Id
* url : http://localhost:4000/doctor/bottle/:bottleId
* url : http://localhost:4000/api/doctor/bottle/:bottleId
* return : bottle Detail
*/
doctor.get('/bottle/:bottleId', doctorCtrl.getBottleDetail);
......@@ -39,7 +39,7 @@ doctor.get('/bottle/:bottleId', doctorCtrl.getBottleDetail);
/**
* 현재 로그인한 유저(의사)의 특정 관리 환자의 특이사항을 기록함
* request parameter : reqUserId, info
* url : http://localhost:4000/doctor/patient
* url : http://localhost:4000/api/doctor/patient
* return : null
*/
doctor.patch('/patient', doctorCtrl.writeReqPatientReport);
......@@ -47,7 +47,7 @@ doctor.patch('/patient', doctorCtrl.writeReqPatientReport);
/**
* 현재 로그인한 유저(의사)의 특정 관리 환자의 약병의 피드백을 등록함.
* request parameter : bottleId, fdbType, feedback
* url : http://localhost:4000/doctor/bottle
* url : http://localhost:4000/api/doctor/bottle
* return : null
*/
doctor.post('/bottle', doctorCtrl.writeReqBottleFeedback);
......@@ -55,15 +55,15 @@ doctor.post('/bottle', doctorCtrl.writeReqBottleFeedback);
/**
* 현재 로그인한 유저(의사)가 이메일로 유저를 검색함
* request parameter : patientId
* url : http://localhost:4000/api/doctor/patient/search/:patientId
* url : http://localhost:4000/api/api/doctor/patient/search/:patientId
* return : patient Info(simple)
*/
doctor.get('/patient/search/:patientId', doctorCtrl.searchPatientById);
doctor.get('/patient/search/:contact', doctorCtrl.searchPatientByContact);
/**
* 현재 로그인한 유저(의사)의 관리 환자를 등록함.
* request parameter : patientId
* url : http://localhost:4000/doctor/patient
* url : http://localhost:4000/api/doctor/patient
* return : null
*/
doctor.post('/patient', doctorCtrl.registerNewPatient);
......@@ -71,11 +71,18 @@ doctor.post('/patient', doctorCtrl.registerNewPatient);
/**
* 현재 로그인한 유저(의사)의 특정 관리 환자를 삭제함.
* request parameter : patientId
* url : http://localhost:4000/doctor/patient/:patientId
* url : http://localhost:4000/api/doctor/patient/:patientId
* return : null
*/
doctor.delete('/patient/:patientId', doctorCtrl.removeReqPatient);
/**
* 의사가 관리하는 환자에 대해 특정 약을 처방함
* request paramter : patientId, medicineId, dosage
* url : http://localhost:4000/api/doctor/prescribe
* return : null
*/
doctor.post('/prescribe', doctorCtrl.prescribeMedicine);
module.exports = doctor;
module.exports = doctor;
\ No newline at end of file
......
//허브(Mqtt Broker)등록 및 삭제
const Hub = require('../../models/hub');
const Bottle = require('../../models/bottle');
const User = require('../../models/user');
const Mqtt = require('../../lib/MqttModule');
const DataProcess = require('../../lib/DataProcess');
const Mqtt = require('../../util/MqttModule');
const DataProcess = require('../../util/DataProcess');
const jwt = require('jsonwebtoken');
const BottleMedicine = require('../../models/bottleMedicine');
//허브 연결
exports.hubConnect = async (ctx) => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
......@@ -20,7 +23,7 @@ exports.hubConnect = async (ctx) => {
return;
}
const { hubId, host, port } = ctx.request.body;
const { hubId, host, hubNm, } = ctx.request.body;
const isExistHub = await Hub.findByHubId(hubId);
if(isExistHub) {
......@@ -30,7 +33,7 @@ exports.hubConnect = async (ctx) => {
const hosting = {
host,
port
port : "1883",
};
Mqtt.mqttOn(hosting, DataProcess.dataPublish);
......@@ -38,7 +41,8 @@ exports.hubConnect = async (ctx) => {
const hub = new Hub({
hubId,
hosting,
userId
userId,
hubNm,
});
await hub.save();
......@@ -47,6 +51,49 @@ exports.hubConnect = async (ctx) => {
};
//허브 연결 해제
exports.hubDisconnect = async(ctx) => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || !user.userTypeCd || user.useYn !== 'Y') {
ctx.status = 403;
return;
}
const { hubId } = ctx.params;
const hub = await Hub.findByHubId(hubId);
if(!hub) {
ctx.status = 404;
return;
}
if(hub.getHub_UserId() !== userId) {
ctx.status = 403;
return;
}
const hosting = await hub.getHubHost();
Mqtt.mqttOff(hosting);
const bottleList = await Bottle.find({ hubId });
await Promise.all(bottleList.map(async bottle => {
await BottleMedicine.updateMany({ bottleId : bottle.bottleId }, { useYn : 'N' });
}));
await Bottle.deleteMany({ hubId });
await Hub.deleteOne({ hubId });
ctx.status = 204;
};
//허브 정보 조회
exports.getHubList = async(ctx) => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
......@@ -74,7 +121,8 @@ exports.getHubList = async(ctx) => {
};
};
exports.hubDisconnect = async(ctx) => {
//허브 이름 변경
exports.setHubName = async ctx => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
ctx.status = 401;
......@@ -90,21 +138,9 @@ exports.hubDisconnect = async(ctx) => {
}
const { hubId } = ctx.params;
const { hubNm } = ctx.request.body;
const hub = await Hub.findByHubId(hubId);
if(!hub) {
ctx.status = 404;
return;
}
if(hub.getHub_UserId() !== userId) {
ctx.status = 403;
return;
}
const hosting = await hub.getHubHost();
Mqtt.mqttOff(hosting);
await Hub.deleteOne({ hubId });
await Hub.updateOne({ hubId }, { hubNm });
ctx.status = 204;
ctx.status = 200;
};
......
......@@ -12,6 +12,14 @@ const hub = new Router();
hub.post('/', hubCtrl.hubConnect);
/**
* 허브 등록 해제
* request parameter : x
* url : http://localhost:4000/api/hub/:hubId
* return : null
*/
hub.delete('/:hubId', hubCtrl.hubDisconnect);
/**
* 로그인한 유저의 허브 목록 가져오기
* request parameter : X
* url : http://localhost:4000/api/hub
......@@ -20,11 +28,13 @@ hub.post('/', hubCtrl.hubConnect);
hub.get('/', hubCtrl.getHubList);
/**
* 허브 등록 해제
* request parameter : x
* 로그인한 유저의 특정 허브 이름 변경
* request parameter : hubId, hubNm
* url : http://localhost:4000/api/hub/:hubId
* return : null
*/
hub.delete('/:hubId', hubCtrl.hubDisconnect);
hub.patch('/:hubId', hubCtrl.setHubName);
module.exports = hub;
\ No newline at end of file
......
......@@ -7,6 +7,7 @@ const hub = require('./hub');
const medicine = require('./medicine');
const doctor = require('./doctor');
const manage = require('./manage');
const test = require('./test');
const api = new Router();
......@@ -17,5 +18,6 @@ api.use('/hub', hub.routes());
api.use('/medicine', medicine.routes());
api.use('/doctor', doctor.routes());
api.use('/manage', manage.routes());
api.use('/test', test.routes());
module.exports = api;
\ No newline at end of file
......
......@@ -12,6 +12,14 @@ const manage = new Router();
manage.get('/doctor', manageCtrl.getDoctorRegReqList);
/**
* 의사 회원탈퇴 요청을 한 회원들의 목록을 리턴
* request parameter : null
* url : http://localhost:4000/api/manage/doctor/sec
* return : doctor request List
*/
manage.get('/doctor/secession', manageCtrl.getDoctorSecReqList);
/**
* 의사 회원가입 요청을 한 특정 회원의 상세정보 확인
* request parameter : doctor Id
* url : http://localhost:4000/api/manage/doctor/:doctorId
......@@ -25,7 +33,7 @@ manage.get('/doctor/:doctorId', manageCtrl.getDoctorRegReqDetail);
* url : http://localhost:4000/api/manage/doctor/accept
* return : null
*/
manage.post('/doctor/accept', manageCtrl.acceptDoctorRegReq);
manage.patch('/doctor/accept', manageCtrl.acceptDoctorRegReq);
/**
* 의사 요청을 한 회원을 거절한다.
......@@ -33,7 +41,15 @@ manage.post('/doctor/accept', manageCtrl.acceptDoctorRegReq);
* url : http://localhost:4000/api/manange/doctor/reject
* return : null
*/
manage.post('/doctor/reject', manageCtrl.rejectDoctorRegReq);
manage.patch('/doctor/reject', manageCtrl.rejectDoctorRegReq);
/**
* 의사 탈퇴 요청을 수락한다.
* request parameter : doctor Id
* url : http://localhost:4000/api/manange/doctor/secession
* return : null
*/
manage.patch('/doctor/secession', manageCtrl.acceptDoctorSecReq);
/**
* 의사 요청을 한 회원의 자격 번호가 유효한지 검증한다
......
const User = require('../../models/user');
const DoctorInfo = require('../../models/doctorInfo');
const Profile = require('../../models/profile');
const PatientInfo = require('../../models/patientInfo');
const jwt = require('jsonwebtoken');
const { viewDoctorLicense } = require('../../util/GoogleCloudStorage');
/**
* 의사 회원가입을 요청한 회원 리스트를 확인한다.
......@@ -25,13 +27,54 @@ exports.getDoctorRegReqList = async ctx => {
}
try {
const doctorRegReqList = await DoctorInfo.find({
const doctorList = await DoctorInfo.find({
useYn : 'W',
});
ctx.status = 200;
ctx.body = {
doctorRegReqList
doctorList
};
} catch(e) {
ctx.status = 500;
ctx.body = {
error : '알 수 없는 에러가 발생했습니다.',
};
console.log(e);
return;
}
};
/**
* 의사 회원탈퇴를 요청한 회원 리스트를 확인한다.
* http methods : get
* @param {*} ctx
* @returns
*/
exports.getDoctorSecReqList = async ctx => {
const token = ctx.req.headers.authorization;
if (!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || user.userTypeCd !== 'MANAGER' || user.useYn !== 'Y') {
ctx.status = 403;
return;
}
try {
const doctorList = await DoctorInfo.find({
useYn : 'WS',
});
ctx.status = 200;
ctx.body = {
doctorList
};
} catch(e) {
......@@ -40,6 +83,7 @@ exports.getDoctorRegReqList = async ctx => {
error : '알 수 없는 에러가 발생했습니다.',
};
console.log(e);
return;
}
};
......@@ -108,9 +152,20 @@ exports.getDoctorRegReqDetail = async ctx => {
return;
}
const doctorLicense = await viewDoctorLicense({
doctorInfo
});
ctx.status = 200;
ctx.body = {
doctorInfo,
doctorInfo : {
...doctorInfo._doc,
info : {
...doctorInfo.info,
doctorLicense,
},
},
};
} catch (e) {
......@@ -118,12 +173,14 @@ exports.getDoctorRegReqDetail = async ctx => {
ctx.body = {
error : '알 수 없는 에러가 발생했습니다.',
};
console.log(e);
return;
}
};
/**
* 의사 요청이 온 회원을 수락한다.
* http methods : post
* http methods : patch
* @param {*} ctx
* @returns
*/
......@@ -143,7 +200,7 @@ exports.acceptDoctorRegReq = async ctx => {
}
try {
const { doctorId } = ctx.request.body;
const { doctorId, validateDoctorLicense } = ctx.request.body;
const doctor = await User.findOne({ userId : doctorId });
if(!doctor) {
ctx.status = 404;
......@@ -169,17 +226,37 @@ exports.acceptDoctorRegReq = async ctx => {
error : '의사로 가입된 회원이 아닙니다.',
};
return;
} else if(!validateDoctorLicense) {
ctx.status = 400;
ctx.body = {
error : '유효한 자격 번호가 아닙니다.',
};
return;
}
const existDoctorInfo = await DoctorInfo.findOne({
'info.validateDoctorLicense' : validateDoctorLicense
});
if(existDoctorInfo) {
ctx.status = 403;
ctx.body = {
error : '중복된 자격번호입니다.',
};
return;
}
const doctorInfo = await DoctorInfo.findOne({
doctorId,
useYn : 'W',
});
doctor.setUseYn('Y');
doctor.save();
doctorInfo.setUseYn('Y');
doctorInfo.save();
await doctor.setUseYn('Y');
await doctor.save();
await doctorInfo.setUseYn('Y');
await doctorInfo.setValidateDoctorLicense(validateDoctorLicense);
await doctorInfo.save();
ctx.status = 200;
......@@ -189,12 +266,13 @@ exports.acceptDoctorRegReq = async ctx => {
error : '알 수 없는 에러가 발생했습니다.',
};
console.log(e);
return;
}
};
/**
* 의사 요청이 온 회원을 거절한다.
* http methods : post
* http methods : patch
* @param {*} ctx
* @returns
*/
......@@ -242,16 +320,79 @@ exports.acceptDoctorRegReq = async ctx => {
return;
}
await DoctorInfo.updateOne({ doctorId, useYn : 'W' }, { useYn : 'N' });
const doctorInfo = await DoctorInfo.findOne({
doctorId,
useYn : 'W',
});
await doctor.setUseYn('N');
await doctor.save();
doctor.setUseYn('N');
doctor.save();
doctorInfo.setUseYn('N');
doctorInfo.save();
ctx.status = 200;
} catch(e) {
ctx.status = 500;
ctx.body = {
error : '알 수 없는 에러가 발생했습니다.',
};
console.log(e);
return;
}
};
/**
* 의사 회원탈퇴 요청을 수락한다.
* http methods : patch
* @param {*} ctx
* @returns
*/
exports.acceptDoctorSecReq = async ctx => {
const token = ctx.req.headers.authorization;
if (!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || user.userTypeCd !== 'MANAGER' || user.useYn !== 'Y') {
ctx.status = 403;
return;
}
try {
const { doctorId } = ctx.request.body;
const doctor = await User.findOne({ userId : doctorId });
if(!doctor) {
ctx.status = 404;
ctx.body = {
error : '존재하지 않는 회원입니다.',
};
return;
} else if(doctor.useYn === 'N') {
ctx.status = 400;
ctx.body = {
error : '탈퇴된 회원입니다.',
};
return;
} else if(doctor.useYn === 'Y') {
ctx.status = 400;
ctx.body = {
error : '이미 가입이 완료된 의사입니다.',
};
return;
} else if(doctor.userTypeCd !== 'DOCTOR') {
ctx.status = 400;
ctx.body = {
error : '의사로 가입된 회원이 아닙니다.',
};
return;
}
await DoctorInfo.updateOne({ doctorId, useYn : 'WS' }, { useYn : 'N' });
await PatientInfo.updateMany({ doctorId : userId, useYn : 'WS' }, { useYn : 'N' });
await doctor.setUseYn('N');
await doctor.save();
ctx.status = 200;
......@@ -261,9 +402,11 @@ exports.acceptDoctorRegReq = async ctx => {
error : '알 수 없는 에러가 발생했습니다.',
};
console.log(e);
return;
}
};
/**
* 회원가입을 요청한 의사의 유효한 자격 번호인지를 검증한다.
* @param {*} ctx
......@@ -284,12 +427,20 @@ exports.validateDoctorLicense = async ctx => {
return;
}
const { doctorLicense } = ctx.request.body;
const doctorInfo = await DoctorInfo.find({ 'info.doctorLicense' : doctorLicense });
const { validateDoctorLicense } = ctx.request.body;
if(!validateDoctorLicense) {
ctx.status = 404;
ctx.body = {
error : '유효한 자격번호를 입력해주세요',
};
return;
}
const doctorInfo = await DoctorInfo.findOne({ 'info.validateDoctorLicense' : validateDoctorLicense });
ctx.status = 200;
ctx.body = {
result : doctorInfo.length > 1 ? false : true,
result : doctorInfo ? false : true,
};
};
......
//test용 api입니다.
const Router = require('koa-router');
const testCtrl = require('./test.ctrl');
const test = new Router();
//푸쉬메시지 테스트
test.post('/fcm', testCtrl.sendFcmMessage);
module.exports = test;
//테스트용 api service
const { sendPushMessage } = require('../../util/FCM');
exports.sendFcmMessage = async ctx => {
const { deviceToken, title, body } = ctx.request.body;
try {
await sendPushMessage(ctx.request.body);
} catch(e) {
console.log('Error at FCM Sending : ', e);
}
};
\ No newline at end of file
......@@ -11,9 +11,18 @@ const user = new Router();
*/
user.get('/', userCtrl.getMyDetail);
/**
* 현재 로그인한 유저에 등록된 의사 목록 가져옴
* 현재 유저 정보 수정
* request parameter : token
* url : http://localhost:4000/api/user
* return : Object User
*/
user.patch('/', userCtrl.updateMyDetail);
/**
* 현재 로그인한 유저에 등록된 의사 목록 가져옴
* request parameter : userNm, birth, contact, password, passwordCheck
* url : http://localhost:4000/api/user/doctor
* return : Doctor List
*/
......
......@@ -38,9 +38,65 @@ exports.getMyDetail = async ctx => {
/**
* 내 정보를 업데이트한다.
* @param {*} ctx
* http methods : post
* http methods : patch
*/
exports.updateMyInfo = async ctx => {
exports.updateMyDetail = async ctx => {
const token = ctx.req.headers.authorization;
if(!token || !token.length) {
ctx.status = 401;
return;
}
// eslint-disable-next-line no-undef
const { userId } = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findByUserId(userId);
if(!user || user.useYn !== 'Y' || user.userTypeCd !== 'NORMAL') {
ctx.status = 403;
return;
}
const profile = await Profile.findByUserId(userId);
if(!profile || profile.useYn !== 'Y') {
ctx.status = 403;
return;
}
const { userNm, birth, contact, password, passwordCheck, } = ctx.request.body;
const existContact = await Profile.findOne({ contact, useYn : 'Y' });
if(existContact) {
ctx.status = 409;
ctx.body = {
error : '이미 가입된 번호',
};
return;
}
//passwordCheck가 있고 로컬 회원이라면 비밀번호 변경함
if(passwordCheck && user.authTypeCd === 'NORMAL') {
//passwordCheck와 password가 같아야함
if(passwordCheck !== password) {
ctx.status = 401;
ctx.body = {
error : '비밀번호가 일치하지 않습니다.',
};
return;
}
await user.setPassword(password);
await user.save();
}
await profile.updateProfileInfo({
userNm,
birth,
contact,
});
await profile.save();
ctx.status = 200;
};
......@@ -76,7 +132,12 @@ exports.getMyDoctorList = async ctx => {
useYn : 'Y',
});
return doctorInfo.info;
if (doctorInfo) {
return ({
...doctorInfo.info,
doctorId : doctorInfo.doctorId,
})
}
}));
ctx.status = 200;
......@@ -112,7 +173,10 @@ exports.viewAllDoctorRegisterReq = async ctx => {
})
const doctorReqList = await Promise.all(patientInfoList.map(async patientInfo => {
const doctor = await DoctorInfo.findOne({ doctorId : patientInfo.doctorId });
const doctor = await DoctorInfo.findOne({
doctorId : patientInfo.doctorId,
useYn : 'Y',
});
return {
patientId : patientInfo.patientId,
doctorId : patientInfo.doctorId,
......@@ -156,9 +220,9 @@ exports.acceptDoctorRegister = async ctx => {
return;
}
patientInfo.updateInfo('환자 등록 요청 수락');
patientInfo.setUseYn('Y');
patientInfo.save();
await patientInfo.updateInfo('환자 등록 요청 수락');
await patientInfo.setUseYn('Y');
await patientInfo.save();
ctx.status = 200;
......
......@@ -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;
......
......@@ -8,6 +8,7 @@ const jwtMiddleware = async (ctx, next) => {
}
try {
// eslint-disable-next-line no-undef
const decoded = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = {
_id : decoded._id,
......
......@@ -5,6 +5,7 @@ const Schema = mongoose.Schema;
const BottleSchema = new Schema ({
bottleId : { type : Number, required : true, unique : true },
hubId : { type : Number, required : true, ref : 'Hub', },
bottleNm : { type : String, required : true, maxlength : 10, },
});
BottleSchema.statics.findByBottleId = function(bottleId) {
......@@ -23,5 +24,9 @@ BottleSchema.methods.getHubId = function() {
return this.hubId;
};
BottleSchema.methods.setBottleNm = function(bottleNm) {
this.bottleNm = bottleNm;
}
module.exports = mongoose.model('Bottle', BottleSchema);
\ No newline at end of file
......
......@@ -16,16 +16,26 @@ const BottleMedicineSchema = new Schema({
doctorId : {
type : String,
ref : 'User',
required : true,
lowercase : true,
},
dosage : {
dailyDosage : {
type : Number,
default : 1,
},
totalDosage : {
type : Number,
default : 1,
},
eachWeight : {
type : Number,
default : 0,
},
totalWeight : {
type : Number,
required : true,
default : 0,
},
regDtm : {
type : Date,
required : true,
default : Date.now,
},
useYn : {
......@@ -39,6 +49,14 @@ BottleMedicineSchema.methods.setDoctorId = function(doctorId) {
this.doctorId = doctorId;
};
BottleMedicineSchema.methods.setEachWeight = function(eachWeight) {
this.eachWeight = eachWeight;
};
BottleMedicineSchema.methods.setTotalWeight = function(totalWeight) {
this.totalWeight = totalWeight;
};
BottleMedicineSchema.methods.setUseYn = function(useYn) {
this.useYn = useYn;
};
......
......@@ -3,9 +3,10 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const DoctorInfoSchema = new Schema({
doctorId : { type : String, required : true, },
doctorId : { type : String, required : true, lowercase : true, },
info : {
doctorLicense : { type : String, required : true, },
validateDoctorLicense : { type : String, default : null },
hospitalNm : { type : String, default : null, },
hospitalAddr : { type : String, default : null, },
contact : { type : String, required : true, },
......@@ -23,5 +24,9 @@ DoctorInfoSchema.methods.setUseYn = function(useYn) {
this.useYn = useYn;
};
DoctorInfoSchema.methods.setValidateDoctorLicense = function(validateDoctorLicense) {
this.info.validateDoctorLicense = validateDoctorLicense;
};
module.exports = mongoose.model('DoctorInfo', DoctorInfoSchema);
......
......@@ -10,7 +10,7 @@ const FeedbackSchema = new Schema({
required : true,
ref : 'BottleMedicine',
},
doctorId : { type : String, required : true, ref : 'User', },
doctorId : { type : String, required : true, ref : 'User', lowercase : true, },
feedback : { type : String, required : true, },
});
......
......@@ -5,7 +5,8 @@ const Schema = mongoose.Schema;
const HubSchema = new Schema ({
hubId : { type : Number, required : true, unique : true },
hosting : { type : Object, default : null },
userId : { type : String, default : null, ref : 'User' },
userId : { type : String, default : null, ref : 'User', lowercase : true, },
hubNm : { type : String, required : true, maxlength : 10, },
});
HubSchema.statics.findByHubId = function(hubId) {
......
const mongoose = require('mongoose');
const moment = require('moment');
require('moment-timezone');
const Schema = mongoose.Schema;
const PatientInfoSchema = new Schema({
patientId : { type : String, required : true, ref : 'User', },
doctorId : { type : String, required : true, ref : 'User', },
patientId : { type : String, required : true, ref : 'User', lowercase : true, },
doctorId : { type : String, required : true, ref : 'User', lowercase : true, },
info : { type : String, required : true, },
useYn : { type : String, required : true, default : 'W', },
});
......@@ -35,9 +36,9 @@ PatientInfoSchema.methods.setUseYn = function(useYn) {
};
PatientInfoSchema.methods.updateInfo = function(info) {
const date = moment(new Date()).format('YYYY-MM-DD hh:mm');
const date = moment.tz('Asia/Seoul').format('YYYY-MM-DD HH:mm');
if(this.info.length)
this.info = this.info.concat('\n\n', `${date} ${info}`);
this.info = this.info.concat('\n\n', `${date} -> ${info}`);
else
this.info = `${date}${info}`;
};
......
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PrescribeInfoSchema = new Schema({
doctorId : { type : String, require : true, },
patientId : { type : String, require : true, },
medicineId : { type : Number, require : true, },
dailyDosage : { type : Number, require : true, },
totalDosage : { type : Number, require : true, },
qrCodeUrl : { type : String, require : true, },
});
module.exports = mongoose.model('PrescribeInfo', PrescribeInfoSchema);
\ No newline at end of file
......@@ -3,10 +3,11 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ProfileSchema = new Schema({
userId : { type : String, required : true, ref : 'User', },
userId : { type : String, required : true, ref : 'User', lowercase : true, },
userNm : { type : String, required : true, },
userAge : { type : Number, required : true, },
birth : { type : String, required : true, },
contact : { type : String, required : true, },
useYn : { type : String, default : 'Y', },
deviceToken : { type : String, default : null, },
});
......@@ -14,12 +15,14 @@ ProfileSchema.statics.findByUserId = function(userId) {
return this.findOne({ userId });
};
ProfileSchema.methods.updateUserContact = function(contact) {
this.contact = contact;
ProfileSchema.methods.setUseYn = function(useYn) {
this.useYn = useYn;
};
ProfileSchema.methods.updateUserAge = function() {
this.userAge = this.userAge + 1;
ProfileSchema.methods.updateProfileInfo = function({ userNm, birth, contact }) {
if(userNm) { this.userNm = userNm }
if(birth) { this.birth = birth }
if(contact) { this.contact = contact }
};
ProfileSchema.methods.updateDeviceToken = function(deviceToken) {
......
......@@ -5,7 +5,6 @@ const Schema = mongoose.Schema;
const TakeMedicineHistorySchema = new Schema ({
takeDate : {
type : Date,
required : true,
default : Date.now,
},
bmId : {
......@@ -13,9 +12,10 @@ const TakeMedicineHistorySchema = new Schema ({
ref : 'BottleMedicine',
required : true,
},
temperature : { type : Number, default : 0 },
humidity : { type : Number, default : 0 },
balance : { type : Number, default : 0 },
temperature : { type : Number, default : 0, },
humidity : { type : Number, default : 0, },
dosage : { type : Number, default : 0, },
balance : { type : Number, default : 0, },
});
......
......@@ -5,9 +5,10 @@ const jwt = require('jsonwebtoken');
const Schema = mongoose.Schema;
const UserSchema = new Schema ({
userId : { type: String, required : true, unique : true, lowercase : true, },
hashedPassword : { type : String, required : true },
userTypeCd : { type : String, required : true, default : 'NORMAL' },
userId : { type: String, required : true, unique : true, lowercase : true, trim : true },
hashedPassword : { type : String, required : true, },
userTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
authTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
useYn : { type : String, default : 'W', required : true, },
});
......
......@@ -3,11 +3,136 @@
* 21/09/14
* Author : 박권수
* 배치 시스템
* 1) 매년 지나면 프로필의 Age를 +1
* 2) Dosage에 따라, Push Notification 발송
* 1) Dosage에 따라, Push Notification 발송
*/
const cron = require('node-cron');
const fs = require('fs');
const Profile = require('../models/profile');
const User = require('../models/user');
const Hub = require('../models/hub');
const Bottle = require('../models/bottle');
const BottleMedicine = require('../models/bottleMedicine');
const Medicine = require('../models/medicine');
const updateMedicineInfo = require('../lib/UpdatingMedicineInfo');
const { sendPushMessage } = require('./FCM');
//매월 1일 0시 0분에 약 정보 업데이트
exports.updateMedicineData = async () => {
cron.schedule('0 0 0 1 * *', () => {
updateMedicineInfo.updateMedicineInfo();
}, {
timezone : 'Asia/Tokyo',
});
};
//매주 일요일마다 불필요한 qrcode 제거
exports.removeQrCode = () => {
cron.schedule('0 0 0 * * 0', () => {
// eslint-disable-next-line no-undef
const qrDir = process.env.QR_DIR;
fs.rm(qrDir, { recursive : true, force : true, }, () => {
fs.mkdir(qrDir, (err) => { if(err) console.log(err) });
});
}, {
timezone : 'Asia/Tokyo',
});
};
//dosage에 따라, Push Notification을 발송한다.
//아침 8시, 점심 12시, 저녁 6시에 한번씩 발송
exports.pushNotifyByDosage = async() => {
//매일 아침 8시 : 복용량 상관 없이 보냄
cron.schedule('0 0 8 * * *', async () => {
const bottleMedicineList = await BottleMedicine.find({ useYn : 'Y', dosage : { $gte : 1 } });
bottleMedicineList.forEach(async bottleMedicine => {
const bottle = await Bottle.findOne({ bottleId : bottleMedicine.bottleId });
const hub = await Hub.findOne({ hubId : bottle.hubId });
const user = await User.findOne({ userId : hub.userId, useYn : 'Y' });
if(user) {
const profile = await Profile.findOne({ userId : user.userId });
const { deviceToken } = profile;
if(deviceToken) {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
pushNotify({
deviceToken,
title : '약 복용 시간입니다',
body : medicine.name + '을 복용하셔야 합니다.',
});
}
}
});
}, {
timezone : 'Asia/Tokyo',
});
//매일 점심 12시 : 복용량이 3인 환자들만
cron.schedule('0 0 12 * * *', async () => {
const bottleMedicineList = await BottleMedicine.find({ useYn : 'Y', dosage : { $gte : 3 } });
bottleMedicineList.forEach(async bottleMedicine => {
const bottle = await Bottle.findOne({ bottleId : bottleMedicine.bottleId });
const hub = await Hub.findOne({ hubId : bottle.hubId });
const user = await User.findOne({ userId : hub.userId, useYn : 'Y' });
if(user) {
const profile = await Profile.findOne({ userId : user.userId });
const { deviceToken } = profile;
if(deviceToken) {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
pushNotify({
deviceToken,
title : '약 복용 시간입니다',
body : medicine.name + '을 복용하셔야 합니다.',
});
}
}
});
}, {
timezone : 'Asia/Tokyo',
});
//매일 저녁 6시
cron.schedule('0 0 18 * * *', async () => {
const bottleMedicineList = await BottleMedicine.find({ useYn : 'Y', dosage : { $gte : 2 } });
bottleMedicineList.forEach(async bottleMedicine => {
const bottle = await Bottle.findOne({ bottleId : bottleMedicine.bottleId });
const hub = await Hub.findOne({ hubId : bottle.hubId });
const user = await User.findOne({ userId : hub.userId, useYn : 'Y' });
if(user) {
const profile = await Profile.findOne({ userId : user.userId });
const { deviceToken } = profile;
if(deviceToken) {
const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
pushNotify({
deviceToken,
title : '약 복용 시간입니다',
body : medicine.name + '을 복용하셔야 합니다.',
});
}
}
});
}, {
timezone : 'Asia/Tokyo',
});
};
const pushNotify = ({ deviceToken, title, body }) => {
//toDo : deviceToken을 받아서 push notification을 발송하는 함수
sendPushMessage({
deviceToken,
title,
body,
});
};
......
const Bottle = require('../models/bottle');
const BottleMedicine = require('../models/bottleMedicine');
const TakeMedicineHist = require('../models/takeMedicineHistory');
//message subscribe 후 message를 가공한 이후 해당 데이터를 보낼 topic과 message를 리턴하는 함수
exports.dataPublish = async (topic, message) => {
if(message.includes('weight')) {
//무게 갱신
const result = await updateBottleMedicineWeight(topic, message);
return result;
} else {
//client가 subscribe를 하면 메시지를 보낸 약병의 topic과 message를 가공 및 보낸 약병의 bottleId를 가져옴
const data = await factoring(topic, message);
//가공된 데이터를 bottleId의 약병에 업데이트
......@@ -13,68 +20,59 @@ exports.dataPublish = async (topic, message) => {
return result;
}
};
//Hub topic : bottle/bottleId
//Hub로부터 받은 message : 개폐여부/온도/습도/초음파센서
//Hub로부터 받은 message : 개폐여부/온도/습도/무게센서
const factoring = async (topic, message) => {
const bottleId = parseInt(topic.split('/')[1]);
const data = message.split('/');
let [isOpen, temperature, humidity, balance] = data;
if(isOpen === '0')
balance = await balanceFactoring(balance);
else balance = '-1';
const [isOpen, temperature, totalWeight, humidity] = data;
return {
bottleId,
isOpen,
openDate : new Date(),
temperature,
humidity,
balance
totalWeight,
};
}
const balanceFactoring = (balance) => {
const max = 10; //Digital Lead Sensor Maximum Value
const slicingBalance = max / 5;
if(parseInt(balance) < slicingBalance || parseInt(balance) > max * 2)
return '80';
else if(parseInt(balance) < slicingBalance * 2)
return '60';
else if(parseInt(balance) < slicingBalance * 3)
return '40';
else if(parseInt(balance) < slicingBalance * 4)
return '20';
else return '0';
}
//bottleId가 포함된 data를 받아서 해당 약병의 data를 업데이트한다.
const bottleInfoUpdate = async(data) => {
let { bottleId, isOpen, openDate, temperature, humidity, balance } = data;
let { bottleId, isOpen, temperature, humidity, totalWeight } = data;
if(!parseInt(isOpen)) {
bottleId = parseInt(bottleId);
isOpen = parseInt(isOpen);
temperature = parseFloat(temperature);
humidity = parseFloat(humidity);
balance = parseInt(balance);
totalWeight = parseFloat(totalWeight);
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
if(bottleMedicine) {
if(isOpen) {
const lastTotalWeight = parseFloat(bottleMedicine.totalWeight);
const { eachWeight } = bottleMedicine;
const dosage = Math.round((lastTotalWeight - totalWeight) / parseFloat(eachWeight));
if(dosage > 0) {
const balance = Math.round(totalWeight / parseFloat(eachWeight));
const takeMedicineHist = new TakeMedicineHist({
takeDate : openDate,
bmId : bottleMedicine._id,
temperature,
humidity,
dosage,
balance,
});
takeMedicineHist.save();
await takeMedicineHist.save();
}
await bottleMedicine.setTotalWeight(totalWeight);
await bottleMedicine.save();
}
}
}
......@@ -84,11 +82,13 @@ const transPublishingTopicAndMessage = async(bottleId) => {
const topic = 'bottle/' + bottleId + '/stb';
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
const takeMedicineHist = await TakeMedicineHist.find({
const takeMedicineHistList = await TakeMedicineHist.find({
bmId : bottleMedicine._id
}).sort((a, b) => a.takeDate < b.takeDate)[0];
}).sort({ takeDate : 'desc' }).limit(1);
const message = 'res/' + await transDate(takeMedicineHist.takeDate) + '/' + bottleMedicine.dosage;
const message = takeMedicineHistList && takeMedicineHistList[0] ?
'res/' + await transDate(takeMedicineHistList[0].takeDate) + '/' + takeMedicineHistList[0].dosage :
'res/' + await transDate(new Date()) + '/' + 0;
return {
topic,
......@@ -100,4 +100,23 @@ const transPublishingTopicAndMessage = async(bottleId) => {
const transDate = (date) => {
return (date.getMonth() + 1 < 10 ? '0' + String(date.getMonth() + 1) : String(date.getMonth() + 1))
+ (date.getDate() < 10 ? '0' + String(date.getDate()) : String(date.getDate()));
}
\ No newline at end of file
};
//무게센서를 이용하여 데이터값을 갱신하는 함수
const updateBottleMedicineWeight = async (topic, message) => {
const bottleId = parseInt(topic.split('/')[1]);
//message = weight/무게
const totalWeight = parseFloat(message.split('/')[1]);
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
const totalDosage = parseInt(bottleMedicine.totalDosage);
//받은 값으로 총 무게를 설정한 이후, 총 무게 / 총 복용량으로 개별 무게를 설정한다.
await bottleMedicine.setTotalWeight(totalWeight);
await bottleMedicine.setEachWeight(totalWeight / totalDosage);
await bottleMedicine.save();
return null;
};
\ No newline at end of file
......
// const fcm = require('firebase-admin');
const axios = require('axios');
// exports.initializeFCM = () => {
// fcm.initializeApp({
// credential: fcm.credential.applicationDefault(),
// });
// };
exports.sendPushMessage = async ({ deviceToken, title, body }) => {
// const notifyMessage = {
// notification : {
// title,
// body,
// },
// token : deviceToken,
// };
// fcm.messaging().send(notifyMessage).then(res => {
// console.log(res);
// }).catch(e => {
// console.log(e);
// });
const message = {
to : deviceToken,
notification : {
title,
body,
priority : 'high',
},
data : null,
}
const url = 'https://fcm.googleapis.com/fcm/send';
const result = await axios.post(url, message, {
headers : {
'Content-Type' : 'application/json',
// eslint-disable-next-line no-undef
'Authorization' : `key=${process.env.FCM_KEY}`,
},
});
console.log(result.data);
};
\ No newline at end of file
const { Storage } = require('@google-cloud/storage');
const fs = require('fs');
const storage = new Storage();
const GoogleStorageUrl = 'https://storage.googleapis.com/';
//의사 아이디, 업로드할 파일명, 업로드할 파일 경로를 인자로 받아, File을 GCS에 업로드 후 GCS 주소를 반환
exports.uploadDoctorLicense = async ({ userId, fileName, filePath }) => {
const destination = userId + '_' + fileName;
try {
const result = await storage.bucket('doctor-info').upload(filePath, {
destination,
});
const doctorLicenseUrl = GoogleStorageUrl + `${result[0].bucket.id}/${result[0].name}`;
return doctorLicenseUrl;
} catch(e) {
console.log(e);
return null;
}
};
//의사 정보를 인자로 받아 해당 Doctor License의 Signed URL을 반환
exports.viewDoctorLicense = async ({ doctorInfo }) => {
try {
const fileName = doctorInfo.info.doctorLicense.split('/').pop();
const file = storage.bucket('doctor-info').file(fileName);
const option = {
version : 'v4',
expires : Date.now() + 1000 * 60 * 15,
action : 'read',
};
const [signedUrl] = file ? await file.getSignedUrl(option) : [null];
return signedUrl;
} catch(e) {
console.log(e);
return null;
}
};
//의사 ID, 약 ID, 복용량을 인자로 받아, QR Code를 생성
exports.uploadQrCode = async ({ directory, qrCodeFileName }) => {
const destination = qrCodeFileName;
try {
//파일을 GCS에 업로드
const result = await storage.bucket('prescribe-medicine-qrcode').upload(directory + '/' + qrCodeFileName, {
destination
});
//업로드 후 파일 삭제
fs.rm(directory + '/' + qrCodeFileName, () => {});
const qrCodeUrl = GoogleStorageUrl + `${result[0].bucket.id}/${result[0].name}`;
return qrCodeUrl;
} catch(e) {
console.log(e);
return null;
}
};
//생성된 QR코드의 signedUrl을 가져옴
exports.getQrCodeUrl = async ({ qrCodeFileName }) => {
try {
const fileName = qrCodeFileName;
const file = storage.bucket('prescribe-medicine-qrcode').file(fileName);
const option = {
version : 'v4',
expires : Date.now() + 1000 * 60 * 15,
action : 'read',
};
const [signedUrl] = file ? await file.getSignedUrl(option) : [null];
return signedUrl;
} catch(e) {
console.log(e);
return null;
}
};
\ No newline at end of file
const mqtt = require('mqtt')
const clientList = []
const mqtt = require('mqtt');
const clientList = [];
exports.mqttOn = async (hosting, foo) => {
const filterIndex = clientList.findIndex(client => {
......@@ -9,27 +9,29 @@ exports.mqttOn = async (hosting, foo) => {
})
if(filterIndex === -1) {
const client = mqtt.connect(hosting)
clientList.push(client)
const client = mqtt.connect(hosting);
clientList.push(client);
client.on('connect', () => {
console.log('Hub connected: ', client.connected)
})
});
client.on('message', async (topic, message) => {
const result = await foo(topic, message.toString())
const result = await foo(topic, message.toString());
console.log('\x1b[1;32msubscribe : topic', topic, 'message : ', message.toString(), '\x1b[0m')
this.mqttPublishMessage(client, result)
})
if(result) this.mqttPublishMessage(client, result);
});
return client
return client;
}
return clientList[filterIndex]
return clientList[filterIndex];
}
exports.mqttSubscribe = (client, topic) => {
client.subscribe(topic)
client.subscribe(topic, () => {
console.log('suscribe', topic);
});
}
exports.mqttPublishMessage = (client, { topic, message }) => {
......@@ -49,10 +51,10 @@ exports.mqttOff = (hosting) => {
return (client.options.clientId === hosting.clientId
&& client.options.host === hosting.host
&& client.options.port === hosting.port)
})
});
if(filterIndex !== -1) {
clientList[filterIndex].end()
clientList.splice(filterIndex, 1)
clientList[filterIndex].end();
clientList.splice(filterIndex, 1);
}
}
\ No newline at end of file
......
const Mqtt = require('../lib/MqttModule');
const DataProcess = require('../lib/DataProcess');
const Mqtt = require('./MqttModule');
const DataProcess = require('./DataProcess');
const Hub = require('../models/hub');
const Bottle = require('../models/bottle');
......
const QrCode = require('qrcode');
const moment = require('moment');
exports.generateQrCode_prescribe = async ({ medicine, dailyDosage, totalDosage, patientId, doctorId }) => {
// eslint-disable-next-line no-undef
const directory = process.env.QR_DIR;
const now = moment().format('YYYY-MM-DD_HH:mm');
const qrCodeFileName = `${now}_${doctorId}_${patientId}_${medicine.medicineId}_${dailyDosage}_${totalDosage}.png`;
try {
await QrCode.toFile(
directory + '/' + qrCodeFileName,
`${medicine.medicineId}/${dailyDosage}/${totalDosage}/${doctorId}/${patientId}/${medicine.name}`,
{
color : {
dark : '#337DFF',
light : '#FFF'
},
}
);
return {
directory,
qrCodeFileName,
};
} catch(e) {
console.log(e);
return null;
}
};
\ No newline at end of file
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
......@@ -14,6 +14,7 @@
"highcharts": "^9.2.0",
"highcharts-react-official": "^3.0.0",
"moment": "^2.29.1",
"qrcode": "^1.4.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
......@@ -52,6 +53,7 @@
]
},
"devDependencies": {
"@types/qrcode": "^1.4.1",
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.12",
"@types/validator": "^13.6.3",
......
import { client } from "./client";
export default {
register : (Data : any) => {
register : (Data : FormData) => {
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);
},
......
......@@ -44,8 +44,8 @@ export default {
},
});
},
searchPatientById : (token : RecoilState<any>, patientId : string) => {
return client.get(`/doctor/patient/search/${patientId}`, {
searchPatientByContact : (token : RecoilState<any>, contact : string) => {
return client.get(`/doctor/patient/search/${contact}`, {
headers : {
Authorization : token,
},
......@@ -65,4 +65,11 @@ export default {
},
});
},
prescribeMedicine : (token : RecoilState<any>, Data : any) => {
return client.post('/doctor/prescribe', Data, {
headers : {
Authorization : token,
},
});
},
};
\ No newline at end of file
......
......@@ -9,6 +9,13 @@ export default {
},
});
},
getDoctorSecReqList : (token : RecoilState<any>) => {
return client.get('/manage/doctor/secession', {
headers : {
Authorization : token,
},
});
},
getDoctorRegReqDetail : (token : RecoilState<any>, doctorId : string) => {
return client.get(`/manage/doctor/${doctorId}`, {
headers : {
......@@ -17,14 +24,21 @@ export default {
});
},
acceptDoctorRegReq : (token : RecoilState<any>, Data : any) => {
return client.post('/manage/doctor/accept', Data, {
return client.patch('/manage/doctor/accept', Data, {
headers : {
Authorization : token,
},
});
},
rejectDoctorRegReq : (token : RecoilState<any>, Data : any) => {
return client.post('/manage/doctor/reject', Data, {
return client.patch('/manage/doctor/reject', Data, {
headers : {
Authorization : token,
},
});
},
acceptDoctorSecReq : (token : RecoilState<any>, Data : any) => {
return client.patch('/manage/doctor/secession', Data, {
headers : {
Authorization : token,
},
......
import styled from 'styled-components';
export const Container = styled.div `
width : 100%;
position : relative;
margin : 5px 0;
padding : 20px 5px;
border-top : 1px solid #ddd;
display : flex;
flex-direction : column;
`;
export const TermsWrapper = styled.div `
width : 100%:
display : flex;
flex-direction : row;
padding : 0 0 20px 0;
margin : 0 0 10px 0;
background-color : transparent;
border : none;
border-bottom : 1px solid #ddd;
`;
export const EachTerms = styled.button `
color : #000;
background-color : transparent;
margin : 0 10px 0 0;
padding : 0 0 5px 0;
cursor : pointer;
font-size : 13px;
font-weight : 400;
border : none;
border-bottom : 1px solid;
transition : .25s all;
&:hover {
color : #337DFF;
opacity : .5;
}
`;
export const InfoWrapper = styled.div `
display : flex;
flex-direction : row;
border : none;
background-color : transparent;
`;
export const LicenseWrapper = styled.div `
flex : 1;
display : flex;
flex-direction : column;
border : none;
background-color : transparent;
justify-content : center;
align-items : flex-start;
`;
export const LicenseExplain = styled.div `
color : #a0a0a0;
font-size : 14px;
font-weight : 400;
padding : 0 5px;
border : none;
`;
export const LicenseImg = styled.img `
height : 60px;
width : 150px;
`;
export const ServiceInfoWrapper = styled.div `
flex : 3;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
border : none;
background-color : transparent;
`;
export const ServiceInfoEach = styled.div `
color : #d0d0d0;
font-size : 13px;
font-weight : 400
`;
\ No newline at end of file
import React from 'react';
import { RouteComponentProps } from 'react-router';
import * as Alert from '../../util/alertMessage';
import * as styled from './FooterStyled';
const ApiLicense = '/static/img/apiLicense.png';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface FooterProps extends RouteComponentProps {}
const Footer = (props : FooterProps) => {
const onGoTerm = () => {
Alert.onWarning('준비중입니다.', () => null);
};
const onGoPrivateLicense = () => {
Alert.onWarning('준비중입니다.', () => null);
};
const onGoServiceCenter = () => {
Alert.onWarning('준비중입니다.', () => null);
};
return (
<styled.Container>
<styled.TermsWrapper>
<styled.EachTerms
onClick = {onGoTerm}
>
이용약관
</styled.EachTerms>
<styled.EachTerms
onClick = {onGoPrivateLicense}
>
개인정보처리방침
</styled.EachTerms>
<styled.EachTerms
onClick = {onGoServiceCenter}
>
고객센터
</styled.EachTerms>
</styled.TermsWrapper>
<styled.InfoWrapper>
<styled.LicenseWrapper>
<styled.LicenseExplain>저작권</styled.LicenseExplain>
<styled.LicenseImg src = {ApiLicense}/>
</styled.LicenseWrapper>
<styled.ServiceInfoWrapper>
<styled.ServiceInfoEach>서비스명 : Smart Medicine Box (SMB)</styled.ServiceInfoEach>
<styled.ServiceInfoEach>서비스제공 : IoT 약병 제작회</styled.ServiceInfoEach>
<styled.ServiceInfoEach>담당자 : 박권수</styled.ServiceInfoEach>
<styled.ServiceInfoEach>주소 : OOO도 OOO시 OOO구 OOO OOO OOO</styled.ServiceInfoEach>
<styled.ServiceInfoEach>연락처 : 010 - 0000 - 0000</styled.ServiceInfoEach>
</styled.ServiceInfoWrapper>
<styled.LicenseWrapper/>
</styled.InfoWrapper>
</styled.Container>
)
};
export default Footer;
\ No newline at end of file
......@@ -10,7 +10,6 @@ import * as styled from './HeaderStyled';
import { authApi } from '../../api';
const headerImg = '/static/img/pharmacy.png';
const backButtonWhite = '/static/img/backButtonWhite.png';
const backButtonBlue = '/static/img/backButtonBlue.png';
const logout = '/static/img/logout.png';
......
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 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;
`;
\ No newline at end of file
import React from 'react';
import * as styled from './ModalStyled';
const closeButton = '/static/img/close.png';
interface ModalProps {
children : JSX.Element,
onModalClose : () => void;
}
const ModalContainer = (props : ModalProps) => {
return (
<styled.ModalContainer>
<styled.ModalClsButtonWrapper>
<styled.ModalClsButton
onClick = {props.onModalClose}
>
<styled.ModalClsButtonImg src = {closeButton}/>
<styled.ModalClsButtonText>닫기</styled.ModalClsButtonText>
</styled.ModalClsButton>
</styled.ModalClsButtonWrapper>
<styled.ModalContentWrapper>
<styled.ModalContent>
{props.children}
</styled.ModalContent>
</styled.ModalContentWrapper>
<styled.ModalClsButtonWrapper />
</styled.ModalContainer>
);
};
export default ModalContainer;
\ No newline at end of file
import moment from 'moment';
export const make = (chartData : any[], numberOfRow : number) => {
export const make = (takeMedicineHist : any[], numberOfRow : number) => {
const now = new Date();
const result : any = {};
new Array(numberOfRow).fill(null).forEach((item : any, index : number) => {
new Array(numberOfRow).fill(null).forEach(() => {
const key = moment(now).format('MM/DD');
result[key] = 0;
now.setDate(now.getDate() - 1);
})
});
chartData.forEach((data : any) => {
takeMedicineHist.forEach((data : any) => {
const key : string = moment(data.takeDate).format('MM/DD');
result[key] = result[key] + 1;
!isNaN(result[key]) ? result[key] = result[key] + data.dosage : null;
});
const categories : any = [];
......@@ -28,5 +28,5 @@ export const make = (chartData : any[], numberOfRow : number) => {
return {
categories,
data,
}
};
};
\ No newline at end of file
......
......@@ -9,6 +9,12 @@ export const token = atom({
effects_UNSTABLE : [persistAtom],
});
export const userId = atom({
key : 'userId',
default : null,
effects_UNSTABLE : [persistAtom],
});
export const userTypeCd = atom({
key : 'userTypeCd',
default : 'NORMAL',
......
......@@ -3,6 +3,7 @@ import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
import Error from '../components/error';
import Loading from '../components/Loading';
import Footer from '../components/Footer';
import { LoginContainer } from "./login";
import { RegisterContainer } from './register';
import { MainContainer } from "./main";
......
......@@ -9,6 +9,7 @@ import * as Alert from '../../util/alertMessage';
import moment from 'moment';
import Header from '../../components/Header';
import Footer from '../../components/Footer';
import BottleInfoPresenter from './BottleInfoPresenter';
import { doctorApi } from '../../api';
......@@ -35,6 +36,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
takeMedicineHist : [],
});
//차트에 표시되는 행의 개수
const numberOfChartItem = 7;
const [chartOption, setChartOption] = useState<any>({
chart : {
......@@ -51,19 +53,22 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
categories : [],
},
series : [{
name : '약 복용 횟수',
name : '약 복용 회분',
color : '#337DFF',
data : [],
}],
});
const [takeMedicineHist, setTakeMedicineHist] = useState<any[]>([]);
const [feedback, setFeedback] = useState<string>('');
const [fdbType, setFdbType] = useState<string>('RECOMMEND');
const [medicineInfoModal, setMedicineInfoModal] = useState<boolean>(false);
const [modalType, setModalType] = useState<string>('hist'); //hist , info
const fetchData = async () => {
setModalType('hist');
setFeedback('');
setFdbType('RECOMMEND');
setMedicineInfoModal(false);
......@@ -71,6 +76,12 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
try {
const result = await doctorApi.getPatientBottleDetail(token, bottleId);
if (result.statusText === 'OK') {
setTakeMedicineHist(result.data.takeMedicineHist.map((takeMedicine : any) => {
return ({
...takeMedicine,
takeDate : moment(takeMedicine.takeDate).format('YYYY년 MM월 DD일 hh시 mm분'),
});
}));
const { categories, data } = makeChart.make(result.data.takeMedicineHist, numberOfChartItem);
setBottleInfo({
...result.data,
......@@ -97,9 +108,8 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
Alert.onError('접근 권한이 없습니다.', () => props.history.push('/'));
}
} catch(e : any) {
Alert.onError(e.response.data.error, () => props.history.push('/'));
console.log(e);
Alert.onError('알 수 없는 에러가 발생했습니다.', () => props.history.push('/'));
}
};
......@@ -123,7 +133,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
Alert.onError('피드백 등록에 실패했습니다.', () => null);
}
} catch(e : any) {
Alert.onError(e.response.data.error, () => fetchData());
Alert.onError('알 수 없는 에러가 발생했습니다.', () => fetchData());
}
} else {
Alert.onError('피드백 내용을 입력하세요.', () => null);
......@@ -134,6 +144,14 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
};
const onViewTakeHist = () => {
if(modalType === 'info') setModalType('hist');
};
const onViewMedicineInfo = () => {
if(modalType === 'hist') setModalType('info');
};
useEffect(() => {
if(userTypeCd !== 'DOCTOR') {
......@@ -148,8 +166,12 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
<BottleInfoPresenter
bottleInfo = {bottleInfo}
chartOption = {chartOption}
takeMedicineHist = {takeMedicineHist}
medicineInfoModal = {medicineInfoModal}
modalType = {modalType}
onViewTakeHist = {onViewTakeHist}
onViewMedicineInfo = {onViewMedicineInfo}
setMedicineInfoModal = {setMedicineInfoModal}
feedback = {feedback}
......@@ -158,6 +180,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
setFdbType = {setFdbType}
onSubmitFeedback = {onSubmitFeedback}
/>
<Footer {...props}/>
</>
);
};
......
......@@ -2,10 +2,10 @@ import React from 'react';
import HighCharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import Modal from '../../components/Modal';
import * as styled from './BottleInfoStyled';
const plus = '/static/img/plus.png';
const closeButton = '/static/img/close.png';
interface BottleInfoProps {
......@@ -16,8 +16,12 @@ interface BottleInfoProps {
takeMedicineHist : any[];
};
chartOption : any;
takeMedicineHist : any[];
medicineInfoModal : boolean;
modalType : string;
onViewTakeHist : () => void;
onViewMedicineInfo : () => void;
setMedicineInfoModal : (arg0 : boolean) => void;
feedback : string;
......@@ -32,17 +36,61 @@ const BottleInfoPresenter = (props : BottleInfoProps) => {
<styled.Container>
{
props.medicineInfoModal ?
<styled.ModalContainer>
<styled.ModalClsButtonWrapper>
<styled.ModalClsButton
onClick = {() => props.setMedicineInfoModal(false)}
<Modal onModalClose = {() => props.setMedicineInfoModal(false)}>
<>
<styled.ModalTypeButtonWrapper>
<styled.ModalTypeButton
isSelect = {props.modalType === 'hist'}
onClick = {props.onViewTakeHist}
>
복용 기록
</styled.ModalTypeButton>
<styled.ModalTypeButton
isSelect = {props.modalType === 'info'}
onClick = {props.onViewMedicineInfo}
>
약 정보
</styled.ModalTypeButton>
</styled.ModalTypeButtonWrapper>
{
props.modalType === 'hist' ?
<>
<styled.MedicineNameWrapper>
<styled.MedicineName>{`복용 기록`}</styled.MedicineName>
<styled.MedicineName style = {{color : '#343434', fontSize : 15, marginTop : 4,}}>{`전체 : ${props.takeMedicineHist.length}건`}</styled.MedicineName>
</styled.MedicineNameWrapper>
<styled.MedicineInfoWrapper>
{
props.takeMedicineHist.map((hist : any) => {
return (
<styled.HistWrapper
key = {hist._id}
>
<styled.ModalClsButtonImg src = {closeButton}/>
<styled.ModalClsButtonText>닫기</styled.ModalClsButtonText>
</styled.ModalClsButton>
</styled.ModalClsButtonWrapper>
<styled.ModalContentWrapper>
<styled.ModalContent>
<styled.HistDtmWrapper>
<styled.HistInfoEachWrapper style = {{fontSize : 11}}>복용 날짜</styled.HistInfoEachWrapper>
<styled.HistDtm>{hist.takeDate}</styled.HistDtm>
</styled.HistDtmWrapper>
<styled.HistInfoWrapper>
<styled.HistInfoEachWrapper>
복용량
<styled.HistInfoEach>{hist.dosage}회분</styled.HistInfoEach>
</styled.HistInfoEachWrapper>
<styled.HistInfoEachWrapper>
약병 내 온도
<styled.HistInfoEach>{hist.temperature}℃</styled.HistInfoEach>
</styled.HistInfoEachWrapper>
<styled.HistInfoEachWrapper>
약병 내 습도
<styled.HistInfoEach>{hist.humidity}%</styled.HistInfoEach>
</styled.HistInfoEachWrapper>
</styled.HistInfoWrapper>
</styled.HistWrapper>
)
})
}
</styled.MedicineInfoWrapper>
</> :
<>
<styled.MedicineNameWrapper>
<styled.MedicineName>{props.bottleInfo.medicine.name}</styled.MedicineName>
<styled.MedicineName style = {{color : '#343434', fontSize : 15, marginTop : 4,}}>{props.bottleInfo.medicine.company}</styled.MedicineName>
......@@ -65,9 +113,10 @@ const BottleInfoPresenter = (props : BottleInfoProps) => {
<styled.MedicineEachInfo style = {{color : '#9B0000'}}>{props.bottleInfo.medicine.antiEffect}</styled.MedicineEachInfo>
</styled.MedicineEachInfoWrapper>
</styled.MedicineInfoWrapper>
</styled.ModalContent>
</styled.ModalContentWrapper>
</styled.ModalContainer> : null
</>
}
</>
</Modal> : null
}
<styled.ChartAndFeedbackWrapper>
<styled.ChartWrapper>
......@@ -143,7 +192,6 @@ const BottleInfoPresenter = (props : BottleInfoProps) => {
<styled.NewFeedbackRegButton
onClick = {props.onSubmitFeedback}
>
{/* <styled.NewFeedbackRegButtonImg /> */}
<styled.NewFeedbackRegButtonText>피드백<br/>등록</styled.NewFeedbackRegButtonText>
</styled.NewFeedbackRegButton>
</styled.NewFeedbackButtonWrapper>
......
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);
}
`;
import styled from 'styled-components';
export const Container = styled.div `
......@@ -32,91 +9,106 @@ export const Container = styled.div `
justify-content : center;
`;
export const ModalContainer = styled.div `
height : 100%;
width : 100%;
z-index : 99;
position : absolute;
export const ModalTypeButtonWrapper = styled.div `
border : none;
display : flex;
flex-direction : column;
animation : ${ModalOn} .5s;
background-color : rgba(52, 52, 52, .7);
flex-direction : row;
`;
width : 100%;
export const ModalClsButtonWrapper = styled.div `
flex : 1;
justify-content : center;
align-items : center;
display : flex;
background-color : transparent;
justify-content : flex-end;
align-items : center;
padding : 0 20px;
gap : 5%;
border : none;
background-color : transprent;
padding : 3% 0 0 0;
`;
export const ModalClsButton = styled.button `
border : none;
background-color : transparent;
cursor : pointer;
export const ModalTypeButton = styled.button<{isSelect : boolean}> `
border : 1px solid #337DFF;
border-radius : 3px;
color : ${props => props.isSelect ? '#fff' : '#337DFF'};
background-color : ${props => props.isSelect ? '#337DFF' : '#fff'};
color : #fff;
padding : 1% 3%;
display : flex;
flex-direction : row;
cursor : pointer;
justify-content : center;
align-items : center;
font-size : 16px;
font-weight : ${props => props.isSelect ? '600' : '500'};
transition : .25s all;
&:hover {
opacity : .5;
}
`;
export const ModalClsButtonImg = styled.img `
height : 20px;
width : 20px;
export const HistWrapper = styled.div `
display : flex;
flex-direction : column;
margin : 0 10px 0 0;
`;
height : 65px;
width : 100%;
export const ModalClsButtonText = styled.div `
font-size : 18px;
font-weight : 700;
border : none;
border-bottom : 1px solid #ddd;
`;
export const ModalContentWrapper = styled.div `
flex : 8;
export const HistDtmWrapper = styled.div `
flex : 2;
padding : 0 3%;
border : none;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
border : none;
`;
export const ModalContent = styled.div `
width : 700px;
height : 500px;
export const HistDtm = styled.div `
font-size : 16px;
color : #000;
`;
export const HistInfoWrapper = styled.div `
flex : 1;
padding : 0 3%;
background-color : #fff;
border : 1.2px solid #337DFF;
border-radius : 5px;
border : none;
display : flex;
flex-direction : column;
flex-direction : row;
align-items : flex-start;
`;
export const HistInfoEachWrapper = styled.div `
flex : 1;
border : none;
// justify-content : center;
display : flex;
flex-direction : row;
align-items : center;
gap : 5%;
font-size : 14px;
font-weight : 500;
color : #a0a0a0;
`;
export const HistInfoEach = styled.div `
font-size : 15px;
font-weight : 600;
color : #337DFF;
`;
export const MedicineNameWrapper = styled.div `
......@@ -169,6 +161,7 @@ export const MedicineEachInfoWrapper = styled.div `
display : flex;
flex-direction : column;
width : 80%;
padding : 20px 10%;
border : none;
......
......@@ -23,6 +23,7 @@ const LoginContainer = (props : LoginProps) => {
});
const [token, setToken] = useRecoilState(recoilUtil.token);
const [userId, setUserId] = useRecoilState(recoilUtil.userId);
const [userTypeCd, setUserTypeCd] = useRecoilState(recoilUtil.userTypeCd);
......@@ -58,13 +59,14 @@ const LoginContainer = (props : LoginProps) => {
const result : any = await authApi.login(loginForm);
if(result.statusText === 'OK' && result.data.userTypeCd !== 'NORMAL') {
setToken(result.data.token);
setUserId(loginForm.userId);
setUserTypeCd(result.data.userTypeCd);
Alert.onSuccess('로그인 성공, 메인 화면으로 이동합니다.', () => props.history.push('/'));
} else if(result.data.userTypeCd === 'NORMAL') {
Alert.onError('권한이 없는 유저입니다.', () => props.history.push('/'));
}
} catch(e) {
Alert.onError(e.response.data.error, () => null);
} catch(e : any) {
Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
}
};
......
......@@ -6,6 +6,7 @@ import * as recoilUtil from '../../util/recoilUtil';
import Header from '../../components/Header';
import Footer from '../../components/Footer';
import DoctorMenuContainer from './doctor';
import ManagerMenuContainer from './manager';
......@@ -32,6 +33,7 @@ const MainContainer = (props : MainProps) => {
userTypeCd === 'MANAGER' ?
<ManagerMenuContainer {...props}/> : null
}
<Footer {...props}/>
</>
);
};
......
import React from 'react';
import * as styled from './MainStyled';
interface MainProps {
userTypeCd : string;
}
const MainPresenter = (props : MainProps) => {
return (
<styled.Container>
<styled.InfoAndSearchWrapper>
<styled.InfoWrapper>
<styled.InfoSquare>
</styled.InfoSquare>
<styled.NewPatientButton>새 환자 등록</styled.NewPatientButton>
</styled.InfoWrapper>
<styled.SearchAndDetailWrapper>
<styled.SearchBarWrapper>
<styled.SearchBar
placeholder = '환자 이름'
/>
<styled.SearchButton>
검색
</styled.SearchButton>
</styled.SearchBarWrapper>
<styled.SearchResultWrapper>
</styled.SearchResultWrapper>
</styled.SearchAndDetailWrapper>
</styled.InfoAndSearchWrapper>
<styled.BottleListWrapper>
bottleListWrapper
</styled.BottleListWrapper>
</styled.Container>
)
};
export default MainPresenter;
\ No newline at end of file
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);
}
`;
import styled from 'styled-components';
export const Container = styled.div `
......@@ -32,93 +9,6 @@ export const Container = styled.div `
justify-content : 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 NewPatientRegisterTitle = styled.div `
font-size : 20px;
font-weight : 700;
......@@ -199,14 +89,18 @@ export const NewPatientSearchResult = styled.div `
export const NewPatientSearchResultInfoWrapper = styled.div `
display : flex;
flex-direction : column;
`;
export const NewPatientSearchResultInfo = styled.div `
display : flex;
flex-direction : row;
font-size : 13px;
font-weight : 600;
color : #a0a0a0;
margin : 0 5px 0 0;
margin : 0 5px 0 5px;
`;
export const NewPatientSearchResultInfoText = styled.div `
......@@ -214,6 +108,8 @@ export const NewPatientSearchResultInfoText = styled.div `
color : #343434;
font-weight : 600;
letter-spacing : 1px;
margin : 0 0 0 5px;
`;
export const NewPatientRegisterButtonWrapper = styled.div `
......@@ -436,6 +332,7 @@ export const MedicineSearchButtonImg = styled.img `
height : 15px;
width : 15px;
transition : .25s all;
`;
export const MedicineSearchResultWrapper = styled.div `
......@@ -499,6 +396,91 @@ export const MedicineSelectButtonImg = styled.img `
width : 15px;
`;
export const MedicineDosageSetWrapper = styled.div `
width : 80%;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
border : none;
margin : 20px 0;
height : 200px;
`;
export const MedicineDosageInfo = styled.div `
font-size : 15px;
font-weight : 500;
color : #a0a0a0;
width : 100%;
margin : 10px 0 10px 0;
border : none;
background-color : transparent;
text-align : center;
`;
export const MedicineDosageInput = styled.input.attrs({
type : 'number',
}) `
margin : 0 0 10px 0;
width : 40%;
padding : 10px 20px;
color : #337DFF;
font-size : 20px;
font-weight : 700;
border : none;
border-bottom : 1px solid #337DFF;
display : flex;
flex-direction : row;
text-align : center;
transition : .25s all;
`;
export const MedicineQRCodeWrapper = styled.div `
width : 80%;
height : 200px;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
margin : 20px 0;
border : none;
`;
export const MedicineQRCodeInfo = styled.div `
font-size : 15px;
font-weight : 500;
color : #a0a0a0;
text-align : center;
`;
export const MedicineQRCode = styled.img `
margin : 10px 0 0 0;
height : 170px;
width : 170px;
`;
export const MedicinePrescribeButtonWrapper = styled.div `
margin : 20px 0 0 0;
......
......@@ -17,11 +17,14 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
const token = useRecoilValue(recoilUtil.token);
const [doctorRegReqList, setDoctorRegReqList] = useState<any>([]);
const [doctorList, setDoctorList] = useState<any>([]);
const [viewType, setViewType] = useState<string>('reg');
const [doctorDetail, setDoctorDetail] = useState<any>({});
const [modalUp, setModalUp] = useState<boolean>(false);
const [validate, setValidate] = useState<string>('W');
const [validateDoctorLicense, setValidateDoctorLicense] = useState<string>('');
......@@ -30,20 +33,29 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
setValidate('W');
try {
await managerApi.getDoctorRegReqList(token)
.then((res : any) => {
const res = viewType === 'reg' ? await managerApi.getDoctorRegReqList(token) : await managerApi.getDoctorSecReqList(token);
if(res.statusText === 'OK') {
setDoctorRegReqList(res.data.doctorRegReqList);
setDoctorList(res.data.doctorList);
} else {
Alert.onError(res.data.error, () => null);
}
}).catch(err => {
Alert.onError(err.response.data.error, () => null);
})
} catch(e) {
Alert.onError(e.response.data.error, () => null);
} catch(e : any) {
console.log(e);
Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
}
};
//가입요청 보기
const onViewRegList = () => {
setViewType('reg');
};
//탈퇴요청 보기
const onViewSecList = () => {
setViewType('sec');
};
//가입요청 상세보기
const onViewDetailReq = async (doctorId : string) => {
setValidate('W');
......@@ -55,13 +67,23 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
setModalUp(true);
}
})
} catch(e) {
Alert.onError(e.response.data.error, () => setModalUp(false));
} catch(e : any) {
Alert.onError('알 수 없는 에러가 발생했습니다.', () => setModalUp(false));
}
};
const onViewLicenseDetail = async (url : string) => {
const licensePage : any = window.open(url);
licensePage.focus();
};
//자격 확인 문서를 보고, 면허 번호를 입력하는 함수
const onSetValidateDoctorLicense = (e : React.ChangeEvent<HTMLInputElement>) => {
setValidateDoctorLicense(e.target.value);
};
//회원 가입 수락
const onAcceptRequest = () => {
const onAcceptRegReq = () => {
if(validate === 'W') {
Alert.onError('먼저 의사의 자격번호가 유효한지 검증해주세요.', () => null);
return;
......@@ -72,14 +94,16 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
const onAccept = async() => {
try {
await managerApi.acceptDoctorRegReq(token, doctorDetail)
.then((res : any) => {
await managerApi.acceptDoctorRegReq(token, {
doctorId : doctorDetail.doctorId,
validateDoctorLicense,
}).then((res : any) => {
if(res.statusText === 'OK') {
Alert.onSuccess('회원 등록이 완료되었습니다.', fetchData);
}
})
} catch(e) {
Alert.onError(e.response.data.error, () => setModalUp(false));
});
} catch(e : any) {
Alert.onError('알 수 없는 에러가 발생했습니다.', () => setModalUp(false));
}
};
......@@ -90,58 +114,95 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
const onRejectRequest = () => {
const onReject = async() => {
try {
await managerApi.rejectDoctorRegReq(token, doctorDetail)
.then((res : any) => {
await managerApi.rejectDoctorRegReq(token, {
doctorId : doctorDetail.doctorId,
}).then((res : any) => {
if(res.statusText === 'OK') {
Alert.onSuccess('회원 등록이 취소되었습니다.', fetchData);
}
})
} catch(e) {
Alert.onError(e.response.data.error, () => setModalUp(false));
});
} catch(e : any) {
Alert.onError('알 수 없는 에러가 발생했습니다.', () => setModalUp(false));
}
};
Alert.onCheck('회원 가입 요청을 취소하시겠습니까?', onReject, () => null);
};
//회원 자격번호 유효 검증 api
const onValidate = async () => {
try {
await managerApi.validateDoctorLicense(token, {
doctorLicense : doctorDetail.info.doctorLicense,
validateDoctorLicense,
}).then(res => {
if(res.statusText === 'OK') {
setValidate(res.data.result ? 'Y' : 'N');
}
}).catch(err => {
Alert.onError(err.response.data, () => {
console.log(err);
Alert.onError('알 수 없는 에러가 발생했습니다.', () => {
setModalUp(false);
setValidate('W');
});
})
} catch(e) {
Alert.onError(e.response.data, () => {
} catch(e : any) {
Alert.onError('알 수 없는 에러가 발생했습니다.', () => {
setModalUp(false);
setValidate('W');
});
}
};
const onAcceptSecReq = (doctorId : string) => {
const onAccept = async () => {
try {
const res = await managerApi.acceptDoctorSecReq(token, {
doctorId,
});
if(res.statusText === 'OK') {
Alert.onSuccess('탈퇴를 승인했습니다.', fetchData);
}
} catch (e : any) {
Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
}
};
Alert.onCheck('회원 탈퇴를 승인하시겠습니까?\n이 작업은 되돌릴 수 없습니다.', onAccept, () => null);
};
useEffect(() => {
setValidate('W');
setValidateDoctorLicense('');
}, [modalUp]);
useEffect(() => {
fetchData();
}, []);
}, [viewType]);
return (
<ManagerMenuPresenter
doctorRegReqList = {doctorRegReqList}
viewType = {viewType}
onViewRegList = {onViewRegList}
onViewSecList = {onViewSecList}
doctorList = {doctorList}
doctorDetail = {doctorDetail}
modalUp = {modalUp}
setModalUp = {setModalUp}
onViewDetailReq = {onViewDetailReq}
onViewLicenseDetail = {onViewLicenseDetail}
validate = {validate}
onValidate = {onValidate}
onAcceptRequest = {onAcceptRequest}
validateDoctorLicense = {validateDoctorLicense}
onSetValidateDoctorLicense = {onSetValidateDoctorLicense}
onAcceptRegReq = {onAcceptRegReq}
onAcceptSecReq = {onAcceptSecReq}
onRejectRequest = {onRejectRequest}
/>
);
......
import React from 'react';
import Modal from '../../../components/Modal';
import * as styled from './ManagerMenuStyled';
const closeButton = '/static/img/close.png';
interface ManagerMenuProps {
doctorRegReqList : any[];
viewType : string;
onViewRegList : () => void;
onViewSecList : () => void;
doctorList : any[];
doctorDetail : any;
modalUp : boolean;
setModalUp : any;
onViewDetailReq : (arg0 : string) => void;
onViewLicenseDetail : (arg0 : string) => void;
validate : string;
onValidate : () => void;
onAcceptRequest : () => void;
validateDoctorLicense : string;
onSetValidateDoctorLicense : React.ChangeEventHandler<HTMLInputElement>;
onAcceptRegReq : () => void;
onAcceptSecReq : (arg0 : string) => void;
onRejectRequest : () => void;
}
......@@ -26,26 +34,33 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
<styled.Container>
{
props.modalUp ?
<styled.ModalContainer>
<styled.ModalClsButtonWrapper>
<styled.ModalClsButton
onClick = {() => props.setModalUp(false)}
>
<styled.ModalClsButtonImg src = {closeButton}/>
<styled.ModalClsButtonText>닫기</styled.ModalClsButtonText>
</styled.ModalClsButton>
</styled.ModalClsButtonWrapper>
<styled.ModalContentWrapper>
<styled.ModalContent>
<Modal onModalClose = {() => props.setModalUp(false)}>
<>
<styled.ModalTitleWrapper>
<styled.ModalTitle>가입 요청 정보</styled.ModalTitle>
</styled.ModalTitleWrapper>
<styled.ModalBodyWrapper>
<styled.ModalBodyLeftAndRight>
<styled.ModalInfoWrapper>
<styled.ModalInfoExplain>의사 자격 번호</styled.ModalInfoExplain>
<styled.DoctorLicenseViewWrapper>
<styled.ModalInfoExplain>
의사 자격 번호
</styled.ModalInfoExplain>
<styled.DoctorLicenseViewButton onClick = {() => props.onViewLicenseDetail(props.doctorDetail.info.doctorLicense)}>
자격정보 확인
</styled.DoctorLicenseViewButton>
</styled.DoctorLicenseViewWrapper>
<styled.ModalInfoNotice>
* 자격 정보 확인 버튼을 눌러 정보를 확인하세요.
<br/>* 정보 확인은 15분간 유효합니다.
<br/>* 확인한 면허 번호를 입력 후 검증하세요.
</styled.ModalInfoNotice>
<styled.ModalInfo>
{props.doctorDetail.info.doctorLicense}
<styled.DoctorLicenseViewInput
placeholder = '의사 면허 번호'
value = {props.validateDoctorLicense}
onChange = {props.onSetValidateDoctorLicense}
/>
<styled.ValidateButton
onClick = {props.onValidate}
disabled = {props.validate !== 'W'}
......@@ -88,7 +103,7 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
</styled.ModalBodyWrapper>
<styled.ModalButtonWrapper>
<styled.ModalButton
onClick = {props.onAcceptRequest}
onClick = {props.onAcceptRegReq}
isAccept = {true}
>
수락
......@@ -100,19 +115,41 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
거절
</styled.ModalButton>
</styled.ModalButtonWrapper>
</styled.ModalContent>
</styled.ModalContentWrapper>
<styled.ModalClsButtonWrapper/>
</styled.ModalContainer> : null
</>
</Modal> : null
}
<styled.ContentWrapper>
<styled.ContentButtonWrapper>
<styled.ContentButton
isSelect = {props.viewType === 'reg'}
onClick = {props.onViewRegList}
>
가입 대기
</styled.ContentButton>
<styled.ContentButton
isSelect = {props.viewType === 'sec'}
onClick = {props.onViewSecList}
>
탈퇴 요청
</styled.ContentButton>
</styled.ContentButtonWrapper>
<styled.ContentTitle>
{
props.viewType === 'sec' ?
<>
탈퇴 대기 중 의사 회원
<styled.ContentExplain>
*승인을 누르면 탈퇴를 승인합니다.
</styled.ContentExplain>
</> :
<>
가입 대기 중 의사 회원
<styled.ContentExplain>
*클릭하면 상세정보를 확인할 수 있습니다.
</styled.ContentExplain>
</>
}
</styled.ContentTitle>
<styled.ContentBody>
<styled.ContentInfoWrapper>
<styled.ContentInfo
isLast = {false}
......@@ -125,18 +162,28 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
이름
</styled.ContentInfo>
<styled.ContentInfo
isLast = {true}
isLast = {props.viewType !== 'sec'}
>
이메일
</styled.ContentInfo>
{
props.viewType === 'sec' ?
<styled.ContentInfo
isLast = {true}
>
탈퇴 수락
</styled.ContentInfo> : null
}
</styled.ContentInfoWrapper>
<styled.ContentBody>
{
props.doctorRegReqList.length ?
props.doctorRegReqList.map((doctor : any) => {
props.doctorList.length ?
props.doctorList.map((doctor : any) => {
return (
<styled.EachContentWrapper
key = {doctor.doctorId}
onClick = {() => props.onViewDetailReq(doctor.doctorId)}
disabled = {props.viewType === 'sec'}
>
<styled.EachContentNm
isLast = {false}
......@@ -149,10 +196,22 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
{doctor.info.doctorNm}
</styled.EachContentNm>
<styled.EachContentNm
isLast = {true}
isLast = {props.viewType !== 'sec'}
>
{doctor.doctorId}
</styled.EachContentNm>
{
props.viewType === 'sec' ?
<styled.EachContentNm
isLast = {true}
>
<styled.AcceptButton
onClick = {() => props.onAcceptSecReq(doctor.doctorId)}
>
승인
</styled.AcceptButton>
</styled.EachContentNm> : null
}
</styled.EachContentWrapper>
)
}) :
......
import styled, { keyframes } from 'styled-components';
import styled 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 `
width : 100%;
height : 80vh;
......@@ -33,92 +11,6 @@ export const Container = styled.div `
`;
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 : 700px;
height : 500px;
background-color : #fff;
border : 1.2px solid #337DFF;
border-radius : 5px;
display : flex;
flex-direction : column;
// justify-content : center;
align-items : center;
`;
export const ModalTitleWrapper = styled.div `
flex : 1;
border : none;
......@@ -183,25 +75,96 @@ export const ModalInfoExplain = styled.div `
letter-spacing : 1px;
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
border : none;
border-bottom : 1px solid #337DFF;
color : #337DFF;
padding : 2px 5px;
padding : 2px 1px;
`;
export const ModalInfo = styled.div `
margin : 5px 0 20px 0;
font-size : 20px;
font-weight : 700;
margin : 5px 0 10px 0;
font-size : 13px;
font-weight : 600;
letter-spacing : 1px;
display : flex;
flex-direction : row;
align-items : center;
`;
export const ModalInfoNotice = styled.div `
font-size : 11px;
color : #bbb;
font-weight : 400;
letter-spacing : 0px;
margin : 5px 0 0px 0;
`;
export const DoctorLicenseViewWrapper = styled.div `
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
border : none;
background-color : transparent;
`;
export const DoctorLicenseViewButton = styled.button `
margin : 5px 0 0 7px;
border : 1px solid #343434;
border-radius : 3px;
background-color : #EAF2FF;
padding : 2px 5px;
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
cursor : pointer;
transition : .25s all;
&:hover {
border : 1px solid #337DFF;
background-color : #337DFF;
color : #fff;
}
font-size : 11px;
`;
export const DoctorLicenseViewInput = styled.input `
padding : 2px 1px;
font-size : 11px;
letter-spacing : 1px;
color : #343434;
border : none;
border-bottom : 1px solid #343434;
&::placeholder {
color : #ccc;
}
`;
export const ValidateButton = styled.button<{validate : string}> `
margin : 0 0 0 15px;
font-size : 11px;
margin : 0 0 0 5px;
padding : 2px 5px;
border-radius : 3px;
......@@ -288,8 +251,45 @@ export const ContentWrapper = styled.div `
`;
export const ContentButtonWrapper = styled.div `
width : 100%;
height : 10%;
border : none;
display : flex;
flex-direction : row;
justify-content : center;
align-items : flex-end;
gap : 10%;
background-color : transparent;
`;
export const ContentButton = styled.button<{isSelect : boolean}> `
background-color : ${props => props.isSelect ? '#337DFF' : 'transparent'};
color : ${props => props.isSelect ? '#fff' : '#337DFF'};
border : 1px solid #337DFF;
border-radius : 4px;
padding : 4px 10px;
cursor : pointer;
display : flex;
justify-content : center;
align-items : center;
transition : .25s all;
&:hover {
opacity : .5;
}
`;
export const ContentTitle = styled.div `
width : 100%;
height : 20%;
border : none;
border-bottom : 1px solid #ddd;
......@@ -298,7 +298,6 @@ export const ContentTitle = styled.div `
justify-content : center;
align-items : center;
padding : 4% 0;
font-size : 22px;
font-weight : 600;
letter-spacing : 1px;
......@@ -318,16 +317,17 @@ export const ContentExplain = styled.div `
export const ContentBody = styled.div `
overflow : scroll;
height : 79%;
min-height : 60%;
max-height : 60%;
border : none;
padding : 0 0 0 3px;
display : flex;
flex-direction : column;
align-items : center;
padding : 0 0 0 3px;
&::-webkit-scrollbar {
width : 3px;
background-color : transparent;
......@@ -341,6 +341,7 @@ export const ContentBody = styled.div `
export const ContentInfoWrapper = styled.div `
width : 100%;
height : 10%;
border : none;
border-bottom : 1px solid #a0a0a0;
......@@ -350,7 +351,6 @@ export const ContentInfoWrapper = styled.div `
justify-content : center;
align-items : center;
padding : 12px 0px;
`;
export const ContentInfo = styled.div<{isLast : boolean}> `
......@@ -388,6 +388,7 @@ export const EachContentWrapper = styled.button `
padding : 10px 0px;
:not(:disabled) {
cursor : pointer;
transition : .1s all;
......@@ -396,6 +397,7 @@ export const EachContentWrapper = styled.button `
background-color : #337DFF;
color : #fff;
}
}
`;
......@@ -417,10 +419,33 @@ export const EachContentNm = styled.div<{isLast : boolean}> `
`;
export const AcceptButton = styled.button `
background-color : transparent;
color : #337DFF;
border : 1px solid #337DFF;
border-radius : 3px;
padding : 2px 10px;
display : flex;
justify-content : center;
align-items : center;
cursor : pointer;
transition : .25s all;
&:hover {
background-color : #337DFF;
color : #fff;
}
`;
export const NothingWrapper = styled.div `
height : 100%;
width : 100%;
border : none;
display : flex;
justify-content : center;
align-items : center;
......
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } 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,13 +19,13 @@ 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 : '',
password : '',
passwordCheck : '',
info : {
doctorLicense : '',
hospitalNm : '',
hospitalAddr : '',
contact : '',
......@@ -34,10 +33,18 @@ const RegisterContainer = (props : RegisterProps) => {
doctorNm : '',
},
});
const [doctorInfoFile, setDoctorInfoFile] = useState<FileList | null>(null);
const doctorInfoFile_Select = useRef(null);
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() => {
......@@ -61,7 +68,6 @@ const RegisterContainer = (props : RegisterProps) => {
}
};
const validateRegisterForm = () => {
if(page === 1) {
if (!validator.isEmail(registerForm.userId)) {
......@@ -74,9 +80,8 @@ const RegisterContainer = (props : RegisterProps) => {
setError('비밀번호가 일치하지 않습니다.')
} else setError(null);
} else if(page === 2) {
if(!registerForm.info.doctorLicense.length &&
!validator.isAlphanumeric(registerForm.info.doctorLicense)) {
setError('의사 자격 번호를 입력해야 합니다.');
if(!doctorInfoFile) {
setError('의사 자격 인증 파일을 첨부해야 합니다.');
} else if(registerForm.info.doctorNm.length < 2) {
setError('의사 이름을 올바르게 입력해야 합니다.');
} else if(!registerForm.info.contact) {
......@@ -116,33 +121,11 @@ const RegisterContainer = (props : RegisterProps) => {
};
const onSetDoctorLicense = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
doctorLicense : e.target.value,
},
});
setDoctorInfoFile(e.target.files);
};
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 +158,56 @@ 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 onConfirmSelectHospital = () => {
setSearchHospital(false);
setHospitalSearchPage(1);
setHospitalSearchPageList([1]);
setHospitalList([]);
};
const onCancelSelectHospital = () => {
Alert.onCheck('병원 등록이 취소됩니다. 계속하시겠습니까?', () => {
setSearchHospital(false);
setHospitalNm('');
setHospitalSearchPage(1);
setHospitalSearchPageList([1]);
setHospitalList([]);
setSelectHospital(null);
}, () => null);
};
const onSubmitButton = () => {
if(error) {
Alert.onError(error, () => null);
......@@ -186,26 +219,80 @@ const RegisterContainer = (props : RegisterProps) => {
} else if(page === 2) {
setPage(3);
} else if(page === 3) {
const Data = new FormData();
Data.append('userId', registerForm.userId);
Data.append('password', registerForm.password);
Data.append('passwordCheck', registerForm.passwordCheck);
Data.append('hospitalNm', registerForm.info.hospitalNm);
Data.append('hospitalAddr', registerForm.info.hospitalAddr);
Data.append('contact', registerForm.info.contact);
Data.append('doctorNm', registerForm.info.doctorNm);
Data.append('doctorType', registerForm.info.doctorType);
Data.append('doctorInfoFile', doctorInfoFile ? doctorInfoFile[0] : '');
const onRegisterDoctor = async () => {
//로딩 진행
setLoading(true);
try {
const result = await authApi.registerDoctor(registerForm);
const result = await authApi.registerDoctor(Data);
if(result.data === 'Created') {
setLoading(false);
Alert.onSuccess('회원가입 성공, 관리자의 승인을 대기하세요.', () => props.history.push('/login'));
}
} catch(e) {
} catch(e : any) {
setLoading(false);
Alert.onError(e.response.data.error, () => null);
}
};
if(selectHospital) {
Alert.onCheck('입력하신 정보로 회원가입을 진행하시겠습니까?', onRegisterDoctor, () => null);
} else {
Alert.onError('검색 버튼을 눌러 병원을 선택해주세요.', () => null);
}
}
};
useEffect(() => {
validateRegisterForm();
}, [registerForm, page]);
}, [registerForm, doctorInfoFile, 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();
......@@ -218,6 +305,8 @@ const RegisterContainer = (props : RegisterProps) => {
<Header {...props}/>
<RegisterPresenter
registerForm = {registerForm}
doctorInfoFile = {doctorInfoFile}
doctorInfoFile_Select = {doctorInfoFile_Select}
page = {page}
error = {error}
......@@ -228,12 +317,28 @@ const RegisterContainer = (props : RegisterProps) => {
onSetPassword = {onSetPassword}
onSetPasswordCheck = {onSetPasswordCheck}
onSetDoctorLicense = {onSetDoctorLicense}
hospitalNm = {hospitalNm}
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}
onConfirmSelectHospital = {onConfirmSelectHospital}
onCancelSelectHospital = {onCancelSelectHospital}
hospitalList = {hospitalList}
selectHospital = {selectHospital}
setSelectHospital = {setSelectHospital}
/>
</>
)
......
This diff is collapsed. Click to expand it.
......@@ -10,6 +10,198 @@ export const Container = styled.div `
align-items : center;
`;
export const SearchTitle = styled.div `
font-weight : 600;
font-size : 20;
color : #337DFF;
display : flex;
flex-direction : row;
align-items : center;
justify-content : center;
`;
export const SearchResultCount = styled.div `
color : #343434;
`;
export const HospitalListWrapper = styled.div `
margin : 20px 0;
height : 200px;
width : 80%;
border : 1px solid #337DFF;
border-radius : 3px;
display : flex;
flex-direction : column;
`;
export const HospitalListInfo = styled.div `
height : 25px;
width : 100%;
border : none;
border-bottom : 2px 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 : 34px;
max-height : 34px;
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 +216,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 +288,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 +314,87 @@ export const RegisterInput = styled.input `
}
`;
export const RegisterFileUploadWrapper = styled.div `
width : 80%;
display : flex;
flex-direction : row;
justify-content : flex-start;
align-items : center;
border : none;
background-color : transparent;
`;
export const RegisterFileUploadButton = styled.button `
display : flex;
flex-direction : row;
justify-content : center;
align-items : center;
border-radius : 3px;
border : 1px solid #343434;
background-color : transparent;
color : #343434;
padding : 3px 4px;
font-size : 12px;
font-weight : 600;
cursor : pointer;
transition : .25s all;
&:hover {
border : 1px solid #337DFF;
color : #fff;
background-color : #337DFF;
}
margin : 0 5% 0 0;
`;
export const RegisterFileUploadInfoText = styled.div `
font-size : 12px;
font-weight : 600;
color : #337DFF;
`;
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;
......
This diff could not be displayed because it is too large.