feat: refine seat assignment flow and profile seat preview
This commit is contained in:
@@ -1020,13 +1020,205 @@ def build_center_chair_viewer_html(layout: dict[str, object]) -> str:
|
||||
f"const STORAGE_KEY = null;\n const placed = new Set({placed_literal});",
|
||||
1,
|
||||
)
|
||||
html = html.replace(
|
||||
""" ctx.strokeStyle = selected
|
||||
? "rgba(220, 38, 38, 0.98)"
|
||||
: active
|
||||
? "rgba(15, 118, 110, 0.98)"
|
||||
: chair.kind === "group"
|
||||
? "rgba(16, 134, 149, 0.74)"
|
||||
: "rgba(21, 149, 142, 0.8)";
|
||||
ctx.lineWidth = (selected ? 2.6 : active ? 2.1 : baseWidth) / camera.scale;""",
|
||||
""" ctx.strokeStyle = selected
|
||||
? "rgba(220, 38, 38, 0.98)"
|
||||
: "rgba(15, 118, 110, 0.88)";
|
||||
ctx.lineWidth = (selected ? 2.6 : active ? 2.0 : 1.6) / camera.scale;""",
|
||||
1,
|
||||
)
|
||||
html = html.replace(
|
||||
"function persistPlaced() {\n localStorage.setItem(STORAGE_KEY, JSON.stringify([...placed]));\n }",
|
||||
"function persistPlaced() {\n return;\n }",
|
||||
1,
|
||||
)
|
||||
html = html.replace(
|
||||
""" window.addEventListener("pointerup", (event) => {
|
||||
if (dragging && dragStart) {
|
||||
const move = Math.hypot(event.clientX - dragStart.x, event.clientY - dragStart.y);
|
||||
if (move < 4) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const picked = pickChair(event.clientX - rect.left, event.clientY - rect.top);
|
||||
if (picked) {
|
||||
if (placed.has(picked.key)) placed.delete(picked.key);
|
||||
else placed.add(picked.key);
|
||||
persistPlaced();
|
||||
}
|
||||
}
|
||||
}
|
||||
dragging = false;
|
||||
dragStart = null;
|
||||
canvas.classList.remove("dragging");
|
||||
requestDraw();
|
||||
});""",
|
||||
""" window.addEventListener("pointerup", () => {
|
||||
dragging = false;
|
||||
dragStart = null;
|
||||
canvas.classList.remove("dragging");
|
||||
requestDraw();
|
||||
});""",
|
||||
1,
|
||||
)
|
||||
html = html.replace(
|
||||
""" document.getElementById("clear-btn").addEventListener("click", () => {
|
||||
placed.clear();
|
||||
persistPlaced();
|
||||
requestDraw();
|
||||
});""",
|
||||
""" document.getElementById("clear-btn").addEventListener("click", () => {
|
||||
requestDraw();
|
||||
});""",
|
||||
1,
|
||||
)
|
||||
bridge_script = """
|
||||
<style>
|
||||
#clear-btn { display: none !important; }
|
||||
.seat-popup {
|
||||
position: absolute;
|
||||
min-width: 190px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: rgba(17,24,39,0.96);
|
||||
color: white;
|
||||
box-shadow: 0 18px 36px rgba(15,23,42,0.22);
|
||||
z-index: 4;
|
||||
}
|
||||
.seat-popup[hidden] { display: none; }
|
||||
.seat-popup strong { display: block; margin-bottom: 6px; font-size: 14px; }
|
||||
.seat-popup div { font-size: 12px; line-height: 1.45; color: rgba(255,255,255,0.82); }
|
||||
.seat-popup button {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 8px 10px;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background: rgba(220, 38, 38, 0.98);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const seatAssignments = new Map();
|
||||
let selectedChairKey = null;
|
||||
let viewerMode = "default";
|
||||
const popup = document.createElement("div");
|
||||
popup.className = "seat-popup";
|
||||
popup.hidden = true;
|
||||
document.querySelector(".viewer").appendChild(popup);
|
||||
|
||||
function getAssignment(key) {
|
||||
return seatAssignments.get(String(key)) || null;
|
||||
}
|
||||
|
||||
function hideSeatPopup() {
|
||||
popup.hidden = true;
|
||||
popup.innerHTML = "";
|
||||
}
|
||||
|
||||
function setViewerMode(mode) {
|
||||
viewerMode = mode === "compact" ? "compact" : "default";
|
||||
const head = document.querySelector(".viewer-head");
|
||||
const actions = document.querySelector(".viewer-actions");
|
||||
if (head) head.style.display = viewerMode === "compact" ? "none" : "";
|
||||
if (actions) actions.style.display = viewerMode === "compact" ? "none" : "";
|
||||
if (viewerMode === "compact") {
|
||||
hideSeatPopup();
|
||||
selectedChairKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
function showSeatPopup(chairKey, x, y) {
|
||||
const assignment = getAssignment(chairKey);
|
||||
if (!assignment) {
|
||||
hideSeatPopup();
|
||||
return;
|
||||
}
|
||||
popup.innerHTML = `
|
||||
<strong>${assignment.name}</strong>
|
||||
<div>직급: ${assignment.rank || "-"}</div>
|
||||
<div>상태: 배치완료</div>
|
||||
<button type="button" data-seatmap-delete="${chairKey}">자리 비우기</button>
|
||||
`;
|
||||
popup.style.left = `${x + 18}px`;
|
||||
popup.style.top = `${y + 18}px`;
|
||||
popup.hidden = false;
|
||||
}
|
||||
|
||||
function focusChair(chairKey, padding = 2200) {
|
||||
const chair = chairGeometry.find((item) => String(item.key) === String(chairKey));
|
||||
if (!chair) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const pad = 24;
|
||||
const minX = chair.minX - padding;
|
||||
const maxX = chair.maxX + padding;
|
||||
const minY = chair.minY - padding;
|
||||
const maxY = chair.maxY + padding;
|
||||
const width = Math.max(1, maxX - minX);
|
||||
const height = Math.max(1, maxY - minY);
|
||||
camera.scale = Math.max(0.002, Math.min(2, Math.min((rect.width - pad * 2) / width, (rect.height - pad * 2) / height)));
|
||||
camera.offsetX = pad - minX * camera.scale + (rect.width - pad * 2 - width * camera.scale) / 2;
|
||||
camera.offsetY = pad - (world.maxY - maxY + world.minY) * camera.scale + (rect.height - pad * 2 - height * camera.scale) / 2;
|
||||
requestDraw();
|
||||
}
|
||||
|
||||
function setAssignments(items) {
|
||||
seatAssignments.clear();
|
||||
placed.clear();
|
||||
(items || []).forEach((item) => {
|
||||
const key = String(item.key || "");
|
||||
if (!key) return;
|
||||
const assignment = {
|
||||
key,
|
||||
name: item.name || "-",
|
||||
rank: item.rank || "-",
|
||||
memberId: Number(item.member_id || 0),
|
||||
};
|
||||
seatAssignments.set(key, assignment);
|
||||
placed.add(key);
|
||||
});
|
||||
if (selectedChairKey && !seatAssignments.has(selectedChairKey)) {
|
||||
selectedChairKey = null;
|
||||
hideSeatPopup();
|
||||
}
|
||||
if (typeof requestDraw === "function") requestDraw();
|
||||
}
|
||||
|
||||
renderTooltip = function renderTooltipOverride() {
|
||||
if (!hovered) {
|
||||
tooltip.classList.remove("visible");
|
||||
hoverChip.textContent = "chair hover: none";
|
||||
return;
|
||||
}
|
||||
const assignment = getAssignment(hovered.key);
|
||||
hoverChip.textContent = assignment
|
||||
? `chair hover: ${assignment.name}`
|
||||
: "chair hover: 공석";
|
||||
tooltip.innerHTML = assignment
|
||||
? `
|
||||
<strong>${assignment.name}</strong>
|
||||
<div>직급: ${assignment.rank || "-"}</div>
|
||||
<div>상태: 배치완료</div>
|
||||
`
|
||||
: `
|
||||
<strong>공석</strong>
|
||||
<div>좌석: ${hovered.key}</div>
|
||||
<div>상태: 미배치</div>
|
||||
`;
|
||||
tooltip.style.left = `${pointer.x + 14}px`;
|
||||
tooltip.style.top = `${pointer.y + 14}px`;
|
||||
tooltip.classList.add("visible");
|
||||
};
|
||||
|
||||
window.__mhSeatmap = {
|
||||
getCanvas() { return document.getElementById("canvas"); },
|
||||
pickChairAt(x, y) { return typeof pickChair === "function" ? pickChair(x, y) : null; },
|
||||
@@ -1034,14 +1226,60 @@ def build_center_chair_viewer_html(layout: dict[str, object]) -> str:
|
||||
placed.clear();
|
||||
(keys || []).forEach((key) => placed.add(String(key)));
|
||||
if (typeof requestDraw === "function") requestDraw();
|
||||
}
|
||||
},
|
||||
setAssignments,
|
||||
focusChair,
|
||||
setViewerMode,
|
||||
};
|
||||
|
||||
canvas.addEventListener("click", (event) => {
|
||||
if (viewerMode === "compact") return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const picked = window.__mhSeatmap.pickChairAt(
|
||||
event.clientX - rect.left,
|
||||
event.clientY - rect.top,
|
||||
);
|
||||
if (!picked) {
|
||||
selectedChairKey = null;
|
||||
hideSeatPopup();
|
||||
if (typeof requestDraw === "function") requestDraw();
|
||||
return;
|
||||
}
|
||||
selectedChairKey = seatAssignments.has(String(picked.key)) ? String(picked.key) : null;
|
||||
if (selectedChairKey) showSeatPopup(selectedChairKey, event.clientX - rect.left, event.clientY - rect.top);
|
||||
else hideSeatPopup();
|
||||
if (typeof requestDraw === "function") requestDraw();
|
||||
});
|
||||
|
||||
popup.addEventListener("click", (event) => {
|
||||
const button = event.target.closest("[data-seatmap-delete]");
|
||||
if (!button) return;
|
||||
const slotKey = String(button.dataset.seatmapDelete || "");
|
||||
if (!slotKey) return;
|
||||
selectedChairKey = null;
|
||||
hideSeatPopup();
|
||||
window.parent.postMessage({ type: "seatmap-clear-slot", key: slotKey }, window.location.origin);
|
||||
});
|
||||
|
||||
canvas.addEventListener("contextmenu", (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
const data = event.data;
|
||||
if (!data || typeof data !== "object") return;
|
||||
if (data.type === "seatmap-set-placed") {
|
||||
window.__mhSeatmap.setPlaced(Array.isArray(data.keys) ? data.keys : []);
|
||||
}
|
||||
if (data.type === "seatmap-set-assignments") {
|
||||
window.__mhSeatmap.setAssignments(Array.isArray(data.items) ? data.items : []);
|
||||
}
|
||||
if (data.type === "seatmap-focus-chair") {
|
||||
window.__mhSeatmap.focusChair(String(data.key || ""), Number(data.padding || 2200));
|
||||
}
|
||||
if (data.type === "seatmap-set-mode") {
|
||||
window.__mhSeatmap.setViewerMode(String(data.mode || "default"));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user