1943 lines
75 KiB
PHP
1943 lines
75 KiB
PHP
@extends('layout.main')
|
|
<style>
|
|
.akre-layout {
|
|
align-items: stretch;
|
|
}
|
|
.akre-column {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.akre-tree-panel {
|
|
border: 1px solid #e1e7ef;
|
|
border-radius: 14px;
|
|
background: #fff;
|
|
overflow: hidden;
|
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
|
|
height: 100%;
|
|
}
|
|
.akre-tree-header {
|
|
padding: 16px 18px;
|
|
border-bottom: 1px solid #edf2f7;
|
|
background: linear-gradient(180deg, #fbfdff 0%, #f4f8fc 100%);
|
|
}
|
|
.akre-tree-body {
|
|
max-height: 150vh;
|
|
min-height: 520px;
|
|
overflow-y: auto;
|
|
scroll-behavior: smooth;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #b8c7db transparent;
|
|
}
|
|
.akre-tree-row.level-0 {
|
|
padding-left: 16px;
|
|
}
|
|
.akre-tree-node {
|
|
border-bottom: 1px solid #f1f5f9;
|
|
}
|
|
.akre-tree-node:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
.akre-tree-row {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
border: 0;
|
|
background: transparent;
|
|
padding: 11px 16px;
|
|
text-align: left;
|
|
color: #1f2937;
|
|
}
|
|
.akre-tree-row-main {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
min-width: 0;
|
|
}
|
|
.akre-tree-row-main span:last-child {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.akre-tree-row:hover {
|
|
background: #f5f9ff;
|
|
}
|
|
.akre-tree-row.active {
|
|
background: #e7f0fb;
|
|
color: #0f3d91;
|
|
font-weight: 600;
|
|
}
|
|
.akre-tree-row.level-1 {
|
|
padding-left: 34px;
|
|
}
|
|
.akre-tree-row.level-2 {
|
|
padding-left: 58px;
|
|
}
|
|
.akre-tree-row.level-3 {
|
|
padding-left: 82px;
|
|
}
|
|
.akre-tree-row.leaf {
|
|
cursor: default;
|
|
}
|
|
.akre-tree-caret {
|
|
width: 12px;
|
|
color: #6b7280;
|
|
transition: transform 0.2s ease;
|
|
flex: 0 0 12px;
|
|
}
|
|
.akre-tree-caret.expanded {
|
|
transform: rotate(90deg);
|
|
}
|
|
.akre-tree-spacer {
|
|
width: 12px;
|
|
flex: 0 0 12px;
|
|
}
|
|
.akre-tree-actions {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex: 0 0 auto;
|
|
}
|
|
.akre-tree-action {
|
|
width: 28px;
|
|
height: 28px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid #d7e3f4;
|
|
border-radius: 999px;
|
|
background: #fff;
|
|
color: #2563eb;
|
|
opacity: 0;
|
|
transition: opacity 0.18s ease, background 0.18s ease, border-color 0.18s ease;
|
|
flex: 0 0 28px;
|
|
}
|
|
.akre-tree-row:hover .akre-tree-action,
|
|
.akre-tree-row.active .akre-tree-action {
|
|
opacity: 1;
|
|
}
|
|
.akre-tree-action:hover {
|
|
background: #eff6ff;
|
|
border-color: #bfdbfe;
|
|
}
|
|
.akre-tree-action.delete {
|
|
color: #dc2626;
|
|
border-color: #fecaca;
|
|
}
|
|
.akre-tree-action.delete:hover {
|
|
background: #fef2f2;
|
|
border-color: #fca5a5;
|
|
}
|
|
.akre-tree-children {
|
|
background: #fff;
|
|
}
|
|
.akre-tree-children[hidden] {
|
|
display: none !important;
|
|
}
|
|
.akre-doc-card {
|
|
border: 1px solid #e1e7ef;
|
|
border-radius: 14px;
|
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
|
|
}
|
|
.akre-doc-toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 16px;
|
|
margin-bottom: 18px;
|
|
}
|
|
.akre-doc-actions {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
}
|
|
.akre-doc-search {
|
|
margin-bottom: 14px;
|
|
}
|
|
.akre-doc-search .form-control {
|
|
min-height: 44px;
|
|
border-radius: 10px;
|
|
background: #fbfdff;
|
|
}
|
|
.akre-pathbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 10px 12px;
|
|
border: 1px solid #dbe5f0;
|
|
border-radius: 12px;
|
|
background: #f8fbff;
|
|
margin-bottom: 14px;
|
|
}
|
|
.akre-pathbar-label {
|
|
color: #64748b;
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: .04em;
|
|
font-weight: 700;
|
|
}
|
|
.akre-pathbar-value {
|
|
color: #1e293b;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.akre-explorer-search {
|
|
margin-bottom: 14px;
|
|
}
|
|
.akre-explorer-search .form-control {
|
|
min-height: 42px;
|
|
border-radius: 10px;
|
|
background: #fbfdff;
|
|
}
|
|
.akre-browser-area {
|
|
border: 1px solid #edf2f7;
|
|
border-radius: 12px;
|
|
/* overflow: hidden; */
|
|
background: #fff;
|
|
margin-bottom: 14px;
|
|
}
|
|
.akre-browser-head {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1.6fr) 120px 160px;
|
|
gap: 12px;
|
|
padding: 10px 14px;
|
|
background: #f8fafc;
|
|
border-bottom: 1px solid #e8eef5;
|
|
color: #64748b;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
}
|
|
.akre-browser-body {
|
|
max-height: 720px;
|
|
overflow-y: auto;
|
|
scroll-behavior: smooth;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #b8c7db transparent;
|
|
}
|
|
.akre-tree-body::-webkit-scrollbar,
|
|
.akre-browser-body::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
.akre-tree-body::-webkit-scrollbar-track,
|
|
.akre-browser-body::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
border-radius: 999px;
|
|
}
|
|
.akre-tree-body::-webkit-scrollbar-thumb,
|
|
.akre-browser-body::-webkit-scrollbar-thumb {
|
|
background: rgba(148, 163, 184, 0.7);
|
|
border-radius: 999px;
|
|
border: 2px solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
.akre-tree-body::-webkit-scrollbar-thumb:hover,
|
|
.akre-browser-body::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(100, 116, 139, 0.85);
|
|
border: 2px solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
.akre-browser-row {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1.6fr) 120px 160px;
|
|
gap: 12px;
|
|
align-items: center;
|
|
width: 100%;
|
|
border: 0;
|
|
border-bottom: 1px solid #eef2f6;
|
|
background: #fff;
|
|
padding: 12px 14px;
|
|
text-align: left;
|
|
}
|
|
.akre-browser-row:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
.akre-browser-row:hover {
|
|
background: #f8fbff;
|
|
}
|
|
.akre-browser-row.active {
|
|
background: #e7f0fb;
|
|
}
|
|
.akre-browser-row.file-row {
|
|
cursor: default;
|
|
}
|
|
.akre-browser-name {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
min-width: 0;
|
|
color: #1f2937;
|
|
}
|
|
.akre-browser-name span:last-child {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.akre-browser-name-block {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
.akre-browser-subpath {
|
|
font-size: 11px;
|
|
color: #64748b;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.akre-folder-icon-big {
|
|
color: #f59e0b;
|
|
font-size: 18px;
|
|
}
|
|
.akre-browser-empty {
|
|
padding: 28px 16px;
|
|
color: #7b8aa0;
|
|
text-align: center;
|
|
}
|
|
.akre-file-icon-big {
|
|
color: #2563eb;
|
|
font-size: 18px;
|
|
}
|
|
.akre-selection-status {
|
|
color: #7b8aa0;
|
|
}
|
|
.akre-parent-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-height: 44px;
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
border: 1px solid #dbe5f0;
|
|
border-radius: 10px;
|
|
background: #f8fbff;
|
|
color: #1e293b;
|
|
font-weight: 600;
|
|
}
|
|
.akre-parent-chip small {
|
|
color: #64748b;
|
|
font-weight: 600;
|
|
}
|
|
@media (max-width: 991.98px) {
|
|
.akre-tree-body {
|
|
min-height: 320px;
|
|
max-height: 380px;
|
|
}
|
|
.akre-doc-toolbar {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
.akre-doc-actions {
|
|
justify-content: flex-start;
|
|
}
|
|
}
|
|
/* Baris Folder */
|
|
.tree-folder {
|
|
background-color: #fcfcfc;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
.tree-folder:hover {
|
|
background-color: #f4f7fb;
|
|
}
|
|
|
|
/* Container Indentasi */
|
|
.indent-container {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Garis Pandu Vertikal */
|
|
.tree-line {
|
|
display: inline-block;
|
|
width: 24px;
|
|
height: 40px; /* Sesuaikan dengan tinggi row */
|
|
border-left: 1px solid #e0e0e0;
|
|
margin-left: 12px;
|
|
position: relative;
|
|
}
|
|
|
|
/* Menghilangkan garis terakhir agar lebih rapi */
|
|
.tree-file:last-child .tree-line {
|
|
height: 20px;
|
|
}
|
|
|
|
/* Ikon dan Teks */
|
|
.folder-icon { color: #ffc107; font-size: 1.2rem; }
|
|
.file-icon { color: #6c757d; font-size: 1.1rem; }
|
|
|
|
.badge-pdf { background-color: #fff0f0; color: #e44d26; border: 1px solid #ffcccc; }
|
|
|
|
/* Aksi Folder (lebih user-friendly) */
|
|
.folder-actions {
|
|
display: inline-flex;
|
|
gap: 6px;
|
|
margin-left: 8px;
|
|
opacity: 0;
|
|
transform: translateY(-1px);
|
|
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
}
|
|
.tree-folder:hover .folder-actions {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
.btn-folder-action {
|
|
padding: 1px 2px;
|
|
font-size: 11px;
|
|
line-height: 1.2;
|
|
border-radius: 999px;
|
|
}
|
|
</style>
|
|
@section('body_main')
|
|
<div class="row g-3 akre-layout">
|
|
<div class="col-lg-4 akre-column">
|
|
<div class="akre-tree-panel">
|
|
<div class="akre-tree-header">
|
|
<h5 class="mb-1">Struktur Akreditasi</h5>
|
|
<p class="text-muted small mb-0">Pilih folder di kiri untuk memfilter dokumen di panel kanan.</p>
|
|
</div>
|
|
<div class="akre-tree-body" id="akreTreeContainer">
|
|
<div class="text-muted small p-3">Memuat instrumen...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-8 akre-column">
|
|
<div class="card akre-doc-card">
|
|
<div class="card-body p-3">
|
|
<div class="tab-content">
|
|
<div class="tab-pane fade show active">
|
|
<div class="akre-doc-toolbar">
|
|
<div>
|
|
<h4 class="mb-1">Dokumen Akreditasi</h4>
|
|
<p class="text-muted small mb-0" id="akreSubtreeHint">Buka struktur di kiri sampai folder tujuan, lalu dokumen akan tampil di kanan.</p>
|
|
</div>
|
|
<div class="akre-doc-actions">
|
|
<div class="d-flex flex-column align-items-start">
|
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary btn-sm"
|
|
id="btnDownloadMultiple"
|
|
disabled
|
|
>
|
|
<i class="ti ti-download me-1"></i>
|
|
Download Terpilih (0)
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-secondary btn-sm"
|
|
id="btnClearSelection"
|
|
disabled
|
|
>
|
|
Reset Pilihan
|
|
</button>
|
|
</div>
|
|
<span
|
|
id="selectedCount"
|
|
class="small akre-selection-status mt-1"
|
|
>
|
|
Belum ada file dipilih
|
|
</span>
|
|
</div>
|
|
@if(Auth::guard('admin')->check())
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline-primary btn-sm"
|
|
onclick="openAddAkreFromFolder('')"
|
|
>
|
|
<i class="ti ti-folder-plus me-1"></i>
|
|
Tambah Folder Akreditasi
|
|
</button>
|
|
@endif
|
|
<!-- Tambah Dokumen -->
|
|
@if(!Auth::guard('admin')->check())
|
|
<button
|
|
type="button"
|
|
class="btn btn-success btn-sm"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#modalCreateFile"
|
|
>
|
|
<i class="ti ti-plus me-1"></i>
|
|
Tambah Dokumen
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="akre-pathbar">
|
|
<span class="akre-pathbar-label">Path</span>
|
|
<span class="akre-pathbar-value" id="akrePathLabel">Belum ada folder dipilih</span>
|
|
</div>
|
|
<div class="akre-explorer-search">
|
|
<input
|
|
type="search"
|
|
id="fileNameSearch"
|
|
class="form-control"
|
|
placeholder="Cari nama file / dokumen di folder aktif"
|
|
autocomplete="off">
|
|
</div>
|
|
<div class="akre-browser-area">
|
|
<div class="akre-browser-head">
|
|
<span>Name</span>
|
|
<span>Tipe</span>
|
|
<span id="akreMetaHeader"></span>
|
|
</div>
|
|
<div class="akre-browser-body" id="akreSubtreeContainer">
|
|
<div class="akre-browser-empty">Pilih folder pada struktur kiri untuk melihat isinya.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@include('dataUnit.modal.create')
|
|
|
|
@if(Auth::guard('admin')->check())
|
|
<div class="modal fade" id="modalAkreInstrumen" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h1 class="modal-title fs-5" id="akreInstrumenModalTitle">Tambah Folder Akreditasi</h1>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form id="formAkreInstrumen" autocomplete="off">
|
|
<div class="modal-body">
|
|
<input type="hidden" id="akreModeInput" value="type">
|
|
<div class="row g-3">
|
|
<div class="col-12">
|
|
<label class="form-label fw-semibold">Lokasi Parent</label>
|
|
<div class="akre-parent-chip">
|
|
<small id="akreParentLabel">Akan ditambahkan ke</small>
|
|
<span id="akreParentDisplay">Root akreditasi</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-semibold">Level Folder Baru</label>
|
|
<input type="text" class="form-control" id="akreLevelDisplay" value="Folder Utama" readonly>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-semibold">Nama Folder Baru <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="akreNameInput" placeholder="Contoh: PAP 1 atau Tata Kelola Rumah Sakit (TKRS)" required>
|
|
<div class="form-text text-muted" id="akreNameHint">Masukkan satu nama folder sesuai level yang sedang dipilih.</div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" name="type" id="akreTypeInput">
|
|
<input type="hidden" name="segment" id="akreSegmentInput">
|
|
<input type="hidden" name="item" id="akreItemInput">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
|
|
<button type="submit" class="btn btn-primary" id="btnSaveAkreInstrumen">Tambah Folder</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<script>
|
|
// === STATE MANAGEMENT ===
|
|
let currentData = [];
|
|
let selectedIds = [];
|
|
let searchTimer;
|
|
let activeFileSearch = '';
|
|
let isAkreDataLoading = false;
|
|
let currentAkreFetchController = null;
|
|
let latestAkreFetchToken = 0;
|
|
const isAdminUser = @json(Auth::guard('admin')->check());
|
|
const btn = document.getElementById('btnDownloadMultiple');
|
|
const clearSelectionBtn = document.getElementById('btnClearSelection');
|
|
// === INITIALIZATION ===
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadAkreData().then(() => initAkreTree(true));
|
|
currentData = [];
|
|
renderAkreSubtree();
|
|
initEventListeners();
|
|
initTooltips();
|
|
configureAkreModal('');
|
|
});
|
|
|
|
function initTooltips() {
|
|
try {
|
|
if (!window.bootstrap?.Tooltip) return;
|
|
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
|
|
const existing = bootstrap.Tooltip.getInstance(el);
|
|
if (existing) existing.dispose();
|
|
new bootstrap.Tooltip(el);
|
|
});
|
|
} catch (_) {}
|
|
}
|
|
|
|
// === EVENT LISTENERS ===
|
|
function initEventListeners() {
|
|
const fileNameSearch = document.getElementById('fileNameSearch');
|
|
if (fileNameSearch) {
|
|
const runFileSearch = (value) => {
|
|
activeFileSearch = String(value || '').trim();
|
|
if (activeAkrePath) {
|
|
fetchData(activeAkrePath, 1);
|
|
} else if (activeFileSearch) {
|
|
fetchData('', 1);
|
|
} else {
|
|
currentData = [];
|
|
renderAkreSubtree();
|
|
}
|
|
};
|
|
|
|
fileNameSearch.addEventListener('input', function(e) {
|
|
clearTimeout(searchTimer);
|
|
searchTimer = setTimeout(() => {
|
|
runFileSearch(e.target.value);
|
|
}, 150);
|
|
});
|
|
|
|
fileNameSearch.addEventListener('keydown', function(e) {
|
|
if (e.key !== 'Enter') return;
|
|
e.preventDefault();
|
|
clearTimeout(searchTimer);
|
|
runFileSearch(e.target.value);
|
|
});
|
|
}
|
|
|
|
// Check All Rows
|
|
const checkAllRows = document.getElementById('checkAllRows');
|
|
if (checkAllRows) checkAllRows.addEventListener('change', function() {
|
|
const checkboxes = document.querySelectorAll('.row-checkbox');
|
|
selectedIds = [];
|
|
checkboxes.forEach(cb => {
|
|
cb.checked = this.checked;
|
|
if (this.checked) selectedIds.push(cb.value);
|
|
});
|
|
updateDownloadButton();
|
|
});
|
|
|
|
if (clearSelectionBtn) {
|
|
clearSelectionBtn.addEventListener('click', function() {
|
|
clearSelectedFiles();
|
|
});
|
|
}
|
|
}
|
|
|
|
// === CORE FUNCTIONS ===
|
|
|
|
/**
|
|
* Mengambil data dari server
|
|
*/
|
|
function fetchData(keyword = '', page = 1) {
|
|
const perPage = 100;
|
|
const requestToken = ++latestAkreFetchToken;
|
|
|
|
if (currentAkreFetchController) {
|
|
currentAkreFetchController.abort();
|
|
}
|
|
|
|
currentAkreFetchController = new AbortController();
|
|
isAkreDataLoading = true;
|
|
currentData = [];
|
|
renderAkreSubtree();
|
|
|
|
const params = new URLSearchParams({
|
|
per_page: String(perPage),
|
|
page: String(page),
|
|
});
|
|
|
|
if (keyword) {
|
|
params.set('folder_path', keyword);
|
|
}
|
|
if (activeFileSearch) {
|
|
params.set('keyword', activeFileSearch);
|
|
}
|
|
|
|
return fetch(`/datatable-akreditasi?${params.toString()}`, {
|
|
signal: currentAkreFetchController.signal,
|
|
})
|
|
.then(res => res.json())
|
|
.then(response => {
|
|
if (requestToken !== latestAkreFetchToken) return;
|
|
if (response.status) {
|
|
currentData = response.data || [];
|
|
} else {
|
|
currentData = [];
|
|
}
|
|
isAkreDataLoading = false;
|
|
renderAkreSubtree();
|
|
})
|
|
.catch(err => {
|
|
if (err?.name === 'AbortError') return;
|
|
if (requestToken !== latestAkreFetchToken) return;
|
|
console.error('Error fetching data:', err);
|
|
currentData = [];
|
|
isAkreDataLoading = false;
|
|
renderAkreSubtree();
|
|
})
|
|
.finally(() => {
|
|
if (requestToken === latestAkreFetchToken) {
|
|
isAkreDataLoading = false;
|
|
currentAkreFetchController = null;
|
|
}
|
|
});
|
|
}
|
|
// === UTILITY FUNCTIONS ===
|
|
|
|
function handleRowCheck(checkbox) {
|
|
const val = String(checkbox.value);
|
|
if (checkbox.checked) {
|
|
if (!selectedIds.includes(val)) selectedIds.push(val);
|
|
} else {
|
|
selectedIds = selectedIds.filter(id => id !== val);
|
|
const checkAllRows = document.getElementById('checkAllRows');
|
|
if (checkAllRows) checkAllRows.checked = false;
|
|
}
|
|
updateDownloadButton();
|
|
}
|
|
|
|
function clearSelectedFiles() {
|
|
selectedIds = [];
|
|
const checkAllRows = document.getElementById('checkAllRows');
|
|
if (checkAllRows) checkAllRows.checked = false;
|
|
document.querySelectorAll('.row-checkbox').forEach(cb => {
|
|
cb.checked = false;
|
|
});
|
|
updateDownloadButton();
|
|
}
|
|
|
|
function updateDownloadButton() {
|
|
const countLabel = document.getElementById('selectedCount');
|
|
const totalSelected = selectedIds.length;
|
|
const hasSelection = totalSelected > 0;
|
|
if (btn) {
|
|
btn.disabled = !hasSelection;
|
|
btn.innerHTML = `<i class="ti ti-download me-1"></i>Download Terpilih (${totalSelected})`;
|
|
}
|
|
if (clearSelectionBtn) {
|
|
clearSelectionBtn.disabled = !hasSelection;
|
|
}
|
|
if (countLabel) {
|
|
countLabel.innerText = hasSelection
|
|
? `${totalSelected} file siap diunduh`
|
|
: 'Belum ada file dipilih';
|
|
}
|
|
}
|
|
|
|
const downloadBtn = document.getElementById('btnDownloadMultiple');
|
|
if (downloadBtn) {
|
|
downloadBtn.addEventListener('click', async function(){
|
|
if(selectedIds.length === 0){
|
|
return;
|
|
}
|
|
|
|
const confirmation = await Swal.fire({
|
|
icon: 'question',
|
|
title: 'Download file terpilih?',
|
|
text: `${selectedIds.length} file akan digabung ke dalam ZIP.`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Ya, download',
|
|
cancelButtonText: 'Batal',
|
|
reverseButtons: true,
|
|
});
|
|
|
|
if (!confirmation.isConfirmed) {
|
|
return;
|
|
}
|
|
|
|
const payload = { ids: Array.from(selectedIds) };
|
|
downloadBtn.disabled = true;
|
|
if (clearSelectionBtn) clearSelectionBtn.disabled = true;
|
|
downloadBtn.innerHTML = '<i class="ti ti-loader me-1"></i>Menyiapkan ZIP...';
|
|
fetch('/download-multiple', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify(payload)
|
|
})
|
|
.then(async (res) => {
|
|
const contentType = res.headers.get('content-type') || '';
|
|
if(!res.ok || contentType.includes('application/json')){
|
|
const err = await res.json().catch(() => ({}));
|
|
throw new Error(err?.message || 'Gagal download file');
|
|
}
|
|
const blob = await res.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
const disposition = res.headers.get('content-disposition') || '';
|
|
const match = disposition.match(/filename="?([^"]+)"?/);
|
|
a.href = url;
|
|
a.download = match?.[1] || 'files.zip';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: `${selectedIds.length} file sedang diunduh.`,
|
|
timer: 1500,
|
|
showConfirmButton: false
|
|
});
|
|
clearSelectedFiles();
|
|
})
|
|
.catch(err => {
|
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.message || 'Gagal download file' });
|
|
})
|
|
.finally(() => {
|
|
updateDownloadButton();
|
|
});
|
|
});
|
|
}
|
|
|
|
function formatDateTime(value) {
|
|
if (!value) return '—';
|
|
const d = new Date(value);
|
|
if (isNaN(d)) return value;
|
|
const pad = n => String(n).padStart(2, '0');
|
|
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} `
|
|
+ `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
}
|
|
const katDok = @json($katDok);
|
|
const akreTreeSource = @json($akreditasiTree ?? []);
|
|
const formCreate = document.getElementById('formFile');
|
|
const modalCreate = document.getElementById('modalCreateFile');
|
|
let colCount = 1;
|
|
|
|
document.addEventListener('change', function(e){
|
|
if(!e.target.classList.contains('toggle-expired')) return;
|
|
const targetId = e.target.getAttribute('data-target');
|
|
if(!targetId) return;
|
|
const fieldWrap = document.getElementById(targetId);
|
|
const input = fieldWrap?.querySelector('input');
|
|
if (input) input.disabled = !e.target.checked;
|
|
});
|
|
|
|
let akreData = Array.isArray(akreTreeSource) ? akreTreeSource : [];
|
|
let akreFlat = [];
|
|
let expandedAkreNodes = new Set();
|
|
let activeAkreNodeKey = null;
|
|
let activeAkrePath = '';
|
|
let pendingAkreFocusPath = '';
|
|
|
|
function loadAkreData(){
|
|
return Promise.resolve(akreData);
|
|
}
|
|
|
|
function refreshAkreData(){
|
|
akreFlat = [];
|
|
return fetch('/json/akreditasi.jff')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
akreData = Array.isArray(data) ? data : [];
|
|
if (pendingAkreFocusPath) {
|
|
const nextNode = findAkreNodeMetaByPath(pendingAkreFocusPath);
|
|
if (nextNode) {
|
|
expandedAkreNodes = new Set(nextNode.ancestors || []);
|
|
activeAkreNodeKey = nextNode.key;
|
|
activeAkrePath = nextNode.path;
|
|
} else {
|
|
expandedAkreNodes = new Set();
|
|
activeAkreNodeKey = null;
|
|
activeAkrePath = '';
|
|
}
|
|
} else {
|
|
expandedAkreNodes = new Set();
|
|
activeAkreNodeKey = null;
|
|
activeAkrePath = '';
|
|
}
|
|
initAkreTree(true);
|
|
pendingAkreFocusPath = '';
|
|
return akreData;
|
|
})
|
|
.catch(() => {
|
|
akreData = [];
|
|
expandedAkreNodes = new Set();
|
|
activeAkreNodeKey = null;
|
|
activeAkrePath = '';
|
|
initAkreTree(true);
|
|
pendingAkreFocusPath = '';
|
|
return akreData;
|
|
});
|
|
}
|
|
|
|
function refreshAkreSelects(){
|
|
return refreshAkreData().then(() => {
|
|
document.querySelectorAll('select.akre-select').forEach(selectEl => {
|
|
const current = selectEl.value;
|
|
fillAkreSelect(selectEl);
|
|
if(current) {
|
|
// restore selection if still exists
|
|
selectEl.value = current;
|
|
}
|
|
if(window.$ && $.fn.select2){
|
|
$(selectEl).trigger('change');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function getAkreFlat(){
|
|
if(akreFlat.length) return akreFlat;
|
|
akreFlat = (akreData || []).flatMap(type => {
|
|
const segments = Array.isArray(type.segment) ? type.segment : [];
|
|
return segments.flatMap(seg => {
|
|
const children = Array.isArray(seg.turunan) ? seg.turunan : [];
|
|
return children.map(child => ({
|
|
value: `${type.name}/${seg.name}/${child.name}`,
|
|
label: `${type.name} / ${child.name}`,
|
|
type: type.name,
|
|
segment: seg.name,
|
|
item: child.name
|
|
}));
|
|
});
|
|
});
|
|
return akreFlat;
|
|
}
|
|
|
|
function fillAkreSelect(selectEl){
|
|
if(!selectEl) return;
|
|
selectEl.innerHTML = '<option value="">Pilih Instrumen</option>';
|
|
getAkreFlat().forEach(optData => {
|
|
const opt = document.createElement('option');
|
|
opt.value = optData.value;
|
|
opt.textContent = optData.label;
|
|
selectEl.appendChild(opt);
|
|
});
|
|
}
|
|
|
|
function initAkreTree(forceRender = false) {
|
|
const treeEl = document.getElementById('akreTreeContainer');
|
|
if (!treeEl) return;
|
|
|
|
if (!Array.isArray(akreData) || akreData.length === 0) {
|
|
treeEl.innerHTML = '<div class="text-muted small p-3">Data instrumen tidak tersedia.</div>';
|
|
return;
|
|
}
|
|
|
|
if (!forceRender && treeEl.dataset.rendered === 'true') {
|
|
syncAkreTreeState();
|
|
return;
|
|
}
|
|
|
|
treeEl.innerHTML = akreData.map((typeItem, typeIndex) => renderAkreTreeNode({
|
|
key: `type-${typeIndex}`,
|
|
label: `${String(typeIndex + 1).padStart(2, '0')}. ${typeItem.name || '-'}`,
|
|
path: typeItem.name || '',
|
|
level: 0,
|
|
children: (Array.isArray(typeItem.segment) ? typeItem.segment : []).map((segment, segmentIndex) => ({
|
|
key: `type-${typeIndex}-segment-${segmentIndex}`,
|
|
label: segment.name || '-',
|
|
path: `${typeItem.name || ''}/${segment.name || ''}`,
|
|
level: 1,
|
|
children: (Array.isArray(segment.turunan) ? segment.turunan : []).map((child, childIndex) => ({
|
|
key: `type-${typeIndex}-segment-${segmentIndex}-child-${childIndex}`,
|
|
label: child.name || '-',
|
|
path: `${typeItem.name || ''}/${segment.name || ''}/${child.name || ''}`,
|
|
level: 2,
|
|
children: []
|
|
}))
|
|
}))
|
|
})).join('');
|
|
|
|
treeEl.dataset.rendered = 'true';
|
|
bindAkreTreeEvents();
|
|
syncAkreTreeState();
|
|
}
|
|
|
|
function renderAkreTreeToggle({ key, label, path, level, expanded, isLeaf, active = false }) {
|
|
return `
|
|
<button type="button"
|
|
class="akre-tree-row level-${level} ${active ? 'active' : ''} ${isLeaf ? 'leaf' : ''}"
|
|
data-node-key="${e(key)}"
|
|
data-path="${e(path || '')}"
|
|
data-is-leaf="${isLeaf ? 'true' : 'false'}">
|
|
<span class="akre-tree-row-main">
|
|
${isLeaf
|
|
? '<span class="akre-tree-spacer"></span>'
|
|
: `<i class="ti ti-chevron-right akre-tree-caret ${expanded ? 'expanded' : ''}"></i>`}
|
|
<span>${label}</span>
|
|
</span>
|
|
${isAdminUser ? `
|
|
<span class="akre-tree-actions">
|
|
${level < 2 ? `
|
|
<span class="akre-tree-action"
|
|
data-add-folder="true"
|
|
data-folder-path="${e(path || '')}"
|
|
title="Tambah folder di sini">
|
|
<i class="ti ti-plus"></i>
|
|
</span>
|
|
` : ''}
|
|
<span class="akre-tree-action delete"
|
|
data-delete-folder="true"
|
|
data-folder-path="${e(path || '')}"
|
|
title="Hapus folder ini">
|
|
<i class="ti ti-trash"></i>
|
|
</span>
|
|
</span>
|
|
` : ''}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
function renderAkreTreeNode(node) {
|
|
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
return `
|
|
<div class="akre-tree-node" data-node-key="${e(node.key)}">
|
|
${renderAkreTreeToggle({
|
|
key: node.key,
|
|
label: node.label,
|
|
path: node.path,
|
|
level: node.level,
|
|
expanded: expandedAkreNodes.has(node.key),
|
|
isLeaf: !hasChildren,
|
|
active: activeAkreNodeKey === node.key
|
|
})}
|
|
${hasChildren ? `
|
|
<div class="akre-tree-children" data-children-of="${e(node.key)}" ${expandedAkreNodes.has(node.key) ? '' : 'hidden'}>
|
|
${node.children.map(child => renderAkreTreeNode(child)).join('')}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function bindAkreTreeEvents() {
|
|
const treeEl = document.getElementById('akreTreeContainer');
|
|
if (!treeEl || treeEl.dataset.bound === 'true') return;
|
|
|
|
treeEl.addEventListener('click', function(e) {
|
|
const addFolderBtn = e.target.closest('[data-add-folder="true"]');
|
|
if (addFolderBtn) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
openAddAkreFromFolder(addFolderBtn.dataset.folderPath || '');
|
|
return;
|
|
}
|
|
|
|
const deleteFolderBtn = e.target.closest('[data-delete-folder="true"]');
|
|
if (deleteFolderBtn) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
deleteAkreFolder(deleteFolderBtn.dataset.folderPath || '');
|
|
return;
|
|
}
|
|
|
|
const row = e.target.closest('.akre-tree-row');
|
|
if (!row) return;
|
|
|
|
const nodeKey = row.dataset.nodeKey || '';
|
|
const path = row.dataset.path || '';
|
|
const isLeaf = row.dataset.isLeaf === 'true';
|
|
activeAkreNodeKey = nodeKey;
|
|
activeAkrePath = path;
|
|
activeFileSearch = '';
|
|
const fileNameSearch = document.getElementById('fileNameSearch');
|
|
if (fileNameSearch) fileNameSearch.value = '';
|
|
if (!isLeaf) {
|
|
toggleAkreNode(nodeKey, false, false);
|
|
}
|
|
fetchData(path, 1);
|
|
syncAkreTreeState();
|
|
});
|
|
|
|
treeEl.dataset.bound = 'true';
|
|
}
|
|
|
|
function syncAkreTreeState() {
|
|
const treeEl = document.getElementById('akreTreeContainer');
|
|
if (!treeEl) return;
|
|
|
|
treeEl.querySelectorAll('.akre-tree-row').forEach(row => {
|
|
const nodeKey = row.dataset.nodeKey || '';
|
|
row.classList.toggle('active', nodeKey === activeAkreNodeKey);
|
|
const caret = row.querySelector('.akre-tree-caret');
|
|
if (caret) caret.classList.toggle('expanded', expandedAkreNodes.has(nodeKey));
|
|
});
|
|
|
|
treeEl.querySelectorAll('.akre-tree-children').forEach(group => {
|
|
const parentKey = group.dataset.childrenOf || '';
|
|
group.hidden = !expandedAkreNodes.has(parentKey);
|
|
});
|
|
}
|
|
|
|
function e(value) {
|
|
return String(value ?? '')
|
|
.replace(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
window.applyAkreFilter = function(nodeKey, path, syncTree = true) {
|
|
activeAkreNodeKey = nodeKey;
|
|
activeAkrePath = path || '';
|
|
fetchData(activeAkrePath, 1);
|
|
if (syncTree) syncAkreTreeState();
|
|
syncAkreSubtreeState();
|
|
}
|
|
|
|
window.toggleAkreNode = function(nodeKey, rerender = true, syncTree = true) {
|
|
if (expandedAkreNodes.has(nodeKey)) {
|
|
expandedAkreNodes.delete(nodeKey);
|
|
} else {
|
|
expandedAkreNodes.add(nodeKey);
|
|
}
|
|
if (rerender) {
|
|
initAkreTree();
|
|
} else {
|
|
if (syncTree) syncAkreTreeState();
|
|
syncAkreSubtreeState();
|
|
}
|
|
}
|
|
|
|
function findAkreNodeMetaByPath(path) {
|
|
const targetPath = String(path || '').trim();
|
|
if (!targetPath) return null;
|
|
|
|
for (let typeIndex = 0; typeIndex < akreData.length; typeIndex++) {
|
|
const typeItem = akreData[typeIndex];
|
|
const typePath = typeItem.name || '';
|
|
const typeKey = `type-${typeIndex}`;
|
|
if (typePath === targetPath) {
|
|
return {
|
|
key: typeKey,
|
|
path: typePath,
|
|
level: 0,
|
|
ancestors: []
|
|
};
|
|
}
|
|
|
|
const segments = Array.isArray(typeItem.segment) ? typeItem.segment : [];
|
|
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
|
const segment = segments[segmentIndex];
|
|
const segmentPath = `${typePath}/${segment.name || ''}`;
|
|
const segmentKey = `${typeKey}-segment-${segmentIndex}`;
|
|
if (segmentPath === targetPath) {
|
|
return {
|
|
key: segmentKey,
|
|
path: segmentPath,
|
|
level: 1,
|
|
ancestors: [typeKey]
|
|
};
|
|
}
|
|
|
|
const children = Array.isArray(segment.turunan) ? segment.turunan : [];
|
|
for (let childIndex = 0; childIndex < children.length; childIndex++) {
|
|
const child = children[childIndex];
|
|
const childPath = `${segmentPath}/${child.name || ''}`;
|
|
const childKey = `${segmentKey}-child-${childIndex}`;
|
|
if (childPath === targetPath) {
|
|
return {
|
|
key: childKey,
|
|
path: childPath,
|
|
level: 2,
|
|
ancestors: [typeKey, segmentKey]
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function findAkreNodeByKey() {
|
|
if (!activeAkreNodeKey) return null;
|
|
|
|
for (let typeIndex = 0; typeIndex < akreData.length; typeIndex++) {
|
|
const typeItem = akreData[typeIndex];
|
|
const typeKey = `type-${typeIndex}`;
|
|
if (typeKey === activeAkreNodeKey) {
|
|
return {
|
|
key: typeKey,
|
|
label: `${String(typeIndex + 1).padStart(2, '0')}. ${typeItem.name || '-'}`,
|
|
path: typeItem.name || '',
|
|
children: (Array.isArray(typeItem.segment) ? typeItem.segment : []).map((segment, segmentIndex) => ({
|
|
key: `${typeKey}-segment-${segmentIndex}`,
|
|
label: segment.name || '-',
|
|
path: `${typeItem.name || ''}/${segment.name || ''}`
|
|
}))
|
|
};
|
|
}
|
|
|
|
const segments = Array.isArray(typeItem.segment) ? typeItem.segment : [];
|
|
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
|
const segment = segments[segmentIndex];
|
|
const segmentKey = `${typeKey}-segment-${segmentIndex}`;
|
|
if (segmentKey === activeAkreNodeKey) {
|
|
return {
|
|
key: segmentKey,
|
|
label: segment.name || '-',
|
|
path: `${typeItem.name || ''}/${segment.name || ''}`,
|
|
children: (Array.isArray(segment.turunan) ? segment.turunan : []).map((child, childIndex) => ({
|
|
key: `${segmentKey}-child-${childIndex}`,
|
|
label: child.name || '-',
|
|
path: `${typeItem.name || ''}/${segment.name || ''}/${child.name || ''}`
|
|
}))
|
|
};
|
|
}
|
|
|
|
const children = Array.isArray(segment.turunan) ? segment.turunan : [];
|
|
for (let childIndex = 0; childIndex < children.length; childIndex++) {
|
|
const child = children[childIndex];
|
|
const childKey = `${segmentKey}-child-${childIndex}`;
|
|
if (childKey === activeAkreNodeKey) {
|
|
return {
|
|
key: childKey,
|
|
label: child.name || '-',
|
|
path: `${typeItem.name || ''}/${segment.name || ''}/${child.name || ''}`,
|
|
children: []
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function renderAkreSubtree() {
|
|
const subtreeEl = document.getElementById('akreSubtreeContainer');
|
|
const hintEl = document.getElementById('akreSubtreeHint');
|
|
const pathLabelEl = document.getElementById('akrePathLabel');
|
|
const metaHeaderEl = document.getElementById('akreMetaHeader');
|
|
if (!subtreeEl) return;
|
|
|
|
const activeNode = findAkreNodeByKey();
|
|
const isSearchingFiles = activeFileSearch.length > 0;
|
|
const renderFileRows = () => (currentData || []).map(item => {
|
|
const fileName = item.file ? item.file.split('/').pop() : 'Unknown';
|
|
const typeDok = fileName.split('.').pop().toUpperCase();
|
|
return `
|
|
<div class="akre-browser-row file-row">
|
|
<span class="akre-browser-name">
|
|
<input type="checkbox" class="form-check-input row-checkbox"
|
|
value="${item.file_directory_id}" ${selectedIds.includes(String(item.file_directory_id)) ? 'checked' : ''}
|
|
onchange="handleRowCheck(this)">
|
|
<i class="ti ti-file-text akre-file-icon-big"></i>
|
|
<span class="akre-browser-name-block">
|
|
<span>${item.nama_dokumen || fileName}</span>
|
|
<small class="akre-browser-subpath">${item.file || '-'}</small>
|
|
</span>
|
|
</span>
|
|
<span>${typeDok}</span>
|
|
<span class="d-inline-flex align-items-center justify-content-between gap-2">
|
|
<span>${formatDateTime(item.entry_at)}</span>
|
|
<a href="/file-download/${item.file_directory_id}" target="_blank"
|
|
class="btn btn-sm btn-outline-primary" download title="Download">
|
|
<i class="ti ti-download"></i>
|
|
</a>
|
|
</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
if (!activeNode) {
|
|
if (metaHeaderEl) metaHeaderEl.textContent = isSearchingFiles ? 'Tanggal Unggah' : 'Struktur';
|
|
if (isAkreDataLoading && isSearchingFiles) {
|
|
if (pathLabelEl) pathLabelEl.textContent = 'Semua folder akreditasi';
|
|
subtreeEl.innerHTML = '<div class="akre-browser-empty">Mencari file...</div>';
|
|
return;
|
|
}
|
|
|
|
if (isSearchingFiles) {
|
|
if (pathLabelEl) pathLabelEl.textContent = 'Semua folder akreditasi';
|
|
const globalFileRows = renderFileRows();
|
|
subtreeEl.innerHTML = globalFileRows.trim().length
|
|
? globalFileRows
|
|
: '<div class="akre-browser-empty">Tidak ada file yang cocok di semua folder.</div>';
|
|
return;
|
|
}
|
|
if (pathLabelEl) pathLabelEl.textContent = 'Belum ada folder dipilih';
|
|
subtreeEl.innerHTML = '<div class="akre-browser-empty">Pilih folder pada struktur kiri untuk melihat isinya.</div>';
|
|
return;
|
|
}
|
|
|
|
if (pathLabelEl) pathLabelEl.textContent = activeNode.path || 'Belum ada folder dipilih';
|
|
if (metaHeaderEl) metaHeaderEl.textContent = (currentData || []).length > 0 ? 'Tanggal Unggah' : 'Struktur';
|
|
|
|
if (isAkreDataLoading) {
|
|
subtreeEl.innerHTML = '<div class="akre-browser-empty">Memuat isi folder...</div>';
|
|
return;
|
|
}
|
|
|
|
const childRows = isSearchingFiles ? '' : (Array.isArray(activeNode.children) ? activeNode.children : []).map(child => `
|
|
<button type="button"
|
|
class="akre-browser-row ${activeAkreNodeKey === child.key ? 'active' : ''}"
|
|
data-browser-key="${e(child.key)}"
|
|
data-browser-path="${e(child.path)}">
|
|
<span class="akre-browser-name">
|
|
<i class="ti ti-folder akre-folder-icon-big"></i>
|
|
<span>${child.label}</span>
|
|
</span>
|
|
<span>Folder</span>
|
|
<span>Turunan</span>
|
|
</button>
|
|
`).join('');
|
|
|
|
const fileRows = renderFileRows();
|
|
|
|
const explorerHtml = `${childRows}${fileRows}`;
|
|
subtreeEl.innerHTML = explorerHtml.trim().length
|
|
? explorerHtml
|
|
: '<div class="akre-browser-empty">Folder ini belum memiliki file.</div>';
|
|
|
|
subtreeEl.dataset.bound = '';
|
|
bindAkreSubtreeEvents();
|
|
syncAkreSubtreeState();
|
|
}
|
|
|
|
function bindAkreSubtreeEvents() {
|
|
const subtreeEl = document.getElementById('akreSubtreeContainer');
|
|
if (!subtreeEl || subtreeEl.dataset.bound === 'true') return;
|
|
|
|
subtreeEl.addEventListener('click', function(e) {
|
|
const browserRow = e.target.closest('[data-browser-key]');
|
|
if (!browserRow) return;
|
|
applyAkreFilter(browserRow.dataset.browserKey || '', browserRow.dataset.browserPath || '');
|
|
});
|
|
|
|
subtreeEl.dataset.bound = 'true';
|
|
}
|
|
|
|
function syncAkreSubtreeState() {
|
|
const subtreeEl = document.getElementById('akreSubtreeContainer');
|
|
if (!subtreeEl) return;
|
|
|
|
const pathLabelEl = document.getElementById('akrePathLabel');
|
|
|
|
subtreeEl.querySelectorAll('[data-browser-key]').forEach(button => {
|
|
const key = button.dataset.browserKey || '';
|
|
button.classList.toggle('active', key === activeAkreNodeKey);
|
|
});
|
|
|
|
if (pathLabelEl) {
|
|
pathLabelEl.textContent = activeAkrePath || 'Belum ada folder dipilih';
|
|
}
|
|
}
|
|
|
|
function setKategoriRequired(index, isRequired){
|
|
const katSelect = document.getElementById(`select_kategori_${index}`);
|
|
if (!katSelect) return;
|
|
if (isRequired) {
|
|
katSelect.setAttribute('required', 'required');
|
|
} else {
|
|
katSelect.removeAttribute('required');
|
|
}
|
|
}
|
|
|
|
function resetAkreFields(index){
|
|
const selectEl = document.getElementById(`akre_select_${index}`);
|
|
const typeInput = document.getElementById(`akre_type_${index}`);
|
|
const segmentInput = document.getElementById(`akre_segment_${index}`);
|
|
const itemInput = document.getElementById(`akre_item_${index}`);
|
|
if(selectEl){
|
|
selectEl.value = '';
|
|
if(window.$ && $.fn.select2) $(selectEl).val(null).trigger('change');
|
|
}
|
|
if(typeInput) typeInput.value = '';
|
|
if(segmentInput) segmentInput.value = '';
|
|
if(itemInput) itemInput.value = '';
|
|
setKategoriRequired(index, false);
|
|
}
|
|
|
|
function enableAkreFields(index){
|
|
const selectEl = document.getElementById(`akre_select_${index}`);
|
|
setKategoriRequired(index, false);
|
|
loadAkreData().then(() => {
|
|
fillAkreSelect(selectEl);
|
|
if(window.$ && $.fn.select2){
|
|
$(selectEl).select2({
|
|
dropdownParent: $('#modalCreateFile'),
|
|
placeholder: 'Pilih Instrumen',
|
|
allowClear: true
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function initKategoriSelect2(index){
|
|
if(!window.$ || !$.fn.select2) return;
|
|
const katSelect = $(`#select_kategori_${index}`);
|
|
const hukumSelect = $(`#select_kategori_hukum_${index}`);
|
|
if(katSelect.length){
|
|
katSelect.select2({
|
|
dropdownParent: $('#modalCreateFile'),
|
|
placeholder:'Pilih Kategori',
|
|
allowClear: true
|
|
});
|
|
}
|
|
if(hukumSelect.length){
|
|
hukumSelect.select2({
|
|
dropdownParent: $('#modalCreateFile'),
|
|
placeholder:'Pilih Kategori Hukum',
|
|
allowClear:true
|
|
});
|
|
}
|
|
}
|
|
|
|
function selectOptionUnitKerjaV1(localCol){
|
|
const selectUnit = $(`#select_id_unit_kerja_${localCol}`);
|
|
const selectSubUnit = $(`#select_id_sub_unit_kerja_${localCol}`);
|
|
|
|
selectUnit.select2({
|
|
placeholder: '-- Pilih Unit Kerja --',
|
|
allowClear:true,
|
|
width: '100%',
|
|
dropdownParent: selectUnit.parent(),
|
|
ajax:{
|
|
url : '/select-unit-kerja-mapping',
|
|
dataType: 'json',
|
|
delay: 250,
|
|
data: function(params){
|
|
return { q: params.term }
|
|
},
|
|
processResults: function(data){
|
|
return {
|
|
results : (data?.data || []).map(item => ({
|
|
id: item.id+'/'+item.name,
|
|
text: item.name,
|
|
sub_units: item.sub_unit_kerja
|
|
}))
|
|
}
|
|
},
|
|
cache: true,
|
|
},
|
|
minimumInputLength: 0,
|
|
});
|
|
|
|
selectSubUnit.select2({
|
|
placeholder: '-- Pilih Sub Unit Kerja --',
|
|
allowClear: true,
|
|
width: '100%',
|
|
dropdownParent: selectSubUnit.parent()
|
|
});
|
|
|
|
selectUnit.on('select2:select', function (e) {
|
|
const data = e.params.data;
|
|
selectSubUnit.empty().append('<option value="" disabled selected>-- Pilih Sub Unit Kerja --</option>');
|
|
if (data.sub_units && data.sub_units.length > 0) {
|
|
data.sub_units.forEach(sub => {
|
|
selectSubUnit.append(`<option value="${sub.id}/${sub.name}">${sub.name}</option>`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
window.addFormV2 = function(){
|
|
const col = document.getElementById('col_add_fileV2');
|
|
if (!col) return;
|
|
const katOptions = (Array.isArray(katDok) ? katDok : [])
|
|
.map(k => `<option value="${k.master_kategori_directory_id}/${k.nama_kategori_directory}">${k.nama_kategori_directory}</option>`)
|
|
.join('');
|
|
|
|
let html = `
|
|
<div class="row g-3 align-items-start" id="col-${colCount}">
|
|
<hr class="my-3" />
|
|
<div class="col-12 d-flex justify-content-end">
|
|
<button type="button"
|
|
class="btn btn-sm btn-danger"
|
|
onclick="removeCol(${colCount})">
|
|
<i class="fa-solid fa-trash"></i> Hapus
|
|
</button>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
|
|
<select class="form-select"
|
|
name="data[${colCount}][id_unit_kerja]"
|
|
id="select_id_unit_kerja_${colCount}"
|
|
required>
|
|
<option value="" disabled selected>Pilih Unit</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
|
|
<select class="form-select"
|
|
name="data[${colCount}][id_sub_unit_kerja]"
|
|
id="select_id_sub_unit_kerja_${colCount}"
|
|
required>
|
|
<option value="" disabled selected>Pilih Sub Unit</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Nomor Dokumen</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">#</span>
|
|
<input type="text"
|
|
class="form-control"
|
|
name="data[${colCount}][no_dokumen]"
|
|
placeholder="Contoh: 001/RS/IT/I/2026">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Nama Dokumen<span class="text-danger">*</span></label>
|
|
<input type="text"
|
|
class="form-control"
|
|
name="data[${colCount}][nama_dokumen]"
|
|
placeholder="Contoh: Panduan Mencuci Tangan" required>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Tanggal Terbit</label>
|
|
<input class="form-control"
|
|
type="date"
|
|
name="data[${colCount}][date_active]">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input toggle-expired"
|
|
type="checkbox"
|
|
id="hasExpired_${colCount}"
|
|
data-target="expiredField_${colCount}">
|
|
<label class="form-check-label" for="hasExpired_${colCount}">Masa Berlaku Dokumen?</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-5" id="expiredField_${colCount}">
|
|
<label class="form-label fw-semibold">Tanggal Kedaluwarsa Dokumen</label>
|
|
<input class="form-control"
|
|
type="date"
|
|
name="data[${colCount}][tgl_expired]" disabled>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Boleh dilihat unit lain? <span class="text-danger">*</span></label>
|
|
<div class="border rounded-3 p-2 bg-light">
|
|
<div class="form-check">
|
|
<input class="form-check-input"
|
|
type="radio"
|
|
name="data[${colCount}][is_permission]"
|
|
id="perm_yes_${colCount}"
|
|
value="1"
|
|
required>
|
|
<label class="form-check-label" for="perm_yes_${colCount}">Ya</label>
|
|
</div>
|
|
<div class="form-check mt-1">
|
|
<input class="form-check-input"
|
|
type="radio"
|
|
name="data[${colCount}][is_permission]"
|
|
id="perm_no_${colCount}"
|
|
value="2"
|
|
required>
|
|
<label class="form-check-label" for="perm_no_${colCount}">Tidak</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Instrumen Akreditasi </label>
|
|
<select class="form-select akre-select" id="akre_select_${colCount}" name="data[${colCount}][akre]" style="width: 350px;">
|
|
<option value="">Pilih Instrumen</option>
|
|
</select>
|
|
<div class="form-text text-muted">Isi form ini bila dokumen yang diunggah merupakan dokumen <strong>akreditasi</strong>.</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Kategori Hukum</label>
|
|
<select class="form-select select-kat-hukum" name="data[${colCount}][kategori_hukum]" id="select_kategori_hukum_${colCount}" style="width: 350px;">
|
|
<option value="">Pilih Kategori Hukum</option>
|
|
<option value="Kebijakan - Peraturan Direktur">Kebijakan - Peraturan Direktur</option>
|
|
<option value="Kebijakan - Keputusan Direktur Utama">Kebijakan - Keputusan Direktur Utama</option>
|
|
<option value="Kebijakan - Surat Edaran">Kebijakan - Surat Edaran</option>
|
|
<option value="Kebijakan - Pengumuman">Kebijakan - Pengumuman</option>
|
|
<option value="Kerjasama - Pelayanan Kesehatan">Kerjasama - Pelayanan Kesehatan</option>
|
|
<option value="Kerjasama - Management">Kerjasama - Management</option>
|
|
<option value="Kerjasama - Pemeliharan">Kerjasama - Pemeliharan</option>
|
|
<option value="Kerjasama - Diklat">Kerjasama - Diklat</option>
|
|
<option value="Kerjasama - Luar Negeri">Kerjasama - Luar Negeri</option>
|
|
<option value="Kerjasama - Area Bisnis">Kerjasama - Area Bisnis</option>
|
|
<option value="Kerjasama - Pendidikan">Kerjasama - Pendidikan</option>
|
|
<option value="Kerjasama - Pengampuan KIA">Kerjasama- Pengampuan KIA</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-semibold">Kategori Lainnya</label>
|
|
<select class="form-select"
|
|
name="data[${colCount}][master_kategori_directory_id]"
|
|
id="select_kategori_${colCount}" style="width: 350px;">
|
|
<option value="">Pilih Kategori</option>
|
|
${katOptions}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-12">
|
|
<label for="fileUpload_${colCount}" class="form-label fw-semibold">📂 Upload Dokumen (PDF)</label>
|
|
<div class="border rounded-3 p-3 bg-white shadow-sm">
|
|
<input class="form-control"
|
|
type="file"
|
|
id="fileUpload_${colCount}"
|
|
accept=".pdf"
|
|
name="data[${colCount}][file]">
|
|
<div class="mt-2 text-success fw-semibold d-none file-name" id="fileName_${colCount}"></div>
|
|
</div>
|
|
<div class="form-text text-muted">Format yang didukung: <b>PDF</b>.</div>
|
|
</div>
|
|
</div>`;
|
|
col.insertAdjacentHTML('beforeend', html);
|
|
selectOptionUnitKerjaV1(colCount);
|
|
initKategoriSelect2(colCount);
|
|
enableAkreFields(colCount);
|
|
setKategoriRequired(colCount, false);
|
|
colCount++;
|
|
}
|
|
|
|
window.removeCol = function(count){
|
|
const el = document.getElementById(`col-${count}`);
|
|
if (el) el.remove();
|
|
}
|
|
|
|
function resetCreateForm(){
|
|
colCount = 1;
|
|
const colAdd = document.getElementById('col_add_fileV2');
|
|
if (colAdd) colAdd.innerHTML = '';
|
|
if (formCreate) {
|
|
formCreate.reset();
|
|
$(formCreate).find('select').val(null).trigger('change');
|
|
$(formCreate).find('input[type="file"]').val('');
|
|
$(formCreate).find('.file-name').addClass('d-none').text('');
|
|
}
|
|
resetAkreFields(0);
|
|
enableAkreFields(0);
|
|
}
|
|
|
|
if (formCreate) {
|
|
const select0 = $('#select_id_unit_kerja_0');
|
|
if (select0.length) selectOptionUnitKerjaV1(0);
|
|
initKategoriSelect2(0);
|
|
enableAkreFields(0);
|
|
|
|
formCreate.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
const submitBtn = formCreate.querySelector('button[type="submit"]');
|
|
if (submitBtn) submitBtn.disabled = true;
|
|
if (submitBtn) submitBtn.textContent = 'menyimpan...';
|
|
const formData = new FormData(formCreate);
|
|
|
|
fetch(`/uploadv2`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]')?.value || ''
|
|
},
|
|
body: formData
|
|
}).then(async(res) => {
|
|
const responseData = await res.json();
|
|
if(responseData.status){
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: responseData.message || 'Data berhasil disimpan.',
|
|
timer: 1500,
|
|
showConfirmButton: false
|
|
});
|
|
const modalInstance = bootstrap.Modal.getInstance(modalCreate);
|
|
modalInstance?.hide();
|
|
resetCreateForm();
|
|
if (activeAkrePath) {
|
|
fetchData(activeAkrePath, 1);
|
|
} else {
|
|
renderAkreSubtree();
|
|
}
|
|
} else {
|
|
throw new Error(responseData.message || 'Terjadi kesalahan saat menyimpan data.');
|
|
}
|
|
}).catch(err => {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Gagal',
|
|
text: err.message || 'Terjadi kesalahan.'
|
|
});
|
|
}).finally(() => {
|
|
if (submitBtn) submitBtn.disabled = false;
|
|
if (submitBtn) submitBtn.textContent = 'Simpan';
|
|
});
|
|
});
|
|
}
|
|
|
|
// Admin: tambah folder/instrumen akreditasi (disimpan di public/json/akreditasi.jff)
|
|
const formAkreInstrumen = document.getElementById('formAkreInstrumen');
|
|
const akreModeInput = document.getElementById('akreModeInput');
|
|
const akreNameInput = document.getElementById('akreNameInput');
|
|
const akreTypeInput = document.getElementById('akreTypeInput');
|
|
const akreSegmentInput = document.getElementById('akreSegmentInput');
|
|
const akreItemInput = document.getElementById('akreItemInput');
|
|
const akreParentDisplay = document.getElementById('akreParentDisplay');
|
|
const akreParentLabel = document.getElementById('akreParentLabel');
|
|
const akreLevelDisplay = document.getElementById('akreLevelDisplay');
|
|
const akreNameHint = document.getElementById('akreNameHint');
|
|
const akreModalTitle = document.getElementById('akreInstrumenModalTitle');
|
|
|
|
function sanitizeAkreFolderName(value) {
|
|
return String(value || '').replace(/[\\\/:*?"<>|]/g, '').trim();
|
|
}
|
|
|
|
function buildAkrePayload() {
|
|
const mode = String(akreModeInput?.value || 'type');
|
|
const typeValue = String(akreTypeInput?.value || '').trim();
|
|
const segmentValue = String(akreSegmentInput?.value || '').trim();
|
|
const itemValue = String(akreItemInput?.value || '').trim();
|
|
|
|
if (mode === 'segment') {
|
|
return { type: typeValue, segment: segmentValue };
|
|
}
|
|
if (mode === 'item') {
|
|
return { type: typeValue, segment: segmentValue, item: itemValue };
|
|
}
|
|
return { type: typeValue };
|
|
}
|
|
|
|
function getAkreParentPath(path) {
|
|
const parts = String(path || '').split('/').filter(Boolean);
|
|
if (parts.length <= 1) return '';
|
|
parts.pop();
|
|
return parts.join('/');
|
|
}
|
|
|
|
function getAkreCurrentParentPath() {
|
|
const mode = String(akreModeInput?.value || 'type');
|
|
const typeValue = String(akreTypeInput?.value || '').trim();
|
|
const segmentValue = String(akreSegmentInput?.value || '').trim();
|
|
|
|
if (mode === 'item') {
|
|
return [typeValue, segmentValue].filter(Boolean).join('/');
|
|
}
|
|
if (mode === 'segment') {
|
|
return typeValue;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function configureAkreModal(folderPath = '') {
|
|
const parts = String(folderPath || '').split('/').filter(Boolean);
|
|
const depth = parts.length;
|
|
|
|
if (akreTypeInput) akreTypeInput.value = parts[0] || '';
|
|
if (akreSegmentInput) akreSegmentInput.value = parts[1] || '';
|
|
if (akreItemInput) akreItemInput.value = '';
|
|
if (akreNameInput) akreNameInput.value = '';
|
|
|
|
if (depth >= 2) {
|
|
if (akreModeInput) akreModeInput.value = 'item';
|
|
if (akreModalTitle) akreModalTitle.textContent = 'Tambah Item Akreditasi';
|
|
if (akreParentLabel) akreParentLabel.textContent = 'Akan ditambahkan ke';
|
|
if (akreParentDisplay) akreParentDisplay.textContent = folderPath;
|
|
if (akreLevelDisplay) akreLevelDisplay.value = 'Item';
|
|
if (akreNameHint) akreNameHint.textContent = 'Masukkan nama item baru untuk segment yang sedang dipilih.';
|
|
const saveBtn = document.getElementById('btnSaveAkreInstrumen');
|
|
if (saveBtn) saveBtn.textContent = 'Tambah Item';
|
|
} else if (depth === 1) {
|
|
if (akreModeInput) akreModeInput.value = 'segment';
|
|
if (akreModalTitle) akreModalTitle.textContent = 'Tambah Subfolder Akreditasi';
|
|
if (akreParentLabel) akreParentLabel.textContent = 'Akan ditambahkan ke';
|
|
if (akreParentDisplay) akreParentDisplay.textContent = folderPath;
|
|
if (akreLevelDisplay) akreLevelDisplay.value = 'Subfolder';
|
|
if (akreNameHint) akreNameHint.textContent = 'Masukkan nama subfolder baru di bawah folder utama yang dipilih.';
|
|
const saveBtn = document.getElementById('btnSaveAkreInstrumen');
|
|
if (saveBtn) saveBtn.textContent = 'Tambah Subfolder';
|
|
} else {
|
|
if (akreModeInput) akreModeInput.value = 'type';
|
|
if (akreModalTitle) akreModalTitle.textContent = 'Tambah Folder Akreditasi';
|
|
if (akreParentLabel) akreParentLabel.textContent = 'Akan ditambahkan ke';
|
|
if (akreParentDisplay) akreParentDisplay.textContent = 'Root akreditasi';
|
|
if (akreLevelDisplay) akreLevelDisplay.value = 'Folder Utama';
|
|
if (akreNameHint) akreNameHint.textContent = 'Masukkan nama folder utama baru.';
|
|
const saveBtn = document.getElementById('btnSaveAkreInstrumen');
|
|
if (saveBtn) saveBtn.textContent = 'Tambah Folder';
|
|
}
|
|
|
|
if (akreNameInput) {
|
|
akreNameInput.placeholder = depth >= 2
|
|
? 'Contoh: PAP 1.3'
|
|
: depth === 1
|
|
? 'Contoh: PAP 2'
|
|
: 'Contoh: Tata Kelola Rumah Sakit (TKRS)';
|
|
}
|
|
}
|
|
|
|
async function deleteAkreFolder(folderPath) {
|
|
const normalizedPath = String(folderPath || '').trim();
|
|
if (!normalizedPath) return;
|
|
|
|
const confirmation = await Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Hapus folder ini?',
|
|
text: `Folder "${normalizedPath}" hanya bisa dihapus jika kosong.`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Ya, hapus',
|
|
cancelButtonText: 'Batal',
|
|
reverseButtons: true,
|
|
});
|
|
|
|
if (!confirmation.isConfirmed) {
|
|
return;
|
|
}
|
|
|
|
fetch('/akreditasi/instrumen', {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]')?.value || '',
|
|
},
|
|
body: JSON.stringify({ path: normalizedPath }),
|
|
})
|
|
.then(async (res) => {
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok || data?.status === false) {
|
|
throw new Error(data?.message || 'Folder gagal dihapus.');
|
|
}
|
|
|
|
pendingAkreFocusPath = getAkreParentPath(normalizedPath);
|
|
if (activeAkrePath === normalizedPath) {
|
|
activeFileSearch = '';
|
|
currentData = [];
|
|
}
|
|
|
|
await refreshAkreSelects();
|
|
|
|
if (activeAkrePath && activeAkrePath !== normalizedPath) {
|
|
await fetchData(activeAkrePath, 1);
|
|
} else if (pendingAkreFocusPath) {
|
|
await fetchData(pendingAkreFocusPath, 1);
|
|
} else {
|
|
currentData = [];
|
|
renderAkreSubtree();
|
|
}
|
|
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: data?.message || 'Folder akreditasi berhasil dihapus.',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Gagal',
|
|
text: err?.message || 'Folder gagal dihapus.',
|
|
});
|
|
});
|
|
}
|
|
|
|
if (formAkreInstrumen) {
|
|
formAkreInstrumen.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
const saveBtn = document.getElementById('btnSaveAkreInstrumen');
|
|
const rawName = sanitizeAkreFolderName(akreNameInput?.value || '');
|
|
|
|
if (akreNameInput) akreNameInput.value = rawName;
|
|
if (!rawName) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Nama folder wajib diisi',
|
|
text: 'Isi satu nama folder yang valid tanpa karakter khusus seperti / atau \\.',
|
|
});
|
|
akreNameInput?.focus();
|
|
return;
|
|
}
|
|
|
|
const mode = String(akreModeInput?.value || 'type');
|
|
if (mode === 'type' && akreTypeInput) akreTypeInput.value = rawName;
|
|
if (mode === 'segment' && akreSegmentInput) akreSegmentInput.value = rawName;
|
|
if (mode === 'item' && akreItemInput) akreItemInput.value = rawName;
|
|
|
|
const payload = buildAkrePayload();
|
|
if (saveBtn) saveBtn.disabled = true;
|
|
if (saveBtn) saveBtn.textContent = 'menyimpan...';
|
|
|
|
fetch('/akreditasi/instrumen', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]')?.value || '',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
.then(async (res) => {
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
const msg = data?.message || (data?.errors ? Object.values(data.errors).flat().join(' ') : '') || 'Terjadi kesalahan.';
|
|
throw new Error(msg);
|
|
}
|
|
if (data?.status === false) {
|
|
throw new Error(data?.message || 'Terjadi kesalahan.');
|
|
}
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: data?.message || 'Folder akreditasi berhasil ditambahkan.',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
});
|
|
|
|
const createdPath = [payload.type, payload.segment, payload.item].filter(Boolean).join('/');
|
|
pendingAkreFocusPath = createdPath;
|
|
|
|
const modalEl = document.getElementById('modalAkreInstrumen');
|
|
const modalInstance = modalEl ? bootstrap.Modal.getInstance(modalEl) : null;
|
|
modalInstance?.hide();
|
|
|
|
formAkreInstrumen.reset();
|
|
configureAkreModal('');
|
|
|
|
// refresh dropdown instrumen di form upload
|
|
return refreshAkreSelects().then(() => {
|
|
if (activeAkrePath) {
|
|
return fetchData(activeAkrePath, 1);
|
|
}
|
|
renderAkreSubtree();
|
|
return null;
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Gagal',
|
|
text: err?.message || 'Terjadi kesalahan.',
|
|
});
|
|
})
|
|
.finally(() => {
|
|
if (saveBtn) saveBtn.disabled = false;
|
|
configureAkreModal(getAkreCurrentParentPath());
|
|
});
|
|
});
|
|
}
|
|
|
|
// Klik tombol "+" di samping folder (tree view) untuk menambah sub-folder sesuai level:
|
|
// - depth 1 (Type) -> tambah Segment
|
|
// - depth 2 (Type/Segment) -> tambah Item
|
|
window.openAddAkreFromFolder = function(folderPath){
|
|
const modalEl = document.getElementById('modalAkreInstrumen');
|
|
if (!modalEl) return;
|
|
|
|
configureAkreModal(folderPath);
|
|
|
|
const instance = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
instance.show();
|
|
setTimeout(() => akreNameInput?.focus(), 150);
|
|
}
|
|
|
|
document.addEventListener('change', function(e){
|
|
if(e.target.classList.contains('akre-select')){
|
|
const id = e.target.id || '';
|
|
const idx = id.split('_').pop();
|
|
const [typeVal = '', segmentVal = '', itemVal = ''] = (e.target.value || '').split('/');
|
|
const typeInput = document.getElementById(`akre_type_${idx}`);
|
|
const segmentInput = document.getElementById(`akre_segment_${idx}`);
|
|
const itemInput = document.getElementById(`akre_item_${idx}`);
|
|
if(typeInput) typeInput.value = typeVal;
|
|
if(segmentInput) segmentInput.value = segmentVal;
|
|
if(itemInput) itemInput.value = itemVal;
|
|
}
|
|
});
|
|
</script>
|
|
@endsection
|