From 5314777c461b5848b3ed2ebe2a7a93ca486e83ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Fri, 27 Feb 2026 10:00:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[payroll]=20=EA=B8=89=EC=97=AC=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=AA=A8=EB=8B=AC=20=EA=B8=88=EC=95=A1=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=BD=A4=EB=A7=88=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 숫자 입력 시 천단위 콤마 자동 표시 - 0인 필드에 포커스 시 공백으로 표시, blur 시 0으로 복원 - 수당/공제 동적 행에도 동일하게 적용 --- resources/views/hr/payrolls/index.blade.php | 87 +++++++++++++++------ 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/resources/views/hr/payrolls/index.blade.php b/resources/views/hr/payrolls/index.blade.php index ef05a3b7..c8eb28c4 100644 --- a/resources/views/hr/payrolls/index.blade.php +++ b/resources/views/hr/payrolls/index.blade.php @@ -180,21 +180,24 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
- +
- +
- +
@@ -384,9 +387,9 @@ function openEditPayrollModal(id, data) { document.getElementById('payrollUserId').disabled = true; document.getElementById('payrollPayYear').value = document.getElementById('payrollYear').value; document.getElementById('payrollPayMonth').value = document.getElementById('payrollMonth').value; - document.getElementById('payrollBaseSalary').value = data.base_salary || 0; - document.getElementById('payrollOvertimePay').value = data.overtime_pay || 0; - document.getElementById('payrollBonus').value = data.bonus || 0; + setMoneyValue(document.getElementById('payrollBaseSalary'), data.base_salary || 0); + setMoneyValue(document.getElementById('payrollOvertimePay'), data.overtime_pay || 0); + setMoneyValue(document.getElementById('payrollBonus'), data.bonus || 0); document.getElementById('payrollNote').value = data.note || ''; // 수당 복원 @@ -416,7 +419,7 @@ function closePayrollModal() { const selected = this.options[this.selectedIndex]; const salary = parseInt(selected.dataset.salary || 0); if (salary > 0) { - document.getElementById('payrollBaseSalary').value = Math.round(salary / 12); + setMoneyValue(document.getElementById('payrollBaseSalary'), Math.round(salary / 12)); recalculate(); } }); @@ -426,9 +429,10 @@ function addAllowanceRow(name, amount) { const container = document.getElementById('allowancesContainer'); const div = document.createElement('div'); div.className = 'flex gap-2 items-center'; + const formattedAmount = amount ? Number(amount).toLocaleString('ko-KR') : ''; div.innerHTML = ` - + @@ -440,9 +444,10 @@ function addDeductionRow(name, amount) { const container = document.getElementById('deductionsContainer'); const div = document.createElement('div'); div.className = 'flex gap-2 items-center'; + const formattedAmount = amount ? Number(amount).toLocaleString('ko-KR') : ''; div.innerHTML = ` - - + + @@ -461,21 +466,21 @@ function doRecalculate() { const allowances = []; document.querySelectorAll('#allowancesContainer > div').forEach(row => { const name = row.querySelector('.allowance-name').value; - const amount = parseFloat(row.querySelector('.allowance-amount').value) || 0; + const amount = parseMoneyValue(row.querySelector('.allowance-amount')); if (name && amount > 0) allowances.push({name, amount}); }); const deductions = []; document.querySelectorAll('#deductionsContainer > div').forEach(row => { const name = row.querySelector('.deduction-name').value; - const amount = parseFloat(row.querySelector('.deduction-amount').value) || 0; + const amount = parseMoneyValue(row.querySelector('.deduction-amount')); if (name && amount > 0) deductions.push({name, amount}); }); const data = { - base_salary: parseFloat(document.getElementById('payrollBaseSalary').value) || 0, - overtime_pay: parseFloat(document.getElementById('payrollOvertimePay').value) || 0, - bonus: parseFloat(document.getElementById('payrollBonus').value) || 0, + base_salary: parseMoneyValue(document.getElementById('payrollBaseSalary')), + overtime_pay: parseMoneyValue(document.getElementById('payrollOvertimePay')), + bonus: parseMoneyValue(document.getElementById('payrollBonus')), allowances: allowances, deductions: deductions, }; @@ -517,19 +522,49 @@ function numberFormat(n) { return Number(n).toLocaleString('ko-KR'); } + // ===== 금액 입력 포맷팅 ===== + function parseMoneyValue(el) { + if (typeof el === 'string') return parseFloat(el.replace(/,/g, '')) || 0; + return parseFloat((el.value || '').replace(/,/g, '')) || 0; + } + + function formatMoneyInput(el) { + const pos = el.selectionStart; + const oldLen = el.value.length; + const raw = el.value.replace(/[^0-9]/g, ''); + const num = parseInt(raw, 10); + el.value = isNaN(num) ? '' : num.toLocaleString('ko-KR'); + const newLen = el.value.length; + const newPos = Math.max(0, pos + (newLen - oldLen)); + el.setSelectionRange(newPos, newPos); + } + + function moneyFocus(el) { + if (parseMoneyValue(el) === 0) el.value = ''; + } + + function moneyBlur(el) { + if (el.value.trim() === '') el.value = '0'; + } + + function setMoneyValue(el, val) { + const num = parseInt(val, 10) || 0; + el.value = num === 0 ? '0' : num.toLocaleString('ko-KR'); + } + // ===== 급여 저장 ===== function submitPayroll() { const allowances = []; document.querySelectorAll('#allowancesContainer > div').forEach(row => { const name = row.querySelector('.allowance-name').value; - const amount = parseFloat(row.querySelector('.allowance-amount').value) || 0; + const amount = parseMoneyValue(row.querySelector('.allowance-amount')); if (name && amount > 0) allowances.push({name, amount}); }); const deductions = []; document.querySelectorAll('#deductionsContainer > div').forEach(row => { const name = row.querySelector('.deduction-name').value; - const amount = parseFloat(row.querySelector('.deduction-amount').value) || 0; + const amount = parseMoneyValue(row.querySelector('.deduction-amount')); if (name && amount > 0) deductions.push({name, amount}); }); @@ -537,9 +572,9 @@ function submitPayroll() { user_id: parseInt(document.getElementById('payrollUserId').value), pay_year: parseInt(document.getElementById('payrollPayYear').value), pay_month: parseInt(document.getElementById('payrollPayMonth').value), - base_salary: parseFloat(document.getElementById('payrollBaseSalary').value) || 0, - overtime_pay: parseFloat(document.getElementById('payrollOvertimePay').value) || 0, - bonus: parseFloat(document.getElementById('payrollBonus').value) || 0, + base_salary: parseMoneyValue(document.getElementById('payrollBaseSalary')), + overtime_pay: parseMoneyValue(document.getElementById('payrollOvertimePay')), + bonus: parseMoneyValue(document.getElementById('payrollBonus')), allowances: allowances.length > 0 ? allowances : null, deductions: deductions.length > 0 ? deductions : null, note: document.getElementById('payrollNote').value || null,