done recap
This commit is contained in:
parent
b00ebbc277
commit
44c4689f77
@ -295,18 +295,21 @@ class DashboardController extends Controller
|
||||
$zipName = 'files_' . time() . '.zip';
|
||||
$zipPath = public_path('zip/' . $zipName);
|
||||
$zip = new ZipArchive;
|
||||
$tempFiles = [];
|
||||
if($zip->open($zipPath, ZipArchive::CREATE) === TRUE){
|
||||
foreach ($paths as $path) {
|
||||
$fullPath = public_path('file/' . $path);
|
||||
if(file_exists($fullPath)){
|
||||
$relativePathInZip = $path;
|
||||
$zip->addFile($fullPath, $relativePathInZip);
|
||||
}else{
|
||||
if(!file_exists($fullPath)){
|
||||
throw new \Exception("File tidak ditemukan: " . $path);
|
||||
}
|
||||
|
||||
$relativePathInZip = $path;
|
||||
$fileToAdd = $this->prepareFileWithWatermark($fullPath, $tempFiles);
|
||||
$zip->addFile($fileToAdd, $relativePathInZip);
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
$this->cleanupTempFiles($tempFiles);
|
||||
return response()->download(public_path('zip/' . $zipName))->deleteFileAfterSend(true);
|
||||
//code...
|
||||
} catch (\Throwable $th) {
|
||||
@ -333,18 +336,21 @@ class DashboardController extends Controller
|
||||
$zipName = 'files_' . time() . '.zip';
|
||||
$zipPath = public_path('zip/' . $zipName);
|
||||
$zip = new ZipArchive;
|
||||
$tempFiles = [];
|
||||
if($zip->open($zipPath, ZipArchive::CREATE) === TRUE){
|
||||
foreach ($data as $path) {
|
||||
$fullPath = public_path('file/' . $path);
|
||||
if(file_exists($fullPath)){
|
||||
$relativePathInZip = $path;
|
||||
$zip->addFile($fullPath, $relativePathInZip);
|
||||
}else{
|
||||
if(!file_exists($fullPath)){
|
||||
throw new \Exception("File tidak ditemukan: " . $path);
|
||||
}
|
||||
|
||||
$relativePathInZip = $path;
|
||||
$fileToAdd = $this->prepareFileWithWatermark($fullPath, $tempFiles);
|
||||
$zip->addFile($fileToAdd, $relativePathInZip);
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
$this->cleanupTempFiles($tempFiles);
|
||||
return response()->download(public_path('zip/' . $zipName))->deleteFileAfterSend(true);
|
||||
} catch (\Throwable $th) {
|
||||
return response()->json([
|
||||
@ -370,10 +376,27 @@ class DashboardController extends Controller
|
||||
}
|
||||
|
||||
public function dataDocumentLast(){
|
||||
$perPage = request('per_page', 10);
|
||||
$perPage = (int) request('per_page', 10);
|
||||
$authUnitId = auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->objectunitkerjapegawaifk;
|
||||
$keyword = request('keyword');
|
||||
|
||||
$data = FileDirectory::where('statusenabled', true)
|
||||
->where(function ($query) use ($authUnitId) {
|
||||
$query->where('permission_file', true)
|
||||
->orWhere(function ($sub) use ($authUnitId) {
|
||||
$sub->where('permission_file', false)
|
||||
->where('id_unit_kerja', $authUnitId);
|
||||
});
|
||||
})
|
||||
->when($keyword, function ($q) use ($keyword) {
|
||||
$q->where(function ($sub) use ($keyword) {
|
||||
$sub->where('file', 'ILIKE', "%{$keyword}%")
|
||||
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
|
||||
});
|
||||
})
|
||||
->orderBy('entry_at', 'desc')
|
||||
->paginate($perPage);
|
||||
|
||||
$payload = [
|
||||
'status' => true,
|
||||
'message' => 'Berhasil mendapatkan data',
|
||||
@ -381,7 +404,10 @@ class DashboardController extends Controller
|
||||
'pagination' => [
|
||||
'current_page' => $data->currentPage(),
|
||||
'next_page' => $data->hasMorePages() ? $data->currentPage() + 1 : null,
|
||||
'has_more' => $data->hasMorePages()
|
||||
'has_more' => $data->hasMorePages(),
|
||||
'last_page' => $data->lastPage(),
|
||||
'per_page' => $data->perPage(),
|
||||
'total' => $data->total(),
|
||||
]
|
||||
];
|
||||
return response()->json($payload);
|
||||
@ -391,10 +417,16 @@ class DashboardController extends Controller
|
||||
DB::connection('dbDirectory')->beginTransaction();
|
||||
try {
|
||||
$datas = request('data');
|
||||
foreach ($datas as $data) {
|
||||
foreach ($datas as $index => $data) {
|
||||
list($id_unit_kerja, $nama_unit_kerja) = explode('/', $data['id_unit_kerja'],2);
|
||||
list($id_sub_unit_kerja, $nama_sub_unit_kerja) = explode('/', $data['id_sub_unit_kerja'],2);
|
||||
list($master_kategori_directory_id, $nama_kategori) = explode('/', $data['master_kategori_directory_id'],2);
|
||||
|
||||
$uploadedFile = request()->file("data.$index.file");
|
||||
if(!$uploadedFile){
|
||||
throw new \RuntimeException('File wajib diunggah pada baris ke-' . ($index+1));
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'id_unit_kerja' => $id_unit_kerja,
|
||||
'id_sub_unit_kerja' => $id_sub_unit_kerja,
|
||||
@ -403,16 +435,16 @@ class DashboardController extends Controller
|
||||
'pegawai_nama_entry' => auth()->user()->dataUser->namalengkap ?? null,
|
||||
'tanggal_terbit' => $data['date_active'] ?? null,
|
||||
'no_dokumen' => $data['no_dokumen'] ?? null,
|
||||
'permission_file' => $data['is_permission'] === "1" ? true : false,
|
||||
'permission_file' => ($data['is_permission'] ?? null) == "1",
|
||||
];
|
||||
if(!empty($data['file'])){
|
||||
$file = $data['file'];
|
||||
$imageName = $file->getClientOriginalName();
|
||||
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}";
|
||||
$file->storeAs($path, $imageName, 'file_directory');
|
||||
$payload['file'] =$path .'/' .$imageName;
|
||||
}
|
||||
|
||||
$imageName = $uploadedFile->getClientOriginalName();
|
||||
$path = "{$nama_unit_kerja}/{$nama_sub_unit_kerja}/{$nama_kategori}";
|
||||
$uploadedFile->storeAs($path, $imageName, 'file_directory');
|
||||
$payload['file'] =$path .'/' .$imageName;
|
||||
|
||||
$fd = FileDirectory::create($payload);
|
||||
|
||||
$payloadLog = [
|
||||
'file_directory_id' => $fd->file_directory_id,
|
||||
'pegawai_id_entry' => $fd->pegawai_id_entry,
|
||||
@ -513,6 +545,71 @@ class DashboardController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate watermarked PDF to temp file (or return original) for zipping.
|
||||
*/
|
||||
private function prepareFileWithWatermark(string $fullPath, array &$tempFiles = [])
|
||||
{
|
||||
$stampFile = public_path('assets/copy.png');
|
||||
$ext = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
|
||||
|
||||
if ($ext !== 'pdf' || !file_exists($stampFile)) {
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
$tempDir = storage_path('app/temp');
|
||||
if (!is_dir($tempDir)) {
|
||||
@mkdir($tempDir, 0777, true);
|
||||
}
|
||||
$tempOut = $tempDir . '/' . uniqid('wm_') . '.pdf';
|
||||
|
||||
try {
|
||||
$this->watermarkCenterToFile($fullPath, $stampFile, $tempOut);
|
||||
} catch (\Throwable $e) {
|
||||
// fallback convert then watermark
|
||||
$converted = $tempDir . '/' . uniqid('conv_') . '.pdf';
|
||||
$this->convertWithGhostscript($fullPath, $converted);
|
||||
$this->watermarkCenterToFile($converted, $stampFile, $tempOut);
|
||||
$tempFiles[] = $converted;
|
||||
}
|
||||
|
||||
$tempFiles[] = $tempOut;
|
||||
return $tempOut;
|
||||
}
|
||||
|
||||
private function watermarkCenterToFile(string $pdfPath, string $stampFile, string $outputPath): void
|
||||
{
|
||||
$pdf = new Fpdi();
|
||||
$pageCount = $pdf->setSourceFile($pdfPath);
|
||||
|
||||
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
|
||||
$tplId = $pdf->importPage($pageNo);
|
||||
$size = $pdf->getTemplateSize($tplId);
|
||||
|
||||
$pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
|
||||
$pdf->useTemplate($tplId);
|
||||
|
||||
$stampW = $size['width'] * 0.60;
|
||||
$stampH = $stampW * 0.75;
|
||||
|
||||
$x = ($size['width'] - $stampW) / 2;
|
||||
$y = ($size['height'] - $stampH) / 2;
|
||||
|
||||
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
|
||||
}
|
||||
|
||||
$pdf->Output($outputPath, 'F');
|
||||
}
|
||||
|
||||
private function cleanupTempFiles(array $tempFiles): void
|
||||
{
|
||||
foreach ($tempFiles as $file) {
|
||||
if (is_string($file) && file_exists($file)) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function convertWithGhostscript(string $inputPdf, string $outputPdf): void
|
||||
{
|
||||
$gs = config('services.ghostscript.path');
|
||||
@ -530,4 +627,78 @@ class DashboardController extends Controller
|
||||
throw new \RuntimeException('Convert Ghostscript gagal (code=' . $code . ')');
|
||||
}
|
||||
}
|
||||
|
||||
public function recapData(){
|
||||
try {
|
||||
$perPage = (int) request('per_page', 10);
|
||||
$page = max(1, (int) request('page', 1));
|
||||
$keyword = strtolower(request('keyword', ''));
|
||||
|
||||
$rows = FileDirectory::where('statusenabled', true)->pluck('file');
|
||||
|
||||
$grouped = [];
|
||||
foreach ($rows as $path) {
|
||||
$parts = array_values(array_filter(explode('/', $path)));
|
||||
if(count($parts) < 4){
|
||||
continue;
|
||||
}
|
||||
|
||||
$unit = $parts[0];
|
||||
$folder = $parts[1]. '/' . $parts[2];
|
||||
if($keyword){
|
||||
$hit = str_contains(strtolower($unit), $keyword) || str_contains(strtolower($folder), $keyword);
|
||||
if(!$hit) continue;
|
||||
}
|
||||
if(!isset($grouped[$unit])){
|
||||
$grouped[$unit] = [];
|
||||
}
|
||||
if (!isset($grouped[$unit][$folder])) {
|
||||
$grouped[$unit][$folder] = 0;
|
||||
}
|
||||
$grouped[$unit][$folder]++;
|
||||
}
|
||||
$result = [];
|
||||
foreach ($grouped as $unitName => $folders) {
|
||||
$data = [];
|
||||
foreach ($folders as $folder => $count) {
|
||||
$data[] = [
|
||||
'folder'=> $folder,
|
||||
'count' => $count,
|
||||
];
|
||||
}
|
||||
|
||||
usort($data, fn($a, $b) => $b['count'] <=> $a['count']);
|
||||
$result[] = [
|
||||
'unit' => $unitName,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
// paginate manually
|
||||
$total = count($result);
|
||||
$chunks = array_chunk($result, $perPage);
|
||||
$currentData = $chunks[$page-1] ?? [];
|
||||
|
||||
return response()->json([
|
||||
'status' => true,
|
||||
'data' => $currentData,
|
||||
'message' => 'Berhasil mendapatkan data',
|
||||
'pagination' => [
|
||||
'current_page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total' => $total,
|
||||
'last_page' => max(1, ceil($total / $perPage)),
|
||||
'has_more' => $page < max(1, ceil($total / $perPage)),
|
||||
]
|
||||
]);
|
||||
} catch (\Throwable $th) {
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Gagal! mendapatkan data'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function recapView(){
|
||||
return view('dashboard.recap', ['title' => 'Rekap Dokumen']);
|
||||
}
|
||||
}
|
||||
|
||||
67
app/Http/Controllers/LogActivityController.php
Normal file
67
app/Http/Controllers/LogActivityController.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\LogActivity;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LogActivityController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
$data =[
|
||||
'title' => 'Log Activity'
|
||||
];
|
||||
return view('logActivity.index', $data);
|
||||
}
|
||||
|
||||
public function datatable(){
|
||||
$perPage = (int) request('per_page', 10);
|
||||
$keyword = request('keyword');
|
||||
$start = request('start_date');
|
||||
$end = request('end_date');
|
||||
|
||||
$query = LogActivity::query()
|
||||
->orderBy('entry_at','desc');
|
||||
|
||||
if($keyword){
|
||||
$query->where(function($q) use ($keyword){
|
||||
$q->where('pegawai_nama_entry', 'ILIKE', "%{$keyword}%")
|
||||
->orWhere('action_type', 'ILIKE', "%{$keyword}%")
|
||||
->orWhere('file', 'ILIKE', "%{$keyword}%")
|
||||
->orWhere('no_dokumen', 'ILIKE', "%{$keyword}%");
|
||||
});
|
||||
}
|
||||
|
||||
if($start){
|
||||
$query->whereDate('entry_at','>=',$start);
|
||||
}
|
||||
if($end){
|
||||
$query->whereDate('entry_at','<=',$end);
|
||||
}
|
||||
|
||||
$paginated = $query->paginate($perPage);
|
||||
$data = $paginated->getCollection()->map(function($item){
|
||||
return [
|
||||
'pegawai_nama_entry' => $item->pegawai_nama_entry,
|
||||
'action_type' => $item->action_type,
|
||||
'file' => $item->file,
|
||||
'no_dokumen' => $item->no_dokumen ?? $item->fileDirectory->no_dokumen ?? '-',
|
||||
'entry_at' => $item->entry_at,
|
||||
'unit_name' => $item->unit_name ?? '-',
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'status' => true,
|
||||
'data' => $data,
|
||||
'pagination' => [
|
||||
'current_page' => $paginated->currentPage(),
|
||||
'next_page' => $paginated->hasMorePages() ? $paginated->currentPage() + 1 : null,
|
||||
'has_more' => $paginated->hasMorePages(),
|
||||
'last_page' => $paginated->lastPage(),
|
||||
'per_page' => $paginated->perPage(),
|
||||
'total' => $paginated->total(),
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -11,4 +11,9 @@ class LogActivity extends Model
|
||||
public $timestamps = false;
|
||||
protected $primaryKey = 'id';
|
||||
protected $guarded = ['id'];
|
||||
|
||||
// public function unitKerja(){
|
||||
// return $this->belongsTo(UnitKerja::class, 'id_unit_kerja', 'id');
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
let allFiles = [];
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".file-input").forEach(input => {
|
||||
bindFileUpload(input);
|
||||
@ -6,72 +5,40 @@
|
||||
});
|
||||
|
||||
function bindFileUpload(input) {
|
||||
const dropArea = input.closest(".file-drop-area");
|
||||
const fileNameBox = dropArea.querySelector(".file-name");
|
||||
const inputId = input?.id;
|
||||
allFiles[inputId] = [];
|
||||
const fileNameBox = input.closest(".border")?.querySelector(".file-name") || input.closest(".file-drop-area")?.querySelector(".file-name");
|
||||
input.addEventListener("change", function () {
|
||||
for (let i = 0; i < this.files.length; i++) {
|
||||
allFiles[inputId].push(this.files[i]);
|
||||
}
|
||||
renderFileList(inputId, fileNameBox);
|
||||
this.value = ""; // reset agar bisa pilih file lagi
|
||||
if (!fileNameBox) return;
|
||||
const file = this.files[0];
|
||||
if (file) {
|
||||
fileNameBox.textContent = `✔ ${file.name}`;
|
||||
fileNameBox.classList.remove("d-none");
|
||||
} else {
|
||||
fileNameBox.textContent = '';
|
||||
fileNameBox.classList.add("d-none");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderFileList(inputId, container) {
|
||||
const files = allFiles[inputId];
|
||||
if (!files || files.length === 0) {
|
||||
container.classList.add("d-none");
|
||||
container.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
let list = "<ul class='list-unstyled mb-0'>";
|
||||
files.forEach((file, index) => {
|
||||
list += `
|
||||
<li class="d-flex justify-content-between align-items-center">
|
||||
<span>✔ ${file.name}</span>
|
||||
<button type="button" class="btn btn-sm btn-danger ms-2"
|
||||
onclick="removeFile('${inputId}', ${index})">✖</button>
|
||||
</li>`;
|
||||
});
|
||||
list += "</ul>";
|
||||
|
||||
container.innerHTML = list;
|
||||
container.classList.remove("d-none");
|
||||
}
|
||||
|
||||
function removeFile(inputId, index) {
|
||||
allFiles[inputId].splice(index, 1);
|
||||
const container = document.querySelector(`#${inputId}`).closest(".file-drop-area").querySelector(".file-name");
|
||||
renderFileList(inputId, container);
|
||||
function resetCreateForm(){
|
||||
colCount = 1;
|
||||
$("#col_add_file").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('');
|
||||
formCreate.find('input[type="radio"]').prop('checked', false);
|
||||
// rebind initial input
|
||||
document.querySelectorAll(".file-input").forEach(bindFileUpload);
|
||||
}
|
||||
|
||||
formCreate.off('submit').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
const submitBtn = $(this).find('button[type="submit"]');
|
||||
submitBtn.prop('disabled', true).text('menyimpan...')
|
||||
let hasFile = Object.keys(allFiles).every(id => {
|
||||
console.log('fil ' , allFiles[id],' length', allFiles[id].length > 0);
|
||||
return Array.isArray(allFiles[id]) && allFiles[id].length > 0;
|
||||
});
|
||||
if(!hasFile){
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: 'Perhatian',
|
||||
text: 'Silahkan upload minimal 1 file sebelum submit'
|
||||
});
|
||||
submitBtn.prop('disabled', false).text('Simpan')
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(this);
|
||||
for (const inputId in allFiles) {
|
||||
allFiles[inputId].forEach((file, index) => {
|
||||
formData.append(`data[${inputId}][file][]`, file); // gunakan inputId = name input file di HTML, misal "files[]"
|
||||
});
|
||||
}
|
||||
fetch(`/upload`, {
|
||||
|
||||
fetch(`/uploadv2`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value,
|
||||
@ -80,43 +47,29 @@
|
||||
}).then(async(res) => {
|
||||
const responseData = await res.json();
|
||||
if (responseData.status) {
|
||||
const handler = function () {
|
||||
Toastify({
|
||||
text: responseData.message || 'Berhasil melakukan aksi!',
|
||||
duration: 3000,
|
||||
gravity: "top", // bisa "bottom"
|
||||
position: "right", // bisa "left"
|
||||
style: {
|
||||
background: "linear-gradient(to right, #00b09b, #96c93d)", // hijau gradasi
|
||||
color: "#fff",
|
||||
}
|
||||
}).showToast();
|
||||
allFiles = [];
|
||||
formCreate.find('input[type="file"].file-input').each(function () {
|
||||
const newInput = $(this).clone(); // clone dengan attribute multiple
|
||||
$(this).replaceWith(newInput); // ganti input lama dengan baru
|
||||
bindFileUpload(newInput[0])
|
||||
// console.log(newInput);
|
||||
|
||||
});
|
||||
colCount = 1;
|
||||
$("#col_add_file").html('')
|
||||
formCreate.find('select').val(null).trigger('change');
|
||||
document.querySelectorAll(".file-name").forEach(el => {
|
||||
el.classList.add("d-none");
|
||||
el.innerHTML = "";
|
||||
});
|
||||
|
||||
if($("#klasifikasi_dok").val().length === 0 || $("#kategori_dok").val().length === 0 ){
|
||||
index()
|
||||
}else{
|
||||
searchData()
|
||||
Toastify({
|
||||
text: responseData.message || 'Berhasil melakukan aksi!',
|
||||
duration: 3000,
|
||||
gravity: "top",
|
||||
position: "right",
|
||||
style: {
|
||||
background: "linear-gradient(to right, #00b09b, #96c93d)",
|
||||
color: "#fff",
|
||||
}
|
||||
submitBtn.prop('disabled', false).text('Simpan')
|
||||
modalCreate.removeEventListener('hidden.bs.modal', handler);
|
||||
};
|
||||
modalCreate.addEventListener('hidden.bs.modal', handler);
|
||||
bootstrap.Modal.getInstance(modalCreate).hide();
|
||||
}).showToast();
|
||||
|
||||
resetCreateForm();
|
||||
|
||||
const kategoriVal = $("#kategori_dok").val() || [];
|
||||
|
||||
if(kategoriVal.length === 0){
|
||||
index()
|
||||
}else{
|
||||
searchData()
|
||||
}
|
||||
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.');
|
||||
}
|
||||
@ -128,9 +81,7 @@
|
||||
title: 'Gagal',
|
||||
text: err.message
|
||||
});
|
||||
submitBtn.prop('disabled', false).text('Simpan...')
|
||||
submitBtn.prop('disabled', false).text('Simpan')
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -55,7 +55,6 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
$('.klasifikasi_dok').select2();
|
||||
$('.kategori_dok').select2();
|
||||
|
||||
if(allAkses){
|
||||
@ -91,54 +90,114 @@ function addForm(){
|
||||
let html = '';
|
||||
|
||||
html += `
|
||||
<div class="row mt-3" id="col-${colCount}">
|
||||
<hr />
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Unit</label>
|
||||
<select class="form-control" name="data[${colCount}][id_unit_kerja]" id="select_id_unit_kerja_${colCount}" required>
|
||||
<div class="row g-3 align-items-start mt-2" id="col-${colCount}">
|
||||
<hr class="my-2" />
|
||||
<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-4">
|
||||
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-control"
|
||||
name="data[${colCount}][id_unit_kerja]"
|
||||
id="select_id_unit_kerja_${colCount}"
|
||||
required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Sub Unit</label>
|
||||
<select class="form-control" name="data[${colCount}][id_sub_unit_kerja]" id="select_id_sub_unit_kerja_${colCount}" required>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-control"
|
||||
name="data[${colCount}][id_sub_unit_kerja]"
|
||||
id="select_id_sub_unit_kerja_${colCount}"
|
||||
required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Kategori Dokumen</label>
|
||||
<select class="form-control" name="data[${colCount}][master_kategori_directory_id]" id="select_kategori_${colCount}" required>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Kategori Dokumen <span class="text-danger">*</span></label>
|
||||
<select class="form-control"
|
||||
name="data[${colCount}][master_kategori_directory_id]"
|
||||
id="select_kategori_${colCount}"
|
||||
required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
${katDok.map(dok => `
|
||||
<option value="${dok?.master_kategori_directory_id}/${dok?.nama_kategori_directory}">${dok?.nama_kategori_directory}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Klasifikasi Dokumen</label>
|
||||
<select class="form-select" name="data[${colCount}][klasifikasi]" required>
|
||||
<option value="" disable >Select Choose</option>
|
||||
${klasifikasiDok.map(kla => `
|
||||
<option value="${kla?.master_klasifikasi_directory_id}/${kla?.nama_klasifikasi_directory}">${kla?.nama_klasifikasi_directory}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-11 mb-2">
|
||||
<label for="fileUpload${colCount}" class="form-label fw-semibold">📂 Upload Dokumen</label>
|
||||
<div class="file-drop-area border rounded-3 p-1 shadow-sm">
|
||||
<input class="file-input" type="file" id="fileUpload${colCount}" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx" multiple>
|
||||
<div class="mt-2 text-success fw-semibold d-none file-name"></div>
|
||||
</div>
|
||||
<div class="form-text text-muted fw-semibold">Format yang didukung: PDF, JPG, PNG, Excel dan Word</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<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-1">
|
||||
<button type="button" class="btn btn-danger mt-4 me-2" onclick="removeCol(${colCount})"><i class="fa-solid fa-trash"></i></button>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">Tanggal Terbit</label>
|
||||
<input class="form-control"
|
||||
type="date"
|
||||
name="data[${colCount}][date_active]"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<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}">Iya</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-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 file-input"
|
||||
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.append(html)
|
||||
let newInput = document.querySelector(`#fileUpload${colCount}`);
|
||||
let newInput = document.querySelector(`#fileUpload_${colCount}`);
|
||||
bindFileUpload(newInput);
|
||||
if(allAkses){
|
||||
selectOptionUnitKerjaV1(colCount)
|
||||
@ -263,5 +322,3 @@ function selectOptionUnitKerjaV2(colCount) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -40,20 +40,23 @@ function renderTree(units, katDok, keyword) {
|
||||
<ul class="file-tree-ul ms-2">
|
||||
${files.map(file => {
|
||||
let fileName = file.file.split('/').pop();
|
||||
console.log(file);
|
||||
const isPublic = String(file.permission_file).toLowerCase() === 'true' || file.permission_file === 1 || file.permission_file === '1';
|
||||
|
||||
return `
|
||||
<li class="file-tree-li">
|
||||
<div class="">
|
||||
📄
|
||||
<a href="#" class="file-link"
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<span>📄</span>
|
||||
<a href="#" class="file-link"
|
||||
data-file="${file?.file}"
|
||||
data-fileName="${fileName || '-'}"
|
||||
data-id="${file.file_directory_id}"
|
||||
data-no_dokumen="${file.no_dokumen || '-'}"
|
||||
data-tanggal_terbit="${file.tanggal_terbit || '-'}" data-permission_file="${file.permission_file || '-'}">${fileName}</a>
|
||||
data-tanggal_terbit="${file.tanggal_terbit || '-'}"
|
||||
data-permission_file="${file.permission_file || '-'}">${fileName}</a>
|
||||
<span class="badge ${isPublic ? 'bg-success' : 'bg-secondary'}">
|
||||
${isPublic ? 'Umum' : 'Internal'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
`;
|
||||
@ -74,13 +77,12 @@ function renderTree(units, katDok, keyword) {
|
||||
`;
|
||||
}
|
||||
|
||||
function index(kategori_dok = [], unitKerja = null, subUnitKerja = [], klasifikasi_id = [], keyword = '') {
|
||||
function index(kategori_dok = [], unitKerja = null, subUnitKerja = [], keyword = '') {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (kategori_dok.length) params.append("kategori", kategori_dok);
|
||||
if (unitKerja) params.append("unitKerja", unitKerja);
|
||||
if (subUnitKerja.length) params.append("subUnit", subUnitKerja);
|
||||
if (klasifikasi_id.length) params.append("klasifikasi", klasifikasi_id);
|
||||
if (keyword) params.append("keyword", keyword);
|
||||
|
||||
fetch(`/data-unit-kerja?${params.toString()}`)
|
||||
@ -126,22 +128,22 @@ function referesh(){
|
||||
|
||||
|
||||
function searchData(){
|
||||
let klasifikasi_id = $("#klasifikasi_dok").val()
|
||||
let kategori_dok = $("#kategori_dok").val()
|
||||
let unitKerja = $("#unit_kerja").val()
|
||||
let subUnitKerja = $("#sub_unit_kerja").val()
|
||||
let keyword = ($("#search_file").val() || '').trim();
|
||||
|
||||
if(klasifikasi_id.length === 0 ||
|
||||
kategori_dok.length === 0 ||
|
||||
subUnitKerja.length === 0){
|
||||
const hasFilters = kategori_dok.length && subUnitKerja.length;
|
||||
|
||||
if(!hasFilters && !keyword){
|
||||
Swal.fire({
|
||||
text: 'Lengkapi Isi form pencarian',
|
||||
text: 'Isi kata kunci atau lengkapkan filter pencarian.',
|
||||
icon: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
index(kategori_dok, unitKerja, subUnitKerja, klasifikasi_id);
|
||||
index(kategori_dok, unitKerja, subUnitKerja, [], keyword);
|
||||
}
|
||||
|
||||
let debounceTimer;
|
||||
@ -154,11 +156,10 @@ function debounceSearch(input) {
|
||||
|
||||
|
||||
function searchFile(keyword){
|
||||
let klasifikasi_id = $("#klasifikasi_dok").val()
|
||||
let kategori_dok = $("#kategori_dok").val()
|
||||
let unitKerja = $("#unit_kerja").val()
|
||||
let subUnitKerja = $("#sub_unit_kerja").val()
|
||||
index(kategori_dok, unitKerja, subUnitKerja, klasifikasi_id, keyword);
|
||||
index(kategori_dok, unitKerja, subUnitKerja, [], keyword);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -63,65 +63,71 @@
|
||||
</div>
|
||||
{{-- @dd($allAkses) --}}
|
||||
<div class="card-body p-3">
|
||||
<div class="row mb-3 mt-3">
|
||||
<!-- Unit Kerja -->
|
||||
<div class="col-md-2">
|
||||
<label for="unit_kerja" class="form-label">Unit Kerja</label>
|
||||
<select class="form-control unit_kerja" id="unit_kerja"{{ ($allAkses && ($allAkses->all_akses || $allAkses->unit_akses)) ? '' : 'disabled' }}>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bg-light rounded-3 p-3 mb-3 shadow-sm border">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label for="unit_kerja" class="form-label fw-semibold text-muted small">Unit Kerja</label>
|
||||
<select class="form-control unit_kerja" id="unit_kerja"{{ ($allAkses && ($allAkses->all_akses || $allAkses->unit_akses)) ? '' : 'disabled' }}>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Sub Unit Kerja -->
|
||||
<div class="col-md-2">
|
||||
<label for="sub_unit_kerja" class="form-label">Sub Unit Kerja</label>
|
||||
<select class="form-control sub_unit_kerja" id="sub_unit_kerja" @if($allAkses && ($allAkses->all_akses || $allAkses->unit_akses)) multiple="multiple" @else disabled @endif>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="sub_unit_kerja" class="form-label fw-semibold text-muted small">Sub Unit Kerja</label>
|
||||
<select class="form-control sub_unit_kerja" id="sub_unit_kerja" @if($allAkses && ($allAkses->all_akses || $allAkses->unit_akses)) multiple="multiple" @else disabled @endif>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Kategori Dokumen -->
|
||||
<div class="col-md-2">
|
||||
<label for="kategori_dok" class="form-label">Kategori Dok.</label>
|
||||
<select class="form-control kategori_dok" id="kategori_dok" multiple="multiple" placeholder="--Pilih kategori dokumen--">
|
||||
<option value="" disabled>Select Choose</option>
|
||||
@foreach ($katDok as $kat)
|
||||
<option value="{{ $kat->master_kategori_directory_id }}">{{ $kat->nama_kategori_directory }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="kategori_dok" class="form-label fw-semibold text-muted small">Kategori Dok.</label>
|
||||
<select class="form-control kategori_dok" id="kategori_dok" multiple="multiple" placeholder="--Pilih kategori dokumen--">
|
||||
<option value="" disabled>Select Choose</option>
|
||||
@foreach ($katDok as $kat)
|
||||
<option value="{{ $kat->master_kategori_directory_id }}">{{ $kat->nama_kategori_directory }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Klasifikasi Dokumen -->
|
||||
<div class="col-md-2">
|
||||
<label for="klasifikasi_dok" class="form-label">Klasifikasi Dok.</label>
|
||||
<select class="form-control klasifikasi_dok" id="klasifikasi_dok" multiple="multiple" placeholder="--Pilih klasifikasi dokumen--">
|
||||
<option value="" disabled>Select Choose</option>
|
||||
@foreach ($klasifikasiDok as $kla)
|
||||
<option value="{{ $kla->master_klasifikasi_directory_id }}">{{ $kla->nama_klasifikasi_directory }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary flex-grow-1" onclick="searchData()">
|
||||
<i class="fa fa-search me-1"></i> Cari
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" onclick="referesh()">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Button Cari -->
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-primary w-100" onclick="searchData()">Cari</button>
|
||||
<div class="col-md-6 d-flex justify-content-md-end">
|
||||
<button type="button" class="btn btn-success w-100 w-md-auto" data-bs-target="#modalCreateFile" data-bs-toggle="modal">
|
||||
<i class="fa fa-plus me-1"></i> Tambah File
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Button Tambah File -->
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-success w-100" data-bs-target="#modalCreateFile" data-bs-toggle="modal">
|
||||
<i data-feather="plus" class="me-1"></i>
|
||||
Tambah File</button>
|
||||
<div class="d-flex align-items-center gap-2 mt-3 flex-wrap">
|
||||
<div class="badge bg-light text-secondary border">
|
||||
Tips: pilih Unit/Sub Unit & Kategori untuk menampilkan data.
|
||||
</div>
|
||||
<div class="ms-auto small text-muted d-flex align-items-center gap-2">
|
||||
<i class="fa fa-filter"></i> Filter aktif siap digunakan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="tree-wrapper" class="col-md-12 mb-3">
|
||||
<div class="d-flex gap-2 mb-3 flex-wrap">
|
||||
<button class="btn btn-sm btn-primary" onclick="referesh()">
|
||||
<div class="d-flex gap-2 mb-3 flex-wrap align-items-center">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="referesh()">
|
||||
<i class="fas fa-sync-alt"></i> Refresh
|
||||
</button>
|
||||
<button type="button" id="btn-download-multi" class="btn btn-sm btn-primary" onclick="downloadMultiple()" disabled>
|
||||
<i class="fas fa-download"></i> Download Multiple (<span id="multi-count">0</span>)
|
||||
</button>
|
||||
<input type="text" id="search_file" oninput="debounceSearch(this)" class="form-control form-control-sm" placeholder="Search" style="max-width:220px;">
|
||||
<div class="input-group input-group-sm" style="max-width:260px;">
|
||||
<span class="input-group-text bg-white"><i class="fa fa-search text-muted"></i></span>
|
||||
<input type="text" id="search_file" oninput="debounceSearch(this)" class="form-control" placeholder="Cari nama/no dokumen">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||
<span class="badge bg-success">Umum</span>
|
||||
<span class="badge bg-secondary">Internal</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="file_tree"></div>
|
||||
</div>
|
||||
@ -141,6 +147,11 @@
|
||||
const allAkses = @json($allAkses);
|
||||
const authUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]?->unitKerja);
|
||||
const authSubUnitKerja = @json(auth()->user()->dataUser?->mappingUnitKerjaPegawai[0]);
|
||||
function isPublic(permissionVal){
|
||||
if(permissionVal === null || permissionVal === undefined) return false;
|
||||
const val = String(permissionVal).toLowerCase();
|
||||
return val === '1' || val === 'true' || val === 'iya' || val === 'yes';
|
||||
}
|
||||
|
||||
let currentFile = null;
|
||||
let idDirectory = null;
|
||||
@ -149,16 +160,22 @@
|
||||
e.preventDefault();
|
||||
|
||||
let fileUrl = e.target.getAttribute('data-file');
|
||||
let fileName = e.target.getAttribute('data-name_file');
|
||||
let upload = e.target.getAttribute('data-upload');
|
||||
let time = e.target.getAttribute('data-time');
|
||||
let klasifikasiView = e.target.getAttribute('data-klasifikasi');
|
||||
console.log(fileUrl);
|
||||
let fileName = e.target.getAttribute('data-fileName') || '-';
|
||||
let noDokumen = e.target.getAttribute('data-no_dokumen') || '-';
|
||||
let tanggalTerbit = e.target.getAttribute('data-tanggal_terbit') || '-';
|
||||
let klasifikasiView = e.target.getAttribute('data-klasifikasi') || '-';
|
||||
let permissionFile = e.target.getAttribute('data-permission_file');
|
||||
|
||||
$("#confirm-upload-dokumen").html(upload);
|
||||
$("#confirm-time-dokumen").html(time);
|
||||
$("#confirm-upload-klasifikasi").html(klasifikasiView);
|
||||
$("#confirm_preview_file").html(fileName);
|
||||
$("#confirm-upload-dokumen").text(noDokumen);
|
||||
$("#confirm-time-dokumen").text(tanggalTerbit);
|
||||
$("#confirm-upload-klasifikasi").text(klasifikasiView);
|
||||
$("#confirm_preview_file").text(fileName);
|
||||
const permEl = document.getElementById('confirm-permission');
|
||||
if (permEl) {
|
||||
const publicDoc = isPublic(permissionFile);
|
||||
permEl.textContent = publicDoc ? 'Bisa dilihat unit lain' : 'Hanya unit ini';
|
||||
permEl.className = 'badge ' + (publicDoc ? 'bg-success' : 'bg-secondary');
|
||||
}
|
||||
|
||||
currentFile = fileUrl;
|
||||
idDirectory = e.target.getAttribute('data-id');
|
||||
@ -203,7 +220,9 @@
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log(data);
|
||||
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
text: 'File berhasil dihapus',
|
||||
icon: 'success',
|
||||
@ -218,13 +237,6 @@
|
||||
li?.remove();
|
||||
}
|
||||
|
||||
// Reset modal preview
|
||||
document.getElementById('file-preview').innerHTML =
|
||||
`<p>📂 Pilih file untuk melihat preview</p>`;
|
||||
document.getElementById('confirm_preview_file').innerHTML = "";
|
||||
document.getElementById('confirm-upload-dokumen').innerHTML = "";
|
||||
document.getElementById('confirm-time-dokumen').innerHTML = "";
|
||||
document.getElementById('confirm-upload-klasifikasi').innerHTML = "";
|
||||
|
||||
// Tutup modal otomatis setelah hapus
|
||||
let modalEl = document.getElementById('previewModal');
|
||||
|
||||
@ -9,54 +9,74 @@
|
||||
</div>
|
||||
|
||||
<!-- Modal Form -->
|
||||
<form id="formFile" action="/upload" enctype="multipart/form-data" method="POST" >
|
||||
<form id="formFile" action="/uploadv2" enctype="multipart/form-data" method="POST" >
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="container" id="container_create">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Unit</label>
|
||||
<select class="form-control" name="data[0][id_unit_kerja]" id="select_id_unit_kerja_0" required>
|
||||
<div class="container" style="max-height: 70vh; overflow-y:auto;">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-control unit_kerja" name="data[0][id_unit_kerja]" id="select_id_unit_kerja_0" required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Sub Unit</label>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
|
||||
<select class="form-control" name="data[0][id_sub_unit_kerja]" id="select_id_sub_unit_kerja_0" required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
<option value="" disable selected>Select Choose</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Kategori Dokumen</label>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Kategori Dokumen <span class="text-danger">*</span></label>
|
||||
<select class="form-control" name="data[0][master_kategori_directory_id]" id="select_kategori_0" required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
@foreach ($katDok as $kat)
|
||||
@foreach ($katDok as $kat)
|
||||
<option value="{{ $kat->master_kategori_directory_id }}/{{ $kat->nama_kategori_directory }}">{{ $kat->nama_kategori_directory }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<label>Klasifikasi File</label>
|
||||
<select class="form-select" name="data[0][klasifikasi]" required>
|
||||
<option value="" disable>Select Choose</option>
|
||||
@foreach ($klasifikasiDok as $klasifikasi)
|
||||
<option value="{{ $klasifikasi->master_klasifikasi_directory_id }}/{{ $klasifikasi->nama_klasifikasi_directory }}">
|
||||
{{ $klasifikasi->nama_klasifikasi_directory }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="col-md-6">
|
||||
<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[0][no_dokumen]" placeholder="Contoh: 001/RS/IT/I/2026">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">Tanggal Terbit</label>
|
||||
<input class="form-control" type="date" name="data[0][date_active]" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<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[0][is_permission]" id="perm_yes" value="1" required>
|
||||
<label class="form-check-label" for="perm_yes">
|
||||
Iya
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input" type="radio" name="data[0][is_permission]" id="perm_no" value="2" required>
|
||||
<label class="form-check-label" for="perm_no">
|
||||
Tidak
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 mb-2">
|
||||
<label for="fileUpload0" class="form-label fw-semibold">📂 Upload Dokumen</label>
|
||||
<div class="file-drop-area border rounded-2 p-1 shadow-sm">
|
||||
<input class="file-input" type="file" id="fileUpload0" accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx" multiple>
|
||||
<label for="fileUpload0" class="form-label fw-semibold">📂 Upload Dokumen (PDF)</label>
|
||||
<div class="border rounded-3 p-3 bg-white shadow-sm">
|
||||
<input class="form-control file-input" type="file" id="fileUpload0" accept=".pdf" name="data[0][file]">
|
||||
<div class="mt-2 text-success fw-semibold d-none file-name"></div>
|
||||
</div>
|
||||
<div class="form-text text-muted fw-semibold">
|
||||
Form bersifat multiple dan format yang didukung: JPG, JPEG, PDF, PNG, PPT Excel dan Word
|
||||
<div class="form-text text-muted">
|
||||
Bisa upload lebih dari 1 file. Format yang didukung: <b>PDF</b>.
|
||||
</div>
|
||||
</div>
|
||||
<div id="col_add_file"></div>
|
||||
<div id="col_add_file" class="col-12"></div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm mt-3" onclick="addForm()">
|
||||
+ Tambah Form
|
||||
|
||||
156
resources/views/dashboard/recap.blade.php
Normal file
156
resources/views/dashboard/recap.blade.php
Normal file
@ -0,0 +1,156 @@
|
||||
@extends('layout.main')
|
||||
@section('body_main')
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white border-0">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2">
|
||||
<div>
|
||||
<h4 class="mb-0">Rekap Dokumen</h4>
|
||||
<small class="text-muted">Ringkasan jumlah file per Unit dan Folder</small>
|
||||
</div>
|
||||
<div class="ms-md-auto d-flex gap-2 align-items-center">
|
||||
<div class="input-group input-group-sm" style="max-width:320px;">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="fa fa-search text-muted"></i>
|
||||
</span>
|
||||
<input type="search" id="recapSearch" class="form-control border-start-0" placeholder="Cari unit atau folder" oninput="debouncedRecapSearch(this.value)">
|
||||
</div>
|
||||
<select id="recapPerPage" class="form-select form-select-sm" style="width:auto;" onchange="changePerPage(this.value)">
|
||||
<option value="5">5</option>
|
||||
<option value="10" selected>10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary btn-sm d-flex align-items-center gap-1" onclick="fetchRecap()">
|
||||
<i class="fa fa-rotate"></i>
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pb-2">
|
||||
<div class="table-responsive" style="max-height: 55vh; overflow-y:auto;">
|
||||
<table class="table table-sm table-hover align-middle">
|
||||
<thead class="table-light shadow-sm">
|
||||
<tr>
|
||||
<th style="width:5%;" class="text-center">#</th>
|
||||
<th style="width:30%;">Unit</th>
|
||||
<th style="width:50%;">Folder</th>
|
||||
<th style="width:15%;" class="text-center">Jumlah</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="recapBody">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-4">Memuat data...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row align-items-center justify-content-between gap-2 mt-3" id="recapPagination"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => fetchRecap());
|
||||
|
||||
let recapDebounce;
|
||||
const recapState = { page:1, perPage:10, keyword:'', lastPage:1 };
|
||||
|
||||
function debouncedRecapSearch(val){
|
||||
clearTimeout(recapDebounce);
|
||||
recapDebounce = setTimeout(() => {
|
||||
recapState.keyword = val;
|
||||
recapState.page = 1;
|
||||
fetchRecap();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function changePerPage(val){
|
||||
recapState.perPage = parseInt(val) || 10;
|
||||
recapState.page = 1;
|
||||
fetchRecap();
|
||||
}
|
||||
|
||||
function fetchRecap(){
|
||||
const tbody = document.getElementById('recapBody');
|
||||
const pager = document.getElementById('recapPagination');
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Memuat data...</td></tr>`;
|
||||
if(pager) pager.innerHTML = '';
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: recapState.page,
|
||||
per_page: recapState.perPage,
|
||||
keyword: recapState.keyword || ''
|
||||
});
|
||||
fetch('/data/recap?' + params.toString())
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const rows = json?.data || [];
|
||||
recapState.lastPage = json?.pagination?.last_page || 1;
|
||||
if(!rows.length){
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-muted py-4">Tidak ada data</td></tr>`;
|
||||
return;
|
||||
}
|
||||
const html = rows.map((row, idx) => {
|
||||
const folderRows = (row.data || []).map((f, i) => `
|
||||
<tr>
|
||||
${i === 0 ? `<td rowspan="${row.data.length}" class="text-center align-middle fw-semibold">${idx+1}</td>` : ''}
|
||||
${i === 0 ? `<td rowspan="${row.data.length}" class="fw-semibold">${row.unit || '-'}</td>` : ''}
|
||||
<td>${f.folder || '-'}</td>
|
||||
<td class="text-center fw-bold">${f.count || 0}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
return folderRows;
|
||||
}).join('');
|
||||
tbody.innerHTML = html;
|
||||
renderRecapPagination();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center text-danger py-4">Gagal memuat data</td></tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
function renderRecapPagination(){
|
||||
const pager = document.getElementById('recapPagination');
|
||||
if(!pager) return;
|
||||
if(recapState.lastPage <= 1){
|
||||
pager.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
const maxButtons = 5;
|
||||
let start = Math.max(1, recapState.page - Math.floor(maxButtons/2));
|
||||
let end = Math.min(recapState.lastPage, start + maxButtons - 1);
|
||||
start = Math.max(1, end - maxButtons + 1);
|
||||
|
||||
let buttons = '';
|
||||
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="prev" ${recapState.page === 1 ? 'disabled' : ''}>‹</button>`;
|
||||
for(let i=start; i<=end; i++){
|
||||
buttons += `<button class="btn btn-sm ${i === recapState.page ? 'btn-primary' : 'btn-outline-secondary'}" data-page="${i}">${i}</button>`;
|
||||
}
|
||||
buttons += `<button class="btn btn-outline-secondary btn-sm" data-page="next" ${recapState.page === recapState.lastPage ? 'disabled' : ''}>›</button>`;
|
||||
|
||||
pager.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 ${recapState.page} dari ${recapState.lastPage}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
pager.querySelectorAll('button[data-page]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const page = btn.getAttribute('data-page');
|
||||
if(page === 'prev' && recapState.page > 1) recapState.page--;
|
||||
else if(page === 'next' && recapState.page < recapState.lastPage) recapState.page++;
|
||||
else if(!isNaN(parseInt(page))) recapState.page = parseInt(page);
|
||||
fetchRecap();
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@ -16,7 +16,7 @@
|
||||
<input type="search"
|
||||
id="tableSearch"
|
||||
class="form-control border-start-0"
|
||||
placeholder="Cari nama file atau folder"
|
||||
placeholder="Cari nama file, No Dokumen atau folder"
|
||||
oninput="debouncedTableSearch(this.value)">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@ -61,7 +61,7 @@
|
||||
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 tableState = { data: [], page: 1, pageSize: 8, search: '', lastPage: 1, total: 0 };
|
||||
const tbody = document.getElementById('tableFolderLastUpdated');
|
||||
const paginationEl = document.getElementById('paginationControls');
|
||||
const summaryEl = document.getElementById('tableSummary');
|
||||
@ -75,7 +75,7 @@
|
||||
if(!isNaN(val) && val > 0){
|
||||
tableState.pageSize = val;
|
||||
tableState.page = 1;
|
||||
renderTable();
|
||||
fetchData();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -159,29 +159,16 @@
|
||||
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++;
|
||||
if(tableState.page < tableState.lastPage) tableState.page++;
|
||||
}else{
|
||||
tableState.page = parseInt(page);
|
||||
}
|
||||
renderTable();
|
||||
fetchData();
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
const pageData = tableState.data || [];
|
||||
|
||||
if(pageData.length === 0){
|
||||
tbody.innerHTML = `
|
||||
@ -195,13 +182,13 @@
|
||||
tbody.innerHTML = pageData.map(buildRow).join('');
|
||||
}
|
||||
|
||||
const from = total === 0 ? 0 : startIdx + 1;
|
||||
const to = Math.min(startIdx + pageData.length, total);
|
||||
const from = tableState.total === 0 ? 0 : ((tableState.page -1) * tableState.pageSize) + 1;
|
||||
const to = Math.min(((tableState.page -1) * tableState.pageSize) + pageData.length, tableState.total);
|
||||
if(summaryEl){
|
||||
summaryEl.textContent = total ? `Menampilkan ${from} - ${to} dari ${total} dokumen` : 'Tidak ada data';
|
||||
summaryEl.textContent = tableState.total ? `Menampilkan ${from} - ${to} dari ${tableState.total} dokumen` : 'Tidak ada data';
|
||||
}
|
||||
|
||||
renderPagination(totalPages);
|
||||
renderPagination(tableState.lastPage || 1);
|
||||
}
|
||||
|
||||
function debouncedTableSearch(value){
|
||||
@ -209,17 +196,23 @@
|
||||
window.tableSearchTimer = setTimeout(() => {
|
||||
tableState.search = value.trim();
|
||||
tableState.page = 1;
|
||||
renderTable();
|
||||
fetchData();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function fetchData(){
|
||||
if(summaryEl) summaryEl.textContent = 'Memuat data...';
|
||||
fetch(`/last-document`)
|
||||
const params = new URLSearchParams({
|
||||
page: tableState.page,
|
||||
per_page: tableState.pageSize,
|
||||
keyword: tableState.search
|
||||
});
|
||||
fetch(`/last-document?${params.toString()}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
tableState.data = data?.data || [];
|
||||
tableState.page = 1;
|
||||
tableState.lastPage = data?.pagination?.last_page || 1;
|
||||
tableState.total = data?.pagination?.total || 0;
|
||||
renderTable();
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
@ -63,9 +63,32 @@
|
||||
</span>
|
||||
<span class="hide-menu">Akses</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link justify-content-between"
|
||||
href="/log-activity" aria-expanded="false">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="d-flex">
|
||||
<i class="ti ti-layout-grid"></i>
|
||||
</span>
|
||||
<span class="hide-menu">Log Aktivity</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link justify-content-between"
|
||||
href="/recap" aria-expanded="false">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="d-flex">
|
||||
<i class="ti ti-layout-grid"></i>
|
||||
</span>
|
||||
<span class="hide-menu">Data Rekap</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<!-- End Sidebar navigation -->
|
||||
|
||||
231
resources/views/logActivity/index.blade.php
Normal file
231
resources/views/logActivity/index.blade.php
Normal file
@ -0,0 +1,231 @@
|
||||
@extends('layout.main')
|
||||
@section('body_main')
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h4 class="mb-0">Log Activity</h4>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center gap-2 mb-3 flex-wrap">
|
||||
<div class="input-group input-group-sm flex-grow-1" style="max-width:320px;">
|
||||
<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 / nomor dokumen / aksi"
|
||||
oninput="debouncedTableSearch(this.value)">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<label class="small mb-0 text-muted">Mulai</label>
|
||||
<input type="date" id="startDate" class="form-control form-control-sm" onchange="applyDateFilter()">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<label class="small mb-0 text-muted">Selesai</label>
|
||||
<input type="date" id="endDate" class="form-control form-control-sm" onchange="applyDateFilter()">
|
||||
</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>
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="refreshLog()">
|
||||
<i class="fa fa-rotate"></i> Refresh
|
||||
</button>
|
||||
<div class="small text-muted ms-md-auto" id="tableSummary"></div>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height: 55vh; overflow-y:auto;">
|
||||
<table class="table table-sm table-hover align-middle mb-0" id="lastUpdatedTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nama</th>
|
||||
<th>Unit</th>
|
||||
<th>Aktivitas</th>
|
||||
<th>No Dokumen</th>
|
||||
<th>Tanggal</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableFolderLastUpdated">
|
||||
<!-- 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tableState = { data: [], page: 1, pageSize: 10, search: '', lastPage: 1, total: 0, startDate: '', endDate: '' };
|
||||
const tbody = document.getElementById('tableFolderLastUpdated');
|
||||
const paginationEl = document.getElementById('paginationControls');
|
||||
const summaryEl = document.getElementById('tableSummary');
|
||||
const pageSizeSelect = document.getElementById('tablePageSize');
|
||||
const startDateInput = document.getElementById('startDate');
|
||||
const endDateInput = document.getElementById('endDate');
|
||||
|
||||
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;
|
||||
fetchData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.applyDateFilter = function(){
|
||||
tableState.startDate = startDateInput.value || '';
|
||||
tableState.endDate = endDateInput.value || '';
|
||||
tableState.page = 1;
|
||||
fetchData();
|
||||
}
|
||||
|
||||
function buildRow(item){
|
||||
let unitKerja = item.file ? item.file.split('/')[0] : '-';
|
||||
let tanggal = item.entry_at ? formatTanggal(item.entry_at) : '-'
|
||||
return `
|
||||
<tr>
|
||||
<td>${item.pegawai_nama_entry || '-'}</td>
|
||||
<td>${unitKerja}</td>
|
||||
<td>${item.action_type || '-'}</td>
|
||||
<td>${item.no_dokumen || '-'}</td>
|
||||
<td class="text-nowrap">${tanggal}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
function formatTanggal(dateString) {
|
||||
const d = new Date(dateString);
|
||||
return d.toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
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' : ''}>‹</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' : ''}>›</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'){
|
||||
if(tableState.page < tableState.lastPage) tableState.page++;
|
||||
}else{
|
||||
tableState.page = parseInt(page);
|
||||
}
|
||||
fetchData();
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(){
|
||||
const pageData = tableState.data || [];
|
||||
if(pageData.length === 0){
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">
|
||||
Tidak ada data
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}else{
|
||||
tbody.innerHTML = pageData.map(buildRow).join('');
|
||||
}
|
||||
|
||||
const from = tableState.total === 0 ? 0 : ((tableState.page -1) * tableState.pageSize) + 1;
|
||||
const to = Math.min(((tableState.page -1) * tableState.pageSize) + pageData.length, tableState.total);
|
||||
if(summaryEl){
|
||||
summaryEl.textContent = tableState.total ? `Menampilkan ${from} - ${to} dari ${tableState.total} aktivitas` : 'Tidak ada data';
|
||||
}
|
||||
|
||||
renderPagination(tableState.lastPage || 1);
|
||||
}
|
||||
|
||||
let searchDebounce;
|
||||
window.debouncedTableSearch = function(value){
|
||||
clearTimeout(searchDebounce);
|
||||
searchDebounce = setTimeout(() => {
|
||||
tableState.search = value.trim();
|
||||
tableState.page = 1;
|
||||
fetchData();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
window.refreshLog = function(){
|
||||
tableState.search = '';
|
||||
tableState.startDate = '';
|
||||
tableState.endDate = '';
|
||||
document.getElementById('tableSearch').value = '';
|
||||
startDateInput.value = '';
|
||||
endDateInput.value = '';
|
||||
tableState.page = 1;
|
||||
fetchData();
|
||||
}
|
||||
|
||||
function fetchData(){
|
||||
if(summaryEl) summaryEl.textContent = 'Memuat data...';
|
||||
const params = new URLSearchParams({
|
||||
page: tableState.page,
|
||||
per_page: tableState.pageSize,
|
||||
keyword: tableState.search || '',
|
||||
start_date: tableState.startDate || '',
|
||||
end_date: tableState.endDate || ''
|
||||
});
|
||||
fetch(`/datatable/log-activity?${params.toString()}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
tableState.data = data?.data || [];
|
||||
tableState.lastPage = data?.pagination?.last_page || 1;
|
||||
tableState.total = data?.pagination?.total || 0;
|
||||
renderTable();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
if(summaryEl) summaryEl.textContent = 'Gagal memuat data';
|
||||
});
|
||||
}
|
||||
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
@ -5,6 +5,7 @@ use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
use App\Http\Controllers\MasterKategoriController;
|
||||
use App\Http\Controllers\MasterKlasifikasiController;
|
||||
use App\Http\Controllers\LogActivityController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(['auth'])->group(function(){
|
||||
@ -33,6 +34,12 @@ Route::middleware(['auth'])->group(function(){
|
||||
|
||||
Route::post('/download-multiple', [DashboardController::class, 'downloadDataMultiple']);
|
||||
Route::post('/download-byfolder', [DashboardController::class, 'downloadDataFolder']);
|
||||
|
||||
Route::get('/log-activity', [LogActivityController::class, 'index']);
|
||||
Route::get('/datatable/log-activity', [LogActivityController::class, 'datatable']);
|
||||
|
||||
Route::get('/recap', [DashboardController::class, 'recapView']);
|
||||
Route::get('/data/recap', [DashboardController::class, 'recapData']);
|
||||
});
|
||||
|
||||
Route::get('/login', [AuthController::class, 'index'])->name('login');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user