1110 lines
30 KiB
JavaScript
1110 lines
30 KiB
JavaScript
window.InstallWait = window.InstallWait || {
|
|
step: 1,
|
|
customer: null,
|
|
job: null,
|
|
category: 0,
|
|
pickup: [],
|
|
schedule: { date:'', note:'' },
|
|
photos: []
|
|
};
|
|
|
|
const IW_CATEGORY_COLORS = {
|
|
0: 'transparent', // 없음
|
|
1: '#ff4d4f',
|
|
2: '#1677ff',
|
|
3: '#52c41a',
|
|
4: '#fa9614',
|
|
5: '#9254de'
|
|
};
|
|
// 하드코딩
|
|
const IW_CATEGORY_LABELS = {
|
|
0: 'No Category',
|
|
1: 'To do list',
|
|
2: 'Power Wash',
|
|
3: 'Category 3',
|
|
4: 'Category 4',
|
|
5: 'Category 5'
|
|
};
|
|
|
|
/* 슬라이드 이동 (100vw 기준) */
|
|
function goInstallWaitStep(step) {
|
|
const wrapper = document.querySelector('.install-wait-steps-wrapper');
|
|
if (!wrapper) return; // DOM 아직 없으면 그냥 나감
|
|
wrapper.style.transform = `translateX(-${(step - 1) * 100}%)`;
|
|
}
|
|
|
|
/* ============================
|
|
STEP 1
|
|
============================ */
|
|
function initAddInstallWaitModal() {
|
|
|
|
const input = document.getElementById('customerSearchInput');
|
|
const result = document.getElementById('customerSearchResult');
|
|
const btnNext = document.getElementById('btnStep1Next');
|
|
|
|
$('#addInstallWaitModal')
|
|
.one('shown.bs.modal', function () {
|
|
const input = document.getElementById('customerSearchInput');
|
|
if (input) input.focus();
|
|
});
|
|
|
|
btnNext.onclick = () => {
|
|
if (!InstallWait.customer) return;
|
|
goInstallWaitStep(2);
|
|
initInstallWaitStep2();
|
|
};
|
|
|
|
let timer = null;
|
|
|
|
input.addEventListener('input', function () {
|
|
const keyword = this.value.trim();
|
|
clearTimeout(timer);
|
|
|
|
if (keyword.length < 2) {
|
|
result.innerHTML = '';
|
|
btnNext.disabled = true;
|
|
return;
|
|
}
|
|
|
|
timer = setTimeout(() => {
|
|
api('search_customer', { keyword }, rows => {
|
|
|
|
if (!rows || !rows.length) {
|
|
result.innerHTML = `<div class="text-muted text-center py-3">No customer found<br>The customer may already be in the wait list or already assigned.</div>`;
|
|
return;
|
|
}
|
|
|
|
result.innerHTML = rows.map(r => `
|
|
<div class="p-2 border-bottom customer-row"
|
|
data-uid="${r.c_uid}"
|
|
data-account="${r.c_accountno}"
|
|
data-name="${r.c_name}"
|
|
data-address="${r.c_address}"
|
|
data-city="${r.c_city}"
|
|
data-postal="${r.c_postal}"
|
|
data-phone="${r.c_phone || ''}">
|
|
<div class="fw-bold">${r.c_accountno} - ${r.c_name}</div>
|
|
<div class="small text-muted">${r.c_address}, ${r.c_city}</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
bindCustomerRowEvents();
|
|
});
|
|
}, 300);
|
|
});
|
|
|
|
|
|
}
|
|
|
|
function bindCustomerRowEvents() {
|
|
|
|
const btnNext = document.getElementById('btnStep1Next');
|
|
let selected = null;
|
|
|
|
document.querySelectorAll('.customer-row').forEach(row => {
|
|
row.onclick = function () {
|
|
|
|
if (selected === this) {
|
|
this.classList.remove('selected');
|
|
selected = null;
|
|
InstallWait.customer = null;
|
|
btnNext.disabled = true;
|
|
btnNext.classList.remove('active-step');
|
|
return;
|
|
}
|
|
|
|
document.querySelectorAll('.customer-row').forEach(r => r.classList.remove('selected'));
|
|
|
|
this.classList.add('selected');
|
|
selected = this;
|
|
|
|
InstallWait.customer = {
|
|
uid: this.dataset.uid,
|
|
account: this.dataset.account,
|
|
name: this.dataset.name,
|
|
address: this.dataset.address,
|
|
city: this.dataset.city,
|
|
postal: this.dataset.postal,
|
|
phone: this.dataset.phone
|
|
};
|
|
|
|
btnNext.disabled = false;
|
|
btnNext.classList.add('active-step');
|
|
};
|
|
});
|
|
}
|
|
|
|
/* ============================
|
|
STEP 2
|
|
============================ */
|
|
function syncCustomerFromStep2() {
|
|
const map = {
|
|
account: 'iwCustomerAccount',
|
|
name: 'iwCustomerName',
|
|
address: 'iwCustomerAddress',
|
|
city: 'iwCustomerCity',
|
|
postal: 'iwCustomerPostal',
|
|
phone: 'iwCustomerPhone'
|
|
};
|
|
|
|
Object.keys(map).forEach(k => {
|
|
InstallWait.customer[k] =
|
|
document.getElementById(map[k]).value.trim();
|
|
});
|
|
}
|
|
|
|
function syncCustomerToInputs() {
|
|
const c = InstallWait.customer || {};
|
|
iwCustomerAccount.value = c.account || '';
|
|
iwCustomerName.value = c.name || '';
|
|
iwCustomerAddress.value = c.address || '';
|
|
iwCustomerCity.value = c.city || '';
|
|
iwCustomerPostal.value = c.postal || '';
|
|
iwCustomerPhone.value = c.phone || '';
|
|
}
|
|
|
|
function initInstallWaitStep2() {
|
|
|
|
syncCustomerToInputs();
|
|
|
|
document.getElementById('btnStep2Back').onclick = () => {
|
|
goInstallWaitStep(1);
|
|
};
|
|
|
|
bindJobSelection();
|
|
bindStep2Next();
|
|
}
|
|
|
|
function bindJobSelection() {
|
|
|
|
const cards = document.querySelectorAll('.iw-job-card');
|
|
const btnNext = btnStep2Next;
|
|
let selected = null;
|
|
|
|
cards.forEach(card => {
|
|
card.onclick = () => {
|
|
|
|
if (selected === card) {
|
|
card.classList.remove('active');
|
|
selected = null;
|
|
InstallWait.job = null;
|
|
btnNext.disabled = true;
|
|
return;
|
|
}
|
|
|
|
cards.forEach(c => c.classList.remove('active'));
|
|
card.classList.add('active');
|
|
|
|
selected = card;
|
|
InstallWait.job = { type: card.dataset.job };
|
|
|
|
btnNext.disabled = false;
|
|
btnNext.classList.add('active-step');
|
|
};
|
|
});
|
|
}
|
|
|
|
function bindStep2Next() {
|
|
|
|
btnStep2Next.onclick = () => {
|
|
if (!InstallWait.job) return;
|
|
|
|
syncCustomerFromStep2();
|
|
const memberUid = document.getElementById('installWaitContext').dataset.memberUid || '';
|
|
api('install_wait_create_draft', {
|
|
c_uid: InstallWait.customer.uid,
|
|
job_type: InstallWait.job.type,
|
|
account: InstallWait.customer.account,
|
|
name: InstallWait.customer.name,
|
|
address: InstallWait.customer.address,
|
|
city: InstallWait.customer.city,
|
|
postal: InstallWait.customer.postal,
|
|
phone: InstallWait.customer.phone,
|
|
member_uid: memberUid
|
|
}, res => {
|
|
|
|
if (!res || !res.ok || !res.iw_uid) return;
|
|
|
|
InstallWait.iw_uid = res.iw_uid;
|
|
|
|
goInstallWaitStep(3);
|
|
initInstallWaitStep3();
|
|
});
|
|
};
|
|
}
|
|
|
|
/* ============================
|
|
STEP 3
|
|
============================ */
|
|
function initInstallWaitStep3() {
|
|
const backBtn = document.getElementById('btnStep3Back');
|
|
backBtn.onclick = () => {
|
|
// id 없으면 그냥 back
|
|
if (!InstallWait.iw_uid) {
|
|
goInstallWaitStep(2);
|
|
return;
|
|
}
|
|
|
|
const iw_uid = InstallWait.iw_uid;
|
|
if (!confirm('Delete this install request?')) return;
|
|
|
|
btnStep3Back.disabled = true;
|
|
|
|
api('install_wait_delete', { iw_uid }, (res) => {
|
|
btnStep3Back.disabled = false;
|
|
|
|
if (!res || !res.ok) {
|
|
// alert(res?.msg || 'Delete failed');
|
|
showPopupMessage(res?.msg || 'Delete failed', 'error', 1500);
|
|
return;
|
|
} else {
|
|
showPopupMessage('Successfully Deleted', 'success', 1500);
|
|
}
|
|
|
|
const rowEl = btnStep3Back.closest('.install-wait-row');
|
|
if (rowEl) rowEl.remove();
|
|
|
|
// 로컬 상태 초기화
|
|
InstallWait.iw_uid = null;
|
|
InstallWait.pickup = [];
|
|
InstallWait.schedule = { date:'', note:'' };
|
|
InstallWait.photos = [];
|
|
|
|
// Step3 UI 잔상 제거
|
|
const area = document.getElementById('iwStep3ContainerArea');
|
|
if (area) area.innerHTML = '';
|
|
|
|
goInstallWaitStep(2);
|
|
initInstallWaitStep2();
|
|
return;
|
|
});
|
|
};
|
|
|
|
// Complete 버튼
|
|
btnStep3Complete.onclick = () => {
|
|
|
|
if (!InstallWait.iw_uid) {
|
|
// alert('Invalid install wait');
|
|
showPopupMessage('Invalid install wait', 'error', 1500);
|
|
return;
|
|
}
|
|
|
|
// 컨테이너 선택여부
|
|
if (!validateContainersBeforeComplete()) {
|
|
return;
|
|
}
|
|
|
|
// set parameter
|
|
const twoOn = document.getElementById('btnTwoPerson').classList.contains('iw-toggle-on');
|
|
const recOn = document.getElementById('btnRecurring').classList.contains('iw-toggle-on');
|
|
const oilOn = document.getElementById('btnNeedOilPickup').classList.contains('iw-toggle-on');
|
|
const payload = {
|
|
iw_uid: InstallWait.iw_uid,
|
|
request_at: document.getElementById('iwRequestDate').value || null,
|
|
due_date: document.getElementById('iwScheduleDate').value || null,
|
|
lock_date: document.getElementById('iwLockDate').checked ? 1 : 0,
|
|
request_by: document.getElementById('iwRequestBy').value || null,
|
|
request_note: document.getElementById('iwRequestNote').value || null,
|
|
cs_note: document.getElementById('iwCsNote').value || null,
|
|
work_note: document.getElementById('iwWorkNote').value || null,
|
|
person_cnt: twoOn ? 2 : null,
|
|
person_reason: twoOn ? document.getElementById('iwTwoPersonReason').value : null,
|
|
scheduled: recOn ? 1 : 0,
|
|
scheduled_cycle: recOn ? document.getElementById('iwRecurringDays').value: 0,
|
|
need_oil: oilOn ? 1 : 0,
|
|
category: InstallWait.category || 0
|
|
};
|
|
btnStep3Complete.disabled = true;
|
|
|
|
// 1단계: install_wait complete
|
|
api('install_wait_complete', payload, (res) => {
|
|
|
|
if (!res || !res.ok) {
|
|
btnStep3Complete.disabled = false;
|
|
console.error(res);
|
|
//alert('Failed to complete install request');
|
|
showPopupMessage(res?.msg || JSON.stringify(res) || 'Save failed', 'error', 1500);
|
|
return;
|
|
}
|
|
|
|
// 2단계: 사진 업로드 있을 때
|
|
const files = document.getElementById('iwPhotos').files;
|
|
if (!files || files.length === 0) {
|
|
showPopupMessage('Successfully Saved', 'success', 1000);
|
|
finishInstallWaitComplete();
|
|
return;
|
|
}
|
|
const memberUid = document.getElementById('installWaitContext').dataset.memberUid || '';
|
|
|
|
const fd = new FormData();
|
|
fd.append('actionStr', 'ADDIMAGE');
|
|
fd.append('i_type', 'install_order');
|
|
fd.append('i_customeruid', InstallWait.customer.uid);
|
|
fd.append('i_memberuid', memberUid);
|
|
fd.append('i_createdby', memberUid);
|
|
fd.append('i_note', 'Before Install');
|
|
fd.append('i_sourceuid', InstallWait.iw_uid);
|
|
// 파일명 순서대로
|
|
[...files].sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }))
|
|
.forEach(file => {
|
|
fd.append('upload_file[]', file);
|
|
});
|
|
|
|
fetch('/lib/user_process.php', {
|
|
method: 'POST',
|
|
body: fd
|
|
})
|
|
.then(() => {
|
|
showPopupMessage('Successfully Saved', 'success', 1000);
|
|
finishInstallWaitComplete();
|
|
})
|
|
.catch(() => {
|
|
//alert('Install completed, but image upload failed');
|
|
showPopupMessage('Saved (photo failed)', 'error', 1500);
|
|
finishInstallWaitComplete();
|
|
});
|
|
});
|
|
};
|
|
|
|
InstallWait.pickup = InstallWait.pickup || [];
|
|
InstallWait.category = 0;
|
|
|
|
setStep3Title();
|
|
renderStep3ActionBoxes();
|
|
loadCurrentContainersIfNeeded();
|
|
renderCategorySelector();
|
|
bindStep3Toggles();
|
|
fillRequestBySelect('Customer');
|
|
initInstallWaitDatePickers();
|
|
}
|
|
|
|
function setStep3Title() {
|
|
const jobLabel = InstallWait.job?.type || '';
|
|
const acct = InstallWait.customer?.account ? ` of ${InstallWait.customer.name} (${InstallWait.customer.account})` : '';
|
|
const createdBy = InstallWait.job?.createdBy || '';
|
|
const titleEl = document.getElementById('iwStep3Title');
|
|
if (titleEl) titleEl.innerHTML = `Step 3 of 3 - ${jobLabel}${acct} Detail
|
|
${createdBy ? `
|
|
<small class="text-muted ms-2">
|
|
created by ${createdBy}
|
|
</small>
|
|
` : ''}
|
|
`;
|
|
}
|
|
|
|
function renderStep3ActionBoxes() {
|
|
|
|
const area = document.getElementById('iwStep3ContainerArea');
|
|
area.innerHTML = '';
|
|
|
|
const jobType = InstallWait.job?.type;
|
|
if (!jobType) return;
|
|
|
|
// Row 1 : CURRENT | PICKUP
|
|
if (['PICKUP','EXCHANGE','INSTALL','RELOCATE','CLEAN'].includes(jobType)) {
|
|
area.insertAdjacentHTML('beforeend', `
|
|
<div class="iw-action-row">
|
|
${renderActionBox('CURRENT')}
|
|
<div><i class="bi bi-arrow-left-right"></i></div>
|
|
${renderActionBox('PICKUP')}
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
// Row 2 : SELECT | INSTALL
|
|
if (['INSTALL','EXCHANGE'].includes(jobType)) {
|
|
area.insertAdjacentHTML('beforeend', `
|
|
<div class="iw-action-row">
|
|
${renderActionBox('SELECT')}
|
|
<div><i class="bi bi-arrow-left-right"></i></div>
|
|
${renderActionBox('INSTALL')}
|
|
</div>
|
|
`);
|
|
}
|
|
bindSelectInstallEvents();
|
|
}
|
|
|
|
function renderActionBox(action) {
|
|
|
|
const icon = {
|
|
CURRENT: 'bi-archive',
|
|
PICKUP: 'bi-dash-circle',
|
|
SELECT: 'bi-plus-square',
|
|
INSTALL: 'bi-plus-circle',
|
|
RELOCATE: 'bi-arrow-left-right'
|
|
}[action] || 'bi-box';
|
|
|
|
const colorClass =
|
|
action === 'PICKUP' ? 'pickup'
|
|
: action === 'INSTALL' ? 'install'
|
|
: '';
|
|
|
|
let content = '';
|
|
|
|
// CURRENT
|
|
if (action === 'CURRENT') {
|
|
content = `<div class="text-muted small">Loading...</div>`;
|
|
}
|
|
|
|
// SELECT 전용 UI
|
|
if (action === 'SELECT') {
|
|
content = renderSelectInstallUI();
|
|
}
|
|
|
|
return `
|
|
<div class="task-action-box" data-action="${action}">
|
|
<div class="task-action-title ${colorClass}">
|
|
<i class="bi ${icon}"></i> ${action}
|
|
</div>
|
|
<div class="mt-2 container-chips" data-action="${action}">
|
|
${content}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderCategorySelector() {
|
|
const wrap = document.getElementById('iwCategorySelectorWrap');
|
|
|
|
if (!wrap) return;
|
|
|
|
wrap.innerHTML = `
|
|
<div class="iw-cat-box">
|
|
${[0,1,2,3,4,5].map(i => `
|
|
<button type="button" title="${IW_CATEGORY_LABELS[i] || ''}"
|
|
class="iw-cat-btn ${InstallWait.category == i ? 'active' : ''}"
|
|
data-value="${i}"
|
|
style="
|
|
width:18px;
|
|
height:18px;
|
|
border-radius:50%;
|
|
border:1px solid;
|
|
background:${IW_CATEGORY_COLORS[i]};
|
|
">
|
|
</button>
|
|
`).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function countTaskChips(action) {
|
|
// action: 'PICKUP' | 'INSTALL'
|
|
const wrap = document.querySelector(`.container-chips[data-action="${action}"]`);
|
|
if (!wrap) return 0;
|
|
|
|
// 프로젝트에서 chip 클래스가 여러 개일 수 있어서 span.chip 기준으로 카운트
|
|
return wrap.querySelectorAll('span.chip').length;
|
|
}
|
|
|
|
function validateContainersBeforeComplete() {
|
|
const jobType = (InstallWait.job?.type || '').toUpperCase();
|
|
|
|
const pickupCnt = countTaskChips('PICKUP');
|
|
const installCnt = countTaskChips('INSTALL');
|
|
|
|
// EXCHANGE: 둘 다 있어야 함
|
|
if (jobType === 'EXCHANGE') {
|
|
if (pickupCnt < 1 || installCnt < 1) {
|
|
//alert('EXCHANGE requires containers in BOTH Pickup and Install. Please select containers.');
|
|
showPopupMessage('Please select containers.', 'error', 1500);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// INSTALL: install에 있어야 함
|
|
if (jobType === 'INSTALL') {
|
|
if (installCnt < 1) {
|
|
//alert('Please select at least 1 container in Install.');
|
|
showPopupMessage('Please select containers.', 'error', 1500);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// PICKUP: pickup에 있어야 함
|
|
if (jobType === 'PICKUP') {
|
|
if (pickupCnt < 1) {
|
|
//alert('Please select at least 1 container in Pickup.');
|
|
showPopupMessage('Please select containers.', 'error', 1500);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 다른 타입(혹시 RELOCATE/CLEAN 등 Step3에서도 쓰면 정책 정해서 추가)
|
|
return true;
|
|
}
|
|
|
|
function loadCurrentContainersIfNeeded(onDone) {
|
|
const jobType = InstallWait.job?.type;
|
|
if (!['PICKUP','EXCHANGE','INSTALL','RELOCATE','CLEAN'].includes(jobType)) {
|
|
if (typeof onDone === 'function') onDone();
|
|
return;
|
|
}
|
|
|
|
const wrap = document.querySelector('.container-chips[data-action="CURRENT"]');
|
|
if (!wrap) {
|
|
if (typeof onDone === 'function') onDone();
|
|
return;
|
|
}
|
|
|
|
api('get_customer_containers_active',
|
|
{ c_uid: InstallWait.customer.uid },
|
|
rows => {
|
|
if (!rows || rows.length === 0) {
|
|
wrap.innerHTML = `<div class="text-muted small">No active containers</div>`;
|
|
if (typeof onDone === 'function') onDone();
|
|
return;
|
|
}
|
|
wrap.innerHTML = rows.map(renderContainerChip).join('');
|
|
bindCurrentContainerChipEvents();
|
|
|
|
if (typeof onDone === 'function') onDone();
|
|
}
|
|
);
|
|
}
|
|
|
|
function renderContainerChip(c) {
|
|
|
|
const type = (c.cc_type || '').toUpperCase();
|
|
const icon = getContainerIconByType(type);
|
|
const isRestaurantOwned = c.cc_owner_type === 'O';
|
|
const chipClass = isRestaurantOwned
|
|
? 'chip chip-restaurant iw-current-chip'
|
|
: 'chip chip-company iw-current-chip';
|
|
|
|
const ownerBadge = isRestaurantOwned
|
|
? '<span class="chip-owner">O</span>'
|
|
: '';
|
|
|
|
const flags =
|
|
(c.cc_has_lock === 'Y' ? ' <i class="bi bi-lock-fill"></i>' : '') +
|
|
(c.cc_has_wheel === 'Y' ? ' <i class="bi bi-gear-fill"></i>' : '') +
|
|
(c.cc_has_woodframe === 'Y'
|
|
? ' <i class="bi bi-bounding-box-circles"></i>'
|
|
: '');
|
|
|
|
const used = c.cc_is_used === 'Y' ? 'Used ' : '';
|
|
|
|
return `
|
|
<span class="${chipClass}"
|
|
data-type="${c.cc_type}"
|
|
data-capacity="${c.cc_capacity || ''}"
|
|
data-is_used="${c.cc_is_used || 'N'}"
|
|
data-owner_type="${c.cc_owner_type || 'C'}"
|
|
data-has_lock="${c.cc_has_lock || 'N'}"
|
|
data-has_wheel="${c.cc_has_wheel || 'N'}"
|
|
data-has_frame="${c.cc_has_woodframe || 'N'}">
|
|
|
|
${ownerBadge}
|
|
<i class="bi ${icon}"></i>
|
|
${used}${c.cc_type}
|
|
${flags}
|
|
</span>
|
|
`;
|
|
}
|
|
|
|
function bindCurrentContainerChipEvents() {
|
|
|
|
const jobType = InstallWait.job?.type;
|
|
const disableClick = ['INSTALL','RELOCATE','CLEAN'].includes(jobType);
|
|
|
|
const currentWrap = document.querySelector('.container-chips[data-action="CURRENT"]');
|
|
const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]');
|
|
if (!currentWrap || !pickupWrap) return;
|
|
|
|
currentWrap.querySelectorAll('.iw-current-chip').forEach(chip => {
|
|
|
|
chip.onclick = () => {
|
|
|
|
if (disableClick) {
|
|
// 클릭은 막고, 시각적 피드백만 줄 수도 있음
|
|
chip.classList.add('disabled');
|
|
return;
|
|
}
|
|
|
|
pickupWrap.appendChild(chip);
|
|
upsertPickupContainers();
|
|
bindPickupContainerChipEvents();
|
|
};
|
|
});
|
|
}
|
|
|
|
function bindPickupContainerChipEvents() {
|
|
|
|
const currentWrap = document.querySelector('.container-chips[data-action="CURRENT"]');
|
|
const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]');
|
|
if (!currentWrap || !pickupWrap) return;
|
|
|
|
pickupWrap.querySelectorAll('.iw-current-chip').forEach(chip => {
|
|
|
|
chip.onclick = () => {
|
|
|
|
// UI 이동
|
|
currentWrap.appendChild(chip);
|
|
|
|
// PICKUP 기준으로 다시 업설트
|
|
upsertPickupContainers();
|
|
|
|
// 이벤트 재바인딩
|
|
bindCurrentContainerChipEvents();
|
|
};
|
|
});
|
|
}
|
|
|
|
function chipKeyFromDataset(ds){
|
|
const norm = v => (v ?? '').toString();
|
|
return [
|
|
norm(ds.type).toUpperCase(),
|
|
norm(ds.capacity),
|
|
norm(ds.is_used),
|
|
norm(ds.owner_type),
|
|
norm(ds.has_lock),
|
|
norm(ds.has_wheel),
|
|
norm(ds.has_frame),
|
|
].join('|');
|
|
}
|
|
|
|
function chipKeyFromIwcRow(r){
|
|
const norm = v => (v ?? '').toString();
|
|
return [
|
|
norm(r.iwc_type).toUpperCase(),
|
|
norm(r.iwc_capacity),
|
|
norm(r.iwc_is_used || 'N'),
|
|
norm(r.iwc_owner_type || 'C'),
|
|
norm(r.iwc_has_lock || 'N'),
|
|
norm(r.iwc_has_wheel || 'N'),
|
|
norm(r.iwc_has_woodframe || 'N'),
|
|
].join('|');
|
|
}
|
|
|
|
function applyPickupFromDBToCurrent(pickupRows) {
|
|
const currentWrap = document.querySelector('.container-chips[data-action="CURRENT"]');
|
|
const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]');
|
|
if (!currentWrap || !pickupWrap) return;
|
|
|
|
// key → remaining count
|
|
const pickupCountMap = {};
|
|
(pickupRows || []).forEach(r => {
|
|
const key = chipKeyFromIwcRow(r);
|
|
pickupCountMap[key] = (pickupCountMap[key] || 0) + 1;
|
|
});
|
|
|
|
// CURRENT에서 하나씩 매칭
|
|
[...currentWrap.querySelectorAll('.iw-current-chip')].forEach(chip => {
|
|
const key = chipKeyFromDataset(chip.dataset);
|
|
if (pickupCountMap[key] > 0) {
|
|
pickupWrap.appendChild(chip);
|
|
pickupCountMap[key]--; // ⭐ 하나 소비
|
|
}
|
|
});
|
|
|
|
bindCurrentContainerChipEvents();
|
|
bindPickupContainerChipEvents();
|
|
}
|
|
|
|
function upsertPickupContainers() {
|
|
|
|
const pickupWrap = document.querySelector('.container-chips[data-action="PICKUP"]');
|
|
if (!pickupWrap) return;
|
|
|
|
const items = [...pickupWrap.querySelectorAll('.iw-current-chip')].map(el => ({
|
|
action: 'PICKUP',
|
|
type: el.dataset.type,
|
|
capacity: el.dataset.capacity || null,
|
|
is_used: el.dataset.is_used || 'N',
|
|
owner_type: el.dataset.owner_type || 'C',
|
|
has_lock: el.dataset.has_lock || 'N',
|
|
has_wheel: el.dataset.has_wheel || 'N',
|
|
has_frame: el.dataset.has_frame || 'N'
|
|
}));
|
|
|
|
api('install_wait_upsert_containers', {
|
|
iw_uid: InstallWait.iw_uid,
|
|
action: 'PICKUP',
|
|
items
|
|
});
|
|
}
|
|
|
|
function renderSelectInstallUI() {
|
|
|
|
const binOptions = Object.entries(window.IW_BINS || {})
|
|
.map(([key, label]) =>
|
|
`<option value="${key}">${label}</option>`
|
|
).join('');
|
|
|
|
return `
|
|
<div class="iw-select-install">
|
|
<div class="d-flex align-items-center mb-2">
|
|
|
|
<!-- 왼쪽: 옵션들 -->
|
|
<div class="d-flex gap-2 align-items-center flex-grow-1">
|
|
|
|
<button type="button" id="iwSelectUsed"
|
|
class="btn btn-sm btn-outline-secondary">
|
|
Used
|
|
</button>
|
|
|
|
<select id="iwSelectType" class="form-select form-select-sm">
|
|
<option value="">Select bin</option>
|
|
${binOptions}
|
|
</select>
|
|
|
|
<input type="number"
|
|
id="iwSelectCapacity"
|
|
class="form-control form-control-sm iw-inline-ctrl"
|
|
placeholder="L"
|
|
readonly>
|
|
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-secondary iw-opt"
|
|
data-opt="L" title="Lock">
|
|
<i class="bi bi-lock-fill"></i>
|
|
</button>
|
|
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-secondary iw-opt"
|
|
data-opt="W" title="Wheel">
|
|
<i class="bi bi-gear-fill"></i>
|
|
</button>
|
|
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-secondary iw-opt"
|
|
data-opt="F" title="Frame">
|
|
<i class="bi bi-bounding-box-circles"></i>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<!-- 오른쪽: Add -->
|
|
<button type="button"
|
|
id="iwAddInstall"
|
|
class="btn btn-sm btn-success ms-2 iw-add-btn">
|
|
<i class="bi bi-plus-lg"></i>
|
|
<span class="ms-1">Add</span>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function bindSelectInstallEvents() {
|
|
|
|
const usedBtn = document.getElementById('iwSelectUsed');
|
|
const typeSel = document.getElementById('iwSelectType');
|
|
const capInp = document.getElementById('iwSelectCapacity');
|
|
|
|
if (!usedBtn || !typeSel) return;
|
|
|
|
usedBtn.onclick = () => {
|
|
usedBtn.classList.toggle('active');
|
|
};
|
|
|
|
// 타입 선택 시 용량 자동 표시
|
|
typeSel.onchange = () => {
|
|
|
|
const val = typeSel.value;
|
|
|
|
// CB 는 직접 입력
|
|
if (val === 'CB') {
|
|
capInp.readOnly = false;
|
|
capInp.value = '';
|
|
capInp.focus();
|
|
return;
|
|
} else if (val === 'D') {
|
|
capInp.value = '200';
|
|
return;
|
|
}
|
|
|
|
capInp.readOnly = true;
|
|
|
|
const m = val.match(/^(\d+)/);
|
|
capInp.value = m ? m[1] : '';
|
|
};
|
|
|
|
// 악세사리 토글
|
|
document.querySelectorAll('.iw-opt').forEach(btn => {
|
|
btn.onclick = () => btn.classList.toggle('active');
|
|
});
|
|
|
|
// Add → INSTALL 이동
|
|
document.getElementById('iwAddInstall').onclick = addInstallFromSelect;
|
|
}
|
|
|
|
function addInstallFromSelect() {
|
|
|
|
const type = document.getElementById('iwSelectType').value;
|
|
if (!type) return showPopupMessage('Select container type.', 'error', 1500); //alert('Select container type');
|
|
|
|
const capacity = document.getElementById('iwSelectCapacity').value || null;
|
|
const is_used = document.getElementById('iwSelectUsed').classList.contains('active') ? 'Y' : 'N';
|
|
|
|
const has_lock = document.querySelector('.iw-opt[data-opt="L"]').classList.contains('active') ? 'Y' : 'N';
|
|
const has_wheel = document.querySelector('.iw-opt[data-opt="W"]').classList.contains('active') ? 'Y' : 'N';
|
|
const has_frame = document.querySelector('.iw-opt[data-opt="F"]').classList.contains('active') ? 'Y' : 'N';
|
|
|
|
const installWrap = document.querySelector('.container-chips[data-action="INSTALL"]');
|
|
|
|
const icon = getContainerIconByType(type);
|
|
const chip = `
|
|
<span class="chip chip-company iw-install-chip"
|
|
data-type="${type}"
|
|
data-capacity="${capacity || ''}"
|
|
data-is_used="${is_used}"
|
|
data-has_lock="${has_lock}"
|
|
data-has_wheel="${has_wheel}"
|
|
data-has_frame="${has_frame}">
|
|
|
|
<i class="bi ${icon}"></i>
|
|
${is_used === 'Y' ? 'Used ' : ''}${type}
|
|
${has_lock === 'Y' ? ' <i class="bi bi-lock-fill"></i>' : ''}
|
|
${has_wheel === 'Y' ? ' <i class="bi bi-gear-fill"></i>' : ''}
|
|
${has_frame === 'Y' ? ' <i class="bi bi-bounding-box-circles"></i>' : ''}
|
|
</span>
|
|
`;
|
|
|
|
installWrap.insertAdjacentHTML('beforeend', chip);
|
|
bindInstallChipEvents();
|
|
upsertInstallContainers();
|
|
}
|
|
|
|
function upsertInstallContainers() {
|
|
|
|
const wrap = document.querySelector('.container-chips[data-action="INSTALL"]');
|
|
|
|
const items = [...wrap.querySelectorAll('.iw-install-chip')].map(el => ({
|
|
action: 'INSTALL',
|
|
type: el.dataset.type,
|
|
capacity: el.dataset.capacity || null,
|
|
is_used: el.dataset.is_used || 'N',
|
|
has_lock: el.dataset.has_lock || 'N',
|
|
has_wheel: el.dataset.has_wheel || 'N',
|
|
has_frame: el.dataset.has_frame || 'N',
|
|
owner_type: 'C'
|
|
}));
|
|
|
|
api('install_wait_upsert_containers', {
|
|
iw_uid: InstallWait.iw_uid,
|
|
action: 'INSTALL',
|
|
items
|
|
});
|
|
}
|
|
|
|
function bindInstallChipEvents() {
|
|
|
|
const installWrap = document.querySelector('.container-chips[data-action="INSTALL"]');
|
|
if (!installWrap) return;
|
|
|
|
installWrap.querySelectorAll('.iw-install-chip').forEach(chip => {
|
|
|
|
chip.onclick = () => {
|
|
chip.remove();
|
|
upsertInstallContainers();
|
|
};
|
|
});
|
|
}
|
|
|
|
function initInstallWaitDatePickers() {
|
|
|
|
if (!$.fn.datepicker) return;
|
|
|
|
$('.date-picker-form').datepicker({
|
|
dateFormat: 'yy-mm-dd',
|
|
changeMonth: true,
|
|
changeYear: true
|
|
});
|
|
}
|
|
|
|
function fillRequestBySelect(selectedValue = 'Customer') {
|
|
const sel = document.getElementById('iwRequestBy');
|
|
if (!sel || !window.IW_REQUEST_BY_OPTIONS) return;
|
|
|
|
sel.innerHTML = window.IW_REQUEST_BY_OPTIONS;
|
|
|
|
if (!sel.querySelector(`option[value="${selectedValue}"]`)) {
|
|
selectedValue = 'Customer';
|
|
}
|
|
|
|
sel.value = selectedValue;
|
|
}
|
|
|
|
// 이미지 여부 체크
|
|
function checkCustomerHasImages(customerUid, iwUid) {
|
|
if (!customerUid || !iwUid) return;
|
|
|
|
const btn = document.getElementById('btnViewPhotos');
|
|
if (!btn) return;
|
|
|
|
const params = new URLSearchParams({
|
|
customer_uid: customerUid,
|
|
i_type: 'install_order'
|
|
});
|
|
|
|
fetch('/lib/customer_image.php?' + params.toString())
|
|
.then(res => res.text())
|
|
.then(html => {
|
|
|
|
const temp = document.createElement('div');
|
|
temp.innerHTML = html;
|
|
|
|
const slider = temp.querySelector('#customerImageSlider');
|
|
|
|
if (!slider) {
|
|
btn.classList.add('d-none');
|
|
return;
|
|
}
|
|
|
|
let meta = [];
|
|
|
|
try {
|
|
meta = JSON.parse(slider.dataset.meta || '[]');
|
|
} catch (e) {
|
|
console.error('image meta parse error:', e);
|
|
btn.classList.add('d-none');
|
|
return;
|
|
}
|
|
|
|
const hasImage = meta.some(m => {
|
|
return parseInt(m.sourceuid || 0, 10) === parseInt(iwUid || 0, 10);
|
|
});
|
|
|
|
console.log('hasImage:', hasImage, 'iwUid:', iwUid, 'meta:', meta);
|
|
|
|
if (hasImage) {
|
|
btn.classList.remove('d-none');
|
|
btn.onclick = () => openCustomerImages(customerUid);
|
|
} else {
|
|
btn.classList.add('d-none');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
btn.classList.add('d-none');
|
|
});
|
|
}
|
|
|
|
function bindStep3Toggles() {
|
|
|
|
const btnTwo = document.getElementById('btnTwoPerson');
|
|
const reason = document.getElementById('iwTwoPersonReason');
|
|
|
|
btnTwo.onclick = () => {
|
|
const on = btnTwo.classList.toggle('iw-toggle-on');
|
|
|
|
reason.disabled = !on;
|
|
if (!on) reason.value = '';
|
|
};
|
|
|
|
const btnRec = document.getElementById('btnRecurring');
|
|
const days = document.getElementById('iwRecurringDays');
|
|
|
|
btnRec.onclick = () => {
|
|
const on = btnRec.classList.toggle('iw-toggle-on');
|
|
|
|
days.disabled = !on;
|
|
if (!on) days.value = '';
|
|
};
|
|
|
|
const btnOil = document.getElementById('btnNeedOilPickup');
|
|
btnOil.onclick = () => {
|
|
btnOil.classList.toggle('iw-toggle-on');
|
|
};
|
|
|
|
bindCategoryEvents();
|
|
}
|
|
|
|
function bindCategoryEvents() {
|
|
document.querySelectorAll('.iw-cat-btn').forEach(btn => {
|
|
btn.onclick = function() {
|
|
|
|
document.querySelectorAll('.iw-cat-btn')
|
|
.forEach(b => b.classList.remove('active'));
|
|
|
|
this.classList.add('active');
|
|
|
|
InstallWait.category = parseInt(this.dataset.value, 10);
|
|
};
|
|
});
|
|
}
|
|
|
|
function getContainerIconByType(type) {
|
|
|
|
type = (type || '').toUpperCase();
|
|
|
|
if (type.includes('D')) return 'bi-database-fill'; // Drum
|
|
if (type.includes('B') && !type.includes('BUCKET'))
|
|
return 'bi-archive-fill'; // Bin
|
|
|
|
return 'bi-box2'; // Fallback
|
|
}
|
|
|
|
function renderDbChipCommon(r, cls) {
|
|
const type = (r.iwc_type || '').toUpperCase();
|
|
const icon = getContainerIconByType(type);
|
|
|
|
const isRestaurantOwned = (r.iwc_owner_type || 'C') === 'O';
|
|
const chipClass = isRestaurantOwned
|
|
? `chip chip-restaurant ${cls}`
|
|
: `chip chip-company ${cls}`;
|
|
|
|
const ownerBadge = isRestaurantOwned ? '<span class="chip-owner">O</span>' : '';
|
|
|
|
const flags =
|
|
(r.iwc_has_lock === 'Y' ? ' <i class="bi bi-lock-fill"></i>' : '') +
|
|
(r.iwc_has_wheel === 'Y' ? ' <i class="bi bi-gear-fill"></i>' : '') +
|
|
((r.iwc_has_woodframe || 'N') === 'Y'
|
|
? ' <i class="bi bi-bounding-box-circles"></i>'
|
|
: '');
|
|
|
|
const used = r.iwc_is_used === 'Y' ? 'Used ' : '';
|
|
|
|
return `
|
|
<span class="${chipClass}"
|
|
data-type="${r.iwc_type}"
|
|
data-capacity="${r.iwc_capacity || ''}"
|
|
data-is_used="${r.iwc_is_used || 'N'}"
|
|
data-owner_type="${r.iwc_owner_type || 'C'}"
|
|
data-has_lock="${r.iwc_has_lock || 'N'}"
|
|
data-has_wheel="${r.iwc_has_wheel || 'N'}"
|
|
data-has_frame="${r.iwc_has_woodframe || 'N'}">
|
|
${ownerBadge}
|
|
<i class="bi ${icon}"></i>
|
|
${used}${r.iwc_type}
|
|
${flags}
|
|
</span>
|
|
`;
|
|
}
|
|
|
|
function renderPickupChipFromDB(r) {
|
|
return renderDbChipCommon(r, 'iw-current-chip'); // PICKUP는 current-chip 클래스 재사용
|
|
}
|
|
|
|
function renderInstallChipFromDB(r) {
|
|
return renderDbChipCommon(r, 'iw-install-chip');
|
|
}
|
|
|
|
|
|
// Complete 액션 종료 후
|
|
function finishInstallWaitComplete() {
|
|
|
|
const modalEl = document.getElementById('addInstallWaitModal');
|
|
|
|
// 모달 완전히 닫힌 뒤 리로드
|
|
modalEl.addEventListener('hidden.bs.modal', function onHidden() {
|
|
modalEl.removeEventListener('hidden.bs.modal', onHidden);
|
|
|
|
// 상태 정리
|
|
InstallWait = { step: 1 };
|
|
|
|
// 이 시점에 리로드
|
|
location.reload();
|
|
});
|
|
|
|
// 모달 닫기 (애니메이션 시작)
|
|
const modal = bootstrap.Modal.getInstance(modalEl);
|
|
if (modal) modal.hide();
|
|
}
|