feat: refine seat assignment flow and profile seat preview

This commit is contained in:
hyunho
2026-03-26 10:03:13 +09:00
parent 6f5e61ca1a
commit 8efb5da65f
4 changed files with 415 additions and 19 deletions

View File

@@ -103,6 +103,10 @@ function isAdmin() {
return getSession()?.user?.role === "admin";
}
function isSlotBasedSeatMap() {
return seatMapState.seatMap?.source_type === "dxf" || seatMapState.seatMap?.source_type === "fixed_html";
}
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&")
@@ -618,6 +622,24 @@ function getDraftPlacedSlotKeys() {
.map((value) => String(value));
}
function getSeatAssignmentPayload() {
const slotMap = getSeatSlotMap();
const memberMap = getMemberMap();
return getPlacementSource()
.map((placement) => {
const slot = slotMap.get(Number(placement.seat_slot_id));
const member = memberMap.get(Number(placement.member_id));
if (!slot || !member) return null;
return {
key: String(slot.slot_key),
member_id: Number(member.id),
name: member.name || "-",
rank: member.rank || "-",
};
})
.filter(Boolean);
}
function syncSeatMapViewerFrame() {
const frame = seatMapBoard?.querySelector("#seatmap-dxf-frame");
if (!frame?.contentWindow) return;
@@ -625,6 +647,22 @@ function syncSeatMapViewerFrame() {
{ type: "seatmap-set-placed", keys: getDraftPlacedSlotKeys() },
window.location.origin,
);
frame.contentWindow.postMessage(
{ type: "seatmap-set-assignments", items: getSeatAssignmentPayload() },
window.location.origin,
);
}
function updateSeatMapDraftUi() {
if (seatMapSaveBtn) {
seatMapSaveBtn.hidden = !isAdmin() || !seatMapState.seatMap;
seatMapSaveBtn.disabled = !seatMapState.dirty;
}
if (seatMapCancelBtn) {
seatMapCancelBtn.hidden = !seatMapState.seatMap;
}
renderUnassignedMembers();
syncSeatMapViewerFrame();
}
function setupSeatMapViewerFrame() {
@@ -657,7 +695,7 @@ function setupSeatMapViewerFrame() {
const matchedSlot = (seatMapState.slots || []).find((item) => String(item.slot_key) === String(picked.key));
if (!matchedSlot) return;
upsertDraftPlacementForSlot(memberId, Number(matchedSlot.id));
renderSeatMap();
updateSeatMapDraftUi();
});
}, { once: true });
}
@@ -767,6 +805,13 @@ function handleEmbeddedNavigationMessage(event) {
hideUserPopover();
setActiveView("organization");
}
if (data.type === "seatmap-clear-slot" && isAdmin()) {
const cleared = clearDraftPlacementBySlotKey(String(data.key || ""));
if (cleared) {
setSeatMapStatus("구성원을 공석으로 이동했습니다. 저장 버튼으로 반영하세요.", "info");
updateSeatMapDraftUi();
}
}
}
async function fetchJson(url, options) {
@@ -848,6 +893,15 @@ async function getImageDimensions(file) {
});
}
function clearDraftPlacementBySlotKey(slotKey) {
const matchedSlot = (seatMapState.slots || []).find((item) => String(item.slot_key) === String(slotKey));
if (!matchedSlot) return false;
const placement = (seatMapState.draftPlacements || []).find((item) => Number(item.seat_slot_id) === Number(matchedSlot.id));
if (!placement) return false;
removeDraftPlacement(Number(placement.member_id));
return true;
}
async function uploadSeatMapImage(file, name) {
const formData = new FormData();
formData.append("file", file);
@@ -927,7 +981,7 @@ function handleSeatMapCellDrop(event) {
event.preventDefault();
const memberId = getDraggedMemberId(event);
if (!memberId) return;
if (seatMapState.seatMap?.source_type === "dxf") {
if (isSlotBasedSeatMap()) {
const slot = event.target.closest(".seatmap-slot");
if (slot) {
upsertDraftPlacementForSlot(memberId, Number(slot.dataset.slotId));
@@ -957,7 +1011,7 @@ function handleSeatMapListDrop(event) {
const memberId = getDraggedMemberId(event);
if (!memberId) return;
removeDraftPlacement(memberId);
renderSeatMap();
updateSeatMapDraftUi();
}
function setActiveView(view) {
@@ -1108,7 +1162,7 @@ if (seatMapFormImage) {
if (seatMapBoard) {
seatMapBoard.addEventListener("wheel", (event) => {
if (seatMapState.seatMap?.source_type !== "dxf") return;
if (!isSlotBasedSeatMap()) return;
event.preventDefault();
zoomDxfSeatMapAtPoint(event.clientX, event.clientY, event.deltaY < 0 ? 1.08 : 0.92);
}, { passive: false });
@@ -1119,7 +1173,7 @@ if (seatMapBoard) {
});
seatMapBoard.addEventListener("dragover", (event) => {
if (!seatMapState.editMode) return;
const target = seatMapState.seatMap?.source_type === "dxf"
const target = isSlotBasedSeatMap()
? (event.target.closest(".seatmap-slot") || event.target.closest("#seatmap-dxf-canvas"))
: event.target.closest(".seatmap-cell");
if (!target) return;
@@ -1131,7 +1185,7 @@ if (seatMapBoard) {
if (seatMapBoardWrap) {
seatMapBoardWrap.addEventListener("mousedown", (event) => {
if (seatMapState.seatMap?.source_type !== "dxf") return;
if (!isSlotBasedSeatMap()) return;
if (seatMapBoard?.querySelector("#seatmap-dxf-canvas")) return;
if (event.button !== 0) return;
if (event.target.closest(".seatmap-member-card, button, input, label")) return;
@@ -1150,7 +1204,7 @@ if (seatMapBoardWrap) {
updateSeatMapViewerHoverChip();
});
seatMapBoardWrap.addEventListener("mousemove", (event) => {
if (seatMapState.seatMap?.source_type !== "dxf") return;
if (!isSlotBasedSeatMap()) return;
if (seatMapBoard?.querySelector("#seatmap-dxf-canvas")) return;
const slot = event.target.closest(".seatmap-slot");
const nextSlotId = slot ? Number(slot.dataset.slotId) : null;
@@ -1207,7 +1261,7 @@ setActiveView(currentView);
renderAuth();
window.addEventListener("resize", () => {
if (seatMapState.seatMap?.source_type !== "dxf" || currentView !== "seatmap") return;
if (!isSlotBasedSeatMap() || currentView !== "seatmap") return;
requestAnimationFrame(() => {
if (seatMapState.zoom === 1) {
centerSeatMapBoard();