티스토리 뷰

WEB/JavaScript

전단계 결재자 포함 조직도

silverline79 2026. 3. 5. 07:13

결재팝업_체크선택_직속 상관부터 최하단 파트원까지.html
0.01MB

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>전단계 결재자 포함 조직도</title>
    <style>
        /* (기존 스타일 유지 및 일부 최적화) */
        :root { --primary: #007bff; --bg: #f4f7f9; --border: #ced4da; }
        body { margin: 0; font-family: 'Malgun Gothic', sans-serif; background: var(--bg); display: flex; justify-content: center; align-items: center; height: 100vh; }
        .popup { width: 1100px; height: 850px; background: #fff; display: flex; box-shadow: 0 10px 25px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; }
        .left-panel { width: 420px; border-right: 1px solid var(--border); display: flex; flex-direction: column; }
        .panel-title { padding: 12px; background: #f8f9fa; font-weight: bold; font-size: 14px; border-bottom: 1px solid var(--border); }
        .tree-container { flex: 1; overflow-y: auto; padding: 15px; background: #fff; }
        ul { list-style: none; padding-left: 20px; margin: 5px 0; border-left: 1px dotted #ccc; }
        .tree-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
        .toggle-btn { width: 16px; cursor: pointer; font-weight: bold; color: #666; font-size: 16px; }
        .nested { display: none; }
        .active { display: block; }
        input[type="checkbox"] { cursor: pointer; }
        .user-node { background: #f9f9f9; border-radius: 4px; margin: 2px 0; }
        .user-node label { color: #2c3e50; font-weight: normal; }
        .dept-label { font-weight: bold; color: #333; cursor: pointer; }
        
        /* 버튼 및 테이블 스타일 */
        .controls { padding: 15px; text-align: center; background: #f1f3f5; border-top: 1px solid #ddd; }
        .btn { padding: 8px 20px; cursor: pointer; border: 1px solid var(--border); background: #fff; border-radius: 4px; font-weight: bold; }
        .btn-primary { background: var(--primary); color: white; border-color: var(--primary); }
        .list-container { height: 220px; overflow-y: auto; padding: 10px; }
        table { width: 100%; border-collapse: collapse; font-size: 12px; }
        th, td { border: 1px solid #eee; padding: 8px; text-align: center; }
        
        /* 오른쪽 패널 */
        .right-panel { flex: 1; display: flex; flex-direction: column; background: #525659; }
.toolbar { padding: 10px; background: #333; color: white; display: flex; gap: 10px; align-items: center; }
        .paper { background: white; width: 500px; min-height: 650px; padding: 40px; margin: 40px auto; transform-origin: top center; transition: 0.2s; }
    </style>
</head>
<body>

<div class="popup">
    <aside class="left-panel">
        <div class="panel-title">🏢 전단계 결재자 포함 조직도</div>
        <div class="tree-container" id="orgTree">
            <ul>
                <li>
                    <div class="tree-item">
                        <span class="toggle-btn" onclick="toggleNode(this)">-</span>
                        <input type="checkbox" class="dept-check">
                        <label class="dept-label">(주)글로벌네트워크</label>
                    </div>
                    <ul class="nested active">
                        <li class="tree-item user-node"><input type="checkbox" class="user-check" data-name="김회장" data-rank="대표이사"><label>👤 김회장 대표이사</label></li>
                        
                        <li>
                            <div class="tree-item">
                                <span class="toggle-btn" onclick="toggleNode(this)">+</span>
                                <input type="checkbox" class="dept-check">
                                <label class="dept-label">기술개발본부</label>
                            </div>
                            <ul class="nested">
                                <li class="tree-item user-node"><input type="checkbox" class="user-check" data-name="이본부" data-rank="본부장"><label>👤 이본부 본부장</label></li>
                                
                                <li>
                                    <div class="tree-item">
                                        <span class="toggle-btn" onclick="toggleNode(this)">+</span>
                                        <input type="checkbox" class="dept-check">
                                        <label class="dept-label">플랫폼개발팀</label>
                                    </div>
                                    <ul class="nested">
                                        <li class="tree-item user-node"><input type="checkbox" class="user-check" data-name="박팀장" data-rank="팀장"><label>👤 박팀장 팀장</label></li>
                                        
                                        <li>
                                            <div class="tree-item">
                                                <span class="toggle-btn" onclick="toggleNode(this)">+</span>
                                                <input type="checkbox" class="dept-check">
                                                <label class="dept-label">백엔드파트</label>
                                            </div>
                                            <ul class="nested">
                                                <li class="tree-item user-node"><input type="checkbox" class="user-check" data-name="최파트" data-rank="파트장"><label>👤 최파트 파트장</label></li>
                                                <li class="tree-item user-node"><input type="checkbox" class="user-check" data-name="정개발" data-rank="사원"><label>👤 정개발 사원</label></li>
                                            </ul>
                                        </li>
                                    </ul>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </li>
            </ul>
        </div>

        <div class="controls">
            <button class="btn btn-primary" onclick="addSelected()">결재선 추가 ▼</button>
            <button class="btn" onclick="clearList()">초기화</button>
        </div>

        <div class="panel-title">결재선 명단</div>
        <div class="list-container">
            <table>
                <thead>
                    <tr><th>순서</th><th>이름</th><th>직급</th><th>삭제</th></tr>
                </thead>
                <tbody id="approvalTable"></tbody>
            </table>
        </div>
    </aside>

    <main class="right-panel">
        <div class="toolbar">
            <button class="btn" onclick="zoom(0.1)">➕ 확대</button>
            <button class="btn" onclick="zoom(-0.1)">➖ 축소</button>
            <button class="btn" onclick="zoom(0, true)">100%</button>
        </div>
        <div class="viewer">
            <div id="paper" class="paper">
                <h2>전단계 결재자 포함 UI</h2>
                <p>각 부서(본부, 팀, 파트) 바로 아래에 소속 결재자가 배치되어 있습니다.</p>
                <p>상위 부서 체크 시 하위 부서원과 하위 조직이 모두 선택됩니다.</p>
            </div>
        </div>
    </main>
</div>

<script>
    // 1. 트리 토글
    function toggleNode(btn) {
        const nestedUl = btn.closest('li').querySelector('.nested');
        if (nestedUl) {
            nestedUl.classList.toggle('active');
            btn.textContent = nestedUl.classList.contains('active') ? '-' : '+';
        }
    }

    // 2. 부서 체크 시 하위 모든 요소(사람+하위부서) 선택
    document.addEventListener('change', function(e) {
        if (e.target.classList.contains('dept-check')) {
            const parentLi = e.target.closest('li');
            const allChildCheckboxes = parentLi.querySelectorAll('input[type="checkbox"]');
            allChildCheckboxes.forEach(cb => cb.checked = e.target.checked);
        }
    });

    // 3. 결재선 추가 로직
    let approvalList = [];
    function addSelected() {
        const userCheckboxes = document.querySelectorAll('.user-check:checked');
        userCheckboxes.forEach(cb => {
            const name = cb.getAttribute('data-name');
            const rank = cb.getAttribute('data-rank');
            if (!approvalList.some(u => u.name === name)) {
                approvalList.push({ name, rank });
            }
        });
        document.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false);
        renderTable();
    }

    function renderTable() {
        const tbody = document.getElementById('approvalTable');
        tbody.innerHTML = approvalList.map((user, i) => `
            <tr><td>${i + 1}</td><td>${user.name}</td><td>${user.rank}</td><td><button onclick="removeItem(${i})">x</button></td></tr>
        `).join('');
    }

    function removeItem(index) {
        approvalList.splice(index, 1);
        renderTable();
    }

    function clearList() {
        approvalList = [];
        renderTable();
    }

    // 4. 확대/축소
    let currentScale = 1.0;
    function zoom(delta, reset = false) {
        currentScale = reset ? 1.0 : currentScale + delta;
        document.getElementById('paper').style.transform = `scale(${currentScale})`;
    }
</script>

</body>
</html>
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함