- [CUSTOMER] access time 정보 입력 방식 수정.
This commit is contained in:
Hyojin Ahn 2026-07-09 08:07:20 -04:00
parent 193dda8add
commit 139113d1cf
1 changed files with 298 additions and 203 deletions

View File

@ -5,8 +5,10 @@
* - Opened as a modal (iframe) from customer_detail.php; reusable on other screens * - Opened as a modal (iframe) from customer_detail.php; reusable on other screens
* - On successful save, notifies parent via window.parent.closeCustomerPopup(true) * - On successful save, notifies parent via window.parent.closeCustomerPopup(true)
* - Save = "full replace": delete the customer's active rows, re-insert current input * - Save = "full replace": delete the customer's active rows, re-insert current input
* - Time is chosen via hour/minute selects (minute step = 5) so granularity is * - Top editor = dual sliders (06:00~18:00 / 10-min step, end optional & off by
* guaranteed regardless of the browser's native time picker * default). Selecting day chip(s) / clicking a time pill binds that window to
* the sliders; moving the sliders edits every selected window live. Each day
* has its own "+ Add another"; days show their windows as compact time pills.
* ===================================================================== */ * ===================================================================== */
include getenv("DOCUMENT_ROOT")."/include/session_include.php"; include getenv("DOCUMENT_ROOT")."/include/session_include.php";
@ -113,47 +115,60 @@ $existingJson = json_encode($existing, JSON_UNESCAPED_UNICODE);
<title>Access Time</title> <title>Access Time</title>
<style> <style>
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { font-family: -apple-system, "Segoe UI", Arial, sans-serif; margin: 0; padding: 14px 16px 80px; color: #333; font-size: 14px; background:#fff; } body { font-family: -apple-system, "Segoe UI", Arial, sans-serif; margin: 0; padding: 12px 14px 70px; color: #333; font-size: 14px; background:#fff; }
h2 { font-size: 16px; margin: 0 0 10px; } h2 { font-size: 15px; margin: 0 0 8px; }
/* time selects */ /* ---- Top editor: bound to the currently selected window(s) ---- */
.tf { display: inline-flex; align-items: center; gap: 2px; } .editor { border: 1px solid #cfe0cf; background: #f4f9f1; border-radius: 8px; padding: 11px 12px; margin-bottom: 12px; }
.tf select { padding: 5px 4px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; background: #fff; } .slrow { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
.tf-colon { color: #999; } .slkind { width: 66px; flex: none; font-size: 12px; font-weight: 600; color: #4a7a3a; }
.slrow input[type=range] { flex: 1; min-width: 60px; accent-color: #7fb069; height: 24px; margin: 0; cursor: pointer; }
.slrow.end input[type=range] { accent-color: #d9932f; }
.slrow.end.off input[type=range] { accent-color: #ccc; }
.sltime { width: 54px; flex: none; text-align: right; font-size: 15px; font-weight: 700; color: #333; font-variant-numeric: tabular-nums; }
.slrow.end.off .sltime { color: #aaa; font-weight: 500; }
/* Quick fill */ /* big, easy-to-hit End checkbox */
.quick { border: 1px solid #cfe0cf; background: #f4f9f1; border-radius: 8px; padding: 10px 12px; margin-bottom: 14px; } .endlbl { width: 66px; flex: none; display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; color: #a06a2a; cursor: pointer; user-select: none; }
.quick-line { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; } .slrow.end.off .endlbl { color: #999; }
.quick-line:last-child { margin-bottom: 0; } .endlbl input { width: 18px; height: 18px; flex: none; cursor: pointer; accent-color: #d9932f; }
.quick input.note { padding: 5px 7px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; flex: 1; min-width: 90px; max-width: 200px; }
.qday { border: 1px solid #bcd1bc; background: #fff; color: #4a7a3a; border-radius: 14px; padding: 2px 11px; font-size: 13px; cursor: pointer; user-select: none; } .ed-line { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
.qday.on { background: #7fb069; border-color: #7fb069; color: #fff; } .ed-line input.note { padding: 6px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; flex: 1; min-width: 90px; }
.qday.wknd { color: #c0392b; }
.qday.wknd.on { color: #fff; } .chips { display: flex; gap: 5px; flex-wrap: wrap; }
.qbtn { border: 1px solid #cfcfcf; background: #fff; border-radius: 4px; font-size: 12px; padding: 4px 10px; cursor: pointer; color: #555; } .chip { border: 1px solid #bcd1bc; background: #fff; color: #4a7a3a; border-radius: 14px; padding: 4px 12px; font-size: 13px; cursor: pointer; user-select: none; }
.chip.on { background: #7fb069; border-color: #7fb069; color: #fff; }
.chip.wknd { color: #c0392b; }
.chip.wknd.on { color: #fff; }
.qbtn { border: 1px solid #cfcfcf; background: #fff; border-radius: 4px; font-size: 12px; padding: 5px 11px; cursor: pointer; color: #555; }
.qbtn:hover { background: #eee; } .qbtn:hover { background: #eee; }
.qbtn.primary { background: #7fb069; border-color: #7fb069; color: #fff; } .hint { font-size: 11px; color: #8aa07d; margin-top: 7px; line-height: 1.5; }
.qbtn.primary:hover { background: #6da356; }
/* Per-day list */ /* ---- Per-day summary: time-only pills + per-day add ---- */
.day { border: 1px solid #e3e3e3; border-radius: 6px; margin-bottom: 8px; } .day-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border: 1px solid #e6e6e6; border-radius: 6px; margin-bottom: 5px; min-height: 38px; }
.day-head { display: flex; align-items: center; justify-content: space-between; padding: 7px 10px; background: #f6f8f3; border-bottom: 1px solid #eee; } .dn { width: 38px; flex: none; font-weight: 600; }
.day-name { font-weight: 600; } .dn.wknd { color: #c0392b; }
.day-name.wknd { color: #c0392b; } .pills { flex: 1; display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.add-win { border: 1px solid #cfcfcf; background: #fff; border-radius: 4px; font-size: 12px; padding: 2px 8px; cursor: pointer; color: #555; } .pill { display: inline-flex; align-items: center; gap: 2px; background: #eef5e8; border: 1px solid #cfe0cf; color: #39632a; border-radius: 14px; padding: 3px 3px 3px 11px; font-size: 12px; cursor: pointer; }
.add-win:hover { background: #7fb069; color: #fff; border-color: #7fb069; } .pill.noend { background: #fdf3e6; border-color: #f0d9b5; color: #a06a2a; }
.wins { padding: 6px 10px; display: flex; flex-direction: column; gap: 6px; } .pill.sel { box-shadow: 0 0 0 2px #7fb069; }
.empty { color: #bbb; font-size: 13px; padding: 4px 0; } .pill.noend.sel { box-shadow: 0 0 0 2px #d9932f; }
.win { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } .pill .lbl { font-variant-numeric: tabular-nums; }
.win input.note { padding: 5px 7px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; flex: 1; min-width: 90px; } .pill .x { border: none; background: transparent; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 0 5px; opacity: .55; }
.tilde { color: #999; } .pill .x:hover { opacity: 1; }
.rm { border: none; background: #fbecec; color: #c0392b; border-radius: 4px; width: 26px; height: 28px; cursor: pointer; font-size: 15px; line-height: 1; } .pill.hasnote .lbl::after { content: " *"; color: #d9932f; font-weight: 700; }
.rm:hover { background: #c0392b; color: #fff; } .closed { color: #bbb; font-size: 12px; }
.dayadd { border: 1px dashed #bcd1bc; background: #fff; color: #4a7a3a; border-radius: 12px; height: 26px; padding: 0 10px; flex: none; cursor: pointer; font-size: 12px; line-height: 1; }
.dayadd:hover { background: #7fb069; color: #fff; border-color: #7fb069; }
.footer { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; border-top: 1px solid #e3e3e3; padding: 10px 16px; display: flex; justify-content: flex-end; gap: 8px; } .footer { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; border-top: 1px solid #e3e3e3; padding: 9px 14px; display: flex; justify-content: flex-end; gap: 8px; }
.btn { padding: 8px 18px; border-radius: 4px; border: 1px solid #ccc; background: #fff; cursor: pointer; font-size: 14px; } .btn { padding: 8px 18px; border-radius: 4px; border: 1px solid #ccc; background: #fff; cursor: pointer; font-size: 14px; }
.btn-save { background: #7fb069; border-color: #7fb069; color: #fff; } .btn-save { background: #7fb069; border-color: #7fb069; color: #fff; }
.btn-save:hover { background: #6da356; } .btn-save:hover { background: #6da356; }
.btn-clear { margin-right: auto; color: #c0392b; border-color: #e3b7b1; }
.btn-clear:hover { background: #c0392b; border-color: #c0392b; color: #fff; }
.btn:disabled { opacity: .6; cursor: default; } .btn:disabled { opacity: .6; cursor: default; }
</style> </style>
</head> </head>
@ -161,188 +176,269 @@ $existingJson = json_encode($existing, JSON_UNESCAPED_UNICODE);
<h2>Access Time</h2> <h2>Access Time</h2>
<!-- Quick fill: enter a time once and add it to multiple days at once --> <!-- Top editor: bound to the selected window(s); moving sliders edits them live -->
<div class="quick"> <div class="editor">
<div class="quick-line"> <div class="slrow start">
<span id="qOpenField"></span> <span class="slkind">Start</span>
<span class="tilde">~</span> <input type="range" id="openR">
<span id="qCloseField"></span> <span class="sltime" id="openT">09:00</span>
<input type="text" id="qNote" class="note" maxlength="200" placeholder="Note (optional)">
</div> </div>
<div class="quick-line" id="qDays"></div> <div class="slrow end off" id="endRow">
<div class="quick-line"> <label class="endlbl"><span>End</span><input type="checkbox" id="endChk"></label>
<input type="range" id="closeR">
<span class="sltime" id="closeT">None</span>
</div>
<div class="ed-line">
<div class="chips" id="chips"></div>
</div>
<div class="ed-line">
<button type="button" class="qbtn" id="qWeekday">Weekdays</button> <button type="button" class="qbtn" id="qWeekday">Weekdays</button>
<button type="button" class="qbtn" id="qWeekend">Weekend</button> <button type="button" class="qbtn" id="qWeekend">Weekend</button>
<button type="button" class="qbtn" id="qAll">Everyday</button> <button type="button" class="qbtn" id="qAll">Everyday</button>
<button type="button" class="qbtn" id="qNone">Unselect</button> <button type="button" class="qbtn" id="qNone">Unselect</button>
<button type="button" class="qbtn primary" id="qApply">Apply</button> <input type="text" id="noteIn" class="note" maxlength="200" placeholder="Note (optional)">
</div> </div>
<div class="hint">Pick day(s) or click a time to select it &mdash; the sliders jump to it and edit every selected time live.<br>Use each day's <b>+ Add</b> to add another time to that day.</div>
</div> </div>
<div id="days"></div> <div id="days"></div>
<div class="footer"> <div class="footer">
<button type="button" class="btn btn-clear" id="clearBtn">Clear all</button>
<button type="button" class="btn" id="closeBtn">Close</button> <button type="button" class="btn" id="closeBtn">Close</button>
<button type="button" class="btn btn-save" id="saveBtn">Save</button> <button type="button" class="btn btn-save" id="saveBtn">Save</button>
</div> </div>
<script> <script>
const DOW_NAMES = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; const DOW_NAMES = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
const MIN_STEP = 5; // minute step (change to 15 if desired) const DAY_MIN = 6 * 60; // 06:00 slider floor (minutes)
const DAY_MAX = 18 * 60; // 18:00 slider ceiling
const STEP = 10; // 10-minute step
const DEF_OPEN = 9 * 60; // default editor start 09:00
const DEF_CLOSE = 17 * 60; // default editor end 17:00 (only when End is on)
const existing = <?=$existingJson?> || {}; const existing = <?=$existingJson?> || {};
const C_UID = <?=$c_uid?>; const C_UID = <?=$c_uid?>;
const daysEl = document.getElementById('days');
function pad(n){ return String(n).padStart(2,'0'); } function pad(n){ return String(n).padStart(2,'0'); }
function esc(s){ return (s||'').replace(/"/g,'&quot;'); } function minToStr(v){ return pad(Math.floor(v / 60)) + ':' + pad(v % 60); }
function strToMin(s){ return /^\d{2}:\d{2}$/.test(s || '') ? (parseInt(s.slice(0,2),10) * 60 + parseInt(s.slice(3,5),10)) : null; }
/* ---- hour/minute select field. Reads value via the hidden input.className ---- */ function clampRange(v){ return Math.max(DAY_MIN, Math.min(DAY_MAX, v)); }
function timeField(cls, value) {
value = value || '';
const wrap = document.createElement('span');
wrap.className = 'tf';
const selH = document.createElement('select');
const selM = document.createElement('select');
const hid = document.createElement('input');
hid.type = 'hidden';
hid.className = cls;
hid.value = value;
// "--" (empty) option
let o = document.createElement('option'); o.value = ''; o.textContent = '--'; selH.appendChild(o);
o = document.createElement('option'); o.value = ''; o.textContent = '--'; selM.appendChild(o);
for (let h = 0; h < 24; h++) { const op = document.createElement('option'); op.value = pad(h); op.textContent = pad(h); selH.appendChild(op); }
const mins = [];
for (let m = 0; m < 60; m += MIN_STEP) mins.push(pad(m));
mins.forEach(function (m) { const op = document.createElement('option'); op.value = m; op.textContent = m; selM.appendChild(op); });
// 새 필드(빈 값)는 분을 00으로 기본 세팅. 시(hour)를 고르면 HH:00 으로 확정됨
let hh = '', mm = '00';
if (/^\d{2}:\d{2}$/.test(value)) {
hh = value.slice(0, 2); mm = value.slice(3, 5);
if (mins.indexOf(mm) === -1) { const op = document.createElement('option'); op.value = mm; op.textContent = mm; selM.appendChild(op); } // keep legacy minute
}
selH.value = hh; selM.value = mm;
function sync() {
const h = selH.value, m = selM.value;
hid.value = (h !== '' && m !== '') ? (h + ':' + m) : '';
}
selH.addEventListener('change', sync);
selM.addEventListener('change', sync);
const colon = document.createElement('span'); colon.className = 'tf-colon'; colon.textContent = ':';
wrap.appendChild(selH); wrap.appendChild(colon); wrap.appendChild(selM); wrap.appendChild(hid);
return wrap;
}
function winRow(w) {
w = w || { open: '', close: '', note: '' };
const div = document.createElement('div');
div.className = 'win';
div.appendChild(timeField('open', w.open));
const tilde = document.createElement('span'); tilde.className = 'tilde'; tilde.textContent = '~'; div.appendChild(tilde);
div.appendChild(timeField('close', w.close));
const note = document.createElement('input');
note.type = 'text'; note.className = 'note'; note.maxLength = 200;
note.placeholder = 'Note (optional)'; note.value = w.note || '';
div.appendChild(note);
const rm = document.createElement('button');
rm.type = 'button'; rm.className = 'rm'; rm.title = 'Delete'; rm.innerHTML = '&times;';
rm.addEventListener('click', function () { const wins = div.parentElement; div.remove(); refreshEmpty(wins); });
div.appendChild(rm);
return div;
}
function refreshEmpty(winsEl) {
const hasRow = winsEl.querySelector('.win');
let emptyEl = winsEl.querySelector('.empty');
if (!hasRow && !emptyEl) {
emptyEl = document.createElement('div');
emptyEl.className = 'empty';
emptyEl.textContent = 'Closed (no hours)';
winsEl.appendChild(emptyEl);
} else if (hasRow && emptyEl) {
emptyEl.remove();
}
}
const winsByDow = {};
function addWindowToDay(d, w) {
const wins = winsByDow[d];
wins.appendChild(winRow(w));
refreshEmpty(wins);
}
/* --------------------------------------------------------------------
* Model
* - windows[]: every time window { id, dow, open, close(min|null), note }
* - selectedByDay: dow -> the ONE selected window object for that day
* Editing the sliders/checkbox writes into every selected window (bulk).
* Editing the note writes the note into every selected window.
* ------------------------------------------------------------------ */
let nextId = 1;
const windows = [];
for (let d = 0; d < 7; d++) { for (let d = 0; d < 7; d++) {
const day = document.createElement('div'); (existing[d] || []).forEach(function (w) {
day.className = 'day'; const o = strToMin(w.open);
day.dataset.dow = d; if (o !== null) windows.push({ id: nextId++, dow: d, open: o, close: strToMin(w.close), note: w.note || '' });
});
}
const selectedByDay = {}; // dow -> window obj
const head = document.createElement('div'); function latestWindow(d) {
head.className = 'day-head'; let best = null;
head.innerHTML = windows.forEach(function (w) { if (w.dow === d && (!best || w.id > best.id)) best = w; });
'<span class="day-name ' + ((d === 0 || d === 6) ? 'wknd' : '') + '">' + DOW_NAMES[d] + '</span>' + return best;
'<button type="button" class="add-win">+ Add time</button>'; }
function editorTemplate() {
const wins = document.createElement('div'); return { open: +openR.value, close: endChk.checked ? +closeR.value : null, note: noteIn.value.trim() };
wins.className = 'wins'; }
winsByDow[d] = wins; function createWindow(d, tmpl) {
const w = { id: nextId++, dow: d, open: tmpl.open, close: tmpl.close, note: tmpl.note };
(existing[d] || []).forEach(function (w) { wins.appendChild(winRow(w)); }); windows.push(w);
refreshEmpty(wins); return w;
head.querySelector('.add-win').addEventListener('click', function () { addWindowToDay(d); });
day.appendChild(head);
day.appendChild(wins);
daysEl.appendChild(day);
} }
/* ----- Quick fill ----- */ /* ================= Editor controls ================= */
document.getElementById('qOpenField').appendChild(timeField('qOpen', '')); const openR = document.getElementById('openR');
document.getElementById('qCloseField').appendChild(timeField('qClose', '')); const closeR = document.getElementById('closeR');
const openT = document.getElementById('openT');
const closeT = document.getElementById('closeT');
const endChk = document.getElementById('endChk');
const endRow = document.getElementById('endRow');
const noteIn = document.getElementById('noteIn');
const qDaysEl = document.getElementById('qDays'); [openR, closeR].forEach(function (r) { r.min = DAY_MIN; r.max = DAY_MAX; r.step = STEP; });
openR.value = DEF_OPEN; closeR.value = DEF_CLOSE; endChk.checked = false;
function paintEditor() {
openT.textContent = minToStr(+openR.value);
if (endChk.checked) {
endRow.classList.remove('off'); closeR.disabled = false;
closeT.textContent = minToStr(+closeR.value);
} else {
endRow.classList.add('off'); closeR.disabled = true;
closeT.textContent = 'None';
}
}
// snap the editor to a window's values WITHOUT applying back (no event fired)
function editorFromWindow(w) {
openR.value = clampRange(w.open);
if (w.close === null) { endChk.checked = false; }
else { endChk.checked = true; closeR.value = clampRange(w.close); }
noteIn.value = w.note || '';
paintEditor();
}
function selectedList() {
return Object.keys(selectedByDay).map(function (d) { return selectedByDay[d]; });
}
function applyTimeToSelected() {
const open = +openR.value;
const close = endChk.checked ? +closeR.value : null;
selectedList().forEach(function (w) { w.open = open; w.close = close; });
render();
}
function applyNoteToSelected() {
const note = noteIn.value.trim();
selectedList().forEach(function (w) { w.note = note; });
render();
}
openR.addEventListener('input', function () {
if (endChk.checked && +closeR.value < +openR.value) closeR.value = openR.value;
paintEditor(); applyTimeToSelected();
});
closeR.addEventListener('input', function () {
if (+closeR.value < +openR.value) openR.value = closeR.value;
paintEditor(); applyTimeToSelected();
});
endChk.addEventListener('change', function () {
if (endChk.checked && +closeR.value < +openR.value) closeR.value = openR.value;
paintEditor(); applyTimeToSelected();
});
noteIn.addEventListener('input', applyNoteToSelected);
paintEditor();
/* ================= Day selection chips ================= */
const chipsEl = document.getElementById('chips');
for (let d = 0; d < 7; d++) { for (let d = 0; d < 7; d++) {
const chip = document.createElement('span'); const chip = document.createElement('span');
chip.className = 'qday' + ((d === 0 || d === 6) ? ' wknd' : ''); chip.className = 'chip' + ((d === 0 || d === 6) ? ' wknd' : '');
chip.dataset.dow = d; chip.dataset.dow = d;
chip.textContent = DOW_NAMES[d]; chip.textContent = DOW_NAMES[d];
chip.addEventListener('click', function () { chip.classList.toggle('on'); }); chip.addEventListener('click', function () { toggleDay(d); });
qDaysEl.appendChild(chip); chipsEl.appendChild(chip);
} }
function setDays(list) { function syncChips() {
qDaysEl.querySelectorAll('.qday').forEach(function (c) { chipsEl.querySelectorAll('.chip').forEach(function (c) {
c.classList.toggle('on', list.includes(parseInt(c.dataset.dow, 10))); c.classList.toggle('on', !!selectedByDay[parseInt(c.dataset.dow, 10)]);
}); });
} }
document.getElementById('qWeekday').addEventListener('click', function () { setDays([1,2,3,4,5]); });
document.getElementById('qWeekend').addEventListener('click', function () { setDays([0,6]); });
document.getElementById('qAll').addEventListener('click', function () { setDays([0,1,2,3,4,5,6]); });
document.getElementById('qNone').addEventListener('click', function () { setDays([]); });
document.getElementById('qApply').addEventListener('click', function () { // chip: select that day's latest window (create one if the day is empty); toggle off if already selected
const open = document.querySelector('.qOpen').value.trim(); function toggleDay(d) {
const close = document.querySelector('.qClose').value.trim(); if (selectedByDay[d]) { delete selectedByDay[d]; syncChips(); render(); return; }
const note = document.getElementById('qNote').value.trim(); let w = latestWindow(d);
if (!open) { alert('Enter a start time.'); return; } if (!w) w = createWindow(d, editorTemplate());
if (close && close < open) { alert('End time is earlier than start time.'); return; } selectedByDay[d] = w;
const targets = []; editorFromWindow(w);
qDaysEl.querySelectorAll('.qday.on').forEach(function (c) { targets.push(parseInt(c.dataset.dow, 10)); }); syncChips(); render();
if (targets.length === 0) { alert('Select day(s) to apply.'); return; } }
targets.forEach(function (d) { addWindowToDay(d, { open: open, close: close, note: note }); });
// group buttons: replace selection with the given days (latest window each, create if empty)
function selectDays(list) {
for (const k in selectedByDay) delete selectedByDay[k];
let ref = null;
list.forEach(function (d) {
let w = latestWindow(d);
if (!w) w = createWindow(d, editorTemplate());
selectedByDay[d] = w;
if (ref === null) ref = w;
});
if (ref) editorFromWindow(ref);
syncChips(); render();
}
document.getElementById('qWeekday').addEventListener('click', function () { selectDays([1,2,3,4,5]); });
document.getElementById('qWeekend').addEventListener('click', function () { selectDays([0,6]); });
document.getElementById('qAll').addEventListener('click', function () { selectDays([0,1,2,3,4,5,6]); });
document.getElementById('qNone').addEventListener('click', function () {
for (const k in selectedByDay) delete selectedByDay[k];
syncChips(); render();
}); });
/* ----- Close / Save ----- */ /* ================= Per-day render ================= */
const daysEl = document.getElementById('days');
const rowsByDow = {};
for (let d = 0; d < 7; d++) {
const row = document.createElement('div');
row.className = 'day-row';
row.innerHTML =
'<span class="dn ' + ((d === 0 || d === 6) ? 'wknd' : '') + '">' + DOW_NAMES[d] + '</span>' +
'<span class="pills"></span>' +
'<button type="button" class="dayadd">+ Add</button>';
// per-day "Add another": new window from the current editor values, selected alone
row.querySelector('.dayadd').addEventListener('click', function () {
const w = createWindow(d, editorTemplate());
for (const k in selectedByDay) delete selectedByDay[k];
selectedByDay[d] = w;
editorFromWindow(w);
syncChips(); render();
});
rowsByDow[d] = row;
daysEl.appendChild(row);
}
function selectPill(d, w) {
if (selectedByDay[d] === w) { delete selectedByDay[d]; syncChips(); render(); return; }
selectedByDay[d] = w;
editorFromWindow(w);
syncChips(); render();
}
function deleteWindow(d, w) {
const i = windows.indexOf(w);
if (i >= 0) windows.splice(i, 1);
if (selectedByDay[d] === w) delete selectedByDay[d];
syncChips(); render();
}
function render() {
for (let d = 0; d < 7; d++) {
const list = windows.filter(function (w) { return w.dow === d; })
.sort(function (a, b) { return a.open - b.open; });
const pills = rowsByDow[d].querySelector('.pills');
pills.innerHTML = '';
if (list.length === 0) {
const c = document.createElement('span');
c.className = 'closed'; c.textContent = 'Closed';
pills.appendChild(c);
continue;
}
list.forEach(function (w) {
const note = (w.note || '').trim();
const sel = selectedByDay[d] === w;
const pill = document.createElement('span');
pill.className = 'pill' + (w.close === null ? ' noend' : '') + (note ? ' hasnote' : '') + (sel ? ' sel' : '');
const lbl = document.createElement('span');
lbl.className = 'lbl';
lbl.textContent = minToStr(w.open) + (w.close === null ? ' ~' : ' ~ ' + minToStr(w.close));
if (note) lbl.title = note;
lbl.addEventListener('click', function () { selectPill(d, w); });
const x = document.createElement('button');
x.type = 'button'; x.className = 'x'; x.innerHTML = '&times;'; x.title = 'Delete';
x.addEventListener('click', function (e) { e.stopPropagation(); deleteWindow(d, w); });
pill.appendChild(lbl); pill.appendChild(x);
pills.appendChild(pill);
});
}
}
render();
/* ================= Close / Save ================= */
function closeUp(changed) { function closeUp(changed) {
if (window.parent && window.parent.closeCustomerPopup) { if (window.parent && window.parent.closeCustomerPopup) {
window.parent.closeCustomerPopup(!!changed); window.parent.closeCustomerPopup(!!changed);
@ -352,21 +448,20 @@ $existingJson = json_encode($existing, JSON_UNESCAPED_UNICODE);
} }
document.getElementById('closeBtn').addEventListener('click', function () { closeUp(false); }); document.getElementById('closeBtn').addEventListener('click', function () { closeUp(false); });
// Clear all: wipe every window from every day (not saved until "Save")
document.getElementById('clearBtn').addEventListener('click', function () {
if (windows.length === 0) return;
if (!confirm('Remove all times for every day?')) return;
windows.length = 0;
for (const k in selectedByDay) delete selectedByDay[k];
syncChips(); render();
});
document.getElementById('saveBtn').addEventListener('click', function () { document.getElementById('saveBtn').addEventListener('click', function () {
const btn = this; const btn = this;
const rows = []; const rows = windows.map(function (w) {
let bad = ''; return { dow: w.dow, open: minToStr(w.open), close: (w.close === null) ? '' : minToStr(w.close), note: (w.note || '').trim() };
for (let d = 0; d < 7; d++) {
winsByDow[d].querySelectorAll('.win').forEach(function (win) {
const open = win.querySelector('.open').value.trim();
const close = win.querySelector('.close').value.trim();
const note = win.querySelector('.note').value.trim();
if (!open) return;
if (close && close < open) { if (!bad) bad = DOW_NAMES[d] + ': end time is earlier than start time.'; return; }
rows.push({ dow: d, open: open, close: close, note: note });
}); });
}
if (bad) { alert(bad); return; }
btn.disabled = true; btn.disabled = true;
const body = new URLSearchParams(); const body = new URLSearchParams();