/** * 表單邏輯模組 * 處理各表單的資料操作、驗證和提交 */ const API_BASE_URL = '/api'; // ==================== 常數映射 ==================== export const categoryMap = { '01': '技術職', '02': '管理職', '03': '業務職', '04': '行政職' }; export const natureMap = { 'FT': '全職', 'PT': '兼職', 'CT': '約聘', 'IN': '實習' }; export const jobCategoryMap = { 'MGR': '管理職', 'TECH': '技術職', 'SALE': '業務職', 'ADMIN': '行政職', 'RD': '研發職', 'PROD': '生產職' }; // ==================== 崗位清單全域變數 ==================== export let positionListData = []; export let currentSortColumn = ''; export let currentSortDirection = 'asc'; // ==================== 崗位基礎資料表單 ==================== export function updateCategoryName() { const category = document.getElementById('positionCategory').value; document.getElementById('positionCategoryName').value = categoryMap[category] || ''; updatePreview(); } export function updateNatureName() { const nature = document.getElementById('positionNature').value; document.getElementById('positionNatureName').value = natureMap[nature] || ''; updatePreview(); } export function changePositionCode() { const currentCode = document.getElementById('positionCode').value; const newCode = prompt('請輸入新的崗位編號:', currentCode); if (newCode && newCode !== currentCode) { document.getElementById('positionCode').value = newCode; showToast('崗位編號已更改!'); updatePreview(); } } export function getPositionFormData() { const form = document.getElementById('positionForm'); const formData = new FormData(form); const data = { basicInfo: {}, recruitInfo: {} }; const basicFields = ['positionCode', 'positionName', 'positionCategory', 'positionCategoryName', 'positionNature', 'positionNatureName', 'headcount', 'positionLevel', 'effectiveDate', 'positionDesc', 'positionRemark']; const recruitFields = ['minEducation', 'requiredGender', 'salaryRange', 'workExperience', 'minAge', 'maxAge', 'jobType', 'recruitPosition', 'jobTitle', 'jobDesc', 'positionReq', 'titleReq', 'majorReq', 'skillReq', 'langReq', 'otherReq', 'superiorPosition', 'recruitRemark']; basicFields.forEach(field => { const value = formData.get(field); if (value) data.basicInfo[field] = value; }); recruitFields.forEach(field => { const value = formData.get(field); if (value) data.recruitInfo[field] = value; }); return data; } export function savePositionAndExit() { const form = document.getElementById('positionForm'); if (!form.checkValidity()) { form.reportValidity(); return; } console.log('Save Position:', getPositionFormData()); showToast('崗位資料已保存!'); } export function savePositionAndNew() { const form = document.getElementById('positionForm'); if (!form.checkValidity()) { form.reportValidity(); return; } console.log('Save Position:', getPositionFormData()); showToast('崗位資料已保存,請繼續新增!'); form.reset(); document.getElementById('effectiveDate').value = new Date().toISOString().split('T')[0]; updatePreview(); } export async function saveToPositionList() { const form = document.getElementById('positionForm'); if (!form.checkValidity()) { form.reportValidity(); return; } const formData = getPositionFormData(); if (!formData.basicInfo.positionCode) { alert('請輸入崗位編號'); return; } if (!formData.basicInfo.positionName) { alert('請輸入崗位名稱'); return; } try { const response = await fetch(API_BASE_URL + '/positions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const result = await response.json(); if (result.success) { showToast(result.message || '崗位已成功儲存至崗位清單!'); setTimeout(() => { switchModule('positionlist'); }, 1500); } else { alert('儲存失敗: ' + (result.error || '未知錯誤')); } } catch (error) { console.error('儲存錯誤:', error); alert('儲存失敗: ' + error.message); } } export function cancelPositionForm() { if (confirm('確定要取消嗎?未保存的資料將會遺失。')) { document.getElementById('positionForm').reset(); updatePreview(); } } // ==================== 職務基礎資料表單 ==================== export function updateJobCategoryName() { const category = document.getElementById('jobCategoryCode').value; document.getElementById('jobCategoryName').value = jobCategoryMap[category] || ''; updatePreview(); } export function changeJobCode() { const currentCode = document.getElementById('jobCode').value; const newCode = prompt('請輸入新的職務編號:', currentCode); if (newCode && newCode !== currentCode) { document.getElementById('jobCode').value = newCode; showToast('職務編號已更改!'); updatePreview(); } } export function getJobFormData() { const form = document.getElementById('jobForm'); const formData = new FormData(form); const data = {}; const fields = ['jobCategoryCode', 'jobCategoryName', 'jobCode', 'jobName', 'jobNameEn', 'jobEffectiveDate', 'jobHeadcount', 'jobSortOrder', 'jobRemark', 'jobLevel']; fields.forEach(field => { const value = formData.get(field); if (value) data[field] = value; }); data.hasAttendanceBonus = document.getElementById('job_hasAttBonus')?.checked || false; data.hasHousingAllowance = document.getElementById('job_hasHouseAllow')?.checked || false; return data; } export async function saveJobToPositionList() { const form = document.getElementById('jobForm'); if (!form.checkValidity()) { form.reportValidity(); return; } const formData = getJobFormData(); if (!formData.jobCode) { alert('請輸入職務代碼'); return; } if (!formData.jobName) { alert('請輸入職務名稱'); return; } const positionData = { basicInfo: { positionCode: formData.jobCode, positionName: formData.jobName, positionCategory: formData.jobCategoryCode || '', effectiveDate: formData.jobEffectiveDate || new Date().toISOString().split('T')[0], headcount: formData.jobHeadcount || 1, positionLevel: formData.jobLevel || '', positionRemark: formData.jobRemark || '' }, recruitInfo: {} }; try { const response = await fetch(API_BASE_URL + '/positions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(positionData) }); const result = await response.json(); if (result.success) { showToast(result.message || '職務已成功儲存至崗位清單!'); setTimeout(() => { switchModule('positionlist'); }, 1500); } else { alert('儲存失敗: ' + (result.error || '未知錯誤')); } } catch (error) { console.error('儲存錯誤:', error); alert('儲存失敗: ' + error.message); } } export function saveJobAndExit() { const form = document.getElementById('jobForm'); if (!form.checkValidity()) { form.reportValidity(); return; } console.log('Save Job:', getJobFormData()); showToast('職務資料已保存!'); } export function saveJobAndNew() { const form = document.getElementById('jobForm'); if (!form.checkValidity()) { form.reportValidity(); return; } console.log('Save Job:', getJobFormData()); showToast('職務資料已保存,請繼續新增!'); form.reset(); document.getElementById('attendanceLabel').textContent = '否'; document.getElementById('housingLabel').textContent = '否'; updatePreview(); } export function cancelJobForm() { if (confirm('確定要取消嗎?未保存的資料將會遺失。')) { document.getElementById('jobForm').reset(); document.getElementById('attendanceLabel').textContent = '否'; document.getElementById('housingLabel').textContent = '否'; updatePreview(); } } // ==================== 崗位描述表單 ==================== export function getJobDescFormData() { const form = document.getElementById('jobDescForm'); const formData = new FormData(form); const data = { basicInfo: {}, positionInfo: {}, responsibilities: {}, requirements: {} }; ['empNo', 'empName', 'positionCode', 'versionDate'].forEach(field => { const el = document.getElementById('jd_' + field); if (el && el.value) data.basicInfo[field] = el.value; }); ['positionName', 'department', 'positionEffectiveDate', 'directSupervisor', 'positionGradeJob', 'reportTo', 'directReports', 'workLocation', 'empAttribute'].forEach(field => { const el = document.getElementById('jd_' + field); if (el && el.value) data.positionInfo[field] = el.value; }); const purpose = document.getElementById('jd_positionPurpose'); if (purpose && purpose.value) data.responsibilities.positionPurpose = purpose.value; const mainResp = document.getElementById('jd_mainResponsibilities'); if (mainResp && mainResp.value) data.responsibilities.mainResponsibilities = mainResp.value; ['education', 'basicSkills', 'professionalKnowledge', 'workExperienceReq', 'otherRequirements'].forEach(field => { const el = document.getElementById('jd_' + field); if (el && el.value) data.requirements[field] = el.value; }); return data; } export async function saveJobDescAndExit() { const formData = getJobDescFormData(); console.log('Save JobDesc:', formData); if (!formData.basicInfo.positionCode) { alert('請輸入崗位代碼'); return; } const requestData = { positionCode: formData.basicInfo.positionCode, positionName: formData.positionInfo.positionName || '', effectiveDate: formData.positionInfo.positionEffectiveDate || new Date().toISOString().split('T')[0], jobDuties: formData.responsibilities.mainResponsibilities || '', requiredSkills: formData.requirements.basicSkills || '', workEnvironment: formData.positionInfo.workLocation || '' }; try { const response = await fetch(API_BASE_URL + '/position-descriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }); const result = await response.json(); if (result.success) { showToast(result.message || '崗位描述已保存!'); setTimeout(() => { switchModule('positionlist'); }, 1000); } else { alert('保存失敗: ' + (result.error || '未知錯誤')); } } catch (error) { console.error('保存錯誤:', error); alert('保存失敗: ' + error.message); } } export async function saveJobDescAndNew() { const formData = getJobDescFormData(); console.log('Save JobDesc:', formData); if (!formData.basicInfo.positionCode) { alert('請輸入崗位代碼'); return; } const requestData = { positionCode: formData.basicInfo.positionCode, positionName: formData.positionInfo.positionName || '', effectiveDate: formData.positionInfo.positionEffectiveDate || new Date().toISOString().split('T')[0], jobDuties: formData.responsibilities.mainResponsibilities || '', requiredSkills: formData.requirements.basicSkills || '', workEnvironment: formData.positionInfo.workLocation || '' }; try { const response = await fetch(API_BASE_URL + '/position-descriptions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }); const result = await response.json(); if (result.success) { showToast(result.message || '崗位描述已保存,請繼續新增!'); document.getElementById('jobDescForm').reset(); document.getElementById('jd_mainResponsibilities').value = '1、\n2、\n3、\n4、'; updatePreview(); } else { alert('保存失敗: ' + (result.error || '未知錯誤')); } } catch (error) { console.error('保存錯誤:', error); alert('保存失敗: ' + error.message); } } export async function saveJobDescToPositionList() { const formData = getJobDescFormData(); if (!formData.basicInfo.positionCode) { alert('請輸入崗位代碼'); return; } const positionData = { basicInfo: { positionCode: formData.basicInfo.positionCode, positionName: formData.positionInfo.positionName || '', effectiveDate: formData.positionInfo.positionEffectiveDate || new Date().toISOString().split('T')[0], positionDesc: formData.responsibilities.mainResponsibilities || '', positionRemark: formData.responsibilities.positionPurpose || '' }, recruitInfo: { minEducation: formData.requirements.education || '', skillReq: formData.requirements.basicSkills || '', workExperience: formData.requirements.workExperienceReq || '', otherReq: formData.requirements.otherRequirements || '' } }; try { const response = await fetch(API_BASE_URL + '/positions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(positionData) }); const result = await response.json(); if (result.success) { showToast(result.message || '崗位描述已成功儲存至崗位清單!'); setTimeout(() => { switchModule('positionlist'); }, 1500); } else { alert('儲存失敗: ' + (result.error || '未知錯誤')); } } catch (error) { console.error('儲存錯誤:', error); alert('儲存失敗: ' + error.message); } } export function cancelJobDescForm() { if (confirm('確定要取消嗎?未保存的資料將會遺失。')) { document.getElementById('jobDescForm').reset(); document.getElementById('jd_mainResponsibilities').value = '1、\n2、\n3、\n4、'; updatePreview(); } } // ==================== 預覽更新 ==================== export function updatePreview() { const activeModuleBtn = document.querySelector('.module-btn.active'); if (!activeModuleBtn) return; const activeModule = activeModuleBtn.dataset.module; let data; if (activeModule === 'position') { data = { module: '崗位基礎資料', ...getPositionFormData() }; } else if (activeModule === 'job') { data = { module: '職務基礎資料', ...getJobFormData() }; } else { data = { module: '崗位描述', ...getJobDescFormData() }; } const previewEl = document.getElementById('jsonPreview'); if (previewEl) { previewEl.textContent = JSON.stringify(data, null, 2); } } // ==================== Toast 訊息 ==================== export function showToast(message) { const toast = document.getElementById('toast'); const toastMessage = document.getElementById('toastMessage'); if (!toast || !toastMessage) { console.warn('Toast elements not found, creating dynamic toast'); const existingToast = document.querySelector('.toast.dynamic-toast'); if (existingToast) existingToast.remove(); const dynamicToast = document.createElement('div'); dynamicToast.className = 'toast dynamic-toast show'; dynamicToast.innerHTML = ` ${message} `; document.body.appendChild(dynamicToast); setTimeout(() => { dynamicToast.classList.remove('show'); setTimeout(() => dynamicToast.remove(), 300); }, 3000); return; } toastMessage.textContent = message; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 3000); } // ==================== 模組切換 ==================== export function switchModule(moduleName) { document.querySelectorAll('.module-btn').forEach(b => { b.classList.remove('active', 'job-active', 'desc-active'); }); document.querySelectorAll('.module-content').forEach(c => c.classList.remove('active')); const targetBtn = document.querySelector(`.module-btn[data-module="${moduleName}"]`); if (targetBtn) { targetBtn.classList.add('active'); if (moduleName === 'job') targetBtn.classList.add('job-active'); if (moduleName === 'jobdesc') targetBtn.classList.add('desc-active'); } const targetModule = document.getElementById('module-' + moduleName); if (targetModule) { targetModule.classList.add('active'); } if (moduleName === 'positionlist') { loadPositionList(); } updatePreview(); } // ==================== 崗位清單功能 ==================== export async function loadPositionList() { try { showToast('正在載入崗位清單...'); const response = await fetch(API_BASE_URL + '/position-list'); const result = await response.json(); if (result.success) { positionListData = result.data; renderPositionList(); showToast('已載入 ' + positionListData.length + ' 筆崗位資料'); } else { alert('載入失敗: ' + (result.error || '未知錯誤')); positionListData = []; renderPositionList(); } } catch (error) { console.error('載入錯誤:', error); alert('載入失敗: ' + error.message); positionListData = []; renderPositionList(); } } export function renderPositionList() { const tbody = document.getElementById('positionListBody'); if (!tbody) return; if (positionListData.length === 0) { tbody.innerHTML = '