- workers 기반 → sso_users 기반 전환 (vacWorkers→vacUsers, /workers→/users) - 휴가 탭: 부서 필터, 이름 검색, balance_type 뱃지, 장기근속 제외 체크박스 - 배정 모달: balance_type/expires_at 필드 추가, 사용자 부서별 optgroup - 부서 탭: 팀장 표시/편집, 승인권한 CRUD - 연차 설정 탭/JS 신규: 기본연차·장기근속·이월연차 설정 UI - API 미완성 대응: 필드명 폴백(worker_id→user_id), 404 graceful degradation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
8.9 KiB
JavaScript
159 lines
8.9 KiB
JavaScript
/* ===== Vacation Settings ===== */
|
|
let vacSettingsLoaded = false, vacSettingsData = {};
|
|
|
|
async function loadVacationSettingsTab() {
|
|
if (vacSettingsLoaded) return;
|
|
await loadVacationSettings();
|
|
vacSettingsLoaded = true;
|
|
}
|
|
|
|
async function loadVacationSettings() {
|
|
const c = document.getElementById('vacSettingsContent');
|
|
try {
|
|
const r = await api('/vacation-settings');
|
|
const arr = r.data || r;
|
|
vacSettingsData = {};
|
|
(Array.isArray(arr) ? arr : []).forEach(s => { vacSettingsData[s.setting_key] = s; });
|
|
renderVacationSettings();
|
|
} catch (e) {
|
|
c.innerHTML = `<div class="bg-white rounded-xl shadow-sm p-8 text-center">
|
|
<i class="fas fa-exclamation-circle text-3xl text-gray-300 mb-3"></i>
|
|
<p class="text-gray-500 text-sm">설정을 불러올 수 없습니다</p>
|
|
<p class="text-gray-400 text-xs mt-1">${escHtml(e.message)}</p>
|
|
</div>`;
|
|
}
|
|
}
|
|
|
|
function _vsVal(key, fallback) {
|
|
const s = vacSettingsData[key];
|
|
return s ? s.setting_value : fallback;
|
|
}
|
|
|
|
function _vsDesc(key) {
|
|
const s = vacSettingsData[key];
|
|
return s && s.description ? s.description : '';
|
|
}
|
|
|
|
function renderVacationSettings() {
|
|
const c = document.getElementById('vacSettingsContent');
|
|
const isAdmin = currentUser && (currentUser.role === 'admin' || currentUser.role === 'system');
|
|
const dis = isAdmin ? '' : 'disabled';
|
|
|
|
c.innerHTML = `
|
|
<div class="space-y-6">
|
|
<!-- 기본 연차 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-5">
|
|
<h3 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-calendar-days text-blue-400 mr-2"></i>기본 연차 설정</h3>
|
|
<div class="grid sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">1년 미만 규칙</label>
|
|
<select id="vs_first_year_rule" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" ${dis}>
|
|
<option value="monthly" ${_vsVal('first_year_rule','monthly')==='monthly'?'selected':''}>월별 발생</option>
|
|
<option value="proportional" ${_vsVal('first_year_rule','monthly')==='proportional'?'selected':''}>비례 배분</option>
|
|
<option value="full" ${_vsVal('first_year_rule','monthly')==='full'?'selected':''}>전체 부여</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">기본 일수</label>
|
|
<input type="number" id="vs_base_days" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('base_days','15')}" min="0" step="1" ${dis}>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">근속 가산 (N년당 1일)</label>
|
|
<input type="number" id="vs_increment_per_years" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('increment_per_years','2')}" min="1" step="1" ${dis}>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">최대 일수</label>
|
|
<input type="number" id="vs_max_days" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('max_days','25')}" min="0" step="1" ${dis}>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 장기근속 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-5">
|
|
<h3 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-award text-emerald-400 mr-2"></i>장기근속 보너스</h3>
|
|
<div class="grid sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">기준 근속년수</label>
|
|
<input type="number" id="vs_long_service_threshold_years" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('long_service_threshold_years','5')}" min="1" step="1" ${dis}>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">보너스 일수</label>
|
|
<input type="number" id="vs_long_service_bonus_days" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('long_service_bonus_days','5')}" min="0" step="1" ${dis}>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">만료 정책</label>
|
|
<select id="vs_long_service_expiry" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" ${dis}>
|
|
<option value="year_end" ${_vsVal('long_service_expiry','year_end')==='year_end'?'selected':''}>연말 만료</option>
|
|
<option value="never" ${_vsVal('long_service_expiry','year_end')==='never'?'selected':''}>만료 없음</option>
|
|
<option value="anniversary" ${_vsVal('long_service_expiry','year_end')==='anniversary'?'selected':''}>입사일 기준</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<label class="flex items-center gap-2 text-sm text-gray-600 cursor-pointer">
|
|
<input type="checkbox" id="vs_long_service_auto_grant" class="rounded" ${_vsVal('long_service_auto_grant','true')==='true'?'checked':''} ${dis}>
|
|
자동 부여
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 이월 연차 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-5">
|
|
<h3 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-rotate text-orange-400 mr-2"></i>이월 연차</h3>
|
|
<div class="grid sm:grid-cols-2 gap-4">
|
|
<div class="flex items-center">
|
|
<label class="flex items-center gap-2 text-sm text-gray-600 cursor-pointer">
|
|
<input type="checkbox" id="vs_carry_over_enabled" class="rounded" ${_vsVal('carry_over_enabled','false')==='true'?'checked':''} ${dis}>
|
|
이월 허용
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">최대 이월 일수</label>
|
|
<input type="number" id="vs_carry_over_max_days" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('carry_over_max_days','5')}" min="0" step="1" ${dis}>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">이월 만료월 (M월말)</label>
|
|
<input type="number" id="vs_carry_over_expiry_month" class="input-field w-full px-3 py-1.5 rounded-lg text-sm" value="${_vsVal('carry_over_expiry_month','3')}" min="1" max="12" step="1" ${dis}>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
${isAdmin ? `
|
|
<div class="flex justify-end">
|
|
<button onclick="saveVacationSettings()" class="px-6 py-2.5 bg-slate-700 text-white rounded-lg hover:bg-slate-800 text-sm font-medium">
|
|
<i class="fas fa-save mr-1.5"></i>설정 저장
|
|
</button>
|
|
</div>` : ''}
|
|
</div>`;
|
|
}
|
|
|
|
async function saveVacationSettings() {
|
|
const fields = {
|
|
first_year_rule: document.getElementById('vs_first_year_rule').value,
|
|
base_days: document.getElementById('vs_base_days').value,
|
|
increment_per_years: document.getElementById('vs_increment_per_years').value,
|
|
max_days: document.getElementById('vs_max_days').value,
|
|
long_service_threshold_years: document.getElementById('vs_long_service_threshold_years').value,
|
|
long_service_bonus_days: document.getElementById('vs_long_service_bonus_days').value,
|
|
long_service_expiry: document.getElementById('vs_long_service_expiry').value,
|
|
long_service_auto_grant: document.getElementById('vs_long_service_auto_grant').checked ? 'true' : 'false',
|
|
carry_over_enabled: document.getElementById('vs_carry_over_enabled').checked ? 'true' : 'false',
|
|
carry_over_max_days: document.getElementById('vs_carry_over_max_days').value,
|
|
carry_over_expiry_month: document.getElementById('vs_carry_over_expiry_month').value,
|
|
};
|
|
|
|
// Only send changed values
|
|
const changes = {};
|
|
for (const [key, val] of Object.entries(fields)) {
|
|
if (_vsVal(key, '') !== val) changes[key] = val;
|
|
}
|
|
if (!Object.keys(changes).length) { showToast('변경된 설정이 없습니다.', 'error'); return; }
|
|
|
|
try {
|
|
await api('/vacation-settings', { method: 'PUT', body: JSON.stringify({ settings: changes }) });
|
|
showToast('설정이 저장되었습니다.');
|
|
vacSettingsLoaded = false;
|
|
await loadVacationSettingsTab();
|
|
} catch (e) { showToast(e.message, 'error'); }
|
|
}
|