feat: [approvals] 양식 선택 2단계 구조 (분류 → 양식)

- 1단계: 분류 선택 (일반/인사·근태/증명서/품의/재무)
- 2단계: 해당 분류 내 양식만 필터링하여 표시
- 분류별 아이콘 표시 (📄📜📋💰👤)
- edit 화면에서 기존 양식의 분류 자동 선택
This commit is contained in:
김보곤
2026-03-06 13:18:44 +09:00
parent e78aef47e5
commit 94664898a5
2 changed files with 159 additions and 11 deletions

View File

@@ -14,18 +14,18 @@
<div class="bg-white rounded-lg shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">문서 내용</h2>
{{-- 양식 선택 + 설명 카드 --}}
{{-- 양식 선택 (2단계: 분류 양식) + 설명 카드 --}}
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label>
<div class="flex gap-3" style="align-items: flex-start;">
<div style="width: 30%; min-width: 180px;" class="shrink-0">
<div class="flex items-center gap-2">
<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">
@foreach($forms as $form)
<option value="{{ $form->id }}">{{ $form->name }}</option>
@endforeach
</select>
</div>
<select id="form_category" 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 font-medium">
</select>
<select id="form_id" class="mt-2 w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
@foreach($forms as $form)
<option value="{{ $form->id }}">{{ $form->name }}</option>
@endforeach
</select>
<button type="button" id="expense-load-btn" style="display: none;" onclick="openExpenseLoadModal()"
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">
@@ -331,6 +331,76 @@ class="p-1 text-gray-400 hover:text-gray-600 transition">
},
};
// 2단계 분류 정의 (코드 → 카테고리)
const formCategoryMap = {
BUSINESS_DRAFT: '일반',
leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태',
employment_cert: '증명서', career_cert: '증명서', appointment_cert: '증명서',
pr_expense: '품의', pr_contract: '품의', pr_purchase: '품의', pr_trip: '품의', pr_settlement: '품의',
expense: '재무',
};
const categoryIcons = {
'일반': '📄', '인사/근태': '👤', '증명서': '📜', '품의': '📋', '재무': '💰',
};
const categoryOrder = ['일반', '인사/근태', '증명서', '품의', '재무'];
// formId → code 역맵, formId → name
const formNames = @json($forms->pluck('name', 'id'));
function buildCategoryOptions() {
const catSelect = document.getElementById('form_category');
const formSelect = document.getElementById('form_id');
const usedCategories = new Set();
// form_id의 옵션에서 사용 가능한 카테고리 수집
Array.from(formSelect.options).forEach(opt => {
const code = formCodes[opt.value];
const cat = formCategoryMap[code];
if (cat) usedCategories.add(cat);
});
catSelect.innerHTML = '';
categoryOrder.forEach(cat => {
if (!usedCategories.has(cat)) return;
const opt = document.createElement('option');
opt.value = cat;
opt.textContent = (categoryIcons[cat] || '') + ' ' + cat;
catSelect.appendChild(opt);
});
}
function filterFormsByCategory(category) {
const formSelect = document.getElementById('form_id');
const currentVal = formSelect.value;
let firstMatch = null;
let hasCurrentInCategory = false;
Array.from(formSelect.options).forEach(opt => {
const code = formCodes[opt.value];
const cat = formCategoryMap[code] || '일반';
const show = cat === category;
opt.style.display = show ? '' : 'none';
opt.disabled = !show;
if (show && !firstMatch) firstMatch = opt.value;
if (show && opt.value === currentVal) hasCurrentInCategory = true;
});
if (!hasCurrentInCategory && firstMatch) {
formSelect.value = firstMatch;
}
formSelect.dispatchEvent(new Event('change'));
}
function selectCategoryByFormId(formId) {
const code = formCodes[formId];
const cat = formCategoryMap[code];
if (cat) {
document.getElementById('form_category').value = cat;
filterFormsByCategory(cat);
}
}
function updateFormDescription(formId) {
const code = formCodes[formId];
const desc = formDescriptions[code];
@@ -724,11 +794,18 @@ function applyBodyTemplate(formId) {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(updateApprovalLineSummary, 200);
// 초기 양식에 대한 폼 모드 전환 + 설명 카드
// 2단계 분류 초기화
buildCategoryOptions();
const initialFormId = document.getElementById('form_id').value;
selectCategoryByFormId(initialFormId);
switchFormMode(initialFormId);
updateFormDescription(initialFormId);
// 분류 변경 → 양식 필터링
document.getElementById('form_category').addEventListener('change', function() {
filterFormsByCategory(this.value);
});
// 양식 변경 시 본문 템플릿 자동 채움 + 설명 카드 업데이트
document.getElementById('form_id').addEventListener('change', function() {
applyBodyTemplate(this.value);

View File

@@ -65,7 +65,9 @@
<label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label>
<div class="flex gap-3" style="align-items: flex-start;">
<div style="width: 30%; min-width: 180px;" class="shrink-0">
<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">
<select id="form_category" 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 font-medium">
</select>
<select id="form_id" class="mt-2 w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
@foreach($forms as $form)
<option value="{{ $form->id }}" {{ $approval->form_id == $form->id ? 'selected' : '' }}>{{ $form->name }}</option>
@endforeach
@@ -356,6 +358,68 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
},
};
// 2단계 분류 정의
const formCategoryMap = {
BUSINESS_DRAFT: '일반',
leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태',
employment_cert: '증명서', career_cert: '증명서', appointment_cert: '증명서',
pr_expense: '품의', pr_contract: '품의', pr_purchase: '품의', pr_trip: '품의', pr_settlement: '품의',
expense: '재무',
};
const categoryIcons = {
'일반': '📄', '인사/근태': '👤', '증명서': '📜', '품의': '📋', '재무': '💰',
};
const categoryOrder = ['일반', '인사/근태', '증명서', '품의', '재무'];
const formNames = @json($forms->pluck('name', 'id'));
function buildCategoryOptions() {
const catSelect = document.getElementById('form_category');
const formSelect = document.getElementById('form_id');
const usedCategories = new Set();
Array.from(formSelect.options).forEach(opt => {
const code = formCodes[opt.value];
const cat = formCategoryMap[code];
if (cat) usedCategories.add(cat);
});
catSelect.innerHTML = '';
categoryOrder.forEach(cat => {
if (!usedCategories.has(cat)) return;
const opt = document.createElement('option');
opt.value = cat;
opt.textContent = (categoryIcons[cat] || '') + ' ' + cat;
catSelect.appendChild(opt);
});
}
function filterFormsByCategory(category) {
const formSelect = document.getElementById('form_id');
const currentVal = formSelect.value;
let firstMatch = null;
let hasCurrentInCategory = false;
Array.from(formSelect.options).forEach(opt => {
const code = formCodes[opt.value];
const cat = formCategoryMap[code] || '일반';
const show = cat === category;
opt.style.display = show ? '' : 'none';
opt.disabled = !show;
if (show && !firstMatch) firstMatch = opt.value;
if (show && opt.value === currentVal) hasCurrentInCategory = true;
});
if (!hasCurrentInCategory && firstMatch) {
formSelect.value = firstMatch;
}
formSelect.dispatchEvent(new Event('change'));
}
function selectCategoryByFormId(formId) {
const code = formCodes[formId];
const cat = formCategoryMap[code];
if (cat) {
document.getElementById('form_category').value = cat;
filterFormsByCategory(cat);
}
}
function updateFormDescription(formId) {
const code = formCodes[formId];
const desc = formDescriptions[code];
@@ -620,11 +684,18 @@ function applyBodyTemplate(formId) {
// 기존 HTML body 자동 감지 → 편집기 자동 활성화 + 결재선 요약 초기화
document.addEventListener('DOMContentLoaded', function () {
// 초기 양식에 대한 폼 모드 전환 + 설명 카드
// 2단계 분류 초기화
buildCategoryOptions();
const initialFormId = document.getElementById('form_id').value;
selectCategoryByFormId(initialFormId);
switchFormMode(initialFormId);
updateFormDescription(initialFormId);
// 분류 변경 → 양식 필터링
document.getElementById('form_category').addEventListener('change', function() {
filterFormsByCategory(this.value);
});
// 재직증명서 기존 데이터 복원
if (isCertForm) {
const certContent = @json($approval->content ?? []);