박권수

Merge branch 'web'

Showing 71 changed files with 4126 additions and 1140 deletions
...@@ -5,10 +5,20 @@ ...@@ -5,10 +5,20 @@
5 "version": "0.2.0", 5 "version": "0.2.0",
6 "configurations": [ 6 "configurations": [
7 { 7 {
8 - "name": "flutter_application_1", 8 + "name": "Attach",
9 - "cwd": "frontend\\flutter_application_1", 9 + "port": 9229,
10 - "request": "launch", 10 + "request": "attach",
11 - "type": "dart" 11 + "skipFiles": [
12 - } 12 + "<node_internals>/**"
13 + ],
14 + "type": "pwa-node"
15 + },
16 + {
17 + "name": "Attach to Chrome",
18 + "port": 9222,
19 + "request": "attach",
20 + "type": "pwa-chrome",
21 + "webRoot": "${workspaceFolder}"
22 + },
13 ] 23 ]
14 } 24 }
...\ No newline at end of file ...\ No newline at end of file
......
1 const Koa = require('koa'); 1 const Koa = require('koa');
2 -const cors = require('@koa/cors');
3 const Router = require('koa-router'); 2 const Router = require('koa-router');
3 +
4 +const cors = require('@koa/cors');
4 const bodyparser = require('koa-bodyparser'); 5 const bodyparser = require('koa-bodyparser');
5 6
6 const Mongoose = require('mongoose'); 7 const Mongoose = require('mongoose');
7 const api = require('./src/api'); 8 const api = require('./src/api');
8 -const updateMedicineInfo = require('./src/lib/UpdatingMedicineInfo');
9 const MqttServer = require('./src/util/MqttServer'); 9 const MqttServer = require('./src/util/MqttServer');
10 +const BatchSystem = require('./src/util/Batch');
11 +// const FCM = require('./src/util/FCM');
10 12
11 require('dotenv').config(); 13 require('dotenv').config();
12 // eslint-disable-next-line no-undef 14 // eslint-disable-next-line no-undef
...@@ -23,10 +25,10 @@ Mongoose.connect(MONGO_URL, { ...@@ -23,10 +25,10 @@ Mongoose.connect(MONGO_URL, {
23 useCreateIndex : true 25 useCreateIndex : true
24 }).then(() => { 26 }).then(() => {
25 console.log('\x1b[1;32mMongo DB is connected : ', MONGO_URL, '\x1b[0m'); 27 console.log('\x1b[1;32mMongo DB is connected : ', MONGO_URL, '\x1b[0m');
26 - // updateMedicineInfo.updateMedicineInfo(); 28 + BatchSystem.updateMedicineData();
27 }).catch(e => { 29 }).catch(e => {
28 console.log(e); 30 console.log(e);
29 -}) 31 +});
30 32
31 app.use(bodyparser()); 33 app.use(bodyparser());
32 router.use('/api', api.routes()); 34 router.use('/api', api.routes());
...@@ -36,4 +38,7 @@ app.use(router.routes()).use(router.allowedMethods()); ...@@ -36,4 +38,7 @@ app.use(router.routes()).use(router.allowedMethods());
36 app.listen(SERVER_PORT, () => { 38 app.listen(SERVER_PORT, () => {
37 console.log('\x1b[1;36mPORT : ', SERVER_PORT, 'is connected\x1b[0m'); 39 console.log('\x1b[1;36mPORT : ', SERVER_PORT, 'is connected\x1b[0m');
38 MqttServer.on(); 40 MqttServer.on();
39 -})
...\ No newline at end of file ...\ No newline at end of file
41 + // FCM.initializeFCM();
42 + BatchSystem.removeQrCode();
43 + BatchSystem.pushNotifyByDosage();
44 +});
...\ No newline at end of file ...\ No newline at end of file
......
This diff could not be displayed because it is too large.
...@@ -17,11 +17,16 @@ ...@@ -17,11 +17,16 @@
17 "author": "박권수", 17 "author": "박권수",
18 "license": "ISC", 18 "license": "ISC",
19 "dependencies": { 19 "dependencies": {
20 + "@google-cloud/storage": "^5.14.2",
20 "@koa/cors": "^3.1.0", 21 "@koa/cors": "^3.1.0",
21 "firebase-admin": "^9.11.1", 22 "firebase-admin": "^9.11.1",
23 + "google-auth-library": "^7.10.0",
24 + "koa-body": "^4.2.0",
22 "moment": "^2.29.1", 25 "moment": "^2.29.1",
26 + "moment-timezone": "^0.5.33",
23 "mqtt": "^4.2.6", 27 "mqtt": "^4.2.6",
24 - "node-cron": "^3.0.0" 28 + "node-cron": "^3.0.0",
29 + "qrcode": "^1.4.4"
25 }, 30 },
26 "devDependencies": { 31 "devDependencies": {
27 "eslint": "^7.32.0" 32 "eslint": "^7.32.0"
......
1 -/* eslint-disable no-undef */
2 //회원가입, 로그인 및 로그아웃에 관한 api 1 //회원가입, 로그인 및 로그아웃에 관한 api
3 const User = require('../../models/user'); 2 const User = require('../../models/user');
4 const Profile = require('../../models/profile'); 3 const Profile = require('../../models/profile');
5 const DoctorInfo = require('../../models/doctorInfo'); 4 const DoctorInfo = require('../../models/doctorInfo');
5 +const Hub = require('../../models/hub');
6 +const Bottle = require('../../models/bottle');
7 +const PatientInfo = require('../../models/patientInfo');
8 +const { uploadDoctorLicense } = require('../../util/GoogleCloudStorage');
6 const Joi = require('joi'); 9 const Joi = require('joi');
7 const jwt = require('jsonwebtoken'); 10 const jwt = require('jsonwebtoken');
11 +const axios = require('axios');
8 12
9 13
10 exports.register = async(ctx) => { 14 exports.register = async(ctx) => {
...@@ -13,8 +17,9 @@ exports.register = async(ctx) => { ...@@ -13,8 +17,9 @@ exports.register = async(ctx) => {
13 password, 17 password,
14 passwordCheck, 18 passwordCheck,
15 userNm, 19 userNm,
16 - userAge, 20 + birth,
17 contact, 21 contact,
22 + deviceToken,
18 } = ctx.request.body; 23 } = ctx.request.body;
19 24
20 const schema = Joi.object().keys({ 25 const schema = Joi.object().keys({
...@@ -22,8 +27,9 @@ exports.register = async(ctx) => { ...@@ -22,8 +27,9 @@ exports.register = async(ctx) => {
22 password : Joi.string().required(), 27 password : Joi.string().required(),
23 passwordCheck : Joi.string().required(), 28 passwordCheck : Joi.string().required(),
24 userNm : Joi.string().required(), 29 userNm : Joi.string().required(),
25 - userAge : Joi.number().required(), 30 + birth : Joi.string().required(),
26 contact : Joi.string().required(), 31 contact : Joi.string().required(),
32 + deviceToken : Joi.string().allow(null),
27 }); 33 });
28 34
29 const result = schema.validate(ctx.request.body); 35 const result = schema.validate(ctx.request.body);
...@@ -44,6 +50,15 @@ exports.register = async(ctx) => { ...@@ -44,6 +50,15 @@ exports.register = async(ctx) => {
44 return; 50 return;
45 } 51 }
46 52
53 + const existContact = await Profile.findOne({ contact, useYn : 'Y' });
54 + if(existContact) {
55 + ctx.status = 409;
56 + ctx.body = {
57 + error : '이미 가입된 번호입니다.',
58 + };
59 + return;
60 + }
61 +
47 const user = new User({ 62 const user = new User({
48 userId, 63 userId,
49 userTypeCd : 'NORMAL', 64 userTypeCd : 'NORMAL',
...@@ -55,8 +70,9 @@ exports.register = async(ctx) => { ...@@ -55,8 +70,9 @@ exports.register = async(ctx) => {
55 const profile = new Profile({ 70 const profile = new Profile({
56 userId, 71 userId,
57 userNm, 72 userNm,
58 - userAge, 73 + birth,
59 - contact, 74 + contact,
75 + deviceToken,
60 }); 76 });
61 77
62 await user.save(); 78 await user.save();
...@@ -66,22 +82,59 @@ exports.register = async(ctx) => { ...@@ -66,22 +82,59 @@ exports.register = async(ctx) => {
66 82
67 }; 83 };
68 84
85 +exports.searchHospital = async ctx => {
86 + const {
87 + hospitalNm,
88 + page,
89 + } = ctx.query;
90 +
91 + const pageSlice = 5;
92 +
93 + const url = 'http://apis.data.go.kr/B551182/hospInfoService1/getHospBasisList1';
94 + // eslint-disable-next-line no-undef
95 + let queryParams = '?' + encodeURIComponent('ServiceKey') + '=' + process.env.SERVICE_KEY;
96 + queryParams += '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent(page);
97 + queryParams += '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent(pageSlice);
98 + queryParams += '&' + encodeURIComponent('yadmNm') + '=' + encodeURIComponent(hospitalNm);
99 +
100 + const result = await axios.get(url + queryParams);
101 +
102 + ctx.status = 200;
103 + ctx.body = {
104 + totalPage : Math.ceil(result.data.response.body.totalCount / pageSlice),
105 + hospitalList : result.data.response.body.items.item,
106 + };
107 +};
108 +
109 +//의사 회원가입
69 exports.doctorRegister = async ctx => { 110 exports.doctorRegister = async ctx => {
70 const { 111 const {
71 userId, 112 userId,
72 password, 113 password,
73 passwordCheck, 114 passwordCheck,
74 - info, 115 +
116 + contact,
117 + hospitalNm,
118 + hospitalAddr,
119 + doctorType,
120 + doctorNm,
75 } = ctx.request.body; 121 } = ctx.request.body;
76 122
123 + const { doctorInfoFile } = ctx.request.files;
124 +
77 const schema = Joi.object().keys({ 125 const schema = Joi.object().keys({
78 userId : Joi.string().email().max(50).required(), 126 userId : Joi.string().email().max(50).required(),
79 password : Joi.string().required(), 127 password : Joi.string().required(),
80 passwordCheck : Joi.string().required(), 128 passwordCheck : Joi.string().required(),
81 - info : Joi.object().required(), 129 + doctorInfoFile : Joi.object().required(),
82 - }) 130 + });
83 - 131 +
84 - const result = schema.validate(ctx.request.body); 132 + const result = schema.validate({
133 + userId,
134 + password,
135 + passwordCheck,
136 + doctorInfoFile,
137 + });
85 if(result.error || password !== passwordCheck) { 138 if(result.error || password !== passwordCheck) {
86 ctx.status = 400; 139 ctx.status = 400;
87 ctx.body = { 140 ctx.body = {
...@@ -113,6 +166,24 @@ exports.doctorRegister = async ctx => { ...@@ -113,6 +166,24 @@ exports.doctorRegister = async ctx => {
113 }; 166 };
114 return; 167 return;
115 } 168 }
169 +
170 +
171 + const [fileName, filePath] = [doctorInfoFile.name, doctorInfoFile.path];
172 + const doctorLicense = await uploadDoctorLicense({
173 + userId,
174 + fileName,
175 + filePath,
176 + });
177 +
178 + const info = {
179 + contact,
180 + hospitalAddr,
181 + hospitalNm,
182 + doctorType,
183 + doctorNm,
184 + doctorLicense,
185 + };
186 +
116 187
117 const doctor = new User({ 188 const doctor = new User({
118 userId, 189 userId,
...@@ -128,21 +199,27 @@ exports.doctorRegister = async ctx => { ...@@ -128,21 +199,27 @@ exports.doctorRegister = async ctx => {
128 useYn : 'W', 199 useYn : 'W',
129 }); 200 });
130 201
131 - 202 + await doctorInfo.save();
132 - doctor.save(); 203 + await doctor.save();
133 - doctorInfo.save();
134 204
135 ctx.status = 201; 205 ctx.status = 201;
136 - 206 +
137 } 207 }
138 208
209 +/**
210 + * 로컬 로그인
211 + * @param {*} ctx
212 + * @returns token
213 + * http methods : POST
214 + */
139 exports.login = async(ctx) => { 215 exports.login = async(ctx) => {
140 - const { userId, password } = ctx.request.body; 216 + const { userId, password, deviceToken } = ctx.request.body;
141 217
142 const schema = Joi.object().keys({ 218 const schema = Joi.object().keys({
143 userId : Joi.string().email().max(50).required(), 219 userId : Joi.string().email().max(50).required(),
144 - password : Joi.string().required() 220 + password : Joi.string().required(),
145 - }) 221 + deviceToken : Joi.string().allow(null),
222 + });
146 223
147 const result = schema.validate(ctx.request.body); 224 const result = schema.validate(ctx.request.body);
148 if(result.error) { 225 if(result.error) {
...@@ -154,7 +231,7 @@ exports.login = async(ctx) => { ...@@ -154,7 +231,7 @@ exports.login = async(ctx) => {
154 } 231 }
155 232
156 const user = await User.findByUserId(userId); 233 const user = await User.findByUserId(userId);
157 - if(!user || !user.userTypeCd) { 234 + if(!user || !user.userTypeCd || user.authTypeCd !== 'NORMAL') {
158 ctx.status = 401; 235 ctx.status = 401;
159 ctx.body = { 236 ctx.body = {
160 error : '존재하지 않는 회원입니다.', 237 error : '존재하지 않는 회원입니다.',
...@@ -170,7 +247,6 @@ exports.login = async(ctx) => { ...@@ -170,7 +247,6 @@ exports.login = async(ctx) => {
170 }; 247 };
171 return; 248 return;
172 } 249 }
173 -
174 if(user.useYn !== 'Y') { 250 if(user.useYn !== 'Y') {
175 ctx.status = 403; 251 ctx.status = 403;
176 ctx.body = { 252 ctx.body = {
...@@ -179,6 +255,16 @@ exports.login = async(ctx) => { ...@@ -179,6 +255,16 @@ exports.login = async(ctx) => {
179 return; 255 return;
180 } 256 }
181 257
258 + //일반 유저의 deviceToken값이 바뀌면 업데이트한다 = 기기가 바뀌면
259 + if(user.userTypeCd === 'NORMAL') {
260 + const profile = await Profile.findByUserId(user.userId);
261 + if(deviceToken && profile.deviceToken !== deviceToken) {
262 + profile.updateDeviceToken(deviceToken);
263 + await profile.save();
264 + }
265 + }
266 +
267 +
182 const token = await user.generateToken(); 268 const token = await user.generateToken();
183 ctx.cookies.set('access_token', token, { 269 ctx.cookies.set('access_token', token, {
184 httpOnly : true, 270 httpOnly : true,
...@@ -188,11 +274,206 @@ exports.login = async(ctx) => { ...@@ -188,11 +274,206 @@ exports.login = async(ctx) => {
188 ctx.status = 200; 274 ctx.status = 200;
189 ctx.body = { 275 ctx.body = {
190 userTypeCd : user.userTypeCd, 276 userTypeCd : user.userTypeCd,
191 - token 277 + token,
278 + };
279 +
280 +};
281 +
282 +//social Register
283 +exports.socialRegister = async ctx => {
284 + const { socialType } = ctx.params;
285 + const { accessToken, deviceToken } = ctx.request.body;
286 +
287 + const verifyingToken =
288 + socialType.toUpperCase() === 'GOOGLE' ? async () => {
289 + //id_token
290 + const result = jwt.decode(accessToken);
291 +
292 + return {
293 + userId : result.email,
294 + userNm : result.name,
295 + contact : `${result.email}_등록되지않은 번호`,
296 + birth : '등록되지않음',
297 + };
298 + }
299 + : socialType.toUpperCase() === 'NAVER' ? async () => {
300 + const url = 'https://openapi.naver.com/v1/nid/me';
301 + const result = await axios.get(url, {
302 + headers : {
303 + Authorization : `Bearer ${accessToken}`,
304 + },
305 + });
306 +
307 + const { email, mobile, name, birthday, birthyear } = result.data.response;
308 +
309 + return {
310 + userId : email,
311 + userNm : name,
312 + contact : mobile,
313 + birth : `${birthyear}-${birthday}`,
314 + };
315 + }
316 + : socialType.toUpperCase() === 'KAKAO' ? async () => {
317 + const url = 'https://kapi.kakao.com/v2/user/me';
318 + const result = await axios.get(url, {
319 + headers : {
320 + Authorization : `Bearer ${accessToken}`,
321 + 'Content-type' : 'application/x-www-form-urlencoded;charset=utf-8',
322 + },
323 + });
324 +
325 + const { email, profile } = result.data.kakao_account;
326 +
327 + return {
328 + userId : email,
329 + userNm : profile.nickname,
330 + contact : `${email}_등록되지않은 번호`,
331 + birth : '등록되지않음',
332 + };
333 +
334 + } : () => null;
335 +
336 +
337 + const verifyingInfo = await verifyingToken();
338 + if(!verifyingInfo || !verifyingInfo.userId) {
339 + ctx.status = 403;
340 + ctx.body = {
341 + error : '잘못된 요청',
342 + };
343 +
344 + return;
345 + }
346 +
347 + const { userId, userNm, birth, contact } = verifyingInfo;
348 +
349 + const existUser = await User.findByUserId(userId);
350 + if(existUser) {
351 + ctx.status = 409;
352 + ctx.body = {
353 + error : '이미 가입된 회원',
354 + };
355 +
356 + return;
357 + }
358 +
359 + const existContact = await Profile.findOne({ contact, useYn : 'Y'});
360 + if(existContact) {
361 + ctx.status = 409;
362 + ctx.body = {
363 + error : '이미 가입된 번호',
364 + };
365 +
366 + return;
367 + }
368 +
369 + const user = new User({
370 + userId,
371 + hashedPassword : 'unnecessary',
372 + authTypeCd : socialType.toUpperCase(),
373 + useYn : 'Y',
374 + });
375 +
376 + const profile = new Profile({
377 + userId,
378 + userNm,
379 + birth,
380 + contact,
381 + deviceToken,
382 + });
383 +
384 + await profile.save();
385 + await user.save();
386 +
387 + ctx.status = 201;
388 +
389 +};
390 +
391 +//social Login
392 +exports.socialLogin = async ctx => {
393 + const { socialType } = ctx.params;
394 + const { accessToken, deviceToken, } = ctx.request.body;
395 +
396 + const verifyingToken =
397 + socialType.toUpperCase() === 'GOOGLE' ? async () => {
398 + //id_token : google Login
399 + const result = jwt.decode(accessToken);
400 +
401 + return result.email;
402 + }
403 + : socialType.toUpperCase() === 'NAVER' ? async () => {
404 + //naver Login
405 + const url = 'https://openapi.naver.com/v1/nid/me';
406 + const result = await axios.get(url, {
407 + headers : {
408 + Authorization : `Bearer ${accessToken}`,
409 + },
410 + });
411 +
412 + return result.data.response.email;
413 + }
414 + : socialType.toUpperCase() === 'KAKAO' ? async () => {
415 + //kakao Login
416 + const url = 'https://kapi.kakao.com/v2/user/me';
417 + const result = await axios.get(url, {
418 + headers : {
419 + Authorization : `Bearer ${accessToken}`,
420 + 'Content-type' : 'application/x-www-form-urlencoded;charset=utf-8',
421 + },
422 + });
423 +
424 + return result.data.kakao_account.email;
425 + } : () => null;
426 +
427 +
428 + const userId = await verifyingToken();
429 + if(!userId) {
430 + ctx.status = 403;
431 + ctx.body = {
432 + error : '잘못된 요청입니다',
433 + };
434 +
435 + return;
436 + }
437 +
438 + const user = await User.findByUserId(userId);
439 + if(!user || user.useYn !== 'Y') {
440 + ctx.status = 404;
441 + ctx.body = {
442 + error : '존재하지 않는 회원입니다.',
443 + };
444 +
445 + return;
446 + } else if (user.authTypeCd !== socialType.toUpperCase()) {
447 + ctx.status = 400;
448 + ctx.body = {
449 + error : '잘못된 소셜 로그인입니다.',
450 + };
451 +
452 + return;
453 + }
454 +
455 + const profile = await Profile.findOne({ userId });
456 + if(profile.deviceToken !== deviceToken) {
457 + profile.updateDeviceToken(deviceToken);
458 + await profile.save();
459 + }
460 +
461 +
462 + const token = await user.generateToken();
463 +
464 + ctx.status = 200;
465 + ctx.body = {
466 + userTypeCd : user.userTypeCd,
467 + token,
192 }; 468 };
193 469
194 }; 470 };
195 471
472 +/**
473 + * 로그아웃
474 + * @param {*} ctx
475 + * httm methods : POST
476 + */
196 exports.logout = async(ctx) => { 477 exports.logout = async(ctx) => {
197 ctx.cookies.set('access_token', null, { 478 ctx.cookies.set('access_token', null, {
198 httpOnly : true, 479 httpOnly : true,
...@@ -202,6 +483,70 @@ exports.logout = async(ctx) => { ...@@ -202,6 +483,70 @@ exports.logout = async(ctx) => {
202 ctx.status = 204; 483 ctx.status = 204;
203 }; 484 };
204 485
486 +
487 +/**
488 + * 회원 탈퇴
489 + * @param {*} ctx
490 + * http methods : delete
491 + */
492 +exports.secession = async ctx => {
493 + const token = ctx.req.headers.authorization;
494 + if(!token || !token.length) {
495 + ctx.status = 401;
496 + return;
497 + }
498 +
499 + // eslint-disable-next-line no-undef
500 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
501 + const user = await User.findByUserId(userId);
502 + if(!user || user.useYn !== 'Y') {
503 + ctx.status = 403;
504 + return;
505 + }
506 +
507 +
508 + const { password } = ctx.query;
509 + const isPasswordTrue = await user.checkPassword(password);
510 + if(!isPasswordTrue) {
511 + ctx.status = 401;
512 + ctx.body = {
513 + error : '비밀번호가 틀렸습니다.',
514 + };
515 + return;
516 + }
517 +
518 + if(user.userTypeCd === 'NORMAL') {
519 + //프로필 삭제
520 + await Profile.updateOne({ userId }, { useYn : 'N' });
521 +
522 + //유저에 등록된 허브, 약병, 약병정보 전부 삭제
523 + const hubList = await Hub.find({ userId });
524 + await Promise.all(hubList.map(async hub => {
525 + await Bottle.deleteMany({ hubId : hub.hubId });
526 + }));
527 +
528 + await Hub.deleteMany({ userId });
529 +
530 + //환자 정보 삭제
531 + await PatientInfo.updateMany({ patientId : userId, useYn : 'Y'}, { useYn : 'N' });
532 +
533 + //유저 삭제
534 + await user.setUseYn('N');
535 + await user.save();
536 +
537 + } else if (user.userTypeCd === 'DOCTOR') {
538 + //의사 정보 및 환자 정보 삭제
539 + await DoctorInfo.updateOne({ doctorId : userId }, { useYn : 'WS' });
540 + await PatientInfo.updateMany({ doctorId : userId }, { useYn : 'WS' });
541 +
542 + await user.setUseYn('WS');
543 + await user.save();
544 + }
545 +
546 + ctx.status = 200;
547 +
548 +};
549 +
205 exports.verifyToken = async(ctx) => { 550 exports.verifyToken = async(ctx) => {
206 const token = ctx.req.headers.authorization; 551 const token = ctx.req.headers.authorization;
207 if(!token || !token.length) { 552 if(!token || !token.length) {
...@@ -214,6 +559,7 @@ exports.verifyToken = async(ctx) => { ...@@ -214,6 +559,7 @@ exports.verifyToken = async(ctx) => {
214 return; 559 return;
215 } 560 }
216 561
562 + // eslint-disable-next-line no-undef
217 jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { 563 jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
218 if (err) { 564 if (err) {
219 ctx.status = 400; 565 ctx.status = 400;
......
1 const Router = require('koa-router') 1 const Router = require('koa-router')
2 +const KoaBody = require('koa-body')({multipart : true});
2 const authCtrl = require('./auth.ctrl') 3 const authCtrl = require('./auth.ctrl')
3 4
4 const auth = new Router() 5 const auth = new Router()
5 6
6 /** 7 /**
7 - * 회원가입 (email type) : 환자 회원가입 8 + * 로컬 회원가입 (email type) : 환자 회원가입
8 * url : http://localhost:4000/api/auth/register 9 * url : http://localhost:4000/api/auth/register
9 * request parameter : userId, password, passwordCheck 10 * request parameter : userId, password, passwordCheck
10 * return : null 11 * return : null
...@@ -12,28 +13,60 @@ const auth = new Router() ...@@ -12,28 +13,60 @@ const auth = new Router()
12 auth.post('/register', authCtrl.register) 13 auth.post('/register', authCtrl.register)
13 14
14 /** 15 /**
15 - * 회원가입 (email type) : 의사 회원가입 16 + * 병원 검색
17 + * url : http://localhost:4000/api/auth/hospital
18 + * request parameter : hospitalNm
19 + * return : xml type data
20 + */
21 +auth.get('/hospital', authCtrl.searchHospital);
22 +
23 +/**
24 + * 로컬 회원가입 (email type) : 의사 회원가입
16 * url : http://localhost:4000/api/auth/register/doctor 25 * url : http://localhost:4000/api/auth/register/doctor
17 - * request parameter : userId, password, passwordCheck, doctorInfo 26 + * request parameter : userId, password, passwordCheck, doctorInfo(File)
18 * return : null 27 * return : null
19 */ 28 */
20 - auth.post('/register/doctor', authCtrl.doctorRegister) 29 +auth.post('/register/doctor', KoaBody, authCtrl.doctorRegister)
21 30
22 /** 31 /**
23 - * 로그인 (email type) 32 + * 로컬 로그인 (email type)
24 * url : http://localhost:4000/api/auth/login 33 * url : http://localhost:4000/api/auth/login
25 * request parameter : userId, password 34 * request parameter : userId, password
26 - * return : userId 35 + * return : token, userTypeCd
27 */ 36 */
28 auth.post('/login', authCtrl.login) 37 auth.post('/login', authCtrl.login)
29 38
30 /** 39 /**
40 + * 소셜 회원가입(Google, Naver, Kakao)
41 + * url : http://localhost:4000/api/auth/register/${socialType}
42 + * request parameter : accessToken
43 + * return : status
44 + */
45 +auth.post('/register/social/:socialType', authCtrl.socialRegister);
46 +
47 +/**
48 + * 소셜 로그인(Google, Naver, Kakao)
49 + * url : http://localhost:4000/api/auth/login/${socialType}
50 + * request parameter
51 + * return : token, userTypeCd
52 + */
53 +auth.post('/login/social/:socialType', authCtrl.socialLogin);
54 +
55 +/**
31 * 로그아웃 56 * 로그아웃
32 * url : http://localhost:4000/api/auth/logout 57 * url : http://localhost:4000/api/auth/logout
33 * request parameter : null 58 * request parameter : null
34 * return : null 59 * return : null
35 */ 60 */
36 -auth.post('/logout', authCtrl.logout) 61 +auth.post('/logout', authCtrl.logout);
62 +
63 +/**
64 + * 회원 탈퇴
65 + * url : http://localhost:4000/api/auth
66 + * request parameter : password
67 + * return : null
68 + */
69 +auth.delete('/', authCtrl.secession);
37 70
38 /** 71 /**
39 * 토큰이 유효한지 확인 72 * 토큰이 유효한지 확인
......
1 -/* eslint-disable no-undef */
2 //어플에서 약병 등록 및, 약병에 관한 정보 조회 = 여기서 mqtt통신으로 broker에 데이터를 요청한다. 1 //어플에서 약병 등록 및, 약병에 관한 정보 조회 = 여기서 mqtt통신으로 broker에 데이터를 요청한다.
3 const Bottle = require('../../models/bottle'); 2 const Bottle = require('../../models/bottle');
4 const Hub = require('../../models/hub'); 3 const Hub = require('../../models/hub');
5 const Medicine = require('../../models/medicine'); 4 const Medicine = require('../../models/medicine');
6 const User = require('../../models/user'); 5 const User = require('../../models/user');
6 +const DoctorInfo = require('../../models/doctorInfo');
7 const PatientInfo = require('../../models/patientInfo'); 7 const PatientInfo = require('../../models/patientInfo');
8 const TakeMedicineHist = require('../../models/takeMedicineHistory'); 8 const TakeMedicineHist = require('../../models/takeMedicineHistory');
9 const BottleMedicine = require('../../models/bottleMedicine'); 9 const BottleMedicine = require('../../models/bottleMedicine');
10 const Feedback = require('../../models/feedback'); 10 const Feedback = require('../../models/feedback');
11 -const Mqtt = require('../../lib/MqttModule'); 11 +const Mqtt = require('../../util/MqttModule');
12 const jwt = require('jsonwebtoken'); 12 const jwt = require('jsonwebtoken');
13 13
14 //약병 등록 14 //약병 등록
...@@ -19,6 +19,7 @@ exports.bottleConnect = async(ctx) => { ...@@ -19,6 +19,7 @@ exports.bottleConnect = async(ctx) => {
19 return; 19 return;
20 } 20 }
21 21
22 + // eslint-disable-next-line no-undef
22 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 23 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
23 const user = await User.findByUserId(userId); 24 const user = await User.findByUserId(userId);
24 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 25 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -26,7 +27,7 @@ exports.bottleConnect = async(ctx) => { ...@@ -26,7 +27,7 @@ exports.bottleConnect = async(ctx) => {
26 return; 27 return;
27 } 28 }
28 29
29 - const { bottleId, hubId } = ctx.request.body; 30 + const { bottleId, hubId, bottleNm } = ctx.request.body;
30 31
31 const isExistBottle = await Bottle.findByBottleId(bottleId); 32 const isExistBottle = await Bottle.findByBottleId(bottleId);
32 if(isExistBottle) { 33 if(isExistBottle) {
...@@ -53,7 +54,8 @@ exports.bottleConnect = async(ctx) => { ...@@ -53,7 +54,8 @@ exports.bottleConnect = async(ctx) => {
53 54
54 const newBottle = new Bottle({ 55 const newBottle = new Bottle({
55 bottleId, 56 bottleId,
56 - hubId 57 + hubId,
58 + bottleNm,
57 }); 59 });
58 60
59 const client = await Mqtt.mqttOn(hosting); 61 const client = await Mqtt.mqttOn(hosting);
...@@ -73,6 +75,7 @@ exports.bottleDisconnect = async(ctx) => { ...@@ -73,6 +75,7 @@ exports.bottleDisconnect = async(ctx) => {
73 return; 75 return;
74 } 76 }
75 77
78 + // eslint-disable-next-line no-undef
76 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 79 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
77 const user = await User.findByUserId(userId); 80 const user = await User.findByUserId(userId);
78 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 81 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -100,6 +103,7 @@ exports.bottleDisconnect = async(ctx) => { ...@@ -100,6 +103,7 @@ exports.bottleDisconnect = async(ctx) => {
100 const topic = 'bottle/' + bottleId + '/bts'; 103 const topic = 'bottle/' + bottleId + '/bts';
101 Mqtt.mqttUnsubscribe(client, topic); 104 Mqtt.mqttUnsubscribe(client, topic);
102 105
106 + await BottleMedicine.updateMany({ bottleId }, { useYn : 'N' });
103 await Bottle.deleteOne({ bottleId }); 107 await Bottle.deleteOne({ bottleId });
104 108
105 ctx.status = 204; 109 ctx.status = 204;
...@@ -114,6 +118,7 @@ exports.getBottleInfo = async(ctx) => { ...@@ -114,6 +118,7 @@ exports.getBottleInfo = async(ctx) => {
114 return; 118 return;
115 } 119 }
116 120
121 + // eslint-disable-next-line no-undef
117 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 122 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
118 const user = await User.findByUserId(userId); 123 const user = await User.findByUserId(userId);
119 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 124 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -146,21 +151,29 @@ exports.getBottleInfo = async(ctx) => { ...@@ -146,21 +151,29 @@ exports.getBottleInfo = async(ctx) => {
146 const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' }); 151 const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
147 152
148 if(bottleMedicine) { 153 if(bottleMedicine) {
154 + const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
155 + const doctorInfo = await DoctorInfo.findOne({ doctorId : bottleMedicine.doctorId });
156 +
149 const takeMedicineHist = await TakeMedicineHist 157 const takeMedicineHist = await TakeMedicineHist
150 .find({ bmId : bottleMedicine._id }) 158 .find({ bmId : bottleMedicine._id })
151 - .sort({ takeDate : 'desc' }) 159 + .sort({ takeDate : 'desc' });
152 - .populate('bmId');
153 160
154 ctx.status = 200; 161 ctx.status = 200;
155 ctx.body = { 162 ctx.body = {
156 - bottle, 163 + medicine,
164 + doctorInfo,
165 + dailyDosage : bottleMedicine.dailyDosage,
166 + totalDosage : bottleMedicine.totalDosage,
157 takeMedicineHist, 167 takeMedicineHist,
158 }; 168 };
159 169
160 } else { 170 } else {
161 ctx.status = 200; 171 ctx.status = 200;
162 ctx.body = { 172 ctx.body = {
163 - bottle, 173 + medicine : null,
174 + doctorInfo : null,
175 + dailyDosage : null,
176 + totalDosage : null,
164 takeMedicineHist : [], 177 takeMedicineHist : [],
165 } 178 }
166 } 179 }
...@@ -175,6 +188,7 @@ exports.getBottleFeedback = async ctx => { ...@@ -175,6 +188,7 @@ exports.getBottleFeedback = async ctx => {
175 return; 188 return;
176 } 189 }
177 190
191 + // eslint-disable-next-line no-undef
178 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 192 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
179 const user = await User.findByUserId(userId); 193 const user = await User.findByUserId(userId);
180 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 194 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -213,7 +227,9 @@ exports.getBottleFeedback = async ctx => { ...@@ -213,7 +227,9 @@ exports.getBottleFeedback = async ctx => {
213 .populate('bmId'); 227 .populate('bmId');
214 228
215 ctx.status = 200; 229 ctx.status = 200;
216 - ctx.body = feedbackList; 230 + ctx.body = {
231 + feedbackList
232 + };
217 } else { 233 } else {
218 ctx.status = 404; 234 ctx.status = 404;
219 ctx.body = { 235 ctx.body = {
...@@ -231,6 +247,7 @@ exports.setMedicine = async(ctx) => { ...@@ -231,6 +247,7 @@ exports.setMedicine = async(ctx) => {
231 return; 247 return;
232 } 248 }
233 249
250 + // eslint-disable-next-line no-undef
234 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 251 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
235 const user = await User.findByUserId(userId); 252 const user = await User.findByUserId(userId);
236 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 253 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -239,7 +256,7 @@ exports.setMedicine = async(ctx) => { ...@@ -239,7 +256,7 @@ exports.setMedicine = async(ctx) => {
239 } 256 }
240 257
241 const { bottleId } = ctx.params; 258 const { bottleId } = ctx.params;
242 - const { medicineId, dosage, doctorId } = ctx.request.body; 259 + const { medicineId, doctorId, dailyDosage, totalDosage, } = ctx.request.body;
243 260
244 const bottle = await Bottle.findByBottleId(bottleId); 261 const bottle = await Bottle.findByBottleId(bottleId);
245 if(!bottle) { 262 if(!bottle) {
...@@ -273,7 +290,8 @@ exports.setMedicine = async(ctx) => { ...@@ -273,7 +290,8 @@ exports.setMedicine = async(ctx) => {
273 let bottleMedicine = new BottleMedicine({ 290 let bottleMedicine = new BottleMedicine({
274 bottleId, 291 bottleId,
275 medicineId, 292 medicineId,
276 - dosage, 293 + dailyDosage,
294 + totalDosage,
277 }); 295 });
278 296
279 if(doctorId !== undefined && doctorId !== null && doctorId !== '') { 297 if(doctorId !== undefined && doctorId !== null && doctorId !== '') {
...@@ -286,14 +304,109 @@ exports.setMedicine = async(ctx) => { ...@@ -286,14 +304,109 @@ exports.setMedicine = async(ctx) => {
286 return; 304 return;
287 } 305 }
288 306
289 - bottleMedicine.setDoctorId(doctorId); 307 + await bottleMedicine.setDoctorId(doctorId);
290 } 308 }
291 309
292 await BottleMedicine.updateMany({ bottleId }, { useYn : 'N '}); 310 await BottleMedicine.updateMany({ bottleId }, { useYn : 'N '});
293 - 311 + await bottleMedicine.save();
294 - bottleMedicine.save(); 312 +
313 + ctx.status = 200;
314 +};
315 +
316 +//약 무게 세팅
317 +exports.setMedicineWeight = async ctx => {
318 + const token = ctx.req.headers.authorization;
319 + if(!token || !token.length) {
320 + ctx.status = 401;
321 + return;
322 + }
323 +
324 + // eslint-disable-next-line no-undef
325 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
326 + const user = await User.findByUserId(userId);
327 + if(!user || !user.userTypeCd || user.useYn !== 'Y') {
328 + ctx.status = 403;
329 + return;
330 + }
331 +
332 + const { bottleId } = ctx.params;
333 +
334 + const bottle = await Bottle.findByBottleId(bottleId);
335 + if(!bottle) {
336 + ctx.status = 404;
337 + ctx.body = {
338 + error : '약병 찾을 수 없음.',
339 + }
340 + return;
341 + }
342 +
343 + const hub = await Hub.findByHubId(bottle.getHubId());
344 + if(hub.getHub_UserId() !== userId) {
345 + ctx.status = 403;
346 + ctx.body = {
347 + error : '해당 허브 권한 없음',
348 + }
349 + return;
350 + }
351 +
352 +
353 + //toDo : 약병에서 가져온 무게 데이터를 이용하여, bottleMedicine값을 갱신.
354 + const client = await Mqtt.mqttOn(await hub.getHubHost());
355 + const topic = 'bottle/' + bottleId + '/stb';
356 + const message = 'weight';
357 + await Mqtt.mqttPublishMessage(client, { topic, message });
358 +
359 +
360 + // const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
361 + // const { totalWeight, totalDosage } = bottleMedicine;
362 + // if(totalDosage) bottleMedicine.setEachWeight(totalWeight / totalDosage);
295 363
296 ctx.status = 200; 364 ctx.status = 200;
365 +
366 +};
367 +
368 +//약병 이름 변경
369 +exports.setBottleName = async ctx => {
370 + const token = ctx.req.headers.authorization;
371 + if(!token || !token.length) {
372 + ctx.status = 401;
373 + return;
374 + }
375 +
376 + // eslint-disable-next-line no-undef
377 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
378 + const user = await User.findByUserId(userId);
379 + if(!user || !user.userTypeCd || user.useYn !== 'Y') {
380 + ctx.status = 403;
381 + return;
382 + }
383 +
384 + const { bottleId } = ctx.params;
385 + const { bottleNm } = ctx.request.body;
386 +
387 + const bottle = await Bottle.findByBottleId(bottleId);
388 + if(!bottle) {
389 + ctx.status = 404;
390 + ctx.body = {
391 + error : '약병 찾을 수 없음.',
392 + }
393 + return;
394 + }
395 +
396 + const hub = await Hub.findByHubId(bottle.getHubId());
397 + if(hub.getHub_UserId() !== userId) {
398 + ctx.status = 403;
399 + ctx.body = {
400 + error : '해당 허브 권한 없음',
401 + }
402 + return;
403 + }
404 +
405 + await bottle.setBottleNm(bottleNm);
406 + await bottle.save();
407 +
408 + ctx.status = 200;
409 +
297 }; 410 };
298 411
299 // //비어있는 약병에 의사를 등록한다. 412 // //비어있는 약병에 의사를 등록한다.
...@@ -344,6 +457,7 @@ exports.getHubsBottleList = async(ctx) => { ...@@ -344,6 +457,7 @@ exports.getHubsBottleList = async(ctx) => {
344 return; 457 return;
345 } 458 }
346 459
460 + // eslint-disable-next-line no-undef
347 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 461 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
348 const user = await User.findByUserId(userId); 462 const user = await User.findByUserId(userId);
349 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 463 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -385,6 +499,7 @@ exports.getAllBottleList = async ctx => { ...@@ -385,6 +499,7 @@ exports.getAllBottleList = async ctx => {
385 return; 499 return;
386 } 500 }
387 501
502 + // eslint-disable-next-line no-undef
388 const { userId } = jwt.verify(token, process.env.JWT_SECRET); 503 const { userId } = jwt.verify(token, process.env.JWT_SECRET);
389 const user = await User.findByUserId(userId); 504 const user = await User.findByUserId(userId);
390 if(!user || !user.userTypeCd || user.useYn !== 'Y') { 505 if(!user || !user.userTypeCd || user.useYn !== 'Y') {
...@@ -402,7 +517,7 @@ exports.getAllBottleList = async ctx => { ...@@ -402,7 +517,7 @@ exports.getAllBottleList = async ctx => {
402 517
403 ctx.status = 200; 518 ctx.status = 200;
404 ctx.body = { 519 ctx.body = {
405 - bottleList 520 + bottleList,
406 }; 521 };
407 522
408 }; 523 };
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -44,6 +44,22 @@ bottle.get('/feedback/:bottleId', bottleCtrl.getBottleFeedback); ...@@ -44,6 +44,22 @@ bottle.get('/feedback/:bottleId', bottleCtrl.getBottleFeedback);
44 bottle.patch('/:bottleId', bottleCtrl.setMedicine); 44 bottle.patch('/:bottleId', bottleCtrl.setMedicine);
45 45
46 /** 46 /**
47 + * 약병에 등록된 약의 무게 갱신
48 + * request parameter : bottleid
49 + * url : http://localhost:4000/api/bottle/weight/:bottleId
50 + * return : null
51 + */
52 +bottle.patch('/weight/:bottleId', bottleCtrl.setMedicineWeight);
53 +
54 +/**
55 + * 약병 이름 변경
56 + * request parameter : bottleid, bottleNm
57 + * url : http://localhost:4000/api/bottle/name/:bottleId
58 + * return : null
59 + */
60 + bottle.patch('/name/:bottleId', bottleCtrl.setBottleName);
61 +
62 +/**
47 * 비어있는 약병에 전담의 등록 63 * 비어있는 약병에 전담의 등록
48 * request parameter : bottleId, doctorId 64 * request parameter : bottleId, doctorId
49 * url : http://localhost:4000/api/bottle/doctor/:bottleId 65 * url : http://localhost:4000/api/bottle/doctor/:bottleId
......
...@@ -9,8 +9,14 @@ const Feedback = require('../../models/feedback'); ...@@ -9,8 +9,14 @@ const Feedback = require('../../models/feedback');
9 const Hub = require('../../models/hub'); 9 const Hub = require('../../models/hub');
10 const PatientInfo = require('../../models/patientInfo'); 10 const PatientInfo = require('../../models/patientInfo');
11 const DoctorInfo = require('../../models/doctorInfo'); 11 const DoctorInfo = require('../../models/doctorInfo');
12 +const PrescribeInfo = require('../../models/prescribeInfo');
13 +
12 const jwt = require('jsonwebtoken'); 14 const jwt = require('jsonwebtoken');
13 15
16 +const { uploadQrCode, getQrCodeUrl } = require('../../util/GoogleCloudStorage');
17 +const QrCodeUtil = require('../../util/QrCodeUtil');
18 +const { sendPushMessage } = require('../../util/FCM');
19 +
14 20
15 /** 21 /**
16 * 현재 로그인한 유저의 의사 정보를 가져온다 22 * 현재 로그인한 유저의 의사 정보를 가져온다
...@@ -150,7 +156,8 @@ exports.getPatientDetail = async ctx => { ...@@ -150,7 +156,8 @@ exports.getPatientDetail = async ctx => {
150 bottleId : bottle.bottleId, 156 bottleId : bottle.bottleId,
151 useYn : 'Y', 157 useYn : 'Y',
152 }); 158 });
153 - reqUserBmList.push(bm); 159 +
160 + if(bm) reqUserBmList.push(bm);
154 })); 161 }));
155 162
156 const bottleList = await Promise.all(reqUserBmList.map(async bottleMedicine => { 163 const bottleList = await Promise.all(reqUserBmList.map(async bottleMedicine => {
...@@ -280,7 +287,7 @@ exports.writeReqPatientReport = async ctx => { ...@@ -280,7 +287,7 @@ exports.writeReqPatientReport = async ctx => {
280 } 287 }
281 288
282 await patientInfo.updateInfo(info); 289 await patientInfo.updateInfo(info);
283 - patientInfo.save(); 290 + await patientInfo.save();
284 291
285 ctx.status = 200; 292 ctx.status = 200;
286 293
...@@ -335,13 +342,31 @@ exports.writeReqBottleFeedback = async ctx => { ...@@ -335,13 +342,31 @@ exports.writeReqBottleFeedback = async ctx => {
335 doctorId : userId, 342 doctorId : userId,
336 feedback, 343 feedback,
337 }); 344 });
338 - newFeedback.save(); 345 + await newFeedback.save();
346 +
347 +
348 + //feedback 알람 보내기
349 + const hub = await Hub.findOne({ hubId : bottle.hubId });
350 + const patientProfile = await Profile.findOne({ userId : hub.userId });
351 + if(patientProfile) {
352 + sendPushMessage({
353 + deviceToken : patientProfile.deviceToken,
354 + title : '의사에게 새로운 알람이 도착했습니다.',
355 + body : feedback,
356 + });
357 + }
339 358
340 ctx.status = 200; 359 ctx.status = 200;
341 360
361 +
342 }; 362 };
343 363
344 -exports.searchPatientById = async ctx => { 364 +/**
365 + * 이메일로 환자를 검색한다.
366 + * @param {*} ctx
367 + * @returns
368 + */
369 +exports.searchPatientByContact = async ctx => {
345 const token = ctx.req.headers.authorization; 370 const token = ctx.req.headers.authorization;
346 if (!token || !token.length) { 371 if (!token || !token.length) {
347 ctx.status = 401; 372 ctx.status = 401;
...@@ -359,22 +384,19 @@ exports.searchPatientById = async ctx => { ...@@ -359,22 +384,19 @@ exports.searchPatientById = async ctx => {
359 return; 384 return;
360 } 385 }
361 386
362 - const { patientId } = ctx.params; 387 + const { contact } = ctx.params;
363 - const patient = await User.findByUserId(patientId); 388 + const patientProfile = await Profile.findOne({ contact, useYn : 'Y' });
364 - if(!patient || patient.useYn !== 'Y') {
365 - ctx.status = 404;
366 - ctx.body = {
367 - error : '존재하지 않는 회원',
368 - };
369 - return;
370 - }
371 389
372 - const patientProfile = await Profile.findOne({ userId : patientId }); 390 + const patientInfo = {
391 + userId : patientProfile.userId,
392 + userNm : patientProfile.userNm,
393 + birth : patientProfile.birth,
394 + contact: patientProfile.contact,
395 + };
373 396
374 ctx.status = 200; 397 ctx.status = 200;
375 ctx.body = { 398 ctx.body = {
376 - patientNm : patientProfile.userNm, 399 + patientInfo,
377 - patientId,
378 }; 400 };
379 }; 401 };
380 402
...@@ -427,8 +449,17 @@ exports.registerNewPatient = async ctx => { ...@@ -427,8 +449,17 @@ exports.registerNewPatient = async ctx => {
427 useYn : 'W', 449 useYn : 'W',
428 }); 450 });
429 451
430 - patientInfo.updateInfo('환자 등록 요청'); 452 + await patientInfo.updateInfo('환자 등록 요청');
431 - patientInfo.save(); 453 + await patientInfo.save();
454 +
455 + const profile = await Profile.findByUserId(patientId);
456 + const { deviceToken } = profile;
457 +
458 + sendPushMessage({
459 + deviceToken,
460 + title : '새로운 의사 등록 요청이 왔습니다.',
461 + body : '어플리케이션을 실행해 확인하세요.',
462 + });
432 463
433 ctx.status = 200; 464 ctx.status = 200;
434 465
...@@ -477,8 +508,113 @@ exports.removeReqPatient = async ctx => { ...@@ -477,8 +508,113 @@ exports.removeReqPatient = async ctx => {
477 } 508 }
478 509
479 await patientInfo.setUseYn('N') 510 await patientInfo.setUseYn('N')
480 - patientInfo.save(); 511 + await patientInfo.save();
481 512
482 ctx.status = 200; 513 ctx.status = 200;
483 514
515 +};
516 +
517 +/**
518 + * 특정 환자에게 특정 약을 처방한다
519 + * @param {*} ctx
520 + * http methods : post
521 + */
522 +exports.prescribeMedicine = async ctx => {
523 + const token = ctx.req.headers.authorization;
524 + if(!token || !token.length) {
525 + ctx.status = 401;
526 + return;
527 + }
528 +
529 + // eslint-disable-next-line no-undef
530 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
531 + const user = await User.findByUserId(userId);
532 + if(!user || user.userTypeCd !== 'DOCTOR') {
533 + ctx.status = 403;
534 + ctx.body = {
535 + error : '권한 없는 유저',
536 + };
537 + return;
538 + }
539 +
540 +
541 + const {
542 + patientId,
543 + medicineId,
544 + dailyDosage,
545 + totalDosage,
546 + } = ctx.request.body;
547 +
548 +
549 + const patientInfo = await PatientInfo.findOne({
550 + patientId,
551 + doctorId : userId,
552 + useYn : 'Y',
553 + })
554 + if(!patientInfo) {
555 + ctx.status = 403;
556 + ctx.body = {
557 + error : '권한 없는 환자',
558 + };
559 + return;
560 + }
561 +
562 + const medicine = await Medicine.findOne({ medicineId });
563 + if(!medicine) {
564 + ctx.status = 404;
565 + ctx.body = {
566 + error : '존재하지 않는 약',
567 + };
568 + return;
569 + }
570 +
571 +
572 + const qrCodeResult = await QrCodeUtil.generateQrCode_prescribe({
573 + medicine,
574 + dailyDosage,
575 + totalDosage,
576 + patientId,
577 + doctorId : userId,
578 + });
579 + if(!qrCodeResult) {
580 + ctx.status = 400;
581 + ctx.body = {
582 + error : 'QR 코드 생성 에러',
583 + };
584 + return;
585 + }
586 +
587 + const qrCodeUrl = await uploadQrCode(qrCodeResult);
588 + if(!qrCodeUrl) {
589 + ctx.status = 400;
590 + ctx.body = {
591 + error : 'QR 코드 생성 후 서버 업로드 에러',
592 + };
593 + return;
594 + }
595 +
596 + const prescribeInfo = new PrescribeInfo({
597 + doctorId : userId,
598 + patientId,
599 + dailyDosage,
600 + totalDosage,
601 + medicineId,
602 + qrCodeUrl,
603 + });
604 + await prescribeInfo.save();
605 +
606 + //특이사항에 처방기록 저장
607 + await patientInfo.updateInfo(`${medicine.name}, 하루 ${dailyDosage}회분 처방, 총 ${totalDosage}회분 처방`);
608 + await patientInfo.save();
609 +
610 +
611 + const { qrCodeFileName } = qrCodeResult;
612 + const qrCode = await getQrCodeUrl({ qrCodeFileName });
613 +
614 + ctx.status = 200;
615 + ctx.body = {
616 + qrCode,
617 + };
618 +
619 +
484 }; 620 };
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -7,7 +7,7 @@ const doctor = new Router(); ...@@ -7,7 +7,7 @@ const doctor = new Router();
7 /** 7 /**
8 * 현재 로그인한 유저(의사)의 정보를 가져옴. 8 * 현재 로그인한 유저(의사)의 정보를 가져옴.
9 * request parameter : token 9 * request parameter : token
10 - * url : http://localhost:4000/doctor/ 10 + * url : http://localhost:4000/api/doctor/
11 * return : doctor's Info 11 * return : doctor's Info
12 */ 12 */
13 doctor.get('/', doctorCtrl.getDoctorsInfo); 13 doctor.get('/', doctorCtrl.getDoctorsInfo);
...@@ -15,7 +15,7 @@ doctor.get('/', doctorCtrl.getDoctorsInfo); ...@@ -15,7 +15,7 @@ doctor.get('/', doctorCtrl.getDoctorsInfo);
15 /** 15 /**
16 * 현재 로그인한 유저(의사)의 관리 환자 목록을 가져옴 16 * 현재 로그인한 유저(의사)의 관리 환자 목록을 가져옴
17 * request parameter 17 * request parameter
18 - * url : http://localhost:4000/doctor/patient 18 + * url : http://localhost:4000/api/doctor/patient
19 * return : patient List 19 * return : patient List
20 */ 20 */
21 doctor.get('/patient', doctorCtrl.getPatientList); 21 doctor.get('/patient', doctorCtrl.getPatientList);
...@@ -23,7 +23,7 @@ doctor.get('/patient', doctorCtrl.getPatientList); ...@@ -23,7 +23,7 @@ doctor.get('/patient', doctorCtrl.getPatientList);
23 /** 23 /**
24 * 현재 로그인한 유저(의사)의 관리 환자 상세 정보를 가져옴 24 * 현재 로그인한 유저(의사)의 관리 환자 상세 정보를 가져옴
25 * request parameter : patient Id 25 * request parameter : patient Id
26 - * url : http://localhost:4000/doctor/patient/:patientId 26 + * url : http://localhost:4000/api/doctor/patient/:patientId
27 * return : patient Detail 27 * return : patient Detail
28 */ 28 */
29 doctor.get('/patient/:patientId', doctorCtrl.getPatientDetail); 29 doctor.get('/patient/:patientId', doctorCtrl.getPatientDetail);
...@@ -31,7 +31,7 @@ doctor.get('/patient/:patientId', doctorCtrl.getPatientDetail); ...@@ -31,7 +31,7 @@ doctor.get('/patient/:patientId', doctorCtrl.getPatientDetail);
31 /** 31 /**
32 * 현재 로그인한 유저(의사)의 관리 약병 상세 정보를 가져옴 32 * 현재 로그인한 유저(의사)의 관리 약병 상세 정보를 가져옴
33 * request parameter : bottle Id 33 * request parameter : bottle Id
34 - * url : http://localhost:4000/doctor/bottle/:bottleId 34 + * url : http://localhost:4000/api/doctor/bottle/:bottleId
35 * return : bottle Detail 35 * return : bottle Detail
36 */ 36 */
37 doctor.get('/bottle/:bottleId', doctorCtrl.getBottleDetail); 37 doctor.get('/bottle/:bottleId', doctorCtrl.getBottleDetail);
...@@ -39,7 +39,7 @@ doctor.get('/bottle/:bottleId', doctorCtrl.getBottleDetail); ...@@ -39,7 +39,7 @@ doctor.get('/bottle/:bottleId', doctorCtrl.getBottleDetail);
39 /** 39 /**
40 * 현재 로그인한 유저(의사)의 특정 관리 환자의 특이사항을 기록함 40 * 현재 로그인한 유저(의사)의 특정 관리 환자의 특이사항을 기록함
41 * request parameter : reqUserId, info 41 * request parameter : reqUserId, info
42 - * url : http://localhost:4000/doctor/patient 42 + * url : http://localhost:4000/api/doctor/patient
43 * return : null 43 * return : null
44 */ 44 */
45 doctor.patch('/patient', doctorCtrl.writeReqPatientReport); 45 doctor.patch('/patient', doctorCtrl.writeReqPatientReport);
...@@ -47,7 +47,7 @@ doctor.patch('/patient', doctorCtrl.writeReqPatientReport); ...@@ -47,7 +47,7 @@ doctor.patch('/patient', doctorCtrl.writeReqPatientReport);
47 /** 47 /**
48 * 현재 로그인한 유저(의사)의 특정 관리 환자의 약병의 피드백을 등록함. 48 * 현재 로그인한 유저(의사)의 특정 관리 환자의 약병의 피드백을 등록함.
49 * request parameter : bottleId, fdbType, feedback 49 * request parameter : bottleId, fdbType, feedback
50 - * url : http://localhost:4000/doctor/bottle 50 + * url : http://localhost:4000/api/doctor/bottle
51 * return : null 51 * return : null
52 */ 52 */
53 doctor.post('/bottle', doctorCtrl.writeReqBottleFeedback); 53 doctor.post('/bottle', doctorCtrl.writeReqBottleFeedback);
...@@ -55,15 +55,15 @@ doctor.post('/bottle', doctorCtrl.writeReqBottleFeedback); ...@@ -55,15 +55,15 @@ doctor.post('/bottle', doctorCtrl.writeReqBottleFeedback);
55 /** 55 /**
56 * 현재 로그인한 유저(의사)가 이메일로 유저를 검색함 56 * 현재 로그인한 유저(의사)가 이메일로 유저를 검색함
57 * request parameter : patientId 57 * request parameter : patientId
58 - * url : http://localhost:4000/api/doctor/patient/search/:patientId 58 + * url : http://localhost:4000/api/api/doctor/patient/search/:patientId
59 * return : patient Info(simple) 59 * return : patient Info(simple)
60 */ 60 */
61 -doctor.get('/patient/search/:patientId', doctorCtrl.searchPatientById); 61 +doctor.get('/patient/search/:contact', doctorCtrl.searchPatientByContact);
62 62
63 /** 63 /**
64 * 현재 로그인한 유저(의사)의 관리 환자를 등록함. 64 * 현재 로그인한 유저(의사)의 관리 환자를 등록함.
65 * request parameter : patientId 65 * request parameter : patientId
66 - * url : http://localhost:4000/doctor/patient 66 + * url : http://localhost:4000/api/doctor/patient
67 * return : null 67 * return : null
68 */ 68 */
69 doctor.post('/patient', doctorCtrl.registerNewPatient); 69 doctor.post('/patient', doctorCtrl.registerNewPatient);
...@@ -71,11 +71,18 @@ doctor.post('/patient', doctorCtrl.registerNewPatient); ...@@ -71,11 +71,18 @@ doctor.post('/patient', doctorCtrl.registerNewPatient);
71 /** 71 /**
72 * 현재 로그인한 유저(의사)의 특정 관리 환자를 삭제함. 72 * 현재 로그인한 유저(의사)의 특정 관리 환자를 삭제함.
73 * request parameter : patientId 73 * request parameter : patientId
74 - * url : http://localhost:4000/doctor/patient/:patientId 74 + * url : http://localhost:4000/api/doctor/patient/:patientId
75 * return : null 75 * return : null
76 */ 76 */
77 doctor.delete('/patient/:patientId', doctorCtrl.removeReqPatient); 77 doctor.delete('/patient/:patientId', doctorCtrl.removeReqPatient);
78 78
79 +/**
80 + * 의사가 관리하는 환자에 대해 특정 약을 처방함
81 + * request paramter : patientId, medicineId, dosage
82 + * url : http://localhost:4000/api/doctor/prescribe
83 + * return : null
84 + */
85 +doctor.post('/prescribe', doctorCtrl.prescribeMedicine);
86 +
79 87
80 -module.exports = doctor;
81 -
...\ No newline at end of file ...\ No newline at end of file
88 +module.exports = doctor;
...\ No newline at end of file ...\ No newline at end of file
......
1 //허브(Mqtt Broker)등록 및 삭제 1 //허브(Mqtt Broker)등록 및 삭제
2 const Hub = require('../../models/hub'); 2 const Hub = require('../../models/hub');
3 +const Bottle = require('../../models/bottle');
3 const User = require('../../models/user'); 4 const User = require('../../models/user');
4 -const Mqtt = require('../../lib/MqttModule'); 5 +const Mqtt = require('../../util/MqttModule');
5 -const DataProcess = require('../../lib/DataProcess'); 6 +const DataProcess = require('../../util/DataProcess');
6 const jwt = require('jsonwebtoken'); 7 const jwt = require('jsonwebtoken');
8 +const BottleMedicine = require('../../models/bottleMedicine');
7 9
10 +//허브 연결
8 exports.hubConnect = async (ctx) => { 11 exports.hubConnect = async (ctx) => {
9 const token = ctx.req.headers.authorization; 12 const token = ctx.req.headers.authorization;
10 if(!token || !token.length) { 13 if(!token || !token.length) {
...@@ -20,7 +23,7 @@ exports.hubConnect = async (ctx) => { ...@@ -20,7 +23,7 @@ exports.hubConnect = async (ctx) => {
20 return; 23 return;
21 } 24 }
22 25
23 - const { hubId, host, port } = ctx.request.body; 26 + const { hubId, host, hubNm, } = ctx.request.body;
24 27
25 const isExistHub = await Hub.findByHubId(hubId); 28 const isExistHub = await Hub.findByHubId(hubId);
26 if(isExistHub) { 29 if(isExistHub) {
...@@ -30,7 +33,7 @@ exports.hubConnect = async (ctx) => { ...@@ -30,7 +33,7 @@ exports.hubConnect = async (ctx) => {
30 33
31 const hosting = { 34 const hosting = {
32 host, 35 host,
33 - port 36 + port : "1883",
34 }; 37 };
35 38
36 Mqtt.mqttOn(hosting, DataProcess.dataPublish); 39 Mqtt.mqttOn(hosting, DataProcess.dataPublish);
...@@ -38,7 +41,8 @@ exports.hubConnect = async (ctx) => { ...@@ -38,7 +41,8 @@ exports.hubConnect = async (ctx) => {
38 const hub = new Hub({ 41 const hub = new Hub({
39 hubId, 42 hubId,
40 hosting, 43 hosting,
41 - userId 44 + userId,
45 + hubNm,
42 }); 46 });
43 47
44 await hub.save(); 48 await hub.save();
...@@ -47,6 +51,49 @@ exports.hubConnect = async (ctx) => { ...@@ -47,6 +51,49 @@ exports.hubConnect = async (ctx) => {
47 51
48 }; 52 };
49 53
54 +//허브 연결 해제
55 +exports.hubDisconnect = async(ctx) => {
56 + const token = ctx.req.headers.authorization;
57 + if(!token || !token.length) {
58 + ctx.status = 401;
59 + return;
60 + }
61 +
62 + // eslint-disable-next-line no-undef
63 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
64 + const user = await User.findByUserId(userId);
65 + if(!user || !user.userTypeCd || user.useYn !== 'Y') {
66 + ctx.status = 403;
67 + return;
68 + }
69 +
70 + const { hubId } = ctx.params;
71 +
72 + const hub = await Hub.findByHubId(hubId);
73 + if(!hub) {
74 + ctx.status = 404;
75 + return;
76 + }
77 + if(hub.getHub_UserId() !== userId) {
78 + ctx.status = 403;
79 + return;
80 + }
81 +
82 + const hosting = await hub.getHubHost();
83 + Mqtt.mqttOff(hosting);
84 +
85 + const bottleList = await Bottle.find({ hubId });
86 + await Promise.all(bottleList.map(async bottle => {
87 + await BottleMedicine.updateMany({ bottleId : bottle.bottleId }, { useYn : 'N' });
88 + }));
89 +
90 + await Bottle.deleteMany({ hubId });
91 + await Hub.deleteOne({ hubId });
92 +
93 + ctx.status = 204;
94 +};
95 +
96 +//허브 정보 조회
50 exports.getHubList = async(ctx) => { 97 exports.getHubList = async(ctx) => {
51 const token = ctx.req.headers.authorization; 98 const token = ctx.req.headers.authorization;
52 if(!token || !token.length) { 99 if(!token || !token.length) {
...@@ -74,7 +121,8 @@ exports.getHubList = async(ctx) => { ...@@ -74,7 +121,8 @@ exports.getHubList = async(ctx) => {
74 }; 121 };
75 }; 122 };
76 123
77 -exports.hubDisconnect = async(ctx) => { 124 +//허브 이름 변경
125 +exports.setHubName = async ctx => {
78 const token = ctx.req.headers.authorization; 126 const token = ctx.req.headers.authorization;
79 if(!token || !token.length) { 127 if(!token || !token.length) {
80 ctx.status = 401; 128 ctx.status = 401;
...@@ -90,21 +138,9 @@ exports.hubDisconnect = async(ctx) => { ...@@ -90,21 +138,9 @@ exports.hubDisconnect = async(ctx) => {
90 } 138 }
91 139
92 const { hubId } = ctx.params; 140 const { hubId } = ctx.params;
141 + const { hubNm } = ctx.request.body;
93 142
94 - const hub = await Hub.findByHubId(hubId); 143 + await Hub.updateOne({ hubId }, { hubNm });
95 - if(!hub) {
96 - ctx.status = 404;
97 - return;
98 - }
99 - if(hub.getHub_UserId() !== userId) {
100 - ctx.status = 403;
101 - return;
102 - }
103 144
104 - const hosting = await hub.getHubHost();
105 - Mqtt.mqttOff(hosting);
106 -
107 - await Hub.deleteOne({ hubId });
108 -
109 - ctx.status = 204;
110 -};
...\ No newline at end of file ...\ No newline at end of file
145 + ctx.status = 200;
146 +};
......
...@@ -12,6 +12,14 @@ const hub = new Router(); ...@@ -12,6 +12,14 @@ const hub = new Router();
12 hub.post('/', hubCtrl.hubConnect); 12 hub.post('/', hubCtrl.hubConnect);
13 13
14 /** 14 /**
15 + * 허브 등록 해제
16 + * request parameter : x
17 + * url : http://localhost:4000/api/hub/:hubId
18 + * return : null
19 + */
20 + hub.delete('/:hubId', hubCtrl.hubDisconnect);
21 +
22 +/**
15 * 로그인한 유저의 허브 목록 가져오기 23 * 로그인한 유저의 허브 목록 가져오기
16 * request parameter : X 24 * request parameter : X
17 * url : http://localhost:4000/api/hub 25 * url : http://localhost:4000/api/hub
...@@ -20,11 +28,13 @@ hub.post('/', hubCtrl.hubConnect); ...@@ -20,11 +28,13 @@ hub.post('/', hubCtrl.hubConnect);
20 hub.get('/', hubCtrl.getHubList); 28 hub.get('/', hubCtrl.getHubList);
21 29
22 /** 30 /**
23 - * 허브 등록 해제 31 + * 로그인한 유저의 특정 허브 이름 변경
24 - * request parameter : x 32 + * request parameter : hubId, hubNm
25 * url : http://localhost:4000/api/hub/:hubId 33 * url : http://localhost:4000/api/hub/:hubId
26 * return : null 34 * return : null
27 */ 35 */
28 -hub.delete('/:hubId', hubCtrl.hubDisconnect); 36 +hub.patch('/:hubId', hubCtrl.setHubName);
37 +
38 +
29 39
30 module.exports = hub; 40 module.exports = hub;
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -7,6 +7,7 @@ const hub = require('./hub'); ...@@ -7,6 +7,7 @@ const hub = require('./hub');
7 const medicine = require('./medicine'); 7 const medicine = require('./medicine');
8 const doctor = require('./doctor'); 8 const doctor = require('./doctor');
9 const manage = require('./manage'); 9 const manage = require('./manage');
10 +const test = require('./test');
10 11
11 const api = new Router(); 12 const api = new Router();
12 13
...@@ -17,5 +18,6 @@ api.use('/hub', hub.routes()); ...@@ -17,5 +18,6 @@ api.use('/hub', hub.routes());
17 api.use('/medicine', medicine.routes()); 18 api.use('/medicine', medicine.routes());
18 api.use('/doctor', doctor.routes()); 19 api.use('/doctor', doctor.routes());
19 api.use('/manage', manage.routes()); 20 api.use('/manage', manage.routes());
21 +api.use('/test', test.routes());
20 22
21 module.exports = api; 23 module.exports = api;
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -12,6 +12,14 @@ const manage = new Router(); ...@@ -12,6 +12,14 @@ const manage = new Router();
12 manage.get('/doctor', manageCtrl.getDoctorRegReqList); 12 manage.get('/doctor', manageCtrl.getDoctorRegReqList);
13 13
14 /** 14 /**
15 + * 의사 회원탈퇴 요청을 한 회원들의 목록을 리턴
16 + * request parameter : null
17 + * url : http://localhost:4000/api/manage/doctor/sec
18 + * return : doctor request List
19 + */
20 + manage.get('/doctor/secession', manageCtrl.getDoctorSecReqList);
21 +
22 +/**
15 * 의사 회원가입 요청을 한 특정 회원의 상세정보 확인 23 * 의사 회원가입 요청을 한 특정 회원의 상세정보 확인
16 * request parameter : doctor Id 24 * request parameter : doctor Id
17 * url : http://localhost:4000/api/manage/doctor/:doctorId 25 * url : http://localhost:4000/api/manage/doctor/:doctorId
...@@ -25,7 +33,7 @@ manage.get('/doctor/:doctorId', manageCtrl.getDoctorRegReqDetail); ...@@ -25,7 +33,7 @@ manage.get('/doctor/:doctorId', manageCtrl.getDoctorRegReqDetail);
25 * url : http://localhost:4000/api/manage/doctor/accept 33 * url : http://localhost:4000/api/manage/doctor/accept
26 * return : null 34 * return : null
27 */ 35 */
28 -manage.post('/doctor/accept', manageCtrl.acceptDoctorRegReq); 36 +manage.patch('/doctor/accept', manageCtrl.acceptDoctorRegReq);
29 37
30 /** 38 /**
31 * 의사 요청을 한 회원을 거절한다. 39 * 의사 요청을 한 회원을 거절한다.
...@@ -33,7 +41,15 @@ manage.post('/doctor/accept', manageCtrl.acceptDoctorRegReq); ...@@ -33,7 +41,15 @@ manage.post('/doctor/accept', manageCtrl.acceptDoctorRegReq);
33 * url : http://localhost:4000/api/manange/doctor/reject 41 * url : http://localhost:4000/api/manange/doctor/reject
34 * return : null 42 * return : null
35 */ 43 */
36 -manage.post('/doctor/reject', manageCtrl.rejectDoctorRegReq); 44 +manage.patch('/doctor/reject', manageCtrl.rejectDoctorRegReq);
45 +
46 +/**
47 + * 의사 탈퇴 요청을 수락한다.
48 + * request parameter : doctor Id
49 + * url : http://localhost:4000/api/manange/doctor/secession
50 + * return : null
51 + */
52 + manage.patch('/doctor/secession', manageCtrl.acceptDoctorSecReq);
37 53
38 /** 54 /**
39 * 의사 요청을 한 회원의 자격 번호가 유효한지 검증한다 55 * 의사 요청을 한 회원의 자격 번호가 유효한지 검증한다
......
1 const User = require('../../models/user'); 1 const User = require('../../models/user');
2 const DoctorInfo = require('../../models/doctorInfo'); 2 const DoctorInfo = require('../../models/doctorInfo');
3 -const Profile = require('../../models/profile'); 3 +const PatientInfo = require('../../models/patientInfo');
4 const jwt = require('jsonwebtoken'); 4 const jwt = require('jsonwebtoken');
5 +const { viewDoctorLicense } = require('../../util/GoogleCloudStorage');
6 +
5 7
6 /** 8 /**
7 * 의사 회원가입을 요청한 회원 리스트를 확인한다. 9 * 의사 회원가입을 요청한 회원 리스트를 확인한다.
...@@ -25,13 +27,54 @@ exports.getDoctorRegReqList = async ctx => { ...@@ -25,13 +27,54 @@ exports.getDoctorRegReqList = async ctx => {
25 } 27 }
26 28
27 try { 29 try {
28 - const doctorRegReqList = await DoctorInfo.find({ 30 + const doctorList = await DoctorInfo.find({
29 useYn : 'W', 31 useYn : 'W',
30 }); 32 });
31 33
32 ctx.status = 200; 34 ctx.status = 200;
33 ctx.body = { 35 ctx.body = {
34 - doctorRegReqList 36 + doctorList
37 + };
38 +
39 + } catch(e) {
40 + ctx.status = 500;
41 + ctx.body = {
42 + error : '알 수 없는 에러가 발생했습니다.',
43 + };
44 + console.log(e);
45 + return;
46 + }
47 +};
48 +
49 +/**
50 + * 의사 회원탈퇴를 요청한 회원 리스트를 확인한다.
51 + * http methods : get
52 + * @param {*} ctx
53 + * @returns
54 + */
55 + exports.getDoctorSecReqList = async ctx => {
56 + const token = ctx.req.headers.authorization;
57 + if (!token || !token.length) {
58 + ctx.status = 401;
59 + return;
60 + }
61 +
62 + // eslint-disable-next-line no-undef
63 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
64 + const user = await User.findByUserId(userId);
65 + if(!user || user.userTypeCd !== 'MANAGER' || user.useYn !== 'Y') {
66 + ctx.status = 403;
67 + return;
68 + }
69 +
70 + try {
71 + const doctorList = await DoctorInfo.find({
72 + useYn : 'WS',
73 + });
74 +
75 + ctx.status = 200;
76 + ctx.body = {
77 + doctorList
35 }; 78 };
36 79
37 } catch(e) { 80 } catch(e) {
...@@ -40,6 +83,7 @@ exports.getDoctorRegReqList = async ctx => { ...@@ -40,6 +83,7 @@ exports.getDoctorRegReqList = async ctx => {
40 error : '알 수 없는 에러가 발생했습니다.', 83 error : '알 수 없는 에러가 발생했습니다.',
41 }; 84 };
42 console.log(e); 85 console.log(e);
86 + return;
43 } 87 }
44 }; 88 };
45 89
...@@ -108,9 +152,20 @@ exports.getDoctorRegReqDetail = async ctx => { ...@@ -108,9 +152,20 @@ exports.getDoctorRegReqDetail = async ctx => {
108 return; 152 return;
109 } 153 }
110 154
155 +
156 + const doctorLicense = await viewDoctorLicense({
157 + doctorInfo
158 + });
159 +
111 ctx.status = 200; 160 ctx.status = 200;
112 ctx.body = { 161 ctx.body = {
113 - doctorInfo, 162 + doctorInfo : {
163 + ...doctorInfo._doc,
164 + info : {
165 + ...doctorInfo.info,
166 + doctorLicense,
167 + },
168 + },
114 }; 169 };
115 170
116 } catch (e) { 171 } catch (e) {
...@@ -118,12 +173,14 @@ exports.getDoctorRegReqDetail = async ctx => { ...@@ -118,12 +173,14 @@ exports.getDoctorRegReqDetail = async ctx => {
118 ctx.body = { 173 ctx.body = {
119 error : '알 수 없는 에러가 발생했습니다.', 174 error : '알 수 없는 에러가 발생했습니다.',
120 }; 175 };
176 + console.log(e);
177 + return;
121 } 178 }
122 }; 179 };
123 180
124 /** 181 /**
125 * 의사 요청이 온 회원을 수락한다. 182 * 의사 요청이 온 회원을 수락한다.
126 - * http methods : post 183 + * http methods : patch
127 * @param {*} ctx 184 * @param {*} ctx
128 * @returns 185 * @returns
129 */ 186 */
...@@ -143,7 +200,7 @@ exports.acceptDoctorRegReq = async ctx => { ...@@ -143,7 +200,7 @@ exports.acceptDoctorRegReq = async ctx => {
143 } 200 }
144 201
145 try { 202 try {
146 - const { doctorId } = ctx.request.body; 203 + const { doctorId, validateDoctorLicense } = ctx.request.body;
147 const doctor = await User.findOne({ userId : doctorId }); 204 const doctor = await User.findOne({ userId : doctorId });
148 if(!doctor) { 205 if(!doctor) {
149 ctx.status = 404; 206 ctx.status = 404;
...@@ -169,17 +226,37 @@ exports.acceptDoctorRegReq = async ctx => { ...@@ -169,17 +226,37 @@ exports.acceptDoctorRegReq = async ctx => {
169 error : '의사로 가입된 회원이 아닙니다.', 226 error : '의사로 가입된 회원이 아닙니다.',
170 }; 227 };
171 return; 228 return;
229 + } else if(!validateDoctorLicense) {
230 + ctx.status = 400;
231 + ctx.body = {
232 + error : '유효한 자격 번호가 아닙니다.',
233 + };
234 + return;
235 + }
236 +
237 + const existDoctorInfo = await DoctorInfo.findOne({
238 + 'info.validateDoctorLicense' : validateDoctorLicense
239 + });
240 + if(existDoctorInfo) {
241 + ctx.status = 403;
242 + ctx.body = {
243 + error : '중복된 자격번호입니다.',
244 + };
245 + return;
172 } 246 }
173 247
248 +
174 const doctorInfo = await DoctorInfo.findOne({ 249 const doctorInfo = await DoctorInfo.findOne({
175 doctorId, 250 doctorId,
176 useYn : 'W', 251 useYn : 'W',
177 }); 252 });
178 253
179 - doctor.setUseYn('Y'); 254 + await doctor.setUseYn('Y');
180 - doctor.save(); 255 + await doctor.save();
181 - doctorInfo.setUseYn('Y'); 256 +
182 - doctorInfo.save(); 257 + await doctorInfo.setUseYn('Y');
258 + await doctorInfo.setValidateDoctorLicense(validateDoctorLicense);
259 + await doctorInfo.save();
183 260
184 ctx.status = 200; 261 ctx.status = 200;
185 262
...@@ -189,12 +266,13 @@ exports.acceptDoctorRegReq = async ctx => { ...@@ -189,12 +266,13 @@ exports.acceptDoctorRegReq = async ctx => {
189 error : '알 수 없는 에러가 발생했습니다.', 266 error : '알 수 없는 에러가 발생했습니다.',
190 }; 267 };
191 console.log(e); 268 console.log(e);
269 + return;
192 } 270 }
193 }; 271 };
194 272
195 /** 273 /**
196 * 의사 요청이 온 회원을 거절한다. 274 * 의사 요청이 온 회원을 거절한다.
197 - * http methods : post 275 + * http methods : patch
198 * @param {*} ctx 276 * @param {*} ctx
199 * @returns 277 * @returns
200 */ 278 */
...@@ -242,16 +320,79 @@ exports.acceptDoctorRegReq = async ctx => { ...@@ -242,16 +320,79 @@ exports.acceptDoctorRegReq = async ctx => {
242 return; 320 return;
243 } 321 }
244 322
323 + await DoctorInfo.updateOne({ doctorId, useYn : 'W' }, { useYn : 'N' });
245 324
246 - const doctorInfo = await DoctorInfo.findOne({ 325 + await doctor.setUseYn('N');
247 - doctorId, 326 + await doctor.save();
248 - useYn : 'W',
249 - });
250 327
251 - doctor.setUseYn('N'); 328 + ctx.status = 200;
252 - doctor.save(); 329 +
253 - doctorInfo.setUseYn('N'); 330 + } catch(e) {
254 - doctorInfo.save(); 331 + ctx.status = 500;
332 + ctx.body = {
333 + error : '알 수 없는 에러가 발생했습니다.',
334 + };
335 + console.log(e);
336 + return;
337 + }
338 +};
339 +
340 +/**
341 + * 의사 회원탈퇴 요청을 수락한다.
342 + * http methods : patch
343 + * @param {*} ctx
344 + * @returns
345 + */
346 + exports.acceptDoctorSecReq = async ctx => {
347 + const token = ctx.req.headers.authorization;
348 + if (!token || !token.length) {
349 + ctx.status = 401;
350 + return;
351 + }
352 +
353 + // eslint-disable-next-line no-undef
354 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
355 + const user = await User.findByUserId(userId);
356 + if(!user || user.userTypeCd !== 'MANAGER' || user.useYn !== 'Y') {
357 + ctx.status = 403;
358 + return;
359 + }
360 +
361 + try {
362 + const { doctorId } = ctx.request.body;
363 + const doctor = await User.findOne({ userId : doctorId });
364 + if(!doctor) {
365 + ctx.status = 404;
366 + ctx.body = {
367 + error : '존재하지 않는 회원입니다.',
368 + };
369 + return;
370 + } else if(doctor.useYn === 'N') {
371 + ctx.status = 400;
372 + ctx.body = {
373 + error : '탈퇴된 회원입니다.',
374 + };
375 + return;
376 + } else if(doctor.useYn === 'Y') {
377 + ctx.status = 400;
378 + ctx.body = {
379 + error : '이미 가입이 완료된 의사입니다.',
380 + };
381 + return;
382 + } else if(doctor.userTypeCd !== 'DOCTOR') {
383 + ctx.status = 400;
384 + ctx.body = {
385 + error : '의사로 가입된 회원이 아닙니다.',
386 + };
387 + return;
388 + }
389 +
390 +
391 + await DoctorInfo.updateOne({ doctorId, useYn : 'WS' }, { useYn : 'N' });
392 + await PatientInfo.updateMany({ doctorId : userId, useYn : 'WS' }, { useYn : 'N' });
393 +
394 + await doctor.setUseYn('N');
395 + await doctor.save();
255 396
256 ctx.status = 200; 397 ctx.status = 200;
257 398
...@@ -261,9 +402,11 @@ exports.acceptDoctorRegReq = async ctx => { ...@@ -261,9 +402,11 @@ exports.acceptDoctorRegReq = async ctx => {
261 error : '알 수 없는 에러가 발생했습니다.', 402 error : '알 수 없는 에러가 발생했습니다.',
262 }; 403 };
263 console.log(e); 404 console.log(e);
405 + return;
264 } 406 }
265 }; 407 };
266 408
409 +
267 /** 410 /**
268 * 회원가입을 요청한 의사의 유효한 자격 번호인지를 검증한다. 411 * 회원가입을 요청한 의사의 유효한 자격 번호인지를 검증한다.
269 * @param {*} ctx 412 * @param {*} ctx
...@@ -284,12 +427,20 @@ exports.validateDoctorLicense = async ctx => { ...@@ -284,12 +427,20 @@ exports.validateDoctorLicense = async ctx => {
284 return; 427 return;
285 } 428 }
286 429
287 - const { doctorLicense } = ctx.request.body; 430 + const { validateDoctorLicense } = ctx.request.body;
288 - const doctorInfo = await DoctorInfo.find({ 'info.doctorLicense' : doctorLicense }); 431 + if(!validateDoctorLicense) {
432 + ctx.status = 404;
433 + ctx.body = {
434 + error : '유효한 자격번호를 입력해주세요',
435 + };
436 + return;
437 + }
438 +
439 + const doctorInfo = await DoctorInfo.findOne({ 'info.validateDoctorLicense' : validateDoctorLicense });
289 440
290 ctx.status = 200; 441 ctx.status = 200;
291 ctx.body = { 442 ctx.body = {
292 - result : doctorInfo.length > 1 ? false : true, 443 + result : doctorInfo ? false : true,
293 }; 444 };
294 445
295 }; 446 };
......
1 +//test용 api입니다.
2 +const Router = require('koa-router');
3 +const testCtrl = require('./test.ctrl');
4 +
5 +const test = new Router();
6 +
7 +//푸쉬메시지 테스트
8 +test.post('/fcm', testCtrl.sendFcmMessage);
9 +
10 +
11 +
12 +module.exports = test;
1 +//테스트용 api service
2 +const { sendPushMessage } = require('../../util/FCM');
3 +
4 +
5 +exports.sendFcmMessage = async ctx => {
6 + const { deviceToken, title, body } = ctx.request.body;
7 +
8 + try {
9 + await sendPushMessage(ctx.request.body);
10 + } catch(e) {
11 + console.log('Error at FCM Sending : ', e);
12 + }
13 +};
...\ No newline at end of file ...\ No newline at end of file
...@@ -11,9 +11,18 @@ const user = new Router(); ...@@ -11,9 +11,18 @@ const user = new Router();
11 */ 11 */
12 user.get('/', userCtrl.getMyDetail); 12 user.get('/', userCtrl.getMyDetail);
13 13
14 +
14 /** 15 /**
15 - * 현재 로그인한 유저에 등록된 의사 목록 가져옴 16 + * 현재 유저 정보 수정
16 * request parameter : token 17 * request parameter : token
18 + * url : http://localhost:4000/api/user
19 + * return : Object User
20 + */
21 +user.patch('/', userCtrl.updateMyDetail);
22 +
23 +/**
24 + * 현재 로그인한 유저에 등록된 의사 목록 가져옴
25 + * request parameter : userNm, birth, contact, password, passwordCheck
17 * url : http://localhost:4000/api/user/doctor 26 * url : http://localhost:4000/api/user/doctor
18 * return : Doctor List 27 * return : Doctor List
19 */ 28 */
......
...@@ -38,10 +38,66 @@ exports.getMyDetail = async ctx => { ...@@ -38,10 +38,66 @@ exports.getMyDetail = async ctx => {
38 /** 38 /**
39 * 내 정보를 업데이트한다. 39 * 내 정보를 업데이트한다.
40 * @param {*} ctx 40 * @param {*} ctx
41 - * http methods : post 41 + * http methods : patch
42 */ 42 */
43 -exports.updateMyInfo = async ctx => { 43 +exports.updateMyDetail = async ctx => {
44 + const token = ctx.req.headers.authorization;
45 + if(!token || !token.length) {
46 + ctx.status = 401;
47 + return;
48 + }
49 +
50 + // eslint-disable-next-line no-undef
51 + const { userId } = jwt.verify(token, process.env.JWT_SECRET);
52 + const user = await User.findByUserId(userId);
53 + if(!user || user.useYn !== 'Y' || user.userTypeCd !== 'NORMAL') {
54 + ctx.status = 403;
55 + return;
56 + }
44 57
58 + const profile = await Profile.findByUserId(userId);
59 + if(!profile || profile.useYn !== 'Y') {
60 + ctx.status = 403;
61 + return;
62 + }
63 +
64 + const { userNm, birth, contact, password, passwordCheck, } = ctx.request.body;
65 +
66 + const existContact = await Profile.findOne({ contact, useYn : 'Y' });
67 + if(existContact) {
68 + ctx.status = 409;
69 + ctx.body = {
70 + error : '이미 가입된 번호',
71 + };
72 +
73 + return;
74 + }
75 +
76 + //passwordCheck가 있고 로컬 회원이라면 비밀번호 변경함
77 + if(passwordCheck && user.authTypeCd === 'NORMAL') {
78 + //passwordCheck와 password가 같아야함
79 + if(passwordCheck !== password) {
80 + ctx.status = 401;
81 + ctx.body = {
82 + error : '비밀번호가 일치하지 않습니다.',
83 + };
84 + return;
85 + }
86 +
87 + await user.setPassword(password);
88 + await user.save();
89 + }
90 +
91 + await profile.updateProfileInfo({
92 + userNm,
93 + birth,
94 + contact,
95 + });
96 +
97 + await profile.save();
98 +
99 + ctx.status = 200;
100 +
45 }; 101 };
46 102
47 /** 103 /**
...@@ -76,7 +132,12 @@ exports.getMyDoctorList = async ctx => { ...@@ -76,7 +132,12 @@ exports.getMyDoctorList = async ctx => {
76 useYn : 'Y', 132 useYn : 'Y',
77 }); 133 });
78 134
79 - return doctorInfo.info; 135 + if (doctorInfo) {
136 + return ({
137 + ...doctorInfo.info,
138 + doctorId : doctorInfo.doctorId,
139 + })
140 + }
80 })); 141 }));
81 142
82 ctx.status = 200; 143 ctx.status = 200;
...@@ -112,7 +173,10 @@ exports.viewAllDoctorRegisterReq = async ctx => { ...@@ -112,7 +173,10 @@ exports.viewAllDoctorRegisterReq = async ctx => {
112 }) 173 })
113 174
114 const doctorReqList = await Promise.all(patientInfoList.map(async patientInfo => { 175 const doctorReqList = await Promise.all(patientInfoList.map(async patientInfo => {
115 - const doctor = await DoctorInfo.findOne({ doctorId : patientInfo.doctorId }); 176 + const doctor = await DoctorInfo.findOne({
177 + doctorId : patientInfo.doctorId,
178 + useYn : 'Y',
179 + });
116 return { 180 return {
117 patientId : patientInfo.patientId, 181 patientId : patientInfo.patientId,
118 doctorId : patientInfo.doctorId, 182 doctorId : patientInfo.doctorId,
...@@ -156,9 +220,9 @@ exports.acceptDoctorRegister = async ctx => { ...@@ -156,9 +220,9 @@ exports.acceptDoctorRegister = async ctx => {
156 return; 220 return;
157 } 221 }
158 222
159 - patientInfo.updateInfo('환자 등록 요청 수락'); 223 + await patientInfo.updateInfo('환자 등록 요청 수락');
160 - patientInfo.setUseYn('Y'); 224 + await patientInfo.setUseYn('Y');
161 - patientInfo.save(); 225 + await patientInfo.save();
162 226
163 ctx.status = 200; 227 ctx.status = 200;
164 228
......
...@@ -11,6 +11,7 @@ exports.updateMedicineInfo = async() => { ...@@ -11,6 +11,7 @@ exports.updateMedicineInfo = async() => {
11 //queryUrl을 return하는 함수 : 한 페이지에 100개의 item씩 요청할 수 있다. 11 //queryUrl을 return하는 함수 : 한 페이지에 100개의 item씩 요청할 수 있다.
12 const getQueryURL = (i) => { 12 const getQueryURL = (i) => {
13 const url = 'http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList'; 13 const url = 'http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList';
14 + // eslint-disable-next-line no-undef
14 const queryParams = '?' + encodeURIComponent('ServiceKey') + '=' + process.env.SERVICE_KEY; 15 const queryParams = '?' + encodeURIComponent('ServiceKey') + '=' + process.env.SERVICE_KEY;
15 const pageNum = '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent(i); 16 const pageNum = '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent(i);
16 const numOfItem = '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent(100); 17 const numOfItem = '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent(100);
...@@ -24,6 +25,7 @@ const getItemsList = async(queryUrl) => { ...@@ -24,6 +25,7 @@ const getItemsList = async(queryUrl) => {
24 let i = 1, getItem = null, items = null; 25 let i = 1, getItem = null, items = null;
25 const result = []; 26 const result = [];
26 27
28 + // eslint-disable-next-line no-constant-condition
27 while(true) { 29 while(true) {
28 getItem = await axios.get(queryUrl(i)); 30 getItem = await axios.get(queryUrl(i));
29 items = getItem.data.body.items; 31 items = getItem.data.body.items;
......
...@@ -8,6 +8,7 @@ const jwtMiddleware = async (ctx, next) => { ...@@ -8,6 +8,7 @@ const jwtMiddleware = async (ctx, next) => {
8 } 8 }
9 9
10 try { 10 try {
11 + // eslint-disable-next-line no-undef
11 const decoded = jwt.verify(token, process.env.JWT_SECRET); 12 const decoded = jwt.verify(token, process.env.JWT_SECRET);
12 ctx.state.user = { 13 ctx.state.user = {
13 _id : decoded._id, 14 _id : decoded._id,
......
...@@ -5,6 +5,7 @@ const Schema = mongoose.Schema; ...@@ -5,6 +5,7 @@ const Schema = mongoose.Schema;
5 const BottleSchema = new Schema ({ 5 const BottleSchema = new Schema ({
6 bottleId : { type : Number, required : true, unique : true }, 6 bottleId : { type : Number, required : true, unique : true },
7 hubId : { type : Number, required : true, ref : 'Hub', }, 7 hubId : { type : Number, required : true, ref : 'Hub', },
8 + bottleNm : { type : String, required : true, maxlength : 10, },
8 }); 9 });
9 10
10 BottleSchema.statics.findByBottleId = function(bottleId) { 11 BottleSchema.statics.findByBottleId = function(bottleId) {
...@@ -23,5 +24,9 @@ BottleSchema.methods.getHubId = function() { ...@@ -23,5 +24,9 @@ BottleSchema.methods.getHubId = function() {
23 return this.hubId; 24 return this.hubId;
24 }; 25 };
25 26
27 +BottleSchema.methods.setBottleNm = function(bottleNm) {
28 + this.bottleNm = bottleNm;
29 +}
30 +
26 31
27 module.exports = mongoose.model('Bottle', BottleSchema); 32 module.exports = mongoose.model('Bottle', BottleSchema);
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -16,16 +16,26 @@ const BottleMedicineSchema = new Schema({ ...@@ -16,16 +16,26 @@ const BottleMedicineSchema = new Schema({
16 doctorId : { 16 doctorId : {
17 type : String, 17 type : String,
18 ref : 'User', 18 ref : 'User',
19 - required : true, 19 + lowercase : true,
20 }, 20 },
21 - dosage : { 21 + dailyDosage : {
22 + type : Number,
23 + default : 1,
24 + },
25 + totalDosage : {
26 + type : Number,
27 + default : 1,
28 + },
29 + eachWeight : {
30 + type : Number,
31 + default : 0,
32 + },
33 + totalWeight : {
22 type : Number, 34 type : Number,
23 - required : true,
24 default : 0, 35 default : 0,
25 }, 36 },
26 regDtm : { 37 regDtm : {
27 type : Date, 38 type : Date,
28 - required : true,
29 default : Date.now, 39 default : Date.now,
30 }, 40 },
31 useYn : { 41 useYn : {
...@@ -39,6 +49,14 @@ BottleMedicineSchema.methods.setDoctorId = function(doctorId) { ...@@ -39,6 +49,14 @@ BottleMedicineSchema.methods.setDoctorId = function(doctorId) {
39 this.doctorId = doctorId; 49 this.doctorId = doctorId;
40 }; 50 };
41 51
52 +BottleMedicineSchema.methods.setEachWeight = function(eachWeight) {
53 + this.eachWeight = eachWeight;
54 +};
55 +
56 +BottleMedicineSchema.methods.setTotalWeight = function(totalWeight) {
57 + this.totalWeight = totalWeight;
58 +};
59 +
42 BottleMedicineSchema.methods.setUseYn = function(useYn) { 60 BottleMedicineSchema.methods.setUseYn = function(useYn) {
43 this.useYn = useYn; 61 this.useYn = useYn;
44 }; 62 };
......
...@@ -3,9 +3,10 @@ const mongoose = require('mongoose'); ...@@ -3,9 +3,10 @@ const mongoose = require('mongoose');
3 const Schema = mongoose.Schema; 3 const Schema = mongoose.Schema;
4 4
5 const DoctorInfoSchema = new Schema({ 5 const DoctorInfoSchema = new Schema({
6 - doctorId : { type : String, required : true, }, 6 + doctorId : { type : String, required : true, lowercase : true, },
7 info : { 7 info : {
8 doctorLicense : { type : String, required : true, }, 8 doctorLicense : { type : String, required : true, },
9 + validateDoctorLicense : { type : String, default : null },
9 hospitalNm : { type : String, default : null, }, 10 hospitalNm : { type : String, default : null, },
10 hospitalAddr : { type : String, default : null, }, 11 hospitalAddr : { type : String, default : null, },
11 contact : { type : String, required : true, }, 12 contact : { type : String, required : true, },
...@@ -23,5 +24,9 @@ DoctorInfoSchema.methods.setUseYn = function(useYn) { ...@@ -23,5 +24,9 @@ DoctorInfoSchema.methods.setUseYn = function(useYn) {
23 this.useYn = useYn; 24 this.useYn = useYn;
24 }; 25 };
25 26
27 +DoctorInfoSchema.methods.setValidateDoctorLicense = function(validateDoctorLicense) {
28 + this.info.validateDoctorLicense = validateDoctorLicense;
29 +};
30 +
26 31
27 module.exports = mongoose.model('DoctorInfo', DoctorInfoSchema); 32 module.exports = mongoose.model('DoctorInfo', DoctorInfoSchema);
......
...@@ -10,7 +10,7 @@ const FeedbackSchema = new Schema({ ...@@ -10,7 +10,7 @@ const FeedbackSchema = new Schema({
10 required : true, 10 required : true,
11 ref : 'BottleMedicine', 11 ref : 'BottleMedicine',
12 }, 12 },
13 - doctorId : { type : String, required : true, ref : 'User', }, 13 + doctorId : { type : String, required : true, ref : 'User', lowercase : true, },
14 feedback : { type : String, required : true, }, 14 feedback : { type : String, required : true, },
15 }); 15 });
16 16
......
...@@ -5,7 +5,8 @@ const Schema = mongoose.Schema; ...@@ -5,7 +5,8 @@ const Schema = mongoose.Schema;
5 const HubSchema = new Schema ({ 5 const HubSchema = new Schema ({
6 hubId : { type : Number, required : true, unique : true }, 6 hubId : { type : Number, required : true, unique : true },
7 hosting : { type : Object, default : null }, 7 hosting : { type : Object, default : null },
8 - userId : { type : String, default : null, ref : 'User' }, 8 + userId : { type : String, default : null, ref : 'User', lowercase : true, },
9 + hubNm : { type : String, required : true, maxlength : 10, },
9 }); 10 });
10 11
11 HubSchema.statics.findByHubId = function(hubId) { 12 HubSchema.statics.findByHubId = function(hubId) {
......
1 const mongoose = require('mongoose'); 1 const mongoose = require('mongoose');
2 const moment = require('moment'); 2 const moment = require('moment');
3 +require('moment-timezone');
3 4
4 const Schema = mongoose.Schema; 5 const Schema = mongoose.Schema;
5 6
6 const PatientInfoSchema = new Schema({ 7 const PatientInfoSchema = new Schema({
7 - patientId : { type : String, required : true, ref : 'User', }, 8 + patientId : { type : String, required : true, ref : 'User', lowercase : true, },
8 - doctorId : { type : String, required : true, ref : 'User', }, 9 + doctorId : { type : String, required : true, ref : 'User', lowercase : true, },
9 info : { type : String, required : true, }, 10 info : { type : String, required : true, },
10 useYn : { type : String, required : true, default : 'W', }, 11 useYn : { type : String, required : true, default : 'W', },
11 }); 12 });
...@@ -35,9 +36,9 @@ PatientInfoSchema.methods.setUseYn = function(useYn) { ...@@ -35,9 +36,9 @@ PatientInfoSchema.methods.setUseYn = function(useYn) {
35 }; 36 };
36 37
37 PatientInfoSchema.methods.updateInfo = function(info) { 38 PatientInfoSchema.methods.updateInfo = function(info) {
38 - const date = moment(new Date()).format('YYYY-MM-DD hh:mm'); 39 + const date = moment.tz('Asia/Seoul').format('YYYY-MM-DD HH:mm');
39 if(this.info.length) 40 if(this.info.length)
40 - this.info = this.info.concat('\n\n', `${date} ${info}`); 41 + this.info = this.info.concat('\n\n', `${date} -> ${info}`);
41 else 42 else
42 this.info = `${date}${info}`; 43 this.info = `${date}${info}`;
43 }; 44 };
......
1 +const mongoose = require('mongoose');
2 +
3 +const Schema = mongoose.Schema;
4 +
5 +const PrescribeInfoSchema = new Schema({
6 + doctorId : { type : String, require : true, },
7 + patientId : { type : String, require : true, },
8 + medicineId : { type : Number, require : true, },
9 + dailyDosage : { type : Number, require : true, },
10 + totalDosage : { type : Number, require : true, },
11 + qrCodeUrl : { type : String, require : true, },
12 +});
13 +
14 +
15 +module.exports = mongoose.model('PrescribeInfo', PrescribeInfoSchema);
...\ No newline at end of file ...\ No newline at end of file
...@@ -3,10 +3,11 @@ const mongoose = require('mongoose'); ...@@ -3,10 +3,11 @@ const mongoose = require('mongoose');
3 const Schema = mongoose.Schema; 3 const Schema = mongoose.Schema;
4 4
5 const ProfileSchema = new Schema({ 5 const ProfileSchema = new Schema({
6 - userId : { type : String, required : true, ref : 'User', }, 6 + userId : { type : String, required : true, ref : 'User', lowercase : true, },
7 userNm : { type : String, required : true, }, 7 userNm : { type : String, required : true, },
8 - userAge : { type : Number, required : true, }, 8 + birth : { type : String, required : true, },
9 contact : { type : String, required : true, }, 9 contact : { type : String, required : true, },
10 + useYn : { type : String, default : 'Y', },
10 deviceToken : { type : String, default : null, }, 11 deviceToken : { type : String, default : null, },
11 }); 12 });
12 13
...@@ -14,12 +15,14 @@ ProfileSchema.statics.findByUserId = function(userId) { ...@@ -14,12 +15,14 @@ ProfileSchema.statics.findByUserId = function(userId) {
14 return this.findOne({ userId }); 15 return this.findOne({ userId });
15 }; 16 };
16 17
17 -ProfileSchema.methods.updateUserContact = function(contact) { 18 +ProfileSchema.methods.setUseYn = function(useYn) {
18 - this.contact = contact; 19 + this.useYn = useYn;
19 }; 20 };
20 21
21 -ProfileSchema.methods.updateUserAge = function() { 22 +ProfileSchema.methods.updateProfileInfo = function({ userNm, birth, contact }) {
22 - this.userAge = this.userAge + 1; 23 + if(userNm) { this.userNm = userNm }
24 + if(birth) { this.birth = birth }
25 + if(contact) { this.contact = contact }
23 }; 26 };
24 27
25 ProfileSchema.methods.updateDeviceToken = function(deviceToken) { 28 ProfileSchema.methods.updateDeviceToken = function(deviceToken) {
......
...@@ -5,7 +5,6 @@ const Schema = mongoose.Schema; ...@@ -5,7 +5,6 @@ const Schema = mongoose.Schema;
5 const TakeMedicineHistorySchema = new Schema ({ 5 const TakeMedicineHistorySchema = new Schema ({
6 takeDate : { 6 takeDate : {
7 type : Date, 7 type : Date,
8 - required : true,
9 default : Date.now, 8 default : Date.now,
10 }, 9 },
11 bmId : { 10 bmId : {
...@@ -13,9 +12,10 @@ const TakeMedicineHistorySchema = new Schema ({ ...@@ -13,9 +12,10 @@ const TakeMedicineHistorySchema = new Schema ({
13 ref : 'BottleMedicine', 12 ref : 'BottleMedicine',
14 required : true, 13 required : true,
15 }, 14 },
16 - temperature : { type : Number, default : 0 }, 15 + temperature : { type : Number, default : 0, },
17 - humidity : { type : Number, default : 0 }, 16 + humidity : { type : Number, default : 0, },
18 - balance : { type : Number, default : 0 }, 17 + dosage : { type : Number, default : 0, },
18 + balance : { type : Number, default : 0, },
19 }); 19 });
20 20
21 21
......
...@@ -5,9 +5,10 @@ const jwt = require('jsonwebtoken'); ...@@ -5,9 +5,10 @@ const jwt = require('jsonwebtoken');
5 const Schema = mongoose.Schema; 5 const Schema = mongoose.Schema;
6 6
7 const UserSchema = new Schema ({ 7 const UserSchema = new Schema ({
8 - userId : { type: String, required : true, unique : true, lowercase : true, }, 8 + userId : { type: String, required : true, unique : true, lowercase : true, trim : true },
9 - hashedPassword : { type : String, required : true }, 9 + hashedPassword : { type : String, required : true, },
10 - userTypeCd : { type : String, required : true, default : 'NORMAL' }, 10 + userTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
11 + authTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
11 useYn : { type : String, default : 'W', required : true, }, 12 useYn : { type : String, default : 'W', required : true, },
12 }); 13 });
13 14
......
...@@ -3,11 +3,136 @@ ...@@ -3,11 +3,136 @@
3 * 21/09/14 3 * 21/09/14
4 * Author : 박권수 4 * Author : 박권수
5 * 배치 시스템 5 * 배치 시스템
6 - * 1) 매년 지나면 프로필의 Age를 +1 6 + * 1) Dosage에 따라, Push Notification 발송
7 - * 2) Dosage에 따라, Push Notification 발송
8 */ 7 */
9 8
10 const cron = require('node-cron'); 9 const cron = require('node-cron');
10 +const fs = require('fs');
11 11
12 const Profile = require('../models/profile'); 12 const Profile = require('../models/profile');
13 -const BottleMedicine = require('../models/bottleMedicine');
...\ No newline at end of file ...\ No newline at end of file
13 +const User = require('../models/user');
14 +const Hub = require('../models/hub');
15 +const Bottle = require('../models/bottle');
16 +const BottleMedicine = require('../models/bottleMedicine');
17 +const Medicine = require('../models/medicine');
18 +
19 +const updateMedicineInfo = require('../lib/UpdatingMedicineInfo');
20 +const { sendPushMessage } = require('./FCM');
21 +
22 +
23 +//매월 1일 0시 0분에 약 정보 업데이트
24 +exports.updateMedicineData = async () => {
25 + cron.schedule('0 0 0 1 * *', () => {
26 + updateMedicineInfo.updateMedicineInfo();
27 + }, {
28 + timezone : 'Asia/Tokyo',
29 + });
30 +};
31 +
32 +//매주 일요일마다 불필요한 qrcode 제거
33 +exports.removeQrCode = () => {
34 + cron.schedule('0 0 0 * * 0', () => {
35 + // eslint-disable-next-line no-undef
36 + const qrDir = process.env.QR_DIR;
37 + fs.rm(qrDir, { recursive : true, force : true, }, () => {
38 + fs.mkdir(qrDir, (err) => { if(err) console.log(err) });
39 + });
40 + }, {
41 + timezone : 'Asia/Tokyo',
42 + });
43 +};
44 +
45 +//dosage에 따라, Push Notification을 발송한다.
46 +//아침 8시, 점심 12시, 저녁 6시에 한번씩 발송
47 +exports.pushNotifyByDosage = async() => {
48 +
49 + //매일 아침 8시 : 복용량 상관 없이 보냄
50 + cron.schedule('0 0 8 * * *', async () => {
51 + const bottleMedicineList = await BottleMedicine.find({ useYn : 'Y', dosage : { $gte : 1 } });
52 + bottleMedicineList.forEach(async bottleMedicine => {
53 + const bottle = await Bottle.findOne({ bottleId : bottleMedicine.bottleId });
54 + const hub = await Hub.findOne({ hubId : bottle.hubId });
55 + const user = await User.findOne({ userId : hub.userId, useYn : 'Y' });
56 +
57 + if(user) {
58 + const profile = await Profile.findOne({ userId : user.userId });
59 + const { deviceToken } = profile;
60 +
61 + if(deviceToken) {
62 + const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
63 + pushNotify({
64 + deviceToken,
65 + title : '약 복용 시간입니다',
66 + body : medicine.name + '을 복용하셔야 합니다.',
67 + });
68 + }
69 + }
70 + });
71 + }, {
72 + timezone : 'Asia/Tokyo',
73 + });
74 +
75 +
76 + //매일 점심 12시 : 복용량이 3인 환자들만
77 + cron.schedule('0 0 12 * * *', async () => {
78 + const bottleMedicineList = await BottleMedicine.find({ useYn : 'Y', dosage : { $gte : 3 } });
79 + bottleMedicineList.forEach(async bottleMedicine => {
80 + const bottle = await Bottle.findOne({ bottleId : bottleMedicine.bottleId });
81 + const hub = await Hub.findOne({ hubId : bottle.hubId });
82 + const user = await User.findOne({ userId : hub.userId, useYn : 'Y' });
83 +
84 + if(user) {
85 + const profile = await Profile.findOne({ userId : user.userId });
86 + const { deviceToken } = profile;
87 +
88 + if(deviceToken) {
89 + const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
90 + pushNotify({
91 + deviceToken,
92 + title : '약 복용 시간입니다',
93 + body : medicine.name + '을 복용하셔야 합니다.',
94 + });
95 + }
96 + }
97 + });
98 + }, {
99 + timezone : 'Asia/Tokyo',
100 + });
101 +
102 +
103 + //매일 저녁 6시
104 + cron.schedule('0 0 18 * * *', async () => {
105 + const bottleMedicineList = await BottleMedicine.find({ useYn : 'Y', dosage : { $gte : 2 } });
106 + bottleMedicineList.forEach(async bottleMedicine => {
107 + const bottle = await Bottle.findOne({ bottleId : bottleMedicine.bottleId });
108 + const hub = await Hub.findOne({ hubId : bottle.hubId });
109 + const user = await User.findOne({ userId : hub.userId, useYn : 'Y' });
110 +
111 + if(user) {
112 + const profile = await Profile.findOne({ userId : user.userId });
113 + const { deviceToken } = profile;
114 +
115 + if(deviceToken) {
116 + const medicine = await Medicine.findOne({ medicineId : bottleMedicine.medicineId });
117 + pushNotify({
118 + deviceToken,
119 + title : '약 복용 시간입니다',
120 + body : medicine.name + '을 복용하셔야 합니다.',
121 + });
122 + }
123 + }
124 + });
125 + }, {
126 + timezone : 'Asia/Tokyo',
127 + });
128 +
129 +};
130 +
131 +const pushNotify = ({ deviceToken, title, body }) => {
132 + //toDo : deviceToken을 받아서 push notification을 발송하는 함수
133 + sendPushMessage({
134 + deviceToken,
135 + title,
136 + body,
137 + });
138 +};
......
1 -const Bottle = require('../models/bottle');
2 const BottleMedicine = require('../models/bottleMedicine'); 1 const BottleMedicine = require('../models/bottleMedicine');
3 const TakeMedicineHist = require('../models/takeMedicineHistory'); 2 const TakeMedicineHist = require('../models/takeMedicineHistory');
4 3
4 +
5 //message subscribe 후 message를 가공한 이후 해당 데이터를 보낼 topic과 message를 리턴하는 함수 5 //message subscribe 후 message를 가공한 이후 해당 데이터를 보낼 topic과 message를 리턴하는 함수
6 exports.dataPublish = async (topic, message) => { 6 exports.dataPublish = async (topic, message) => {
7 - //client가 subscribe를 하면 메시지를 보낸 약병의 topic과 message를 가공 및 보낸 약병의 bottleId를 가져옴 7 + if(message.includes('weight')) {
8 - const data = await factoring(topic, message); 8 + //무게 갱신
9 - //가공된 데이터를 bottleId의 약병에 업데이트 9 + const result = await updateBottleMedicineWeight(topic, message);
10 - await bottleInfoUpdate(data); 10 +
11 - //가공된 데이터를 메시지로 만들어 topic과 message 리턴 11 + return result;
12 - const result = await transPublishingTopicAndMessage(data.bottleId); 12 +
13 + } else {
14 + //client가 subscribe를 하면 메시지를 보낸 약병의 topic과 message를 가공 및 보낸 약병의 bottleId를 가져옴
15 + const data = await factoring(topic, message);
16 + //가공된 데이터를 bottleId의 약병에 업데이트
17 + await bottleInfoUpdate(data);
18 + //가공된 데이터를 메시지로 만들어 topic과 message 리턴
19 + const result = await transPublishingTopicAndMessage(data.bottleId);
13 20
14 - return result; 21 + return result;
15 22
23 + }
16 }; 24 };
17 25
18 //Hub topic : bottle/bottleId 26 //Hub topic : bottle/bottleId
19 -//Hub로부터 받은 message : 개폐여부/온도/습도/초음파센서 27 +//Hub로부터 받은 message : 개폐여부/온도/습도/무게센서
20 const factoring = async (topic, message) => { 28 const factoring = async (topic, message) => {
21 const bottleId = parseInt(topic.split('/')[1]); 29 const bottleId = parseInt(topic.split('/')[1]);
22 const data = message.split('/'); 30 const data = message.split('/');
23 - let [isOpen, temperature, humidity, balance] = data; 31 + const [isOpen, temperature, totalWeight, humidity] = data;
24 32
25 - if(isOpen === '0')
26 - balance = await balanceFactoring(balance);
27 - else balance = '-1';
28 -
29 return { 33 return {
30 bottleId, 34 bottleId,
31 isOpen, 35 isOpen,
32 - openDate : new Date(), 36 + temperature,
33 - temperature,
34 humidity, 37 humidity,
35 - balance 38 + totalWeight,
36 }; 39 };
37 40
38 } 41 }
39 42
40 -const balanceFactoring = (balance) => {
41 - const max = 10; //Digital Lead Sensor Maximum Value
42 - const slicingBalance = max / 5;
43 -
44 - if(parseInt(balance) < slicingBalance || parseInt(balance) > max * 2)
45 - return '80';
46 - else if(parseInt(balance) < slicingBalance * 2)
47 - return '60';
48 - else if(parseInt(balance) < slicingBalance * 3)
49 - return '40';
50 - else if(parseInt(balance) < slicingBalance * 4)
51 - return '20';
52 - else return '0';
53 -
54 -}
55 -
56 //bottleId가 포함된 data를 받아서 해당 약병의 data를 업데이트한다. 43 //bottleId가 포함된 data를 받아서 해당 약병의 data를 업데이트한다.
57 const bottleInfoUpdate = async(data) => { 44 const bottleInfoUpdate = async(data) => {
58 - let { bottleId, isOpen, openDate, temperature, humidity, balance } = data; 45 + let { bottleId, isOpen, temperature, humidity, totalWeight } = data;
59 -
60 - bottleId = parseInt(bottleId);
61 - isOpen = parseInt(isOpen);
62 - temperature = parseFloat(temperature);
63 - humidity = parseFloat(humidity);
64 - balance = parseInt(balance);
65 -
66 - const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
67 46
68 - if(bottleMedicine) { 47 + if(!parseInt(isOpen)) {
69 - if(isOpen) { 48 + bottleId = parseInt(bottleId);
70 - const takeMedicineHist = new TakeMedicineHist({ 49 + temperature = parseFloat(temperature);
71 - takeDate : openDate, 50 + humidity = parseFloat(humidity);
72 - bmId : bottleMedicine._id, 51 + totalWeight = parseFloat(totalWeight);
73 - temperature, 52 +
74 - humidity, 53 + const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
75 - balance, 54 +
76 - }); 55 + if(bottleMedicine) {
77 - takeMedicineHist.save(); 56 + const lastTotalWeight = parseFloat(bottleMedicine.totalWeight);
57 + const { eachWeight } = bottleMedicine;
58 +
59 + const dosage = Math.round((lastTotalWeight - totalWeight) / parseFloat(eachWeight));
60 +
61 + if(dosage > 0) {
62 + const balance = Math.round(totalWeight / parseFloat(eachWeight));
63 +
64 + const takeMedicineHist = new TakeMedicineHist({
65 + bmId : bottleMedicine._id,
66 + temperature,
67 + humidity,
68 + dosage,
69 + balance,
70 + });
71 + await takeMedicineHist.save();
72 + }
73 +
74 + await bottleMedicine.setTotalWeight(totalWeight);
75 + await bottleMedicine.save();
78 } 76 }
79 } 77 }
80 } 78 }
...@@ -84,11 +82,13 @@ const transPublishingTopicAndMessage = async(bottleId) => { ...@@ -84,11 +82,13 @@ const transPublishingTopicAndMessage = async(bottleId) => {
84 const topic = 'bottle/' + bottleId + '/stb'; 82 const topic = 'bottle/' + bottleId + '/stb';
85 83
86 const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' }); 84 const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
87 - const takeMedicineHist = await TakeMedicineHist.find({ 85 + const takeMedicineHistList = await TakeMedicineHist.find({
88 bmId : bottleMedicine._id 86 bmId : bottleMedicine._id
89 - }).sort((a, b) => a.takeDate < b.takeDate)[0]; 87 + }).sort({ takeDate : 'desc' }).limit(1);
90 88
91 - const message = 'res/' + await transDate(takeMedicineHist.takeDate) + '/' + bottleMedicine.dosage; 89 + const message = takeMedicineHistList && takeMedicineHistList[0] ?
90 + 'res/' + await transDate(takeMedicineHistList[0].takeDate) + '/' + takeMedicineHistList[0].dosage :
91 + 'res/' + await transDate(new Date()) + '/' + 0;
92 92
93 return { 93 return {
94 topic, 94 topic,
...@@ -100,4 +100,23 @@ const transPublishingTopicAndMessage = async(bottleId) => { ...@@ -100,4 +100,23 @@ const transPublishingTopicAndMessage = async(bottleId) => {
100 const transDate = (date) => { 100 const transDate = (date) => {
101 return (date.getMonth() + 1 < 10 ? '0' + String(date.getMonth() + 1) : String(date.getMonth() + 1)) 101 return (date.getMonth() + 1 < 10 ? '0' + String(date.getMonth() + 1) : String(date.getMonth() + 1))
102 + (date.getDate() < 10 ? '0' + String(date.getDate()) : String(date.getDate())); 102 + (date.getDate() < 10 ? '0' + String(date.getDate()) : String(date.getDate()));
103 -}
...\ No newline at end of file ...\ No newline at end of file
103 +};
104 +
105 +
106 +//무게센서를 이용하여 데이터값을 갱신하는 함수
107 +const updateBottleMedicineWeight = async (topic, message) => {
108 + const bottleId = parseInt(topic.split('/')[1]);
109 + //message = weight/무게
110 + const totalWeight = parseFloat(message.split('/')[1]);
111 +
112 + const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
113 + const totalDosage = parseInt(bottleMedicine.totalDosage);
114 + //받은 값으로 총 무게를 설정한 이후, 총 무게 / 총 복용량으로 개별 무게를 설정한다.
115 + await bottleMedicine.setTotalWeight(totalWeight);
116 + await bottleMedicine.setEachWeight(totalWeight / totalDosage);
117 +
118 + await bottleMedicine.save();
119 +
120 + return null;
121 +
122 +};
...\ No newline at end of file ...\ No newline at end of file
......
1 +// const fcm = require('firebase-admin');
2 +const axios = require('axios');
3 +
4 +// exports.initializeFCM = () => {
5 +// fcm.initializeApp({
6 +// credential: fcm.credential.applicationDefault(),
7 +// });
8 +// };
9 +
10 +exports.sendPushMessage = async ({ deviceToken, title, body }) => {
11 + // const notifyMessage = {
12 + // notification : {
13 + // title,
14 + // body,
15 + // },
16 + // token : deviceToken,
17 + // };
18 + // fcm.messaging().send(notifyMessage).then(res => {
19 + // console.log(res);
20 + // }).catch(e => {
21 + // console.log(e);
22 + // });
23 +
24 + const message = {
25 + to : deviceToken,
26 + notification : {
27 + title,
28 + body,
29 + priority : 'high',
30 + },
31 + data : null,
32 + }
33 +
34 + const url = 'https://fcm.googleapis.com/fcm/send';
35 + const result = await axios.post(url, message, {
36 + headers : {
37 + 'Content-Type' : 'application/json',
38 + // eslint-disable-next-line no-undef
39 + 'Authorization' : `key=${process.env.FCM_KEY}`,
40 + },
41 + });
42 +
43 + console.log(result.data);
44 +};
...\ No newline at end of file ...\ No newline at end of file
1 +const { Storage } = require('@google-cloud/storage');
2 +const fs = require('fs');
3 +
4 +const storage = new Storage();
5 +const GoogleStorageUrl = 'https://storage.googleapis.com/';
6 +
7 +
8 +//의사 아이디, 업로드할 파일명, 업로드할 파일 경로를 인자로 받아, File을 GCS에 업로드 후 GCS 주소를 반환
9 +exports.uploadDoctorLicense = async ({ userId, fileName, filePath }) => {
10 + const destination = userId + '_' + fileName;
11 + try {
12 + const result = await storage.bucket('doctor-info').upload(filePath, {
13 + destination,
14 + });
15 +
16 + const doctorLicenseUrl = GoogleStorageUrl + `${result[0].bucket.id}/${result[0].name}`;
17 +
18 + return doctorLicenseUrl;
19 + } catch(e) {
20 + console.log(e);
21 + return null;
22 + }
23 +};
24 +
25 +//의사 정보를 인자로 받아 해당 Doctor License의 Signed URL을 반환
26 +exports.viewDoctorLicense = async ({ doctorInfo }) => {
27 + try {
28 + const fileName = doctorInfo.info.doctorLicense.split('/').pop();
29 + const file = storage.bucket('doctor-info').file(fileName);
30 + const option = {
31 + version : 'v4',
32 + expires : Date.now() + 1000 * 60 * 15,
33 + action : 'read',
34 + };
35 +
36 + const [signedUrl] = file ? await file.getSignedUrl(option) : [null];
37 +
38 + return signedUrl;
39 + } catch(e) {
40 + console.log(e);
41 + return null;
42 + }
43 +};
44 +
45 +//의사 ID, 약 ID, 복용량을 인자로 받아, QR Code를 생성
46 +exports.uploadQrCode = async ({ directory, qrCodeFileName }) => {
47 + const destination = qrCodeFileName;
48 + try {
49 + //파일을 GCS에 업로드
50 + const result = await storage.bucket('prescribe-medicine-qrcode').upload(directory + '/' + qrCodeFileName, {
51 + destination
52 + });
53 +
54 + //업로드 후 파일 삭제
55 + fs.rm(directory + '/' + qrCodeFileName, () => {});
56 +
57 + const qrCodeUrl = GoogleStorageUrl + `${result[0].bucket.id}/${result[0].name}`;
58 +
59 + return qrCodeUrl;
60 + } catch(e) {
61 + console.log(e);
62 + return null;
63 + }
64 +};
65 +
66 +//생성된 QR코드의 signedUrl을 가져옴
67 +exports.getQrCodeUrl = async ({ qrCodeFileName }) => {
68 + try {
69 + const fileName = qrCodeFileName;
70 + const file = storage.bucket('prescribe-medicine-qrcode').file(fileName);
71 + const option = {
72 + version : 'v4',
73 + expires : Date.now() + 1000 * 60 * 15,
74 + action : 'read',
75 + };
76 +
77 + const [signedUrl] = file ? await file.getSignedUrl(option) : [null];
78 +
79 + return signedUrl;
80 + } catch(e) {
81 + console.log(e);
82 + return null;
83 + }
84 +};
...\ No newline at end of file ...\ No newline at end of file
1 -const mqtt = require('mqtt') 1 +const mqtt = require('mqtt');
2 -const clientList = [] 2 +const clientList = [];
3 3
4 exports.mqttOn = async (hosting, foo) => { 4 exports.mqttOn = async (hosting, foo) => {
5 const filterIndex = clientList.findIndex(client => { 5 const filterIndex = clientList.findIndex(client => {
...@@ -9,27 +9,29 @@ exports.mqttOn = async (hosting, foo) => { ...@@ -9,27 +9,29 @@ exports.mqttOn = async (hosting, foo) => {
9 }) 9 })
10 10
11 if(filterIndex === -1) { 11 if(filterIndex === -1) {
12 - const client = mqtt.connect(hosting) 12 + const client = mqtt.connect(hosting);
13 - clientList.push(client) 13 + clientList.push(client);
14 14
15 client.on('connect', () => { 15 client.on('connect', () => {
16 console.log('Hub connected: ', client.connected) 16 console.log('Hub connected: ', client.connected)
17 - }) 17 + });
18 18
19 client.on('message', async (topic, message) => { 19 client.on('message', async (topic, message) => {
20 - const result = await foo(topic, message.toString()) 20 + const result = await foo(topic, message.toString());
21 console.log('\x1b[1;32msubscribe : topic', topic, 'message : ', message.toString(), '\x1b[0m') 21 console.log('\x1b[1;32msubscribe : topic', topic, 'message : ', message.toString(), '\x1b[0m')
22 - this.mqttPublishMessage(client, result) 22 + if(result) this.mqttPublishMessage(client, result);
23 - }) 23 + });
24 24
25 - return client 25 + return client;
26 } 26 }
27 27
28 - return clientList[filterIndex] 28 + return clientList[filterIndex];
29 } 29 }
30 30
31 exports.mqttSubscribe = (client, topic) => { 31 exports.mqttSubscribe = (client, topic) => {
32 - client.subscribe(topic) 32 + client.subscribe(topic, () => {
33 + console.log('suscribe', topic);
34 + });
33 } 35 }
34 36
35 exports.mqttPublishMessage = (client, { topic, message }) => { 37 exports.mqttPublishMessage = (client, { topic, message }) => {
...@@ -49,10 +51,10 @@ exports.mqttOff = (hosting) => { ...@@ -49,10 +51,10 @@ exports.mqttOff = (hosting) => {
49 return (client.options.clientId === hosting.clientId 51 return (client.options.clientId === hosting.clientId
50 && client.options.host === hosting.host 52 && client.options.host === hosting.host
51 && client.options.port === hosting.port) 53 && client.options.port === hosting.port)
52 - }) 54 + });
53 55
54 if(filterIndex !== -1) { 56 if(filterIndex !== -1) {
55 - clientList[filterIndex].end() 57 + clientList[filterIndex].end();
56 - clientList.splice(filterIndex, 1) 58 + clientList.splice(filterIndex, 1);
57 } 59 }
58 } 60 }
...\ No newline at end of file ...\ No newline at end of file
......
1 -const Mqtt = require('../lib/MqttModule'); 1 +const Mqtt = require('./MqttModule');
2 -const DataProcess = require('../lib/DataProcess'); 2 +const DataProcess = require('./DataProcess');
3 const Hub = require('../models/hub'); 3 const Hub = require('../models/hub');
4 const Bottle = require('../models/bottle'); 4 const Bottle = require('../models/bottle');
5 5
......
1 +const QrCode = require('qrcode');
2 +const moment = require('moment');
3 +
4 +
5 +exports.generateQrCode_prescribe = async ({ medicine, dailyDosage, totalDosage, patientId, doctorId }) => {
6 + // eslint-disable-next-line no-undef
7 + const directory = process.env.QR_DIR;
8 +
9 + const now = moment().format('YYYY-MM-DD_HH:mm');
10 + const qrCodeFileName = `${now}_${doctorId}_${patientId}_${medicine.medicineId}_${dailyDosage}_${totalDosage}.png`;
11 +
12 + try {
13 + await QrCode.toFile(
14 + directory + '/' + qrCodeFileName,
15 + `${medicine.medicineId}/${dailyDosage}/${totalDosage}/${doctorId}/${patientId}/${medicine.name}`,
16 + {
17 + color : {
18 + dark : '#337DFF',
19 + light : '#FFF'
20 + },
21 + }
22 + );
23 +
24 + return {
25 + directory,
26 + qrCodeFileName,
27 + };
28 +
29 + } catch(e) {
30 + console.log(e);
31 + return null;
32 + }
33 +
34 +};
...\ No newline at end of file ...\ No newline at end of file
This diff could not be displayed because it is too large.
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
5 "requires": true, 5 "requires": true,
6 "packages": { 6 "packages": {
7 "": { 7 "": {
8 + "name": "web",
8 "version": "0.1.0", 9 "version": "0.1.0",
9 "dependencies": { 10 "dependencies": {
10 "@testing-library/jest-dom": "^5.11.4", 11 "@testing-library/jest-dom": "^5.11.4",
...@@ -18,10 +19,12 @@ ...@@ -18,10 +19,12 @@
18 "highcharts": "^9.2.0", 19 "highcharts": "^9.2.0",
19 "highcharts-react-official": "^3.0.0", 20 "highcharts-react-official": "^3.0.0",
20 "moment": "^2.29.1", 21 "moment": "^2.29.1",
22 + "qrcode": "^1.4.4",
21 "react": "^17.0.2", 23 "react": "^17.0.2",
22 "react-dom": "^17.0.2", 24 "react-dom": "^17.0.2",
23 "react-router-dom": "^5.2.0", 25 "react-router-dom": "^5.2.0",
24 "react-scripts": "4.0.3", 26 "react-scripts": "4.0.3",
27 + "react-spinners": "^0.11.0",
25 "recoil": "^0.4.0", 28 "recoil": "^0.4.0",
26 "recoil-persist": "^3.0.0", 29 "recoil-persist": "^3.0.0",
27 "styled-components": "^5.3.0", 30 "styled-components": "^5.3.0",
...@@ -31,6 +34,7 @@ ...@@ -31,6 +34,7 @@
31 "web-vitals": "^1.0.1" 34 "web-vitals": "^1.0.1"
32 }, 35 },
33 "devDependencies": { 36 "devDependencies": {
37 + "@types/qrcode": "^1.4.1",
34 "@types/react-router-dom": "^5.1.8", 38 "@types/react-router-dom": "^5.1.8",
35 "@types/styled-components": "^5.1.12", 39 "@types/styled-components": "^5.1.12",
36 "@types/validator": "^13.6.3", 40 "@types/validator": "^13.6.3",
...@@ -1247,11 +1251,14 @@ ...@@ -1247,11 +1251,14 @@
1247 } 1251 }
1248 }, 1252 },
1249 "node_modules/@babel/runtime": { 1253 "node_modules/@babel/runtime": {
1250 - "version": "7.12.18", 1254 + "version": "7.15.4",
1251 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.18.tgz", 1255 + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
1252 - "integrity": "sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg==", 1256 + "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
1253 "dependencies": { 1257 "dependencies": {
1254 "regenerator-runtime": "^0.13.4" 1258 "regenerator-runtime": "^0.13.4"
1259 + },
1260 + "engines": {
1261 + "node": ">=6.9.0"
1255 } 1262 }
1256 }, 1263 },
1257 "node_modules/@babel/runtime-corejs3": { 1264 "node_modules/@babel/runtime-corejs3": {
...@@ -1332,6 +1339,23 @@ ...@@ -1332,6 +1339,23 @@
1332 "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", 1339 "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
1333 "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" 1340 "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
1334 }, 1341 },
1342 + "node_modules/@emotion/cache": {
1343 + "version": "11.4.0",
1344 + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.4.0.tgz",
1345 + "integrity": "sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g==",
1346 + "dependencies": {
1347 + "@emotion/memoize": "^0.7.4",
1348 + "@emotion/sheet": "^1.0.0",
1349 + "@emotion/utils": "^1.0.0",
1350 + "@emotion/weak-memoize": "^0.2.5",
1351 + "stylis": "^4.0.3"
1352 + }
1353 + },
1354 + "node_modules/@emotion/hash": {
1355 + "version": "0.8.0",
1356 + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
1357 + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
1358 + },
1335 "node_modules/@emotion/is-prop-valid": { 1359 "node_modules/@emotion/is-prop-valid": {
1336 "version": "0.8.8", 1360 "version": "0.8.8",
1337 "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", 1361 "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
...@@ -1345,6 +1369,49 @@ ...@@ -1345,6 +1369,49 @@
1345 "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", 1369 "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
1346 "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" 1370 "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
1347 }, 1371 },
1372 + "node_modules/@emotion/react": {
1373 + "version": "11.4.1",
1374 + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.4.1.tgz",
1375 + "integrity": "sha512-pRegcsuGYj4FCdZN6j5vqCALkNytdrKw3TZMekTzNXixRg4wkLsU5QEaBG5LC6l01Vppxlp7FE3aTHpIG5phLg==",
1376 + "dependencies": {
1377 + "@babel/runtime": "^7.13.10",
1378 + "@emotion/cache": "^11.4.0",
1379 + "@emotion/serialize": "^1.0.2",
1380 + "@emotion/sheet": "^1.0.2",
1381 + "@emotion/utils": "^1.0.0",
1382 + "@emotion/weak-memoize": "^0.2.5",
1383 + "hoist-non-react-statics": "^3.3.1"
1384 + },
1385 + "peerDependencies": {
1386 + "@babel/core": "^7.0.0",
1387 + "react": ">=16.8.0"
1388 + },
1389 + "peerDependenciesMeta": {
1390 + "@babel/core": {
1391 + "optional": true
1392 + },
1393 + "@types/react": {
1394 + "optional": true
1395 + }
1396 + }
1397 + },
1398 + "node_modules/@emotion/serialize": {
1399 + "version": "1.0.2",
1400 + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
1401 + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
1402 + "dependencies": {
1403 + "@emotion/hash": "^0.8.0",
1404 + "@emotion/memoize": "^0.7.4",
1405 + "@emotion/unitless": "^0.7.5",
1406 + "@emotion/utils": "^1.0.0",
1407 + "csstype": "^3.0.2"
1408 + }
1409 + },
1410 + "node_modules/@emotion/sheet": {
1411 + "version": "1.0.2",
1412 + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.2.tgz",
1413 + "integrity": "sha512-QQPB1B70JEVUHuNtzjHftMGv6eC3Y9wqavyarj4x4lg47RACkeSfNo5pxIOKizwS9AEFLohsqoaxGQj4p0vSIw=="
1414 + },
1348 "node_modules/@emotion/stylis": { 1415 "node_modules/@emotion/stylis": {
1349 "version": "0.8.5", 1416 "version": "0.8.5",
1350 "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", 1417 "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
...@@ -1355,6 +1422,16 @@ ...@@ -1355,6 +1422,16 @@
1355 "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", 1422 "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
1356 "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" 1423 "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
1357 }, 1424 },
1425 + "node_modules/@emotion/utils": {
1426 + "version": "1.0.0",
1427 + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
1428 + "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
1429 + },
1430 + "node_modules/@emotion/weak-memoize": {
1431 + "version": "0.2.5",
1432 + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
1433 + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
1434 + },
1358 "node_modules/@eslint/eslintrc": { 1435 "node_modules/@eslint/eslintrc": {
1359 "version": "0.4.3", 1436 "version": "0.4.3",
1360 "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", 1437 "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
...@@ -2229,17 +2306,6 @@ ...@@ -2229,17 +2306,6 @@
2229 "node": ">=10" 2306 "node": ">=10"
2230 } 2307 }
2231 }, 2308 },
2232 - "node_modules/@testing-library/dom/node_modules/@babel/runtime": {
2233 - "version": "7.14.8",
2234 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
2235 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
2236 - "dependencies": {
2237 - "regenerator-runtime": "^0.13.4"
2238 - },
2239 - "engines": {
2240 - "node": ">=6.9.0"
2241 - }
2242 - },
2243 "node_modules/@testing-library/dom/node_modules/chalk": { 2309 "node_modules/@testing-library/dom/node_modules/chalk": {
2244 "version": "4.1.2", 2310 "version": "4.1.2",
2245 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 2311 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
...@@ -2276,17 +2342,6 @@ ...@@ -2276,17 +2342,6 @@
2276 "yarn": ">=1" 2342 "yarn": ">=1"
2277 } 2343 }
2278 }, 2344 },
2279 - "node_modules/@testing-library/jest-dom/node_modules/@babel/runtime": {
2280 - "version": "7.14.8",
2281 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
2282 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
2283 - "dependencies": {
2284 - "regenerator-runtime": "^0.13.4"
2285 - },
2286 - "engines": {
2287 - "node": ">=6.9.0"
2288 - }
2289 - },
2290 "node_modules/@testing-library/jest-dom/node_modules/chalk": { 2345 "node_modules/@testing-library/jest-dom/node_modules/chalk": {
2291 "version": "3.0.0", 2346 "version": "3.0.0",
2292 "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", 2347 "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
...@@ -2315,17 +2370,6 @@ ...@@ -2315,17 +2370,6 @@
2315 "react-dom": "*" 2370 "react-dom": "*"
2316 } 2371 }
2317 }, 2372 },
2318 - "node_modules/@testing-library/react/node_modules/@babel/runtime": {
2319 - "version": "7.14.8",
2320 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
2321 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
2322 - "dependencies": {
2323 - "regenerator-runtime": "^0.13.4"
2324 - },
2325 - "engines": {
2326 - "node": ">=6.9.0"
2327 - }
2328 - },
2329 "node_modules/@testing-library/user-event": { 2373 "node_modules/@testing-library/user-event": {
2330 "version": "12.8.3", 2374 "version": "12.8.3",
2331 "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz", 2375 "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz",
...@@ -2341,17 +2385,6 @@ ...@@ -2341,17 +2385,6 @@
2341 "@testing-library/dom": ">=7.21.4" 2385 "@testing-library/dom": ">=7.21.4"
2342 } 2386 }
2343 }, 2387 },
2344 - "node_modules/@testing-library/user-event/node_modules/@babel/runtime": {
2345 - "version": "7.14.8",
2346 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
2347 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
2348 - "dependencies": {
2349 - "regenerator-runtime": "^0.13.4"
2350 - },
2351 - "engines": {
2352 - "node": ">=6.9.0"
2353 - }
2354 - },
2355 "node_modules/@types/anymatch": { 2388 "node_modules/@types/anymatch": {
2356 "version": "1.3.1", 2389 "version": "1.3.1",
2357 "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", 2390 "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
...@@ -2536,6 +2569,15 @@ ...@@ -2536,6 +2569,15 @@
2536 "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", 2569 "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
2537 "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" 2570 "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
2538 }, 2571 },
2572 + "node_modules/@types/qrcode": {
2573 + "version": "1.4.1",
2574 + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.4.1.tgz",
2575 + "integrity": "sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA==",
2576 + "dev": true,
2577 + "dependencies": {
2578 + "@types/node": "*"
2579 + }
2580 + },
2539 "node_modules/@types/react": { 2581 "node_modules/@types/react": {
2540 "version": "17.0.16", 2582 "version": "17.0.16",
2541 "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.16.tgz", 2583 "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.16.tgz",
...@@ -4813,6 +4855,25 @@ ...@@ -4813,6 +4855,25 @@
4813 "isarray": "^1.0.0" 4855 "isarray": "^1.0.0"
4814 } 4856 }
4815 }, 4857 },
4858 + "node_modules/buffer-alloc": {
4859 + "version": "1.2.0",
4860 + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
4861 + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
4862 + "dependencies": {
4863 + "buffer-alloc-unsafe": "^1.1.0",
4864 + "buffer-fill": "^1.0.0"
4865 + }
4866 + },
4867 + "node_modules/buffer-alloc-unsafe": {
4868 + "version": "1.1.0",
4869 + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
4870 + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
4871 + },
4872 + "node_modules/buffer-fill": {
4873 + "version": "1.0.0",
4874 + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
4875 + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
4876 + },
4816 "node_modules/buffer-from": { 4877 "node_modules/buffer-from": {
4817 "version": "1.1.1", 4878 "version": "1.1.1",
4818 "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 4879 "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
...@@ -6483,6 +6544,11 @@ ...@@ -6483,6 +6544,11 @@
6483 "randombytes": "^2.0.0" 6544 "randombytes": "^2.0.0"
6484 } 6545 }
6485 }, 6546 },
6547 + "node_modules/dijkstrajs": {
6548 + "version": "1.0.2",
6549 + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
6550 + "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg=="
6551 + },
6486 "node_modules/dir-glob": { 6552 "node_modules/dir-glob": {
6487 "version": "3.0.1", 6553 "version": "3.0.1",
6488 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 6554 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
...@@ -12762,6 +12828,14 @@ ...@@ -12762,6 +12828,14 @@
12762 "node": ">=6" 12828 "node": ">=6"
12763 } 12829 }
12764 }, 12830 },
12831 + "node_modules/pngjs": {
12832 + "version": "3.4.0",
12833 + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
12834 + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
12835 + "engines": {
12836 + "node": ">=4.0.0"
12837 + }
12838 + },
12765 "node_modules/pnp-webpack-plugin": { 12839 "node_modules/pnp-webpack-plugin": {
12766 "version": "1.6.4", 12840 "version": "1.6.4",
12767 "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", 12841 "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz",
...@@ -14095,6 +14169,193 @@ ...@@ -14095,6 +14169,193 @@
14095 "teleport": ">=0.2.0" 14169 "teleport": ">=0.2.0"
14096 } 14170 }
14097 }, 14171 },
14172 + "node_modules/qrcode": {
14173 + "version": "1.4.4",
14174 + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
14175 + "integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==",
14176 + "dependencies": {
14177 + "buffer": "^5.4.3",
14178 + "buffer-alloc": "^1.2.0",
14179 + "buffer-from": "^1.1.1",
14180 + "dijkstrajs": "^1.0.1",
14181 + "isarray": "^2.0.1",
14182 + "pngjs": "^3.3.0",
14183 + "yargs": "^13.2.4"
14184 + },
14185 + "bin": {
14186 + "qrcode": "bin/qrcode"
14187 + },
14188 + "engines": {
14189 + "node": ">=4"
14190 + }
14191 + },
14192 + "node_modules/qrcode/node_modules/ansi-regex": {
14193 + "version": "4.1.0",
14194 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
14195 + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
14196 + "engines": {
14197 + "node": ">=6"
14198 + }
14199 + },
14200 + "node_modules/qrcode/node_modules/ansi-styles": {
14201 + "version": "3.2.1",
14202 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
14203 + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
14204 + "dependencies": {
14205 + "color-convert": "^1.9.0"
14206 + },
14207 + "engines": {
14208 + "node": ">=4"
14209 + }
14210 + },
14211 + "node_modules/qrcode/node_modules/buffer": {
14212 + "version": "5.7.1",
14213 + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
14214 + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
14215 + "funding": [
14216 + {
14217 + "type": "github",
14218 + "url": "https://github.com/sponsors/feross"
14219 + },
14220 + {
14221 + "type": "patreon",
14222 + "url": "https://www.patreon.com/feross"
14223 + },
14224 + {
14225 + "type": "consulting",
14226 + "url": "https://feross.org/support"
14227 + }
14228 + ],
14229 + "dependencies": {
14230 + "base64-js": "^1.3.1",
14231 + "ieee754": "^1.1.13"
14232 + }
14233 + },
14234 + "node_modules/qrcode/node_modules/cliui": {
14235 + "version": "5.0.0",
14236 + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
14237 + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
14238 + "dependencies": {
14239 + "string-width": "^3.1.0",
14240 + "strip-ansi": "^5.2.0",
14241 + "wrap-ansi": "^5.1.0"
14242 + }
14243 + },
14244 + "node_modules/qrcode/node_modules/emoji-regex": {
14245 + "version": "7.0.3",
14246 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
14247 + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
14248 + },
14249 + "node_modules/qrcode/node_modules/find-up": {
14250 + "version": "3.0.0",
14251 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
14252 + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
14253 + "dependencies": {
14254 + "locate-path": "^3.0.0"
14255 + },
14256 + "engines": {
14257 + "node": ">=6"
14258 + }
14259 + },
14260 + "node_modules/qrcode/node_modules/is-fullwidth-code-point": {
14261 + "version": "2.0.0",
14262 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
14263 + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
14264 + "engines": {
14265 + "node": ">=4"
14266 + }
14267 + },
14268 + "node_modules/qrcode/node_modules/isarray": {
14269 + "version": "2.0.5",
14270 + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
14271 + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
14272 + },
14273 + "node_modules/qrcode/node_modules/locate-path": {
14274 + "version": "3.0.0",
14275 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
14276 + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
14277 + "dependencies": {
14278 + "p-locate": "^3.0.0",
14279 + "path-exists": "^3.0.0"
14280 + },
14281 + "engines": {
14282 + "node": ">=6"
14283 + }
14284 + },
14285 + "node_modules/qrcode/node_modules/p-locate": {
14286 + "version": "3.0.0",
14287 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
14288 + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
14289 + "dependencies": {
14290 + "p-limit": "^2.0.0"
14291 + },
14292 + "engines": {
14293 + "node": ">=6"
14294 + }
14295 + },
14296 + "node_modules/qrcode/node_modules/string-width": {
14297 + "version": "3.1.0",
14298 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
14299 + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
14300 + "dependencies": {
14301 + "emoji-regex": "^7.0.1",
14302 + "is-fullwidth-code-point": "^2.0.0",
14303 + "strip-ansi": "^5.1.0"
14304 + },
14305 + "engines": {
14306 + "node": ">=6"
14307 + }
14308 + },
14309 + "node_modules/qrcode/node_modules/strip-ansi": {
14310 + "version": "5.2.0",
14311 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
14312 + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
14313 + "dependencies": {
14314 + "ansi-regex": "^4.1.0"
14315 + },
14316 + "engines": {
14317 + "node": ">=6"
14318 + }
14319 + },
14320 + "node_modules/qrcode/node_modules/wrap-ansi": {
14321 + "version": "5.1.0",
14322 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
14323 + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
14324 + "dependencies": {
14325 + "ansi-styles": "^3.2.0",
14326 + "string-width": "^3.0.0",
14327 + "strip-ansi": "^5.0.0"
14328 + },
14329 + "engines": {
14330 + "node": ">=6"
14331 + }
14332 + },
14333 + "node_modules/qrcode/node_modules/yargs": {
14334 + "version": "13.3.2",
14335 + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
14336 + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
14337 + "dependencies": {
14338 + "cliui": "^5.0.0",
14339 + "find-up": "^3.0.0",
14340 + "get-caller-file": "^2.0.1",
14341 + "require-directory": "^2.1.1",
14342 + "require-main-filename": "^2.0.0",
14343 + "set-blocking": "^2.0.0",
14344 + "string-width": "^3.0.0",
14345 + "which-module": "^2.0.0",
14346 + "y18n": "^4.0.0",
14347 + "yargs-parser": "^13.1.2"
14348 + }
14349 + },
14350 + "node_modules/qrcode/node_modules/yargs-parser": {
14351 + "version": "13.1.2",
14352 + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
14353 + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
14354 + "dependencies": {
14355 + "camelcase": "^5.0.0",
14356 + "decamelize": "^1.2.0"
14357 + }
14358 + },
14098 "node_modules/qs": { 14359 "node_modules/qs": {
14099 "version": "6.7.0", 14360 "version": "6.7.0",
14100 "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 14361 "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
...@@ -14737,6 +14998,18 @@ ...@@ -14737,6 +14998,18 @@
14737 "node": ">=0.10.0" 14998 "node": ">=0.10.0"
14738 } 14999 }
14739 }, 15000 },
15001 + "node_modules/react-spinners": {
15002 + "version": "0.11.0",
15003 + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.11.0.tgz",
15004 + "integrity": "sha512-rDZc0ABWn/M1OryboGsWVmIPg8uYWl0L35jPUhr40+Yg+syVPjeHwvnB7XWaRpaKus3M0cG9BiJA+ZB0dAwWyw==",
15005 + "dependencies": {
15006 + "@emotion/react": "^11.1.4"
15007 + },
15008 + "peerDependencies": {
15009 + "react": "^16.0.0 || ^17.0.0",
15010 + "react-dom": "^16.0.0 || ^17.0.0"
15011 + }
15012 + },
14740 "node_modules/read-pkg": { 15013 "node_modules/read-pkg": {
14741 "version": "5.2.0", 15014 "version": "5.2.0",
14742 "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", 15015 "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
...@@ -16776,6 +17049,11 @@ ...@@ -16776,6 +17049,11 @@
16776 "node": ">=8" 17049 "node": ">=8"
16777 } 17050 }
16778 }, 17051 },
17052 + "node_modules/stylis": {
17053 + "version": "4.0.10",
17054 + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
17055 + "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
17056 + },
16779 "node_modules/supports-color": { 17057 "node_modules/supports-color": {
16780 "version": "7.2.0", 17058 "version": "7.2.0",
16781 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 17059 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
...@@ -20048,9 +20326,9 @@ ...@@ -20048,9 +20326,9 @@
20048 } 20326 }
20049 }, 20327 },
20050 "@babel/runtime": { 20328 "@babel/runtime": {
20051 - "version": "7.12.18", 20329 + "version": "7.15.4",
20052 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.18.tgz", 20330 + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
20053 - "integrity": "sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg==", 20331 + "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
20054 "requires": { 20332 "requires": {
20055 "regenerator-runtime": "^0.13.4" 20333 "regenerator-runtime": "^0.13.4"
20056 } 20334 }
...@@ -20124,6 +20402,23 @@ ...@@ -20124,6 +20402,23 @@
20124 "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", 20402 "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
20125 "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" 20403 "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
20126 }, 20404 },
20405 + "@emotion/cache": {
20406 + "version": "11.4.0",
20407 + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.4.0.tgz",
20408 + "integrity": "sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g==",
20409 + "requires": {
20410 + "@emotion/memoize": "^0.7.4",
20411 + "@emotion/sheet": "^1.0.0",
20412 + "@emotion/utils": "^1.0.0",
20413 + "@emotion/weak-memoize": "^0.2.5",
20414 + "stylis": "^4.0.3"
20415 + }
20416 + },
20417 + "@emotion/hash": {
20418 + "version": "0.8.0",
20419 + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
20420 + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
20421 + },
20127 "@emotion/is-prop-valid": { 20422 "@emotion/is-prop-valid": {
20128 "version": "0.8.8", 20423 "version": "0.8.8",
20129 "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", 20424 "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
...@@ -20137,6 +20432,37 @@ ...@@ -20137,6 +20432,37 @@
20137 "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", 20432 "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
20138 "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" 20433 "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
20139 }, 20434 },
20435 + "@emotion/react": {
20436 + "version": "11.4.1",
20437 + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.4.1.tgz",
20438 + "integrity": "sha512-pRegcsuGYj4FCdZN6j5vqCALkNytdrKw3TZMekTzNXixRg4wkLsU5QEaBG5LC6l01Vppxlp7FE3aTHpIG5phLg==",
20439 + "requires": {
20440 + "@babel/runtime": "^7.13.10",
20441 + "@emotion/cache": "^11.4.0",
20442 + "@emotion/serialize": "^1.0.2",
20443 + "@emotion/sheet": "^1.0.2",
20444 + "@emotion/utils": "^1.0.0",
20445 + "@emotion/weak-memoize": "^0.2.5",
20446 + "hoist-non-react-statics": "^3.3.1"
20447 + }
20448 + },
20449 + "@emotion/serialize": {
20450 + "version": "1.0.2",
20451 + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
20452 + "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
20453 + "requires": {
20454 + "@emotion/hash": "^0.8.0",
20455 + "@emotion/memoize": "^0.7.4",
20456 + "@emotion/unitless": "^0.7.5",
20457 + "@emotion/utils": "^1.0.0",
20458 + "csstype": "^3.0.2"
20459 + }
20460 + },
20461 + "@emotion/sheet": {
20462 + "version": "1.0.2",
20463 + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.2.tgz",
20464 + "integrity": "sha512-QQPB1B70JEVUHuNtzjHftMGv6eC3Y9wqavyarj4x4lg47RACkeSfNo5pxIOKizwS9AEFLohsqoaxGQj4p0vSIw=="
20465 + },
20140 "@emotion/stylis": { 20466 "@emotion/stylis": {
20141 "version": "0.8.5", 20467 "version": "0.8.5",
20142 "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", 20468 "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
...@@ -20147,6 +20473,16 @@ ...@@ -20147,6 +20473,16 @@
20147 "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", 20473 "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
20148 "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" 20474 "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
20149 }, 20475 },
20476 + "@emotion/utils": {
20477 + "version": "1.0.0",
20478 + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
20479 + "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
20480 + },
20481 + "@emotion/weak-memoize": {
20482 + "version": "0.2.5",
20483 + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
20484 + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
20485 + },
20150 "@eslint/eslintrc": { 20486 "@eslint/eslintrc": {
20151 "version": "0.4.3", 20487 "version": "0.4.3",
20152 "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", 20488 "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
...@@ -20777,14 +21113,6 @@ ...@@ -20777,14 +21113,6 @@
20777 "pretty-format": "^26.6.2" 21113 "pretty-format": "^26.6.2"
20778 }, 21114 },
20779 "dependencies": { 21115 "dependencies": {
20780 - "@babel/runtime": {
20781 - "version": "7.14.8",
20782 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
20783 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
20784 - "requires": {
20785 - "regenerator-runtime": "^0.13.4"
20786 - }
20787 - },
20788 "chalk": { 21116 "chalk": {
20789 "version": "4.1.2", 21117 "version": "4.1.2",
20790 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 21118 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
...@@ -20812,14 +21140,6 @@ ...@@ -20812,14 +21140,6 @@
20812 "redent": "^3.0.0" 21140 "redent": "^3.0.0"
20813 }, 21141 },
20814 "dependencies": { 21142 "dependencies": {
20815 - "@babel/runtime": {
20816 - "version": "7.14.8",
20817 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
20818 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
20819 - "requires": {
20820 - "regenerator-runtime": "^0.13.4"
20821 - }
20822 - },
20823 "chalk": { 21143 "chalk": {
20824 "version": "3.0.0", 21144 "version": "3.0.0",
20825 "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", 21145 "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
...@@ -20838,16 +21158,6 @@ ...@@ -20838,16 +21158,6 @@
20838 "requires": { 21158 "requires": {
20839 "@babel/runtime": "^7.12.5", 21159 "@babel/runtime": "^7.12.5",
20840 "@testing-library/dom": "^7.28.1" 21160 "@testing-library/dom": "^7.28.1"
20841 - },
20842 - "dependencies": {
20843 - "@babel/runtime": {
20844 - "version": "7.14.8",
20845 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
20846 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
20847 - "requires": {
20848 - "regenerator-runtime": "^0.13.4"
20849 - }
20850 - }
20851 } 21161 }
20852 }, 21162 },
20853 "@testing-library/user-event": { 21163 "@testing-library/user-event": {
...@@ -20856,16 +21166,6 @@ ...@@ -20856,16 +21166,6 @@
20856 "integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==", 21166 "integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==",
20857 "requires": { 21167 "requires": {
20858 "@babel/runtime": "^7.12.5" 21168 "@babel/runtime": "^7.12.5"
20859 - },
20860 - "dependencies": {
20861 - "@babel/runtime": {
20862 - "version": "7.14.8",
20863 - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
20864 - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
20865 - "requires": {
20866 - "regenerator-runtime": "^0.13.4"
20867 - }
20868 - }
20869 } 21169 }
20870 }, 21170 },
20871 "@types/anymatch": { 21171 "@types/anymatch": {
...@@ -21056,6 +21356,15 @@ ...@@ -21056,6 +21356,15 @@
21056 "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", 21356 "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
21057 "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" 21357 "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
21058 }, 21358 },
21359 + "@types/qrcode": {
21360 + "version": "1.4.1",
21361 + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.4.1.tgz",
21362 + "integrity": "sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA==",
21363 + "dev": true,
21364 + "requires": {
21365 + "@types/node": "*"
21366 + }
21367 + },
21059 "@types/react": { 21368 "@types/react": {
21060 "version": "17.0.16", 21369 "version": "17.0.16",
21061 "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.16.tgz", 21370 "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.16.tgz",
...@@ -22917,6 +23226,25 @@ ...@@ -22917,6 +23226,25 @@
22917 "isarray": "^1.0.0" 23226 "isarray": "^1.0.0"
22918 } 23227 }
22919 }, 23228 },
23229 + "buffer-alloc": {
23230 + "version": "1.2.0",
23231 + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
23232 + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
23233 + "requires": {
23234 + "buffer-alloc-unsafe": "^1.1.0",
23235 + "buffer-fill": "^1.0.0"
23236 + }
23237 + },
23238 + "buffer-alloc-unsafe": {
23239 + "version": "1.1.0",
23240 + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
23241 + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
23242 + },
23243 + "buffer-fill": {
23244 + "version": "1.0.0",
23245 + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
23246 + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
23247 + },
22920 "buffer-from": { 23248 "buffer-from": {
22921 "version": "1.1.1", 23249 "version": "1.1.1",
22922 "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 23250 "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
...@@ -24256,6 +24584,11 @@ ...@@ -24256,6 +24584,11 @@
24256 "randombytes": "^2.0.0" 24584 "randombytes": "^2.0.0"
24257 } 24585 }
24258 }, 24586 },
24587 + "dijkstrajs": {
24588 + "version": "1.0.2",
24589 + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
24590 + "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg=="
24591 + },
24259 "dir-glob": { 24592 "dir-glob": {
24260 "version": "3.0.1", 24593 "version": "3.0.1",
24261 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 24594 "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
...@@ -29110,6 +29443,11 @@ ...@@ -29110,6 +29443,11 @@
29110 } 29443 }
29111 } 29444 }
29112 }, 29445 },
29446 + "pngjs": {
29447 + "version": "3.4.0",
29448 + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
29449 + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
29450 + },
29113 "pnp-webpack-plugin": { 29451 "pnp-webpack-plugin": {
29114 "version": "1.6.4", 29452 "version": "1.6.4",
29115 "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", 29453 "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz",
...@@ -30180,6 +30518,148 @@ ...@@ -30180,6 +30518,148 @@
30180 "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 30518 "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
30181 "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" 30519 "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
30182 }, 30520 },
30521 + "qrcode": {
30522 + "version": "1.4.4",
30523 + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
30524 + "integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==",
30525 + "requires": {
30526 + "buffer": "^5.4.3",
30527 + "buffer-alloc": "^1.2.0",
30528 + "buffer-from": "^1.1.1",
30529 + "dijkstrajs": "^1.0.1",
30530 + "isarray": "^2.0.1",
30531 + "pngjs": "^3.3.0",
30532 + "yargs": "^13.2.4"
30533 + },
30534 + "dependencies": {
30535 + "ansi-regex": {
30536 + "version": "4.1.0",
30537 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
30538 + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
30539 + },
30540 + "ansi-styles": {
30541 + "version": "3.2.1",
30542 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
30543 + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
30544 + "requires": {
30545 + "color-convert": "^1.9.0"
30546 + }
30547 + },
30548 + "buffer": {
30549 + "version": "5.7.1",
30550 + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
30551 + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
30552 + "requires": {
30553 + "base64-js": "^1.3.1",
30554 + "ieee754": "^1.1.13"
30555 + }
30556 + },
30557 + "cliui": {
30558 + "version": "5.0.0",
30559 + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
30560 + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
30561 + "requires": {
30562 + "string-width": "^3.1.0",
30563 + "strip-ansi": "^5.2.0",
30564 + "wrap-ansi": "^5.1.0"
30565 + }
30566 + },
30567 + "emoji-regex": {
30568 + "version": "7.0.3",
30569 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
30570 + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
30571 + },
30572 + "find-up": {
30573 + "version": "3.0.0",
30574 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
30575 + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
30576 + "requires": {
30577 + "locate-path": "^3.0.0"
30578 + }
30579 + },
30580 + "is-fullwidth-code-point": {
30581 + "version": "2.0.0",
30582 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
30583 + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
30584 + },
30585 + "isarray": {
30586 + "version": "2.0.5",
30587 + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
30588 + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
30589 + },
30590 + "locate-path": {
30591 + "version": "3.0.0",
30592 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
30593 + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
30594 + "requires": {
30595 + "p-locate": "^3.0.0",
30596 + "path-exists": "^3.0.0"
30597 + }
30598 + },
30599 + "p-locate": {
30600 + "version": "3.0.0",
30601 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
30602 + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
30603 + "requires": {
30604 + "p-limit": "^2.0.0"
30605 + }
30606 + },
30607 + "string-width": {
30608 + "version": "3.1.0",
30609 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
30610 + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
30611 + "requires": {
30612 + "emoji-regex": "^7.0.1",
30613 + "is-fullwidth-code-point": "^2.0.0",
30614 + "strip-ansi": "^5.1.0"
30615 + }
30616 + },
30617 + "strip-ansi": {
30618 + "version": "5.2.0",
30619 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
30620 + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
30621 + "requires": {
30622 + "ansi-regex": "^4.1.0"
30623 + }
30624 + },
30625 + "wrap-ansi": {
30626 + "version": "5.1.0",
30627 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
30628 + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
30629 + "requires": {
30630 + "ansi-styles": "^3.2.0",
30631 + "string-width": "^3.0.0",
30632 + "strip-ansi": "^5.0.0"
30633 + }
30634 + },
30635 + "yargs": {
30636 + "version": "13.3.2",
30637 + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
30638 + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
30639 + "requires": {
30640 + "cliui": "^5.0.0",
30641 + "find-up": "^3.0.0",
30642 + "get-caller-file": "^2.0.1",
30643 + "require-directory": "^2.1.1",
30644 + "require-main-filename": "^2.0.0",
30645 + "set-blocking": "^2.0.0",
30646 + "string-width": "^3.0.0",
30647 + "which-module": "^2.0.0",
30648 + "y18n": "^4.0.0",
30649 + "yargs-parser": "^13.1.2"
30650 + }
30651 + },
30652 + "yargs-parser": {
30653 + "version": "13.1.2",
30654 + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
30655 + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
30656 + "requires": {
30657 + "camelcase": "^5.0.0",
30658 + "decamelize": "^1.2.0"
30659 + }
30660 + }
30661 + }
30662 + },
30183 "qs": { 30663 "qs": {
30184 "version": "6.7.0", 30664 "version": "6.7.0",
30185 "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 30665 "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
...@@ -30669,6 +31149,14 @@ ...@@ -30669,6 +31149,14 @@
30669 } 31149 }
30670 } 31150 }
30671 }, 31151 },
31152 + "react-spinners": {
31153 + "version": "0.11.0",
31154 + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.11.0.tgz",
31155 + "integrity": "sha512-rDZc0ABWn/M1OryboGsWVmIPg8uYWl0L35jPUhr40+Yg+syVPjeHwvnB7XWaRpaKus3M0cG9BiJA+ZB0dAwWyw==",
31156 + "requires": {
31157 + "@emotion/react": "^11.1.4"
31158 + }
31159 + },
30672 "read-pkg": { 31160 "read-pkg": {
30673 "version": "5.2.0", 31161 "version": "5.2.0",
30674 "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", 31162 "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
...@@ -32283,6 +32771,11 @@ ...@@ -32283,6 +32771,11 @@
32283 } 32771 }
32284 } 32772 }
32285 }, 32773 },
32774 + "stylis": {
32775 + "version": "4.0.10",
32776 + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
32777 + "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
32778 + },
32286 "supports-color": { 32779 "supports-color": {
32287 "version": "7.2.0", 32780 "version": "7.2.0",
32288 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 32781 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
14 "highcharts": "^9.2.0", 14 "highcharts": "^9.2.0",
15 "highcharts-react-official": "^3.0.0", 15 "highcharts-react-official": "^3.0.0",
16 "moment": "^2.29.1", 16 "moment": "^2.29.1",
17 + "qrcode": "^1.4.4",
17 "react": "^17.0.2", 18 "react": "^17.0.2",
18 "react-dom": "^17.0.2", 19 "react-dom": "^17.0.2",
19 "react-router-dom": "^5.2.0", 20 "react-router-dom": "^5.2.0",
...@@ -52,6 +53,7 @@ ...@@ -52,6 +53,7 @@
52 ] 53 ]
53 }, 54 },
54 "devDependencies": { 55 "devDependencies": {
56 + "@types/qrcode": "^1.4.1",
55 "@types/react-router-dom": "^5.1.8", 57 "@types/react-router-dom": "^5.1.8",
56 "@types/styled-components": "^5.1.12", 58 "@types/styled-components": "^5.1.12",
57 "@types/validator": "^13.6.3", 59 "@types/validator": "^13.6.3",
......
1 import { client } from "./client"; 1 import { client } from "./client";
2 2
3 export default { 3 export default {
4 - register : (Data : any) => { 4 + register : (Data : FormData) => {
5 return client.post('/auth/register', Data); 5 return client.post('/auth/register', Data);
6 }, 6 },
7 7
8 + searchHospital : (hospitalNm : string, page : number) => {
9 + return client.get('/auth/hospital', {
10 + params : {
11 + hospitalNm,
12 + page,
13 + },
14 + });
15 + },
16 +
8 registerDoctor : (Data : any) => { 17 registerDoctor : (Data : any) => {
9 return client.post('/auth/register/doctor', Data); 18 return client.post('/auth/register/doctor', Data);
10 }, 19 },
......
...@@ -44,8 +44,8 @@ export default { ...@@ -44,8 +44,8 @@ export default {
44 }, 44 },
45 }); 45 });
46 }, 46 },
47 - searchPatientById : (token : RecoilState<any>, patientId : string) => { 47 + searchPatientByContact : (token : RecoilState<any>, contact : string) => {
48 - return client.get(`/doctor/patient/search/${patientId}`, { 48 + return client.get(`/doctor/patient/search/${contact}`, {
49 headers : { 49 headers : {
50 Authorization : token, 50 Authorization : token,
51 }, 51 },
...@@ -65,4 +65,11 @@ export default { ...@@ -65,4 +65,11 @@ export default {
65 }, 65 },
66 }); 66 });
67 }, 67 },
68 + prescribeMedicine : (token : RecoilState<any>, Data : any) => {
69 + return client.post('/doctor/prescribe', Data, {
70 + headers : {
71 + Authorization : token,
72 + },
73 + });
74 + },
68 }; 75 };
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,6 +9,13 @@ export default { ...@@ -9,6 +9,13 @@ export default {
9 }, 9 },
10 }); 10 });
11 }, 11 },
12 + getDoctorSecReqList : (token : RecoilState<any>) => {
13 + return client.get('/manage/doctor/secession', {
14 + headers : {
15 + Authorization : token,
16 + },
17 + });
18 + },
12 getDoctorRegReqDetail : (token : RecoilState<any>, doctorId : string) => { 19 getDoctorRegReqDetail : (token : RecoilState<any>, doctorId : string) => {
13 return client.get(`/manage/doctor/${doctorId}`, { 20 return client.get(`/manage/doctor/${doctorId}`, {
14 headers : { 21 headers : {
...@@ -17,14 +24,21 @@ export default { ...@@ -17,14 +24,21 @@ export default {
17 }); 24 });
18 }, 25 },
19 acceptDoctorRegReq : (token : RecoilState<any>, Data : any) => { 26 acceptDoctorRegReq : (token : RecoilState<any>, Data : any) => {
20 - return client.post('/manage/doctor/accept', Data, { 27 + return client.patch('/manage/doctor/accept', Data, {
21 headers : { 28 headers : {
22 Authorization : token, 29 Authorization : token,
23 }, 30 },
24 }); 31 });
25 }, 32 },
26 rejectDoctorRegReq : (token : RecoilState<any>, Data : any) => { 33 rejectDoctorRegReq : (token : RecoilState<any>, Data : any) => {
27 - return client.post('/manage/doctor/reject', Data, { 34 + return client.patch('/manage/doctor/reject', Data, {
35 + headers : {
36 + Authorization : token,
37 + },
38 + });
39 + },
40 + acceptDoctorSecReq : (token : RecoilState<any>, Data : any) => {
41 + return client.patch('/manage/doctor/secession', Data, {
28 headers : { 42 headers : {
29 Authorization : token, 43 Authorization : token,
30 }, 44 },
......
1 +import styled from 'styled-components';
2 +
3 +export const Container = styled.div `
4 + width : 100%;
5 + position : relative;
6 +
7 + margin : 5px 0;
8 + padding : 20px 5px;
9 +
10 + border-top : 1px solid #ddd;
11 +
12 + display : flex;
13 + flex-direction : column;
14 +`;
15 +
16 +export const TermsWrapper = styled.div `
17 + width : 100%:
18 +
19 + display : flex;
20 + flex-direction : row;
21 +
22 + padding : 0 0 20px 0;
23 + margin : 0 0 10px 0;
24 +
25 + background-color : transparent;
26 + border : none;
27 +
28 + border-bottom : 1px solid #ddd;
29 +`;
30 +
31 +export const EachTerms = styled.button `
32 + color : #000;
33 + background-color : transparent;
34 +
35 + margin : 0 10px 0 0;
36 + padding : 0 0 5px 0;
37 +
38 + cursor : pointer;
39 +
40 + font-size : 13px;
41 +
42 + font-weight : 400;
43 + border : none;
44 + border-bottom : 1px solid;
45 +
46 + transition : .25s all;
47 +
48 + &:hover {
49 + color : #337DFF;
50 + opacity : .5;
51 + }
52 +`;
53 +
54 +export const InfoWrapper = styled.div `
55 + display : flex;
56 + flex-direction : row;
57 +
58 + border : none;
59 + background-color : transparent;
60 +`;
61 +
62 +export const LicenseWrapper = styled.div `
63 + flex : 1;
64 + display : flex;
65 + flex-direction : column;
66 +
67 + border : none;
68 + background-color : transparent;
69 + justify-content : center;
70 + align-items : flex-start;
71 +
72 +`;
73 +
74 +export const LicenseExplain = styled.div `
75 + color : #a0a0a0;
76 + font-size : 14px;
77 +
78 + font-weight : 400;
79 + padding : 0 5px;
80 +
81 + border : none;
82 +`;
83 +
84 +export const LicenseImg = styled.img `
85 + height : 60px;
86 + width : 150px;
87 +`;
88 +
89 +export const ServiceInfoWrapper = styled.div `
90 + flex : 3;
91 + display : flex;
92 + flex-direction : column;
93 + justify-content : center;
94 + align-items : center;
95 +
96 + border : none;
97 + background-color : transparent;
98 +`;
99 +
100 +export const ServiceInfoEach = styled.div `
101 + color : #d0d0d0;
102 + font-size : 13px;
103 +
104 + font-weight : 400
105 +`;
...\ No newline at end of file ...\ No newline at end of file
1 +import React from 'react';
2 +import { RouteComponentProps } from 'react-router';
3 +
4 +import * as Alert from '../../util/alertMessage';
5 +import * as styled from './FooterStyled';
6 +
7 +
8 +const ApiLicense = '/static/img/apiLicense.png';
9 +
10 +// eslint-disable-next-line @typescript-eslint/no-empty-interface
11 +interface FooterProps extends RouteComponentProps {}
12 +
13 +
14 +const Footer = (props : FooterProps) => {
15 +
16 +
17 + const onGoTerm = () => {
18 + Alert.onWarning('준비중입니다.', () => null);
19 + };
20 +
21 + const onGoPrivateLicense = () => {
22 + Alert.onWarning('준비중입니다.', () => null);
23 + };
24 +
25 + const onGoServiceCenter = () => {
26 + Alert.onWarning('준비중입니다.', () => null);
27 + };
28 +
29 + return (
30 + <styled.Container>
31 + <styled.TermsWrapper>
32 + <styled.EachTerms
33 + onClick = {onGoTerm}
34 + >
35 + 이용약관
36 + </styled.EachTerms>
37 + <styled.EachTerms
38 + onClick = {onGoPrivateLicense}
39 + >
40 + 개인정보처리방침
41 + </styled.EachTerms>
42 + <styled.EachTerms
43 + onClick = {onGoServiceCenter}
44 + >
45 + 고객센터
46 + </styled.EachTerms>
47 + </styled.TermsWrapper>
48 + <styled.InfoWrapper>
49 + <styled.LicenseWrapper>
50 + <styled.LicenseExplain>저작권</styled.LicenseExplain>
51 + <styled.LicenseImg src = {ApiLicense}/>
52 + </styled.LicenseWrapper>
53 + <styled.ServiceInfoWrapper>
54 + <styled.ServiceInfoEach>서비스명 : Smart Medicine Box (SMB)</styled.ServiceInfoEach>
55 + <styled.ServiceInfoEach>서비스제공 : IoT 약병 제작회</styled.ServiceInfoEach>
56 + <styled.ServiceInfoEach>담당자 : 박권수</styled.ServiceInfoEach>
57 + <styled.ServiceInfoEach>주소 : OOO도 OOO시 OOO구 OOO OOO OOO</styled.ServiceInfoEach>
58 + <styled.ServiceInfoEach>연락처 : 010 - 0000 - 0000</styled.ServiceInfoEach>
59 + </styled.ServiceInfoWrapper>
60 + <styled.LicenseWrapper/>
61 + </styled.InfoWrapper>
62 + </styled.Container>
63 + )
64 +};
65 +
66 +export default Footer;
...\ No newline at end of file ...\ No newline at end of file
...@@ -10,7 +10,6 @@ import * as styled from './HeaderStyled'; ...@@ -10,7 +10,6 @@ import * as styled from './HeaderStyled';
10 import { authApi } from '../../api'; 10 import { authApi } from '../../api';
11 11
12 const headerImg = '/static/img/pharmacy.png'; 12 const headerImg = '/static/img/pharmacy.png';
13 -const backButtonWhite = '/static/img/backButtonWhite.png';
14 const backButtonBlue = '/static/img/backButtonBlue.png'; 13 const backButtonBlue = '/static/img/backButtonBlue.png';
15 const logout = '/static/img/logout.png'; 14 const logout = '/static/img/logout.png';
16 15
......
1 +import styled, { keyframes } from 'styled-components';
2 +
3 +
4 +const ModalOn = keyframes `
5 + 0% {
6 + background-color : rgba(52, 52, 52, .0);
7 + }
8 + 20% {
9 + background-color : rgba(52, 52, 52, .2);
10 + }
11 + 40% {
12 + background-color : rgba(52, 52, 52, .4);
13 + }
14 + 60% {
15 + background-color : rgba(52, 52, 52, .5);
16 + }
17 + 80% {
18 + background-color : rgba(52, 52, 52, .6);
19 + }
20 + 100% {
21 + background-color : rgba(52, 52, 52, .7);
22 + }
23 +
24 +`;
25 +
26 +
27 +export const ModalContainer = styled.div `
28 + height : 100%;
29 + width : 100%;
30 + z-index : 99;
31 + position : absolute;
32 +
33 + display : flex;
34 + flex-direction : column;
35 +
36 + animation : ${ModalOn} .5s;
37 +
38 + background-color : rgba(52, 52, 52, .7);
39 +
40 +`;
41 +
42 +export const ModalClsButtonWrapper = styled.div `
43 + flex : 1;
44 +
45 + display : flex;
46 +
47 + justify-content : flex-end;
48 + align-items : center;
49 + padding : 0 20px;
50 +
51 + border : none;
52 + background-color : transprent;
53 +`;
54 +
55 +export const ModalClsButton = styled.button `
56 + border : none;
57 + background-color : transparent;
58 +
59 + cursor : pointer;
60 +
61 + color : #fff;
62 +
63 + display : flex;
64 + flex-direction : row;
65 +
66 + justify-content : center;
67 + align-items : center;
68 +
69 + transition : .25s all;
70 + &:hover {
71 + opacity : .5;
72 + }
73 +`;
74 +
75 +export const ModalClsButtonImg = styled.img `
76 + height : 20px;
77 + width : 20px;
78 +
79 + margin : 0 10px 0 0;
80 +`;
81 +
82 +export const ModalClsButtonText = styled.div `
83 + font-size : 18px;
84 + font-weight : 700;
85 +`;
86 +
87 +export const ModalContentWrapper = styled.div `
88 + flex : 8;
89 +
90 + display : flex;
91 + flex-direction : column;
92 +
93 + justify-content : center;
94 + align-items : center;
95 +
96 + border : none;
97 +`;
98 +
99 +export const ModalContent = styled.div `
100 + width : 600px;
101 + height : 400px;
102 +
103 + background-color : #fff;
104 + border : 1.2px solid #337DFF;
105 + border-radius : 5px;
106 +
107 + display : flex;
108 + flex-direction : column;
109 +
110 + justify-content : center;
111 + align-items : center;
112 +`;
...\ No newline at end of file ...\ No newline at end of file
1 +import React from 'react';
2 +
3 +import * as styled from './ModalStyled';
4 +
5 +const closeButton = '/static/img/close.png';
6 +
7 +
8 +interface ModalProps {
9 + children : JSX.Element,
10 + onModalClose : () => void;
11 +}
12 +
13 +const ModalContainer = (props : ModalProps) => {
14 + return (
15 + <styled.ModalContainer>
16 + <styled.ModalClsButtonWrapper>
17 + <styled.ModalClsButton
18 + onClick = {props.onModalClose}
19 + >
20 + <styled.ModalClsButtonImg src = {closeButton}/>
21 + <styled.ModalClsButtonText>닫기</styled.ModalClsButtonText>
22 + </styled.ModalClsButton>
23 + </styled.ModalClsButtonWrapper>
24 + <styled.ModalContentWrapper>
25 + <styled.ModalContent>
26 + {props.children}
27 + </styled.ModalContent>
28 + </styled.ModalContentWrapper>
29 + <styled.ModalClsButtonWrapper />
30 + </styled.ModalContainer>
31 + );
32 +};
33 +
34 +export default ModalContainer;
...\ No newline at end of file ...\ No newline at end of file
1 import moment from 'moment'; 1 import moment from 'moment';
2 2
3 -export const make = (chartData : any[], numberOfRow : number) => { 3 +export const make = (takeMedicineHist : any[], numberOfRow : number) => {
4 const now = new Date(); 4 const now = new Date();
5 const result : any = {}; 5 const result : any = {};
6 - new Array(numberOfRow).fill(null).forEach((item : any, index : number) => { 6 + new Array(numberOfRow).fill(null).forEach(() => {
7 const key = moment(now).format('MM/DD'); 7 const key = moment(now).format('MM/DD');
8 result[key] = 0; 8 result[key] = 0;
9 now.setDate(now.getDate() - 1); 9 now.setDate(now.getDate() - 1);
10 - }) 10 + });
11 11
12 - chartData.forEach((data : any) => { 12 + takeMedicineHist.forEach((data : any) => {
13 const key : string = moment(data.takeDate).format('MM/DD'); 13 const key : string = moment(data.takeDate).format('MM/DD');
14 - result[key] = result[key] + 1; 14 + !isNaN(result[key]) ? result[key] = result[key] + data.dosage : null;
15 }); 15 });
16 16
17 const categories : any = []; 17 const categories : any = [];
...@@ -28,5 +28,5 @@ export const make = (chartData : any[], numberOfRow : number) => { ...@@ -28,5 +28,5 @@ export const make = (chartData : any[], numberOfRow : number) => {
28 return { 28 return {
29 categories, 29 categories,
30 data, 30 data,
31 - } 31 + };
32 }; 32 };
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -9,6 +9,12 @@ export const token = atom({ ...@@ -9,6 +9,12 @@ export const token = atom({
9 effects_UNSTABLE : [persistAtom], 9 effects_UNSTABLE : [persistAtom],
10 }); 10 });
11 11
12 +export const userId = atom({
13 + key : 'userId',
14 + default : null,
15 + effects_UNSTABLE : [persistAtom],
16 +});
17 +
12 export const userTypeCd = atom({ 18 export const userTypeCd = atom({
13 key : 'userTypeCd', 19 key : 'userTypeCd',
14 default : 'NORMAL', 20 default : 'NORMAL',
......
...@@ -3,6 +3,7 @@ import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'; ...@@ -3,6 +3,7 @@ import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
3 3
4 import Error from '../components/error'; 4 import Error from '../components/error';
5 import Loading from '../components/Loading'; 5 import Loading from '../components/Loading';
6 +import Footer from '../components/Footer';
6 import { LoginContainer } from "./login"; 7 import { LoginContainer } from "./login";
7 import { RegisterContainer } from './register'; 8 import { RegisterContainer } from './register';
8 import { MainContainer } from "./main"; 9 import { MainContainer } from "./main";
......
...@@ -9,6 +9,7 @@ import * as Alert from '../../util/alertMessage'; ...@@ -9,6 +9,7 @@ import * as Alert from '../../util/alertMessage';
9 import moment from 'moment'; 9 import moment from 'moment';
10 10
11 import Header from '../../components/Header'; 11 import Header from '../../components/Header';
12 +import Footer from '../../components/Footer';
12 import BottleInfoPresenter from './BottleInfoPresenter'; 13 import BottleInfoPresenter from './BottleInfoPresenter';
13 14
14 import { doctorApi } from '../../api'; 15 import { doctorApi } from '../../api';
...@@ -35,6 +36,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -35,6 +36,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
35 takeMedicineHist : [], 36 takeMedicineHist : [],
36 }); 37 });
37 38
39 + //차트에 표시되는 행의 개수
38 const numberOfChartItem = 7; 40 const numberOfChartItem = 7;
39 const [chartOption, setChartOption] = useState<any>({ 41 const [chartOption, setChartOption] = useState<any>({
40 chart : { 42 chart : {
...@@ -51,19 +53,22 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -51,19 +53,22 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
51 categories : [], 53 categories : [],
52 }, 54 },
53 series : [{ 55 series : [{
54 - name : '약 복용 횟수', 56 + name : '약 복용 회분',
55 color : '#337DFF', 57 color : '#337DFF',
56 data : [], 58 data : [],
57 }], 59 }],
58 }); 60 });
61 + const [takeMedicineHist, setTakeMedicineHist] = useState<any[]>([]);
59 62
60 const [feedback, setFeedback] = useState<string>(''); 63 const [feedback, setFeedback] = useState<string>('');
61 const [fdbType, setFdbType] = useState<string>('RECOMMEND'); 64 const [fdbType, setFdbType] = useState<string>('RECOMMEND');
62 65
63 const [medicineInfoModal, setMedicineInfoModal] = useState<boolean>(false); 66 const [medicineInfoModal, setMedicineInfoModal] = useState<boolean>(false);
67 + const [modalType, setModalType] = useState<string>('hist'); //hist , info
64 68
65 69
66 const fetchData = async () => { 70 const fetchData = async () => {
71 + setModalType('hist');
67 setFeedback(''); 72 setFeedback('');
68 setFdbType('RECOMMEND'); 73 setFdbType('RECOMMEND');
69 setMedicineInfoModal(false); 74 setMedicineInfoModal(false);
...@@ -71,6 +76,12 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -71,6 +76,12 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
71 try { 76 try {
72 const result = await doctorApi.getPatientBottleDetail(token, bottleId); 77 const result = await doctorApi.getPatientBottleDetail(token, bottleId);
73 if (result.statusText === 'OK') { 78 if (result.statusText === 'OK') {
79 + setTakeMedicineHist(result.data.takeMedicineHist.map((takeMedicine : any) => {
80 + return ({
81 + ...takeMedicine,
82 + takeDate : moment(takeMedicine.takeDate).format('YYYY년 MM월 DD일 hh시 mm분'),
83 + });
84 + }));
74 const { categories, data } = makeChart.make(result.data.takeMedicineHist, numberOfChartItem); 85 const { categories, data } = makeChart.make(result.data.takeMedicineHist, numberOfChartItem);
75 setBottleInfo({ 86 setBottleInfo({
76 ...result.data, 87 ...result.data,
...@@ -97,9 +108,8 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -97,9 +108,8 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
97 Alert.onError('접근 권한이 없습니다.', () => props.history.push('/')); 108 Alert.onError('접근 권한이 없습니다.', () => props.history.push('/'));
98 } 109 }
99 } catch(e : any) { 110 } catch(e : any) {
100 - Alert.onError(e.response.data.error, () => props.history.push('/'));
101 -
102 console.log(e); 111 console.log(e);
112 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => props.history.push('/'));
103 } 113 }
104 }; 114 };
105 115
...@@ -123,7 +133,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -123,7 +133,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
123 Alert.onError('피드백 등록에 실패했습니다.', () => null); 133 Alert.onError('피드백 등록에 실패했습니다.', () => null);
124 } 134 }
125 } catch(e : any) { 135 } catch(e : any) {
126 - Alert.onError(e.response.data.error, () => fetchData()); 136 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => fetchData());
127 } 137 }
128 } else { 138 } else {
129 Alert.onError('피드백 내용을 입력하세요.', () => null); 139 Alert.onError('피드백 내용을 입력하세요.', () => null);
...@@ -134,6 +144,14 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -134,6 +144,14 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
134 144
135 }; 145 };
136 146
147 + const onViewTakeHist = () => {
148 + if(modalType === 'info') setModalType('hist');
149 + };
150 +
151 + const onViewMedicineInfo = () => {
152 + if(modalType === 'hist') setModalType('info');
153 + };
154 +
137 155
138 useEffect(() => { 156 useEffect(() => {
139 if(userTypeCd !== 'DOCTOR') { 157 if(userTypeCd !== 'DOCTOR') {
...@@ -148,8 +166,12 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -148,8 +166,12 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
148 <BottleInfoPresenter 166 <BottleInfoPresenter
149 bottleInfo = {bottleInfo} 167 bottleInfo = {bottleInfo}
150 chartOption = {chartOption} 168 chartOption = {chartOption}
169 + takeMedicineHist = {takeMedicineHist}
151 170
152 medicineInfoModal = {medicineInfoModal} 171 medicineInfoModal = {medicineInfoModal}
172 + modalType = {modalType}
173 + onViewTakeHist = {onViewTakeHist}
174 + onViewMedicineInfo = {onViewMedicineInfo}
153 setMedicineInfoModal = {setMedicineInfoModal} 175 setMedicineInfoModal = {setMedicineInfoModal}
154 176
155 feedback = {feedback} 177 feedback = {feedback}
...@@ -158,6 +180,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => { ...@@ -158,6 +180,7 @@ const BottleInfoContainer = (props : BottleInfoProps) => {
158 setFdbType = {setFdbType} 180 setFdbType = {setFdbType}
159 onSubmitFeedback = {onSubmitFeedback} 181 onSubmitFeedback = {onSubmitFeedback}
160 /> 182 />
183 + <Footer {...props}/>
161 </> 184 </>
162 ); 185 );
163 }; 186 };
......
...@@ -2,10 +2,10 @@ import React from 'react'; ...@@ -2,10 +2,10 @@ import React from 'react';
2 import HighCharts from 'highcharts'; 2 import HighCharts from 'highcharts';
3 import HighchartsReact from 'highcharts-react-official'; 3 import HighchartsReact from 'highcharts-react-official';
4 4
5 +import Modal from '../../components/Modal';
5 import * as styled from './BottleInfoStyled'; 6 import * as styled from './BottleInfoStyled';
6 7
7 const plus = '/static/img/plus.png'; 8 const plus = '/static/img/plus.png';
8 -const closeButton = '/static/img/close.png';
9 9
10 10
11 interface BottleInfoProps { 11 interface BottleInfoProps {
...@@ -16,8 +16,12 @@ interface BottleInfoProps { ...@@ -16,8 +16,12 @@ interface BottleInfoProps {
16 takeMedicineHist : any[]; 16 takeMedicineHist : any[];
17 }; 17 };
18 chartOption : any; 18 chartOption : any;
19 + takeMedicineHist : any[];
19 20
20 medicineInfoModal : boolean; 21 medicineInfoModal : boolean;
22 + modalType : string;
23 + onViewTakeHist : () => void;
24 + onViewMedicineInfo : () => void;
21 setMedicineInfoModal : (arg0 : boolean) => void; 25 setMedicineInfoModal : (arg0 : boolean) => void;
22 26
23 feedback : string; 27 feedback : string;
...@@ -32,42 +36,87 @@ const BottleInfoPresenter = (props : BottleInfoProps) => { ...@@ -32,42 +36,87 @@ const BottleInfoPresenter = (props : BottleInfoProps) => {
32 <styled.Container> 36 <styled.Container>
33 { 37 {
34 props.medicineInfoModal ? 38 props.medicineInfoModal ?
35 - <styled.ModalContainer> 39 + <Modal onModalClose = {() => props.setMedicineInfoModal(false)}>
36 - <styled.ModalClsButtonWrapper> 40 + <>
37 - <styled.ModalClsButton 41 + <styled.ModalTypeButtonWrapper>
38 - onClick = {() => props.setMedicineInfoModal(false)} 42 + <styled.ModalTypeButton
43 + isSelect = {props.modalType === 'hist'}
44 + onClick = {props.onViewTakeHist}
39 > 45 >
40 - <styled.ModalClsButtonImg src = {closeButton}/> 46 + 복용 기록
41 - <styled.ModalClsButtonText>닫기</styled.ModalClsButtonText> 47 + </styled.ModalTypeButton>
42 - </styled.ModalClsButton> 48 + <styled.ModalTypeButton
43 - </styled.ModalClsButtonWrapper> 49 + isSelect = {props.modalType === 'info'}
44 - <styled.ModalContentWrapper> 50 + onClick = {props.onViewMedicineInfo}
45 - <styled.ModalContent> 51 + >
46 - <styled.MedicineNameWrapper> 52 + 약 정보
47 - <styled.MedicineName>{props.bottleInfo.medicine.name}</styled.MedicineName> 53 + </styled.ModalTypeButton>
48 - <styled.MedicineName style = {{color : '#343434', fontSize : 15, marginTop : 4,}}>{props.bottleInfo.medicine.company}</styled.MedicineName> 54 + </styled.ModalTypeButtonWrapper>
49 - </styled.MedicineNameWrapper> 55 + {
50 - <styled.MedicineInfoWrapper> 56 + props.modalType === 'hist' ?
51 - <styled.MedicineEachInfoWrapper> 57 + <>
52 - <styled.MedicineEachInfoTitle>효능</styled.MedicineEachInfoTitle> 58 + <styled.MedicineNameWrapper>
53 - <styled.MedicineEachInfo>{props.bottleInfo.medicine.target}</styled.MedicineEachInfo> 59 + <styled.MedicineName>{`복용 기록`}</styled.MedicineName>
54 - </styled.MedicineEachInfoWrapper> 60 + <styled.MedicineName style = {{color : '#343434', fontSize : 15, marginTop : 4,}}>{`전체 : ${props.takeMedicineHist.length}건`}</styled.MedicineName>
55 - <styled.MedicineEachInfoWrapper> 61 + </styled.MedicineNameWrapper>
56 - <styled.MedicineEachInfoTitle>복용 정보</styled.MedicineEachInfoTitle> 62 + <styled.MedicineInfoWrapper>
57 - <styled.MedicineEachInfo>{props.bottleInfo.medicine.dosage}</styled.MedicineEachInfo> 63 + {
58 - </styled.MedicineEachInfoWrapper> 64 + props.takeMedicineHist.map((hist : any) => {
59 - <styled.MedicineEachInfoWrapper> 65 + return (
60 - <styled.MedicineEachInfoTitle style = {{color : '#FF3F3F', fontWeight : 'bold'}}>주의 사항</styled.MedicineEachInfoTitle> 66 + <styled.HistWrapper
61 - <styled.MedicineEachInfo style = {{color : '#9B0000'}}>{props.bottleInfo.medicine.warn}</styled.MedicineEachInfo> 67 + key = {hist._id}
62 - </styled.MedicineEachInfoWrapper> 68 + >
63 - <styled.MedicineEachInfoWrapper> 69 + <styled.HistDtmWrapper>
64 - <styled.MedicineEachInfoTitle style = {{color : '#FF3F3F', fontWeight : 'bold'}}>부작용</styled.MedicineEachInfoTitle> 70 + <styled.HistInfoEachWrapper style = {{fontSize : 11}}>복용 날짜</styled.HistInfoEachWrapper>
65 - <styled.MedicineEachInfo style = {{color : '#9B0000'}}>{props.bottleInfo.medicine.antiEffect}</styled.MedicineEachInfo> 71 + <styled.HistDtm>{hist.takeDate}</styled.HistDtm>
66 - </styled.MedicineEachInfoWrapper> 72 + </styled.HistDtmWrapper>
67 - </styled.MedicineInfoWrapper> 73 + <styled.HistInfoWrapper>
68 - </styled.ModalContent> 74 + <styled.HistInfoEachWrapper>
69 - </styled.ModalContentWrapper> 75 + 복용량
70 - </styled.ModalContainer> : null 76 + <styled.HistInfoEach>{hist.dosage}회분</styled.HistInfoEach>
77 + </styled.HistInfoEachWrapper>
78 + <styled.HistInfoEachWrapper>
79 + 약병 내 온도
80 + <styled.HistInfoEach>{hist.temperature}℃</styled.HistInfoEach>
81 + </styled.HistInfoEachWrapper>
82 + <styled.HistInfoEachWrapper>
83 + 약병 내 습도
84 + <styled.HistInfoEach>{hist.humidity}%</styled.HistInfoEach>
85 + </styled.HistInfoEachWrapper>
86 + </styled.HistInfoWrapper>
87 + </styled.HistWrapper>
88 + )
89 + })
90 + }
91 + </styled.MedicineInfoWrapper>
92 + </> :
93 + <>
94 + <styled.MedicineNameWrapper>
95 + <styled.MedicineName>{props.bottleInfo.medicine.name}</styled.MedicineName>
96 + <styled.MedicineName style = {{color : '#343434', fontSize : 15, marginTop : 4,}}>{props.bottleInfo.medicine.company}</styled.MedicineName>
97 + </styled.MedicineNameWrapper>
98 + <styled.MedicineInfoWrapper>
99 + <styled.MedicineEachInfoWrapper>
100 + <styled.MedicineEachInfoTitle>효능</styled.MedicineEachInfoTitle>
101 + <styled.MedicineEachInfo>{props.bottleInfo.medicine.target}</styled.MedicineEachInfo>
102 + </styled.MedicineEachInfoWrapper>
103 + <styled.MedicineEachInfoWrapper>
104 + <styled.MedicineEachInfoTitle>복용 정보</styled.MedicineEachInfoTitle>
105 + <styled.MedicineEachInfo>{props.bottleInfo.medicine.dosage}</styled.MedicineEachInfo>
106 + </styled.MedicineEachInfoWrapper>
107 + <styled.MedicineEachInfoWrapper>
108 + <styled.MedicineEachInfoTitle style = {{color : '#FF3F3F', fontWeight : 'bold'}}>주의 사항</styled.MedicineEachInfoTitle>
109 + <styled.MedicineEachInfo style = {{color : '#9B0000'}}>{props.bottleInfo.medicine.warn}</styled.MedicineEachInfo>
110 + </styled.MedicineEachInfoWrapper>
111 + <styled.MedicineEachInfoWrapper>
112 + <styled.MedicineEachInfoTitle style = {{color : '#FF3F3F', fontWeight : 'bold'}}>부작용</styled.MedicineEachInfoTitle>
113 + <styled.MedicineEachInfo style = {{color : '#9B0000'}}>{props.bottleInfo.medicine.antiEffect}</styled.MedicineEachInfo>
114 + </styled.MedicineEachInfoWrapper>
115 + </styled.MedicineInfoWrapper>
116 + </>
117 + }
118 + </>
119 + </Modal> : null
71 } 120 }
72 <styled.ChartAndFeedbackWrapper> 121 <styled.ChartAndFeedbackWrapper>
73 <styled.ChartWrapper> 122 <styled.ChartWrapper>
...@@ -107,7 +156,7 @@ const BottleInfoPresenter = (props : BottleInfoProps) => { ...@@ -107,7 +156,7 @@ const BottleInfoPresenter = (props : BottleInfoProps) => {
107 /> 156 />
108 <styled.NewFeedbackButtonWrapper> 157 <styled.NewFeedbackButtonWrapper>
109 <styled.NewFeedbackTypeButtonWrapper> 158 <styled.NewFeedbackTypeButtonWrapper>
110 - <styled.NewFeedbackTypeButtonEachWrapper> 159 + <styled.NewFeedbackTypeButtonEachWrapper>
111 <styled.NewFeedbackTypeButton 160 <styled.NewFeedbackTypeButton
112 valueType = 'RECOMMEND' 161 valueType = 'RECOMMEND'
113 selected = {props.fdbType === 'RECOMMEND'} 162 selected = {props.fdbType === 'RECOMMEND'}
...@@ -143,7 +192,6 @@ const BottleInfoPresenter = (props : BottleInfoProps) => { ...@@ -143,7 +192,6 @@ const BottleInfoPresenter = (props : BottleInfoProps) => {
143 <styled.NewFeedbackRegButton 192 <styled.NewFeedbackRegButton
144 onClick = {props.onSubmitFeedback} 193 onClick = {props.onSubmitFeedback}
145 > 194 >
146 - {/* <styled.NewFeedbackRegButtonImg /> */}
147 <styled.NewFeedbackRegButtonText>피드백<br/>등록</styled.NewFeedbackRegButtonText> 195 <styled.NewFeedbackRegButtonText>피드백<br/>등록</styled.NewFeedbackRegButtonText>
148 </styled.NewFeedbackRegButton> 196 </styled.NewFeedbackRegButton>
149 </styled.NewFeedbackButtonWrapper> 197 </styled.NewFeedbackButtonWrapper>
......
1 -import styled, { keyframes } from 'styled-components'; 1 +import styled from 'styled-components';
2 -
3 -
4 -const ModalOn = keyframes `
5 - 0% {
6 - background-color : rgba(52, 52, 52, .0);
7 - }
8 - 20% {
9 - background-color : rgba(52, 52, 52, .2);
10 - }
11 - 40% {
12 - background-color : rgba(52, 52, 52, .4);
13 - }
14 - 60% {
15 - background-color : rgba(52, 52, 52, .5);
16 - }
17 - 80% {
18 - background-color : rgba(52, 52, 52, .6);
19 - }
20 - 100% {
21 - background-color : rgba(52, 52, 52, .7);
22 - }
23 -
24 -`;
25 2
26 3
27 export const Container = styled.div ` 4 export const Container = styled.div `
...@@ -32,91 +9,106 @@ export const Container = styled.div ` ...@@ -32,91 +9,106 @@ export const Container = styled.div `
32 justify-content : center; 9 justify-content : center;
33 `; 10 `;
34 11
35 -export const ModalContainer = styled.div ` 12 +export const ModalTypeButtonWrapper = styled.div `
36 - height : 100%; 13 + border : none;
37 - width : 100%;
38 - z-index : 99;
39 - position : absolute;
40 -
41 display : flex; 14 display : flex;
42 - flex-direction : column; 15 + flex-direction : row;
43 -
44 - animation : ${ModalOn} .5s;
45 -
46 - background-color : rgba(52, 52, 52, .7);
47 -
48 -`;
49 -
50 -export const ModalClsButtonWrapper = styled.div `
51 - flex : 1;
52 16
53 - display : flex; 17 + width : 100%;
54 18
55 - justify-content : flex-end; 19 + justify-content : center;
56 align-items : center; 20 align-items : center;
57 - padding : 0 20px;
58 21
59 - border : none;
60 - background-color : transprent;
61 -`;
62 -
63 -export const ModalClsButton = styled.button `
64 - border : none;
65 background-color : transparent; 22 background-color : transparent;
23 +
24 + gap : 5%;
66 25
67 - cursor : pointer; 26 + padding : 3% 0 0 0;
27 +`;
68 28
69 - color : #fff; 29 +export const ModalTypeButton = styled.button<{isSelect : boolean}> `
30 + border : 1px solid #337DFF;
31 + border-radius : 3px;
32 + color : ${props => props.isSelect ? '#fff' : '#337DFF'};
33 + background-color : ${props => props.isSelect ? '#337DFF' : '#fff'};
70 34
71 - display : flex; 35 + padding : 1% 3%;
72 - flex-direction : row;
73 36
74 - justify-content : center; 37 + cursor : pointer;
75 - align-items : center; 38 +
39 + font-size : 16px;
40 + font-weight : ${props => props.isSelect ? '600' : '500'};
76 41
77 transition : .25s all; 42 transition : .25s all;
43 +
78 &:hover { 44 &:hover {
79 opacity : .5; 45 opacity : .5;
80 } 46 }
47 +
81 `; 48 `;
82 49
83 -export const ModalClsButtonImg = styled.img ` 50 +export const HistWrapper = styled.div `
84 - height : 20px; 51 + display : flex;
85 - width : 20px; 52 + flex-direction : column;
86 53
87 - margin : 0 10px 0 0; 54 + height : 65px;
88 -`; 55 + width : 100%;
89 56
90 -export const ModalClsButtonText = styled.div ` 57 + border : none;
91 - font-size : 18px; 58 + border-bottom : 1px solid #ddd;
92 - font-weight : 700;
93 `; 59 `;
94 60
95 -export const ModalContentWrapper = styled.div ` 61 +export const HistDtmWrapper = styled.div `
96 - flex : 8; 62 + flex : 2;
63 + padding : 0 3%;
64 +
65 + border : none;
97 66
98 display : flex; 67 display : flex;
99 flex-direction : column; 68 flex-direction : column;
100 69
101 justify-content : center; 70 justify-content : center;
102 - align-items : center;
103 71
104 - border : none;
105 `; 72 `;
106 73
107 -export const ModalContent = styled.div ` 74 +export const HistDtm = styled.div `
108 - width : 700px; 75 + font-size : 16px;
109 - height : 500px; 76 + color : #000;
77 +`;
78 +
79 +export const HistInfoWrapper = styled.div `
80 + flex : 1;
81 + padding : 0 3%;
82 +
83 + border : none;
110 84
111 - background-color : #fff;
112 - border : 1.2px solid #337DFF;
113 - border-radius : 5px;
114 -
115 display : flex; 85 display : flex;
116 - flex-direction : column; 86 + flex-direction : row;
87 + align-items : flex-start;
88 +
89 +`;
90 +
91 +export const HistInfoEachWrapper = styled.div `
92 + flex : 1;
93 + border : none;
117 94
118 - // justify-content : center; 95 + display : flex;
96 + flex-direction : row;
119 align-items : center; 97 align-items : center;
98 +
99 + gap : 5%;
100 +
101 + font-size : 14px;
102 + font-weight : 500;
103 +
104 + color : #a0a0a0;
105 +`;
106 +
107 +export const HistInfoEach = styled.div `
108 + font-size : 15px;
109 + font-weight : 600;
110 +
111 + color : #337DFF;
120 `; 112 `;
121 113
122 export const MedicineNameWrapper = styled.div ` 114 export const MedicineNameWrapper = styled.div `
...@@ -169,6 +161,7 @@ export const MedicineEachInfoWrapper = styled.div ` ...@@ -169,6 +161,7 @@ export const MedicineEachInfoWrapper = styled.div `
169 display : flex; 161 display : flex;
170 flex-direction : column; 162 flex-direction : column;
171 163
164 +
172 width : 80%; 165 width : 80%;
173 padding : 20px 10%; 166 padding : 20px 10%;
174 border : none; 167 border : none;
......
...@@ -23,6 +23,7 @@ const LoginContainer = (props : LoginProps) => { ...@@ -23,6 +23,7 @@ const LoginContainer = (props : LoginProps) => {
23 }); 23 });
24 24
25 const [token, setToken] = useRecoilState(recoilUtil.token); 25 const [token, setToken] = useRecoilState(recoilUtil.token);
26 + const [userId, setUserId] = useRecoilState(recoilUtil.userId);
26 const [userTypeCd, setUserTypeCd] = useRecoilState(recoilUtil.userTypeCd); 27 const [userTypeCd, setUserTypeCd] = useRecoilState(recoilUtil.userTypeCd);
27 28
28 29
...@@ -58,13 +59,14 @@ const LoginContainer = (props : LoginProps) => { ...@@ -58,13 +59,14 @@ const LoginContainer = (props : LoginProps) => {
58 const result : any = await authApi.login(loginForm); 59 const result : any = await authApi.login(loginForm);
59 if(result.statusText === 'OK' && result.data.userTypeCd !== 'NORMAL') { 60 if(result.statusText === 'OK' && result.data.userTypeCd !== 'NORMAL') {
60 setToken(result.data.token); 61 setToken(result.data.token);
62 + setUserId(loginForm.userId);
61 setUserTypeCd(result.data.userTypeCd); 63 setUserTypeCd(result.data.userTypeCd);
62 Alert.onSuccess('로그인 성공, 메인 화면으로 이동합니다.', () => props.history.push('/')); 64 Alert.onSuccess('로그인 성공, 메인 화면으로 이동합니다.', () => props.history.push('/'));
63 } else if(result.data.userTypeCd === 'NORMAL') { 65 } else if(result.data.userTypeCd === 'NORMAL') {
64 Alert.onError('권한이 없는 유저입니다.', () => props.history.push('/')); 66 Alert.onError('권한이 없는 유저입니다.', () => props.history.push('/'));
65 } 67 }
66 - } catch(e) { 68 + } catch(e : any) {
67 - Alert.onError(e.response.data.error, () => null); 69 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
68 } 70 }
69 71
70 }; 72 };
......
...@@ -6,6 +6,7 @@ import * as recoilUtil from '../../util/recoilUtil'; ...@@ -6,6 +6,7 @@ import * as recoilUtil from '../../util/recoilUtil';
6 6
7 7
8 import Header from '../../components/Header'; 8 import Header from '../../components/Header';
9 +import Footer from '../../components/Footer';
9 import DoctorMenuContainer from './doctor'; 10 import DoctorMenuContainer from './doctor';
10 import ManagerMenuContainer from './manager'; 11 import ManagerMenuContainer from './manager';
11 12
...@@ -32,6 +33,7 @@ const MainContainer = (props : MainProps) => { ...@@ -32,6 +33,7 @@ const MainContainer = (props : MainProps) => {
32 userTypeCd === 'MANAGER' ? 33 userTypeCd === 'MANAGER' ?
33 <ManagerMenuContainer {...props}/> : null 34 <ManagerMenuContainer {...props}/> : null
34 } 35 }
36 + <Footer {...props}/>
35 </> 37 </>
36 ); 38 );
37 }; 39 };
......
1 -import React from 'react';
2 -
3 -import * as styled from './MainStyled';
4 -
5 -
6 -interface MainProps {
7 - userTypeCd : string;
8 -}
9 -
10 -const MainPresenter = (props : MainProps) => {
11 - return (
12 - <styled.Container>
13 - <styled.InfoAndSearchWrapper>
14 - <styled.InfoWrapper>
15 - <styled.InfoSquare>
16 -
17 - </styled.InfoSquare>
18 - <styled.NewPatientButton>새 환자 등록</styled.NewPatientButton>
19 - </styled.InfoWrapper>
20 - <styled.SearchAndDetailWrapper>
21 - <styled.SearchBarWrapper>
22 - <styled.SearchBar
23 - placeholder = '환자 이름'
24 - />
25 - <styled.SearchButton>
26 - 검색
27 - </styled.SearchButton>
28 - </styled.SearchBarWrapper>
29 - <styled.SearchResultWrapper>
30 -
31 - </styled.SearchResultWrapper>
32 - </styled.SearchAndDetailWrapper>
33 - </styled.InfoAndSearchWrapper>
34 - <styled.BottleListWrapper>
35 - bottleListWrapper
36 - </styled.BottleListWrapper>
37 - </styled.Container>
38 - )
39 -};
40 -
41 -export default MainPresenter;
...\ No newline at end of file ...\ No newline at end of file
...@@ -11,13 +11,13 @@ import * as Alert from '../../../util/alertMessage'; ...@@ -11,13 +11,13 @@ import * as Alert from '../../../util/alertMessage';
11 import { doctorApi, medicineApi } from '../../../api'; 11 import { doctorApi, medicineApi } from '../../../api';
12 12
13 13
14 -//toDo : Generate QR Code By Medicine Id
15 14
16 type DoctorMenuProps = RouteComponentProps 15 type DoctorMenuProps = RouteComponentProps
17 16
18 const DoctorMenuContainer = (props : DoctorMenuProps) => { 17 const DoctorMenuContainer = (props : DoctorMenuProps) => {
19 18
20 const token = useRecoilValue(recoilUtil.token); 19 const token = useRecoilValue(recoilUtil.token);
20 + const userId = useRecoilValue(recoilUtil.userId);
21 const [loading, setLoading] = useRecoilState(recoilUtil.loading); 21 const [loading, setLoading] = useRecoilState(recoilUtil.loading);
22 22
23 const [doctorInfo, setDoctorInfo] = useState<any>({ 23 const [doctorInfo, setDoctorInfo] = useState<any>({
...@@ -33,7 +33,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -33,7 +33,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
33 const [info, setInfo] = useState<any>({ 33 const [info, setInfo] = useState<any>({
34 infoType : 'DOCTOR', 34 infoType : 'DOCTOR',
35 userNm : '', 35 userNm : '',
36 - userAge : 0, 36 + birth : '',
37 contact : '', 37 contact : '',
38 doctorType : '', 38 doctorType : '',
39 patientInfo : '', 39 patientInfo : '',
...@@ -48,13 +48,18 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -48,13 +48,18 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
48 const [editPatientInfo, setEditPatientInfo] = useState<string>(''); 48 const [editPatientInfo, setEditPatientInfo] = useState<string>('');
49 49
50 const [newPatientRegisterModal, setNewPatientRegisterModal] = useState<boolean>(false); 50 const [newPatientRegisterModal, setNewPatientRegisterModal] = useState<boolean>(false);
51 - const [newPatientSearchId, setNewPatientSearchId] = useState<string>(''); 51 + const [newPatientSearchContact, setNewPatientSearchContact] = useState<string>('');
52 const [newPatientSearchResult, setNewPatientSearchResult] = useState<any | null>(null); 52 const [newPatientSearchResult, setNewPatientSearchResult] = useState<any | null>(null);
53 53
54 const [prescribeModal, setPrescribeModal] = useState<boolean>(false); 54 const [prescribeModal, setPrescribeModal] = useState<boolean>(false);
55 + const [prescribeModalStep, setPrescribeModalStep] = useState<number>(1);
55 const [searchMedicineKeyword, setSearchMedicineKeyword] = useState<string>(''); 56 const [searchMedicineKeyword, setSearchMedicineKeyword] = useState<string>('');
56 const [medicineList, setMedicineList] = useState<any>([]); 57 const [medicineList, setMedicineList] = useState<any>([]);
57 const [prescribeMedicine, setPrescribeMedicine] = useState<any>(null); 58 const [prescribeMedicine, setPrescribeMedicine] = useState<any>(null);
59 + const [dailyDosage, setDailyDosage] = useState<string>('1');
60 + const [totalDay, setTotalDay] = useState<string>('1');
61 +
62 + const [qrcodeUrl, setQrcodeUrl] = useState<string | null>(null);
58 63
59 64
60 const fetchData = async() => { 65 const fetchData = async() => {
...@@ -69,7 +74,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -69,7 +74,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
69 userNm : doctorInfo.doctorNm, 74 userNm : doctorInfo.doctorNm,
70 doctorType : doctorInfo.doctorType, 75 doctorType : doctorInfo.doctorType,
71 contact : doctorInfo.contact, 76 contact : doctorInfo.contact,
72 - userAge : null, 77 + birth : null,
73 patientInfo : '', 78 patientInfo : '',
74 }); 79 });
75 80
...@@ -79,7 +84,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -79,7 +84,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
79 }).catch(error => console.log(error)); 84 }).catch(error => console.log(error));
80 } 85 }
81 setLoading(false); 86 setLoading(false);
82 - } catch(e) { 87 + } catch(e : any) {
83 console.log(e); 88 console.log(e);
84 setLoading(false); 89 setLoading(false);
85 } 90 }
...@@ -94,10 +99,12 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -94,10 +99,12 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
94 setLoading(true); 99 setLoading(true);
95 await doctorApi.getPatientDetail(token, patientId).then(res => { 100 await doctorApi.getPatientDetail(token, patientId).then(res => {
96 setPatientDetail(res.data); 101 setPatientDetail(res.data);
102 +
103 + const birth = res.data.profile.birth.split('-');
97 setInfo({ 104 setInfo({
98 infoType : 'PATIENT', 105 infoType : 'PATIENT',
99 userNm : res.data.profile.userNm, 106 userNm : res.data.profile.userNm,
100 - userAge : res.data.profile.userAge, 107 + birth : `${birth[0]}년 ${birth[1]}월 ${birth[2]}일`,
101 contact : res.data.profile.contact, 108 contact : res.data.profile.contact,
102 doctorType : null, 109 doctorType : null,
103 patientInfo : res.data.info, 110 patientInfo : res.data.info,
...@@ -118,7 +125,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -118,7 +125,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
118 userNm : doctorInfo.doctorNm, 125 userNm : doctorInfo.doctorNm,
119 doctorType : doctorInfo.doctorType, 126 doctorType : doctorInfo.doctorType,
120 contact : doctorInfo.contact, 127 contact : doctorInfo.contact,
121 - userAge : null, 128 + birth : null,
122 patientInfo : '', 129 patientInfo : '',
123 }); 130 });
124 setFilteringPatientList([]); 131 setFilteringPatientList([]);
...@@ -145,7 +152,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -145,7 +152,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
145 } 152 }
146 153
147 } catch(e : any) { 154 } catch(e : any) {
148 - Alert.onError(e.response.data.error, () => null); 155 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
149 } 156 }
150 }; 157 };
151 158
...@@ -158,15 +165,15 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -158,15 +165,15 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
158 }; 165 };
159 166
160 167
161 - const onSetNewPatientSearchId = (e : React.ChangeEvent<HTMLInputElement>) => { 168 + const onSetNewPatientSearchContact = (e : React.ChangeEvent<HTMLInputElement>) => {
162 - setNewPatientSearchId(e.target.value); 169 + setNewPatientSearchContact(e.target.value);
163 }; 170 };
164 171
165 - const onSearchNewPatientByEmail = async () => { 172 + const onSearchNewPatientByContact = async () => {
166 try { 173 try {
167 setLoading(true); 174 setLoading(true);
168 - await doctorApi.searchPatientById(token, newPatientSearchId).then(res => { 175 + await doctorApi.searchPatientByContact(token, newPatientSearchContact).then(res => {
169 - setNewPatientSearchResult(res.data); 176 + setNewPatientSearchResult(res.data.patientInfo);
170 setLoading(false); 177 setLoading(false);
171 }).catch(err => { 178 }).catch(err => {
172 console.log(err); 179 console.log(err);
...@@ -176,29 +183,30 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -176,29 +183,30 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
176 }); 183 });
177 } catch(e : any) { 184 } catch(e : any) {
178 setLoading(false); 185 setLoading(false);
179 - Alert.onError(e.response.data.error, () => null); 186 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
180 } 187 }
181 }; 188 };
182 189
183 const onRegisterNewPatient = () => { 190 const onRegisterNewPatient = () => {
184 if(newPatientSearchResult) { 191 if(newPatientSearchResult) {
185 - const { patientId, patientNm } = newPatientSearchResult; 192 + const { userId, userNm } = newPatientSearchResult;
186 const onRegisterReq = async () => { 193 const onRegisterReq = async () => {
187 try { 194 try {
188 const result = await doctorApi.registerPatient(token, { 195 const result = await doctorApi.registerPatient(token, {
189 - patientId, 196 + patientId : userId,
190 }); 197 });
191 if(result.statusText === 'OK') { 198 if(result.statusText === 'OK') {
192 Alert.onSuccess('환자에게 담당의 등록 요청을 전송했습니다.', () => null); 199 Alert.onSuccess('환자에게 담당의 등록 요청을 전송했습니다.', () => null);
200 + setNewPatientRegisterModal(false);
193 } else { 201 } else {
194 Alert.onError('환자에게 담당의 등록 요청을 실패했습니다.', () => null); 202 Alert.onError('환자에게 담당의 등록 요청을 실패했습니다.', () => null);
195 } 203 }
196 } catch(e : any) { 204 } catch(e : any) {
197 - Alert.onError(e.response.data.error, () => null); 205 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
198 } 206 }
199 }; 207 };
200 208
201 - Alert.onCheck(`${patientNm} 환자에게 담당의 등록 요청을 전송하시겠습니까?`, onRegisterReq, () => null); 209 + Alert.onCheck(`${userNm} 환자에게 담당의 등록 요청을 전송하시겠습니까?`, onRegisterReq, () => null);
202 } else { 210 } else {
203 Alert.onError('환자를 먼저 검색해주세요.', () => null); 211 Alert.onError('환자를 먼저 검색해주세요.', () => null);
204 } 212 }
...@@ -206,14 +214,17 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -206,14 +214,17 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
206 214
207 const onCloseModal = async () => { 215 const onCloseModal = async () => {
208 setNewPatientRegisterModal(false); 216 setNewPatientRegisterModal(false);
209 - setNewPatientSearchId(''); 217 + setNewPatientSearchContact('');
210 setNewPatientSearchResult(null); 218 setNewPatientSearchResult(null);
211 setEditModal(false); 219 setEditModal(false);
212 setEditPatientInfo(''); 220 setEditPatientInfo('');
213 setPrescribeModal(false); 221 setPrescribeModal(false);
222 + setPrescribeModalStep(1);
214 setSearchMedicineKeyword(''); 223 setSearchMedicineKeyword('');
215 setMedicineList([]); 224 setMedicineList([]);
216 setPrescribeMedicine(null); 225 setPrescribeMedicine(null);
226 + setDailyDosage('1');
227 + setTotalDay('1');
217 }; 228 };
218 229
219 const onGoBottleDetail = (bottleId : number) => { 230 const onGoBottleDetail = (bottleId : number) => {
...@@ -231,18 +242,68 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -231,18 +242,68 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
231 setLoading(true); 242 setLoading(true);
232 const res = await medicineApi.searchMedicine(token, searchMedicineKeyword); 243 const res = await medicineApi.searchMedicine(token, searchMedicineKeyword);
233 if(res.statusText === 'OK') { 244 if(res.statusText === 'OK') {
234 - console.log(res.data.medicineList)
235 setMedicineList(res.data.medicineList); 245 setMedicineList(res.data.medicineList);
236 } 246 }
237 setLoading(false); 247 setLoading(false);
238 } catch(e : any) { 248 } catch(e : any) {
239 - Alert.onError(e.response.data.error, () => null); 249 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
240 } 250 }
241 }; 251 };
242 252
253 + const onSetDailyDosage = (e : React.ChangeEvent<HTMLInputElement>) => {
254 + setDailyDosage(e.target.value);
255 + };
256 +
257 + const onSetTotalDay = (e : React.ChangeEvent<HTMLInputElement>) => {
258 + setTotalDay(e.target.value);
259 + };
260 +
261 + const onSetNextStepPrescribe = () => {
262 + if(prescribeMedicine) setPrescribeModalStep(prescribeModalStep + 1);
263 + else Alert.onWarning('먼저 처방할 약을 선택해야 합니다.', () => null);
264 + };
265 +
266 + const onSetPrevStepPrescribe = () => {
267 + if(prescribeModalStep > 1) setPrescribeModalStep(prescribeModalStep - 1);
268 + };
269 +
243 const onPrescribeSubmit = async() => { 270 const onPrescribeSubmit = async() => {
244 - //toDo : 처방해서, QR코드 생성 271 + const onPrescribeMedicine = async () => {
245 - Alert.onWarning('작업 중입니다.', () => null); 272 + setLoading(true);
273 + try {
274 + const res = await doctorApi.prescribeMedicine(token, {
275 + patientId : patientDetail.profile.userId,
276 + medicineId : prescribeMedicine.medicineId,
277 + dailyDosage,
278 + totalDosage : String(parseInt(totalDay) * parseInt(dailyDosage)),
279 + });
280 +
281 + if(res.statusText === 'OK') {
282 + setQrcodeUrl(res.data.qrCode);
283 + setLoading(false);
284 + Alert.onSuccess('처방 정보가 생성 되었습니다.', () => onSetNextStepPrescribe());
285 + }
286 + } catch(e : any) {
287 + setLoading(false);
288 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
289 + }
290 + };
291 +
292 + Alert.onCheck(`${prescribeMedicine.name}(일 복용량:${dailyDosage})\n을 ${totalDay}일동안 처방하시겠습니까?`, async () => {
293 + await onPrescribeMedicine();
294 + }, () => null);
295 + };
296 +
297 + const onPrintQrcode = async(divId : string) => {
298 + const printContent : any = document.getElementById(divId);
299 + const windowOpen : any = window.open('', '_blank');
300 +
301 + //toDo : 현재 인증되지 않은 사용자(=http)이기 때문에, GCS에서 signed url을 불러와도 만료되어, 이미지가 정상 표시 안됨 : 해결 필요
302 + windowOpen.document.writeln(printContent.innerHTML);
303 + windowOpen.document.close();
304 + windowOpen.focus();
305 + windowOpen.print();
306 + windowOpen.close();
246 }; 307 };
247 308
248 const onPrescribeCancel = () => { 309 const onPrescribeCancel = () => {
...@@ -289,21 +350,30 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => { ...@@ -289,21 +350,30 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
289 350
290 newPatientRegisterModal = {newPatientRegisterModal} 351 newPatientRegisterModal = {newPatientRegisterModal}
291 setNewPatientRegisterModal = {setNewPatientRegisterModal} 352 setNewPatientRegisterModal = {setNewPatientRegisterModal}
292 - newPatientSearchId = {newPatientSearchId} 353 + newPatientSearchContact = {newPatientSearchContact}
293 - onSetNewPatientSearchId = {onSetNewPatientSearchId} 354 + onSetNewPatientSearchContact = {onSetNewPatientSearchContact}
294 - onSearchNewPatientByEmail = {onSearchNewPatientByEmail} 355 + onSearchNewPatientByContact = {onSearchNewPatientByContact}
295 onRegisterNewPatient = {onRegisterNewPatient} 356 onRegisterNewPatient = {onRegisterNewPatient}
296 onCloseModal = {onCloseModal} 357 onCloseModal = {onCloseModal}
297 358
298 prescribeModal = {prescribeModal} 359 prescribeModal = {prescribeModal}
360 + prescribeModalStep = {prescribeModalStep}
361 + onSetNextStepPrescribe = {onSetNextStepPrescribe}
362 + onSetPrevStepPrescribe = {onSetPrevStepPrescribe}
299 setPrescribeModal = {setPrescribeModal} 363 setPrescribeModal = {setPrescribeModal}
300 searchMedicineKeyword = {searchMedicineKeyword} 364 searchMedicineKeyword = {searchMedicineKeyword}
301 onSetSearchMedicineKeyword = {onSetSearchMedicineKeyword} 365 onSetSearchMedicineKeyword = {onSetSearchMedicineKeyword}
302 medicineList = {medicineList} 366 medicineList = {medicineList}
303 searchMedicine = {searchMedicine} 367 searchMedicine = {searchMedicine}
304 prescribeMedicine = {prescribeMedicine} 368 prescribeMedicine = {prescribeMedicine}
369 + dailyDosage = {dailyDosage}
370 + onSetDailyDosage = {onSetDailyDosage}
371 + totalDay = {totalDay}
372 + onSetTotalDay = {onSetTotalDay}
373 + qrcodeUrl = {qrcodeUrl}
305 setPrescribeMedicine = {setPrescribeMedicine} 374 setPrescribeMedicine = {setPrescribeMedicine}
306 onPrescribeSubmit = {onPrescribeSubmit} 375 onPrescribeSubmit = {onPrescribeSubmit}
376 + onPrintQrcode = {onPrintQrcode}
307 onPrescribeCancel = {onPrescribeCancel} 377 onPrescribeCancel = {onPrescribeCancel}
308 378
309 newPatientSearchResult = {newPatientSearchResult} 379 newPatientSearchResult = {newPatientSearchResult}
......
1 import React from 'react'; 1 import React from 'react';
2 2
3 +import Modal from '../../../components/Modal';
4 +
3 import * as styled from './DoctorMenuStyled'; 5 import * as styled from './DoctorMenuStyled';
4 6
5 const medicineImg = '/static/img/medicine.png'; 7 const medicineImg = '/static/img/medicine.png';
6 const addButton = '/static/img/plus.png'; 8 const addButton = '/static/img/plus.png';
7 const lensImg = '/static/img/lens.png'; 9 const lensImg = '/static/img/lens.png';
8 -const closeButton = '/static/img/close.png';
9 const edit = '/static/img/edit.png'; 10 const edit = '/static/img/edit.png';
10 const refreshing = '/static/img/refreshing.png'; 11 const refreshing = '/static/img/refreshing.png';
11 const check = '/static/img/check.png'; 12 const check = '/static/img/check.png';
...@@ -18,7 +19,7 @@ interface DoctorMenuProps { ...@@ -18,7 +19,7 @@ interface DoctorMenuProps {
18 userNm : string; 19 userNm : string;
19 doctorType : string | null; 20 doctorType : string | null;
20 contact : string; 21 contact : string;
21 - userAge : number | null; 22 + birth : number | null;
22 patientInfo : string; 23 patientInfo : string;
23 }; 24 };
24 searchPatientKeyword : string; 25 searchPatientKeyword : string;
...@@ -38,9 +39,9 @@ interface DoctorMenuProps { ...@@ -38,9 +39,9 @@ interface DoctorMenuProps {
38 39
39 newPatientRegisterModal : boolean; 40 newPatientRegisterModal : boolean;
40 setNewPatientRegisterModal : any; 41 setNewPatientRegisterModal : any;
41 - newPatientSearchId: string; 42 + newPatientSearchContact: string;
42 - onSetNewPatientSearchId : React.ChangeEventHandler<HTMLInputElement>; 43 + onSetNewPatientSearchContact : React.ChangeEventHandler<HTMLInputElement>;
43 - onSearchNewPatientByEmail : () => void; 44 + onSearchNewPatientByContact : () => void;
44 onRegisterNewPatient : () => void; 45 onRegisterNewPatient : () => void;
45 onCloseModal : () => void; 46 onCloseModal : () => void;
46 47
...@@ -48,6 +49,10 @@ interface DoctorMenuProps { ...@@ -48,6 +49,10 @@ interface DoctorMenuProps {
48 49
49 prescribeModal : boolean; 50 prescribeModal : boolean;
50 setPrescribeModal : any; 51 setPrescribeModal : any;
52 + prescribeModalStep : number;
53 + onSetNextStepPrescribe : () => void;
54 + onSetPrevStepPrescribe : () => void;
55 +
51 searchMedicineKeyword : string; 56 searchMedicineKeyword : string;
52 onSetSearchMedicineKeyword : React.ChangeEventHandler<HTMLInputElement>; 57 onSetSearchMedicineKeyword : React.ChangeEventHandler<HTMLInputElement>;
53 58
...@@ -57,7 +62,15 @@ interface DoctorMenuProps { ...@@ -57,7 +62,15 @@ interface DoctorMenuProps {
57 prescribeMedicine : any; 62 prescribeMedicine : any;
58 setPrescribeMedicine : (arg0 : any) => void; 63 setPrescribeMedicine : (arg0 : any) => void;
59 64
65 + dailyDosage : string;
66 + onSetDailyDosage : React.ChangeEventHandler<HTMLInputElement>;
67 + totalDay : string;
68 + onSetTotalDay : React.ChangeEventHandler<HTMLInputElement>;
69 +
70 + qrcodeUrl : string | null;
71 +
60 onPrescribeSubmit : () => void; 72 onPrescribeSubmit : () => void;
73 + onPrintQrcode : (arg0 : string) => void;
61 onPrescribeCancel : () => void; 74 onPrescribeCancel : () => void;
62 } 75 }
63 76
...@@ -65,188 +78,227 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => { ...@@ -65,188 +78,227 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => {
65 return ( 78 return (
66 <styled.Container> 79 <styled.Container>
67 { 80 {
68 - props.newPatientRegisterModal ? 81 + props.newPatientRegisterModal ?
69 - <styled.ModalContainer> 82 + <Modal onModalClose = {() => props.setNewPatientRegisterModal(false)}>
70 - <styled.ModalClsButtonWrapper> 83 + <>
71 - <styled.ModalClsButton 84 + <styled.NewPatientRegisterTitle>새 환자 등록</styled.NewPatientRegisterTitle>
72 - onClick = {() => props.setNewPatientRegisterModal(false)} 85 + <styled.NewPatientSearchWrapper>
86 + <styled.NewPatientSearchInput
87 + placeholder = '환자의 연락처를 입력하세요.'
88 + value = {props.newPatientSearchContact}
89 + onChange = {props.onSetNewPatientSearchContact}
90 + />
91 + <styled.NewPatientSearchButton
92 + onClick = {props.onSearchNewPatientByContact}
73 > 93 >
74 - <styled.ModalClsButtonImg src = {closeButton}/> 94 + <styled.NewPatientSearchButtonImg src = {lensImg}/>
75 - <styled.ModalClsButtonText>닫기</styled.ModalClsButtonText> 95 + </styled.NewPatientSearchButton>
76 - </styled.ModalClsButton> 96 + </styled.NewPatientSearchWrapper>
77 - </styled.ModalClsButtonWrapper> 97 + <styled.NewPatientSearchResultWrapper>
78 - <styled.ModalContentWrapper> 98 + {
79 - <styled.ModalContent> 99 + props.newPatientSearchResult ?
80 - <styled.NewPatientRegisterTitle>새 환자 등록</styled.NewPatientRegisterTitle> 100 + <styled.NewPatientSearchResult>
81 - <styled.NewPatientSearchWrapper> 101 + <styled.NewPatientSearchResultInfoWrapper>
82 - <styled.NewPatientSearchInput 102 + <styled.NewPatientSearchResultInfo>
83 - placeholder = '환자 이메일을 입력하세요.' 103 + 이름 :
84 - value = {props.newPatientSearchId} 104 + <styled.NewPatientSearchResultInfoText>
85 - onChange = {props.onSetNewPatientSearchId} 105 + {props.newPatientSearchResult.userNm}
86 - /> 106 + </styled.NewPatientSearchResultInfoText>
87 - <styled.NewPatientSearchButton 107 + </styled.NewPatientSearchResultInfo>
88 - onClick = {props.onSearchNewPatientByEmail} 108 + <styled.NewPatientSearchResultInfo>
89 - > 109 + 생년월일 :
90 - <styled.NewPatientSearchButtonImg src = {lensImg}/> 110 + <styled.NewPatientSearchResultInfoText>
91 - </styled.NewPatientSearchButton> 111 + {props.newPatientSearchResult.birth}
92 - </styled.NewPatientSearchWrapper> 112 + </styled.NewPatientSearchResultInfoText>
93 - <styled.NewPatientSearchResultWrapper> 113 + </styled.NewPatientSearchResultInfo>
94 - { 114 + </styled.NewPatientSearchResultInfoWrapper>
95 - props.newPatientSearchResult ? 115 + </styled.NewPatientSearchResult> :
96 - <styled.NewPatientSearchResult> 116 + '🤔검색 결과가 없습니다.'
97 - <styled.NewPatientSearchResultInfoWrapper> 117 + }
98 - <styled.NewPatientSearchResultInfo>이름 : </styled.NewPatientSearchResultInfo> 118 + </styled.NewPatientSearchResultWrapper>
99 - <styled.NewPatientSearchResultInfoText> 119 + <styled.NewPatientRegisterButtonWrapper>
100 - {props.newPatientSearchResult.patientNm} 120 + <styled.NewPatientRegisterButton
101 - </styled.NewPatientSearchResultInfoText> 121 + onClick = {props.onRegisterNewPatient}
102 - </styled.NewPatientSearchResultInfoWrapper> 122 + >
103 - </styled.NewPatientSearchResult> : 123 + 확인
104 - '🤔검색 결과가 없습니다.' 124 + </styled.NewPatientRegisterButton>
105 - } 125 + <styled.NewPatientRegisterButton
106 - </styled.NewPatientSearchResultWrapper> 126 + onClick = {props.onCloseModal}
107 - <styled.NewPatientRegisterButtonWrapper> 127 + >
108 - <styled.NewPatientRegisterButton 128 + 취소
109 - onClick = {props.onRegisterNewPatient} 129 + </styled.NewPatientRegisterButton>
110 - > 130 + </styled.NewPatientRegisterButtonWrapper>
111 - 확인 131 + </>
112 - </styled.NewPatientRegisterButton> 132 + </Modal> : null
113 - <styled.NewPatientRegisterButton
114 - onClick = {props.onCloseModal}
115 - >
116 - 취소
117 - </styled.NewPatientRegisterButton>
118 - </styled.NewPatientRegisterButtonWrapper>
119 - </styled.ModalContent>
120 - </styled.ModalContentWrapper>
121 - <styled.ModalClsButtonWrapper/>
122 - </styled.ModalContainer> : null
123 } 133 }
124 { 134 {
125 props.editModal ? 135 props.editModal ?
126 - <styled.ModalContainer> 136 + <Modal onModalClose = {() => props.setEditModal(false)}>
127 - <styled.ModalClsButtonWrapper> 137 + <>
128 - <styled.ModalClsButton 138 + <styled.PatientInfoViewContainer>
129 - onClick = {() => props.setEditModal(false)} 139 + <styled.PatientInfoPatientNmWrapper>
140 + <styled.PatientInfoPatientNmInfo>이름 : </styled.PatientInfoPatientNmInfo>
141 + <styled.PatientInfoPatientNm>{props.info.userNm}</styled.PatientInfoPatientNm>
142 + </styled.PatientInfoPatientNmWrapper>
143 + <styled.PatientInfoView>
144 +
145 + {
146 + props.info.patientInfo.split('\n\n').map((patientInfoText : string) => {
147 + return (
148 + <div key = {patientInfoText}>
149 + {patientInfoText}<br/><br/>
150 + </div>
151 + )
152 + })
153 + }
154 + </styled.PatientInfoView>
155 + </styled.PatientInfoViewContainer>
156 + <styled.PatientInfoEditWrapper>
157 + <styled.PatientInfoEditInput
158 + value = {props.editPatientInfo}
159 + onChange = {props.onEditPatientInfo}
160 + />
161 + </styled.PatientInfoEditWrapper>
162 + <styled.PatientInfoEditButtonWrapper>
163 + <styled.PatientInfoEditButton
164 + onClick = {props.onSubmitPatientInfo}
130 > 165 >
131 - <styled.ModalClsButtonImg src = {closeButton}/> 166 + 확인
132 - <styled.ModalClsButtonText>닫기</styled.ModalClsButtonText> 167 + </styled.PatientInfoEditButton>
133 - </styled.ModalClsButton> 168 + <styled.PatientInfoEditButton
134 - </styled.ModalClsButtonWrapper> 169 + onClick = {props.onCloseModal}
135 - <styled.ModalContentWrapper> 170 + >
136 - <styled.ModalContent> 171 + 취소
137 - <styled.PatientInfoViewContainer> 172 + </styled.PatientInfoEditButton>
138 - <styled.PatientInfoPatientNmWrapper> 173 + </styled.PatientInfoEditButtonWrapper>
139 - <styled.PatientInfoPatientNmInfo>이름 : </styled.PatientInfoPatientNmInfo> 174 + </>
140 - <styled.PatientInfoPatientNm>{props.info.userNm}</styled.PatientInfoPatientNm> 175 + </Modal> : null
141 - </styled.PatientInfoPatientNmWrapper>
142 - <styled.PatientInfoView>
143 -
144 - {
145 - props.info.patientInfo.split('\n\n').map((patientInfoText : string) => {
146 - return (
147 - <div key = {patientInfoText}>
148 - {patientInfoText}<br/><br/>
149 - </div>
150 - )
151 - })
152 - }
153 - </styled.PatientInfoView>
154 - </styled.PatientInfoViewContainer>
155 - <styled.PatientInfoEditWrapper>
156 - <styled.PatientInfoEditInput
157 - value = {props.editPatientInfo}
158 - onChange = {props.onEditPatientInfo}
159 - />
160 - </styled.PatientInfoEditWrapper>
161 - <styled.PatientInfoEditButtonWrapper>
162 - <styled.PatientInfoEditButton
163 - onClick = {props.onSubmitPatientInfo}
164 - >
165 - 확인
166 - </styled.PatientInfoEditButton>
167 - <styled.PatientInfoEditButton
168 - onClick = {props.onCloseModal}
169 - >
170 - 취소
171 - </styled.PatientInfoEditButton>
172 - </styled.PatientInfoEditButtonWrapper>
173 - </styled.ModalContent>
174 - </styled.ModalContentWrapper>
175 - <styled.ModalClsButtonWrapper/>
176 - </styled.ModalContainer> : null
177 } 176 }
178 { 177 {
179 props.prescribeModal ? 178 props.prescribeModal ?
180 - <styled.ModalContainer> 179 + <Modal onModalClose = {props.onCloseModal}>
181 - <styled.ModalClsButtonWrapper> 180 + <>
182 - <styled.ModalClsButton 181 + <styled.MedicineSearchTitle>
183 - onClick = {props.onCloseModal} 182 + {
183 + props.prescribeModalStep === 1 ?
184 + '약 검색' :
185 + props.prescribeModalStep === 2 ?
186 + '복용량 입력' :
187 + '처방 정보 QR코드'
188 + }
189 + </styled.MedicineSearchTitle>
190 + {
191 + props.prescribeModalStep === 1 ?
192 + <>
193 + <styled.MedicineSearchInputWrapper>
194 + <styled.MedicineSearchInput
195 + placeholder = '증상, 또는 약 이름을 검색하세요.'
196 + onChange = {props.onSetSearchMedicineKeyword}
197 + value = {props.searchMedicineKeyword}
198 + />
199 + <styled.MedicineSearchButton
200 + onClick = {props.searchMedicine}
201 + >
202 + <styled.MedicineSearchButtonImg src = {lensImg}/>
203 + </styled.MedicineSearchButton>
204 + </styled.MedicineSearchInputWrapper>
205 + <styled.MedicineSearchResultWrapper>
206 + {
207 + props.medicineList.length ?
208 + props.medicineList.map((medicine : any) => {
209 + return (
210 + <styled.MedicineSearchResultEach
211 + key = {medicine.medicineId}
212 + onClick = {() => props.setPrescribeMedicine(
213 + props.prescribeMedicine && props.prescribeMedicine.medicineId === medicine.medicineId ?
214 + null : medicine
215 + )}
216 + >
217 + <styled.MedicineSearchResultEachInfo>
218 + {medicine.name}
219 + </styled.MedicineSearchResultEachInfo>
220 + <styled.MedicineSearchButtonImg
221 + src = {
222 + props.prescribeMedicine && props.prescribeMedicine.medicineId === medicine.medicineId ?
223 + check : uncheck
224 + }
225 + />
226 + </styled.MedicineSearchResultEach>
227 + )
228 + }) :
229 + <styled.NothingWrapper style = {{fontSize : 13,}}>
230 + 🤔검색 결과가 없습니다.
231 + </styled.NothingWrapper>
232 + }
233 + </styled.MedicineSearchResultWrapper>
234 + </>
235 + :
236 + props.prescribeModalStep === 2 ?
237 + <styled.MedicineDosageSetWrapper>
238 + <styled.MedicineDosageInfo>
239 + *하루 복용량을 입력하세요.
240 + </styled.MedicineDosageInfo>
241 + <styled.MedicineDosageInput
242 + value = {props.dailyDosage}
243 + onChange = {props.onSetDailyDosage}
244 + min = {1}
245 + max = {3}
246 + />
247 + <styled.MedicineDosageInfo>
248 + *총 며칠 분량인지 입력하세요.
249 + </styled.MedicineDosageInfo>
250 + <styled.MedicineDosageInput
251 + value = {props.totalDay}
252 + onChange = {props.onSetTotalDay}
253 + />
254 + </styled.MedicineDosageSetWrapper>
255 + :
256 + <styled.MedicineQRCodeWrapper
257 + id = 'qrCodePrint'
184 > 258 >
185 - <styled.ModalClsButtonImg src = {closeButton}/> 259 + <styled.MedicineQRCodeInfo>
186 - <styled.ModalClsButtonText>닫기</styled.ModalClsButtonText> 260 + *어플리케이션에서 QR코드를 스캔하면 약병에 약이 등록됩니다.
187 - </styled.ModalClsButton> 261 + </styled.MedicineQRCodeInfo>
188 - </styled.ModalClsButtonWrapper>
189 - <styled.ModalContentWrapper>
190 - <styled.ModalContent>
191 - <styled.MedicineSearchTitle>
192 - 약 검색
193 - </styled.MedicineSearchTitle>
194 - <styled.MedicineSearchInputWrapper>
195 - <styled.MedicineSearchInput
196 - placeholder = '증상, 또는 약 이름을 검색하세요.'
197 - onChange = {props.onSetSearchMedicineKeyword}
198 - value = {props.searchMedicineKeyword}
199 - />
200 - <styled.MedicineSearchButton
201 - onClick = {props.searchMedicine}
202 - >
203 - <styled.MedicineSearchButtonImg src = {lensImg}/>
204 - </styled.MedicineSearchButton>
205 - </styled.MedicineSearchInputWrapper>
206 - <styled.MedicineSearchResultWrapper>
207 { 262 {
208 - props.medicineList.length ? 263 + props.qrcodeUrl ?
209 - props.medicineList.map((medicine : any) => { 264 + <styled.MedicineQRCode
210 - return ( 265 + src = {props.qrcodeUrl}/> : null
211 - <styled.MedicineSearchResultEach
212 - key = {medicine.medicineId}
213 - onClick = {() => props.setPrescribeMedicine(medicine)}
214 - >
215 - <styled.MedicineSearchResultEachInfo>
216 - {medicine.name}
217 - </styled.MedicineSearchResultEachInfo>
218 - <styled.MedicineSearchButtonImg
219 - src = {
220 - props.prescribeMedicine && props.prescribeMedicine.medicineId === medicine.medicineId ?
221 - check : uncheck
222 - }
223 - />
224 - </styled.MedicineSearchResultEach>
225 - )
226 - }) :
227 - <styled.NothingWrapper style = {{fontSize : 13,}}>
228 - 🤔검색 결과가 없습니다.
229 - </styled.NothingWrapper>
230 } 266 }
231 - </styled.MedicineSearchResultWrapper> 267 + </styled.MedicineQRCodeWrapper>
232 - <styled.MedicinePrescribeButtonWrapper> 268 + }
233 - <styled.MedicinePrescribeButton 269 + <styled.MedicinePrescribeButtonWrapper>
234 - isClose = {false} 270 + {
235 - onClick = {props.onPrescribeSubmit} 271 + props.prescribeModalStep === 1 ?
236 - > 272 + <styled.MedicinePrescribeButton
237 - 처방 273 + isClose = {false}
238 - </styled.MedicinePrescribeButton> 274 + onClick = {props.onSetNextStepPrescribe}
239 - <styled.MedicinePrescribeButton 275 + >
240 - isClose = {true} 276 + 다음 단계
241 - onClick = {props.onPrescribeCancel} 277 + </styled.MedicinePrescribeButton> :
242 - > 278 + props.prescribeModalStep === 2 ?
243 - 취소 279 + <styled.MedicinePrescribeButton
244 - </styled.MedicinePrescribeButton> 280 + isClose = {false}
245 - </styled.MedicinePrescribeButtonWrapper> 281 + onClick = {props.onPrescribeSubmit}
246 - </styled.ModalContent> 282 + >
247 - </styled.ModalContentWrapper> 283 + 처방
248 - <styled.ModalClsButtonWrapper/> 284 + </styled.MedicinePrescribeButton>
249 - </styled.ModalContainer> : null 285 + :
286 + <styled.MedicinePrescribeButton
287 + isClose = {false}
288 + onClick = {() => props.onPrintQrcode('qrCodePrint')}
289 + >
290 + 출력
291 + </styled.MedicinePrescribeButton>
292 + }
293 + <styled.MedicinePrescribeButton
294 + isClose = {true}
295 + onClick = {props.onPrescribeCancel}
296 + >
297 + 취소
298 + </styled.MedicinePrescribeButton>
299 + </styled.MedicinePrescribeButtonWrapper>
300 + </>
301 + </Modal> : null
250 } 302 }
251 <styled.InfoAndSearchWrapper> 303 <styled.InfoAndSearchWrapper>
252 <styled.InfoWrapper> 304 <styled.InfoWrapper>
...@@ -287,7 +339,7 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => { ...@@ -287,7 +339,7 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => {
287 </styled.InfoEachWrapper> 339 </styled.InfoEachWrapper>
288 <styled.InfoEachWrapper> 340 <styled.InfoEachWrapper>
289 <styled.InfoEachTopic>생년월일</styled.InfoEachTopic> 341 <styled.InfoEachTopic>생년월일</styled.InfoEachTopic>
290 - <styled.InfoEachText>{props.info.userAge}세</styled.InfoEachText> 342 + <styled.InfoEachText>{props.info.birth}</styled.InfoEachText>
291 </styled.InfoEachWrapper> 343 </styled.InfoEachWrapper>
292 <styled.InfoEachWrapper> 344 <styled.InfoEachWrapper>
293 <styled.InfoEachTopic>연락처</styled.InfoEachTopic> 345 <styled.InfoEachTopic>연락처</styled.InfoEachTopic>
...@@ -351,7 +403,7 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => { ...@@ -351,7 +403,7 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => {
351 onClick = {() => props.onFetchPatientDetail(patient.userId)} 403 onClick = {() => props.onFetchPatientDetail(patient.userId)}
352 > 404 >
353 <styled.SearchResultEachText isLast = {false}>{patient.userNm}</styled.SearchResultEachText> 405 <styled.SearchResultEachText isLast = {false}>{patient.userNm}</styled.SearchResultEachText>
354 - <styled.SearchResultEachText isLast = {false}>{patient.userAge}세</styled.SearchResultEachText> 406 + <styled.SearchResultEachText isLast = {false}>{patient.birth}</styled.SearchResultEachText>
355 <styled.SearchResultEachText isLast = {true}>{patient.contact}</styled.SearchResultEachText> 407 <styled.SearchResultEachText isLast = {true}>{patient.contact}</styled.SearchResultEachText>
356 </styled.SearchResultEach> 408 </styled.SearchResultEach>
357 ) 409 )
......
1 -import styled, { keyframes } from 'styled-components'; 1 +import styled from 'styled-components';
2 -
3 -
4 -const ModalOn = keyframes `
5 - 0% {
6 - background-color : rgba(52, 52, 52, .0);
7 - }
8 - 20% {
9 - background-color : rgba(52, 52, 52, .2);
10 - }
11 - 40% {
12 - background-color : rgba(52, 52, 52, .4);
13 - }
14 - 60% {
15 - background-color : rgba(52, 52, 52, .5);
16 - }
17 - 80% {
18 - background-color : rgba(52, 52, 52, .6);
19 - }
20 - 100% {
21 - background-color : rgba(52, 52, 52, .7);
22 - }
23 -
24 -`;
25 2
26 3
27 export const Container = styled.div ` 4 export const Container = styled.div `
...@@ -32,93 +9,6 @@ export const Container = styled.div ` ...@@ -32,93 +9,6 @@ export const Container = styled.div `
32 justify-content : center; 9 justify-content : center;
33 `; 10 `;
34 11
35 -export const ModalContainer = styled.div `
36 - height : 100%;
37 - width : 100%;
38 - z-index : 99;
39 - position : absolute;
40 -
41 - display : flex;
42 - flex-direction : column;
43 -
44 - animation : ${ModalOn} .5s;
45 -
46 - background-color : rgba(52, 52, 52, .7);
47 -
48 -`;
49 -
50 -export const ModalClsButtonWrapper = styled.div `
51 - flex : 1;
52 -
53 - display : flex;
54 -
55 - justify-content : flex-end;
56 - align-items : center;
57 - padding : 0 20px;
58 -
59 - border : none;
60 - background-color : transprent;
61 -`;
62 -
63 -export const ModalClsButton = styled.button `
64 - border : none;
65 - background-color : transparent;
66 -
67 - cursor : pointer;
68 -
69 - color : #fff;
70 -
71 - display : flex;
72 - flex-direction : row;
73 -
74 - justify-content : center;
75 - align-items : center;
76 -
77 - transition : .25s all;
78 - &:hover {
79 - opacity : .5;
80 - }
81 -`;
82 -
83 -export const ModalClsButtonImg = styled.img `
84 - height : 20px;
85 - width : 20px;
86 -
87 - margin : 0 10px 0 0;
88 -`;
89 -
90 -export const ModalClsButtonText = styled.div `
91 - font-size : 18px;
92 - font-weight : 700;
93 -`;
94 -
95 -export const ModalContentWrapper = styled.div `
96 - flex : 8;
97 -
98 - display : flex;
99 - flex-direction : column;
100 -
101 - justify-content : center;
102 - align-items : center;
103 -
104 - border : none;
105 -`;
106 -
107 -export const ModalContent = styled.div `
108 - width : 600px;
109 - height : 400px;
110 -
111 - background-color : #fff;
112 - border : 1.2px solid #337DFF;
113 - border-radius : 5px;
114 -
115 - display : flex;
116 - flex-direction : column;
117 -
118 - justify-content : center;
119 - align-items : center;
120 -`;
121 -
122 export const NewPatientRegisterTitle = styled.div ` 12 export const NewPatientRegisterTitle = styled.div `
123 font-size : 20px; 13 font-size : 20px;
124 font-weight : 700; 14 font-weight : 700;
...@@ -199,14 +89,18 @@ export const NewPatientSearchResult = styled.div ` ...@@ -199,14 +89,18 @@ export const NewPatientSearchResult = styled.div `
199 89
200 export const NewPatientSearchResultInfoWrapper = styled.div ` 90 export const NewPatientSearchResultInfoWrapper = styled.div `
201 display : flex; 91 display : flex;
92 + flex-direction : column;
202 `; 93 `;
203 94
204 export const NewPatientSearchResultInfo = styled.div ` 95 export const NewPatientSearchResultInfo = styled.div `
96 + display : flex;
97 + flex-direction : row;
98 +
205 font-size : 13px; 99 font-size : 13px;
206 font-weight : 600; 100 font-weight : 600;
207 color : #a0a0a0; 101 color : #a0a0a0;
208 102
209 - margin : 0 5px 0 0; 103 + margin : 0 5px 0 5px;
210 `; 104 `;
211 105
212 export const NewPatientSearchResultInfoText = styled.div ` 106 export const NewPatientSearchResultInfoText = styled.div `
...@@ -214,6 +108,8 @@ export const NewPatientSearchResultInfoText = styled.div ` ...@@ -214,6 +108,8 @@ export const NewPatientSearchResultInfoText = styled.div `
214 color : #343434; 108 color : #343434;
215 font-weight : 600; 109 font-weight : 600;
216 letter-spacing : 1px; 110 letter-spacing : 1px;
111 +
112 + margin : 0 0 0 5px;
217 `; 113 `;
218 114
219 export const NewPatientRegisterButtonWrapper = styled.div ` 115 export const NewPatientRegisterButtonWrapper = styled.div `
...@@ -436,6 +332,7 @@ export const MedicineSearchButtonImg = styled.img ` ...@@ -436,6 +332,7 @@ export const MedicineSearchButtonImg = styled.img `
436 height : 15px; 332 height : 15px;
437 width : 15px; 333 width : 15px;
438 334
335 + transition : .25s all;
439 `; 336 `;
440 337
441 export const MedicineSearchResultWrapper = styled.div ` 338 export const MedicineSearchResultWrapper = styled.div `
...@@ -499,6 +396,91 @@ export const MedicineSelectButtonImg = styled.img ` ...@@ -499,6 +396,91 @@ export const MedicineSelectButtonImg = styled.img `
499 width : 15px; 396 width : 15px;
500 `; 397 `;
501 398
399 +export const MedicineDosageSetWrapper = styled.div `
400 + width : 80%;
401 +
402 + display : flex;
403 + flex-direction : column;
404 +
405 + justify-content : center;
406 + align-items : center;
407 +
408 + border : none;
409 +
410 + margin : 20px 0;
411 +
412 + height : 200px;
413 +`;
414 +
415 +export const MedicineDosageInfo = styled.div `
416 + font-size : 15px;
417 + font-weight : 500;
418 +
419 + color : #a0a0a0;
420 +
421 + width : 100%;
422 + margin : 10px 0 10px 0;
423 +
424 + border : none;
425 + background-color : transparent;
426 +
427 + text-align : center;
428 +`;
429 +
430 +export const MedicineDosageInput = styled.input.attrs({
431 + type : 'number',
432 +}) `
433 + margin : 0 0 10px 0;
434 + width : 40%;
435 +
436 + padding : 10px 20px;
437 + color : #337DFF;
438 + font-size : 20px;
439 +
440 + font-weight : 700;
441 +
442 + border : none;
443 + border-bottom : 1px solid #337DFF;
444 +
445 + display : flex;
446 + flex-direction : row;
447 +
448 + text-align : center;
449 +
450 + transition : .25s all;
451 +`;
452 +
453 +export const MedicineQRCodeWrapper = styled.div `
454 + width : 80%;
455 + height : 200px;
456 +
457 + display : flex;
458 + flex-direction : column;
459 +
460 + justify-content : center;
461 + align-items : center;
462 +
463 + margin : 20px 0;
464 +
465 + border : none;
466 +`;
467 +
468 +export const MedicineQRCodeInfo = styled.div `
469 + font-size : 15px;
470 + font-weight : 500;
471 +
472 + color : #a0a0a0;
473 +
474 + text-align : center;
475 +`;
476 +
477 +export const MedicineQRCode = styled.img `
478 + margin : 10px 0 0 0;
479 +
480 + height : 170px;
481 + width : 170px;
482 +`;
483 +
502 export const MedicinePrescribeButtonWrapper = styled.div ` 484 export const MedicinePrescribeButtonWrapper = styled.div `
503 margin : 20px 0 0 0; 485 margin : 20px 0 0 0;
504 486
......
...@@ -17,11 +17,14 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => { ...@@ -17,11 +17,14 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
17 17
18 const token = useRecoilValue(recoilUtil.token); 18 const token = useRecoilValue(recoilUtil.token);
19 19
20 - const [doctorRegReqList, setDoctorRegReqList] = useState<any>([]); 20 + const [doctorList, setDoctorList] = useState<any>([]);
21 +
22 + const [viewType, setViewType] = useState<string>('reg');
21 23
22 const [doctorDetail, setDoctorDetail] = useState<any>({}); 24 const [doctorDetail, setDoctorDetail] = useState<any>({});
23 const [modalUp, setModalUp] = useState<boolean>(false); 25 const [modalUp, setModalUp] = useState<boolean>(false);
24 const [validate, setValidate] = useState<string>('W'); 26 const [validate, setValidate] = useState<string>('W');
27 + const [validateDoctorLicense, setValidateDoctorLicense] = useState<string>('');
25 28
26 29
27 30
...@@ -30,20 +33,29 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => { ...@@ -30,20 +33,29 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
30 setValidate('W'); 33 setValidate('W');
31 34
32 try { 35 try {
33 - await managerApi.getDoctorRegReqList(token) 36 + const res = viewType === 'reg' ? await managerApi.getDoctorRegReqList(token) : await managerApi.getDoctorSecReqList(token);
34 - .then((res : any) => { 37 + if(res.statusText === 'OK') {
35 - if(res.statusText === 'OK') { 38 + setDoctorList(res.data.doctorList);
36 - setDoctorRegReqList(res.data.doctorRegReqList); 39 + } else {
37 - } 40 + Alert.onError(res.data.error, () => null);
38 - }).catch(err => { 41 + }
39 - Alert.onError(err.response.data.error, () => null); 42 + } catch(e : any) {
40 - }) 43 + console.log(e);
41 - } catch(e) { 44 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
42 - Alert.onError(e.response.data.error, () => null);
43 } 45 }
44 }; 46 };
45 47
48 + //가입요청 보기
49 + const onViewRegList = () => {
50 + setViewType('reg');
51 + };
52 +
53 + //탈퇴요청 보기
54 + const onViewSecList = () => {
55 + setViewType('sec');
56 + };
46 57
58 + //가입요청 상세보기
47 const onViewDetailReq = async (doctorId : string) => { 59 const onViewDetailReq = async (doctorId : string) => {
48 setValidate('W'); 60 setValidate('W');
49 61
...@@ -55,13 +67,23 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => { ...@@ -55,13 +67,23 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
55 setModalUp(true); 67 setModalUp(true);
56 } 68 }
57 }) 69 })
58 - } catch(e) { 70 + } catch(e : any) {
59 - Alert.onError(e.response.data.error, () => setModalUp(false)); 71 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => setModalUp(false));
60 } 72 }
61 }; 73 };
62 74
75 + const onViewLicenseDetail = async (url : string) => {
76 + const licensePage : any = window.open(url);
77 + licensePage.focus();
78 + };
79 +
80 + //자격 확인 문서를 보고, 면허 번호를 입력하는 함수
81 + const onSetValidateDoctorLicense = (e : React.ChangeEvent<HTMLInputElement>) => {
82 + setValidateDoctorLicense(e.target.value);
83 + };
84 +
63 //회원 가입 수락 85 //회원 가입 수락
64 - const onAcceptRequest = () => { 86 + const onAcceptRegReq = () => {
65 if(validate === 'W') { 87 if(validate === 'W') {
66 Alert.onError('먼저 의사의 자격번호가 유효한지 검증해주세요.', () => null); 88 Alert.onError('먼저 의사의 자격번호가 유효한지 검증해주세요.', () => null);
67 return; 89 return;
...@@ -72,14 +94,16 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => { ...@@ -72,14 +94,16 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
72 94
73 const onAccept = async() => { 95 const onAccept = async() => {
74 try { 96 try {
75 - await managerApi.acceptDoctorRegReq(token, doctorDetail) 97 + await managerApi.acceptDoctorRegReq(token, {
76 - .then((res : any) => { 98 + doctorId : doctorDetail.doctorId,
77 - if(res.statusText === 'OK') { 99 + validateDoctorLicense,
78 - Alert.onSuccess('회원 등록이 완료되었습니다.', fetchData); 100 + }).then((res : any) => {
79 - } 101 + if(res.statusText === 'OK') {
80 - }) 102 + Alert.onSuccess('회원 등록이 완료되었습니다.', fetchData);
81 - } catch(e) { 103 + }
82 - Alert.onError(e.response.data.error, () => setModalUp(false)); 104 + });
105 + } catch(e : any) {
106 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => setModalUp(false));
83 } 107 }
84 }; 108 };
85 109
...@@ -90,58 +114,95 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => { ...@@ -90,58 +114,95 @@ const ManagerMenuContainer = (props : ManagerMenuProps) => {
90 const onRejectRequest = () => { 114 const onRejectRequest = () => {
91 const onReject = async() => { 115 const onReject = async() => {
92 try { 116 try {
93 - await managerApi.rejectDoctorRegReq(token, doctorDetail) 117 + await managerApi.rejectDoctorRegReq(token, {
94 - .then((res : any) => { 118 + doctorId : doctorDetail.doctorId,
95 - if(res.statusText === 'OK') { 119 + }).then((res : any) => {
96 - Alert.onSuccess('회원 등록이 취소되었습니다.', fetchData); 120 + if(res.statusText === 'OK') {
97 - } 121 + Alert.onSuccess('회원 등록이 취소되었습니다.', fetchData);
98 - }) 122 + }
99 - } catch(e) { 123 + });
100 - Alert.onError(e.response.data.error, () => setModalUp(false)); 124 + } catch(e : any) {
125 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => setModalUp(false));
101 } 126 }
102 }; 127 };
103 128
104 Alert.onCheck('회원 가입 요청을 취소하시겠습니까?', onReject, () => null); 129 Alert.onCheck('회원 가입 요청을 취소하시겠습니까?', onReject, () => null);
105 }; 130 };
106 131
132 + //회원 자격번호 유효 검증 api
107 const onValidate = async () => { 133 const onValidate = async () => {
108 try { 134 try {
109 await managerApi.validateDoctorLicense(token, { 135 await managerApi.validateDoctorLicense(token, {
110 - doctorLicense : doctorDetail.info.doctorLicense, 136 + validateDoctorLicense,
111 }).then(res => { 137 }).then(res => {
112 if(res.statusText === 'OK') { 138 if(res.statusText === 'OK') {
113 setValidate(res.data.result ? 'Y' : 'N'); 139 setValidate(res.data.result ? 'Y' : 'N');
114 } 140 }
115 }).catch(err => { 141 }).catch(err => {
116 - Alert.onError(err.response.data, () => { 142 + console.log(err);
143 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => {
117 setModalUp(false); 144 setModalUp(false);
118 setValidate('W'); 145 setValidate('W');
119 }); 146 });
120 }) 147 })
121 - } catch(e) { 148 + } catch(e : any) {
122 - Alert.onError(e.response.data, () => { 149 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => {
123 setModalUp(false); 150 setModalUp(false);
124 setValidate('W'); 151 setValidate('W');
125 }); 152 });
126 } 153 }
127 }; 154 };
128 155
156 + const onAcceptSecReq = (doctorId : string) => {
157 + const onAccept = async () => {
158 + try {
159 + const res = await managerApi.acceptDoctorSecReq(token, {
160 + doctorId,
161 + });
162 + if(res.statusText === 'OK') {
163 + Alert.onSuccess('탈퇴를 승인했습니다.', fetchData);
164 + }
165 + } catch (e : any) {
166 + Alert.onError('알 수 없는 에러가 발생했습니다.', () => null);
167 + }
168 + };
169 +
170 + Alert.onCheck('회원 탈퇴를 승인하시겠습니까?\n이 작업은 되돌릴 수 없습니다.', onAccept, () => null);
171 + };
172 +
173 +
174 +
175 + useEffect(() => {
176 + setValidate('W');
177 + setValidateDoctorLicense('');
178 + }, [modalUp]);
179 +
129 useEffect(() => { 180 useEffect(() => {
130 fetchData(); 181 fetchData();
131 - }, []); 182 + }, [viewType]);
132 183
133 return ( 184 return (
134 <ManagerMenuPresenter 185 <ManagerMenuPresenter
135 - doctorRegReqList = {doctorRegReqList} 186 + viewType = {viewType}
187 + onViewRegList = {onViewRegList}
188 + onViewSecList = {onViewSecList}
189 +
190 + doctorList = {doctorList}
136 191
137 doctorDetail = {doctorDetail} 192 doctorDetail = {doctorDetail}
138 modalUp = {modalUp} 193 modalUp = {modalUp}
139 setModalUp = {setModalUp} 194 setModalUp = {setModalUp}
140 onViewDetailReq = {onViewDetailReq} 195 onViewDetailReq = {onViewDetailReq}
196 + onViewLicenseDetail = {onViewLicenseDetail}
197 +
141 validate = {validate} 198 validate = {validate}
142 onValidate = {onValidate} 199 onValidate = {onValidate}
143 200
144 - onAcceptRequest = {onAcceptRequest} 201 + validateDoctorLicense = {validateDoctorLicense}
202 + onSetValidateDoctorLicense = {onSetValidateDoctorLicense}
203 +
204 + onAcceptRegReq = {onAcceptRegReq}
205 + onAcceptSecReq = {onAcceptSecReq}
145 onRejectRequest = {onRejectRequest} 206 onRejectRequest = {onRejectRequest}
146 /> 207 />
147 ); 208 );
......
1 import React from 'react'; 1 import React from 'react';
2 2
3 +import Modal from '../../../components/Modal';
3 import * as styled from './ManagerMenuStyled'; 4 import * as styled from './ManagerMenuStyled';
4 5
5 -const closeButton = '/static/img/close.png';
6 -
7 -
8 6
9 interface ManagerMenuProps { 7 interface ManagerMenuProps {
10 - doctorRegReqList : any[]; 8 + viewType : string;
9 + onViewRegList : () => void;
10 + onViewSecList : () => void;
11 +
12 + doctorList : any[];
11 13
12 doctorDetail : any; 14 doctorDetail : any;
13 modalUp : boolean; 15 modalUp : boolean;
14 setModalUp : any; 16 setModalUp : any;
15 onViewDetailReq : (arg0 : string) => void; 17 onViewDetailReq : (arg0 : string) => void;
18 + onViewLicenseDetail : (arg0 : string) => void;
19 +
16 validate : string; 20 validate : string;
17 onValidate : () => void; 21 onValidate : () => void;
18 22
19 - onAcceptRequest : () => void; 23 + validateDoctorLicense : string;
24 + onSetValidateDoctorLicense : React.ChangeEventHandler<HTMLInputElement>;
25 +
26 + onAcceptRegReq : () => void;
27 + onAcceptSecReq : (arg0 : string) => void;
20 onRejectRequest : () => void; 28 onRejectRequest : () => void;
21 29
22 } 30 }
...@@ -26,117 +34,156 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => { ...@@ -26,117 +34,156 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
26 <styled.Container> 34 <styled.Container>
27 { 35 {
28 props.modalUp ? 36 props.modalUp ?
29 - <styled.ModalContainer> 37 + <Modal onModalClose = {() => props.setModalUp(false)}>
30 - <styled.ModalClsButtonWrapper> 38 + <>
31 - <styled.ModalClsButton 39 + <styled.ModalTitleWrapper>
32 - onClick = {() => props.setModalUp(false)} 40 + <styled.ModalTitle>가입 요청 정보</styled.ModalTitle>
41 + </styled.ModalTitleWrapper>
42 + <styled.ModalBodyWrapper>
43 + <styled.ModalBodyLeftAndRight>
44 + <styled.ModalInfoWrapper>
45 + <styled.DoctorLicenseViewWrapper>
46 + <styled.ModalInfoExplain>
47 + 의사 자격 번호
48 + </styled.ModalInfoExplain>
49 + <styled.DoctorLicenseViewButton onClick = {() => props.onViewLicenseDetail(props.doctorDetail.info.doctorLicense)}>
50 + 자격정보 확인
51 + </styled.DoctorLicenseViewButton>
52 + </styled.DoctorLicenseViewWrapper>
53 + <styled.ModalInfoNotice>
54 + * 자격 정보 확인 버튼을 눌러 정보를 확인하세요.
55 + <br/>* 정보 확인은 15분간 유효합니다.
56 + <br/>* 확인한 면허 번호를 입력 후 검증하세요.
57 + </styled.ModalInfoNotice>
58 + <styled.ModalInfo>
59 + <styled.DoctorLicenseViewInput
60 + placeholder = '의사 면허 번호'
61 + value = {props.validateDoctorLicense}
62 + onChange = {props.onSetValidateDoctorLicense}
63 + />
64 + <styled.ValidateButton
65 + onClick = {props.onValidate}
66 + disabled = {props.validate !== 'W'}
67 + validate = {props.validate}
68 + >
69 + {
70 + props.validate === 'Y' ?
71 + '검증 완료' :
72 + props.validate === 'W' ?
73 + '검증' :
74 + props.validate === 'N' ?
75 + '검증 실패' : null
76 + }
77 + </styled.ValidateButton>
78 + </styled.ModalInfo>
79 + </styled.ModalInfoWrapper>
80 + <styled.ModalInfoWrapper>
81 + <styled.ModalInfoExplain>이름</styled.ModalInfoExplain>
82 + <styled.ModalInfo>{props.doctorDetail.info.doctorNm}</styled.ModalInfo>
83 + </styled.ModalInfoWrapper>
84 + <styled.ModalInfoWrapper>
85 + <styled.ModalInfoExplain>연락처</styled.ModalInfoExplain>
86 + <styled.ModalInfo>{props.doctorDetail.info.contact}</styled.ModalInfo>
87 + </styled.ModalInfoWrapper>
88 + </styled.ModalBodyLeftAndRight>
89 + <styled.ModalBodyLeftAndRight>
90 + <styled.ModalInfoWrapper>
91 + <styled.ModalInfoExplain>전문 분야</styled.ModalInfoExplain>
92 + <styled.ModalInfo>{props.doctorDetail.info.doctorType}</styled.ModalInfo>
93 + </styled.ModalInfoWrapper>
94 + <styled.ModalInfoWrapper>
95 + <styled.ModalInfoExplain>병원명</styled.ModalInfoExplain>
96 + <styled.ModalInfo>{props.doctorDetail.info.hospitalNm}</styled.ModalInfo>
97 + </styled.ModalInfoWrapper>
98 + <styled.ModalInfoWrapper>
99 + <styled.ModalInfoExplain>병원 주소</styled.ModalInfoExplain>
100 + <styled.ModalInfo>{props.doctorDetail.info.hospitalAddr}</styled.ModalInfo>
101 + </styled.ModalInfoWrapper>
102 + </styled.ModalBodyLeftAndRight>
103 + </styled.ModalBodyWrapper>
104 + <styled.ModalButtonWrapper>
105 + <styled.ModalButton
106 + onClick = {props.onAcceptRegReq}
107 + isAccept = {true}
33 > 108 >
34 - <styled.ModalClsButtonImg src = {closeButton}/> 109 + 수락
35 - <styled.ModalClsButtonText>닫기</styled.ModalClsButtonText> 110 + </styled.ModalButton>
36 - </styled.ModalClsButton> 111 + <styled.ModalButton
37 - </styled.ModalClsButtonWrapper> 112 + onClick = {props.onRejectRequest}
38 - <styled.ModalContentWrapper> 113 + isAccept = {false}
39 - <styled.ModalContent> 114 + >
40 - <styled.ModalTitleWrapper> 115 + 거절
41 - <styled.ModalTitle>가입 요청 정보</styled.ModalTitle> 116 + </styled.ModalButton>
42 - </styled.ModalTitleWrapper> 117 + </styled.ModalButtonWrapper>
43 - <styled.ModalBodyWrapper> 118 + </>
44 - <styled.ModalBodyLeftAndRight> 119 + </Modal> : null
45 - <styled.ModalInfoWrapper>
46 - <styled.ModalInfoExplain>의사 자격 번호</styled.ModalInfoExplain>
47 - <styled.ModalInfo>
48 - {props.doctorDetail.info.doctorLicense}
49 - <styled.ValidateButton
50 - onClick = {props.onValidate}
51 - disabled = {props.validate !== 'W'}
52 - validate = {props.validate}
53 - >
54 - {
55 - props.validate === 'Y' ?
56 - '검증 완료' :
57 - props.validate === 'W' ?
58 - '검증' :
59 - props.validate === 'N' ?
60 - '검증 실패' : null
61 - }
62 - </styled.ValidateButton>
63 - </styled.ModalInfo>
64 - </styled.ModalInfoWrapper>
65 - <styled.ModalInfoWrapper>
66 - <styled.ModalInfoExplain>이름</styled.ModalInfoExplain>
67 - <styled.ModalInfo>{props.doctorDetail.info.doctorNm}</styled.ModalInfo>
68 - </styled.ModalInfoWrapper>
69 - <styled.ModalInfoWrapper>
70 - <styled.ModalInfoExplain>연락처</styled.ModalInfoExplain>
71 - <styled.ModalInfo>{props.doctorDetail.info.contact}</styled.ModalInfo>
72 - </styled.ModalInfoWrapper>
73 - </styled.ModalBodyLeftAndRight>
74 - <styled.ModalBodyLeftAndRight>
75 - <styled.ModalInfoWrapper>
76 - <styled.ModalInfoExplain>전문 분야</styled.ModalInfoExplain>
77 - <styled.ModalInfo>{props.doctorDetail.info.doctorType}</styled.ModalInfo>
78 - </styled.ModalInfoWrapper>
79 - <styled.ModalInfoWrapper>
80 - <styled.ModalInfoExplain>병원명</styled.ModalInfoExplain>
81 - <styled.ModalInfo>{props.doctorDetail.info.hospitalNm}</styled.ModalInfo>
82 - </styled.ModalInfoWrapper>
83 - <styled.ModalInfoWrapper>
84 - <styled.ModalInfoExplain>병원 주소</styled.ModalInfoExplain>
85 - <styled.ModalInfo>{props.doctorDetail.info.hospitalAddr}</styled.ModalInfo>
86 - </styled.ModalInfoWrapper>
87 - </styled.ModalBodyLeftAndRight>
88 - </styled.ModalBodyWrapper>
89 - <styled.ModalButtonWrapper>
90 - <styled.ModalButton
91 - onClick = {props.onAcceptRequest}
92 - isAccept = {true}
93 - >
94 - 수락
95 - </styled.ModalButton>
96 - <styled.ModalButton
97 - onClick = {props.onRejectRequest}
98 - isAccept = {false}
99 - >
100 - 거절
101 - </styled.ModalButton>
102 - </styled.ModalButtonWrapper>
103 - </styled.ModalContent>
104 - </styled.ModalContentWrapper>
105 - <styled.ModalClsButtonWrapper/>
106 - </styled.ModalContainer> : null
107 } 120 }
108 <styled.ContentWrapper> 121 <styled.ContentWrapper>
122 + <styled.ContentButtonWrapper>
123 + <styled.ContentButton
124 + isSelect = {props.viewType === 'reg'}
125 + onClick = {props.onViewRegList}
126 + >
127 + 가입 대기
128 + </styled.ContentButton>
129 + <styled.ContentButton
130 + isSelect = {props.viewType === 'sec'}
131 + onClick = {props.onViewSecList}
132 + >
133 + 탈퇴 요청
134 + </styled.ContentButton>
135 + </styled.ContentButtonWrapper>
109 <styled.ContentTitle> 136 <styled.ContentTitle>
110 - 가입 대기 중 의사 회원 137 + {
111 - <styled.ContentExplain> 138 + props.viewType === 'sec' ?
112 - *클릭하면 상세정보를 확인할 수 있습니다. 139 + <>
113 - </styled.ContentExplain> 140 + 탈퇴 대기 중 의사 회원
141 + <styled.ContentExplain>
142 + *승인을 누르면 탈퇴를 승인합니다.
143 + </styled.ContentExplain>
144 + </> :
145 + <>
146 + 가입 대기 중 의사 회원
147 + <styled.ContentExplain>
148 + *클릭하면 상세정보를 확인할 수 있습니다.
149 + </styled.ContentExplain>
150 + </>
151 + }
114 </styled.ContentTitle> 152 </styled.ContentTitle>
115 - <styled.ContentBody> 153 + <styled.ContentInfoWrapper>
116 - <styled.ContentInfoWrapper> 154 + <styled.ContentInfo
117 - <styled.ContentInfo 155 + isLast = {false}
118 - isLast = {false} 156 + >
119 - > 157 + 분야
120 - 분야 158 + </styled.ContentInfo>
121 - </styled.ContentInfo> 159 + <styled.ContentInfo
122 - <styled.ContentInfo 160 + isLast = {false}
123 - isLast = {false} 161 + >
124 - > 162 + 이름
125 - 이름 163 + </styled.ContentInfo>
126 - </styled.ContentInfo> 164 + <styled.ContentInfo
127 - <styled.ContentInfo 165 + isLast = {props.viewType !== 'sec'}
166 + >
167 + 이메일
168 + </styled.ContentInfo>
169 + {
170 + props.viewType === 'sec' ?
171 + <styled.ContentInfo
128 isLast = {true} 172 isLast = {true}
129 > 173 >
130 - 이메일 174 + 탈퇴 수락
131 - </styled.ContentInfo> 175 + </styled.ContentInfo> : null
132 - </styled.ContentInfoWrapper> 176 + }
177 + </styled.ContentInfoWrapper>
178 + <styled.ContentBody>
133 { 179 {
134 - props.doctorRegReqList.length ? 180 + props.doctorList.length ?
135 - props.doctorRegReqList.map((doctor : any) => { 181 + props.doctorList.map((doctor : any) => {
136 return ( 182 return (
137 <styled.EachContentWrapper 183 <styled.EachContentWrapper
138 key = {doctor.doctorId} 184 key = {doctor.doctorId}
139 onClick = {() => props.onViewDetailReq(doctor.doctorId)} 185 onClick = {() => props.onViewDetailReq(doctor.doctorId)}
186 + disabled = {props.viewType === 'sec'}
140 > 187 >
141 <styled.EachContentNm 188 <styled.EachContentNm
142 isLast = {false} 189 isLast = {false}
...@@ -149,10 +196,22 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => { ...@@ -149,10 +196,22 @@ const ManagerMenuPresenter = (props : ManagerMenuProps) => {
149 {doctor.info.doctorNm} 196 {doctor.info.doctorNm}
150 </styled.EachContentNm> 197 </styled.EachContentNm>
151 <styled.EachContentNm 198 <styled.EachContentNm
152 - isLast = {true} 199 + isLast = {props.viewType !== 'sec'}
153 > 200 >
154 {doctor.doctorId} 201 {doctor.doctorId}
155 </styled.EachContentNm> 202 </styled.EachContentNm>
203 + {
204 + props.viewType === 'sec' ?
205 + <styled.EachContentNm
206 + isLast = {true}
207 + >
208 + <styled.AcceptButton
209 + onClick = {() => props.onAcceptSecReq(doctor.doctorId)}
210 + >
211 + 승인
212 + </styled.AcceptButton>
213 + </styled.EachContentNm> : null
214 + }
156 </styled.EachContentWrapper> 215 </styled.EachContentWrapper>
157 ) 216 )
158 }) : 217 }) :
......
1 -import styled, { keyframes } from 'styled-components'; 1 +import styled from 'styled-components';
2 2
3 3
4 -const ModalOn = keyframes `
5 - 0% {
6 - background-color : rgba(52, 52, 52, .0);
7 - }
8 - 20% {
9 - background-color : rgba(52, 52, 52, .2);
10 - }
11 - 40% {
12 - background-color : rgba(52, 52, 52, .4);
13 - }
14 - 60% {
15 - background-color : rgba(52, 52, 52, .5);
16 - }
17 - 80% {
18 - background-color : rgba(52, 52, 52, .6);
19 - }
20 - 100% {
21 - background-color : rgba(52, 52, 52, .7);
22 - }
23 -
24 -`;
25 -
26 export const Container = styled.div ` 4 export const Container = styled.div `
27 width : 100%; 5 width : 100%;
28 height : 80vh; 6 height : 80vh;
...@@ -33,92 +11,6 @@ export const Container = styled.div ` ...@@ -33,92 +11,6 @@ export const Container = styled.div `
33 `; 11 `;
34 12
35 13
36 -export const ModalContainer = styled.div `
37 - height : 100%;
38 - width : 100%;
39 - z-index : 99;
40 - position : absolute;
41 -
42 - display : flex;
43 - flex-direction : column;
44 -
45 - animation : ${ModalOn} .5s;
46 -
47 - background-color : rgba(52, 52, 52, .7);
48 -`;
49 -
50 -export const ModalClsButtonWrapper = styled.div `
51 - flex : 1;
52 -
53 - display : flex;
54 -
55 - justify-content : flex-end;
56 - align-items : center;
57 - padding : 0 20px;
58 -
59 - border : none;
60 - background-color : transprent;
61 -`;
62 -
63 -export const ModalClsButton = styled.button `
64 - border : none;
65 - background-color : transparent;
66 -
67 - cursor : pointer;
68 -
69 - color : #fff;
70 -
71 - display : flex;
72 - flex-direction : row;
73 -
74 - justify-content : center;
75 - align-items : center;
76 -
77 - transition : .25s all;
78 - &:hover {
79 - opacity : .5;
80 - }
81 -`;
82 -
83 -export const ModalClsButtonImg = styled.img `
84 - height : 20px;
85 - width : 20px;
86 -
87 - margin : 0 10px 0 0;
88 -`;
89 -
90 -export const ModalClsButtonText = styled.div `
91 - font-size : 18px;
92 - font-weight : 700;
93 -`;
94 -
95 -export const ModalContentWrapper = styled.div `
96 - flex : 8;
97 -
98 - display : flex;
99 - flex-direction : column;
100 -
101 - justify-content : center;
102 - align-items : center;
103 -
104 - border : none;
105 -`;
106 -
107 -export const ModalContent = styled.div `
108 - width : 700px;
109 - height : 500px;
110 -
111 - background-color : #fff;
112 - border : 1.2px solid #337DFF;
113 - border-radius : 5px;
114 -
115 - display : flex;
116 - flex-direction : column;
117 -
118 - // justify-content : center;
119 - align-items : center;
120 -`;
121 -
122 export const ModalTitleWrapper = styled.div ` 14 export const ModalTitleWrapper = styled.div `
123 flex : 1; 15 flex : 1;
124 border : none; 16 border : none;
...@@ -183,25 +75,96 @@ export const ModalInfoExplain = styled.div ` ...@@ -183,25 +75,96 @@ export const ModalInfoExplain = styled.div `
183 75
184 letter-spacing : 1px; 76 letter-spacing : 1px;
185 77
78 + display : flex;
79 + flex-direction : row;
80 + justify-content : center;
81 + align-items : center;
82 +
186 border : none; 83 border : none;
187 border-bottom : 1px solid #337DFF; 84 border-bottom : 1px solid #337DFF;
188 85
189 color : #337DFF; 86 color : #337DFF;
190 - padding : 2px 5px; 87 + padding : 2px 1px;
191 `; 88 `;
192 89
193 export const ModalInfo = styled.div ` 90 export const ModalInfo = styled.div `
194 - margin : 5px 0 20px 0; 91 + margin : 5px 0 10px 0;
195 - font-size : 20px; 92 + font-size : 13px;
196 - font-weight : 700; 93 + font-weight : 600;
94 +
95 + letter-spacing : 1px;
96 +
97 + display : flex;
98 + flex-direction : row;
99 + align-items : center;
100 +`;
101 +
102 +export const ModalInfoNotice = styled.div `
103 + font-size : 11px;
104 + color : #bbb;
105 +
106 + font-weight : 400;
107 +
108 + letter-spacing : 0px;
109 +
110 + margin : 5px 0 0px 0;
111 +`;
112 +
113 +export const DoctorLicenseViewWrapper = styled.div `
114 + display : flex;
115 + flex-direction : row;
116 +
117 + justify-content : center;
118 + align-items : center;
119 +
120 + border : none;
121 + background-color : transparent;
122 +`;
123 +
124 +export const DoctorLicenseViewButton = styled.button `
125 + margin : 5px 0 0 7px;
126 +
127 + border : 1px solid #343434;
128 + border-radius : 3px;
129 + background-color : #EAF2FF;
130 + padding : 2px 5px;
197 131
198 display : flex; 132 display : flex;
199 flex-direction : row; 133 flex-direction : row;
134 + justify-content : center;
200 align-items : center; 135 align-items : center;
136 +
137 + cursor : pointer;
138 + transition : .25s all;
139 +
140 + &:hover {
141 + border : 1px solid #337DFF;
142 + background-color : #337DFF;
143 + color : #fff;
144 + }
145 +
146 + font-size : 11px;
147 +`;
148 +
149 +export const DoctorLicenseViewInput = styled.input `
150 + padding : 2px 1px;
151 +
152 + font-size : 11px;
153 + letter-spacing : 1px;
154 + color : #343434;
155 +
156 + border : none;
157 + border-bottom : 1px solid #343434;
158 +
159 + &::placeholder {
160 + color : #ccc;
161 + }
201 `; 162 `;
202 163
203 export const ValidateButton = styled.button<{validate : string}> ` 164 export const ValidateButton = styled.button<{validate : string}> `
204 - margin : 0 0 0 15px; 165 + font-size : 11px;
166 +
167 + margin : 0 0 0 5px;
205 padding : 2px 5px; 168 padding : 2px 5px;
206 169
207 border-radius : 3px; 170 border-radius : 3px;
...@@ -288,8 +251,45 @@ export const ContentWrapper = styled.div ` ...@@ -288,8 +251,45 @@ export const ContentWrapper = styled.div `
288 251
289 `; 252 `;
290 253
254 +export const ContentButtonWrapper = styled.div `
255 + width : 100%;
256 + height : 10%;
257 + border : none;
258 +
259 + display : flex;
260 + flex-direction : row;
261 + justify-content : center;
262 + align-items : flex-end;
263 +
264 + gap : 10%;
265 +
266 + background-color : transparent;
267 +`;
268 +
269 +export const ContentButton = styled.button<{isSelect : boolean}> `
270 + background-color : ${props => props.isSelect ? '#337DFF' : 'transparent'};
271 + color : ${props => props.isSelect ? '#fff' : '#337DFF'};
272 + border : 1px solid #337DFF;
273 + border-radius : 4px;
274 +
275 + padding : 4px 10px;
276 +
277 + cursor : pointer;
278 +
279 + display : flex;
280 + justify-content : center;
281 + align-items : center;
282 +
283 + transition : .25s all;
284 +
285 + &:hover {
286 + opacity : .5;
287 + }
288 +`;
289 +
291 export const ContentTitle = styled.div ` 290 export const ContentTitle = styled.div `
292 width : 100%; 291 width : 100%;
292 + height : 20%;
293 border : none; 293 border : none;
294 border-bottom : 1px solid #ddd; 294 border-bottom : 1px solid #ddd;
295 295
...@@ -298,7 +298,6 @@ export const ContentTitle = styled.div ` ...@@ -298,7 +298,6 @@ export const ContentTitle = styled.div `
298 justify-content : center; 298 justify-content : center;
299 align-items : center; 299 align-items : center;
300 300
301 - padding : 4% 0;
302 font-size : 22px; 301 font-size : 22px;
303 font-weight : 600; 302 font-weight : 600;
304 letter-spacing : 1px; 303 letter-spacing : 1px;
...@@ -318,16 +317,17 @@ export const ContentExplain = styled.div ` ...@@ -318,16 +317,17 @@ export const ContentExplain = styled.div `
318 export const ContentBody = styled.div ` 317 export const ContentBody = styled.div `
319 overflow : scroll; 318 overflow : scroll;
320 319
321 - height : 79%; 320 + min-height : 60%;
321 + max-height : 60%;
322 322
323 border : none; 323 border : none;
324 324
325 - padding : 0 0 0 3px;
326 -
327 display : flex; 325 display : flex;
328 flex-direction : column; 326 flex-direction : column;
329 align-items : center; 327 align-items : center;
330 328
329 + padding : 0 0 0 3px;
330 +
331 &::-webkit-scrollbar { 331 &::-webkit-scrollbar {
332 width : 3px; 332 width : 3px;
333 background-color : transparent; 333 background-color : transparent;
...@@ -341,6 +341,7 @@ export const ContentBody = styled.div ` ...@@ -341,6 +341,7 @@ export const ContentBody = styled.div `
341 341
342 export const ContentInfoWrapper = styled.div ` 342 export const ContentInfoWrapper = styled.div `
343 width : 100%; 343 width : 100%;
344 + height : 10%;
344 border : none; 345 border : none;
345 border-bottom : 1px solid #a0a0a0; 346 border-bottom : 1px solid #a0a0a0;
346 347
...@@ -350,7 +351,6 @@ export const ContentInfoWrapper = styled.div ` ...@@ -350,7 +351,6 @@ export const ContentInfoWrapper = styled.div `
350 justify-content : center; 351 justify-content : center;
351 align-items : center; 352 align-items : center;
352 353
353 - padding : 12px 0px;
354 `; 354 `;
355 355
356 export const ContentInfo = styled.div<{isLast : boolean}> ` 356 export const ContentInfo = styled.div<{isLast : boolean}> `
...@@ -388,13 +388,15 @@ export const EachContentWrapper = styled.button ` ...@@ -388,13 +388,15 @@ export const EachContentWrapper = styled.button `
388 388
389 padding : 10px 0px; 389 padding : 10px 0px;
390 390
391 - cursor : pointer; 391 + :not(:disabled) {
392 + cursor : pointer;
392 393
393 - transition : .1s all; 394 + transition : .1s all;
394 395
395 - &:hover { 396 + &:hover {
396 - background-color : #337DFF; 397 + background-color : #337DFF;
397 - color : #fff; 398 + color : #fff;
399 + }
398 } 400 }
399 401
400 `; 402 `;
...@@ -417,10 +419,33 @@ export const EachContentNm = styled.div<{isLast : boolean}> ` ...@@ -417,10 +419,33 @@ export const EachContentNm = styled.div<{isLast : boolean}> `
417 419
418 `; 420 `;
419 421
422 +export const AcceptButton = styled.button `
423 + background-color : transparent;
424 + color : #337DFF;
425 + border : 1px solid #337DFF;
426 + border-radius : 3px;
427 + padding : 2px 10px;
428 +
429 + display : flex;
430 + justify-content : center;
431 + align-items : center;
432 +
433 + cursor : pointer;
434 +
435 + transition : .25s all;
436 +
437 + &:hover {
438 + background-color : #337DFF;
439 + color : #fff;
440 + }
441 +`;
442 +
420 export const NothingWrapper = styled.div ` 443 export const NothingWrapper = styled.div `
421 height : 100%; 444 height : 100%;
422 width : 100%; 445 width : 100%;
423 446
447 + border : none;
448 +
424 display : flex; 449 display : flex;
425 justify-content : center; 450 justify-content : center;
426 align-items : center; 451 align-items : center;
......
1 -import React, { useState, useEffect } from "react"; 1 +import React, { useState, useEffect, useRef } from "react";
2 import { RouteComponentProps } from 'react-router-dom'; 2 import { RouteComponentProps } from 'react-router-dom';
3 3
4 -import { useRecoilValue } from "recoil"; 4 +import { useRecoilValue, useRecoilState } from "recoil";
5 import * as recoilUtil from '../../util/recoilUtil'; 5 import * as recoilUtil from '../../util/recoilUtil';
6 6
7 import validator from 'validator'; 7 import validator from 'validator';
...@@ -11,7 +11,6 @@ import Header from '../../components/Header'; ...@@ -11,7 +11,6 @@ import Header from '../../components/Header';
11 import RegisterPresenter from "./RegisterPresenter"; 11 import RegisterPresenter from "./RegisterPresenter";
12 12
13 import { authApi } from '../../api'; 13 import { authApi } from '../../api';
14 -import { resourceLimits } from "worker_threads";
15 14
16 15
17 // eslint-disable-next-line @typescript-eslint/no-empty-interface 16 // eslint-disable-next-line @typescript-eslint/no-empty-interface
...@@ -20,13 +19,13 @@ interface RegisterProps extends RouteComponentProps {} ...@@ -20,13 +19,13 @@ interface RegisterProps extends RouteComponentProps {}
20 const RegisterContainer = (props : RegisterProps) => { 19 const RegisterContainer = (props : RegisterProps) => {
21 20
22 const token = useRecoilValue(recoilUtil.token); 21 const token = useRecoilValue(recoilUtil.token);
22 + const [loading, setLoading] = useRecoilState(recoilUtil.loading);
23 23
24 const [registerForm, setRegisterForm] = useState<any>({ 24 const [registerForm, setRegisterForm] = useState<any>({
25 userId : '', 25 userId : '',
26 password : '', 26 password : '',
27 passwordCheck : '', 27 passwordCheck : '',
28 info : { 28 info : {
29 - doctorLicense : '',
30 hospitalNm : '', 29 hospitalNm : '',
31 hospitalAddr : '', 30 hospitalAddr : '',
32 contact : '', 31 contact : '',
...@@ -34,10 +33,18 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -34,10 +33,18 @@ const RegisterContainer = (props : RegisterProps) => {
34 doctorNm : '', 33 doctorNm : '',
35 }, 34 },
36 }); 35 });
36 + const [doctorInfoFile, setDoctorInfoFile] = useState<FileList | null>(null);
37 + const doctorInfoFile_Select = useRef(null);
37 38
38 const [page, setPage] = useState<number>(1); 39 const [page, setPage] = useState<number>(1);
39 const [error, setError] = useState<string | null>(null); 40 const [error, setError] = useState<string | null>(null);
40 41
42 + const [searchHospital, setSearchHospital] = useState<boolean>(false);
43 + const [hospitalNm, setHospitalNm] = useState<string>('');
44 + const [hospitalSearchPage, setHospitalSearchPage] = useState<number>(1);
45 + const [hospitalSearchPageList, setHospitalSearchPageList] = useState<number[]>([1]);
46 + const [hospitalList, setHospitalList] = useState<any[]>([]);
47 + const [selectHospital, setSelectHospital] = useState<any>(null);
41 48
42 49
43 const fetchData = async() => { 50 const fetchData = async() => {
...@@ -46,7 +53,7 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -46,7 +53,7 @@ const RegisterContainer = (props : RegisterProps) => {
46 if (result.statusText === 'OK') { 53 if (result.statusText === 'OK') {
47 props.history.push('/'); 54 props.history.push('/');
48 } 55 }
49 - } 56 + }
50 }; 57 };
51 58
52 const onCancleRegister = () => { 59 const onCancleRegister = () => {
...@@ -61,7 +68,6 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -61,7 +68,6 @@ const RegisterContainer = (props : RegisterProps) => {
61 } 68 }
62 }; 69 };
63 70
64 -
65 const validateRegisterForm = () => { 71 const validateRegisterForm = () => {
66 if(page === 1) { 72 if(page === 1) {
67 if (!validator.isEmail(registerForm.userId)) { 73 if (!validator.isEmail(registerForm.userId)) {
...@@ -74,9 +80,8 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -74,9 +80,8 @@ const RegisterContainer = (props : RegisterProps) => {
74 setError('비밀번호가 일치하지 않습니다.') 80 setError('비밀번호가 일치하지 않습니다.')
75 } else setError(null); 81 } else setError(null);
76 } else if(page === 2) { 82 } else if(page === 2) {
77 - if(!registerForm.info.doctorLicense.length && 83 + if(!doctorInfoFile) {
78 - !validator.isAlphanumeric(registerForm.info.doctorLicense)) { 84 + setError('의사 자격 인증 파일을 첨부해야 합니다.');
79 - setError('의사 자격 번호를 입력해야 합니다.');
80 } else if(registerForm.info.doctorNm.length < 2) { 85 } else if(registerForm.info.doctorNm.length < 2) {
81 setError('의사 이름을 올바르게 입력해야 합니다.'); 86 setError('의사 이름을 올바르게 입력해야 합니다.');
82 } else if(!registerForm.info.contact) { 87 } else if(!registerForm.info.contact) {
...@@ -116,33 +121,11 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -116,33 +121,11 @@ const RegisterContainer = (props : RegisterProps) => {
116 }; 121 };
117 122
118 const onSetDoctorLicense = (e : React.ChangeEvent<HTMLInputElement>) => { 123 const onSetDoctorLicense = (e : React.ChangeEvent<HTMLInputElement>) => {
119 - setRegisterForm({ 124 + setDoctorInfoFile(e.target.files);
120 - ...registerForm,
121 - info : {
122 - ...registerForm.info,
123 - doctorLicense : e.target.value,
124 - },
125 - });
126 }; 125 };
127 126
128 const onSetHospitalNm = (e : React.ChangeEvent<HTMLInputElement>) => { 127 const onSetHospitalNm = (e : React.ChangeEvent<HTMLInputElement>) => {
129 - setRegisterForm({ 128 + setHospitalNm(e.target.value);
130 - ...registerForm,
131 - info : {
132 - ...registerForm.info,
133 - hospitalNm : e.target.value,
134 - },
135 - });
136 - };
137 -
138 - const onSetHospitalAddr = (e : React.ChangeEvent<HTMLInputElement>) => {
139 - setRegisterForm({
140 - ...registerForm,
141 - info : {
142 - ...registerForm.info,
143 - hospitalAddr : e.target.value,
144 - },
145 - });
146 }; 129 };
147 130
148 const onSetContact = (e : React.ChangeEvent<HTMLInputElement>) => { 131 const onSetContact = (e : React.ChangeEvent<HTMLInputElement>) => {
...@@ -175,6 +158,56 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -175,6 +158,56 @@ const RegisterContainer = (props : RegisterProps) => {
175 }); 158 });
176 }; 159 };
177 160
161 + const onSearchHospital = async () => {
162 + try {
163 + setLoading(true);
164 + setSearchHospital(true);
165 + const result = await authApi.searchHospital(hospitalNm, hospitalSearchPage);
166 + if(result.statusText === 'OK') {
167 + setLoading(false);
168 + setHospitalSearchPageList(new Array(result.data.totalPage).fill(null).map((item : null, index : number) => index + 1));
169 + setHospitalList(result.data.hospitalList.length ? result.data.hospitalList : [result.data.hospitalList]);
170 + }
171 + } catch(e : any) {
172 + setLoading(false);
173 + Alert.onError('알 수 없는 에러로 검색에 실패했습니다.', () => null);
174 + }
175 + };
176 +
177 + const onSetSearchPrevPage = () => {
178 + //set Prev Page
179 + const pageSlice = 5;
180 + if(hospitalSearchPage > pageSlice) {
181 + setHospitalSearchPage(Math.floor((hospitalSearchPage - 1) / pageSlice) * pageSlice);
182 + }
183 + };
184 +
185 + const onSetSearchNextPage = () => {
186 + //set Next Page
187 + const pageSlice = 5;
188 + if(hospitalSearchPage <= Math.floor((hospitalSearchPageList.length - 1) / pageSlice) * pageSlice) {
189 + setHospitalSearchPage(Math.ceil(hospitalSearchPage / pageSlice) * pageSlice + 1);
190 + }
191 + };
192 +
193 + const onConfirmSelectHospital = () => {
194 + setSearchHospital(false);
195 + setHospitalSearchPage(1);
196 + setHospitalSearchPageList([1]);
197 + setHospitalList([]);
198 + };
199 +
200 + const onCancelSelectHospital = () => {
201 + Alert.onCheck('병원 등록이 취소됩니다. 계속하시겠습니까?', () => {
202 + setSearchHospital(false);
203 + setHospitalNm('');
204 + setHospitalSearchPage(1);
205 + setHospitalSearchPageList([1]);
206 + setHospitalList([]);
207 + setSelectHospital(null);
208 + }, () => null);
209 + };
210 +
178 const onSubmitButton = () => { 211 const onSubmitButton = () => {
179 if(error) { 212 if(error) {
180 Alert.onError(error, () => null); 213 Alert.onError(error, () => null);
...@@ -186,26 +219,80 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -186,26 +219,80 @@ const RegisterContainer = (props : RegisterProps) => {
186 } else if(page === 2) { 219 } else if(page === 2) {
187 setPage(3); 220 setPage(3);
188 } else if(page === 3) { 221 } else if(page === 3) {
222 +
223 + const Data = new FormData();
224 + Data.append('userId', registerForm.userId);
225 + Data.append('password', registerForm.password);
226 + Data.append('passwordCheck', registerForm.passwordCheck);
227 +
228 + Data.append('hospitalNm', registerForm.info.hospitalNm);
229 + Data.append('hospitalAddr', registerForm.info.hospitalAddr);
230 + Data.append('contact', registerForm.info.contact);
231 + Data.append('doctorNm', registerForm.info.doctorNm);
232 + Data.append('doctorType', registerForm.info.doctorType);
233 +
234 + Data.append('doctorInfoFile', doctorInfoFile ? doctorInfoFile[0] : '');
235 +
236 +
189 const onRegisterDoctor = async () => { 237 const onRegisterDoctor = async () => {
238 + //로딩 진행
239 + setLoading(true);
240 +
190 try { 241 try {
191 - const result = await authApi.registerDoctor(registerForm); 242 + const result = await authApi.registerDoctor(Data);
192 if(result.data === 'Created') { 243 if(result.data === 'Created') {
244 + setLoading(false);
193 Alert.onSuccess('회원가입 성공, 관리자의 승인을 대기하세요.', () => props.history.push('/login')); 245 Alert.onSuccess('회원가입 성공, 관리자의 승인을 대기하세요.', () => props.history.push('/login'));
194 } 246 }
195 - } catch(e) { 247 + } catch(e : any) {
248 + setLoading(false);
196 Alert.onError(e.response.data.error, () => null); 249 Alert.onError(e.response.data.error, () => null);
197 } 250 }
198 }; 251 };
199 252
200 - Alert.onCheck('입력하신 정보로 회원가입을 진행하시겠습니까?', onRegisterDoctor, () => null); 253 + if(selectHospital) {
254 + Alert.onCheck('입력하신 정보로 회원가입을 진행하시겠습니까?', onRegisterDoctor, () => null);
255 + } else {
256 + Alert.onError('검색 버튼을 눌러 병원을 선택해주세요.', () => null);
257 + }
258 +
201 } 259 }
202 260
203 }; 261 };
204 262
263 +
264 +
205 useEffect(() => { 265 useEffect(() => {
206 validateRegisterForm(); 266 validateRegisterForm();
207 - }, [registerForm, page]); 267 + }, [registerForm, doctorInfoFile, page]);
268 +
269 + useEffect(() => {
270 + if(selectHospital) {
271 + setHospitalNm(selectHospital.yadmNm);
272 + setRegisterForm({
273 + ...registerForm,
274 + info : {
275 + ...registerForm.info,
276 + hospitalNm : selectHospital.yadmNm,
277 + hospitalAddr : selectHospital.addr,
278 + },
279 + });
280 + } else {
281 + setHospitalNm('');
282 + setRegisterForm({
283 + ...registerForm,
284 + info : {
285 + ...registerForm.info,
286 + hospitalNm : '',
287 + hospitalAddr : '',
288 + },
289 + });
290 + }
291 + }, [selectHospital]);
208 292
293 + useEffect(() => {
294 + if(searchHospital) onSearchHospital();
295 + }, [hospitalSearchPage]);
209 296
210 useEffect(() => { 297 useEffect(() => {
211 fetchData(); 298 fetchData();
...@@ -218,6 +305,8 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -218,6 +305,8 @@ const RegisterContainer = (props : RegisterProps) => {
218 <Header {...props}/> 305 <Header {...props}/>
219 <RegisterPresenter 306 <RegisterPresenter
220 registerForm = {registerForm} 307 registerForm = {registerForm}
308 + doctorInfoFile = {doctorInfoFile}
309 + doctorInfoFile_Select = {doctorInfoFile_Select}
221 page = {page} 310 page = {page}
222 error = {error} 311 error = {error}
223 312
...@@ -228,12 +317,28 @@ const RegisterContainer = (props : RegisterProps) => { ...@@ -228,12 +317,28 @@ const RegisterContainer = (props : RegisterProps) => {
228 onSetPassword = {onSetPassword} 317 onSetPassword = {onSetPassword}
229 onSetPasswordCheck = {onSetPasswordCheck} 318 onSetPasswordCheck = {onSetPasswordCheck}
230 onSetDoctorLicense = {onSetDoctorLicense} 319 onSetDoctorLicense = {onSetDoctorLicense}
320 + hospitalNm = {hospitalNm}
231 onSetHospitalNm = {onSetHospitalNm} 321 onSetHospitalNm = {onSetHospitalNm}
232 - onSetHospitalAddr = {onSetHospitalAddr}
233 onSetContact = {onSetContact} 322 onSetContact = {onSetContact}
234 onSetDoctorType = {onSetDoctorType} 323 onSetDoctorType = {onSetDoctorType}
235 onSetDoctorNm = {onSetDoctorNm} 324 onSetDoctorNm = {onSetDoctorNm}
236 onSubmitButton = {onSubmitButton} 325 onSubmitButton = {onSubmitButton}
326 +
327 + searchHospital = {searchHospital}
328 + setSearchHospital = {setSearchHospital}
329 + onSearchHospital = {onSearchHospital}
330 + hospitalSearchPage = {hospitalSearchPage}
331 + setHospitalSearchPage = {setHospitalSearchPage}
332 + hospitalSearchPageList = {hospitalSearchPageList}
333 + onSetSearchPrevPage = {onSetSearchPrevPage}
334 + onSetSearchNextPage = {onSetSearchNextPage}
335 +
336 + onConfirmSelectHospital = {onConfirmSelectHospital}
337 + onCancelSelectHospital = {onCancelSelectHospital}
338 +
339 + hospitalList = {hospitalList}
340 + selectHospital = {selectHospital}
341 + setSelectHospital = {setSelectHospital}
237 /> 342 />
238 </> 343 </>
239 ) 344 )
......
1 import React from 'react'; 1 import React from 'react';
2 2
3 +import Modal from '../../components/Modal';
3 import * as styled from './RegisterStyled'; 4 import * as styled from './RegisterStyled';
4 5
5 6
7 +const lensImg = '/static/img/lens.png';
8 +const check = '/static/img/check.png';
9 +const uncheck = '/static/img/uncheck.png'
10 +const next = '/static/img/next.png';
11 +const prev = '/static/img/prev.png';
12 +
13 +
6 interface RegisterProps { 14 interface RegisterProps {
7 registerForm : { 15 registerForm : {
8 userId : string; 16 userId : string;
9 password : string; 17 password : string;
10 passwordCheck : string; 18 passwordCheck : string;
11 info : { 19 info : {
12 - doctorLicense : string;
13 hospitalNm : string; 20 hospitalNm : string;
14 hospitalAddr : string; 21 hospitalAddr : string;
15 contact : string; 22 contact : string;
...@@ -17,6 +24,8 @@ interface RegisterProps { ...@@ -17,6 +24,8 @@ interface RegisterProps {
17 doctorNm : string; 24 doctorNm : string;
18 }, 25 },
19 }; 26 };
27 + doctorInfoFile : FileList | null;
28 + doctorInfoFile_Select : any;
20 page : number; 29 page : number;
21 error : string | null; 30 error : string | null;
22 31
...@@ -27,18 +36,122 @@ interface RegisterProps { ...@@ -27,18 +36,122 @@ interface RegisterProps {
27 onSetPassword : React.ChangeEventHandler<HTMLInputElement>; 36 onSetPassword : React.ChangeEventHandler<HTMLInputElement>;
28 onSetPasswordCheck : React.ChangeEventHandler<HTMLInputElement>; 37 onSetPasswordCheck : React.ChangeEventHandler<HTMLInputElement>;
29 onSetDoctorLicense : React.ChangeEventHandler<HTMLInputElement>; 38 onSetDoctorLicense : React.ChangeEventHandler<HTMLInputElement>;
39 + hospitalNm : string;
30 onSetHospitalNm : React.ChangeEventHandler<HTMLInputElement>; 40 onSetHospitalNm : React.ChangeEventHandler<HTMLInputElement>;
31 - onSetHospitalAddr : React.ChangeEventHandler<HTMLInputElement>;
32 onSetContact : React.ChangeEventHandler<HTMLInputElement>; 41 onSetContact : React.ChangeEventHandler<HTMLInputElement>;
33 onSetDoctorType : React.ChangeEventHandler<HTMLInputElement>; 42 onSetDoctorType : React.ChangeEventHandler<HTMLInputElement>;
34 onSetDoctorNm : React.ChangeEventHandler<HTMLInputElement>; 43 onSetDoctorNm : React.ChangeEventHandler<HTMLInputElement>;
35 onSubmitButton : () => void; 44 onSubmitButton : () => void;
36 45
46 + searchHospital : boolean;
47 + setSearchHospital : (arg0 : boolean) => void;
48 + onSearchHospital : () => void;
49 +
50 + hospitalSearchPage : number;
51 + setHospitalSearchPage : (arg0 : number) => void;
52 + hospitalSearchPageList : number[];
53 + onSetSearchPrevPage : () => void;
54 + onSetSearchNextPage : () => void;
55 +
56 + onConfirmSelectHospital : () => void;
57 + onCancelSelectHospital : () => void;
58 +
59 + hospitalList : any[];
60 + selectHospital : any;
61 + setSelectHospital : (arg0 : any) => void;
37 } 62 }
38 63
39 const RegisterPresenter = (props : RegisterProps) => { 64 const RegisterPresenter = (props : RegisterProps) => {
40 return ( 65 return (
41 <styled.Container> 66 <styled.Container>
67 + {
68 + props.searchHospital ?
69 + <Modal onModalClose = {props.onCancelSelectHospital}>
70 + <>
71 + <styled.SearchTitle>
72 + {`[${props.hospitalNm}] 에 대한 검색 결과 : `}
73 + <styled.SearchResultCount style = {{marginLeft : 5, marginRight : 5,}}>총 </styled.SearchResultCount>
74 + {props.hospitalSearchPageList.length}
75 + <styled.SearchResultCount>페이지</styled.SearchResultCount>
76 + </styled.SearchTitle>
77 + <styled.HospitalListWrapper>
78 + <styled.HospitalListInfo>
79 + <styled.HospitalListInfoEach isLast = {false}>이름</styled.HospitalListInfoEach>
80 + <styled.HospitalListInfoEach isLast = {false}>주소</styled.HospitalListInfoEach>
81 + <styled.HospitalListInfoEach isLast = {true}>선택</styled.HospitalListInfoEach>
82 + </styled.HospitalListInfo>
83 + {
84 + props.hospitalList.map((hospital : any) => {
85 + return (
86 + <styled.HospitalListEach
87 + key = {hospital.addr}
88 + >
89 + <styled.HospitalListEachInfo isLast = {false}>
90 + {hospital.yadmNm}
91 + </styled.HospitalListEachInfo>
92 + <styled.HospitalListEachInfo isLast = {false}>
93 + {hospital.addr}
94 + </styled.HospitalListEachInfo>
95 + <styled.HospitalListEachInfo isLast = {true}>
96 + <styled.CheckButton
97 + onClick = {() => props.setSelectHospital(hospital)}
98 + >
99 + <styled.CheckButtonImg src = {
100 + props.selectHospital && props.selectHospital.addr === hospital.addr ?
101 + check : uncheck
102 + }/>
103 + </styled.CheckButton>
104 + </styled.HospitalListEachInfo>
105 + </styled.HospitalListEach>
106 + )
107 + })
108 + }
109 + </styled.HospitalListWrapper>
110 + <styled.PageWrapper>
111 + <styled.PageButton
112 + isSelect = {false}
113 + onClick = {props.onSetSearchPrevPage}
114 + >
115 + <styled.PageArrowImg src = {prev}/>
116 + </styled.PageButton>
117 + {
118 + props.hospitalSearchPageList.slice(Math.floor((props.hospitalSearchPage - 1) / 5) * 5, Math.floor((props.hospitalSearchPage - 1) / 5) * 5 + 5)
119 + .map((page : number) => {
120 + return (
121 + <styled.PageButton
122 + key = {page}
123 + isSelect = {props.hospitalSearchPage === page}
124 + onClick = {() => props.setHospitalSearchPage(page)}
125 + >
126 + {page}
127 + </styled.PageButton>
128 + )
129 + })
130 + }
131 + <styled.PageButton
132 + isSelect = {false}
133 + onClick = {props.onSetSearchNextPage}
134 + >
135 + <styled.PageArrowImg src = {next}/>
136 + </styled.PageButton>
137 + </styled.PageWrapper>
138 + <styled.ModalButtonWrapper>
139 + <styled.ModalButton
140 + isCloseButton = {false}
141 + onClick = {props.onConfirmSelectHospital}
142 + >
143 + 확인
144 + </styled.ModalButton>
145 + <styled.ModalButton
146 + isCloseButton = {true}
147 + onClick = {props.onCancelSelectHospital}
148 + >
149 + 취소
150 + </styled.ModalButton>
151 + </styled.ModalButtonWrapper>
152 + </>
153 + </Modal> : null
154 + }
42 <styled.RegisterWrapper> 155 <styled.RegisterWrapper>
43 <styled.RegisterBackButtonWrapper> 156 <styled.RegisterBackButtonWrapper>
44 <styled.RegisterBackButton 157 <styled.RegisterBackButton
...@@ -92,11 +205,19 @@ const RegisterPresenter = (props : RegisterProps) => { ...@@ -92,11 +205,19 @@ const RegisterPresenter = (props : RegisterProps) => {
92 <> 205 <>
93 <styled.RegisterInputWrapper> 206 <styled.RegisterInputWrapper>
94 <styled.RegisterInputText>의사 자격증 번호</styled.RegisterInputText> 207 <styled.RegisterInputText>의사 자격증 번호</styled.RegisterInputText>
95 - <styled.RegisterInput 208 + <input type = 'file'
96 - placeholder = "Doctor's License" 209 + style = {{ display : 'none' }}
97 - value = {props.registerForm.info.doctorLicense}
98 onChange = {props.onSetDoctorLicense} 210 onChange = {props.onSetDoctorLicense}
211 + ref = {props.doctorInfoFile_Select}
99 /> 212 />
213 + <styled.RegisterFileUploadWrapper>
214 + <styled.RegisterFileUploadButton onClick = {() => props.doctorInfoFile_Select.current.click()}>
215 + 파일 첨부
216 + </styled.RegisterFileUploadButton>
217 + <styled.RegisterFileUploadInfoText>
218 + {props.doctorInfoFile ? props.doctorInfoFile[0].name : ''}
219 + </styled.RegisterFileUploadInfoText>
220 + </styled.RegisterFileUploadWrapper>
100 </styled.RegisterInputWrapper> 221 </styled.RegisterInputWrapper>
101 <styled.RegisterInputWrapper> 222 <styled.RegisterInputWrapper>
102 <styled.RegisterInputText>이름</styled.RegisterInputText> 223 <styled.RegisterInputText>이름</styled.RegisterInputText>
...@@ -118,27 +239,33 @@ const RegisterPresenter = (props : RegisterProps) => { ...@@ -118,27 +239,33 @@ const RegisterPresenter = (props : RegisterProps) => {
118 props.page === 3 ? 239 props.page === 3 ?
119 <> 240 <>
120 <styled.RegisterInputWrapper> 241 <styled.RegisterInputWrapper>
121 - <styled.RegisterInputText>전문 분야</styled.RegisterInputText> 242 + <styled.RegisterInputText>병원</styled.RegisterInputText>
122 - <styled.RegisterInput 243 + <styled.RegisterInputWrapperForSearch>
123 - placeholder = "Doctor's Type" 244 + <styled.RegisterInput
124 - value = {props.registerForm.info.doctorType} 245 + placeholder = 'Hospital'
125 - onChange = {props.onSetDoctorType} 246 + value = {props.hospitalNm}
126 - /> 247 + onChange = {props.onSetHospitalNm}
248 + />
249 + <styled.RegisterInputSearchButton
250 + onClick = {props.onSearchHospital}
251 + >
252 + <styled.RegisterInputSearchButtonImg src = {lensImg}/>
253 + </styled.RegisterInputSearchButton>
254 + </styled.RegisterInputWrapperForSearch>
127 </styled.RegisterInputWrapper> 255 </styled.RegisterInputWrapper>
128 <styled.RegisterInputWrapper> 256 <styled.RegisterInputWrapper>
129 - <styled.RegisterInputText>병원 이름</styled.RegisterInputText> 257 + <styled.RegisterInputText>주소</styled.RegisterInputText>
130 <styled.RegisterInput 258 <styled.RegisterInput
131 - placeholder = 'Hospital' 259 + placeholder = 'Address'
132 - value = {props.registerForm.info.hospitalNm} 260 + value = {props.registerForm.info.hospitalAddr}
133 - onChange = {props.onSetHospitalNm}
134 /> 261 />
135 </styled.RegisterInputWrapper> 262 </styled.RegisterInputWrapper>
136 <styled.RegisterInputWrapper> 263 <styled.RegisterInputWrapper>
137 - <styled.RegisterInputText>병원 주소</styled.RegisterInputText> 264 + <styled.RegisterInputText>전문 분야</styled.RegisterInputText>
138 <styled.RegisterInput 265 <styled.RegisterInput
139 - placeholder = 'Address' 266 + placeholder = "Doctor's Type"
140 - value = {props.registerForm.info.hospitalAddr} 267 + value = {props.registerForm.info.doctorType}
141 - onChange = {props.onSetHospitalAddr} 268 + onChange = {props.onSetDoctorType}
142 /> 269 />
143 </styled.RegisterInputWrapper> 270 </styled.RegisterInputWrapper>
144 </> : null 271 </> : null
......
...@@ -10,6 +10,198 @@ export const Container = styled.div ` ...@@ -10,6 +10,198 @@ export const Container = styled.div `
10 align-items : center; 10 align-items : center;
11 `; 11 `;
12 12
13 +export const SearchTitle = styled.div `
14 + font-weight : 600;
15 + font-size : 20;
16 +
17 + color : #337DFF;
18 +
19 + display : flex;
20 + flex-direction : row;
21 +
22 + align-items : center;
23 + justify-content : center;
24 +
25 +`;
26 +
27 +export const SearchResultCount = styled.div `
28 + color : #343434;
29 +`;
30 +
31 +export const HospitalListWrapper = styled.div `
32 + margin : 20px 0;
33 +
34 + height : 200px;
35 + width : 80%;
36 +
37 + border : 1px solid #337DFF;
38 + border-radius : 3px;
39 +
40 + display : flex;
41 + flex-direction : column;
42 +`;
43 +
44 +export const HospitalListInfo = styled.div `
45 +
46 + height : 25px;
47 + width : 100%;
48 +
49 + border : none;
50 + border-bottom : 2px solid #ddd;
51 +
52 + display : flex;
53 + flex-direction : row;
54 +`;
55 +
56 +export const HospitalListInfoEach = styled.div<{isLast : boolean}> `
57 + flex : ${props => props.isLast ? '1' : '3'};
58 +
59 + display : flex;
60 + align-items : center;
61 + justify-content : center;
62 +
63 + font-size : 14px;
64 + font-weight : 500;
65 +
66 + color : #343434;
67 +
68 + border : none;
69 + border-right : ${props => props.isLast ? 'none' : '1px solid #ddd'};
70 +
71 + padding : 3px 5px;
72 +`;
73 +
74 +export const HospitalListEach = styled.div `
75 + min-height : 34px;
76 + max-height : 34px;
77 + width : 100%;
78 +
79 + display : flex;
80 + flex-direction : row;
81 +
82 + border : none;
83 + border-bottom : 1px solid #ddd;
84 +`;
85 +
86 +export const HospitalListEachInfo = styled.div<{isLast : boolean}> `
87 + flex : ${props => props.isLast ? '1' : '3'};
88 +
89 + display : flex;
90 +
91 + font-size : 12px;
92 + font-weight : 500;
93 +
94 + justify-content : center;
95 + align-items : center;
96 +
97 + border : none;
98 + border-right : ${props => props.isLast ? 'none' : '1px solid #ddd'};
99 +
100 + padding : 3px 5px;
101 +`;
102 +
103 +export const CheckButton = styled.button `
104 + border : none;
105 + background-color : transparent;
106 +
107 + height : 15px;
108 + width : 15px;
109 +
110 + display : flex;
111 + flex-direction : row;
112 + justify-content : center;
113 +
114 + cursor : pointer;
115 + transition : .25s all;
116 +
117 + &:hover {
118 + opacity : .5;
119 + }
120 +`;
121 +
122 +export const CheckButtonImg = styled.img `
123 + height : 15px;
124 + width : 15px;
125 +`;
126 +
127 +export const PageWrapper = styled.div `
128 + width : 50%;
129 + display : flex;
130 + flex-direction : row;
131 +
132 + justify-content : center;
133 + align-items : center;
134 +
135 + gap : 2%;
136 +`;
137 +
138 +export const PageButton = styled.button<{isSelect : boolean}> `
139 + height : 18px;
140 + width : 18px;
141 +
142 + display : flex;
143 + align-items : center;
144 + justify-content : center;
145 +
146 + border : none;
147 + border-radius : 4px;
148 + background-color : ${props => props.isSelect ? '#337DFF' : 'transparent'};
149 + color : ${props => props.isSelect ? '#fff' : '#343434'};
150 +
151 + font-size : 12px;
152 + font-weight : 600;
153 +
154 + cursor : pointer;
155 +
156 + transition : .25s all;
157 +
158 + &:hover {
159 + opacity : .7;
160 + }
161 +`;
162 +
163 +export const PageArrowImg = styled.img `
164 + height : 15px;
165 + width : 15px;
166 +`;
167 +
168 +export const ModalButtonWrapper = styled.div `
169 + margin : 20px 0 0 0;
170 + width : 50%;
171 +
172 + display : flex;
173 + flex-direction : row;
174 +
175 + justify-content : center;
176 + align-items : center;
177 +
178 + border : none;
179 +
180 + gap : 10%;
181 +`;
182 +
183 +export const ModalButton = styled.div<{isCloseButton : boolean}> `
184 + padding : 2.5% 10%;
185 + cursor : pointer;
186 +
187 + border : 1px solid ${props => props.isCloseButton ? '#343434' : '#337DFF'};
188 + background-color : ${props => props.isCloseButton ? 'transparent' : '#337DFF'};
189 +
190 + border-radius : 5px;
191 +
192 + color : ${props => props.isCloseButton ? '#343434' : '#fff'};
193 + font-weight : 600;
194 + font-size : 16px;
195 +
196 + transition : .25s all;
197 +
198 + &:hover {
199 + opacity : .7;
200 + }
201 +
202 +`
203 +
204 +
13 export const RegisterWrapper = styled.div ` 205 export const RegisterWrapper = styled.div `
14 width : 35%; 206 width : 35%;
15 border : none; 207 border : none;
...@@ -24,10 +216,9 @@ export const RegisterWrapper = styled.div ` ...@@ -24,10 +216,9 @@ export const RegisterWrapper = styled.div `
24 padding : 30px 3px; 216 padding : 30px 3px;
25 217
26 box-shadow: 0px 0px 10px #a0a0a0; 218 box-shadow: 0px 0px 10px #a0a0a0;
27 -
28 -
29 `; 219 `;
30 220
221 +
31 export const RegisterBackButtonWrapper = styled.div ` 222 export const RegisterBackButtonWrapper = styled.div `
32 width : 100%; 223 width : 100%;
33 border : none; 224 border : none;
...@@ -97,6 +288,18 @@ export const RegisterInputText = styled.div ` ...@@ -97,6 +288,18 @@ export const RegisterInputText = styled.div `
97 288
98 `; 289 `;
99 290
291 +export const RegisterInputWrapperForSearch = styled.div `
292 + display : flex;
293 + flex-direction : row;
294 +
295 + justify-content : center;
296 +
297 + width : 100%;
298 +
299 + border : none;
300 + background-color : transparent;
301 +`;
302 +
100 export const RegisterInput = styled.input ` 303 export const RegisterInput = styled.input `
101 width : 80%; 304 width : 80%;
102 padding : 5px 10px; 305 padding : 5px 10px;
...@@ -111,6 +314,87 @@ export const RegisterInput = styled.input ` ...@@ -111,6 +314,87 @@ export const RegisterInput = styled.input `
111 } 314 }
112 `; 315 `;
113 316
317 +export const RegisterFileUploadWrapper = styled.div `
318 + width : 80%;
319 + display : flex;
320 + flex-direction : row;
321 +
322 + justify-content : flex-start;
323 + align-items : center;
324 +
325 + border : none;
326 + background-color : transparent;
327 +`;
328 +
329 +export const RegisterFileUploadButton = styled.button `
330 + display : flex;
331 + flex-direction : row;
332 +
333 + justify-content : center;
334 + align-items : center;
335 +
336 + border-radius : 3px;
337 + border : 1px solid #343434;
338 + background-color : transparent;
339 + color : #343434;
340 +
341 + padding : 3px 4px;
342 +
343 + font-size : 12px;
344 + font-weight : 600;
345 +
346 + cursor : pointer;
347 + transition : .25s all;
348 +
349 + &:hover {
350 + border : 1px solid #337DFF;
351 + color : #fff;
352 + background-color : #337DFF;
353 + }
354 +
355 + margin : 0 5% 0 0;
356 +
357 +`;
358 +
359 +export const RegisterFileUploadInfoText = styled.div `
360 + font-size : 12px;
361 + font-weight : 600;
362 +
363 + color : #337DFF;
364 +`;
365 +
366 +export const RegisterInputSearchButton = styled.button `
367 + position : absolute;
368 +
369 + height : 25px;
370 + width : 25px;
371 +
372 + align-self : end;
373 +
374 + margin : 0 0 1px 24%;
375 +
376 + background-color : transparent;
377 + border : none;
378 +
379 + transition : .25s all;
380 + &:hover {
381 + opacity : .5;
382 + }
383 +
384 + display : flex;
385 + flex-direction : row;
386 +
387 + justify-content : center;
388 + align-items : center;
389 +
390 + cursor : pointer;
391 +`;
392 +
393 +export const RegisterInputSearchButtonImg = styled.img `
394 + height : 20px;
395 + width : 20px;
396 +`;
397 +
114 export const RegisterButtonWrapper = styled.div ` 398 export const RegisterButtonWrapper = styled.div `
115 margin : 20px 0 0 0; 399 margin : 20px 0 0 0;
116 400
......
This diff could not be displayed because it is too large.