feat: PMR 처방 비교 기능

- 비교 모드 토글 체크박스 추가
- 상태 분류: 🆕추가 / 🔄변경 / 중단 / ✓동일
- 변경된 값: '1정 → 2정' 형태로 표시
- 색상 코딩: 녹색(추가), 노랑(변경), 빨강(중단)
- 이전 처방 < > 네비게이션 시 자동 비교
This commit is contained in:
thug0bin 2026-03-04 23:40:09 +09:00
parent 6192f635ca
commit d8aa073564

View File

@ -311,6 +311,66 @@
text-align: center;
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>
</head>
<body>
@ -362,6 +422,16 @@
<div class="text">환자를 선택하세요</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;">
<button class="btn btn-secondary" onclick="selectAll()">전체 선택</button>
<button class="btn btn-secondary" onclick="previewLabels()" style="background:#3b82f6;color:#fff;">👁️ 미리보기</button>
@ -401,8 +471,10 @@
<script>
let currentPrescriptionId = null;
let currentPatientCode = null;
let currentMedications = [];
let historyData = [];
let historyIndex = 0;
let compareMode = false;
// HTML 이스케이프
function escapeHtml(text) {
@ -547,6 +619,9 @@
document.getElementById('actionBar').style.display = 'flex';
// 현재 약품 저장
currentMedications = data.medications;
// 이전 처방 로드
currentPatientCode = data.patient.code;
if (currentPatientCode) {
@ -571,9 +646,11 @@
historyData = data.history;
historyIndex = 0;
section.style.display = 'block';
document.getElementById('compareToggle').style.display = 'flex';
renderHistory();
} else {
section.style.display = 'none';
document.getElementById('compareToggle').style.display = 'none';
historyData = [];
}
} catch (err) {
@ -642,6 +719,7 @@
if (historyIndex > 0) {
historyIndex--;
renderHistory();
if (compareMode) applyCompareMode();
}
}
@ -649,14 +727,186 @@
if (historyIndex < historyData.length - 1) {
historyIndex++;
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() {
document.getElementById('detailHeader').style.display = 'none';
document.getElementById('actionBar').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 = `
<div class="empty-state">
<div class="icon">👈</div>
@ -665,8 +915,10 @@
`;
currentPrescriptionId = null;
currentPatientCode = null;
currentMedications = [];
historyData = [];
historyIndex = 0;
compareMode = false;
}
// 전체 선택 토글