217 lines
8.8 KiB
PHP
217 lines
8.8 KiB
PHP
<?php
|
|
/* =====================================================================
|
|
* /lib/customer_closing_days_popup.php
|
|
* - Customer Closing Days (closure date ranges) 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" within current/future only; past ranges are not shown and are left untouched
|
|
* ===================================================================== */
|
|
|
|
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 = [];
|
|
|
|
$clean = [];
|
|
foreach ($rows as $r) {
|
|
$start = trim($r['start'] ?? '');
|
|
$end = trim($r['end'] ?? '');
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $start)) continue; // start date required
|
|
if ($end === '') $end = $start; // empty end = same day
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $end)) continue;
|
|
if ($end < $start) { $tmp = $start; $start = $end; $end = $tmp; } // auto-fix if reversed
|
|
|
|
$note = isset($r['note']) ? trim($r['note']) : '';
|
|
$note = utf8_cut($note, 200);
|
|
|
|
$clean[] = ['start' => $start, 'end' => $end, 'note' => $note];
|
|
}
|
|
|
|
usort($clean, function ($a, $b) { return strcmp($a['start'], $b['start']); });
|
|
|
|
$uEsc = addslashes($user);
|
|
|
|
// full replace
|
|
// full replace — 현재/미래만 관리, 과거 행은 건드리지 않음
|
|
qry("DELETE FROM tbl_customer_closing_days
|
|
WHERE ccd_customer_uid = " . $c_uid . " AND ccd_end_date >= CURDATE()");
|
|
|
|
foreach ($clean as $r) {
|
|
$noteV = ($r['note'] === '') ? "NULL" : "'" . addslashes($r['note']) . "'";
|
|
qry("INSERT INTO tbl_customer_closing_days
|
|
(ccd_customer_uid, ccd_start_date, ccd_end_date, ccd_note, ccd_createdby, ccd_created_at)
|
|
VALUES
|
|
($c_uid, '{$r['start']}', '{$r['end']}', $noteV, '$uEsc', NOW())");
|
|
}
|
|
|
|
echo json_encode(['ok' => true]);
|
|
exit;
|
|
}
|
|
|
|
/* =====================================================================
|
|
* GET: load existing (current/future only — past is not shown)
|
|
* ===================================================================== */
|
|
$existing = [];
|
|
if ($c_uid > 0) {
|
|
$res = qry("SELECT ccd_start_date, ccd_end_date, ccd_note
|
|
FROM tbl_customer_closing_days
|
|
WHERE ccd_customer_uid = $c_uid AND ccd_end_date >= CURDATE()
|
|
ORDER BY ccd_start_date");
|
|
while ($row = fetch_array($res)) {
|
|
$existing[] = [
|
|
'start' => $row['ccd_start_date'],
|
|
'end' => $row['ccd_end_date'],
|
|
'note' => $row['ccd_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>Closing Days</title>
|
|
<style>
|
|
* { 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; }
|
|
h2 { font-size: 16px; margin: 0 0 10px; }
|
|
.toolbar { display:flex; gap:8px; flex-wrap:wrap; margin-bottom: 10px; }
|
|
.add { border: 1px solid #7fb069; background: #7fb069; color: #fff; border-radius: 4px; font-size: 13px; padding: 6px 12px; cursor: pointer; }
|
|
.add:hover { background: #6da356; }
|
|
.rows { display: flex; flex-direction: column; gap: 6px; }
|
|
.empty { color: #bbb; font-size: 13px; padding: 6px 0; }
|
|
.row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 0; }
|
|
.row input { padding: 5px 7px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; }
|
|
.row input[type=date] { width: 150px; }
|
|
.row input.note { flex: 1; min-width: 120px; }
|
|
.tilde { color: #999; }
|
|
|
|
.rm { border: none; background: #fbecec; color: #c0392b; border-radius: 4px; width: 26px; height: 28px; cursor: pointer; font-size: 15px; line-height: 1; }
|
|
.rm:hover { background: #c0392b; color: #fff; }
|
|
.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; }
|
|
.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:disabled { opacity: .6; cursor: default; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h2>Closing Days</h2>
|
|
|
|
<div class="toolbar">
|
|
<button type="button" class="add" id="addBtn">+ Add closing</button>
|
|
</div>
|
|
|
|
<div class="rows" id="rows"></div>
|
|
|
|
<div class="footer">
|
|
<button type="button" class="btn" id="closeBtn">Close</button>
|
|
<button type="button" class="btn btn-save" id="saveBtn">Save</button>
|
|
</div>
|
|
|
|
<script>
|
|
const existing = <?=$existingJson?> || [];
|
|
const C_UID = <?=$c_uid?>;
|
|
|
|
const rowsEl = document.getElementById('rows');
|
|
|
|
function esc(s){ return (s||'').replace(/"/g,'"'); }
|
|
|
|
function makeRow(d) {
|
|
d = d || { start: '', end: '', note: '' };
|
|
const row = document.createElement('div');
|
|
row.className = 'row';
|
|
row.innerHTML =
|
|
'<input type="date" class="start" value="' + (d.start || '') + '">' +
|
|
'<span class="tilde">~</span>' +
|
|
'<input type="date" class="end" value="' + (d.end || '') + '">' +
|
|
'<input type="text" class="note" maxlength="200" placeholder="Note (optional)" value="' + esc(d.note) + '">' +
|
|
'<button type="button" class="rm" title="Delete">×</button>';
|
|
row.querySelector('.rm').addEventListener('click', function () { row.remove(); refreshEmpty(); });
|
|
return row;
|
|
}
|
|
|
|
function refreshEmpty() {
|
|
const has = rowsEl.querySelector('.row');
|
|
let emptyEl = rowsEl.querySelector('.empty');
|
|
if (!has && !emptyEl) {
|
|
emptyEl = document.createElement('div');
|
|
emptyEl.className = 'empty';
|
|
emptyEl.textContent = 'No closing days.';
|
|
rowsEl.appendChild(emptyEl);
|
|
} else if (has && emptyEl) {
|
|
emptyEl.remove();
|
|
}
|
|
}
|
|
|
|
existing.forEach(function (d) { rowsEl.appendChild(makeRow(d)); });
|
|
refreshEmpty();
|
|
|
|
document.getElementById('addBtn').addEventListener('click', function () { rowsEl.appendChild(makeRow()); refreshEmpty(); });
|
|
|
|
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); });
|
|
|
|
document.getElementById('saveBtn').addEventListener('click', function () {
|
|
const btn = this;
|
|
const rows = [];
|
|
let bad = false;
|
|
document.querySelectorAll('.row').forEach(function (row) {
|
|
const start = row.querySelector('.start').value.trim();
|
|
let end = row.querySelector('.end').value.trim();
|
|
const note = row.querySelector('.note').value.trim();
|
|
if (!start) return;
|
|
if (!end) end = start;
|
|
if (end < start) bad = true;
|
|
rows.push({ start: start, end: end, note: note });
|
|
});
|
|
if (bad) { alert('Some rows have an end date earlier than the start date.'); return; }
|
|
|
|
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>
|