361 lines
9.4 KiB
Plaintext
361 lines
9.4 KiB
Plaintext
<%= javascript_tag do %>
|
|
function toggleDept(id) {
|
|
var children = document.querySelectorAll('.dept-children-' + id);
|
|
var toggle = document.getElementById('toggle-' + id);
|
|
children.forEach(function(el) {
|
|
if (el.style.display === 'none') {
|
|
el.style.display = 'flex';
|
|
toggle.textContent = '[-]';
|
|
} else {
|
|
el.style.display = 'none';
|
|
toggle.textContent = '[+]';
|
|
}
|
|
});
|
|
}
|
|
<% end %>
|
|
|
|
<style>
|
|
.org-chart-container {
|
|
font-family: 'Malgun Gothic', Arial, sans-serif;
|
|
padding: 30px;
|
|
background: #f5f5f5;
|
|
min-height: 80vh;
|
|
overflow-x: auto;
|
|
}
|
|
.org-chart-title {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
color: #333;
|
|
}
|
|
.org-level {
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
position: relative;
|
|
padding: 20px 0;
|
|
}
|
|
.org-level::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
border-left: 2px solid #ccc;
|
|
height: 20px;
|
|
}
|
|
.org-level.first-level::before {
|
|
display: none;
|
|
}
|
|
.org-level-label {
|
|
position: absolute;
|
|
left: 20px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: #e9ecef;
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
color: #666;
|
|
}
|
|
.org-node {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin: 10px 15px;
|
|
position: relative;
|
|
}
|
|
.org-node::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -20px;
|
|
left: 50%;
|
|
border-left: 2px solid #ccc;
|
|
height: 20px;
|
|
}
|
|
.org-level.first-level .org-node::before {
|
|
display: none;
|
|
}
|
|
.org-card {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
padding: 15px 20px;
|
|
min-width: 160px;
|
|
text-align: center;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
position: relative;
|
|
border-top: 4px solid #6c757d;
|
|
cursor: pointer;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
.org-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
}
|
|
.org-card.org-ceo {
|
|
border-top-color: #dc3545;
|
|
min-width: 200px;
|
|
background: linear-gradient(to bottom, #fff5f5, #fff);
|
|
}
|
|
.org-card.org-executive {
|
|
border-top-color: #6f42c1;
|
|
background: linear-gradient(to bottom, #f8f5ff, #fff);
|
|
}
|
|
.org-card.org-division {
|
|
border-top-color: #007bff;
|
|
}
|
|
.org-card.org-team {
|
|
border-top-color: #28a745;
|
|
}
|
|
.org-type {
|
|
font-size: 10px;
|
|
color: #999;
|
|
margin-bottom: 5px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
.org-card h4 {
|
|
margin: 0 0 8px 0;
|
|
color: #333;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
}
|
|
.org-leader {
|
|
font-size: 13px;
|
|
color: #007bff;
|
|
margin-bottom: 5px;
|
|
}
|
|
.org-leader.no-leader {
|
|
color: #999;
|
|
font-style: italic;
|
|
}
|
|
.org-leader.acting {
|
|
color: #856404;
|
|
}
|
|
.org-members {
|
|
font-size: 11px;
|
|
color: #666;
|
|
}
|
|
.org-connector {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
position: relative;
|
|
height: 30px;
|
|
}
|
|
.org-connector::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
border-left: 2px solid #ccc;
|
|
height: 30px;
|
|
}
|
|
.org-branch {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin: 0 10px;
|
|
}
|
|
.org-branch-children {
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
position: relative;
|
|
padding-top: 20px;
|
|
}
|
|
.org-branch-children::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
border-left: 2px solid #ccc;
|
|
height: 20px;
|
|
}
|
|
.org-branch-children-vertical {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
position: relative;
|
|
padding-top: 15px;
|
|
margin-top: 5px;
|
|
}
|
|
.org-branch-children-vertical::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
border-left: 2px solid #ccc;
|
|
height: 15px;
|
|
}
|
|
.org-node-vertical {
|
|
position: relative;
|
|
margin: 5px 0;
|
|
}
|
|
.org-node-vertical::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: -20px;
|
|
border-top: 2px solid #ccc;
|
|
width: 20px;
|
|
}
|
|
.org-node-vertical:first-child::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: -20px;
|
|
border-left: 2px solid #ccc;
|
|
height: 50%;
|
|
transform: translateY(-100%);
|
|
}
|
|
.org-node-vertical:last-child::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -20px;
|
|
border-left: 2px solid #ccc;
|
|
height: 50%;
|
|
}
|
|
.org-node-vertical:not(:first-child):not(:last-child)::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -20px;
|
|
border-left: 2px solid #ccc;
|
|
height: 100%;
|
|
}
|
|
.org-branch-children-vertical .org-card {
|
|
min-width: 140px;
|
|
padding: 10px 15px;
|
|
}
|
|
.org-branch-children-vertical .org-card h4 {
|
|
font-size: 13px;
|
|
}
|
|
.no-data {
|
|
text-align: center;
|
|
padding: 50px;
|
|
color: #999;
|
|
}
|
|
.horizontal-line {
|
|
height: 2px;
|
|
background: #ccc;
|
|
margin: 0 20px;
|
|
position: relative;
|
|
}
|
|
</style>
|
|
|
|
<div class="org-chart-container">
|
|
<h2 class="org-chart-title">조직도</h2>
|
|
|
|
<p style="text-align: center; margin-bottom: 20px;">
|
|
<%= link_to '조직도 관리', '/org_chart', class: 'button' %>
|
|
</p>
|
|
|
|
<% ceo = Department.ceo.first %>
|
|
<% if ceo %>
|
|
<%# CEO 레벨 %>
|
|
<div class="org-level first-level">
|
|
<div class="org-node" style="margin: 0;">
|
|
<div class="org-card org-ceo" onclick="location.href='<%= department_path(ceo) %>'">
|
|
<div class="org-type">CEO</div>
|
|
<h4><%= ceo.name %></h4>
|
|
<% if ceo.effective_leader %>
|
|
<div class="org-leader <%= 'acting' if ceo.has_acting_leader? %>">
|
|
<%= ceo.has_acting_leader? ? '대결: ' : '' %><%= ceo.effective_leader.name %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<% executives = Department.executives.sorted %>
|
|
<% if executives.any? %>
|
|
<div class="org-connector"></div>
|
|
|
|
<%# C-Level 레벨 %>
|
|
<div class="org-level" style="background: rgba(111, 66, 193, 0.05); border-radius: 8px; margin: 0 50px;">
|
|
<span class="org-level-label">C-Level</span>
|
|
<% executives.each do |exec| %>
|
|
<div class="org-node">
|
|
<div class="org-card org-executive" onclick="location.href='<%= department_path(exec) %>'">
|
|
<div class="org-type"><%= exec.type_name %></div>
|
|
<h4><%= exec.name %></h4>
|
|
<% if exec.effective_leader %>
|
|
<div class="org-leader <%= 'acting' if exec.has_acting_leader? %>">
|
|
<%= exec.has_acting_leader? ? '대결: ' : '' %><%= exec.effective_leader.name %>
|
|
</div>
|
|
<% else %>
|
|
<div class="org-leader no-leader">(담당자)</div>
|
|
<% end %>
|
|
<div class="org-members"><%= exec.all_members.count %>명</div>
|
|
</div>
|
|
|
|
<%# C-Level 산하 본부/팀 %>
|
|
<% exec_divisions = Department.divisions.where(parent_id: exec.id).sorted %>
|
|
<% exec_teams = Department.teams.where(parent_id: exec.id).sorted %>
|
|
<% if exec_divisions.any? || exec_teams.any? %>
|
|
<div class="org-branch-children">
|
|
<% exec_divisions.each do |div| %>
|
|
<%= render_org_branch(div) %>
|
|
<% end %>
|
|
<% exec_teams.each do |team| %>
|
|
<div class="org-node">
|
|
<div class="org-card org-team" onclick="location.href='<%= department_path(team) %>'">
|
|
<div class="org-type">팀</div>
|
|
<h4><%= team.name %></h4>
|
|
<% if team.effective_leader %>
|
|
<div class="org-leader <%= 'acting' if team.has_acting_leader? %>">
|
|
<%= team.has_acting_leader? ? '대결: ' : '' %><%= team.effective_leader.name %>
|
|
</div>
|
|
<% else %>
|
|
<div class="org-leader no-leader">(팀장 미지정)</div>
|
|
<% end %>
|
|
<div class="org-members"><%= team.all_members.count %>명</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%# CEO 직속 본부/팀 (C-Level 거치지 않는) %>
|
|
<% direct_divisions = Department.divisions.where(parent_id: [nil, ceo.id]).sorted %>
|
|
<% direct_teams = Department.teams.where(parent_id: [nil, ceo.id]).sorted %>
|
|
|
|
<% if direct_divisions.any? || direct_teams.any? %>
|
|
<div class="org-connector"></div>
|
|
|
|
<div class="org-level" style="background: rgba(0, 123, 255, 0.05); border-radius: 8px; margin: 0 50px;">
|
|
<span class="org-level-label">본부/팀</span>
|
|
<% direct_divisions.each do |div| %>
|
|
<%= render_org_branch(div) %>
|
|
<% end %>
|
|
<% direct_teams.each do |team| %>
|
|
<div class="org-node">
|
|
<div class="org-card org-team" onclick="location.href='<%= department_path(team) %>'">
|
|
<div class="org-type">팀</div>
|
|
<h4><%= team.name %></h4>
|
|
<% if team.effective_leader %>
|
|
<div class="org-leader <%= 'acting' if team.has_acting_leader? %>">
|
|
<%= team.has_acting_leader? ? '대결: ' : '' %><%= team.effective_leader.name %>
|
|
</div>
|
|
<% else %>
|
|
<div class="org-leader no-leader">(팀장 미지정)</div>
|
|
<% end %>
|
|
<div class="org-members"><%= team.all_members.count %>명</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% else %>
|
|
<div class="no-data">
|
|
<p>조직도가 설정되지 않았습니다.</p>
|
|
<p><%= link_to 'CEO 등록하기', new_department_path(type: 'ceo'), class: 'button-positive' %></p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|