progress rapiin dashboard

This commit is contained in:
JokoPrasetio 2026-01-21 16:47:31 +07:00
parent 8654e70e9a
commit fc9f6d280b
15 changed files with 939 additions and 119 deletions

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\AksesFile; use App\Models\AksesFile;
use App\Models\FileDirectory; use App\Models\FileDirectory;
use App\Models\LogActivity;
use App\Models\MasterKategori; use App\Models\MasterKategori;
use App\Models\MasterKlasifikasi; use App\Models\MasterKlasifikasi;
use App\Models\SubUnitKerja; use App\Models\SubUnitKerja;
@ -12,6 +13,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use setasign\Fpdi\Fpdi;
use ZipArchive; use ZipArchive;
use function PHPUnit\Framework\isEmpty; use function PHPUnit\Framework\isEmpty;
@ -182,32 +184,7 @@ class DashboardController extends Controller
public function OptionUnitKerja(){ public function OptionUnitKerja(){
$q = request()->get('q'); $q = request()->get('q');
$authPegawai = auth()->user()?->dataUser;
$authUnitKerja = optional($authPegawai->mappingUnitKerjaPegawai[0] ?? null)->objectunitkerjapegawaifk;
$authSubUnitKerja = optional($authPegawai->mappingUnitKerjaPegawai[0] ?? null)->objectsubunitkerjapegawaifk;
$aksesFile = AksesFile::where('pegawai_id', $authPegawai->id)
->where('statusenabled', true)
->first();
$query = UnitKerja::where('statusenabled', true); $query = UnitKerja::where('statusenabled', true);
if($aksesFile){
if($aksesFile->all_akses){
}elseif($aksesFile->unit_akses){
$query->where('id', $aksesFile->unit_akses);
}
}else{
$data= $query->where('id', $authUnitKerja)
->with(['subUnitKerja' => function($query) use($authSubUnitKerja){
$query->where('id', $authSubUnitKerja);
}])->select('id', 'name')->get();
return response()->json([
'status' => true,
'data' => $data
], 200);
}
$data = $query->when($q, function ($query, $q){ $data = $query->when($q, function ($query, $q){
$query->where('name', 'ILIKE', '%' .$q . '%'); $query->where('name', 'ILIKE', '%' .$q . '%');
@ -220,30 +197,60 @@ class DashboardController extends Controller
} }
public function deleteFile(string $id){ public function deleteFile(string $id){
$data = FileDirectory::where('file_directory_id', $id)->first(); DB::connection('dbDirectory')->beginTransaction();
if(!$data){ try {
return response()->json([ $data = FileDirectory::where('file_directory_id', $id)->first();
'success' => false, if(!$data){
'message' => 'File tidak ditemukan' return response()->json([
]); 'success' => false,
} 'message' => 'File tidak ditemukan'
$oldPath= public_path('file/' . $data->file); ]);
$fileInfo = pathinfo($data->file);
$newFileName = $fileInfo['filename'] . '_deleted.' . $fileInfo['extension'];
$newPath = public_path('file/' . $fileInfo['dirname'] . '/' . $newFileName);
if (file_exists($oldPath)) {
// pastikan folder tujuan ada
if (!is_dir(dirname($newPath))) {
mkdir(dirname($newPath), 0777, true);
}
rename($oldPath, $newPath);
} }
$oldPath= public_path('file/' . $data->file);
$fileInfo = pathinfo($data->file);
$newFileName = $fileInfo['filename'] . '_deleted.' . $fileInfo['extension'];
$newPath = public_path('file/' . $fileInfo['dirname'] . '/' . $newFileName);
if (file_exists($oldPath)) {
// pastikan folder tujuan ada
if (!is_dir(dirname($newPath))) {
mkdir(dirname($newPath), 0777, true);
}
rename($oldPath, $newPath);
}
$data->update(['statusenabled' => false, 'file' => $fileInfo['dirname'].'/'. $newFileName]);
$payloadLog = [
'file_directory_id' => $data->file_directory_id,
'pegawai_id_entry' => $data->pegawai_id_entry,
'pegawai_nama_entry' => $data->pegawai_nama_entry,
'entry_at' => $data->entry_at,
'action_type' => 'Hapus Dokumen',
'statusenabled' => true,
'mod_change' => $data->entry_at,
'id_unit_kerja' => $data->id_unit_kerja,
'id_sub_unit_kerja' => $data->id_sub_unit_kerja,
'file' => $data->file,
'tanggal_terbit' => $data->tanggal_terbit,
'no_dokumen' => $data->no_dokumen,
'permission_file' => $data->permission_file,
];
LogActivity::create($payloadLog);
DB::connection('dbDirectory')->commit();
return response()->json([
'success' => true,
'message' => 'Berhasil menghapus data'
]);
} catch (\Throwable $th) {
DB::connection('dbDirectory')->rollBack();
return response()->json([
'success' => false,
'message' => 'Gagal menghapus data'
]);
//throw $th;
}
$data->update(['statusenabled' => false, 'file' => $fileInfo['dirname'].'/'. $newFileName]);
return response()->json([
'success' => true,
'message' => 'Berhasil menghapus data'
]);
} }
public function optionSubUnitKerja(string $id){ public function optionSubUnitKerja(string $id){
@ -349,8 +356,15 @@ class DashboardController extends Controller
} }
public function dashboardVersion2(){ public function dashboardVersion2(){
$katDok = MasterKategori::where('statusenabled', true)->select('master_kategori_directory_id', 'nama_kategori_directory')->get();
$authMapping = auth()->user()?->dataUser?->mappingUnitKerjaPegawai[0];
$authUnitKerja = $authMapping->objectunitkerjapegawaifk;
$authSubUnitKerja = $authMapping->objectsubunitkerjapegawaifk;
$data = [ $data = [
'title' => 'Dashboard', 'title' => 'Dashboard',
'katDok' => $katDok,
'authUnitKerja' => $authUnitKerja,
'authSubUnitKerja' => $authSubUnitKerja,
]; ];
return view('dashboardV2.index', $data); return view('dashboardV2.index', $data);
} }
@ -372,4 +386,148 @@ class DashboardController extends Controller
]; ];
return response()->json($payload); return response()->json($payload);
} }
public function storeVersion2(){
DB::connection('dbDirectory')->beginTransaction();
try {
$datas = request('data');
foreach ($datas as $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);
$payload = [
'id_unit_kerja' => $id_unit_kerja,
'id_sub_unit_kerja' => $id_sub_unit_kerja,
'master_kategori_directory_id' => $master_kategori_directory_id,
'pegawai_id_entry' => auth()->user()->dataUser->id ?? 1,
'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,
];
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;
}
$fd = FileDirectory::create($payload);
$payloadLog = [
'file_directory_id' => $fd->file_directory_id,
'pegawai_id_entry' => $fd->pegawai_id_entry,
'pegawai_nama_entry' => $fd->pegawai_nama_entry,
'entry_at' => $fd->entry_at,
'action_type' => 'Upload Dokumen',
'statusenabled' => true,
'mod_change' => $fd->entry_at,
'id_unit_kerja' => $fd->id_unit_kerja,
'id_sub_unit_kerja' => $fd->id_sub_unit_kerja,
'file' => $fd->file,
'tanggal_terbit' => $fd->tanggal_terbit,
'no_dokumen' => $fd->no_dokumen,
'permission_file' => $fd->permission_file,
];
LogActivity::create($payloadLog);
}
DB::connection('dbDirectory')->commit();
return response()->json([
'status' => true,
'message' => 'Data berhasil disimpan'
], 200);
} catch (\Throwable $th) {
DB::connection('dbDirectory')->rollback();
return response()->json([
'status' => false,
'message' => $th->getMessage()
], 500);
}
}
public function dataPdf($fileDirectoryId)
{
$data = FileDirectory::where('file_directory_id', $fileDirectoryId)->first();
$filePath = public_path('file/' . $data->file);
if (!file_exists($filePath)) {
abort(404, 'PDF Tidak ditemukan');
}
$stampFile = public_path('assets/copy.png');
if (!file_exists($stampFile)) {
// kalau watermark tidak ada, tampilkan file asli
return response()->file($filePath, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="preview.pdf"',
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
]);
}
$tempConverted = storage_path('app/temp/' . uniqid('conv_') . '.pdf');
try {
// coba watermark langsung
return $this->watermarkCenterAndStream($filePath, $stampFile);
} catch (\Throwable $e) {
// kalau gagal (PDF modern) -> convert dulu
// dd($e);
$this->convertWithGhostscript($filePath, $tempConverted);
$resp = $this->watermarkCenterAndStream($tempConverted, $stampFile);
@unlink($tempConverted);
return $resp;
}
}
private function watermarkCenterAndStream(string $pdfPath, string $stampFile)
{
$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);
// watermark tengah (skala adaptif)
$stampW = $size['width'] * 0.60;
$stampH = $stampW * 0.75; // sesuaikan rasio gambar kalau perlu
$x = ($size['width'] - $stampW) / 2;
$y = ($size['height'] - $stampH) / 2;
$pdf->Image($stampFile, $x, $y, $stampW, $stampH);
}
$output = $pdf->Output('S');
return response($output, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="preview.pdf"',
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
]);
}
private function convertWithGhostscript(string $inputPdf, string $outputPdf): void
{
$gs = config('services.ghostscript.path');
if (!$gs || !file_exists($gs)) {
throw new \RuntimeException('Ghostscript tidak ditemukan. Cek GHOSTSCRIPT_PATH di .env');
}
$cmd = "\"{$gs}\" -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/prepress "
. "-dNOPAUSE -dBATCH -dQUIET -sOutputFile=\"{$outputPdf}\" \"{$inputPdf}\"";
exec($cmd, $out, $code);
if ($code !== 0 || !file_exists($outputPdf)) {
throw new \RuntimeException('Convert Ghostscript gagal (code=' . $code . ')');
}
}
} }

View File

@ -12,10 +12,4 @@ class FileDirectory extends Model
protected $primaryKey = 'file_directory_id'; protected $primaryKey = 'file_directory_id';
protected $guarded = ['file_directory_id']; protected $guarded = ['file_directory_id'];
// protected $with = ['klasifikasi'];
// // public function klasifikasi(){
// // return $this->belongsTo(MasterKlasifikasi::class, 'master_klasifikasi_directory_id', 'master_klasifikasi_directory_id');
// // }
} }

View File

@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LogActivity extends Model
{
protected $connection = 'dbDirectory';
protected $table = 'logging.log_activity_file_directory';
public $timestamps = false;
protected $primaryKey = 'id';
protected $guarded = ['id'];
}

View File

@ -8,7 +8,9 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1" "laravel/tinker": "^2.10.1",
"setasign/fpdf": "^1.8",
"setasign/fpdi": "^2.6"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",

120
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "c514d8f7b9fc5970bdd94287905ef584", "content-hash": "26cb6fff423ee0edff5bec050b76b10f",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -3274,6 +3274,124 @@
}, },
"time": "2025-06-25T14:20:11+00:00" "time": "2025-06-25T14:20:11+00:00"
}, },
{
"name": "setasign/fpdf",
"version": "1.8.6",
"source": {
"type": "git",
"url": "https://github.com/Setasign/FPDF.git",
"reference": "0838e0ee4925716fcbbc50ad9e1799b5edfae0a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Setasign/FPDF/zipball/0838e0ee4925716fcbbc50ad9e1799b5edfae0a0",
"reference": "0838e0ee4925716fcbbc50ad9e1799b5edfae0a0",
"shasum": ""
},
"require": {
"ext-gd": "*",
"ext-zlib": "*"
},
"type": "library",
"autoload": {
"classmap": [
"fpdf.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Olivier Plathey",
"email": "oliver@fpdf.org",
"homepage": "http://fpdf.org/"
}
],
"description": "FPDF is a PHP class which allows to generate PDF files with pure PHP. F from FPDF stands for Free: you may use it for any kind of usage and modify it to suit your needs.",
"homepage": "http://www.fpdf.org",
"keywords": [
"fpdf",
"pdf"
],
"support": {
"source": "https://github.com/Setasign/FPDF/tree/1.8.6"
},
"time": "2023-06-26T14:44:25+00:00"
},
{
"name": "setasign/fpdi",
"version": "v2.6.4",
"source": {
"type": "git",
"url": "https://github.com/Setasign/FPDI.git",
"reference": "4b53852fde2734ec6a07e458a085db627c60eada"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/4b53852fde2734ec6a07e458a085db627c60eada",
"reference": "4b53852fde2734ec6a07e458a085db627c60eada",
"shasum": ""
},
"require": {
"ext-zlib": "*",
"php": "^7.1 || ^8.0"
},
"conflict": {
"setasign/tfpdf": "<1.31"
},
"require-dev": {
"phpunit/phpunit": "^7",
"setasign/fpdf": "~1.8.6",
"setasign/tfpdf": "~1.33",
"squizlabs/php_codesniffer": "^3.5",
"tecnickcom/tcpdf": "^6.8"
},
"suggest": {
"setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured."
},
"type": "library",
"autoload": {
"psr-4": {
"setasign\\Fpdi\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Slabon",
"email": "jan.slabon@setasign.com",
"homepage": "https://www.setasign.com"
},
{
"name": "Maximilian Kresse",
"email": "maximilian.kresse@setasign.com",
"homepage": "https://www.setasign.com"
}
],
"description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.",
"homepage": "https://www.setasign.com/fpdi",
"keywords": [
"fpdf",
"fpdi",
"pdf"
],
"support": {
"issues": "https://github.com/Setasign/FPDI/issues",
"source": "https://github.com/Setasign/FPDI/tree/v2.6.4"
},
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/setasign/fpdi",
"type": "tidelift"
}
],
"time": "2025-08-05T09:57:14+00:00"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v7.3.0", "version": "v7.3.0",

View File

@ -34,5 +34,8 @@ return [
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
], ],
], ],
'ghostscript' => [
'path' => env('GHOSTSCRIPT_PATH'),
],
]; ];

BIN
public/assets/copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

@ -40,33 +40,20 @@ function renderTree(units, katDok, keyword) {
<ul class="file-tree-ul ms-2"> <ul class="file-tree-ul ms-2">
${files.map(file => { ${files.map(file => {
let fileName = file.file.split('/').pop(); let fileName = file.file.split('/').pop();
let uploadedBy = file.pegawai_nama_entry || "Unknown"; console.log(file);
let uploadedAt = new Date(file.entry_at).toLocaleString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit"
});
return ` return `
<li class="file-tree-li"> <li class="file-tree-li">
<div class=""> <div class="">
📄 <a href="#" 📄
class="file-link" <a href="#" class="file-link"
data-file="${file?.file}" data-file="${file?.file}"
data-name_file="${fileName}" data-fileName="${fileName || '-'}"
data-upload="${uploadedBy}" data-id="${file.file_directory_id}"
data-time="${uploadedAt}" data-no_dokumen="${file.no_dokumen || '-'}"
data-klasifikasi="${file?.klasifikasi?.nama_klasifikasi_directory}" data-tanggal_terbit="${file.tanggal_terbit || '-'}" data-permission_file="${file.permission_file || '-'}">${fileName}</a>
data-id="${file?.file_directory_id}"
title="Diupload oleh: ${uploadedBy} pada ${uploadedAt}">
${fileName}
</a>
</div> </div>
<small class="text-muted fst-italic">
Upload by ${uploadedBy} · ${uploadedAt}
</small>
</li> </li>
`; `;

View File

@ -153,6 +153,7 @@
let upload = e.target.getAttribute('data-upload'); let upload = e.target.getAttribute('data-upload');
let time = e.target.getAttribute('data-time'); let time = e.target.getAttribute('data-time');
let klasifikasiView = e.target.getAttribute('data-klasifikasi'); let klasifikasiView = e.target.getAttribute('data-klasifikasi');
console.log(fileUrl);
$("#confirm-upload-dokumen").html(upload); $("#confirm-upload-dokumen").html(upload);
$("#confirm-time-dokumen").html(time); $("#confirm-time-dokumen").html(time);
@ -164,17 +165,8 @@
let ext = fileUrl.split('.').pop().toLowerCase(); let ext = fileUrl.split('.').pop().toLowerCase();
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>`;
if (['jpg','jpeg','png','gif','webp'].includes(ext)) { $("#previewModal").modal('show')
previewBox.innerHTML = `<img src="/file/${fileUrl}" class="img-fluid rounded shadow-sm" width="100%" alt="preview">`;
} else if (ext === 'pdf') {
previewBox.innerHTML = `<iframe src="/file/${fileUrl}" width="100%" height="500px" style="border:none;"></iframe>`;
} else {
previewBox.innerHTML = `
<p class="text-muted">Tidak bisa preview file ini. Silakan download:</p>
<a href="/file/${fileUrl}" target="_blank" class="btn btn-sm btn-primary">⬇️ Download</a>
`;
}
// 🔥 Tampilkan modal // 🔥 Tampilkan modal
$("#previewModal").modal("show"); $("#previewModal").modal("show");
@ -261,19 +253,19 @@
} }
$("#download-file").off('click').on('click', function(){ // $("#download-file").off('click').on('click', function(){
if(currentFile){ // if(currentFile){
let link = document.createElement('a'); // let link = document.createElement('a');
link.href = 'file/' + currentFile; // alamat file // link.href = 'file/' + currentFile; // alamat file
link.download = currentFile.split('/').pop(); // nama file otomatis // link.download = currentFile.split('/').pop(); // nama file otomatis
document.body.appendChild(link); // document.body.appendChild(link);
link.click(); // link.click();
document.body.removeChild(link); // document.body.removeChild(link);
}else{ // }else{
console.error('error'); // console.error('error');
} // }
}) // })
}); });

View File

@ -15,7 +15,6 @@
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;"> <div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
<div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10"> <div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
<button type="button" class="btn btn-sm btn-outline-danger mb-2 me-2" id="delete-file">🗑 Hapus</button> <button type="button" class="btn btn-sm btn-outline-danger mb-2 me-2" id="delete-file">🗑 Hapus</button>
<button type="button" class="btn btn-sm btn-outline-primary mb-2" id="download-file"> Download</button>
</div> </div>
<div id="file-preview" <div id="file-preview"
class="text-center text-muted d-flex justify-content-center align-items-center" class="text-center text-muted d-flex justify-content-center align-items-center"
@ -24,17 +23,38 @@
</div> </div>
</div> </div>
<!-- Footer --> <!-- Footer -->
<div class="modal-footer d-flex justify-content-between"> <div class="modal-footer d-flex justify-content-between align-items-center">
<div>
<div class="text-black">Klasifikasi Dokumen <strong id="confirm-upload-klasifikasi"></strong></div> <div class="small text-muted">
<div class="mt-2 text-muted fst-italic small text-black"> <span class="me-3">
Ditambahkan oleh <strong id="confirm-upload-dokumen"></strong> pada <span id="confirm-time-dokumen"></span> Nomor Dokumen:
</div> <strong id="confirm-upload-dokumen" class="text-dark">-</strong>
</span>
<span class="me-3">
Tanggal Terbit:
<strong id="confirm-time-dokumen" class="text-dark">-</strong>
</span>
<span>
Akses Dokumen:
<span id="confirm-permission" class="badge bg-secondary">-</span>
</span>
</div>
<!-- Button -->
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-primary" id="btn-view-full">
Lihat Full
</button>
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">
Tutup
</button>
</div>
</div> </div>
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Tutup</button>
</div>
</div> </div>
</div> </div>

View File

@ -3,13 +3,14 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header d-flex align-items-center justify-content-between">
<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>
</div> </div>
<div class="card-body p-2"> <div class="card-body p-2">
<div class="d-flex mb-3"> <div class="d-flex align-items-center gap-2 mb-3">
<input type="text" onchange="searchData(this)" class="form-control form-control-sm" placeholder="Search"> <input type="text" onchange="searchData(this)" class="form-control" placeholder="Search">
<button type="button" class="btn btn-primary ms-2">Cari</button> <button type="button" class="btn btn-primary">Cari</button>
</div> </div>
<table class="table table-bordered"> <table class="table table-bordered">
<thead> <thead>
@ -28,8 +29,14 @@
</div> </div>
</div> </div>
</div> </div>
@include('dashboardV2.modal.create')
@include('dashboardV2.modal.view')
<script> <script>
const katDok = @json($katDok);
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]);
function fetchData(){ function fetchData(){
fetch(`/last-document`) fetch(`/last-document`)
.then(response => response.json()) .then(response => response.json())
@ -49,6 +56,7 @@
} }
tbody.innerHTML = resData.map(item => { tbody.innerHTML = resData.map(item => {
const fullPath = item.file; const fullPath = item.file;
console.log(item);
const parts = fullPath.split('/'); const parts = fullPath.split('/');
@ -57,7 +65,12 @@
return ` return `
<tr> <tr>
<td>${fileName}</td> <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>${folderPath}</td>
<td>${formatTanggal(item.entry_at)}</td> <td>${formatTanggal(item.entry_at)}</td>
</tr> </tr>
@ -81,5 +94,365 @@
}); });
} }
fetchData() fetchData()
let colCount = 1;
$(document).ready(function() {
$('.unit_kerja').select2({
placeholder: '--- Pilih Unit Kerja ---',
allowClear: true,
width: '100%',
ajax: {
url : '/select-unit-kerja',
dataType: 'json',
delay: 250,
data: function(params){
let q = '';
if(allAkses){
q = params.term;
}else{
q = authUnitKerja?.name ?? '';
}
return { q };
},
processResults: function(data){
let results = data?.data.map(item => ({
id: item.id,
text: item.name
}));
return { results };
},
cache: true,
},
minimumInputLength: 0,
});
$('.sub_unit_kerja').select2({
placeholder: '-- Pilih Sub Unit Kerja --',
allowClear: true,
width: '100%',
});
// --- isi default unit kerja ---
if(authUnitKerja){
let option = new Option(authUnitKerja.name, authUnitKerja.id, true, true);
$('.unit_kerja').append(option).trigger('change');
}
let initialUnit = $('.unit_kerja').val();
if(initialUnit){
loadSubUnitKerja(initialUnit);
}
// jalankan setiap kali unit_kerja berubah
$('.unit_kerja').on('change', function(){
let idUnit = $(this).val();
if(idUnit){
loadSubUnitKerja(idUnit);
}
});
selectOptionUnitKerjaV1(0);
});
function loadSubUnitKerja(unitId){
$('.sub_unit_kerja').empty().append('<option value="" disabled>-- Pilih Sub Unit Kerja --</option>');
$.ajax({
url: `/select-sub-unit-kerja/${unitId}`,
method: 'GET',
success: function(response) {
if (response?.data) {
response.data.forEach(unit => {
let selected = (authSubUnitKerja && unit.id === authSubUnitKerja.objectsubunitkerjapegawaifk);
const option = new Option(unit.name, unit.id, false, selected);
$('.sub_unit_kerja').append(option);
});
$('.sub_unit_kerja').trigger('change');
}
}
});
}
function addFormV2(){
let col = $("#col_add_fileV2");
let html = '';
html += `
<div class="row g-3 align-items-start" id="col-${colCount}">
<hr class="my-3" />
<div class="col-12 d-flex justify-content-end">
<button type="button"
class="btn btn-sm btn-danger"
onclick="removeCol(${colCount})">
<i class="fa-solid fa-trash"></i> Hapus
</button>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Unit <span class="text-danger">*</span></label>
<select class="form-select"
name="data[${colCount}][id_unit_kerja]"
id="select_id_unit_kerja_${colCount}"
required>
<option value="" disabled selected>Pilih Unit</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Sub Unit <span class="text-danger">*</span></label>
<select class="form-select"
name="data[${colCount}][id_sub_unit_kerja]"
id="select_id_sub_unit_kerja_${colCount}"
required>
<option value="" disabled selected>Pilih Sub Unit</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Kategori Dokumen <span class="text-danger">*</span></label>
<select class="form-select"
name="data[${colCount}][master_kategori_directory_id]"
id="select_kategori_${colCount}"
required>
<option value="" disabled selected>Pilih Kategori</option>
@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">
<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 class="form-text text-muted">Opsional, isi jika ada nomor resmi dokumen.</div>
</div>
<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"
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)
selectOptionUnitKerjaV1(colCount)
colCount++;
}
function removeCol(count){
$(`#col-${count}`).remove()
}
function selectOptionUnitKerjaV1(colCount) {
let selectUnit = $(`#select_id_unit_kerja_${colCount}`);
let selectSubUnit = $(`#select_id_sub_unit_kerja_${colCount}`);
// inisialisasi select2 untuk Unit Kerja
selectUnit.select2({
placeholder: '-- Pilih Unit Kerja --',
allowClear:true,
width: '100%',
dropdownParent: selectUnit.parent(),
ajax:{
url : '/select-unit-kerja',
dataType: 'json',
delay: 250,
data: function(params){
return { q: params.term }
},
processResults: function(data){
return {
results : data?.data.map(item => ({
id: item.id+'/'+item.name,
text: item.name,
sub_units: item.sub_unit_kerja // kirim ke front
}))
}
},
cache: true,
},
minimumInputLength: 1,
});
selectSubUnit.select2({
placeholder: '-- Pilih Sub Unit Kerja --',
allowClear: true,
width: '100%',
dropdownParent: selectSubUnit.parent()
});
// event ketika unit kerja dipilih
selectUnit.on('select2:select', function (e) {
let data = e.params.data; // data unit kerja terpilih
selectSubUnit.empty().append('<option value="" disabled selected>-- Pilih Sub Unit Kerja --</option>');
if (data.sub_units && data.sub_units.length > 0) {
data.sub_units.forEach(sub => {
selectSubUnit.append(`<option value="${sub.id}/${sub.name}">${sub.name}</option>`);
});
}
// aktifkan select2 untuk sub unit
});
}
let currentFile = null;
let idDirectory = null;
document.addEventListener('click', function(e){
if(e.target.matches('.file-link')){
e.preventDefault();
let fileUrl = e.target.getAttribute('data-file');
let noDokumen = e.target.getAttribute('data-no_dokumen')
let tanggalTerbit = e.target.getAttribute('data-tanggal_terbit')
let permissionFile = e.target.getAttribute('data-permission_file')
let fileName = e.target.getAttribute('data-fileName')
currentFile = fileUrl;
idDirectory = e.target.getAttribute('data-id');
const titleEl = document.getElementById('confirm_preview_file');
if (titleEl) titleEl.textContent = fileName;
// set footer info
const noEl = document.getElementById('confirm-upload-dokumen');
if (noEl) noEl.textContent = noDokumen;
const tglEl = document.getElementById('confirm-time-dokumen');
if (tglEl) tglEl.textContent = tanggalTerbit;
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');
}
let previewBox = document.getElementById('file-preview');
previewBox.innerHTML = `<iframe src="/file-preview/${idDirectory}" width="100%" height="500px" style="border:none;"></iframe>`;
$("#previewModal").modal('show')
}
if(e.target.id === 'delete-file'){
if(!currentFile){
Swal.fire({
text: "Tidak ada file yang dipilih!",
icon: "warning",
confirmButtonText: "OK"
});
return;
}
Swal.fire({
title: 'Yakin ingin menghapus file ini?',
text: "File akan dihapus",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Ya, hapus',
cancelButtonText: 'Batal'
}).then((result) => {
if(result.isConfirmed){
fetch(`/delete-file/${idDirectory}`, {
method: 'DELETE',
headers: {
'Content-Type' : 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ file: currentFile })
})
.then(res => res.json())
.then(data => {
if(data.success){
Swal.fire({
text: 'File berhasil dihapus',
icon: 'success',
timer: 2000,
showConfirmButton: false
});
$("#previewModal").modal("hide");
fetchData()
currentFile = null;
idDirectory = null;
}else{
Swal.fire({
text: 'Gagal menghapus file',
icon: 'error'
});
}
})
.catch(err => {
console.log(err);
Swal.fire({
text: 'Terjadi error saat menghapus file ',
icon: 'error'
});
});
}
})
}
if(e.target.matches('#btn-view-full')){
window.open(`/file-preview/${idDirectory}`, '_blank');
}
})
</script> </script>
@endsection @endsection

View File

@ -0,0 +1,96 @@
<div class="modal fade" id="modalCreateFile" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h1 class="modal-title fs-5">Aksi </h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Modal Form -->
<form id="formFile" action="/uploadv2" enctype="multipart/form-data" method="POST">
@csrf
<div class="modal-body">
<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-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 selected>Select Choose</option>
</select>
</div>
<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)
<option value="{{ $kat->master_kategori_directory_id }}/{{ $kat->nama_kategori_directory }}">{{ $kat->nama_kategori_directory }}</option>
@endforeach
</select>
</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[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 (PDF)</label>
<div class="border rounded-3 p-3 bg-white shadow-sm">
<input class="form-control" 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">
Bisa upload lebih dari 1 file. Format yang didukung: <b>PDF</b>.
</div>
</div>
<div id="col_add_fileV2" class="col-12"></div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-3" onclick="addFormV2()">
+ Tambah Form
</button>
</div>
</div>
<!-- Modal Footer -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
<button type="submit" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,61 @@
<!-- Modal Preview -->
<div class="modal fade" id="previewModal" tabindex="-1" aria-labelledby="previewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<!-- Header -->
<div class="modal-header">
<h6 class="modal-title" id="previewModalLabel">
📄 Preview <strong id="confirm_preview_file"></strong>
</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Body -->
<div class="modal-body p-2" style="min-height:250px; max-height:70vh; overflow:auto;">
<div class="d-flex justify-content-end align-items-center sticky-top bg-white z-10">
<button type="button" class="btn btn-sm btn-outline-danger mb-2 me-2" id="delete-file">🗑 Hapus</button>
</div>
<div id="file-preview"
class="text-center text-muted d-flex justify-content-center align-items-center"
style="height:100%;">
<p>📂 Pilih file untuk melihat preview</p>
</div>
</div>
<!-- Footer -->
<div class="modal-footer d-flex justify-content-between align-items-center">
<div class="small text-muted">
<span class="me-3">
Nomor Dokumen:
<strong id="confirm-upload-dokumen" class="text-dark">-</strong>
</span>
<span class="me-3">
Tanggal Terbit:
<strong id="confirm-time-dokumen" class="text-dark">-</strong>
</span>
<span>
Akses Dokumen:
<span id="confirm-permission" class="badge bg-secondary">-</span>
</span>
</div>
<!-- Button -->
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-primary" id="btn-view-full">
Lihat Full
</button>
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">
Tutup
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -43,7 +43,7 @@
</a> </a>
</li> </li>
<li class="sidebar-item"> {{-- <li class="sidebar-item">
<a class="sidebar-link justify-content-between" <a class="sidebar-link justify-content-between"
href="/master-klasifikasi" aria-expanded="false"> href="/master-klasifikasi" aria-expanded="false">
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
@ -53,7 +53,7 @@
<span class="hide-menu">Master Klasifikasi</span> <span class="hide-menu">Master Klasifikasi</span>
</div> </div>
</a> </a>
</li> </li> --}}
<li class="sidebar-item"> <li class="sidebar-item">
<a class="sidebar-link justify-content-between" <a class="sidebar-link justify-content-between"
href="/akses" aria-expanded="false"> href="/akses" aria-expanded="false">

View File

@ -12,6 +12,8 @@ Route::middleware(['auth'])->group(function(){
Route::get('/', [DashboardController::class, 'index']); Route::get('/', [DashboardController::class, 'index']);
Route::get('/new', [DashboardController::class, 'dashboardVersion2']); Route::get('/new', [DashboardController::class, 'dashboardVersion2']);
Route::get('/last-document', [DashboardController::class, 'dataDocumentLast']); Route::get('/last-document', [DashboardController::class, 'dataDocumentLast']);
Route::post('/uploadv2', [DashboardController::class, 'storeVersion2']);
Route::get('/file-preview/{id}', [DashboardController::class, 'dataPdf']);
Route::post('/upload', [DashboardController::class, 'store']); Route::post('/upload', [DashboardController::class, 'store']);
Route::get('/data-unit-kerja', [DashboardController::class, 'dataUnitKerja']); Route::get('/data-unit-kerja', [DashboardController::class, 'dataUnitKerja']);