feat: PMR 처방 비교 기능
- 비교 모드 토글 체크박스 추가 - 상태 분류: 🆕추가 / 🔄변경 / ❌중단 / ✓동일 - 변경된 값: '1정 → 2정' 형태로 표시 - 색상 코딩: 녹색(추가), 노랑(변경), 빨강(중단) - 이전 처방 < > 네비게이션 시 자동 비교
This commit is contained in:
parent
6192f635ca
commit
d8aa073564
@ -311,6 +311,66 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 처방 비교 상태 */
|
||||||
|
.med-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.status-added { background: #dcfce7; color: #166534; }
|
||||||
|
.status-removed { background: #fee2e2; color: #991b1b; }
|
||||||
|
.status-changed { background: #fef3c7; color: #92400e; }
|
||||||
|
.status-same { background: #f1f5f9; color: #64748b; }
|
||||||
|
|
||||||
|
tr.row-added { background: #f0fdf4 !important; }
|
||||||
|
tr.row-removed { background: #fef2f2 !important; opacity: 0.7; }
|
||||||
|
tr.row-changed { background: #fffbeb !important; }
|
||||||
|
|
||||||
|
.change-arrow {
|
||||||
|
color: #94a3b8;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
.change-from {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
.change-to {
|
||||||
|
background: #fbbf24;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 비교 모드 토글 */
|
||||||
|
.compare-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 15px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.compare-toggle input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.compare-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.compare-legend span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -362,6 +422,16 @@
|
|||||||
<div class="text">환자를 선택하세요</div>
|
<div class="text">환자를 선택하세요</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="compare-toggle" id="compareToggle" style="display:none;">
|
||||||
|
<input type="checkbox" id="compareMode" onchange="toggleCompareMode()">
|
||||||
|
<label for="compareMode">이전 처방과 비교</label>
|
||||||
|
<div class="compare-legend" id="compareLegend" style="display:none;">
|
||||||
|
<span><span class="med-status status-added">🆕 추가</span></span>
|
||||||
|
<span><span class="med-status status-changed">🔄 변경</span></span>
|
||||||
|
<span><span class="med-status status-removed">❌ 중단</span></span>
|
||||||
|
<span><span class="med-status status-same">✓ 동일</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="action-bar" id="actionBar" style="display:none;">
|
<div class="action-bar" id="actionBar" style="display:none;">
|
||||||
<button class="btn btn-secondary" onclick="selectAll()">전체 선택</button>
|
<button class="btn btn-secondary" onclick="selectAll()">전체 선택</button>
|
||||||
<button class="btn btn-secondary" onclick="previewLabels()" style="background:#3b82f6;color:#fff;">👁️ 미리보기</button>
|
<button class="btn btn-secondary" onclick="previewLabels()" style="background:#3b82f6;color:#fff;">👁️ 미리보기</button>
|
||||||
@ -401,8 +471,10 @@
|
|||||||
<script>
|
<script>
|
||||||
let currentPrescriptionId = null;
|
let currentPrescriptionId = null;
|
||||||
let currentPatientCode = null;
|
let currentPatientCode = null;
|
||||||
|
let currentMedications = [];
|
||||||
let historyData = [];
|
let historyData = [];
|
||||||
let historyIndex = 0;
|
let historyIndex = 0;
|
||||||
|
let compareMode = false;
|
||||||
|
|
||||||
// HTML 이스케이프
|
// HTML 이스케이프
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
@ -547,6 +619,9 @@
|
|||||||
|
|
||||||
document.getElementById('actionBar').style.display = 'flex';
|
document.getElementById('actionBar').style.display = 'flex';
|
||||||
|
|
||||||
|
// 현재 약품 저장
|
||||||
|
currentMedications = data.medications;
|
||||||
|
|
||||||
// 이전 처방 로드
|
// 이전 처방 로드
|
||||||
currentPatientCode = data.patient.code;
|
currentPatientCode = data.patient.code;
|
||||||
if (currentPatientCode) {
|
if (currentPatientCode) {
|
||||||
@ -571,9 +646,11 @@
|
|||||||
historyData = data.history;
|
historyData = data.history;
|
||||||
historyIndex = 0;
|
historyIndex = 0;
|
||||||
section.style.display = 'block';
|
section.style.display = 'block';
|
||||||
|
document.getElementById('compareToggle').style.display = 'flex';
|
||||||
renderHistory();
|
renderHistory();
|
||||||
} else {
|
} else {
|
||||||
section.style.display = 'none';
|
section.style.display = 'none';
|
||||||
|
document.getElementById('compareToggle').style.display = 'none';
|
||||||
historyData = [];
|
historyData = [];
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -642,6 +719,7 @@
|
|||||||
if (historyIndex > 0) {
|
if (historyIndex > 0) {
|
||||||
historyIndex--;
|
historyIndex--;
|
||||||
renderHistory();
|
renderHistory();
|
||||||
|
if (compareMode) applyCompareMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,14 +727,186 @@
|
|||||||
if (historyIndex < historyData.length - 1) {
|
if (historyIndex < historyData.length - 1) {
|
||||||
historyIndex++;
|
historyIndex++;
|
||||||
renderHistory();
|
renderHistory();
|
||||||
|
if (compareMode) applyCompareMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비교 모드 토글
|
||||||
|
function toggleCompareMode() {
|
||||||
|
compareMode = document.getElementById('compareMode').checked;
|
||||||
|
document.getElementById('compareLegend').style.display = compareMode ? 'flex' : 'none';
|
||||||
|
|
||||||
|
if (compareMode) {
|
||||||
|
applyCompareMode();
|
||||||
|
} else {
|
||||||
|
// 비교 모드 해제 - 테이블 다시 렌더링
|
||||||
|
rerenderMedicationTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 처방 비교 로직
|
||||||
|
function comparePrescriptions(current, previous) {
|
||||||
|
const result = [];
|
||||||
|
const prevMap = new Map(previous.map(m => [m.medication_code, m]));
|
||||||
|
const currCodes = new Set(current.map(m => m.medication_code));
|
||||||
|
|
||||||
|
// 현재 처방 약품 처리
|
||||||
|
for (const curr of current) {
|
||||||
|
const prev = prevMap.get(curr.medication_code);
|
||||||
|
if (!prev) {
|
||||||
|
// 추가된 약
|
||||||
|
result.push({ ...curr, status: 'added' });
|
||||||
|
} else {
|
||||||
|
// 비교
|
||||||
|
const changes = [];
|
||||||
|
if (parseFloat(curr.dosage) !== parseFloat(prev.dosage)) {
|
||||||
|
changes.push({ field: 'dosage', from: prev.dosage, to: curr.dosage });
|
||||||
|
}
|
||||||
|
if (parseInt(curr.frequency) !== parseInt(prev.frequency)) {
|
||||||
|
changes.push({ field: 'frequency', from: prev.frequency, to: curr.frequency });
|
||||||
|
}
|
||||||
|
if (parseInt(curr.duration) !== parseInt(prev.duration)) {
|
||||||
|
changes.push({ field: 'duration', from: prev.duration, to: curr.duration });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.length > 0) {
|
||||||
|
result.push({ ...curr, status: 'changed', changes });
|
||||||
|
} else {
|
||||||
|
result.push({ ...curr, status: 'same' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 중단된 약 (이전에 있었는데 현재 없음)
|
||||||
|
for (const prev of previous) {
|
||||||
|
if (!currCodes.has(prev.medication_code)) {
|
||||||
|
result.push({ ...prev, status: 'removed' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 비교 모드 적용
|
||||||
|
function applyCompareMode() {
|
||||||
|
if (historyData.length === 0 || !currentMedications.length) return;
|
||||||
|
|
||||||
|
const prevMeds = historyData[historyIndex].medications || [];
|
||||||
|
const compared = comparePrescriptions(currentMedications, prevMeds);
|
||||||
|
|
||||||
|
// 테이블 다시 렌더링
|
||||||
|
renderComparedTable(compared);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 비교 결과 테이블 렌더링
|
||||||
|
function renderComparedTable(compared) {
|
||||||
|
const medList = document.getElementById('medicationList');
|
||||||
|
|
||||||
|
// 상태별 정렬: 추가 > 변경 > 동일 > 중단
|
||||||
|
const order = { added: 0, changed: 1, same: 2, removed: 3 };
|
||||||
|
compared.sort((a, b) => order[a.status] - order[b.status]);
|
||||||
|
|
||||||
|
medList.innerHTML = `
|
||||||
|
<table class="med-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:40px;"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th>
|
||||||
|
<th>약품명</th>
|
||||||
|
<th>상태</th>
|
||||||
|
<th>용량</th>
|
||||||
|
<th>횟수</th>
|
||||||
|
<th>일수</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${compared.map(m => {
|
||||||
|
const rowClass = 'row-' + m.status;
|
||||||
|
const statusLabel = {
|
||||||
|
added: '<span class="med-status status-added">🆕 추가</span>',
|
||||||
|
removed: '<span class="med-status status-removed">❌ 중단</span>',
|
||||||
|
changed: '<span class="med-status status-changed">🔄 변경</span>',
|
||||||
|
same: '<span class="med-status status-same">✓ 동일</span>'
|
||||||
|
}[m.status];
|
||||||
|
|
||||||
|
// 변경된 필드 찾기
|
||||||
|
const getChangeValue = (field, value) => {
|
||||||
|
if (m.status !== 'changed' || !m.changes) return value || '-';
|
||||||
|
const change = m.changes.find(c => c.field === field);
|
||||||
|
if (change) {
|
||||||
|
return `<span class="change-from">${change.from || '-'}</span>` +
|
||||||
|
`<span class="change-arrow">→</span>` +
|
||||||
|
`<span class="change-to">${change.to || '-'}</span>`;
|
||||||
|
}
|
||||||
|
return value || '-';
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabled = m.status === 'removed' ? 'disabled' : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr class="${rowClass}" data-add-info="${escapeHtml(m.add_info || '')}">
|
||||||
|
<td><input type="checkbox" class="med-check" data-code="${m.medication_code}" ${disabled}></td>
|
||||||
|
<td>
|
||||||
|
<div class="med-name">${m.med_name || m.medication_code}</div>
|
||||||
|
<div class="med-code">${m.medication_code}</div>
|
||||||
|
${m.add_info ? `<div style="font-size:0.75rem;color:#6b7280;">${escapeHtml(m.add_info)}</div>` : ''}
|
||||||
|
</td>
|
||||||
|
<td>${statusLabel}</td>
|
||||||
|
<td>${m.status === 'changed' ? getChangeValue('dosage', m.dosage) : (m.dosage || '-')}</td>
|
||||||
|
<td>${m.status === 'changed' ? getChangeValue('frequency', m.frequency) : (m.frequency || '-')}회</td>
|
||||||
|
<td>${m.status === 'changed' ? getChangeValue('duration', m.duration) : (m.duration || '-')}일</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 일반 테이블로 복원
|
||||||
|
function rerenderMedicationTable() {
|
||||||
|
if (!currentMedications.length) return;
|
||||||
|
|
||||||
|
const medList = document.getElementById('medicationList');
|
||||||
|
medList.innerHTML = `
|
||||||
|
<table class="med-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:40px;"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th>
|
||||||
|
<th>약품명</th>
|
||||||
|
<th>제형</th>
|
||||||
|
<th>용량</th>
|
||||||
|
<th>횟수</th>
|
||||||
|
<th>일수</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${currentMedications.map(m => `
|
||||||
|
<tr data-add-info="${escapeHtml(m.add_info || '')}">
|
||||||
|
<td><input type="checkbox" class="med-check" data-code="${m.medication_code}"></td>
|
||||||
|
<td>
|
||||||
|
<div class="med-name">${m.med_name || m.medication_code}</div>
|
||||||
|
<div class="med-code">${m.medication_code}</div>
|
||||||
|
${m.add_info ? `<div style="font-size:0.75rem;color:#6b7280;">${escapeHtml(m.add_info)}</div>` : ''}
|
||||||
|
</td>
|
||||||
|
<td>${m.formulation ? `<span class="med-form">${m.formulation}</span>` : '-'}</td>
|
||||||
|
<td><span class="med-dosage">${m.dosage || '-'}</span></td>
|
||||||
|
<td>${m.frequency || '-'}회</td>
|
||||||
|
<td>${m.duration || '-'}일</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// 상세 초기화
|
// 상세 초기화
|
||||||
function clearDetail() {
|
function clearDetail() {
|
||||||
document.getElementById('detailHeader').style.display = 'none';
|
document.getElementById('detailHeader').style.display = 'none';
|
||||||
document.getElementById('actionBar').style.display = 'none';
|
document.getElementById('actionBar').style.display = 'none';
|
||||||
document.getElementById('historySection').style.display = 'none';
|
document.getElementById('historySection').style.display = 'none';
|
||||||
|
document.getElementById('compareToggle').style.display = 'none';
|
||||||
|
document.getElementById('compareMode').checked = false;
|
||||||
|
document.getElementById('compareLegend').style.display = 'none';
|
||||||
document.getElementById('medicationList').innerHTML = `
|
document.getElementById('medicationList').innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<div class="icon">👈</div>
|
<div class="icon">👈</div>
|
||||||
@ -665,8 +915,10 @@
|
|||||||
`;
|
`;
|
||||||
currentPrescriptionId = null;
|
currentPrescriptionId = null;
|
||||||
currentPatientCode = null;
|
currentPatientCode = null;
|
||||||
|
currentMedications = [];
|
||||||
historyData = [];
|
historyData = [];
|
||||||
historyIndex = 0;
|
historyIndex = 0;
|
||||||
|
compareMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 전체 선택 토글
|
// 전체 선택 토글
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user