feat: [approvals] 양식 선택 시 설명 카드 표시

- 지출결의서/품의서 5종 선택 시 우측에 설명 카드 노출
- 드롭다운 30% + 설명 카드 70% 레이아웃
- 양식별 아이콘/색상/설명 텍스트 (사전승인 vs 사후보고 등)
- create/edit 동일 적용
This commit is contained in:
김보곤
2026-03-06 12:54:53 +09:00
parent b35b352f19
commit 7bbfc9dab5
2 changed files with 177 additions and 25 deletions

View File

@@ -14,22 +14,37 @@
<div class="bg-white rounded-lg shadow-sm p-6"> <div class="bg-white rounded-lg shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">문서 내용</h2> <h2 class="text-lg font-semibold text-gray-800 mb-4">문서 내용</h2>
{{-- 양식 선택 --}} {{-- 양식 선택 + 설명 카드 --}}
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label> <label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label>
<div class="flex items-center gap-2"> <div class="flex gap-3" style="align-items: flex-start;">
<select id="form_id" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"> <div style="width: 30%; min-width: 180px;" class="shrink-0">
@foreach($forms as $form) <div class="flex items-center gap-2">
<option value="{{ $form->id }}">{{ $form->name }}</option> <select id="form_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
@endforeach @foreach($forms as $form)
</select> <option value="{{ $form->id }}">{{ $form->name }}</option>
<button type="button" id="expense-load-btn" style="display: none;" onclick="openExpenseLoadModal()" @endforeach
class="shrink-0 px-3 py-2 bg-amber-50 text-amber-700 hover:bg-amber-100 border border-amber-200 rounded-lg text-sm font-medium transition inline-flex items-center gap-1"> </select>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </div>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/> <button type="button" id="expense-load-btn" style="display: none;" onclick="openExpenseLoadModal()"
</svg> class="mt-2 w-full px-3 py-2 bg-amber-50 text-amber-700 hover:bg-amber-100 border border-amber-200 rounded-lg text-sm font-medium transition inline-flex items-center justify-center gap-1">
불러오기 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</button> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</svg>
불러오기
</button>
</div>
<div id="form-description-card" style="flex: 1; display: none;">
<div class="rounded-lg border p-3 text-sm transition-all" id="form-desc-inner">
<div class="flex items-start gap-2">
<div id="form-desc-icon" class="shrink-0 mt-0.5"></div>
<div>
<div id="form-desc-title" class="font-semibold text-sm mb-1"></div>
<div id="form-desc-text" class="text-xs leading-relaxed"></div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
@@ -242,6 +257,73 @@ class="p-1 text-gray-400 hover:text-gray-600 transition">
const linesData = @json($lines); const linesData = @json($lines);
let isExpenseForm = false; let isExpenseForm = false;
let isPurchaseRequestForm = false; let isPurchaseRequestForm = false;
const formDescriptions = {
expense: {
title: '지출결의서',
icon: '💰',
color: 'border-amber-200 bg-amber-50',
titleColor: 'text-amber-800',
textColor: 'text-amber-700',
text: '이미 발생한 지출에 대해 사후 보고하는 문서입니다. 법인카드 사용 내역, 계좌이체 등 실제 지출이 완료된 건에 대해 증빙자료와 함께 결재를 요청합니다.',
},
pr_expense: {
title: '지출품의서',
icon: '📋',
color: 'border-orange-200 bg-orange-50',
titleColor: 'text-orange-800',
textColor: 'text-orange-700',
text: '지출이 발생하기 전 사전 승인을 받는 문서입니다. 예산 범위 내에서 지출 항목과 금액을 기재하여 사전에 승락을 받습니다.',
},
pr_contract: {
title: '계약체결품의서',
icon: '📝',
color: 'border-purple-200 bg-purple-50',
titleColor: 'text-purple-800',
textColor: 'text-purple-700',
text: '외부 업체와의 계약 체결 전 승인을 받는 문서입니다. 계약 상대방, 계약 내용, 기간, 금액, 주요 조건 등을 명시하여 계약 진행에 대한 사전 승락을 받습니다.',
},
pr_purchase: {
title: '구매품의서',
icon: '🛒',
color: 'border-blue-200 bg-blue-50',
titleColor: 'text-blue-800',
textColor: 'text-blue-700',
text: '물품 구매 전 사전 승인을 받는 문서입니다. 구매할 품목, 수량, 단가, 납품업체 등을 기재하여 구매 진행에 대한 사전 승락을 받습니다.',
},
pr_trip: {
title: '출장품의서',
icon: '✈️',
color: 'border-green-200 bg-green-50',
titleColor: 'text-green-800',
textColor: 'text-green-700',
text: '출장 전 계획 승인을 받는 문서입니다. 출장지, 기간, 업무 내용, 예상 경비(교통비·숙박비·식비 등)를 기재하여 출장 진행에 대한 사전 승락을 받습니다.',
},
pr_settlement: {
title: '비용정산품의서',
icon: '🧾',
color: 'border-teal-200 bg-teal-50',
titleColor: 'text-teal-800',
textColor: 'text-teal-700',
text: '업무 수행 중 발생한 비용의 정산 승인을 받는 문서입니다. 사용일자별 항목과 금액을 기재하고, 법인카드 사용 또는 개인 선지출 여부를 명시합니다.',
},
};
function updateFormDescription(formId) {
const code = formCodes[formId];
const desc = formDescriptions[code];
const card = document.getElementById('form-description-card');
if (!desc) { card.style.display = 'none'; return; }
const inner = document.getElementById('form-desc-inner');
inner.className = 'rounded-lg border p-3 text-sm transition-all ' + desc.color;
document.getElementById('form-desc-icon').innerHTML = '<span style="font-size: 1.25rem;">' + desc.icon + '</span>';
document.getElementById('form-desc-title').className = 'font-semibold text-sm mb-1 ' + desc.titleColor;
document.getElementById('form-desc-title').textContent = desc.title;
document.getElementById('form-desc-text').className = 'text-xs leading-relaxed ' + desc.textColor;
document.getElementById('form-desc-text').textContent = desc.text;
card.style.display = '';
}
let isLeaveForm = false; let isLeaveForm = false;
let isCertForm = false; let isCertForm = false;
let isCareerCertForm = false; let isCareerCertForm = false;
@@ -620,12 +702,15 @@ function applyBodyTemplate(formId) {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
setTimeout(updateApprovalLineSummary, 200); setTimeout(updateApprovalLineSummary, 200);
// 초기 양식에 대한 폼 모드 전환 // 초기 양식에 대한 폼 모드 전환 + 설명 카드
switchFormMode(document.getElementById('form_id').value); const initialFormId = document.getElementById('form_id').value;
switchFormMode(initialFormId);
updateFormDescription(initialFormId);
// 양식 변경 시 본문 템플릿 자동 채움 // 양식 변경 시 본문 템플릿 자동 채움 + 설명 카드 업데이트
document.getElementById('form_id').addEventListener('change', function() { document.getElementById('form_id').addEventListener('change', function() {
applyBodyTemplate(this.value); applyBodyTemplate(this.value);
updateFormDescription(this.value);
}); });
}); });

View File

@@ -63,11 +63,26 @@
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label> <label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label>
<select id="form_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"> <div class="flex gap-3" style="align-items: flex-start;">
@foreach($forms as $form) <div style="width: 30%; min-width: 180px;" class="shrink-0">
<option value="{{ $form->id }}" {{ $approval->form_id == $form->id ? 'selected' : '' }}>{{ $form->name }}</option> <select id="form_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
@endforeach @foreach($forms as $form)
</select> <option value="{{ $form->id }}" {{ $approval->form_id == $form->id ? 'selected' : '' }}>{{ $form->name }}</option>
@endforeach
</select>
</div>
<div id="form-description-card" style="flex: 1; display: none;">
<div class="rounded-lg border p-3 text-sm transition-all" id="form-desc-inner">
<div class="flex items-start gap-2">
<div id="form-desc-icon" class="shrink-0 mt-0.5"></div>
<div>
<div id="form-desc-title" class="font-semibold text-sm mb-1"></div>
<div id="form-desc-text" class="text-xs leading-relaxed"></div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@@ -268,6 +283,55 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
let isPurchaseRequestForm = false; let isPurchaseRequestForm = false;
let isCertForm = false; let isCertForm = false;
const formDescriptions = {
expense: {
title: '지출결의서', icon: '💰',
color: 'border-amber-200 bg-amber-50', titleColor: 'text-amber-800', textColor: 'text-amber-700',
text: '이미 발생한 지출에 대해 사후 보고하는 문서입니다. 법인카드 사용 내역, 계좌이체 등 실제 지출이 완료된 건에 대해 증빙자료와 함께 결재를 요청합니다.',
},
pr_expense: {
title: '지출품의서', icon: '📋',
color: 'border-orange-200 bg-orange-50', titleColor: 'text-orange-800', textColor: 'text-orange-700',
text: '지출이 발생하기 전 사전 승인을 받는 문서입니다. 예산 범위 내에서 지출 항목과 금액을 기재하여 사전에 승락을 받습니다.',
},
pr_contract: {
title: '계약체결품의서', icon: '📝',
color: 'border-purple-200 bg-purple-50', titleColor: 'text-purple-800', textColor: 'text-purple-700',
text: '외부 업체와의 계약 체결 전 승인을 받는 문서입니다. 계약 상대방, 계약 내용, 기간, 금액, 주요 조건 등을 명시하여 계약 진행에 대한 사전 승락을 받습니다.',
},
pr_purchase: {
title: '구매품의서', icon: '🛒',
color: 'border-blue-200 bg-blue-50', titleColor: 'text-blue-800', textColor: 'text-blue-700',
text: '물품 구매 전 사전 승인을 받는 문서입니다. 구매할 품목, 수량, 단가, 납품업체 등을 기재하여 구매 진행에 대한 사전 승락을 받습니다.',
},
pr_trip: {
title: '출장품의서', icon: '✈️',
color: 'border-green-200 bg-green-50', titleColor: 'text-green-800', textColor: 'text-green-700',
text: '출장 전 계획 승인을 받는 문서입니다. 출장지, 기간, 업무 내용, 예상 경비(교통비·숙박비·식비 등)를 기재하여 출장 진행에 대한 사전 승락을 받습니다.',
},
pr_settlement: {
title: '비용정산품의서', icon: '🧾',
color: 'border-teal-200 bg-teal-50', titleColor: 'text-teal-800', textColor: 'text-teal-700',
text: '업무 수행 중 발생한 비용의 정산 승인을 받는 문서입니다. 사용일자별 항목과 금액을 기재하고, 법인카드 사용 또는 개인 선지출 여부를 명시합니다.',
},
};
function updateFormDescription(formId) {
const code = formCodes[formId];
const desc = formDescriptions[code];
const card = document.getElementById('form-description-card');
if (!desc) { card.style.display = 'none'; return; }
const inner = document.getElementById('form-desc-inner');
inner.className = 'rounded-lg border p-3 text-sm transition-all ' + desc.color;
document.getElementById('form-desc-icon').innerHTML = '<span style="font-size: 1.25rem;">' + desc.icon + '</span>';
document.getElementById('form-desc-title').className = 'font-semibold text-sm mb-1 ' + desc.titleColor;
document.getElementById('form-desc-title').textContent = desc.title;
document.getElementById('form-desc-text').className = 'text-xs leading-relaxed ' + desc.textColor;
document.getElementById('form-desc-text').textContent = desc.text;
card.style.display = '';
}
function escapeHtml(str) { function escapeHtml(str) {
if (!str) return ''; if (!str) return '';
const div = document.createElement('div'); const div = document.createElement('div');
@@ -516,8 +580,10 @@ function applyBodyTemplate(formId) {
// 기존 HTML body 자동 감지 → 편집기 자동 활성화 + 결재선 요약 초기화 // 기존 HTML body 자동 감지 → 편집기 자동 활성화 + 결재선 요약 초기화
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// 초기 양식에 대한 폼 모드 전환 // 초기 양식에 대한 폼 모드 전환 + 설명 카드
switchFormMode(document.getElementById('form_id').value); const initialFormId = document.getElementById('form_id').value;
switchFormMode(initialFormId);
updateFormDescription(initialFormId);
// 재직증명서 기존 데이터 복원 // 재직증명서 기존 데이터 복원
if (isCertForm) { if (isCertForm) {
@@ -566,9 +632,10 @@ function applyBodyTemplate(formId) {
// Alpine 초기화 후 기존 결재선 요약 표시 // Alpine 초기화 후 기존 결재선 요약 표시
setTimeout(updateApprovalLineSummary, 200); setTimeout(updateApprovalLineSummary, 200);
// 양식 변경 시 본문 템플릿 자동 채움 // 양식 변경 시 본문 템플릿 자동 채움 + 설명 카드 업데이트
document.getElementById('form_id').addEventListener('change', function() { document.getElementById('form_id').addEventListener('change', function() {
applyBodyTemplate(this.value); applyBodyTemplate(this.value);
updateFormDescription(this.value);
}); });
}); });