feat: split seatmap admin and readonly flows
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-text);
|
||||
@@ -53,6 +54,7 @@ body {
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.org-canvas {
|
||||
@@ -332,6 +333,29 @@ body {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.member-edit-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 380px) minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.member-edit-left-pane,
|
||||
.member-edit-right-pane {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.member-edit-left-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.member-seat-field {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.member-detail-top-row {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
@@ -577,6 +601,10 @@ body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.member-edit-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.member-detail-top-row,
|
||||
.member-inline-info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -593,8 +593,8 @@ function updateFabMenu() {
|
||||
const menu = document.getElementById('fab-menu');
|
||||
let html = '<button class="fab-sub shadow-xl" data-label="리스트" onclick="openListViewModal(event)">📋</button>';
|
||||
html += '<button class="fab-sub shadow-xl" data-label="조직도 인쇄(A3)" onclick="printA3()">🖨️</button>';
|
||||
html += '<button class="fab-sub shadow-xl" data-label="자리배치도" onclick="openSeatMapView(event)">🪑</button>';
|
||||
if (isAdmin) {
|
||||
html += '<button class="fab-sub shadow-xl" data-label="자리배치도" onclick="openSeatMapView(event)">🪑</button>';
|
||||
html += '<button class="fab-sub shadow-xl" data-label="조직현황 업로드" onclick="triggerUpload(event)">⬆️</button>';
|
||||
html += '<button class="fab-sub shadow-xl" data-label="신규 구성원" onclick="openAddModal(event)">👤</button>';
|
||||
html += '<button class="fab-sub shadow-xl" data-label="신규 팀/그룹/셀" onclick="openUnitAddModal(event)">🏢</button>';
|
||||
@@ -606,7 +606,7 @@ function openSeatMapView(event) {
|
||||
event.stopPropagation();
|
||||
document.getElementById('fab-container').classList.remove('active');
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: 'open-seatmap' }, '*');
|
||||
window.parent.postMessage({ type: 'open-seatmap', readOnly: !isAdmin }, '*');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,6 +960,22 @@ async function hydrateMemberSeatPreview(member) {
|
||||
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);
|
||||
}, { once: true });
|
||||
@@ -1023,6 +1039,7 @@ function openModal(id) {
|
||||
}
|
||||
|
||||
document.getElementById('modal-title').innerText = id ? '구성원 정보 수정' : '신규 구성원 추가';
|
||||
modal.querySelector('.modal-content').classList.add('wide');
|
||||
fieldsArea.className = 'flex flex-col w-full';
|
||||
fieldsArea.style.maxHeight = 'none';
|
||||
fieldsArea.style.overflowY = 'visible';
|
||||
@@ -1078,44 +1095,47 @@ function openModal(id) {
|
||||
<input type="hidden" id="m-id" value="${id || ''}">
|
||||
<input type="hidden" id="m-photo-hidden" value="${member['사진'] || ''}">
|
||||
<input type="hidden" id="m-seat-hidden" value="${member['자리위치'] || ''}">
|
||||
<div class="col-span-2 member-basic-top-row">
|
||||
<div class="member-photo-field">
|
||||
<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">
|
||||
<img id="m-photo-preview" src="${member['사진'] || getPhotoPlaceholder(member['이름'] || '')}" alt="프로필 미리보기" class="member-photo-preview">
|
||||
<div class="col-span-2 member-edit-layout">
|
||||
<div class="member-edit-left-pane">
|
||||
<div class="member-photo-field">
|
||||
<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">
|
||||
<img id="m-photo-preview" src="${member['사진'] || getPhotoPlaceholder(member['이름'] || '')}" alt="프로필 미리보기" class="member-photo-preview">
|
||||
</div>
|
||||
<div class="member-photo-upload-controls">
|
||||
<label class="member-photo-file-label" for="m-photo-file">
|
||||
<input id="m-photo-file" type="file" accept="image/png,image/jpeg,image/webp,image/gif" onchange="handlePhotoFileChange(event)">
|
||||
<span>사진 파일 선택</span>
|
||||
</label>
|
||||
<strong id="m-photo-file-name" class="member-photo-file-name">선택된 파일 없음</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-photo-upload-controls">
|
||||
<label class="member-photo-file-label" for="m-photo-file">
|
||||
<input id="m-photo-file" type="file" accept="image/png,image/jpeg,image/webp,image/gif" onchange="handlePhotoFileChange(event)">
|
||||
<span>사진 파일 선택</span>
|
||||
</label>
|
||||
<strong id="m-photo-file-name" class="member-photo-file-name">선택된 파일 없음</strong>
|
||||
</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">
|
||||
<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">
|
||||
<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>
|
||||
<div class="member-inline-info-card member-inline-info-card-full">
|
||||
<label>이메일</label>
|
||||
<input id="m-email" value="${member['이메일'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
<div class="member-inline-info-card member-inline-info-card-full">
|
||||
<label>이메일</label>
|
||||
<input id="m-email" value="${member['이메일'] || ''}" class="w-full bg-white p-3 rounded-xl border font-bold text-sm outline-none">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="text-[11px] font-black text-slate-600 block mb-2">자리 위치</label>
|
||||
${renderSeatPreviewCard(member['자리위치'] || '')}
|
||||
</div>
|
||||
</div>
|
||||
${orgFields}
|
||||
@@ -1130,6 +1150,9 @@ function openModal(id) {
|
||||
<button onclick="saveMember()" class="flex-1 bg-indigo-600 text-white py-3.5 rounded-xl font-bold text-sm">저장</button>
|
||||
`;
|
||||
modal.style.display = 'flex';
|
||||
if (id) {
|
||||
hydrateMemberSeatPreview(member);
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
|
||||
Reference in New Issue
Block a user