This commit is contained in:
Jokoprasetio 2026-01-22 13:01:28 +07:00
parent fc9f6d280b
commit b00ebbc277
14 changed files with 262 additions and 47 deletions

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM php:8.2-cli
RUN apt-get update && apt-get install -y \
git unzip zip ca-certificates openssh-client \
ghostscript \
libpng-dev libjpeg-dev libfreetype6-dev \
libzip-dev \
libpq-dev \
&& update-ca-certificates \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd zip pdo pdo_pgsql pgsql \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
services:
app:
build: .
container_name: project_directory-rsab
working_dir: /var/www
volumes:
- ./:/var/www
ports:
- "8010:8010"
command: bash -lc "composer install && php artisan serve --host=0.0.0.0 --port=8010"

View File

@ -7,16 +7,37 @@
<h4 class="mb-0">Data Terakhir</h4>
<button type="button" class="btn btn-success" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
</div>
<div class="card-body p-2">
<div class="d-flex align-items-center gap-2 mb-3">
<input type="text" onchange="searchData(this)" class="form-control" placeholder="Search">
<button type="button" class="btn btn-primary">Cari</button>
<div class="card-body p-3">
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
<div class="input-group input-group-sm flex-grow-1">
<span class="input-group-text bg-white border-end-0">
<i class="fa fa-search text-muted"></i>
</span>
<input type="search"
id="tableSearch"
class="form-control border-start-0"
placeholder="Cari nama file atau folder"
oninput="debouncedTableSearch(this.value)">
</div>
<div class="d-flex align-items-center gap-2">
<select id="tablePageSize" class="form-select form-select-sm" style="width: auto;">
<option value="5">5</option>
<option value="10"selected>10</option>
<option value="20">20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div class="small text-muted ms-md-auto" id="tableSummary">Memuat data...</div>
</div>
<table class="table table-bordered">
<div class="table-responsive" style="max-height: 100vh; overflow-y:auto;">
<table class="table table-sm table-hover align-middle mb-0" id="lastUpdatedTable">
<thead>
<tr>
<th>Nomor Surat</th>
<th>Nama</th>
<th>File Folder</th>
<th>Akses</th>
<th>Tanggal Modifikasi</th>
</tr>
</thead>
@ -24,6 +45,8 @@
<!-- data dari fetch masuk sini -->
</tbody>
</table>
</div>
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mt-3" id="paginationControls"></div>
</div>
</div>
@ -36,50 +59,173 @@
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
const authSubUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]->sub_unit_kerja);
const mappingUnitKerjaPegawai = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]);
const formCreate = $("#formFile")
const modalCreate = document.getElementById('modalCreateFile')
const tableState = { data: [], filtered: [], page: 1, pageSize: 8, search: '' };
const tbody = document.getElementById('tableFolderLastUpdated');
const paginationEl = document.getElementById('paginationControls');
const summaryEl = document.getElementById('tableSummary');
const pageSizeSelect = document.getElementById('tablePageSize');
if(pageSizeSelect){
const initialSize = parseInt(pageSizeSelect.value);
if(!isNaN(initialSize)) tableState.pageSize = initialSize;
pageSizeSelect.addEventListener('change', (e) => {
const val = parseInt(e.target.value);
if(!isNaN(val) && val > 0){
tableState.pageSize = val;
tableState.page = 1;
renderTable();
}
});
}
function resetCreateForm(){
colCount = 1;
$("#col_add_fileV2").html('');
formCreate[0]?.reset();
formCreate.find('select').val(null).trigger('change');
formCreate.find('input[type="file"]').val('');
formCreate.find('.file-name').addClass('d-none').text('');
}
function isPublic(permissionVal){
if(permissionVal === null || permissionVal === undefined) return false;
const val = String(permissionVal).toLowerCase();
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
}
function buildRow(item){
const parts = (item.file || '').split('/');
const fileName = parts.pop() || '-';
const folderPath = parts.join('/') || '-';
const publicDoc = isPublic(item.permission_file);
return `
<tr>
<td class="text-nowrap">${item.no_dokumen || '-'}</td>
<td>
<span class="me-1">📄</span>
<a href="#" class="file-link"
data-file="${item.file}"
data-fileName="${fileName}"
data-id="${item.file_directory_id}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}"
data-permission_file="${item.permission_file || '-'}">${fileName}</a>
</td>
<td class="text-break"><span class="badge bg-light text-dark fw-semibold">${folderPath}</span></td>
<td>
<span class="badge ${publicDoc ? 'bg-success' : 'bg-secondary'}">
${publicDoc ? 'Umum' : 'Internal Unit'}
</span>
</td>
<td class="text-nowrap">${formatTanggal(item.entry_at)}</td>
</tr>
`;
}
function renderPagination(totalPages){
if(!paginationEl) return;
if(totalPages <= 1){
paginationEl.innerHTML = '';
return;
}
const maxButtons = 5;
let start = Math.max(1, tableState.page - Math.floor(maxButtons/2));
let end = Math.min(totalPages, start + maxButtons - 1);
start = Math.max(1, end - maxButtons + 1);
let buttons = '';
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="prev" ${tableState.page === 1 ? 'disabled' : ''}> Sebelumnya</button>`;
for(let i=start; i<=end; i++){
buttons += `<button class="btn btn-sm ${i === tableState.page ? 'btn-primary' : 'btn-outline-secondary'}" data-page="${i}">${i}</button>`;
}
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="next" ${tableState.page === totalPages ? 'disabled' : ''}>Berikutnya </button>`;
paginationEl.innerHTML = `
<div class="d-flex align-items-center gap-2 flex-wrap">
<div class="btn-group" role="group">${buttons}</div>
<span class="small text-muted">Halaman ${tableState.page} dari ${totalPages}</span>
</div>
`;
}
if(paginationEl){
paginationEl.addEventListener('click', (e) => {
const page = e.target.getAttribute('data-page');
if(!page) return;
if(page === 'prev' && tableState.page > 1) tableState.page--;
else if(page === 'next'){
const totalPages = Math.max(1, Math.ceil(tableState.filtered.length / tableState.pageSize));
if(tableState.page < totalPages) tableState.page++;
}else{
tableState.page = parseInt(page);
}
renderTable();
});
}
function renderTable(){
const term = tableState.search.toLowerCase();
tableState.filtered = tableState.data.filter(item => {
const fileName = (item.file || '').split('/').pop().toLowerCase();
const folderPath = (item.file || '').toLowerCase();
return fileName.includes(term) || folderPath.includes(term);
});
const total = tableState.filtered.length;
const totalPages = Math.max(1, Math.ceil(total / tableState.pageSize));
if(tableState.page > totalPages) tableState.page = totalPages;
const startIdx = (tableState.page - 1) * tableState.pageSize;
const pageData = tableState.filtered.slice(startIdx, startIdx + tableState.pageSize);
if(pageData.length === 0){
tbody.innerHTML = `
<tr>
<td colspan="5" class="text-center text-muted py-4">
Tidak ada data yang cocok
</td>
</tr>
`;
}else{
tbody.innerHTML = pageData.map(buildRow).join('');
}
const from = total === 0 ? 0 : startIdx + 1;
const to = Math.min(startIdx + pageData.length, total);
if(summaryEl){
summaryEl.textContent = total ? `Menampilkan ${from} - ${to} dari ${total} dokumen` : 'Tidak ada data';
}
renderPagination(totalPages);
}
function debouncedTableSearch(value){
clearTimeout(window.tableSearchTimer);
window.tableSearchTimer = setTimeout(() => {
tableState.search = value.trim();
tableState.page = 1;
renderTable();
}, 250);
}
function fetchData(){
if(summaryEl) summaryEl.textContent = 'Memuat data...';
fetch(`/last-document`)
.then(response => response.json())
.then(data => {
const tbody = document.getElementById('tableFolderLastUpdated');
const resData = data?.data || [];
if (resData.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="3" class="text-center text-muted">
Tidak ada data
</td>
</tr>
`;
return;
}
tbody.innerHTML = resData.map(item => {
const fullPath = item.file;
console.log(item);
const parts = fullPath.split('/');
const fileName = parts.pop(); // ambil paling belakang
const folderPath = parts.join('/'); // gabung sisanya
return `
<tr>
<td>📄<a href="#" class="file-link"
data-file="${item.file}"
data-fileName="${fileName || '-'}"
data-id="${item.file_directory_id}"
data-no_dokumen="${item.no_dokumen || '-'}"
data-tanggal_terbit="${item.tanggal_terbit || '-'}" data-permission_file="${item.permission_file || '-'}">${fileName}</a></td>
<td>${folderPath}</td>
<td>${formatTanggal(item.entry_at)}</td>
</tr>
`;
}).join('');
tableState.data = data?.data || [];
tableState.page = 1;
renderTable();
})
.catch(error => {
console.error('Error : ', error);
if(summaryEl) summaryEl.textContent = 'Gagal memuat data';
})
.catch(error => console.error('Error : ', error))
}
function formatTanggal(dateString) {
@ -381,10 +527,9 @@
const permEl = document.getElementById('confirm-permission');
if (permEl) {
// contoh label permission biar enak dibaca
const isPublic = (permissionFile === 'true' || permissionFile.toLowerCase() === 'iya');
permEl.textContent = isPublic ? 'Bisa dilihat unit lain' : 'Hanya unit ini';
permEl.className = 'badge ' + (isPublic ? 'bg-success' : 'bg-secondary');
const publicDoc = isPublic(permissionFile);
permEl.textContent = publicDoc ? 'Bisa dilihat unit lain' : 'Hanya unit ini';
permEl.className = 'badge ' + (publicDoc ? 'bg-success' : 'bg-secondary');
}
let previewBox = document.getElementById('file-preview');
previewBox.innerHTML = `<iframe src="/file-preview/${idDirectory}" width="100%" height="500px" style="border:none;"></iframe>`;
@ -454,5 +599,51 @@
window.open(`/file-preview/${idDirectory}`, '_blank');
}
})
formCreate.off('submit').on('submit', function(e){
e.preventDefault();
const submitBtn = $(this).find('button[type="submit"]');
submitBtn.prop('disabled', true).text('menyimpan...')
const formData = new FormData(this);
console.log(formData);
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){
Toastify({
text: responseData.message || 'Berhasil melakukan aksi!',
duration: 3000,
gravity: "top",
position: "right",
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)",
color: "#fff",
}
}).showToast();
resetCreateForm();
fetchData()
submitBtn.prop('disabled', false).text('Simpan')
const modalInstance = bootstrap.Modal.getInstance(modalCreate);
modalInstance?.hide();
} else {
throw new Error(responseData.message || 'Terjadi kesalahan saat menyimpan data.');
}
}).catch(err => {
if (err.message) {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: err.message
});
submitBtn.prop('disabled', false).text('Simpan')
}
});
});
</script>
@endsection

0
storage/app/.gitignore vendored Normal file → Executable file
View File

0
storage/app/private/.gitignore vendored Normal file → Executable file
View File

0
storage/app/public/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/cache/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/cache/data/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/sessions/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/testing/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/views/.gitignore vendored Normal file → Executable file
View File

0
storage/logs/.gitignore vendored Normal file → Executable file
View File