박권수

Merge branch 'web'

......@@ -20,7 +20,8 @@
"@koa/cors": "^3.1.0",
"firebase-admin": "^9.11.1",
"moment": "^2.29.1",
"mqtt": "^4.2.6"
"mqtt": "^4.2.6",
"node-cron": "^3.0.0"
},
"devDependencies": {
"eslint": "^7.32.0"
......
......@@ -143,14 +143,11 @@ exports.getBottleInfo = async(ctx) => {
const message = 'req';
await Mqtt.mqttPublishMessage(client, { topic, message });
const bottleMedicine = await BottleMedicine.find({ bottleId })
.sort({ regDtm : 'desc' })
.limit(1);
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
if(bottleMedicine.length) {
if(bottleMedicine) {
const takeMedicineHist = await TakeMedicineHist
.find({ bmId : bottleMedicine[0]._id })
.find({ bmId : bottleMedicine._id })
.sort({ takeDate : 'desc' })
.populate('bmId');
......@@ -208,12 +205,10 @@ exports.getBottleFeedback = async ctx => {
return;
}
const bottleMedicine = await BottleMedicine.find({ bottleId })
.sort({ regDtm : 'desc' })
.limit(1);
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
if(bottleMedicine.length) {
const feedbackList = await Feedback.find({ bmId : bottleMedicine[0]._id })
if(bottleMedicine) {
const feedbackList = await Feedback.find({ bmId : bottleMedicine._id })
.sort({ fdbDtm : 'desc' })
.populate('bmId');
......@@ -294,6 +289,7 @@ exports.setMedicine = async(ctx) => {
bottleMedicine.setDoctorId(doctorId);
}
await BottleMedicine.updateMany({ bottleId }, { useYn : 'N '});
bottleMedicine.save();
......
......@@ -145,11 +145,12 @@ exports.getPatientDetail = async ctx => {
const reqUserBmList = [];
await Promise.all(reqUserBottleList.map(async bottle => {
const bmList = await BottleMedicine.find({
const bm = await BottleMedicine.findOne({
doctorId : userId,
bottleId : bottle.bottleId,
}).sort({ regDtm : 'desc' }).limit(1);
reqUserBmList.push(...bmList);
useYn : 'Y',
});
reqUserBmList.push(bm);
}));
const bottleList = await Promise.all(reqUserBmList.map(async bottleMedicine => {
......@@ -207,7 +208,7 @@ exports.getBottleDetail = async ctx => {
return;
}
const bottleMedicine = await BottleMedicine.findOne({ bottleId, doctorId : userId });
const bottleMedicine = await BottleMedicine.findOne({ bottleId, doctorId : userId, useYn : 'Y' });
if(!bottleMedicine) {
ctx.status = 403;
ctx.body = {
......@@ -318,11 +319,9 @@ exports.writeReqBottleFeedback = async ctx => {
return;
}
const bottleMedicine = await BottleMedicine.find({ bottleId, doctorId : userId })
.sort({ regDtm : 'desc' })
.limit(1);
const bottleMedicine = await BottleMedicine.findOne({ bottleId, doctorId : userId, useYn : 'Y' });
if(!bottleMedicine.length) {
if(!bottleMedicine) {
ctx.status = 403;
ctx.body = {
error : '약병에 대한 권한 없음'
......@@ -332,7 +331,7 @@ exports.writeReqBottleFeedback = async ctx => {
const newFeedback = new Feedback({
fdbType,
bmId : bottleMedicine[0]._id,
bmId : bottleMedicine._id,
doctorId : userId,
feedback,
});
......
......@@ -63,7 +63,7 @@ const bottleInfoUpdate = async(data) => {
humidity = parseFloat(humidity);
balance = parseInt(balance);
const bottleMedicine = await BottleMedicine.find({ bottleId }).sort((a, b) => a.regDtm < b.regDtm)[0];
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
if(bottleMedicine) {
if(isOpen) {
......@@ -83,7 +83,7 @@ const bottleInfoUpdate = async(data) => {
const transPublishingTopicAndMessage = async(bottleId) => {
const topic = 'bottle/' + bottleId + '/stb';
const bottleMedicine = await BottleMedicine.find({ bottleId }).sort((a, b) => a.regDtm < b.regDtm)[0];
const bottleMedicine = await BottleMedicine.findOne({ bottleId, useYn : 'Y' });
const takeMedicineHist = await TakeMedicineHist.find({
bmId : bottleMedicine._id
}).sort((a, b) => a.takeDate < b.takeDate)[0];
......
......@@ -28,11 +28,20 @@ const BottleMedicineSchema = new Schema({
required : true,
default : Date.now,
},
useYn : {
type : String,
required : true,
default : 'Y',
},
});
BottleMedicineSchema.methods.setDoctorId = function(doctorId) {
this.doctorId = doctorId;
};
BottleMedicineSchema.methods.setUseYn = function(useYn) {
this.useYn = useYn;
};
module.exports = mongoose.model('BottleMedicine', BottleMedicineSchema);
\ No newline at end of file
......
//toDO : Batch System
/**
* 21/09/14
* Author : 박권수
* 배치 시스템
* 1) 매년 지나면 프로필의 Age를 +1
* 2) Dosage에 따라, Push Notification 발송
*/
const cron = require('node-cron');
const Profile = require('../models/profile');
const BottleMedicine = require('../models/bottleMedicine');
\ No newline at end of file
This diff is collapsed. Click to expand it.
......@@ -18,6 +18,7 @@
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react-spinners": "^0.11.0",
"recoil": "^0.4.0",
"recoil-persist": "^3.0.0",
"styled-components": "^5.3.0",
......
......@@ -74,7 +74,7 @@ const Header = (props : HeaderProps) => {
</styled.HeaderLeftWrapper>
<styled.HeaderCenterWrapper>
<styled.TitleImg src = {headerImg} />
<styled.Title>내 손 안의 주치의</styled.Title>
<styled.Title>SMART MEDICINE BOX for Doctor</styled.Title>
</styled.HeaderCenterWrapper>
<styled.HeaderRightWrapper>
{
......
import styled from 'styled-components';
export const Container = styled.div `
z-index : 9999;
position : absolute;
height : 110vh;
width : 100%;
display : flex;
justify-content : center;
align-items : center;
background-color : rgba(0, 0, 0, .3);
`;
\ No newline at end of file
import React, { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import * as recoilItem from '../../util/recoilUtil';
import * as styled from './LoadingStyled';
import Loader from 'react-spinners/BeatLoader'
const LoadingContainer = () => {
const loading = useRecoilValue(recoilItem.loading);
return (
loading ?
<styled.Container>
<Loader color = '#337DFF' loading = {loading} size = {20}/>
</styled.Container> : null
)
};
export default LoadingContainer;
\ No newline at end of file
......@@ -13,4 +13,10 @@ export const userTypeCd = atom({
key : 'userTypeCd',
default : 'NORMAL',
effects_UNSTABLE : [persistAtom],
});
export const loading = atom({
key : 'loading',
default : false,
effects_UNSTABLE : [persistAtom],
});
\ No newline at end of file
......
......@@ -2,6 +2,7 @@ import React from "react";
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
import Error from '../components/error';
import Loading from '../components/Loading';
import { LoginContainer } from "./login";
import { RegisterContainer } from './register';
import { MainContainer } from "./main";
......@@ -12,6 +13,7 @@ const Router = () => {
return (
<BrowserRouter>
<Error />
<Loading />
<Switch>
<Route exact path = '/' component = {MainContainer}/>
<Route exact path = '/login' component = {LoginContainer}/>
......
......@@ -11,11 +11,14 @@ import * as Alert from '../../../util/alertMessage';
import { doctorApi, medicineApi } from '../../../api';
//toDo : Generate QR Code By Medicine Id
type DoctorMenuProps = RouteComponentProps
const DoctorMenuContainer = (props : DoctorMenuProps) => {
const token = useRecoilValue(recoilUtil.token);
const [loading, setLoading] = useRecoilState(recoilUtil.loading);
const [doctorInfo, setDoctorInfo] = useState<any>({
doctorNm : '',
......@@ -39,7 +42,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
const [searchPatientKeyword, setSearchPatientKeyword] = useState<string>('');
const [filteringPatientList, setFilteringPatientList] = useState<any>([]);
const [patientDetail, setPatientDetail] = useState<any>();
const [patientDetail, setPatientDetail] = useState<any>(null);
const [editModal, setEditModal] = useState<boolean>(false);
const [editPatientInfo, setEditPatientInfo] = useState<string>('');
......@@ -50,11 +53,13 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
const [prescribeModal, setPrescribeModal] = useState<boolean>(false);
const [searchMedicineKeyword, setSearchMedicineKeyword] = useState<string>('');
const [medicineInfo, setMedicineInfo] = useState<any>();
const [medicineList, setMedicineList] = useState<any>([]);
const [prescribeMedicine, setPrescribeMedicine] = useState<any>(null);
const fetchData = async() => {
try {
setLoading(true);
const res = await doctorApi.getDoctorsInfo(token);
if(res.statusText === 'OK') {
const { doctorInfo } = res.data;
......@@ -73,8 +78,10 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
setPatientList(res.data.patientList);
}).catch(error => console.log(error));
}
setLoading(false);
} catch(e) {
console.log(e);
setLoading(false);
}
};
......@@ -84,6 +91,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
const onFetchPatientDetail = async (patientId : string) => {
try {
setLoading(true);
await doctorApi.getPatientDetail(token, patientId).then(res => {
setPatientDetail(res.data);
setInfo({
......@@ -95,13 +103,16 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
patientInfo : res.data.info,
});
}).catch(err => console.log(err));
setLoading(false);
} catch(e) {
console.log(e);
setLoading(false);
}
};
const onInitialize = async () => {
await fetchData();
setPatientDetail(null);
setInfo({
infoType : 'DOCTOR',
userNm : doctorInfo.doctorNm,
......@@ -112,12 +123,7 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
});
setFilteringPatientList([]);
setSearchPatientKeyword('');
setEditModal(false);
setEditPatientInfo('');
setNewPatientRegisterModal(false);
setNewPatientSearchId('');
setNewPatientSearchResult(null);
setPatientDetail(null);
onCloseModal();
};
const onEditPatientInfo = (e : React.ChangeEvent<HTMLTextAreaElement>) => {
......@@ -149,7 +155,6 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
Alert.onError('환자의 특이사항을 기록하세요.', () => null);
}
};
......@@ -159,14 +164,18 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
const onSearchNewPatientByEmail = async () => {
try {
setLoading(true);
await doctorApi.searchPatientById(token, newPatientSearchId).then(res => {
setNewPatientSearchResult(res.data);
setLoading(false);
}).catch(err => {
console.log(err);
setLoading(false);
Alert.onError('검색 결과가 없습니다.', () => null);
setNewPatientSearchResult(null);
});
} catch(e : any) {
setLoading(false);
Alert.onError(e.response.data.error, () => null);
}
};
......@@ -203,6 +212,8 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
setEditPatientInfo('');
setPrescribeModal(false);
setSearchMedicineKeyword('');
setMedicineList([]);
setPrescribeMedicine(null);
};
const onGoBottleDetail = (bottleId : number) => {
......@@ -214,16 +225,32 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
};
const searchMedicine = async() => {
setMedicineList([]);
setPrescribeMedicine(null);
try {
setLoading(true);
const res = await medicineApi.searchMedicine(token, searchMedicineKeyword);
if(res.statusText === 'OK') {
setMedicineInfo(res.data);
console.log(res.data.medicineList)
setMedicineList(res.data.medicineList);
}
setLoading(false);
} catch(e : any) {
Alert.onError(e.response.data.error, () => null);
}
};
const onPrescribeSubmit = async() => {
//toDo : 처방해서, QR코드 생성
Alert.onWarning('작업 중입니다.', () => null);
};
const onPrescribeCancel = () => {
Alert.onCheck('취소하시면 작업중인 내용이 사라집니다.', () => {
onCloseModal();
}, () => null)
};
useEffect(() => {
if(!token || !token.length) {
......@@ -272,8 +299,12 @@ const DoctorMenuContainer = (props : DoctorMenuProps) => {
setPrescribeModal = {setPrescribeModal}
searchMedicineKeyword = {searchMedicineKeyword}
onSetSearchMedicineKeyword = {onSetSearchMedicineKeyword}
medicineInfo = {medicineInfo}
medicineList = {medicineList}
searchMedicine = {searchMedicine}
prescribeMedicine = {prescribeMedicine}
setPrescribeMedicine = {setPrescribeMedicine}
onPrescribeSubmit = {onPrescribeSubmit}
onPrescribeCancel = {onPrescribeCancel}
newPatientSearchResult = {newPatientSearchResult}
/>
......
......@@ -8,6 +8,8 @@ const lensImg = '/static/img/lens.png';
const closeButton = '/static/img/close.png';
const edit = '/static/img/edit.png';
const refreshing = '/static/img/refreshing.png';
const check = '/static/img/check.png';
const uncheck = '/static/img/uncheck.png'
interface DoctorMenuProps {
......@@ -49,8 +51,14 @@ interface DoctorMenuProps {
searchMedicineKeyword : string;
onSetSearchMedicineKeyword : React.ChangeEventHandler<HTMLInputElement>;
medicineInfo : any;
medicineList : any;
searchMedicine : () => void;
prescribeMedicine : any;
setPrescribeMedicine : (arg0 : any) => void;
onPrescribeSubmit : () => void;
onPrescribeCancel : () => void;
}
const DoctorMenuPresenter = (props : DoctorMenuProps) => {
......@@ -180,7 +188,61 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => {
</styled.ModalClsButtonWrapper>
<styled.ModalContentWrapper>
<styled.ModalContent>
<styled.MedicineSearchTitle>
약 검색
</styled.MedicineSearchTitle>
<styled.MedicineSearchInputWrapper>
<styled.MedicineSearchInput
placeholder = '증상, 또는 약 이름을 검색하세요.'
onChange = {props.onSetSearchMedicineKeyword}
value = {props.searchMedicineKeyword}
/>
<styled.MedicineSearchButton
onClick = {props.searchMedicine}
>
<styled.MedicineSearchButtonImg src = {lensImg}/>
</styled.MedicineSearchButton>
</styled.MedicineSearchInputWrapper>
<styled.MedicineSearchResultWrapper>
{
props.medicineList.length ?
props.medicineList.map((medicine : any) => {
return (
<styled.MedicineSearchResultEach
key = {medicine.medicineId}
onClick = {() => props.setPrescribeMedicine(medicine)}
>
<styled.MedicineSearchResultEachInfo>
{medicine.name}
</styled.MedicineSearchResultEachInfo>
<styled.MedicineSearchButtonImg
src = {
props.prescribeMedicine && props.prescribeMedicine.medicineId === medicine.medicineId ?
check : uncheck
}
/>
</styled.MedicineSearchResultEach>
)
}) :
<styled.NothingWrapper style = {{fontSize : 13,}}>
🤔검색 결과가 없습니다.
</styled.NothingWrapper>
}
</styled.MedicineSearchResultWrapper>
<styled.MedicinePrescribeButtonWrapper>
<styled.MedicinePrescribeButton
isClose = {false}
onClick = {props.onPrescribeSubmit}
>
처방
</styled.MedicinePrescribeButton>
<styled.MedicinePrescribeButton
isClose = {true}
onClick = {props.onPrescribeCancel}
>
취소
</styled.MedicinePrescribeButton>
</styled.MedicinePrescribeButtonWrapper>
</styled.ModalContent>
</styled.ModalContentWrapper>
<styled.ModalClsButtonWrapper/>
......@@ -303,7 +365,7 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => {
</styled.InfoAndSearchWrapper>
<styled.BottleListWrapper>
{
props.patientDetail && props.patientDetail.bottleList ?
props.patientDetail && props.patientDetail.bottleList.length ?
props.patientDetail.bottleList.map((bottle : any) => {
return (
<styled.EachBottleWrapper
......@@ -316,6 +378,11 @@ const DoctorMenuPresenter = (props : DoctorMenuProps) => {
</styled.EachBottleWrapper>
)
}) :
props.patientDetail && !props.patientDetail.bottleList.length ?
<styled.NothingWrapper>
🤔관리하고 있는 환자의 약병이 없습니다.
</styled.NothingWrapper>
:
<styled.NothingWrapper>
🤔먼저 환자를 선택하세요.
</styled.NothingWrapper>
......
......@@ -372,6 +372,166 @@ export const PatientInfoEditButton = styled.button `
}
`;
export const MedicineSearchTitle = styled.div `
font-size : 20px;
font-weight : 700;
color : #337DFF;
`;
export const MedicineSearchInputWrapper = styled.div `
margin : 20px 0;
display : flex;
flex-direction : row;
justify-content : space-between;
align-items : center;
width : 80%;
border : none;
border-bottom : 1px solid #343434;
`;
export const MedicineSearchInput = styled.input `
width : 80%;
border : none;
padding : 10px;
font-size : 15px;
font-weight : 500;
color : #343434;
transition : .25s all;
&::placeholder {
color : #dedede;
}
`;
export const MedicineSearchButton = styled.button `
width : 30px;
height : 30px;
display : flex;
justify-content : center;
align-items : center;
border : none;
background : transparent;
cursor : pointer;
transition : .25s all;
&:hover {
opacity : .5;
}
`;
export const MedicineSearchButtonImg = styled.img `
height : 15px;
width : 15px;
`;
export const MedicineSearchResultWrapper = styled.div `
overflow : scroll;
border : 1px solid;
min-height : 180px;
max-height : 180px;
width : 80%;
border : .5px solid #337DFF;
&::-webkit-scrollbar {
width : 3px;
background-color : transparent;
height : 1px;
}
&::-webkit-scrollbar-thumb {
background-color : #337DFF;
}
`;
export const MedicineSearchResultEach = styled.button `
width : 100%;
height : 36px;
display : flex;
flex-direction : row;
align-items : center;
justify-content : space-between;
border : none;
border-bottom : 1px solid #dedede;
cursor : pointer;
background : transparent;
color : #343434;
font-size : 15px;
font-weight : 500;
transition : .1s all;
&:hover {
background-color : #337DFF;
color : #fff;
}
`;
export const MedicineSearchResultEachInfo = styled.div `
margin : 0 10px;
`;
export const MedicineSelectButtonImg = styled.img `
height : 15px;
width : 15px;
`;
export const MedicinePrescribeButtonWrapper = styled.div `
margin : 20px 0 0 0;
width : 40%;
display : flex;
flex-direction : row;
justify-content : space-between;
`;
export const MedicinePrescribeButton = styled.button<{isClose : boolean}> `
height : 40px;
width : 100px;
background-color : ${props => props.isClose ? 'transparent' : '#337DFF'};
border : 1px solid ${props => props.isClose ? '#343434' : '#337DFF'};
border-radius : 4px;
font-size : 16px;
font-weight : 600;
color : ${props => props.isClose ? '#343434' : '#fff'};
cursor : pointer;
transition : .25s all;
&:hover {
opacity : .7;
}
`;
export const InfoAndSearchWrapper = styled.div `
......@@ -508,6 +668,12 @@ export const NewPatientButton = styled.button `
background-color : #337DFF;
color : #fff;
}
&:disabled {
cursor : default;
background-color : #337DFF;
color : #fff;
}
`;
export const SearchAndDetailWrapper = styled.div `
......
This diff could not be displayed because it is too large.