Yanz Mini Shell
[_]
[-]
[X]
[
HomeShell 1
] [
HomeShell 2
] [
Upload
] [
Command Shell
] [
Scripting
] [
About
]
[ Directory ] =>
/
home
uncpac53
public_html
fsains.uncp.ac.id
pages
partials
Action
[*]
New File
[*]
New Folder
Sensitive File
[*]
/etc/passwd
[*]
/etc/shadow
[*]
/etc/resolv.conf
[
Delete
] [
Edit
] [
Rename
] [
Back
]
<?php // ============================================================= // pages/struktur-organisasi.php // Merender bagan struktur organisasi secara OTOMATIS dari tabel // `struktur_organisasi`. Admin cukup tambah/edit/hapus jabatan di // admin/struktur-organisasi.php — bagan di sini update sendiri. // // RESPONSIVE: satu struktur HTML, dua tampilan lewat CSS media query. // - Desktop (>900px): bagan pohon horizontal dengan garis penghubung, // zoom, dan geser (pan). // - Mobile (<=900px): otomatis berubah jadi LIST VERTIKAL bertingkat // (indentasi + garis panduan), full-width, tanpa perlu zoom/geser, // diketuk untuk buka/tutup bawahan. // ============================================================= require_once __DIR__ . '/../config/config.php'; $page_title = 'Struktur Organisasi - ' . setting('site_name'); $meta_description = 'Struktur organisasi dan jajaran pimpinan Fakultas Sains UNCP'; // Ambil semua jabatan aktif $rows = []; $res = $conn->query("SELECT * FROM struktur_organisasi WHERE is_active = 1 " . softDeleteFilter($conn, 'struktur_organisasi') . " ORDER BY sort_order ASC, id ASC"); if ($res) { while ($r = $res->fetch_assoc()) { $rows[] = $r; } } // Susun jadi tree (parent_id -> children[]). Kalau parent-nya nonaktif/terhapus, // node tersebut otomatis jadi "root" sendiri supaya bagan tidak pernah rusak/hilang. $byId = []; foreach ($rows as $r) { $byId[(int)$r['id']] = $r['id']; } $tree = []; $childrenMap = []; foreach ($rows as $r) { $pid = $r['parent_id'] !== null ? (int)$r['parent_id'] : null; if ($pid !== null && isset($byId[$pid])) { $childrenMap[$pid][] = $r; } else { $tree[] = $r; } } // Warna aksen bergilir per level (level 0 = puncak) $levelColors = ['#0a2b3e', '#00bfff', '#22c55e', '#a855f7', '#f97316']; function renderNode($node, $childrenMap, $base_url, $levelColors, $level = 0) { $id = (int)$node['id']; $full_name = trim(($node['gelar_depan'] ? $node['gelar_depan'] . ' ' : '') . $node['nama'] . ($node['gelar_belakang'] ? ', ' . $node['gelar_belakang'] : '')); $children = $childrenMap[$id] ?? []; $hasChildren = count($children) > 0; $accent = $levelColors[$level % count($levelColors)]; $photoUrl = !empty($node['foto']) ? $base_url . '/assets/images/struktur-organisasi/' . e($node['foto']) : ''; $initial = mb_strtoupper(mb_substr($node['nama'], 0, 1)); ?> <li data-level="<?= (int)$level ?>"> <div class="org-card" tabindex="0" role="button" style="--accent: <?= $accent ?>;" data-jabatan="<?= e($node['jabatan']) ?>" data-nama="<?= e($full_name) ?>" data-photo="<?= $photoUrl ?>" data-initial="<?= e($initial) ?>"> <?php if ($photoUrl): ?> <img src="<?= $photoUrl ?>" alt="<?= e($node['nama']) ?>" class="org-photo" loading="lazy"> <?php else: ?> <div class="org-photo org-photo-placeholder"><?= e($initial) ?></div> <?php endif; ?> <div class="org-text"> <div class="org-jabatan"><?= e($node['jabatan']) ?></div> <div class="org-nama"><?= e($full_name) ?></div> </div> <?php if ($hasChildren): ?> <button type="button" class="org-toggle" aria-expanded="true" aria-label="Lipat/buka bawahan"> <i class="fas fa-chevron-down"></i><span class="org-toggle-count"><?= count($children) ?></span> </button> <?php endif; ?> </div> <?php if ($hasChildren): ?> <ul> <?php foreach ($children as $child) { renderNode($child, $childrenMap, $base_url, $levelColors, $level + 1); } ?> </ul> <?php endif; ?> </li> <?php } include __DIR__ . '/../includes/header.php'; ?> <div class="page-hero" style="background: linear-gradient(135deg, #0a2b3e, #115277); color: white; padding: 60px 0 44px; margin-bottom: 0;"> <div class="container"> <nav style="font-size: 0.85rem; opacity: 0.8; margin-bottom: 16px;"> <a href="<?= $base_url ?>/index.php" style="color: #fff000; text-decoration: none;">Beranda</a> <span style="margin: 0 8px;">›</span> <span>Struktur Organisasi</span> </nav> <h1 style="font-family: 'Poppins', sans-serif; font-size: 2.2rem; font-weight: 800;" data-aos="fade-up"> Struktur Organisasi </h1> <p class="org-hero-hint" style="opacity:0.85; margin-top:8px; font-size:0.95rem;"></p> </div> </div> <div class="container" style="padding: 32px 0 60px;"> <?php if (count($tree) === 0): ?> <p style="text-align:center; color:#9ca3af; padding: 60px 0;">Struktur organisasi belum tersedia.</p> <?php else: ?> <div class="org-toolbar"> <div class="org-search"> <i class="fas fa-search"></i> <input type="text" id="orgSearch" placeholder="Cari nama atau jabatan..."> </div> <div class="org-zoom" id="orgZoomControls"> <button type="button" id="orgZoomOut" aria-label="Perkecil"><i class="fas fa-minus"></i></button> <span id="orgZoomLevel">100%</span> <button type="button" id="orgZoomIn" aria-label="Perbesar"><i class="fas fa-plus"></i></button> <button type="button" id="orgZoomReset" aria-label="Reset tampilan"><i class="fas fa-compress-arrows-alt"></i></button> </div> <div class="org-expand-controls"> <button type="button" id="orgExpandAll"><i class="fas fa-angle-double-down"></i> <span>Buka Semua</span></button> <button type="button" id="orgCollapseAll"><i class="fas fa-angle-double-up"></i> <span>Tutup Semua</span></button> </div> </div> <div class="org-chart-wrapper" id="orgWrapper"> <ul class="org-chart" id="orgChart"> <?php foreach ($tree as $root) { renderNode($root, $childrenMap, $base_url, $levelColors); } ?> </ul> </div> <!-- Modal detail --> <div class="org-modal-backdrop" id="orgModalBackdrop"> <div class="org-modal" role="dialog" aria-modal="true"> <button type="button" class="org-modal-close" id="orgModalClose" aria-label="Tutup"><i class="fas fa-times"></i></button> <div class="org-modal-photo-wrap"> <img id="orgModalPhoto" class="org-modal-photo" alt=""> <div id="orgModalInitial" class="org-modal-photo org-photo-placeholder"></div> </div> <div class="org-modal-jabatan" id="orgModalJabatan"></div> <div class="org-modal-nama" id="orgModalNama"></div> </div> </div> <?php endif; ?> </div> <style> /* ===================================================================== BASE — dipakai di semua ukuran layar ===================================================================== */ .org-toolbar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; margin-bottom: 20px; } .org-search { position: relative; flex: 1; min-width: 200px; max-width: 360px; } .org-search i { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: #94a3b8; font-size: 0.85rem; } .org-search input { width: 100%; padding: 11px 16px 11px 40px; border-radius: 999px; border: 1px solid #e5e7eb; font-size: 0.9rem; outline: none; transition: border-color 0.2s, box-shadow 0.2s; } .org-search input:focus { border-color: #00bfff; box-shadow: 0 0 0 3px rgba(0,191,255,0.15); } .org-zoom { display: flex; align-items: center; gap: 4px; background: #fff; border: 1px solid #e5e7eb; border-radius: 999px; padding: 4px; } .org-zoom button { width: 32px; height: 32px; border: none; background: transparent; border-radius: 50%; color: #0a2b3e; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s; } .org-zoom button:hover { background: #f1f5f9; } .org-zoom span { font-size: 0.78rem; font-weight: 600; color: #64748b; width: 42px; text-align: center; } .org-expand-controls { display: flex; gap: 8px; } .org-expand-controls button { border: 1px solid #e5e7eb; background: #fff; border-radius: 999px; padding: 8px 14px; font-size: 0.78rem; font-weight: 600; color: #475569; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: background 0.2s; } .org-expand-controls button:hover { background: #f8fafc; } .org-chart, .org-chart ul { list-style: none; margin: 0; padding: 0; } /* Kartu — bentuk dasarnya sama di semua layar, posisinya yang beda */ .org-card { position: relative; background: #fff; border: 1px solid #e5e7eb; border-radius: 16px; box-shadow: 0 4px 14px rgba(10,43,62,0.06); transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s; cursor: pointer; border-top: 3px solid var(--accent, #00bfff); } .org-card:hover, .org-card:focus-visible { box-shadow: 0 12px 24px rgba(10,43,62,0.14); outline: none; } .org-card.org-match { box-shadow: 0 0 0 3px var(--accent, #00bfff), 0 12px 24px rgba(10,43,62,0.14); } .org-card.org-dim { opacity: 0.3; } .org-photo { border-radius: 50%; object-fit: cover; border: 2.5px solid var(--accent, #00bfff); flex: none; } .org-photo-placeholder { display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--accent, #00bfff) 12%, white); color: var(--accent, #00bfff); font-family: 'Poppins', sans-serif; font-weight: 700; } .org-jabatan { font-family: 'Poppins', sans-serif; font-weight: 700; color: var(--accent, #00bfff); text-transform: uppercase; letter-spacing: 0.03em; } .org-nama { font-weight: 600; color: #0a2b3e; line-height: 1.3; } .org-toggle { border: none; background: transparent; cursor: pointer; display: flex; align-items: center; justify-content: center; color: #94a3b8; } .org-toggle i { transition: transform 0.25s; } li.collapsed > .org-card .org-toggle i { transform: rotate(-90deg); } /* Modal detail (sama di semua layar) */ .org-modal-backdrop { position: fixed; inset: 0; background: rgba(10,43,62,0.55); display: flex; align-items: center; justify-content: center; padding: 20px; opacity: 0; pointer-events: none; transition: opacity 0.25s; z-index: 1000; backdrop-filter: blur(2px); } .org-modal-backdrop.open { opacity: 1; pointer-events: auto; } .org-modal { background: #fff; border-radius: 24px; padding: 40px 32px 32px; text-align: center; max-width: 320px; width: 100%; position: relative; transform: scale(0.9) translateY(10px); transition: transform 0.25s; box-shadow: 0 30px 60px rgba(0,0,0,0.3); } .org-modal-backdrop.open .org-modal { transform: scale(1) translateY(0); } .org-modal-close { position: absolute; top: 14px; right: 14px; width: 32px; height: 32px; border-radius: 50%; border: none; background: #f1f5f9; color: #64748b; cursor: pointer; } .org-modal-close:hover { background: #e2e8f0; } .org-modal-photo-wrap { margin-bottom: 16px; } .org-modal-photo { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; margin: 0 auto; border: 4px solid #00bfff; display: none; } .org-modal-photo.org-photo-placeholder { display: flex; font-size: 2.2rem; border: 4px solid #00bfff; } .org-modal-jabatan { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: #00bfff; margin-bottom: 6px; } .org-modal-nama { font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 1.25rem; color: #0a2b3e; } @keyframes cardIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .org-card { animation: none !important; } } /* ===================================================================== DESKTOP / TABLET (>900px) — bagan pohon horizontal ===================================================================== */ @media (min-width: 901px) { .org-zoom { display: flex; } .org-chart-wrapper { overflow: auto; padding: 30px 10px 50px; cursor: grab; border-radius: 20px; background: radial-gradient(circle at 1px 1px, #e5eaf0 1.5px, transparent 0); background-size: 22px 22px; background-color: #fbfcfe; border: 1px solid #eef1f5; max-height: 74vh; } .org-chart-wrapper.dragging { cursor: grabbing; user-select: none; } .org-chart { zoom: 1; } .org-chart, .org-chart ul { display: flex; justify-content: center; } .org-chart { min-width: fit-content; margin: 0 auto; } .org-chart ul { padding-top: 44px; } .org-chart li { position: relative; padding: 0 16px; display: flex; flex-direction: column; align-items: center; text-align: center; } .org-chart li::before { content: ''; position: absolute; top: -24px; left: 50%; width: 0; height: 24px; border-left: 2px solid #cbd5e1; transform-origin: top; animation: growLine 0.4s ease both; } .org-chart > li::before { display: none; } .org-chart li > ul { position: relative; } .org-chart li > ul::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 0; border-top: 2px solid #cbd5e1; } .org-chart li > ul > li { padding-top: 24px; } .org-chart li > ul > li::before { top: 0; } .org-chart li > ul:has(> li:only-child)::before { display: none; } .org-chart li > ul > li:first-child::after, .org-chart li > ul > li:last-child::after { content: ''; position: absolute; top: -2px; height: 2px; width: 50%; background: #fbfcfe; } .org-chart li > ul > li:first-child::after { left: 0; } .org-chart li > ul > li:last-child::after { right: 0; } .org-chart li.collapsed > ul { display: none; } @keyframes growLine { from { transform: scaleY(0); } to { transform: scaleY(1); } } .org-card { display: flex; flex-direction: column; align-items: center; padding: 18px 18px 16px; min-width: 172px; max-width: 204px; animation: cardIn 0.4s ease both; } .org-card:hover, .org-card:focus-visible { transform: translateY(-6px); } .org-photo { width: 60px; height: 60px; margin: 0 auto 10px; } .org-photo-placeholder { font-size: 1.4rem; } .org-text { display: contents; } .org-jabatan { font-size: 0.76rem; margin-bottom: 4px; } .org-nama { font-size: 0.92rem; } .org-toggle { margin-top: 12px; border: 1px solid #e5e7eb; background: #f8fafc; border-radius: 999px; padding: 4px 12px; font-size: 0.72rem; font-weight: 600; color: #64748b; gap: 6px; } .org-toggle:hover { background: #eef2f7; } li.collapsed > .org-card .org-toggle { background: var(--accent); color: #fff; border-color: var(--accent); } .org-toggle-count::before { content: ' '; } } /* ===================================================================== MOBILE (<=900px) — list vertikal bertingkat, tanpa geser/zoom ===================================================================== */ @media (max-width: 900px) { .org-zoom { display: none; } /* tidak perlu zoom di layar sempit */ .org-chart-wrapper { background: #fff; border: 1px solid #eef1f5; border-radius: 18px; padding: 8px; box-shadow: 0 4px 16px rgba(10,43,62,0.05); } .org-chart { padding: 4px; } .org-chart ul { margin-left: 18px; padding-left: 16px; border-left: 2px dashed #e2e8f0; margin-top: 4px; margin-bottom: 6px; } .org-chart li { margin: 4px 0; } .org-chart li.collapsed > ul { display: none; } .org-card { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; text-align: left; animation: cardIn 0.3s ease both; } .org-card:active { transform: scale(0.98); } .org-photo { width: 42px; height: 42px; } .org-photo-placeholder { font-size: 1rem; } .org-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; } .org-jabatan { font-size: 0.68rem; } .org-nama { font-size: 0.88rem; white-space: normal; } .org-toggle { flex: none; width: 32px; height: 32px; border-radius: 50%; background: #f1f5f9; } li.collapsed > .org-card .org-toggle { background: var(--accent); color: #fff; } .org-toggle-count { display: none; } /* di HP cukup ikon panah, hemat tempat */ /* level puncak (Dekan) sedikit lebih menonjol */ li[data-level="0"] > .org-card { background: #f8fafc; } li[data-level="0"] > .org-card .org-photo { width: 50px; height: 50px; } li[data-level="0"] > .org-card .org-nama { font-size: 0.95rem; } .org-toolbar { gap: 10px; } .org-search { max-width: none; } .org-expand-controls button span { display: none; } } @media (max-width: 400px) { .org-expand-controls button { padding: 8px 10px; } } </style> <script> (function () { const wrapper = document.getElementById('orgWrapper'); const chart = document.getElementById('orgChart'); const heroHint = document.querySelector('.org-hero-hint'); if (!wrapper || !chart) return; function isDesktop() { return window.innerWidth > 900; } function updateHint() { if (!heroHint) return; heroHint.textContent = isDesktop() ? 'Klik kartu untuk detail • klik panah untuk lipat/buka • geser & zoom untuk menjelajah.' : 'Ketuk jabatan untuk melihat bawahannya, atau ketuk kartu untuk detail.'; } updateHint(); window.addEventListener('resize', updateHint); /* ---------- Lipat / buka cabang ---------- */ chart.addEventListener('click', function (e) { const toggle = e.target.closest('.org-toggle'); if (toggle) { e.stopPropagation(); const li = toggle.closest('li'); const isCollapsed = li.classList.toggle('collapsed'); toggle.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true'); return; } const card = e.target.closest('.org-card'); if (card) openModal(card); }); chart.addEventListener('keydown', function (e) { if ((e.key === 'Enter' || e.key === ' ') && e.target.classList.contains('org-card')) { e.preventDefault(); openModal(e.target); } }); document.getElementById('orgExpandAll').addEventListener('click', function () { chart.querySelectorAll('li.collapsed').forEach(function (li) { li.classList.remove('collapsed'); }); }); document.getElementById('orgCollapseAll').addEventListener('click', function () { chart.querySelectorAll('li').forEach(function (li) { if (li.querySelector(':scope > ul')) li.classList.add('collapsed'); }); }); /* ---------- Modal detail ---------- */ const backdrop = document.getElementById('orgModalBackdrop'); const mPhoto = document.getElementById('orgModalPhoto'); const mInitial = document.getElementById('orgModalInitial'); const mJabatan = document.getElementById('orgModalJabatan'); const mNama = document.getElementById('orgModalNama'); function openModal(card) { const photo = card.dataset.photo; mJabatan.textContent = card.dataset.jabatan || ''; mNama.textContent = card.dataset.nama || ''; if (photo) { mPhoto.src = photo; mPhoto.style.display = 'block'; mInitial.style.display = 'none'; } else { mPhoto.style.display = 'none'; mInitial.style.display = 'flex'; mInitial.textContent = card.dataset.initial || ''; mInitial.style.setProperty('--accent', getComputedStyle(card).getPropertyValue('--accent')); } backdrop.classList.add('open'); } function closeModal() { backdrop.classList.remove('open'); } document.getElementById('orgModalClose').addEventListener('click', closeModal); backdrop.addEventListener('click', function (e) { if (e.target === backdrop) closeModal(); }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeModal(); }); /* ---------- Zoom (desktop saja) ---------- */ let zoom = 1; const zoomLabel = document.getElementById('orgZoomLevel'); function applyZoom() { chart.style.zoom = zoom; if (zoomLabel) zoomLabel.textContent = Math.round(zoom * 100) + '%'; } const zi = document.getElementById('orgZoomIn'); const zo = document.getElementById('orgZoomOut'); const zr = document.getElementById('orgZoomReset'); if (zi) zi.addEventListener('click', function () { zoom = Math.min(1.6, +(zoom + 0.1).toFixed(2)); applyZoom(); }); if (zo) zo.addEventListener('click', function () { zoom = Math.max(0.5, +(zoom - 0.1).toFixed(2)); applyZoom(); }); if (zr) zr.addEventListener('click', function () { zoom = 1; applyZoom(); wrapper.scrollTo({ left: (chart.scrollWidth - wrapper.clientWidth) / 2, top: 0, behavior: 'smooth' }); }); /* ---------- Geser (drag to pan) — desktop saja ---------- */ let isDown = false, startX, startY, scrollLeft, scrollTop; wrapper.addEventListener('mousedown', function (e) { if (!isDesktop()) return; if (e.target.closest('.org-card') || e.target.closest('.org-toggle')) return; isDown = true; wrapper.classList.add('dragging'); startX = e.pageX; startY = e.pageY; scrollLeft = wrapper.scrollLeft; scrollTop = wrapper.scrollTop; }); window.addEventListener('mouseup', function () { isDown = false; wrapper.classList.remove('dragging'); }); wrapper.addEventListener('mouseleave', function () { isDown = false; wrapper.classList.remove('dragging'); }); wrapper.addEventListener('mousemove', function (e) { if (!isDown) return; e.preventDefault(); wrapper.scrollLeft = scrollLeft - (e.pageX - startX); wrapper.scrollTop = scrollTop - (e.pageY - startY); }); /* Posisikan scroll ke tengah saat pertama dibuka (desktop saja) */ if (isDesktop()) { requestAnimationFrame(function () { wrapper.scrollLeft = (chart.scrollWidth - wrapper.clientWidth) / 2; }); } /* ---------- Pencarian ---------- */ const searchInput = document.getElementById('orgSearch'); function expandAncestors(li) { let parentLi = li.parentElement && li.parentElement.closest('li'); while (parentLi) { parentLi.classList.remove('collapsed'); parentLi = parentLi.parentElement && parentLi.parentElement.closest('li'); } } searchInput.addEventListener('input', function () { const q = this.value.trim().toLowerCase(); const cards = chart.querySelectorAll('.org-card'); cards.forEach(function (card) { card.classList.remove('org-match', 'org-dim'); }); if (!q) return; let firstMatch = null; cards.forEach(function (card) { const text = (card.dataset.jabatan + ' ' + card.dataset.nama).toLowerCase(); if (text.includes(q)) { card.classList.add('org-match'); expandAncestors(card.closest('li')); if (!firstMatch) firstMatch = card; } else { card.classList.add('org-dim'); } }); if (firstMatch && !isDesktop()) firstMatch.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); })(); </script> <?php include __DIR__ . '/../includes/footer.php'; ?>
Free Space : 55389663232 Byte