483 lines
21 KiB
PHP
483 lines
21 KiB
PHP
<?php
|
|
/* =====================================================================
|
|
* /lib/customer_access_time_popup.php
|
|
* - Customer Access Time (weekly pickup-availability windows) editor popup
|
|
* - Opened as a modal (iframe) from customer_detail.php; reusable on other screens
|
|
* - On successful save, notifies parent via window.parent.closeCustomerPopup(true)
|
|
* - Save = "full replace": delete the customer's active rows, re-insert current input
|
|
* - Top editor = dual sliders (06:00~18:00 / 10-min step, end optional & off by
|
|
* 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";
|
|
require_once getenv("DOCUMENT_ROOT")."/assets/dbCon.php";
|
|
|
|
$c_uid = isset($_REQUEST['c_uid']) ? intval($_REQUEST['c_uid']) : 0;
|
|
$goStr = $_GET['goStr'] ?? '';
|
|
$user = $_SESSION['ss_INITIAL'] ?? ($_SESSION['ss_UID'] ?? 'system');
|
|
|
|
// mbstring 미설치 환경 대비: UTF-8 문자 기준 N자로 자르기
|
|
if (!function_exists('utf8_cut')) {
|
|
function utf8_cut($s, $n) {
|
|
if (function_exists('mb_substr')) return mb_substr($s, 0, $n);
|
|
preg_match_all('/./us', (string)$s, $mm);
|
|
return (count($mm[0]) <= $n) ? $s : implode('', array_slice($mm[0], 0, $n));
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
* AJAX save
|
|
* ===================================================================== */
|
|
if (($_POST['action'] ?? '') === 'save') {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
if ($c_uid <= 0) { echo json_encode(['ok' => false, 'msg' => 'invalid c_uid']); exit; }
|
|
|
|
$rows = json_decode($_POST['data'] ?? '[]', true);
|
|
if (!is_array($rows)) $rows = [];
|
|
|
|
// validate + clean
|
|
$clean = [];
|
|
foreach ($rows as $r) {
|
|
$dow = isset($r['dow']) ? intval($r['dow']) : -1;
|
|
if ($dow < 0 || $dow > 6) continue;
|
|
|
|
$open = trim($r['open'] ?? '');
|
|
if (!preg_match('/^\d{2}:\d{2}$/', $open)) continue; // start time required
|
|
|
|
$close = trim($r['close'] ?? '');
|
|
if ($close !== '' && !preg_match('/^\d{2}:\d{2}$/', $close)) continue; // end optional (open-ended)
|
|
|
|
$note = isset($r['note']) ? trim($r['note']) : '';
|
|
$note = utf8_cut($note, 200);
|
|
|
|
$clean[] = ['dow' => $dow, 'open' => $open, 'close' => $close, 'note' => $note];
|
|
}
|
|
|
|
// sort by dow then start time, assign seq within each dow
|
|
usort($clean, function ($a, $b) {
|
|
if ($a['dow'] !== $b['dow']) return $a['dow'] - $b['dow'];
|
|
return strcmp($a['open'], $b['open']);
|
|
});
|
|
|
|
$uEsc = addslashes($user);
|
|
|
|
// full replace
|
|
qry("DELETE FROM tbl_customer_accesstime WHERE ca_customer_uid = " . $c_uid);
|
|
|
|
$seqByDow = [];
|
|
foreach ($clean as $r) {
|
|
$d = $r['dow'];
|
|
$seqByDow[$d] = isset($seqByDow[$d]) ? $seqByDow[$d] + 1 : 1;
|
|
$seq = $seqByDow[$d];
|
|
|
|
$openV = "'" . $r['open'] . ":00'";
|
|
$closeV = ($r['close'] === '') ? "NULL" : "'" . $r['close'] . ":00'";
|
|
$noteV = ($r['note'] === '') ? "NULL" : "'" . addslashes($r['note']) . "'";
|
|
|
|
qry("INSERT INTO tbl_customer_accesstime
|
|
(ca_customer_uid, ca_day_of_week, ca_seq, ca_open_time, ca_close_time, ca_note,
|
|
ca_status, ca_createdby, ca_created_at)
|
|
VALUES
|
|
($c_uid, $d, $seq, $openV, $closeV, $noteV, 'A', '$uEsc', NOW())");
|
|
}
|
|
|
|
echo json_encode(['ok' => true]);
|
|
exit;
|
|
}
|
|
|
|
/* =====================================================================
|
|
* GET: load existing
|
|
* ===================================================================== */
|
|
$existing = []; // dow => [ {open, close, note}, ... ]
|
|
if ($c_uid > 0) {
|
|
$res = qry("SELECT ca_day_of_week, ca_seq, ca_open_time, ca_close_time, ca_note
|
|
FROM tbl_customer_accesstime
|
|
WHERE ca_customer_uid = $c_uid AND ca_status = 'A'
|
|
ORDER BY ca_day_of_week, ca_seq");
|
|
while ($row = fetch_array($res)) {
|
|
$existing[(int)$row['ca_day_of_week']][] = [
|
|
'open' => substr($row['ca_open_time'], 0, 5),
|
|
'close' => $row['ca_close_time'] ? substr($row['ca_close_time'], 0, 5) : '',
|
|
'note' => $row['ca_note'],
|
|
];
|
|
}
|
|
}
|
|
$existingJson = json_encode($existing, JSON_UNESCAPED_UNICODE);
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Access Time</title>
|
|
<style>
|
|
* { box-sizing: border-box; }
|
|
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: 15px; margin: 0 0 8px; }
|
|
|
|
/* ---- Top editor: bound to the currently selected window(s) ---- */
|
|
.editor { border: 1px solid #cfe0cf; background: #f4f9f1; border-radius: 8px; padding: 11px 12px; margin-bottom: 12px; }
|
|
.slrow { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
|
|
.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; }
|
|
|
|
/* big, easy-to-hit End checkbox */
|
|
.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; }
|
|
.slrow.end.off .endlbl { color: #999; }
|
|
.endlbl input { width: 18px; height: 18px; flex: none; cursor: pointer; accent-color: #d9932f; }
|
|
|
|
.ed-line { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
|
|
.ed-line input.note { padding: 6px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; flex: 1; min-width: 90px; }
|
|
|
|
.chips { display: flex; gap: 5px; flex-wrap: wrap; }
|
|
.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; }
|
|
.hint { font-size: 11px; color: #8aa07d; margin-top: 7px; line-height: 1.5; }
|
|
|
|
/* ---- Per-day summary: time-only pills + per-day add ---- */
|
|
.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; }
|
|
.dn { width: 38px; flex: none; font-weight: 600; }
|
|
.dn.wknd { color: #c0392b; }
|
|
.pills { flex: 1; display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
|
|
.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; }
|
|
.pill.noend { background: #fdf3e6; border-color: #f0d9b5; color: #a06a2a; }
|
|
.pill.sel { box-shadow: 0 0 0 2px #7fb069; }
|
|
.pill.noend.sel { box-shadow: 0 0 0 2px #d9932f; }
|
|
.pill .lbl { font-variant-numeric: tabular-nums; }
|
|
.pill .x { border: none; background: transparent; color: inherit; cursor: pointer; font-size: 15px; line-height: 1; padding: 0 5px; opacity: .55; }
|
|
.pill .x:hover { opacity: 1; }
|
|
.pill.hasnote .lbl::after { content: " *"; color: #d9932f; font-weight: 700; }
|
|
.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: 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-save { background: #7fb069; border-color: #7fb069; color: #fff; }
|
|
.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; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h2>Access Time</h2>
|
|
|
|
<!-- Top editor: bound to the selected window(s); moving sliders edits them live -->
|
|
<div class="editor">
|
|
<div class="slrow start">
|
|
<span class="slkind">Start</span>
|
|
<input type="range" id="openR">
|
|
<span class="sltime" id="openT">09:00</span>
|
|
</div>
|
|
<div class="slrow end off" id="endRow">
|
|
<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="qWeekend">Weekend</button>
|
|
<button type="button" class="qbtn" id="qAll">Everyday</button>
|
|
<button type="button" class="qbtn" id="qNone">Unselect</button>
|
|
<input type="text" id="noteIn" class="note" maxlength="200" placeholder="Note (optional)">
|
|
</div>
|
|
<div class="hint">Pick day(s) or click a time to select it — 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 id="days"></div>
|
|
|
|
<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 btn-save" id="saveBtn">Save</button>
|
|
</div>
|
|
|
|
<script>
|
|
const DOW_NAMES = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
|
|
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 C_UID = <?=$c_uid?>;
|
|
|
|
function pad(n){ return String(n).padStart(2,'0'); }
|
|
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; }
|
|
function clampRange(v){ return Math.max(DAY_MIN, Math.min(DAY_MAX, v)); }
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 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++) {
|
|
(existing[d] || []).forEach(function (w) {
|
|
const o = strToMin(w.open);
|
|
if (o !== null) windows.push({ id: nextId++, dow: d, open: o, close: strToMin(w.close), note: w.note || '' });
|
|
});
|
|
}
|
|
const selectedByDay = {}; // dow -> window obj
|
|
|
|
function latestWindow(d) {
|
|
let best = null;
|
|
windows.forEach(function (w) { if (w.dow === d && (!best || w.id > best.id)) best = w; });
|
|
return best;
|
|
}
|
|
function editorTemplate() {
|
|
return { open: +openR.value, close: endChk.checked ? +closeR.value : null, note: noteIn.value.trim() };
|
|
}
|
|
function createWindow(d, tmpl) {
|
|
const w = { id: nextId++, dow: d, open: tmpl.open, close: tmpl.close, note: tmpl.note };
|
|
windows.push(w);
|
|
return w;
|
|
}
|
|
|
|
/* ================= Editor controls ================= */
|
|
const openR = document.getElementById('openR');
|
|
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');
|
|
|
|
[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++) {
|
|
const chip = document.createElement('span');
|
|
chip.className = 'chip' + ((d === 0 || d === 6) ? ' wknd' : '');
|
|
chip.dataset.dow = d;
|
|
chip.textContent = DOW_NAMES[d];
|
|
chip.addEventListener('click', function () { toggleDay(d); });
|
|
chipsEl.appendChild(chip);
|
|
}
|
|
function syncChips() {
|
|
chipsEl.querySelectorAll('.chip').forEach(function (c) {
|
|
c.classList.toggle('on', !!selectedByDay[parseInt(c.dataset.dow, 10)]);
|
|
});
|
|
}
|
|
|
|
// chip: select that day's latest window (create one if the day is empty); toggle off if already selected
|
|
function toggleDay(d) {
|
|
if (selectedByDay[d]) { delete selectedByDay[d]; syncChips(); render(); return; }
|
|
let w = latestWindow(d);
|
|
if (!w) w = createWindow(d, editorTemplate());
|
|
selectedByDay[d] = w;
|
|
editorFromWindow(w);
|
|
syncChips(); render();
|
|
}
|
|
|
|
// 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();
|
|
});
|
|
|
|
/* ================= 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 = '×'; 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) {
|
|
if (window.parent && window.parent.closeCustomerPopup) {
|
|
window.parent.closeCustomerPopup(!!changed);
|
|
} else {
|
|
if (changed) location.reload(); else history.back();
|
|
}
|
|
}
|
|
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 () {
|
|
const btn = this;
|
|
const rows = windows.map(function (w) {
|
|
return { dow: w.dow, open: minToStr(w.open), close: (w.close === null) ? '' : minToStr(w.close), note: (w.note || '').trim() };
|
|
});
|
|
|
|
btn.disabled = true;
|
|
const body = new URLSearchParams();
|
|
body.set('action', 'save');
|
|
body.set('c_uid', C_UID);
|
|
body.set('data', JSON.stringify(rows));
|
|
|
|
fetch(location.href, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString() })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (res) {
|
|
if (res && res.ok) { closeUp(true); }
|
|
else { alert('Save failed: ' + (res && res.msg ? res.msg : 'unknown')); btn.disabled = false; }
|
|
})
|
|
.catch(function (e) { alert('Save error: ' + e); btn.disabled = false; });
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|