최정우 최정우 2023-04-15
230415 문진표 + 진료기록 + 심전도 + 혈압 + 공통파일업로드 처리(오류수정필요) 완성
@2026383f70375565bcf8873f8c1e4bc6f6e47711
 
client/resources/css/common-file.css (added)
+++ client/resources/css/common-file.css
@@ -0,0 +1,26 @@
+.file_input {text-align: left;}
+.file_input label {position: relative; cursor: pointer; display: inline-block; vertical-align: middle; overflow: hidden; width:100px; height:28px; background:#888; border:1px solid #666; color:#fff; text-align: center; line-height: 28px; font-weight:400; transition-duration:0.5s;}
+.file_input label:hover {background:#666; border-color:#444;}
+.file_input label input {position: absolute; width:0; height: 0; overflow: hidden; display: none;}
+
+.file_edite_box {width:100%; position:relative; height:130px; margin-top: 10px;}
+.file_edite_box.multiple-false {height:55px;}
+.file_edite_box p {color: #A6A6A6; text-align: center; position: absolute; z-index:2; top: 0; bottom: 0; left: 0; right: 0; margin:auto; height: 33px; width: fit-content; font-weight: 500; padding:5px; cursor: pointer;}
+.file_edite_box p > span {font-size:15px; margin:5px;}
+.file_edite_box p > span.icon::before {content: ' \271A '}
+.file_edite_box p.hidden {opacity: 0; z-index:3}
+.file_edite_box .filename {margin-top: 0px; width:100%; display:inline-block; position:absolute; z-index:1; border:1px solid #ddd; padding: 10px; height:100%; overflow-y:scroll; left:0}
+
+.file_edite_box:hover > p {color: #7BA518;}
+.file_edite_box:hover > .filename {border: 1px solid #7BA518;}
+
+.filename ul {background: #fff; text-align: left;}
+.filename ul > li {width:100%; margin-bottom:8px; font-size:13px; color:#333;}
+.filename ul > li > span {font-size:12px; margin-left:5px;}
+.filename ul > li li {padding-left:20px; font-size:13px; color:#555;}
+.filename ul > li li:before {content:'└'; color:#555; margin-right:5px;}
+
+.file_view {width:100%; position:relative; height:auto;}
+.file_view > .marker {padding: 2px 10px; border: 1px solid #ddd; border-radius: 3px; margin-left: 3px; font-size: 12px; background: #f9f9f9; font-weight: 400; cursor: default;}
+
+.filename a {cursor: default !important;}(No newline at end of file)
client/resources/css/common.css
--- client/resources/css/common.css
+++ client/resources/css/common.css
@@ -344,7 +344,8 @@
 }
 
 .btn-large:last-child,
-.btn-small:last-child {
+.btn-small:last-child,
+.btn-more-small {
   margin-right: 0;
 }
 
 
client/views/component/file/CommonFile.jsx (added)
+++ client/views/component/file/CommonFile.jsx
@@ -0,0 +1,247 @@
+import React from "react";
+
+export default function CommonFile ({commonFileList, multiple, accept, isEdit}) {
+    
+
+    let _multiple = (multiple == undefined || multiple == null) ? true : multiple;
+    let _isEdit = (isEdit == undefined || isEdit == null) ? true : isEdit;
+    let _accept = (accept == undefined || accept == null) ? null : accept;
+
+    const [componentFileList, setComponentFileList] = React.useState(commonFileList);
+
+    const inputFile = React.useRef();
+
+    const fileChange = (eventFiles) => {
+        //console.log('event에서 발생한 파일 : ', eventFiles);
+
+        //event에서 발생한 파일이 없을 때 -> return
+        if (eventFiles == false || eventFiles == null || eventFiles.length == 0) {
+            return;
+        }
+
+        //파일 한 개만 선택 가능할 때, 기존 파일이 있을 때 -> 확인 메세지 출력
+        if (_multiple == false && commonFileList.length > 0) {
+            //새 파일 사용 -> 기존 파일 삭제
+            if (confirm('기 선택 파일을 제거하고 새로 선택한 파일로 사용하시겠습니까?')) {
+                commonFileList.splice(0, commonFileList.length);
+            } else {//사용 안함 -> input file value(선택된 새파일) 제거
+                inputFile.current.value = null;
+                return;
+            }
+        }
+
+        
+        let loopSize = _multiple ? eventFiles.length : 1;
+        //console.log('_multiple ? eventFiles.length : ', loopSize);
+        for (var i = 0; i < loopSize; i++) {
+            //console.log('eventFiles['+i+'] : ', eventFiles[i]);
+
+            commonFileList.push({
+                commonFileGroupIdx: 0,
+                fileIdx: 0,
+                fileOriginName: getFileNameExceptExtension(eventFiles[i].name),
+                fileExtension: getFileExtension(eventFiles[i].name),
+                fileMaskName: null,
+                fileRelativePath: null,
+                fileAbsolutePath: null,
+                fileInsertDatetime: null,
+                fileSize: eventFiles[i].size,
+                fileDownloadCount: 0,
+                checkMessage: {
+                    isSuccess: false,
+		            message: '파일 검사 중',
+		            status: 0
+                }
+            });
+
+            //console.log('commonFileList : ', commonFileList);
+            //console.log('componentFileList : ', componentFileList);
+            setComponentFileList([...commonFileList]);
+            
+            //서버에 파일 업로드 후, 업로드된 내용 받아오기
+            const currentCommonFileIndex = commonFileList.length - 1;
+            const currentLoopIndex = i;
+            serverUpload(eventFiles[i], (data) => {
+                commonFileList[currentCommonFileIndex] = data;
+                setComponentFileList([...commonFileList]);
+
+                /*
+                 * 마지막 파일 일 때, -> input file에 value 지워주기
+                 * 필요이유 : 같은 파일 선택시, onchange가 동작하지 않음);
+                 * Callback에 넣은 이유 : 모든 처리완료 후, 지워줘야함 아니면, 처리 중간에 eventFiles애 값이 사라지는 현상이 발생함
+                 */
+                if ((currentLoopIndex + 1) == loopSize) {
+                    inputFile.current.value = null;
+                }
+            });
+        }
+    };
+
+    //서버에 파일 업로드
+    const serverUpload = (eventFile, callback, commonFileIndex) => {
+        //console.log('serverUpload eventFile : ', eventFile, ' commonFileIndex : ', commonFileIndex, ' commonFileList['+commonFileIndex+'] : ', commonFileList[commonFileIndex]);
+
+        var formData = new FormData();
+        formData.append('file', eventFile);
+        //console.log('formData : ', formData);
+
+        fetch('/common/file/upload.file', {
+			method: "POST",
+			/* headers: {
+				'Content-Type': 'multipart/form-data; charset=UTF-8'
+			}, */
+			body: formData,
+		}).then((response) => response.json()).then((data) => {
+			console.log("setFileAndUpload in serverUpload - response data : ", data);
+            callback(data[0]);
+		}).catch((error) => {
+			console.log('serverUpload() /common/file/upload.file error : ', error);
+		});
+    }
+
+    //파일명에서 확장자 얻기
+    const getFileExtension = (fileName) => {
+        if (fileName == undefined || fileName == null) {
+			return null;
+		} else {
+			const index = fileName.lastIndexOf(".");
+			if (index > -1) {
+                console.log('fileName.substring('+index+' + 1) : ', fileName.substring(index + 1));
+				return fileName.substring(index + 1).toLowerCase();
+			} else {
+				return null;
+			}
+		}
+    };
+
+    //파일명에서 확장자를 제외한 파일명 얻기
+    const getFileNameExceptExtension = (fileName) => {
+        if (fileName == undefined || fileName == null) {
+			return null;
+		} else {
+			const index = fileName.lastIndexOf(".");
+			if (index > -1) {
+                console.log('fileName.substring(0, '+index+') : ', fileName.substring(0, index));
+				return fileName.substring(0, index);
+			} else {
+				return null;
+			}
+		}
+    };
+
+    //적절한 파일 사이즈로 만들어주는 기능
+    const getFileSize = (byteSize) => {
+        var result = byteSize + " bytes";
+        // optional code for multiples approximation
+        var type = ["KB", "MB", "GB", "TB", "PB"];
+        for (var i = 0, reSize = (byteSize / 1024); reSize > 1 && i < type.length; reSize /= 1024, i++) {
+            result = reSize.toFixed(3) + " " + type[i];
+        }
+        return result;
+    }
+
+    //파일 다운로드
+    const fileDownload = (commonFile) => {
+        if (commonFile == undefined || commonFile.fileIdx == undefined
+            || commonFile.fileIdx == null || commonFile.fileIdx == 0) {
+            alert('서버에 업로드된 파일이 아닙니다.(파일다운로드 불가)');
+            return;
+        }
+
+        var formTag = document.createElement('form');
+        formTag.action = "/common/file/download.file";
+        formTag.method = "post";
+
+        var fileKeyInput = document.createElement('input');
+        fileKeyInput.type = "hidden";
+        fileKeyInput.name = "fileIdx";
+        fileKeyInput.value = commonFile.fileIdx;
+        formTag.appendChild(fileKeyInput);
+
+        document.body.appendChild(formTag);
+        formTag.submit();
+        formTag.remove();
+    };
+
+    //파일 삭제
+    const fileDelete = (commonFile, idx) => {
+        if (commonFile == undefined || commonFile.fileIdx == undefined
+            || commonFile.fileIdx == null || commonFile.fileIdx == 0) {
+            commonFileList.splice(idx, 1);
+            setComponentFileList([...commonFileList]);
+            return;
+        }
+
+        var formTag = document.createElement('form');
+        formTag.action = "/common/file/delete.file";
+        formTag.method = "post";
+
+        var fileKeyInput = document.createElement('input');
+        fileKeyInput.type = "hidden";
+        fileKeyInput.name = "fileIdx";
+        fileKeyInput.value = commonFile.fileIdx;
+        formTag.appendChild(fileKeyInput);
+
+        document.body.appendChild(formTag);
+        formTag.submit();
+        formTag.remove();
+    };
+
+    React.useEffect(() => {
+        console.log('===================================');
+        console.log('commonFileList : ', commonFileList);
+        console.log('componentFileList : ', componentFileList);
+        console.log('===================================');
+        console.log('');
+    }, [componentFileList])
+
+    return (
+        <div className="component-file-content" id="file-content">
+            <div id="file_edite">
+
+                <div className="file_input">
+                    <label className="btn_lightblue" title="파일선택">파일선택
+                        <input type="file" ref={inputFile} multiple={_multiple} accept={_accept}
+                            onChange={(e) => fileChange(e.target.files)}/>
+                    </label>
+                </div>
+
+                <div className={_multiple ? 'file_edite_box' : 'file_edite_box multiple-false'}
+                    onDragOver={(e) => e.preventDefault()}
+                    onDragEnter={(e) => e.preventDefault()}
+                    onDrop={(e) => {e.preventDefault(); fileChange(e.dataTransfer.files);}}>
+                    {componentFileList == null || componentFileList.length == 0
+                    ? <p onClick={() => {inputFile.current.click()}}>
+                        <span className="icon"></span>파일을 끌어다 넣거나, 파일을 선택해주세요.
+                    </p>
+                    : null}
+                    <div className="filename">
+                        <ul>
+                            {componentFileList.map((item, idx) => {return (
+                            <li>
+                                {/* 첨부파일 이미지 */}
+
+                                {/* 첨부파일 명 */}
+                                <a>{item.fileOriginName}.{item.fileExtension} [{getFileSize(item.fileSize)}]</a>
+
+                                {/* 파일첨부 결과 메세지 */}
+                                {item.checkMessage != null && item.checkMessage.message != null && item.checkMessage.message.length > 0
+                                ? <span className={item.checkMessage.isSuccess ? 'green' : 'red'}>({item.checkMessage.message})</span>
+                                : null}
+
+                                {/* 파일 다운로드 버튼 */}
+                                {item.fileIdx != null && item.fileIdx > 0
+                                 ? <button className="btn-more-small gray-btn margin-left2" onClick={() => fileDownload(item)}>다운로드</button>
+                                : null}
+                                
+                                {/* 파일 삭제 버튼 */}
+                                <button className="btn-more-small red-btn margin-left2" onClick={() => fileDelete(item, idx)}>삭제</button>
+                            </li>
+                            )})}
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    )
+}
client/views/index.jsx
--- client/views/index.jsx
+++ client/views/index.jsx
@@ -17,6 +17,7 @@
 import "../resources/css/reset.css";
 import "../resources/css/layout.css";
 import "../resources/css/common.css";
+import "../resources/css/common-file.css";
 import "../resources/css/main.css";
 import "../resources/css/responsive.css";
 
client/views/pages/healthcare/Medicalcare.jsx
--- client/views/pages/healthcare/Medicalcare.jsx
+++ client/views/pages/healthcare/Medicalcare.jsx
@@ -6,11 +6,8 @@
 import SubTitle from "../../component/SubTitle.jsx";
 import Modal from "../../component/Modal.jsx";
 import DetailTitle from "../../component/DetailTitle.jsx";
-import Modal_Questionnaire from "../../component/Modal_Questionnaire.jsx";
-import Modal_MedicalHistory from "../../component/Modal_MedicalHistory.jsx";
-import Modal_Blood from "../../component/Modal_Blood.jsx";
-import Modal_ECG from "../../component/Modal_ECG.jsx";
 import Pagination from "../../component/Pagination.jsx";
+import CommonFile from "../../component/file/CommonFile.jsx";
 
 import CommonUtil from "../../../resources/js/CommonUtil.js";
 
@@ -258,7 +255,6 @@
 
 
 
-
     /****************** 병원 진료 기록 (시작) ******************/
     const hospitalMedicalRecordInit = {
         'senior_id': null,
@@ -293,6 +289,7 @@
 			return false;
 		}
 
+        return true;
     }
 
 
@@ -359,6 +356,33 @@
 		});
     }
 
+    //병원 진료 기록 삭제
+	const hospitalMedicalRecordDelete = () => {
+        if (confirm('진료 기록을 삭제하시겠습니까?') == false) {
+            return;
+        }
+
+		fetch("/hospital/hospitalMedicalRecordDelete.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(hospitalMedicalRecord),
+		}).then((response) => response.json()).then((data) => {
+			console.log("병원 진료 기록 삭제 결과(건수) : ", data);
+			if (data > 0) {
+                setHospitalMedicalRecordInit();
+                hospitalMedicalRecordSelectList();
+                //closeModal2();
+				alert("삭제완료");
+			} else {
+				alert("삭제에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('hospitalMedicalRecordDelete() /hospital/hospitalMedicalRecordDelete.json error : ', error);
+		});
+    }
+
     //초기화 취소
     const setHospitalMedicalRecordInit = () => {
         setHospitalMedicalRecord({...hospitalMedicalRecordInit});
@@ -389,9 +413,401 @@
     /****************** 병원 진료 기록 (종료) ******************/
 
 
-    
-    const ecgSelectList = () => {};
-    const bloodPressureSelectList = () => {};
+
+    /****************** 심전도 (시작) ******************/
+    const ecgInit = {
+        'senior_id': null,
+        'ecg_record_idx': null,
+        'bradycardia_count': 0,
+        'tachycardia_count': 0,
+        'max_heart_rate': 0.0,
+        'min_heart_rate': 0.0,
+        'abnormal_heart_rate': 0.0,
+        'average_heart_rate': 0.0,
+        'max_rr': 0.0,
+        'min_rr': 0.0,
+        'average_rr': 0.0,
+        'ecg_reading_date': CommonUtil.getDate(),
+        'ecg_finding_type': '',
+        'ecg_finding_content': '',
+        'agent_id': defaultUserId,
+        'medical_record_idx': null,
+        'common_group_file_idx': null,
+        commonFileList: [],//심전도 업로드 파일 목록
+
+        isEdit: false,//직접 수정일 때, true (수정 or 등록 상관없음);
+    };
+    //심전도 정보
+    const [ecg, setEcg] = React.useState({...ecgInit});
+    const ecgRef = React.useRef({...ecgInit});
+
+    //심전도 유효성 검사
+    const ecgValidation = () => {
+        const target = ecg;
+		const targetRef = ecgRef;
+
+		if (CommonUtil.isEmpty(target['ecg_reading_date']) == true) {
+			targetRef.current['ecg_reading_date'].focus();
+			alert("판독 소견 측정 일자를 선택해 주세요.");
+			return false;
+		}
+        if (CommonUtil.isEmpty(target['ecg_finding_type']) == true) {
+			targetRef.current['ecg_finding_type'].focus();
+			alert("판독 소견 측정 종류를 입력해 주세요.");
+			return false;
+		}
+        if (CommonUtil.isEmpty(target['ecg_finding_content']) == true) {
+			targetRef.current['ecg_finding_content'].focus();
+			alert("판독 소견 측정 내용을 입력해 주세요.");
+			return false;
+		}
+        //파일 입력
+		if (target.isEdit == false) {
+            if (CommonUtil.isEmpty(target['commonFileList']) == true) {
+                //targetRef.current['commonFileList'].focus();
+                alert("심전도 측정 파일을 올려주세요.");
+                return false;
+            }
+        } else {//직접 입력
+            if (CommonUtil.isEmpty(target['bradycardia_count']) == true) {
+                targetRef.current['bradycardia_count'].focus();
+                alert("서맥 횟수를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['tachycardia_count']) == true) {
+                targetRef.current['tachycardia_count'].focus();
+                alert("빈맥 횟수 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['max_heart_rate']) == true) {
+                targetRef.current['average_heart_rate'].focus();
+                alert("최대 심박수를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['min_heart_rate']) == true) {
+                targetRef.current['average_heart_rate'].focus();
+                alert("최소 심박수를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['average_heart_rate']) == true) {
+                targetRef.current['average_heart_rate'].focus();
+                alert("평균 심박수를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['abnormal_heart_rate']) == true) {
+                targetRef.current['abnormal_heart_rate'].focus();
+                alert("이상 심박수를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['max_rr']) == true) {
+                targetRef.current['max_rr'].focus();
+                alert("최대 R-R를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['min_rr']) == true) {
+                targetRef.current['min_rr'].focus();
+                alert("최소 R-R를 입력해 주세요.");
+                return false;
+            }
+            if (CommonUtil.isEmpty(target['average_rr']) == true) {
+                targetRef.current['average_rr'].focus();
+                alert("평균 R-R를 입력해 주세요.");
+                return false;
+            }
+        }
+        
+        return true;
+    }
+
+
+	//심전도 등록
+	const ecgInsert = () => {
+        if (ecgValidation() == false) {
+            return;
+        }
+
+        ecg['senior_id'] = targetSenior['senior_id'];
+        ecg['agent_id'] = defaultUserId;
+        setEcg({...ecg});
+
+		fetch("/hospital/ecgInsert.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(ecg),
+		}).then((response) => response.json()).then((data) => {
+			console.log("심전도 등록 결과(건수) : ", data);
+			if (data > 0) {
+                setEcgInit();
+                ecgSelectList();
+                //closeModal3();
+				alert("등록완료");
+				
+			} else {
+				alert("등록에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('ecgInsert() /hospital/ecgInsert.json error : ', error);
+		});
+    }
+
+    //심전도 수정
+	const ecgUpdate = () => {
+        if (ecgValidation() == false) {
+            return;
+        }
+
+        ecg['senior_id'] = targetSenior['senior_id'];
+        ecg['agent_id'] = defaultUserId;
+        setEcg({...ecg});
+
+		fetch("/hospital/ecgUpdate.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(ecg),
+		}).then((response) => response.json()).then((data) => {
+			console.log("심전도 수정 결과(건수) : ", data);
+			if (data > 0) {
+                setEcgInit();
+                ecg.commonFileList = [];
+                setEcg({...ecg});
+                ecgSelectList();
+                //closeModal3();
+				alert("수정완료");
+			} else {
+				alert("수정에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('ecgUpdate() /hospital/ecgUpdate.json error : ', error);
+		});
+    }
+
+    //심전도 삭제
+	const ecgDelete = () => {
+        if (confirm('심전도 판독 소견을 삭제하시겠습니까?') == false) {
+            return;
+        }
+
+		fetch("/hospital/ecgDelete.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(ecg),
+		}).then((response) => response.json()).then((data) => {
+			console.log("심전도 삭제 결과(건수) : ", data);
+			if (data > 0) {
+                setEcgInit();
+                ecg.commonFileList = [];
+                setEcg({...ecg});
+                ecgSelectList();
+                //closeModal3();
+				alert("삭제완료");
+			} else {
+				alert("삭제에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('ecgDelete() /hospital/ecgDelete.json error : ', error);
+		});
+    }
+
+    //초기화 취소
+    const setEcgInit = () => {
+        setEcg({...ecgInit});
+    }
+
+    //심전도 정보
+    const [ecgList, setEcgList] = React.useState({ecgList: [], ecgListCount:0, search: {currentPage: 1, perPage: 5}});
+    //심전도 목록 조회
+	const ecgSelectList = (currentPage) => {
+        ecgList.search.currentPage = CommonUtil.isEmpty(currentPage) ? 1 : currentPage;
+        ecgList.search['senior_id'] = targetSenior['senior_id'];
+        setEcgList({...ecgList});
+
+		fetch("/hospital/ecgSelectList.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(ecgList.search),
+		}).then((response) => response.json()).then((data) => {
+			console.log("심전도 목록 조회 결과(건수) : ", data);
+            data.search = ecgList.search;
+            setEcgList(data);
+		}).catch((error) => {
+			console.log('ecgSelectList() /hospital/ecgSelectList.json error : ', error);
+		});
+    }
+    /****************** 심전도 기록 (종료) ******************/
+
+
+
+    /****************** 혈압 (시작) ******************/
+    const bloodPressureInit = {
+        'senior_id': null,
+        'blood_pressure_record_idx': null,
+        'max_blood_pressure': null,
+        'min_blood_pressure': null,
+        'pulse_rate': null,
+        'medical_record_idx': null,
+        'blood_pressure_record_date': CommonUtil.getDate(),
+        'agent_id': defaultUserId
+    };
+    //혈압 정보
+    const [bloodPressure, setBloodPressure] = React.useState({...bloodPressureInit});
+    const bloodPressureRef = React.useRef({...bloodPressureInit});
+
+    //혈압 유효성 검사
+    const bloodPressureValidation = () => {
+        const target = bloodPressure;
+		const targetRef = bloodPressureRef;
+
+		if (CommonUtil.isEmpty(target['blood_pressure_record_date']) == true) {
+			targetRef.current['blood_pressure_record_date'].focus();
+			alert("진료 일자를 선택해 주세요.");
+			return false;
+		}
+		if (CommonUtil.isEmpty(target['max_blood_pressure']) == true) {
+			targetRef.current['max_blood_pressure'].focus();
+			alert("최고 혈압을 입력해 주세요.");
+			return false;
+		}
+        if (CommonUtil.isEmpty(target['min_blood_pressure']) == true) {
+			targetRef.current['min_blood_pressure'].focus();
+			alert("최저 혈압을 입력해 주세요.");
+			return false;
+		}
+        if (CommonUtil.isEmpty(target['pulse_rate']) == true) {
+			targetRef.current['pulse_rate'].focus();
+			alert("맥박수를 입력해 주세요.");
+			return false;
+		}
+
+        return true;
+    }
+
+
+	//혈압 등록
+	const bloodPressureInsert = () => {
+        if (bloodPressureValidation() == false) {
+            return;
+        }
+
+        bloodPressure['senior_id'] = targetSenior['senior_id'];
+        bloodPressure['agent_id'] = defaultUserId;
+        setBloodPressure({...bloodPressure});
+
+		fetch("/hospital/bloodPressureInsert.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(bloodPressure),
+		}).then((response) => response.json()).then((data) => {
+			console.log("혈압 등록 결과(건수) : ", data);
+			if (data > 0) {
+                setBloodPressureInit();
+                bloodPressureSelectList();
+                //closeModal2();
+				alert("등록완료");
+				
+			} else {
+				alert("등록에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('bloodPressureInsert() /hospital/bloodPressureInsert.json error : ', error);
+		});
+    }
+
+    //혈압 수정
+	const bloodPressureUpdate = () => {
+        if (bloodPressureValidation() == false) {
+            return;
+        }
+
+        bloodPressure['senior_id'] = targetSenior['senior_id'];
+        bloodPressure['agent_id'] = defaultUserId;
+        setBloodPressure({...bloodPressure});
+
+		fetch("/hospital/bloodPressureUpdate.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(bloodPressure),
+		}).then((response) => response.json()).then((data) => {
+			console.log("혈압 수정 결과(건수) : ", data);
+			if (data > 0) {
+                setBloodPressureInit();
+                bloodPressureSelectList();
+                //closeModal2();
+				alert("수정완료");
+			} else {
+				alert("수정에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('bloodPressureUpdate() /hospital/bloodPressureUpdate.json error : ', error);
+		});
+    }
+
+    //혈압 삭제
+	const bloodPressureDelete = () => {
+        if (confirm('혈압 측정 정보를 삭제하시겠습니까?') == false) {
+            return;
+        }
+
+		fetch("/hospital/bloodPressureDelete.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(bloodPressure),
+		}).then((response) => response.json()).then((data) => {
+			console.log("혈압 삭제 결과(건수) : ", data);
+			if (data > 0) {
+                setBloodPressureInit();
+                bloodPressureSelectList();
+                //closeModal2();
+				alert("삭제완료");
+			} else {
+				alert("삭제에 실패하였습니다. 관리자에게 문의바랍니다.");
+			}
+		}).catch((error) => {
+			console.log('bloodPressureDelete() /hospital/bloodPressureDelete.json error : ', error);
+		});
+    }
+
+    //초기화 취소
+    const setBloodPressureInit = () => {
+        setBloodPressure({...bloodPressureInit});
+    }
+
+    //혈압 정보
+    const [bloodPressureList, setBloodPressureList] = React.useState({bloodPressureList: [], bloodPressureListCount:0, search: {currentPage: 1, perPage: 5}});
+    //혈압 목록 조회
+	const bloodPressureSelectList = (currentPage) => {
+        bloodPressureList.search.currentPage = CommonUtil.isEmpty(currentPage) ? 1 : currentPage;
+        bloodPressureList.search['senior_id'] = targetSenior['senior_id'];
+        setBloodPressureList({...bloodPressureList});
+
+		fetch("/hospital/bloodPressureSelectList.json", {
+			method: "POST",
+			headers: {
+				'Content-Type': 'application/json; charset=UTF-8'
+			},
+			body: JSON.stringify(bloodPressureList.search),
+		}).then((response) => response.json()).then((data) => {
+			console.log("혈압 목록 조회 결과(건수) : ", data);
+            data.search = bloodPressureList.search;
+            setBloodPressureList(data);
+		}).catch((error) => {
+			console.log('bloodPressureSelectList() /hospital/bloodPressureSelectList.json error : ', error);
+		});
+    }
+    /****************** 혈압 (종료) ******************/
 
     React.useEffect(() => {
         searching();
@@ -519,9 +935,6 @@
 							<th>생년월일</th>
 							<th>성별</th>
 							<th>연락처</th>
-                            <th>최고혈압</th>
-							<th>최저혈압</th>
-							<th>맥박수</th>
 							<th>혈압관리</th>
 						</tr>
 					</thead>
@@ -534,9 +947,6 @@
 							<td data-label="생년월일">{item['user_birth']}</td>
 							<td data-label="성별">{item['user_gender']}</td>
 							<td data-label="연락처">{item['user_phonenumber']}</td>
-                            <td>120</td>
-							<td>80</td>
-							<td>76</td>
 							<td data-label="혈압관리">
                                 <button className="btn-small gray-btn" onClick={() => openModal4(item)}>혈압관리</button>
 							</td>
@@ -544,7 +954,7 @@
 						)})}
 						{CommonUtil.isEmpty(senior.seniorList) ?
 						<tr>
-							<td colSpan={10}>조회된 데이터가 없습니다</td>
+							<td colSpan={7}>조회된 데이터가 없습니다</td>
 						</tr>  
 						: null}
 					</tbody>
@@ -949,6 +1359,7 @@
                             : <>
                                 <button className="btn-small gray-btn" onClick={setHospitalMedicalRecordInit}>수정취소</button>
                                 <button className="btn-small red-btn" onClick={hospitalMedicalRecordUpdate}>수정</button>
+                                <button className="btn-small red-btn" onClick={hospitalMedicalRecordDelete}>삭제</button>
                             </>
                         }
                     </div>
@@ -996,53 +1407,178 @@
                 <div className="board-wrap">
                     <table className="margin-bottom2 senior-insert ">
                         <tr>
-                            <th>대상자명</th>
-                            <td className="flex-start">
-                                <input type="text" placeholder="자동입력"/>
-                            </td>              
-                        </tr>
-                        <tr>
                             <th>측정 일자</th>
-                            <td className="flex-start">
-                                <input type="text" placeholder="자동입력"/>
+                            <td colSpan={5}>
+                                <input type="date" value={ecg['ecg_reading_date']}
+                                    onChange={(e) => {
+                                        ecg['ecg_reading_date'] = e.target.value;
+                                        setEcg({...ecg});
+                                    }}
+                                    ref={el => ecgRef.current['ecg_reading_date'] = el}
+                                />
                             </td>              
                         </tr>
                         <tr>
+                            <th>판독 소견 종류</th>
+                            <td colSpan={5}>
+                                <input type="text" value={ecg['ecg_finding_type']}
+                                    onChange={(e) => {
+                                        ecg['ecg_finding_type'] = e.target.value;
+                                        setEcg({...ecg});
+                                    }}
+                                    ref={el => ecgRef.current['ecg_finding_type'] = el}
+                                />
+                            </td>              
+                        </tr>
+                        <tr>
+                            <th>판독 소견 내용</th>
+                            <td colSpan={5}>
+                                <textarea className="medicine" cols="30" rows="2"
+                                    value={ecg['ecg_finding_content']}
+                                    onChange={(e) => {
+                                        ecg['ecg_finding_content'] = e.target.value;
+                                        setEcg({...ecg});
+                                    }}
+                                    ref={el => ecgRef.current['ecg_finding_content'] = el}
+                                ></textarea>
+                            </td>              
+                        </tr>
+                        {ecg.isEdit == false
+                        ? <tr>
                             <th>측정 파일</th>
-                            <td className="flex-start">
-                                <input type="file"></input>
+                            <td colSpan={5}>
+                                <CommonFile commonFileList={ecg['commonFileList']} multiple={false} accept={'.dat, .ecg'}/>
                             </td>              
                         </tr>
-                        <tr>
-                            <th>등록자</th>
-                            <td className="flex-start">
-                                <input type="text" placeholder="자동입력"/>
-                            </td>              
-                        </tr>       
+                        : <>
+                            <tr>
+                                <th>서맥 횟수</th>
+                                <td>
+                                    <input type="number" value={ecg['bradycardia_count']}
+                                        onChange={(e) => {
+                                            ecg['bradycardia_count'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['bradycardia_count'] = el}
+                                    />
+                                </td>
+                                <th>빈맥 횟수</th>
+                                <td>
+                                    <input type="number" value={ecg['tachycardia_count']}
+                                        onChange={(e) => {
+                                            ecg['tachycardia_count'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['tachycardia_count'] = el}
+                                    />
+                                </td>
+                                <th>이상 심박수</th>
+                                <td>
+                                    <input type="number" value={ecg['abnormal_heart_rate']}
+                                        onChange={(e) => {
+                                            ecg['abnormal_heart_rate'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['abnormal_heart_rate'] = el}
+                                    />
+                                </td>
+                            </tr>
+                            <tr>
+                                <th>최대 심박수</th>
+                                <td>
+                                    <input type="number" value={ecg['max_heart_rate']}
+                                        onChange={(e) => {
+                                            ecg['max_heart_rate'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['max_heart_rate'] = el}
+                                    />
+                                </td>
+                                <th>최소 심박수</th>
+                                <td>
+                                    <input type="number" value={ecg['min_heart_rate']}
+                                        onChange={(e) => {
+                                            ecg['min_heart_rate'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['min_heart_rate'] = el}
+                                    />
+                                </td>
+                                <th>평균 심박수</th>
+                                <td>
+                                    <input type="number" value={ecg['average_heart_rate']}
+                                        onChange={(e) => {
+                                            ecg['average_heart_rate'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['average_heart_rate'] = el}
+                                    />
+                                </td>
+                            </tr>
+                            <tr>
+                                <th>최대 R-R</th>
+                                <td>
+                                    <input type="number" value={ecg['max_rr']}
+                                        onChange={(e) => {
+                                            ecg['max_rr'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['max_rr'] = el}
+                                    />
+                                </td>
+                                <th>최소 R-R</th>
+                                <td>
+                                    <input type="number" value={ecg['min_rr']}
+                                        onChange={(e) => {
+                                            ecg['min_rr'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['min_rr'] = el}
+                                    />
+                                </td>
+                                <th>평균 R-R</th>
+                                <td>
+                                    <input type="number" value={ecg['average_rr']}
+                                        onChange={(e) => {
+                                            ecg['average_rr'] = e.target.value;
+                                            setEcg({...ecg});
+                                        }}
+                                        ref={el => ecgRef.current['average_rr'] = el}
+                                    />
+                                </td>
+                            </tr>
+                        </>}
                     </table>
                     <div className="btn-wrap flex-center margin-bottom5">
-                        <button className="btn-small red-btn" onClick={closeModal3}>저장</button>
+                        {CommonUtil.isEmpty(ecg['ecg_record_idx'])
+                            ? <button className="btn-small red-btn" onClick={ecgInsert}>등록</button>
+                            : <>
+                                <button className="btn-small gray-btn" onClick={setEcgInit}>수정취소</button>
+                                <button className="btn-small red-btn" onClick={ecgUpdate}>수정</button>
+                                <button className="btn-small red-btn" onClick={ecgDelete}>삭제</button>
+                            </>
+                        }
                     </div>
                     <div>
                         <table className="caregiver-user senior-table">
                             <thead>
                                 <tr>
                                     <th>No</th>
-                                    <th>측정 일자</th>
                                     <th>소견 작성 일자</th>
+                                    <th>판독 소견 종류</th>
                                     <th>소견 작성자</th>
                                 </tr>
                             </thead>
                             <tbody>
-                                {senior.seniorList.map((item, idx) => { return (
-                                <tr key={idx}>
-                                    <td data-label="No">{senior.seniorListCount - idx - (senior.search.currentPage - 1) * senior.search.perPage}</td>
-                                    <td data-label="소속기관명">{item['agency_name']}</td>
-                                    <td data-label="이름">{item['user_name']}</td>
-                                    <td data-label="생년월일">{item['user_birth']}</td>
+                                {ecgList.ecgList.map((item, idx) => { return (
+                                <tr key={idx} onClick={() => {item.isEdit = false; setEcg(item);}}>
+                                    <td data-label="No">{ecgList.ecgListCount - idx - (ecgList.search.currentPage - 1) * ecgList.search.perPage}</td>
+                                    <td data-label="소견 작성 일자">{item['ecg_reading_date']}</td>
+                                    <td data-label="판독 소견 종류">{item['ecg_finding_type']}</td>
+                                    <td data-label="소견 작성자">{item['insert_user_name']}</td>
                                 </tr>
                                 )})}
-                                {CommonUtil.isEmpty(senior.seniorList) ?
+                                {CommonUtil.isEmpty(ecgList.ecgList) ?
                                 <tr>
                                     <td colSpan={4}>조회된 데이터가 없습니다</td>
                                 </tr>  
@@ -1050,11 +1586,11 @@
                             </tbody>
                         </table>
                         <Pagination
-                            currentPage={senior.search.currentPage}
-                            perPage={senior.search.perPage}
-                            totalCount={senior.seniorListCount}
+                            currentPage={ecgList.search.currentPage}
+                            perPage={ecgList.search.perPage}
+                            totalCount={ecgList.ecgListCount}
                             maxRange={5}
-                            click={seniorSelectList}
+                            click={ecgSelectList}
                         />
                     </div>
                 </div>
@@ -1065,45 +1601,70 @@
                 <div className="board-wrap">
                     <table className="margin-bottom2 senior-insert ">
                         <tr>
-                            <th>대상자명</th>
-                            <td className="flex-start">
-                                <input type="text" placeholder="자동입력"/>
-                            </td>              
-                        </tr>
-                        <tr>
                             <th>측정일자</th>
-                            <td className="flex-start">
-                                <input type="text" placeholder="자동입력"/>
+                            <td>
+                                <input type="date" value={bloodPressure['blood_pressure_record_date']}
+                                    onChange={(e) => {
+                                        bloodPressure['blood_pressure_record_date'] = e.target.value;
+                                        setBloodPressure({...bloodPressure});
+                                    }}
+                                    ref={el => bloodPressureRef.current['blood_pressure_record_date'] = el}
+                                />
                             </td>              
                         </tr>
                         <tr>
                             <th>최고혈압</th>
-                            <td className="flex-start">
-                                <input type="text" />
-                            </td>              
+                            <td>
+                                <input type="number" value={bloodPressure['max_blood_pressure']}
+                                    onChange={(e) => {
+                                        bloodPressure['max_blood_pressure'] = e.target.value;
+                                        setBloodPressure({...bloodPressure});
+                                    }}
+                                    ref={el => bloodPressureRef.current['max_blood_pressure'] = el}
+                                />
+                            </td>         
                         </tr>
                         <tr>
                             <th>최저혈압</th>
-                            <td className="flex-start">
-                                <input type="text" />
-                            </td>              
+                            <td>
+                                <input type="number" value={bloodPressure['min_blood_pressure']}
+                                    onChange={(e) => {
+                                        bloodPressure['min_blood_pressure'] = e.target.value;
+                                        setBloodPressure({...bloodPressure});
+                                    }}
+                                    ref={el => bloodPressureRef.current['min_blood_pressure'] = el}
+                                />
+                            </td>
                         </tr>
                         <tr>
                             <th>맥박수</th>
-                            <td className="flex-start">
-                                <input type="text" />
-                            </td>              
+                            <td>
+                                <input type="number" value={bloodPressure['pulse_rate']}
+                                    onChange={(e) => {
+                                        bloodPressure['pulse_rate'] = e.target.value;
+                                        setBloodPressure({...bloodPressure});
+                                    }}
+                                    ref={el => bloodPressureRef.current['pulse_rate'] = el}
+                                />
+                            </td>
                         </tr>      
                     </table>
                     <div className="btn-wrap flex-center margin-bottom5">
-                        <button className="btn-small red-btn" onClick={closeModal4}>저장</button>
+                        {CommonUtil.isEmpty(bloodPressure['blood_pressure_record_idx'])
+                            ? <button className="btn-small red-btn" onClick={bloodPressureInsert}>등록</button>
+                            : <>
+                                <button className="btn-small gray-btn" onClick={setBloodPressureInit}>수정취소</button>
+                                <button className="btn-small red-btn" onClick={bloodPressureUpdate}>수정</button>
+                                <button className="btn-small red-btn" onClick={bloodPressureDelete}>삭제</button>
+                            </>
+                        }
                     </div>
                     <div>
                         <table className="caregiver-user senior-insert  senior-table">
                             <thead>
                                 <tr>
                                     <th>No</th>
-                                    <th>측정 일자</th>
+                                    <th>기록 작성일</th>
                                     <th>최고 혈압</th>
                                     <th>최저 혈압</th>
                                     <th>맥박수</th>
@@ -1111,28 +1672,29 @@
                                 </tr>
                             </thead>
                             <tbody>
-                                {senior.seniorList.map((item, idx) => { return (
-                                <tr key={idx}>
-                                    <td data-label="No">{senior.seniorListCount - idx - (senior.search.currentPage - 1) * senior.search.perPage}</td>
-                                    <td data-label="소속기관명">{item['agency_name']}</td>
-                                    <td data-label="이름">{item['user_name']}</td>
-                                    <td data-label="생년월일">{item['user_birth']}</td>
-                                    <td data-label="성별">{item['user_gender']}</td>
+                                {bloodPressureList.bloodPressureList.map((item, idx) => { return (
+                                <tr key={idx} onClick={() => {setBloodPressure(item);}}>
+                                    <td data-label="No">{bloodPressureList.bloodPressureListCount - idx - (bloodPressureList.search.currentPage - 1) * bloodPressureList.search.perPage}</td>
+                                    <td data-label="기록 작성일">{item['blood_pressure_record_date']}</td>
+                                    <td data-label="최고 혈압">{item['max_blood_pressure']}</td>
+                                    <td data-label="최저 혈압">{item['min_blood_pressure']}</td>
+                                    <td data-label="맥박수">{item['pulse_rate']}</td>
+                                    <td data-label="기록 작성자">{item['insert_user_name']}</td>
                                 </tr>
                                 )})}
-                                {CommonUtil.isEmpty(senior.seniorList) ?
+                                {CommonUtil.isEmpty(bloodPressureList.bloodPressureList) ?
                                 <tr>
-                                    <td colSpan={5}>조회된 데이터가 없습니다</td>
+                                    <td colSpan={6}>조회된 데이터가 없습니다</td>
                                 </tr>  
                                 : null}
                             </tbody>
                         </table>
                         <Pagination
-                            currentPage={senior.search.currentPage}
-                            perPage={senior.search.perPage}
-                            totalCount={senior.seniorListCount}
+                            currentPage={bloodPressureList.search.currentPage}
+                            perPage={bloodPressureList.search.perPage}
+                            totalCount={bloodPressureList.bloodPressureListCount}
                             maxRange={5}
-                            click={seniorSelectList}
+                            click={bloodPressureSelectList}
                         />
                     </div>
                 </div>
server/modules/web/Server.js
--- server/modules/web/Server.js
+++ server/modules/web/Server.js
@@ -109,6 +109,24 @@
         return `${request.params['0']}.json`;
     }
 }));
+
+
+/**
+ * @author : 최정우
+ * @since : 2023.03.18
+ * @dscription : REST API 서버에 File 요청 보내기(Proxy)
+ * express-http-proxy 라이브러리에서 Request Body 구문해석을 못하도록 강제로 막음 (parseReqBody: false)
+ * => 이유 : 구문해석 후, express-http-proxy가 Body의 내용을 변형시키는 듯 함.
+ *           그래서, API 서버에 제대로된 Body를 전달하지 못 함
+ */
+webServer.use('*.file', expressProxy(API_SERVER_HOST, {
+    parseReqBody: false,
+    proxyReqPathResolver: function (request) {
+        console.log('request : ', request.url, request.params[0]);
+        return `${request.params['0']}.file`;
+    }
+}));
+
     
 /**
  * @author : 최정우
Add a comment
List