feat: [payroll] 급여 등록 모달 금액 입력 콤마 자동 포맷팅
- 숫자 입력 시 천단위 콤마 자동 표시 - 0인 필드에 포커스 시 공백으로 표시, blur 시 0으로 복원 - 수당/공제 동적 행에도 동일하게 적용
This commit is contained in:
@@ -180,21 +180,24 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">기본급</label>
|
||||
<input type="number" id="payrollBaseSalary" name="base_salary" min="0" step="1000" value="0"
|
||||
onchange="recalculate()"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<input type="text" id="payrollBaseSalary" name="base_salary" inputmode="numeric" value="0"
|
||||
oninput="formatMoneyInput(this); recalculate()"
|
||||
onfocus="moneyFocus(this)" onblur="moneyBlur(this)"
|
||||
class="money-input w-full px-3 py-2 border border-gray-300 rounded-lg text-sm text-right focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">고정연장근로수당</label>
|
||||
<input type="number" id="payrollOvertimePay" name="overtime_pay" min="0" step="1000" value="0"
|
||||
onchange="recalculate()"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<input type="text" id="payrollOvertimePay" name="overtime_pay" inputmode="numeric" value="0"
|
||||
oninput="formatMoneyInput(this); recalculate()"
|
||||
onfocus="moneyFocus(this)" onblur="moneyBlur(this)"
|
||||
class="money-input w-full px-3 py-2 border border-gray-300 rounded-lg text-sm text-right focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">상여금</label>
|
||||
<input type="number" id="payrollBonus" name="bonus" min="0" step="1000" value="0"
|
||||
onchange="recalculate()"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<input type="text" id="payrollBonus" name="bonus" inputmode="numeric" value="0"
|
||||
oninput="formatMoneyInput(this); recalculate()"
|
||||
onfocus="moneyFocus(this)" onblur="moneyBlur(this)"
|
||||
class="money-input w-full px-3 py-2 border border-gray-300 rounded-lg text-sm text-right focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 = `
|
||||
<input type="text" placeholder="수당명" value="${name || ''}" class="allowance-name px-3 py-1.5 border border-gray-300 rounded-lg text-sm" style="flex: 1 1 120px;">
|
||||
<input type="number" placeholder="금액" value="${amount || ''}" min="0" step="1000" onchange="recalculate()" class="allowance-amount px-3 py-1.5 border border-gray-300 rounded-lg text-sm" style="flex: 0 0 120px;">
|
||||
<input type="text" inputmode="numeric" placeholder="금액" value="${formattedAmount}" oninput="formatMoneyInput(this); recalculate()" onfocus="moneyFocus(this)" onblur="moneyBlur(this)" class="money-input allowance-amount px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-right" style="flex: 0 0 120px;">
|
||||
<button type="button" onclick="this.parentElement.remove(); recalculate();" class="shrink-0 text-red-400 hover:text-red-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
@@ -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 = `
|
||||
<input type="text" placeholder="공제명" value="${name || ''}" class="deduction-name px-3 py-1.5 border border-gray-300 rounded-lg text-sm" style="flex: 1 1 120px;">
|
||||
<input type="number" placeholder="금액" value="${amount || ''}" min="0" step="1000" onchange="recalculate()" class="deduction-amount px-3 py-1.5 border border-gray-300 rounded-lg text-sm" style="flex: 0 0 120px;">
|
||||
<input type="text" inputmode="numeric" placeholder="공제명" value="${name || ''}" class="deduction-name px-3 py-1.5 border border-gray-300 rounded-lg text-sm" style="flex: 1 1 120px;">
|
||||
<input type="text" inputmode="numeric" placeholder="금액" value="${formattedAmount}" oninput="formatMoneyInput(this); recalculate()" onfocus="moneyFocus(this)" onblur="moneyBlur(this)" class="money-input deduction-amount px-3 py-1.5 border border-gray-300 rounded-lg text-sm text-right" style="flex: 0 0 120px;">
|
||||
<button type="button" onclick="this.parentElement.remove(); recalculate();" class="shrink-0 text-red-400 hover:text-red-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user