20260205 업데이트(컨텐츠 페이지 연결)
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { w2grid, w2ui, w2popup, w2alert, w2confirm }
|
||||
from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
from 'https://cdn.jsdelivr.net/gh/vitmalina/w2ui@master/dist/w2ui.es6.min.js'
|
||||
|
||||
|
||||
const OK_YN_ITEMS = [
|
||||
{ id: 'Y', text: '승인' },
|
||||
{ id: 'N', text: '미승인' }
|
||||
]
|
||||
|
||||
/* -------------------------------------------------
|
||||
공통 유틸
|
||||
@@ -19,6 +25,14 @@ function fmtPrice(val) {
|
||||
: v.toLocaleString(undefined, { minimumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
function forceEndEdit(grid) {
|
||||
if (!grid) return
|
||||
if (grid.last && grid.last.inEdit) {
|
||||
// ✅ W2UI 표준 방식으로 편집을 종료해야 데이터가 정상적으로 커밋됩니다.
|
||||
grid.editField(null)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
서비스 등록 팝업
|
||||
------------------------------------------------- */
|
||||
@@ -196,7 +210,7 @@ function createServiceGrid() {
|
||||
text: '합계면적(m²)',
|
||||
size: '110px',
|
||||
attr: 'align=right',
|
||||
editable: { type: 'int' },
|
||||
editable: false,
|
||||
render: r => fmtNum(r.sum_area)
|
||||
},
|
||||
{
|
||||
@@ -207,20 +221,22 @@ function createServiceGrid() {
|
||||
},
|
||||
{
|
||||
field: 'ok_yn',
|
||||
text: '승인여부',
|
||||
size: '80px',
|
||||
attr: 'align=center',
|
||||
text: '사용승인',
|
||||
size: '110px',
|
||||
editable: {
|
||||
type: 'combo',
|
||||
items: [
|
||||
{ id: 'N', text: '미승인' },
|
||||
{ id: 'Y', text: '승인' }
|
||||
],
|
||||
type: 'list',
|
||||
items: OK_YN_ITEMS,
|
||||
showAll: true,
|
||||
openOnFocus: true
|
||||
},
|
||||
render(record) {
|
||||
if (record.ok_yn === 'Y') return '승인'
|
||||
return '미승인'
|
||||
const val = record.ok_yn
|
||||
// 1. 객체인 경우 (W2UI list editor 또는 정규화 결과)
|
||||
if (val && typeof val === 'object') return val.text || ''
|
||||
// 2. 문자열/기타 값인 경우 (ID 매칭 시도)
|
||||
const rawId = String(val || '').trim().toUpperCase()
|
||||
const item = OK_YN_ITEMS.find(i => String(i.id).toUpperCase() === rawId)
|
||||
return item ? item.text : (val || '')
|
||||
}
|
||||
},
|
||||
{ field: 'rmks', text: '비고', size: '150px', editable: { type: 'text' } }
|
||||
@@ -232,17 +248,45 @@ function createServiceGrid() {
|
||||
일반 필드 처리 (date 제외)
|
||||
----------------------------- */
|
||||
onChange(event) {
|
||||
const g = this
|
||||
|
||||
// 🔥 현재 편집 세션 강제 종료
|
||||
if (g.last?.inEdit) {
|
||||
g.editField(null)
|
||||
}
|
||||
event.onComplete = (ev) => {
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
const field = g.columns[ev.column].field
|
||||
if (!g || ev.column == null) return
|
||||
|
||||
const field = g.columns[ev.column]?.field
|
||||
if (!field) return
|
||||
|
||||
let val = ev.value_new
|
||||
if (typeof val === 'object' && val !== null) val = val.id
|
||||
|
||||
// ✅ 사용승인 전용 처리
|
||||
if (field === 'ok_yn') {
|
||||
// 화면은 객체
|
||||
g.set(ev.recid, { ok_yn: val })
|
||||
|
||||
// 🔥 변경사항은 id 기준으로 확정
|
||||
g.mergeChanges(ev.recid, {
|
||||
ok_yn: val?.id || null
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 나머지 컬럼
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
val = val.id
|
||||
}
|
||||
|
||||
g.set(ev.recid, { [field]: val })
|
||||
g.mergeChanges(ev.recid, { [field]: val })
|
||||
|
||||
if (['itm_qty','dis_rt','add_area'].includes(field)) {
|
||||
recalcRow(ev.recid)
|
||||
// ✅ 재계산 (50ms 지연하여 포커스 이슈 및 그리드 리프레시 충돌 방지)
|
||||
if (['itm_qty', 'dis_rt', 'add_area', 'area'].includes(field)) {
|
||||
setTimeout(() => recalcRow(ev.recid), 50)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -251,35 +295,29 @@ function createServiceGrid() {
|
||||
날짜 전용 처리 (핵심)
|
||||
----------------------------- */
|
||||
onEditField(event) {
|
||||
if (event.field !== 'end_dt') return;
|
||||
if (event.field !== 'end_dt') return
|
||||
|
||||
event.onComplete = () => {
|
||||
const g = w2ui.serviceGrid;
|
||||
const recid = event.recid;
|
||||
const g = w2ui.serviceGrid
|
||||
const recid = event.recid
|
||||
|
||||
const input = document.querySelector(
|
||||
`#grid_${g.name}_edit input`
|
||||
);
|
||||
if (!input) return;
|
||||
)
|
||||
if (!input) return
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
// 값만 반영
|
||||
g.set(recid, { end_dt: input.value })
|
||||
|
||||
// 🔥 날짜는 문자열 그대로 즉시 확정
|
||||
g.set(recid, {
|
||||
end_dt: input.value // YYYY-MM-DD
|
||||
});
|
||||
|
||||
// 🔥 w2ui 방식으로 edit 종료
|
||||
input.blur();
|
||||
|
||||
g.refreshRow(recid);
|
||||
});
|
||||
};
|
||||
// 🔥 아무 것도 하지 마라
|
||||
// w2ui가 blur 시 자동 종료
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
상품 목록
|
||||
------------------------------------------------- */
|
||||
@@ -288,9 +326,9 @@ function createProductList() {
|
||||
new w2grid({
|
||||
name: 'productList',
|
||||
box: '#productList',
|
||||
url: '/kngil/bbs/adm_product_popup.php',
|
||||
url: '/kngil/bbs/adm_product_popup.php',
|
||||
columns: [
|
||||
{ field: 'itm_nm', text: '상품명', size: '120px' },
|
||||
{ field: 'itm_nm', text: '상품명', size: '120px' },
|
||||
{
|
||||
field: 'area',
|
||||
text: '제공량',
|
||||
@@ -363,7 +401,7 @@ function addServiceFromProduct(p) {
|
||||
sum_amt: Math.floor(unit * qty * 1.1),
|
||||
|
||||
end_dt: endDt,
|
||||
ok_yn: 'N',
|
||||
ok_yn: OK_YN_ITEMS.find(o => o.id === 'N'),
|
||||
rmks: '',
|
||||
|
||||
_new: true
|
||||
@@ -388,14 +426,14 @@ function deleteSelectedRows() {
|
||||
}
|
||||
|
||||
const recid = sel[0]
|
||||
const row = g.get(recid)
|
||||
const row = g.get(recid)
|
||||
|
||||
if (!row || !row.sq_no) {
|
||||
w2alert('삭제할 수 없는 항목입니다.')
|
||||
return
|
||||
}
|
||||
|
||||
if (row.ok_yn === 'Y') {
|
||||
if (row.ok_yn?.id === 'Y') {
|
||||
w2alert('승인된 항목은 삭제할 수 없습니다.')
|
||||
return
|
||||
}
|
||||
@@ -407,7 +445,7 @@ function deleteSelectedRows() {
|
||||
}
|
||||
|
||||
function deleteServiceImmediately(row) {
|
||||
console.log({
|
||||
console.log({
|
||||
action: 'delete',
|
||||
member_id: row.member_id,
|
||||
sq_no: row.sq_no
|
||||
@@ -422,23 +460,23 @@ function deleteServiceImmediately(row) {
|
||||
sq_no: row.sq_no
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
w2alert('삭제되었습니다.')
|
||||
w2ui.serviceGrid.remove(row.recid)
|
||||
calcSummary()
|
||||
} else {
|
||||
w2alert(res.message || '삭제 실패')
|
||||
}
|
||||
})
|
||||
.catch(() => w2alert('서버 오류'))
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
w2alert('삭제되었습니다.')
|
||||
w2ui.serviceGrid.remove(row.recid)
|
||||
calcSummary()
|
||||
} else {
|
||||
w2alert(res.message || '삭제 실패')
|
||||
}
|
||||
})
|
||||
.catch(() => w2alert('서버 오류'))
|
||||
}
|
||||
|
||||
|
||||
function isServiceItem(r) {
|
||||
return r.itm_cd && r.itm_cd.startsWith('ZET01')
|
||||
|| r.itm_nm === '서비스'
|
||||
|| r.itm_nm === '서비스'
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
@@ -455,62 +493,61 @@ function loadExistingPurchase(memberId, buyDate) {
|
||||
buy_date: buyDate
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
|
||||
if (json.status !== 'success') {
|
||||
w2alert(json.message || '조회 실패')
|
||||
return
|
||||
}
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
g.clear()
|
||||
|
||||
json.records.forEach((r, i) => {
|
||||
g.add({
|
||||
recid: i + 1,
|
||||
member_id: memberId,
|
||||
sq_no: r.sq_no,
|
||||
|
||||
itm_cd: r.itm_cd,
|
||||
itm_nm: r.itm_nm,
|
||||
itm_area: Number(r.itm_area || 0),
|
||||
add_area: Number(r.add_area || 0),
|
||||
itm_amt: Number(r.itm_amt),
|
||||
itm_qty: Number(r.itm_qty),
|
||||
|
||||
// area
|
||||
area: isServiceItem(r)
|
||||
? 1
|
||||
: (r.itm_qty ? Number(r.itm_area) / Number(r.itm_qty) : 0),
|
||||
|
||||
dis_rt: Number(r.dis_rt || 0), // 문자열 → 숫자
|
||||
supply: Number(r.buy_amt),
|
||||
vat_amt: Number(r.vat_amt),
|
||||
sum_amt: Number(r.sum_amt),
|
||||
|
||||
end_dt: r.end_dt || '',
|
||||
ok_yn: r.ok_yn || 'N',
|
||||
rmks: r.rmks ?? '', // undefined 방지
|
||||
|
||||
_existing: true
|
||||
})
|
||||
|
||||
g.set(i + 1, {
|
||||
w2ui: { style: 'background:#f3f3f3;color:#555' }
|
||||
})
|
||||
})
|
||||
|
||||
g.refresh()
|
||||
// 🔥 모든 행 재계산
|
||||
g.records.forEach(r => {
|
||||
if (!r._deleted) {
|
||||
recalcRow(r.recid)
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
if (json.status !== 'success') {
|
||||
w2alert(json.message || '조회 실패')
|
||||
return
|
||||
}
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
g.clear()
|
||||
const records = normalizeServiceOkYn(json.records)
|
||||
records.forEach((r, i) => {
|
||||
g.add({
|
||||
recid: i + 1,
|
||||
member_id: memberId,
|
||||
sq_no: r.sq_no,
|
||||
|
||||
itm_cd: r.itm_cd,
|
||||
itm_nm: r.itm_nm,
|
||||
itm_area: Number(r.itm_area || 0),
|
||||
add_area: Number(r.add_area || 0),
|
||||
itm_amt: Number(r.itm_amt),
|
||||
itm_qty: Number(r.itm_qty),
|
||||
|
||||
// area
|
||||
area: isServiceItem(r)
|
||||
? 1
|
||||
: (r.itm_qty ? Number(r.itm_area) / Number(r.itm_qty) : 0),
|
||||
|
||||
dis_rt: Number(r.dis_rt || 0), // 문자열 → 숫자
|
||||
supply: Number(r.buy_amt),
|
||||
vat_amt: Number(r.vat_amt),
|
||||
sum_amt: Number(r.sum_amt),
|
||||
|
||||
end_dt: r.end_dt || '',
|
||||
ok_yn: r.ok_yn, // 이미 normalizeServiceOkYn에서 객체로 변환됨
|
||||
rmks: r.rmks ?? '', // undefined 방지
|
||||
|
||||
_existing: true
|
||||
})
|
||||
|
||||
g.set(i + 1, {
|
||||
w2ui: { style: 'background:#f3f3f3;color:#555' }
|
||||
})
|
||||
})
|
||||
|
||||
g.refresh()
|
||||
// 🔥 모든 행 재계산
|
||||
g.records.forEach(r => {
|
||||
if (!r._deleted) {
|
||||
recalcRow(r.recid)
|
||||
}
|
||||
})
|
||||
calcSummary()
|
||||
})
|
||||
calcSummary()
|
||||
})
|
||||
.catch(() => w2alert('서버 오류'))
|
||||
.catch(() => w2alert('서버 오류'))
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
@@ -522,11 +559,11 @@ function recalcRow(recid) {
|
||||
const r = g.get(recid)
|
||||
if (!r) return
|
||||
|
||||
const qty = Number(r.itm_qty || 0)
|
||||
const qty = Number(r.itm_qty || 0)
|
||||
const unit = Number(r.itm_amt || 0)
|
||||
const rate = Number(r.dis_rt || 0)
|
||||
const area = Number(r.area || 0)
|
||||
const add = Number(r.add_area || 0)
|
||||
const add = Number(r.add_area || 0)
|
||||
|
||||
// 🔥 적용면적 = 제공량 * 수량
|
||||
r.itm_area = area * qty
|
||||
@@ -563,13 +600,13 @@ function calcSummary() {
|
||||
w2ui.serviceGrid.records.forEach(r => {
|
||||
if (r._deleted) return
|
||||
supply += Number(r.supply || 0)
|
||||
vat += Number(r.vat_amt || 0)
|
||||
total += Number(r.sum_amt || 0)
|
||||
vat += Number(r.vat_amt || 0)
|
||||
total += Number(r.sum_amt || 0)
|
||||
})
|
||||
|
||||
document.getElementById('sumSupply').innerText = supply.toLocaleString()
|
||||
document.getElementById('sumVat').innerText = vat.toLocaleString()
|
||||
document.getElementById('sumTotal').innerText = total.toLocaleString()
|
||||
document.getElementById('sumVat').innerText = vat.toLocaleString()
|
||||
document.getElementById('sumTotal').innerText = total.toLocaleString()
|
||||
}
|
||||
|
||||
function saveService(ctx) {
|
||||
@@ -582,29 +619,33 @@ function saveService(ctx) {
|
||||
|
||||
const g = w2ui.serviceGrid
|
||||
|
||||
// 🔥🔥🔥 핵심: 현재 편집중인 셀 강제 반영
|
||||
g.save()
|
||||
// 🔥🔥🔥 핵심 1: 편집중인 셀 강제 종료
|
||||
g.finishEditing?.()
|
||||
g.save?.()
|
||||
|
||||
const items = g.records.map(r => ({
|
||||
sq_no: r.sq_no || null,
|
||||
itm_cd: r.itm_cd,
|
||||
itm_area: r.itm_area,
|
||||
add_area: r.add_area || 0,
|
||||
itm_amt: r.itm_amt,
|
||||
itm_qty: r.itm_qty,
|
||||
const items = g.records
|
||||
.filter(r => !r._deleted)
|
||||
.map(r => ({
|
||||
sq_no: r.sq_no || null,
|
||||
itm_cd: r.itm_cd,
|
||||
itm_area: r.itm_area,
|
||||
add_area: r.add_area || 0,
|
||||
itm_amt: r.itm_amt,
|
||||
itm_qty: r.itm_qty,
|
||||
|
||||
dis_rt: r.dis_rt,
|
||||
buy_amt: r.supply,
|
||||
vat_amt: r.vat_amt,
|
||||
sum_amt: r.sum_amt,
|
||||
end_dt: dateToYMD(r.end_dt),
|
||||
ok_yn: r.ok_yn || 'N',
|
||||
rmks: r.rmks,
|
||||
dis_rt: r.dis_rt,
|
||||
buy_amt: r.supply,
|
||||
vat_amt: r.vat_amt,
|
||||
sum_amt: r.sum_amt,
|
||||
end_dt: dateToYMD(r.end_dt),
|
||||
ok_yn: r.ok_yn?.id || 'N',
|
||||
rmks: r.rmks,
|
||||
|
||||
_new: r._new || false,
|
||||
_existing: r._existing || false,
|
||||
_deleted: r._deleted || false
|
||||
}))
|
||||
// 🔥 상태 플래그 유지
|
||||
_new: !!r._new,
|
||||
_existing: !!r._existing,
|
||||
_deleted: !!r._deleted
|
||||
}))
|
||||
|
||||
fetch('/kngil/bbs/adm_service.php', {
|
||||
method: 'POST',
|
||||
@@ -616,17 +657,20 @@ function saveService(ctx) {
|
||||
items
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
w2alert('저장되었습니다.')
|
||||
loadExistingPurchase(ctx.memberId, buyDate)
|
||||
} else {
|
||||
w2alert(res.message || '저장 실패')
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status === 'success') {
|
||||
w2alert('저장되었습니다.')
|
||||
console.log('changes =', g.getChanges()) // 이제 정상적으로 다 나올 것
|
||||
loadExistingPurchase(ctx.memberId, buyDate)
|
||||
} else {
|
||||
w2alert(res.message || '저장 실패')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
function normalizeDate(val) {
|
||||
if (!val) return null
|
||||
|
||||
@@ -675,4 +719,17 @@ function dateToYMD(val) {
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function normalizeServiceOkYn(records) {
|
||||
return records.map(r => {
|
||||
const yn = typeof r.ok_yn === 'string'
|
||||
? r.ok_yn.trim()
|
||||
: r.ok_yn
|
||||
|
||||
return {
|
||||
...r,
|
||||
ok_yn: OK_YN_ITEMS.find(o => String(o.id).toUpperCase() === String(yn || '').trim().toUpperCase()) || null
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user