progress
This commit is contained in:
parent
fc9f6d280b
commit
b00ebbc277
14
Dockerfile
Normal file
14
Dockerfile
Normal 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
0
bootstrap/cache/.gitignore
vendored
Normal file → Executable file
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal 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"
|
||||||
@ -7,16 +7,37 @@
|
|||||||
<h4 class="mb-0">Data Terakhir</h4>
|
<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>
|
<button type="button" class="btn btn-success" data-bs-target="#modalCreateFile" data-bs-toggle="modal">Tambah File</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-3">
|
||||||
<div class="d-flex align-items-center gap-2 mb-3">
|
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3">
|
||||||
<input type="text" onchange="searchData(this)" class="form-control" placeholder="Search">
|
<div class="input-group input-group-sm flex-grow-1">
|
||||||
<button type="button" class="btn btn-primary">Cari</button>
|
<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>
|
||||||
<table class="table table-bordered">
|
<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>
|
||||||
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Nomor Surat</th>
|
||||||
<th>Nama</th>
|
<th>Nama</th>
|
||||||
<th>File Folder</th>
|
<th>File Folder</th>
|
||||||
|
<th>Akses</th>
|
||||||
<th>Tanggal Modifikasi</th>
|
<th>Tanggal Modifikasi</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -25,6 +46,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -36,50 +59,173 @@
|
|||||||
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
||||||
const authSubUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]->sub_unit_kerja);
|
const authSubUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]->sub_unit_kerja);
|
||||||
const mappingUnitKerjaPegawai = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]);
|
const mappingUnitKerjaPegawai = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]);
|
||||||
|
const formCreate = $("#formFile")
|
||||||
function fetchData(){
|
const modalCreate = document.getElementById('modalCreateFile')
|
||||||
fetch(`/last-document`)
|
const tableState = { data: [], filtered: [], page: 1, pageSize: 8, search: '' };
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const tbody = document.getElementById('tableFolderLastUpdated');
|
const tbody = document.getElementById('tableFolderLastUpdated');
|
||||||
const resData = data?.data || [];
|
const paginationEl = document.getElementById('paginationControls');
|
||||||
|
const summaryEl = document.getElementById('tableSummary');
|
||||||
|
const pageSizeSelect = document.getElementById('tablePageSize');
|
||||||
|
|
||||||
if (resData.length === 0) {
|
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 = `
|
tbody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-center text-muted">
|
<td colspan="5" class="text-center text-muted py-4">
|
||||||
Tidak ada data
|
Tidak ada data yang cocok
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
return;
|
}else{
|
||||||
|
tbody.innerHTML = pageData.map(buildRow).join('');
|
||||||
}
|
}
|
||||||
tbody.innerHTML = resData.map(item => {
|
|
||||||
const fullPath = item.file;
|
|
||||||
console.log(item);
|
|
||||||
|
|
||||||
const parts = fullPath.split('/');
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
const fileName = parts.pop(); // ambil paling belakang
|
renderPagination(totalPages);
|
||||||
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('');
|
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
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) {
|
function formatTanggal(dateString) {
|
||||||
@ -381,10 +527,9 @@
|
|||||||
|
|
||||||
const permEl = document.getElementById('confirm-permission');
|
const permEl = document.getElementById('confirm-permission');
|
||||||
if (permEl) {
|
if (permEl) {
|
||||||
// contoh label permission biar enak dibaca
|
const publicDoc = isPublic(permissionFile);
|
||||||
const isPublic = (permissionFile === 'true' || permissionFile.toLowerCase() === 'iya');
|
permEl.textContent = publicDoc ? 'Bisa dilihat unit lain' : 'Hanya unit ini';
|
||||||
permEl.textContent = isPublic ? 'Bisa dilihat unit lain' : 'Hanya unit ini';
|
permEl.className = 'badge ' + (publicDoc ? 'bg-success' : 'bg-secondary');
|
||||||
permEl.className = 'badge ' + (isPublic ? 'bg-success' : 'bg-secondary');
|
|
||||||
}
|
}
|
||||||
let previewBox = document.getElementById('file-preview');
|
let previewBox = document.getElementById('file-preview');
|
||||||
previewBox.innerHTML = `<iframe src="/file-preview/${idDirectory}" width="100%" height="500px" style="border:none;"></iframe>`;
|
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');
|
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>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/private/.gitignore
vendored
Normal file → Executable file
0
storage/app/private/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/data/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/data/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
Loading…
x
Reference in New Issue
Block a user