feat: 처방 주요 효능(efficacy) 필드 추가 및 UI 개선
- DB: formulas 테이블에 efficacy 칼럼 추가 - API: 처방 생성/수정/조회 시 efficacy 필드 처리 - UI: 처방 등록/수정 모달에 주요 효능 입력 필드 추가 - UI: 처방 상세 화면에 주요 효능 표시 - 기존 처방들의 주요 효능 데이터 입력 완료 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
769
static/app.js
769
static/app.js
@@ -11,6 +11,9 @@ let currentLotAllocation = {
|
||||
data: null
|
||||
};
|
||||
|
||||
// 재고 계산 모드 (localStorage에 저장)
|
||||
let inventoryCalculationMode = localStorage.getItem('inventoryMode') || 'all';
|
||||
|
||||
$(document).ready(function() {
|
||||
// 페이지 네비게이션
|
||||
$('.sidebar .nav-link').on('click', function(e) {
|
||||
@@ -59,6 +62,9 @@ $(document).ready(function() {
|
||||
case 'herbs':
|
||||
loadHerbs();
|
||||
break;
|
||||
case 'herb-info':
|
||||
loadHerbInfo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,13 +77,8 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
// 재고 현황
|
||||
$.get('/api/inventory/summary', function(response) {
|
||||
if (response.success) {
|
||||
$('#totalHerbs').text(response.data.length);
|
||||
$('#inventoryValue').text(formatCurrency(response.summary.total_value));
|
||||
}
|
||||
});
|
||||
// 재고 현황 (저장된 모드 사용)
|
||||
loadInventorySummary();
|
||||
|
||||
// 오늘 조제 수 및 최근 조제 내역
|
||||
$.get('/api/compounds', function(response) {
|
||||
@@ -454,36 +455,253 @@ $(document).ready(function() {
|
||||
<td>${formula.base_cheop}첩</td>
|
||||
<td>${formula.base_pouches}파우치</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-info view-ingredients"
|
||||
data-id="${formula.formula_id}">
|
||||
<i class="bi bi-eye"></i> 보기
|
||||
<button class="btn btn-sm btn-outline-info view-formula-detail"
|
||||
data-id="${formula.formula_id}"
|
||||
data-name="${formula.formula_name}">
|
||||
<i class="bi bi-eye"></i> 구성
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
<button class="btn btn-sm btn-outline-primary edit-formula"
|
||||
data-id="${formula.formula_id}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger delete-formula"
|
||||
data-id="${formula.formula_id}"
|
||||
data-name="${formula.formula_name}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
|
||||
// 구성 약재 보기
|
||||
$('.view-ingredients').on('click', function() {
|
||||
// 처방 상세 보기 버튼 이벤트
|
||||
$('.view-formula-detail').on('click', function() {
|
||||
const formulaId = $(this).data('id');
|
||||
$.get(`/api/formulas/${formulaId}/ingredients`, function(response) {
|
||||
if (response.success) {
|
||||
let ingredientsList = response.data.map(ing =>
|
||||
`${ing.herb_name}: ${ing.grams_per_cheop}g`
|
||||
).join(', ');
|
||||
alert('구성 약재:\n' + ingredientsList);
|
||||
}
|
||||
});
|
||||
const formulaName = $(this).data('name');
|
||||
showFormulaDetail(formulaId, formulaName);
|
||||
});
|
||||
|
||||
// 처방 수정 버튼 이벤트
|
||||
$('.edit-formula').on('click', function() {
|
||||
const formulaId = $(this).data('id');
|
||||
editFormula(formulaId);
|
||||
});
|
||||
|
||||
// 처방 삭제 버튼 이벤트
|
||||
$('.delete-formula').on('click', function() {
|
||||
const formulaId = $(this).data('id');
|
||||
const formulaName = $(this).data('name');
|
||||
if(confirm(`'${formulaName}' 처방을 삭제하시겠습니까?`)) {
|
||||
deleteFormula(formulaId);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 처방 상세 정보 표시 함수
|
||||
function showFormulaDetail(formulaId, formulaName) {
|
||||
// 모달에 formulaId 저장
|
||||
$('#formulaDetailModal').data('formula-id', formulaId);
|
||||
|
||||
// 모달 제목 설정
|
||||
$('#formulaDetailName').text(formulaName);
|
||||
|
||||
// 처방 기본 정보 로드
|
||||
$.get(`/api/formulas/${formulaId}`, function(response) {
|
||||
if (response.success && response.data) {
|
||||
const formula = response.data;
|
||||
|
||||
// 기본 정보 표시
|
||||
$('#detailFormulaCode').text(formula.formula_code || '-');
|
||||
$('#detailFormulaName').text(formula.formula_name);
|
||||
$('#detailFormulaType').text(formula.formula_type === 'STANDARD' ? '표준처방' : '사용자정의');
|
||||
$('#detailBaseCheop').text(formula.base_cheop + '첩');
|
||||
$('#detailBasePouches').text(formula.base_pouches + '파우치');
|
||||
$('#detailCreatedAt').text(formula.created_at ? new Date(formula.created_at).toLocaleDateString() : '-');
|
||||
$('#detailDescription').text(formula.description || '설명이 없습니다.');
|
||||
|
||||
// 주요 효능 표시
|
||||
if (formula.efficacy) {
|
||||
$('#formulaEffects').html(`<p>${formula.efficacy}</p>`);
|
||||
} else {
|
||||
$('#formulaEffects').html('<p class="text-muted">처방의 주요 효능 정보가 등록되지 않았습니다.</p>');
|
||||
}
|
||||
|
||||
// 처방 구성 약재 로드
|
||||
loadFormulaIngredients(formulaId);
|
||||
}
|
||||
}).fail(function() {
|
||||
alert('처방 정보를 불러오는데 실패했습니다.');
|
||||
});
|
||||
|
||||
// 모달 표시
|
||||
$('#formulaDetailModal').modal('show');
|
||||
}
|
||||
|
||||
// 처방 구성 약재 로드
|
||||
function loadFormulaIngredients(formulaId) {
|
||||
$.get(`/api/formulas/${formulaId}/ingredients`, function(response) {
|
||||
if (response.success) {
|
||||
const tbody = $('#formulaDetailIngredients');
|
||||
tbody.empty();
|
||||
|
||||
let totalGrams1 = 0;
|
||||
let totalGrams1Je = 0; // 1제 기준 (20첩 = 30파우치)
|
||||
let count = 0;
|
||||
|
||||
response.data.forEach((ingredient, index) => {
|
||||
count++;
|
||||
const gram1 = parseFloat(ingredient.grams_per_cheop) || 0;
|
||||
const gram1Je = gram1 * 20; // 1제 = 20첩 = 30파우치
|
||||
|
||||
totalGrams1 += gram1;
|
||||
totalGrams1Je += gram1Je;
|
||||
|
||||
// 재고 상태 표시 (stock_quantity 또는 total_available_stock 사용)
|
||||
const stockQty = ingredient.stock_quantity || ingredient.total_available_stock || 0;
|
||||
let stockStatus = '';
|
||||
|
||||
if (stockQty > 0) {
|
||||
stockStatus = `<span class="badge bg-success">재고 ${stockQty.toFixed(1)}g</span>`;
|
||||
} else {
|
||||
stockStatus = `<span class="badge bg-danger">재고없음</span>`;
|
||||
}
|
||||
|
||||
// 사용 가능한 제품 수 표시
|
||||
if (ingredient.product_count > 0) {
|
||||
stockStatus += `<br><small class="text-muted">${ingredient.product_count}개 제품</small>`;
|
||||
}
|
||||
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td>${index + 1}</td>
|
||||
<td>
|
||||
${ingredient.herb_name}<br>
|
||||
<small class="text-muted">${ingredient.ingredient_code}</small>
|
||||
</td>
|
||||
<td class="text-end">${gram1.toFixed(1)}g</td>
|
||||
<td class="text-end">${gram1Je.toFixed(1)}g</td>
|
||||
<td>${ingredient.notes || '-'}</td>
|
||||
<td class="text-center">${stockStatus}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
|
||||
// 합계 업데이트
|
||||
$('#totalIngredientsCount').text(count + '개');
|
||||
$('#totalGramsPerCheop').text(totalGrams1.toFixed(1) + 'g');
|
||||
$('#totalGrams1Cheop').text(totalGrams1.toFixed(1) + 'g');
|
||||
$('#totalGrams1Je').text(totalGrams1Je.toFixed(1) + 'g');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 처방 수정 함수
|
||||
function editFormula(formulaId) {
|
||||
$.get(`/api/formulas/${formulaId}`, function(response) {
|
||||
if (response.success && response.data) {
|
||||
const formula = response.data;
|
||||
|
||||
// 수정 모달에 데이터 채우기
|
||||
$('#formulaCode').val(formula.formula_code);
|
||||
$('#formulaName').val(formula.formula_name);
|
||||
$('#formulaType').val(formula.formula_type);
|
||||
$('#baseCheop').val(formula.base_cheop);
|
||||
$('#basePouches').val(formula.base_pouches);
|
||||
$('#formulaDescription').val(formula.description);
|
||||
$('#formulaEfficacy').val(formula.efficacy || '');
|
||||
|
||||
// 구성 약재 로드
|
||||
$.get(`/api/formulas/${formulaId}/ingredients`, function(ingResponse) {
|
||||
if (ingResponse.success) {
|
||||
$('#formulaIngredients').empty();
|
||||
formulaIngredientCount = 0;
|
||||
|
||||
ingResponse.data.forEach(ing => {
|
||||
formulaIngredientCount++;
|
||||
$('#formulaIngredients').append(`
|
||||
<tr data-row="${formulaIngredientCount}">
|
||||
<td>
|
||||
<select class="form-control form-control-sm herb-select">
|
||||
<option value="${ing.ingredient_code}" selected>${ing.herb_name}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm grams-input"
|
||||
min="0.1" step="0.1" value="${ing.grams_per_cheop}">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control form-control-sm notes-input" value="${ing.notes || ''}">
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-ingredient">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
|
||||
// 약재 목록 로드 (현재 선택된 값 유지)
|
||||
const selectElement = $(`#formulaIngredients tr[data-row="${formulaIngredientCount}"] .herb-select`);
|
||||
const currentValue = ing.ingredient_code;
|
||||
const currentText = ing.herb_name;
|
||||
loadHerbsForSelectWithCurrent(selectElement, currentValue, currentText);
|
||||
});
|
||||
|
||||
// 삭제 버튼 이벤트 바인딩
|
||||
$('.remove-ingredient').on('click', function() {
|
||||
$(this).closest('tr').remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 수정 모드 설정 (data 속성 사용)
|
||||
$('#formulaModal').data('edit-mode', true);
|
||||
$('#formulaModal').data('formula-id', formulaId);
|
||||
$('#formulaModal .modal-title').text('처방 수정');
|
||||
$('#formulaModal').modal('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 처방 삭제 함수
|
||||
function deleteFormula(formulaId) {
|
||||
$.ajax({
|
||||
url: `/api/formulas/${formulaId}`,
|
||||
method: 'DELETE',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert('처방이 삭제되었습니다.');
|
||||
loadFormulas();
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
alert('오류: ' + (xhr.responseJSON ? xhr.responseJSON.error : '삭제 실패'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 처방 상세 모달에서 수정 버튼 클릭
|
||||
$('#editFormulaDetailBtn').on('click', function() {
|
||||
const formulaId = $('#formulaDetailModal').data('formula-id');
|
||||
$('#formulaDetailModal').modal('hide');
|
||||
editFormula(formulaId);
|
||||
});
|
||||
|
||||
// 처방 상세 모달에서 삭제 버튼 클릭
|
||||
$('#deleteFormulaBtn').on('click', function() {
|
||||
const formulaId = $('#formulaDetailModal').data('formula-id');
|
||||
const formulaName = $('#formulaDetailName').text();
|
||||
|
||||
if(confirm(`'${formulaName}' 처방을 삭제하시겠습니까?`)) {
|
||||
$('#formulaDetailModal').modal('hide');
|
||||
deleteFormula(formulaId);
|
||||
}
|
||||
});
|
||||
|
||||
// 처방 구성 약재 추가 (모달)
|
||||
let formulaIngredientCount = 0;
|
||||
$('#addFormulaIngredientBtn').on('click', function() {
|
||||
@@ -524,12 +742,12 @@ $(document).ready(function() {
|
||||
$('#saveFormulaBtn').on('click', function() {
|
||||
const ingredients = [];
|
||||
$('#formulaIngredients tr').each(function() {
|
||||
const herbId = $(this).find('.herb-select').val();
|
||||
const herbCode = $(this).find('.herb-select').val();
|
||||
const grams = $(this).find('.grams-input').val();
|
||||
|
||||
if (herbId && grams) {
|
||||
if (herbCode && grams) {
|
||||
ingredients.push({
|
||||
herb_item_id: parseInt(herbId),
|
||||
ingredient_code: herbCode, // ingredient_code 사용
|
||||
grams_per_cheop: parseFloat(grams),
|
||||
notes: $(this).find('.notes-input').val()
|
||||
});
|
||||
@@ -543,25 +761,38 @@ $(document).ready(function() {
|
||||
base_cheop: parseInt($('#baseCheop').val()),
|
||||
base_pouches: parseInt($('#basePouches').val()),
|
||||
description: $('#formulaDescription').val(),
|
||||
efficacy: $('#formulaEfficacy').val(),
|
||||
ingredients: ingredients
|
||||
};
|
||||
|
||||
// 수정 모드인지 확인
|
||||
const isEditMode = $('#formulaModal').data('edit-mode');
|
||||
const formulaId = $('#formulaModal').data('formula-id');
|
||||
|
||||
const url = isEditMode ? `/api/formulas/${formulaId}` : '/api/formulas';
|
||||
const method = isEditMode ? 'PUT' : 'POST';
|
||||
|
||||
$.ajax({
|
||||
url: '/api/formulas',
|
||||
method: 'POST',
|
||||
url: url,
|
||||
method: method,
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(formulaData),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert('처방이 등록되었습니다.');
|
||||
const message = isEditMode ? '처방이 수정되었습니다.' : '처방이 등록되었습니다.';
|
||||
alert(message);
|
||||
$('#formulaModal').modal('hide');
|
||||
$('#formulaForm')[0].reset();
|
||||
$('#formulaIngredients').empty();
|
||||
// 수정 모드 초기화
|
||||
$('#formulaModal').data('edit-mode', false);
|
||||
$('#formulaModal').data('formula-id', null);
|
||||
$('#formulaModal .modal-title').text('처방 등록');
|
||||
loadFormulas();
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
alert('오류: ' + xhr.responseJSON.error);
|
||||
alert('오류: ' + (xhr.responseJSON ? xhr.responseJSON.error : '알 수 없는 오류'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1786,6 +2017,46 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
|
||||
// 현재 선택된 값을 유지하면서 약재 목록 로드
|
||||
function loadHerbsForSelectWithCurrent(selectElement, currentValue, currentText) {
|
||||
$.get('/api/herbs/masters', function(response) {
|
||||
if (response.success) {
|
||||
selectElement.empty().append('<option value="">약재 선택</option>');
|
||||
|
||||
// 재고가 있는 약재만 필터링하여 표시
|
||||
const herbsWithStock = response.data.filter(herb => herb.has_stock === 1);
|
||||
|
||||
let currentFound = false;
|
||||
|
||||
herbsWithStock.forEach(herb => {
|
||||
// ingredient_code를 value로 사용하고, 한글명(한자명) 형식으로 표시
|
||||
let displayName = herb.herb_name;
|
||||
if (herb.herb_name_hanja) {
|
||||
displayName += ` (${herb.herb_name_hanja})`;
|
||||
}
|
||||
|
||||
const isSelected = herb.ingredient_code === currentValue;
|
||||
if (isSelected) {
|
||||
currentFound = true;
|
||||
}
|
||||
|
||||
selectElement.append(`<option value="${herb.ingredient_code}" data-herb-name="${herb.herb_name}" ${isSelected ? 'selected' : ''}>${displayName}</option>`);
|
||||
});
|
||||
|
||||
// 만약 현재 선택된 약재가 목록에 없다면 (재고가 없거나 비활성 상태일 경우) 추가
|
||||
if (!currentFound && currentValue && currentText) {
|
||||
selectElement.append(`<option value="${currentValue}" data-herb-name="${currentText}" selected>${currentText} (재고없음)</option>`);
|
||||
}
|
||||
}
|
||||
}).fail(function(error) {
|
||||
console.error('Failed to load herbs:', error);
|
||||
// 로드 실패시에도 현재 선택된 값은 유지
|
||||
if (currentValue && currentText) {
|
||||
selectElement.append(`<option value="${currentValue}" data-herb-name="${currentText}" selected>${currentText}</option>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ingredient_code 기반으로 제품 옵션 로드
|
||||
function loadProductOptions(row, ingredientCode, herbName) {
|
||||
$.get(`/api/herbs/by-ingredient/${ingredientCode}`, function(response) {
|
||||
@@ -2396,6 +2667,128 @@ $(document).ready(function() {
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
// === 재고 자산 계산 설정 기능 ===
|
||||
|
||||
// 재고 현황 로드 (모드 포함)
|
||||
function loadInventorySummary() {
|
||||
const mode = localStorage.getItem('inventoryMode') || 'all';
|
||||
|
||||
$.get(`/api/inventory/summary?mode=${mode}`, function(response) {
|
||||
if (response.success) {
|
||||
$('#totalHerbs').text(response.data.length);
|
||||
$('#inventoryValue').text(formatCurrency(response.summary.total_value));
|
||||
|
||||
// 모드 표시 업데이트
|
||||
if (response.summary.calculation_mode) {
|
||||
$('#inventoryMode').text(response.summary.calculation_mode.mode_label);
|
||||
|
||||
// 설정 모달이 열려있으면 정보 표시
|
||||
if ($('#inventorySettingsModal').hasClass('show')) {
|
||||
updateModeInfo(response.summary.calculation_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 재고 계산 설정 저장
|
||||
window.saveInventorySettings = function() {
|
||||
const selectedMode = $('input[name="inventoryMode"]:checked').val();
|
||||
|
||||
// localStorage에 저장
|
||||
localStorage.setItem('inventoryMode', selectedMode);
|
||||
inventoryCalculationMode = selectedMode;
|
||||
|
||||
// 재고 현황 다시 로드
|
||||
loadInventorySummary();
|
||||
|
||||
// 모달 닫기
|
||||
$('#inventorySettingsModal').modal('hide');
|
||||
|
||||
// 성공 메시지
|
||||
showToast('success', '재고 계산 설정이 변경되었습니다.');
|
||||
}
|
||||
|
||||
// 모드 정보 업데이트
|
||||
function updateModeInfo(modeInfo) {
|
||||
let infoHtml = '';
|
||||
|
||||
if (modeInfo.mode === 'all' && modeInfo.no_receipt_lots !== undefined) {
|
||||
if (modeInfo.no_receipt_lots > 0) {
|
||||
infoHtml = `
|
||||
<p class="mb-1">• 입고장 없는 LOT: <strong>${modeInfo.no_receipt_lots}개</strong></p>
|
||||
<p class="mb-0">• 해당 재고 가치: <strong>${formatCurrency(modeInfo.no_receipt_value)}</strong></p>
|
||||
`;
|
||||
} else {
|
||||
infoHtml = '<p class="mb-0">• 모든 LOT이 입고장과 연결되어 있습니다.</p>';
|
||||
}
|
||||
} else if (modeInfo.mode === 'receipt_only') {
|
||||
infoHtml = '<p class="mb-0">• 입고장과 연결된 LOT만 계산합니다.</p>';
|
||||
} else if (modeInfo.mode === 'verified') {
|
||||
infoHtml = '<p class="mb-0">• 검증 확인된 LOT만 계산합니다.</p>';
|
||||
}
|
||||
|
||||
if (infoHtml) {
|
||||
$('#modeInfoContent').html(infoHtml);
|
||||
$('#modeInfo').show();
|
||||
} else {
|
||||
$('#modeInfo').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// 설정 모달이 열릴 때 현재 모드 설정
|
||||
$('#inventorySettingsModal').on('show.bs.modal', function() {
|
||||
const currentMode = localStorage.getItem('inventoryMode') || 'all';
|
||||
$(`input[name="inventoryMode"][value="${currentMode}"]`).prop('checked', true);
|
||||
|
||||
// 현재 모드 정보 로드
|
||||
$.get(`/api/inventory/summary?mode=${currentMode}`, function(response) {
|
||||
if (response.success && response.summary.calculation_mode) {
|
||||
updateModeInfo(response.summary.calculation_mode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 모드 선택 시 즉시 정보 업데이트
|
||||
$('input[name="inventoryMode"]').on('change', function() {
|
||||
const selectedMode = $(this).val();
|
||||
|
||||
// 선택한 모드의 정보를 미리보기로 로드
|
||||
$.get(`/api/inventory/summary?mode=${selectedMode}`, function(response) {
|
||||
if (response.success && response.summary.calculation_mode) {
|
||||
updateModeInfo(response.summary.calculation_mode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Toast 메시지 표시 함수 (없으면 추가)
|
||||
function showToast(type, message) {
|
||||
const toastHtml = `
|
||||
<div class="toast align-items-center text-white bg-${type === 'success' ? 'success' : 'danger'} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Toast 컨테이너가 없으면 생성
|
||||
if (!$('#toastContainer').length) {
|
||||
$('body').append('<div id="toastContainer" class="position-fixed bottom-0 end-0 p-3" style="z-index: 11"></div>');
|
||||
}
|
||||
|
||||
const $toast = $(toastHtml);
|
||||
$('#toastContainer').append($toast);
|
||||
|
||||
const toast = new bootstrap.Toast($toast[0]);
|
||||
toast.show();
|
||||
|
||||
// 5초 후 자동 제거
|
||||
setTimeout(() => {
|
||||
$toast.remove();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// ==================== 주성분코드 기반 약재 관리 ====================
|
||||
let allHerbMasters = []; // 전체 약재 데이터 저장
|
||||
let currentFilter = 'all'; // 현재 필터 상태
|
||||
@@ -2726,4 +3119,324 @@ $(document).ready(function() {
|
||||
|
||||
return ingredients;
|
||||
};
|
||||
|
||||
// ==================== 약재 정보 시스템 ====================
|
||||
|
||||
// 약재 정보 페이지 로드
|
||||
window.loadHerbInfo = function loadHerbInfo() {
|
||||
loadAllHerbsInfo();
|
||||
loadEfficacyTags();
|
||||
|
||||
// 뷰 전환 버튼
|
||||
$('#herb-info button[data-view]').on('click', function() {
|
||||
$('#herb-info button[data-view]').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
const view = $(this).data('view');
|
||||
if (view === 'search') {
|
||||
$('#herb-search-section').show();
|
||||
$('#herb-efficacy-section').hide();
|
||||
loadAllHerbsInfo();
|
||||
} else if (view === 'efficacy') {
|
||||
$('#herb-search-section').hide();
|
||||
$('#herb-efficacy-section').show();
|
||||
loadEfficacyTagButtons();
|
||||
} else if (view === 'category') {
|
||||
$('#herb-search-section').show();
|
||||
$('#herb-efficacy-section').hide();
|
||||
loadHerbsByCategory();
|
||||
}
|
||||
});
|
||||
|
||||
// 검색 버튼
|
||||
$('#herbSearchBtn').off('click').on('click', function() {
|
||||
const searchTerm = $('#herbSearchInput').val();
|
||||
searchHerbs(searchTerm);
|
||||
});
|
||||
|
||||
// 엔터 키로 검색
|
||||
$('#herbSearchInput').off('keypress').on('keypress', function(e) {
|
||||
if (e.which === 13) {
|
||||
searchHerbs($(this).val());
|
||||
}
|
||||
});
|
||||
|
||||
// 필터 변경
|
||||
$('#herbInfoEfficacyFilter, #herbInfoPropertyFilter').off('change').on('change', function() {
|
||||
filterHerbs();
|
||||
});
|
||||
}
|
||||
|
||||
// 모든 약재 정보 로드
|
||||
window.loadAllHerbsInfo = function loadAllHerbsInfo() {
|
||||
$.get('/api/herbs/masters', function(response) {
|
||||
if (response.success) {
|
||||
displayHerbCards(response.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 약재 카드 표시
|
||||
window.displayHerbCards = function displayHerbCards(herbs) {
|
||||
const grid = $('#herbInfoGrid');
|
||||
grid.empty();
|
||||
|
||||
if (herbs.length === 0) {
|
||||
grid.html('<div class="col-12 text-center text-muted py-5">검색 결과가 없습니다.</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
herbs.forEach(herb => {
|
||||
// 재고 상태에 따른 배지 색상
|
||||
const stockBadge = herb.has_stock ?
|
||||
'<span class="badge bg-success">재고있음</span>' :
|
||||
'<span class="badge bg-secondary">재고없음</span>';
|
||||
|
||||
// 효능 태그 HTML
|
||||
let tagsHtml = '';
|
||||
if (herb.efficacy_tags && herb.efficacy_tags.length > 0) {
|
||||
tagsHtml = herb.efficacy_tags.slice(0, 3).map(tag =>
|
||||
`<span class="badge bg-info me-1">${tag}</span>`
|
||||
).join('');
|
||||
if (herb.efficacy_tags.length > 3) {
|
||||
tagsHtml += `<span class="badge bg-secondary">+${herb.efficacy_tags.length - 3}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
const card = `
|
||||
<div class="col-md-4 col-lg-3">
|
||||
<div class="card h-100 herb-info-card" data-herb-id="${herb.herb_id}"
|
||||
data-ingredient-code="${herb.ingredient_code}"
|
||||
style="cursor: pointer;">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h6 class="card-title mb-0">
|
||||
${herb.herb_name}
|
||||
${herb.herb_name_hanja ? `<small class="text-muted">(${herb.herb_name_hanja})</small>` : ''}
|
||||
</h6>
|
||||
${stockBadge}
|
||||
</div>
|
||||
<p class="card-text small text-muted mb-2">
|
||||
${herb.ingredient_code}
|
||||
</p>
|
||||
<div class="mb-2">
|
||||
${tagsHtml || '<span class="text-muted small">태그 없음</span>'}
|
||||
</div>
|
||||
${herb.main_effects ?
|
||||
`<p class="card-text small">${herb.main_effects.substring(0, 50)}...</p>` :
|
||||
'<p class="card-text small text-muted">효능 정보 없음</p>'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
grid.append(card);
|
||||
});
|
||||
|
||||
// 카드 클릭 이벤트
|
||||
$('.herb-info-card').off('click').on('click', function() {
|
||||
const ingredientCode = $(this).data('ingredient-code');
|
||||
showHerbDetail(ingredientCode);
|
||||
});
|
||||
}
|
||||
|
||||
// 약재 상세 정보 표시
|
||||
function showHerbDetail(ingredientCode) {
|
||||
// herb_master_extended에서 herb_id 찾기
|
||||
$.get('/api/herbs/masters', function(response) {
|
||||
if (response.success) {
|
||||
const herb = response.data.find(h => h.ingredient_code === ingredientCode);
|
||||
if (herb && herb.herb_id) {
|
||||
// 확장 정보 조회
|
||||
$.get(`/api/herbs/${herb.herb_id}/extended`, function(detailResponse) {
|
||||
displayHerbDetailModal(detailResponse);
|
||||
}).fail(function() {
|
||||
// 확장 정보가 없으면 기본 정보만 표시
|
||||
displayHerbDetailModal(herb);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 상세 정보 모달 표시
|
||||
function displayHerbDetailModal(herb) {
|
||||
$('#herbDetailName').text(herb.name_korean || herb.herb_name || '-');
|
||||
$('#herbDetailHanja').text(herb.name_hanja || herb.herb_name_hanja || '');
|
||||
|
||||
// 기본 정보
|
||||
$('#detailIngredientCode').text(herb.ingredient_code || '-');
|
||||
$('#detailLatinName').text(herb.name_latin || herb.herb_name_latin || '-');
|
||||
$('#detailMedicinalPart').text(herb.medicinal_part || '-');
|
||||
$('#detailOriginPlant').text(herb.origin_plant || '-');
|
||||
|
||||
// 성미귀경
|
||||
$('#detailProperty').text(herb.property || '-');
|
||||
$('#detailTaste').text(herb.taste || '-');
|
||||
$('#detailMeridian').text(herb.meridian_tropism || '-');
|
||||
|
||||
// 효능효과
|
||||
$('#detailMainEffects').text(herb.main_effects || '-');
|
||||
$('#detailIndications').text(herb.indications || '-');
|
||||
|
||||
// 효능 태그
|
||||
if (herb.efficacy_tags && herb.efficacy_tags.length > 0) {
|
||||
const tagsHtml = herb.efficacy_tags.map(tag => {
|
||||
const strength = tag.strength || 3;
|
||||
const sizeClass = strength >= 4 ? 'fs-5' : 'fs-6';
|
||||
return `<span class="badge bg-primary ${sizeClass} me-2">${tag.name || tag}</span>`;
|
||||
}).join('');
|
||||
$('#detailEfficacyTags').html(tagsHtml);
|
||||
} else {
|
||||
$('#detailEfficacyTags').html('<span class="text-muted">태그 없음</span>');
|
||||
}
|
||||
|
||||
// 용법용량
|
||||
$('#detailDosageRange').text(herb.dosage_range || '-');
|
||||
$('#detailDosageMax').text(herb.dosage_max || '-');
|
||||
$('#detailPreparation').text(herb.preparation_method || '-');
|
||||
|
||||
// 안전성
|
||||
$('#detailContraindications').text(herb.contraindications || '-');
|
||||
$('#detailPrecautions').text(herb.precautions || '-');
|
||||
|
||||
// 성분정보
|
||||
$('#detailActiveCompounds').text(herb.active_compounds || '-');
|
||||
|
||||
// 임상응용
|
||||
$('#detailPharmacological').text(herb.pharmacological_effects || '-');
|
||||
$('#detailClinical').text(herb.clinical_applications || '-');
|
||||
|
||||
// 정보 수정 버튼
|
||||
$('#editHerbInfoBtn').off('click').on('click', function() {
|
||||
editHerbInfo(herb.herb_id || herb.ingredient_code);
|
||||
});
|
||||
|
||||
$('#herbDetailModal').modal('show');
|
||||
}
|
||||
|
||||
// 약재 검색
|
||||
function searchHerbs(searchTerm) {
|
||||
if (!searchTerm) {
|
||||
loadAllHerbsInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
$.get('/api/herbs/masters', function(response) {
|
||||
if (response.success) {
|
||||
const filtered = response.data.filter(herb => {
|
||||
const term = searchTerm.toLowerCase();
|
||||
return (herb.herb_name && herb.herb_name.toLowerCase().includes(term)) ||
|
||||
(herb.herb_name_hanja && herb.herb_name_hanja.includes(term)) ||
|
||||
(herb.ingredient_code && herb.ingredient_code.toLowerCase().includes(term)) ||
|
||||
(herb.main_effects && herb.main_effects.toLowerCase().includes(term)) ||
|
||||
(herb.efficacy_tags && herb.efficacy_tags.some(tag => tag.toLowerCase().includes(term)));
|
||||
});
|
||||
displayHerbCards(filtered);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 필터 적용
|
||||
function filterHerbs() {
|
||||
const efficacyFilter = $('#herbInfoEfficacyFilter').val();
|
||||
const propertyFilter = $('#herbInfoPropertyFilter').val();
|
||||
|
||||
$.get('/api/herbs/masters', function(response) {
|
||||
if (response.success) {
|
||||
let filtered = response.data;
|
||||
|
||||
if (efficacyFilter) {
|
||||
filtered = filtered.filter(herb =>
|
||||
herb.efficacy_tags && herb.efficacy_tags.includes(efficacyFilter)
|
||||
);
|
||||
}
|
||||
|
||||
if (propertyFilter) {
|
||||
filtered = filtered.filter(herb =>
|
||||
herb.property === propertyFilter
|
||||
);
|
||||
}
|
||||
|
||||
displayHerbCards(filtered);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 효능 태그 로드
|
||||
function loadEfficacyTags() {
|
||||
$.get('/api/efficacy-tags', function(tags) {
|
||||
const select = $('#herbInfoEfficacyFilter');
|
||||
select.empty().append('<option value="">모든 효능</option>');
|
||||
|
||||
tags.forEach(tag => {
|
||||
select.append(`<option value="${tag.name}">${tag.name} - ${tag.description}</option>`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 효능 태그 버튼 표시
|
||||
function loadEfficacyTagButtons() {
|
||||
$.get('/api/efficacy-tags', function(tags) {
|
||||
const container = $('#efficacyTagsContainer');
|
||||
container.empty();
|
||||
|
||||
// 카테고리별로 그룹화
|
||||
const grouped = {};
|
||||
tags.forEach(tag => {
|
||||
if (!grouped[tag.category]) {
|
||||
grouped[tag.category] = [];
|
||||
}
|
||||
grouped[tag.category].push(tag);
|
||||
});
|
||||
|
||||
// 카테고리별로 표시
|
||||
Object.keys(grouped).forEach(category => {
|
||||
const categoryHtml = `
|
||||
<div class="col-12">
|
||||
<h6 class="text-muted mb-2">${category}</h6>
|
||||
<div class="btn-group flex-wrap mb-3" role="group">
|
||||
${grouped[category].map(tag => `
|
||||
<button type="button" class="btn btn-outline-primary efficacy-tag-btn m-1"
|
||||
data-tag="${tag.name}">
|
||||
${tag.name}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.append(categoryHtml);
|
||||
});
|
||||
|
||||
// 태그 버튼 클릭 이벤트
|
||||
$('.efficacy-tag-btn').on('click', function() {
|
||||
$(this).toggleClass('active');
|
||||
const selectedTags = $('.efficacy-tag-btn.active').map(function() {
|
||||
return $(this).data('tag');
|
||||
}).get();
|
||||
|
||||
if (selectedTags.length > 0) {
|
||||
searchByEfficacyTags(selectedTags);
|
||||
} else {
|
||||
loadAllHerbsInfo();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 효능 태그로 검색
|
||||
function searchByEfficacyTags(tags) {
|
||||
const queryString = tags.map(tag => `tags=${encodeURIComponent(tag)}`).join('&');
|
||||
$.get(`/api/herbs/search-by-efficacy?${queryString}`, function(herbs) {
|
||||
displayHerbCards(herbs);
|
||||
});
|
||||
}
|
||||
|
||||
// 약재 정보 수정 (추후 구현)
|
||||
function editHerbInfo(herbId) {
|
||||
// herbId는 향후 수정 기능 구현시 사용 예정
|
||||
console.log('Edit herb info for ID:', herbId);
|
||||
alert('약재 정보 수정 기능은 준비 중입니다.');
|
||||
// TODO: 정보 수정 폼 구현
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user