feat: [approvals] 양식 선택 2단계 구조 (분류 → 양식)
- 1단계: 분류 선택 (일반/인사·근태/증명서/품의/재무) - 2단계: 해당 분류 내 양식만 필터링하여 표시 - 분류별 아이콘 표시 (📄📜📋💰👤) - edit 화면에서 기존 양식의 분류 자동 선택
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 ?? []);
|
||||
|
||||
Reference in New Issue
Block a user