Fix seatmap slot matching and update member modal layout
This commit is contained in:
@@ -7,6 +7,7 @@ let isListMode = false;
|
||||
let emptyStateMessage = '서버에 조직 데이터가 없습니다. 상단의 업로드 버튼으로 초기 데이터를 넣어주세요.';
|
||||
let photoPreviewObjectUrl = null;
|
||||
let seatMapLayoutCache = null;
|
||||
const seatMapOfficeKeys = ['technical-development-center', 'hanmac-building-6f', 'hanmac-building-7f'];
|
||||
|
||||
const levelOrder = ['부서', '그룹', '디비전', '팀', '셀'];
|
||||
const dropdownFields = ['소속회사', '직급', '직책', ...levelOrder];
|
||||
@@ -147,23 +148,28 @@ async function loadMembers(message) {
|
||||
render();
|
||||
}
|
||||
|
||||
async function loadActiveSeatMapLayout(force = false) {
|
||||
async function loadSeatMapLayouts(force = false) {
|
||||
if (seatMapLayoutCache && !force) {
|
||||
return seatMapLayoutCache;
|
||||
}
|
||||
try {
|
||||
const activePayload = await apiFetch('/api/seat-maps/active');
|
||||
const seatMap = activePayload?.item;
|
||||
if (!seatMap?.id) {
|
||||
seatMapLayoutCache = null;
|
||||
return null;
|
||||
}
|
||||
const layoutPayload = await apiFetch(`/api/seat-maps/${seatMap.id}/layout`);
|
||||
seatMapLayoutCache = layoutPayload;
|
||||
return layoutPayload;
|
||||
const layouts = (await Promise.all(seatMapOfficeKeys.map(async (officeKey) => {
|
||||
try {
|
||||
const activePayload = await apiFetch(`/api/seat-maps/active?office_key=${encodeURIComponent(officeKey)}`);
|
||||
const seatMap = activePayload?.item;
|
||||
if (!seatMap?.id) {
|
||||
return null;
|
||||
}
|
||||
return await apiFetch(`/api/seat-maps/${seatMap.id}/layout`);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}))).filter(Boolean);
|
||||
seatMapLayoutCache = layouts;
|
||||
return layouts;
|
||||
} catch {
|
||||
seatMapLayoutCache = null;
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,22 +178,62 @@ function handleSeatMapLayoutUpdated() {
|
||||
loadMembers().catch(() => { });
|
||||
}
|
||||
|
||||
function getMemberSeatInfo(layout, memberId) {
|
||||
if (!layout || !memberId) {
|
||||
function getMemberSeatInfo(layouts, memberId) {
|
||||
if (!Array.isArray(layouts) || !memberId) {
|
||||
return null;
|
||||
}
|
||||
const placement = (layout.placements || []).find((item) => Number(item.member_id) === Number(memberId));
|
||||
if (!placement) {
|
||||
return null;
|
||||
for (const layout of layouts) {
|
||||
const placement = (layout.placements || []).find((item) => Number(item.member_id) === Number(memberId));
|
||||
if (!placement) {
|
||||
continue;
|
||||
}
|
||||
const slot = (layout.slots || []).find((item) => Number(item.id) === Number(placement.seat_slot_id));
|
||||
return {
|
||||
layout,
|
||||
seatMapId: layout.seat_map?.id || null,
|
||||
seatMapName: layout.seat_map?.name || '자리배치도',
|
||||
seatLabel: placement.seat_label || slot?.label || '',
|
||||
slotKey: slot?.slot_key || '',
|
||||
assigned: true,
|
||||
};
|
||||
}
|
||||
const slot = (layout.slots || []).find((item) => Number(item.id) === Number(placement.seat_slot_id));
|
||||
return {
|
||||
seatMapId: layout.seat_map?.id || null,
|
||||
seatMapName: layout.seat_map?.name || '자리배치도',
|
||||
seatLabel: placement.seat_label || slot?.label || '',
|
||||
slotKey: slot?.slot_key || '',
|
||||
assigned: true,
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildSeatAssignments(layout) {
|
||||
if (!layout || !Array.isArray(layout.placements) || !Array.isArray(layout.members) || !Array.isArray(layout.slots)) {
|
||||
return [];
|
||||
}
|
||||
return layout.placements.map((placement) => {
|
||||
const slot = layout.slots.find((item) => Number(item.id) === Number(placement.seat_slot_id));
|
||||
const memberItem = layout.members.find((item) => Number(item.id) === Number(placement.member_id));
|
||||
if (!slot || !memberItem) return null;
|
||||
return {
|
||||
key: String(slot.slot_key || ''),
|
||||
member_id: Number(memberItem.id),
|
||||
name: memberItem.name || '-',
|
||||
rank: memberItem.rank || '-',
|
||||
};
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
function applySeatPreviewFrameState(frame, seatInfo, layout) {
|
||||
if (!frame?.contentWindow || !seatInfo?.slotKey) {
|
||||
return;
|
||||
}
|
||||
const postState = () => {
|
||||
if (!frame.contentWindow) {
|
||||
return;
|
||||
}
|
||||
frame.contentWindow.postMessage({
|
||||
type: 'seatmap-set-assignments',
|
||||
items: buildSeatAssignments(layout),
|
||||
}, window.location.origin);
|
||||
frame.contentWindow.postMessage({ type: 'seatmap-set-mode', mode: 'compact' }, window.location.origin);
|
||||
frame.contentWindow.postMessage({ type: 'seatmap-focus-chair', key: seatInfo.slotKey, padding: 2600 }, window.location.origin);
|
||||
};
|
||||
postState();
|
||||
setTimeout(postState, 120);
|
||||
}
|
||||
|
||||
async function syncMembers(nextMembers) {
|
||||
@@ -953,15 +999,16 @@ async function hydrateMemberSeatPreview(member) {
|
||||
seatLabel: member['자리위치'] || '',
|
||||
slotKey: '',
|
||||
});
|
||||
const layout = await loadActiveSeatMapLayout(true);
|
||||
const layouts = await loadSeatMapLayouts(true);
|
||||
if (!document.getElementById('member-seat-preview')) {
|
||||
return;
|
||||
}
|
||||
const seatInfo = getMemberSeatInfo(layout, member.id) || {
|
||||
seatMapName: layout?.seat_map?.name || '자리배치도',
|
||||
const seatInfo = getMemberSeatInfo(layouts, member.id) || {
|
||||
layout: null,
|
||||
seatMapName: '자리배치도',
|
||||
seatLabel: member['자리위치'] || '',
|
||||
slotKey: '',
|
||||
assigned: Boolean(member['자리위치']),
|
||||
assigned: false,
|
||||
};
|
||||
target.innerHTML = renderSeatPreviewCard(seatInfo);
|
||||
if (!seatInfo.assigned || !seatInfo.seatMapId || !seatInfo.slotKey) {
|
||||
@@ -972,27 +1019,7 @@ async function hydrateMemberSeatPreview(member) {
|
||||
return;
|
||||
}
|
||||
frame.addEventListener('load', () => {
|
||||
if (!frame.contentWindow) {
|
||||
return;
|
||||
}
|
||||
frame.contentWindow.postMessage({
|
||||
type: 'seatmap-set-assignments',
|
||||
items: Array.isArray(layout?.placements) && Array.isArray(layout?.members) && Array.isArray(layout?.slots)
|
||||
? layout.placements.map((placement) => {
|
||||
const slot = layout.slots.find((item) => Number(item.id) === Number(placement.seat_slot_id));
|
||||
const memberItem = layout.members.find((item) => Number(item.id) === Number(placement.member_id));
|
||||
if (!slot || !memberItem) return null;
|
||||
return {
|
||||
key: String(slot.slot_key || ''),
|
||||
member_id: Number(memberItem.id),
|
||||
name: memberItem.name || '-',
|
||||
rank: memberItem.rank || '-',
|
||||
};
|
||||
}).filter(Boolean)
|
||||
: [],
|
||||
}, window.location.origin);
|
||||
frame.contentWindow.postMessage({ type: 'seatmap-set-mode', mode: 'compact' }, window.location.origin);
|
||||
frame.contentWindow.postMessage({ type: 'seatmap-focus-chair', key: seatInfo.slotKey, padding: 2600 }, window.location.origin);
|
||||
applySeatPreviewFrameState(frame, seatInfo, seatInfo.layout);
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
@@ -1112,7 +1139,7 @@ function openModal(id) {
|
||||
<input type="hidden" id="m-seat-hidden" value="${member['자리위치'] || ''}">
|
||||
<div class="col-span-2 member-edit-layout">
|
||||
<div class="member-edit-left-pane">
|
||||
<div class="member-photo-field">
|
||||
<div class="member-edit-profile-card">
|
||||
<label class="text-[11px] font-black text-slate-600 block">프로필 사진</label>
|
||||
<div class="member-photo-upload-card member-photo-upload-card-compact">
|
||||
<div class="member-photo-preview-wrap">
|
||||
@@ -1126,21 +1153,16 @@ function openModal(id) {
|
||||
<strong id="m-photo-file-name" class="member-photo-file-name">선택된 파일 없음</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-seat-field">
|
||||
<div id="member-seat-preview">${renderSeatPreviewCard({ assigned: false, seatLabel: member['자리위치'] || '', seatMapName: '자리배치도', slotKey: '' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-edit-right-pane">
|
||||
<div class="member-name-field">
|
||||
<label class="text-[11px] font-black text-slate-600 block">이름 (필수)</label>
|
||||
<input id="m-name" value="${member['이름'] || ''}" oninput="syncPhotoPreviewFromUrl()" class="w-full bg-slate-50 p-3 rounded-xl border font-bold text-sm outline-none">
|
||||
<div class="member-inline-info-grid member-inline-info-grid-edit">
|
||||
<div class="member-inline-info-card">
|
||||
<div class="member-name-field member-name-field-compact">
|
||||
<label class="text-[11px] font-black text-slate-600 block">이름 (필수)</label>
|
||||
<input id="m-name" value="${member['이름'] || ''}" oninput="syncPhotoPreviewFromUrl()" class="w-full bg-slate-50 p-3 rounded-xl border font-bold text-sm outline-none">
|
||||
</div>
|
||||
<div class="member-inline-info-grid member-inline-info-grid-stacked">
|
||||
<div class="member-inline-info-card member-inline-info-card-full">
|
||||
<label>사번</label>
|
||||
<input id="m-employee-id" value="${member['사번'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
|
||||
</div>
|
||||
<div class="member-inline-info-card">
|
||||
<div class="member-inline-info-card member-inline-info-card-full">
|
||||
<label>전화번호</label>
|
||||
<input id="m-phone" value="${member['전화번호'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
|
||||
</div>
|
||||
@@ -1151,6 +1173,11 @@ function openModal(id) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-edit-right-pane">
|
||||
<div class="member-seat-field member-seat-field-emphasis">
|
||||
<div id="member-seat-preview">${renderSeatPreviewCard({ assigned: false, seatLabel: member['자리위치'] || '', seatMapName: '자리배치도', slotKey: '' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${orgFields}
|
||||
|
||||
Reference in New Issue
Block a user