diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..4e7467d
Binary files /dev/null and b/.DS_Store differ
diff --git a/Helpers/PdfHelper.php b/Helpers/PdfHelper.php
new file mode 100644
index 0000000..ace93d9
--- /dev/null
+++ b/Helpers/PdfHelper.php
@@ -0,0 +1,83 @@
+ '<',
+ '>' => '>',
+ '&' => '&',
+ '"' => '"',
+ "'" => ''',
+ '≤' => '≤',
+ '≥' => '≥',
+ '≠' => '!=',
+ '≈' => '~',
+ '×' => 'x',
+ '÷' => '/',
+ '–' => '-',
+ '—' => '-',
+ '' => '"',
+ '' => '"',
+ '' => "'",
+ '' => "'",
+ ];
+
+ // First pass: replace with HTML entities
+ $safeText = str_replace(array_keys($replacements), array_values($replacements), $text);
+
+ // Ensure UTF-8 encoding
+ if (!mb_check_encoding($safeText, 'UTF-8')) {
+ $safeText = mb_convert_encoding($safeText, 'UTF-8', 'auto');
+ }
+
+ // Remove any remaining non-ASCII characters that could cause issues
+ $safeText = preg_replace('/[^\x20-\x7E\xA0-\xFF]/', '', $safeText);
+
+ return $safeText;
+ }
+
+ /**
+ * Format mathematical symbols to text representation
+ *
+ * @param string $text
+ * @return string
+ */
+ public static function formatMathSymbols($text)
+ {
+ if (empty($text)) {
+ return '';
+ }
+
+ $mathReplacements = [
+ '<' => 'kurang dari',
+ '>' => 'lebih dari',
+ '<=' => 'kurang dari sama dengan',
+ '>=' => 'lebih dari sama dengan',
+ '!=' => 'tidak sama dengan',
+ '==' => 'sama dengan',
+ '≤' => 'kurang dari sama dengan',
+ '≥' => 'lebih dari sama dengan',
+ '≠' => 'tidak sama dengan',
+ '≈' => 'kira-kira',
+ '≡' => 'identik dengan',
+ '≅' => 'hampir sama dengan',
+ ];
+
+ return str_replace(array_keys($mathReplacements), array_values($mathReplacements), $text);
+ }
+}
diff --git a/Http/Controllers/ImageController.php b/Http/Controllers/ImageController.php
new file mode 100644
index 0000000..5542013
--- /dev/null
+++ b/Http/Controllers/ImageController.php
@@ -0,0 +1,27 @@
+imageService = $imageService;
+ }
+
+ public function show(Request $request, $path)
+ {
+ $width = $request->query('w');
+ $quality = $request->query('q', 80);
+
+ $resizedPath = $this->imageService->resize($path, $width, null, $quality);
+
+ return response()->file(storage_path('app/public/' . $resizedPath));
+ }
+}
diff --git a/app/.DS_Store b/app/.DS_Store
new file mode 100644
index 0000000..a7bb668
Binary files /dev/null and b/app/.DS_Store differ
diff --git a/app/Exports/ReferensiLinkExport.php b/app/Exports/ReferensiLinkExport.php
new file mode 100644
index 0000000..2af7269
--- /dev/null
+++ b/app/Exports/ReferensiLinkExport.php
@@ -0,0 +1,128 @@
+filters = $filters;
+ }
+
+ /**
+ * Query data yang akan diexport
+ */
+ public function query()
+ {
+ $query = ReferensiLink::with(['createdBy', 'updatedBy'])
+ ->select('referensi_link.*');
+
+ // Apply filters
+ if (isset($this->filters['kategori']) && !empty($this->filters['kategori'])) {
+ $query->where('kategori', $this->filters['kategori']);
+ }
+
+ if (isset($this->filters['status']) && $this->filters['status'] !== '') {
+ $query->where('is_active', $this->filters['status']);
+ }
+
+ if (isset($this->filters['search']) && !empty($this->filters['search'])) {
+ $query->search($this->filters['search']);
+ }
+
+ return $query->ordered();
+ }
+
+ /**
+ * Header kolom
+ */
+ public function headings(): array
+ {
+ return [
+ 'No',
+ 'Nama',
+ 'Link',
+ 'Kategori',
+ 'Deskripsi',
+ 'Status Aktif',
+ 'Urutan',
+ 'Dibuat Oleh',
+ 'Diupdate Oleh',
+ 'Tanggal Dibuat',
+ 'Tanggal Diupdate',
+ ];
+ }
+
+ /**
+ * Mapping data untuk setiap baris
+ */
+ public function map($referensiLink): array
+ {
+ static $rowNumber = 0;
+ $rowNumber++;
+
+ return [
+ $rowNumber,
+ $referensiLink->name,
+ $referensiLink->link,
+ $referensiLink->kategori ?? '-',
+ $referensiLink->deskripsi ?? '-',
+ $referensiLink->is_active ? 'Aktif' : 'Tidak Aktif',
+ $referensiLink->urutan,
+ $referensiLink->createdBy ? $referensiLink->createdBy->name : '-',
+ $referensiLink->updatedBy ? $referensiLink->updatedBy->name : '-',
+ $referensiLink->created_at->format('d-m-Y H:i:s'),
+ $referensiLink->updated_at->format('d-m-Y H:i:s'),
+ ];
+ }
+
+ /**
+ * Styling untuk worksheet
+ */
+ public function styles(Worksheet $sheet)
+ {
+ return [
+ // Header styling
+ 1 => [
+ 'font' => ['bold' => true, 'size' => 12],
+ 'fill' => ['fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, 'startColor' => ['rgb' => 'E2EFDA']],
+ 'alignment' => ['horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER],
+ ],
+ // Alternating row colors
+ 'A2:K1000' => [
+ 'fill' => ['fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, 'startColor' => ['rgb' => 'F8F9FA']],
+ ],
+ ];
+ }
+
+ /**
+ * Format kolom
+ */
+ public function columnFormats(): array
+ {
+ return [
+ 'F' => NumberFormat::FORMAT_DATE_DDMMYYYY, // Tanggal dibuat
+ 'G' => NumberFormat::FORMAT_DATE_DDMMYYYY, // Tanggal diupdate
+ ];
+ }
+
+ /**
+ * Custom title untuk sheet
+ */
+ public function title(): string
+ {
+ return 'Referensi Link';
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/# Run on VM.sh b/app/Http/Controllers/# Run on VM.sh
new file mode 100644
index 0000000..93ed1a1
--- /dev/null
+++ b/app/Http/Controllers/# Run on VM.sh
@@ -0,0 +1,32 @@
+# Run on VM
+rclone hashsum MD5 "gdrive:01_Data Mentah/itdel/bawang_putih/BPM1" --drive-shared-with-me --output-file itdel_bawang_putih_gdrive.txt
+rclone hashsum MD5 "gcs:transit_bucket_ysds/itdel/BPM1" --output-file itdel_bawang_putih_gcs.txt
+rclone check --checksum "gcs:transit_bucket_ysds/itdel/BPM1" "gdrive:01_Data Mentah/itdel/bawang_putih/BPM1" --drive-shared-with-me --combined="itdel_bawang_putih_cheksum_bucket_drive.txt" --log-file="itdel_bawang_putih_count_checksum_bucket_drive.txt"
+# Run on Local Machine
+rclone hashsum MD5 "/data/storageserver/0.RAW/WGS/revio call/Raw/itdel/bawang_putih/BPM1" --output-file itdel_bawang_putih_local.txt
+rclone check --checksum "/data/storageserver/0.RAW/WGS/revio call/Raw/itdel/bawang_putih/BPM1" "gdrive:01_Data Mentah/itdel/bawang_putih/BPM1" --drive-shared-with-me --combined="itdel_bawang_putih_checksum_local_gdrive.txt" --log-file="itdel_bawang_putih_count_checksum_local_gdrive.txt"
+
+# Run on VM
+rclone hashsum MD5 "gdrive:01_Data Mentah/itdel/kemenyan" --drive-shared-with-me --output-file itdel_kemenyan_gdrive.txt
+rclone hashsum MD5 "gcs:transit_bucket_ysds/itdel/kemenyan" --output-file itdel_kemenyan_gcs.txt
+rclone check --checksum "gcs:transit_bucket_ysds/itdel/kemenyan" "gdrive:01_Data Mentah/itdel/kemenyan" --drive-shared-with-me --combined="itdel_kemenyan_checksum_bucket_drive.txt" --log-file="itdel_kemenyan_count_checksum_bucket_drive.txt"
+# Run on Local Machine
+rclone hashsum MD5 "/data/storageserver/0.RAW/WGS/revio call/Raw/itdel/kemenyan" --output-file itdel_kemenyan_local.txt
+rclone check --checksum "/data/storageserver/0.RAW/WGS/revio call/Raw/itdel/kemenyan" "gdrive:01_Data Mentah/itdel/kemenyan" --drive-shared-with-me --combined="itdel_kemenyan_checksum_local_gdrive.txt" --log-file="itdel_kemenyan_count_checksum_local_gdrive.txt"
+
+# Run on VM
+rclone hashsum MD5 "gdrive:01_Data Mentah/ugm/prima/bawang_putih" --drive-shared-with-me --output-file ugm_prima_bawang_putih_gdrive.txt
+rclone hashsum MD5 "gcs:transit_bucket_ysds/bawang_putih_prima_UGM" --output-file ugm_prima_bawang_putih_gcs.txt
+rclone check --checksum "gcs:transit_bucket_ysds/bawang_putih_prima_UGM" "gdrive:01_Data Mentah/ugm/prima/bawang_putih" --drive-shared-with-me --combined="ugm_prima_bawang_putih_checksum_bucket_drive.txt" --log-file="ugm_prima_bawang_putih_count_checksum_bucket_drive.txt"
+# Run on Local Machine
+rclone hashsum MD5 "/data/storageserver/0.RAW/WGS/revio call/Raw/bawang_putih" --output-file ugm_prima_bawang_putih_local.txt
+rclone check --checksum "/data/storageserver/0.RAW/WGS/revio call/Raw/bawang_putih" "gdrive:01_Data Mentah/ugm/prima/bawang_putih" --drive-shared-with-me --combined="ugm_prima_bawang_putih_checksum_local_gdrive.txt" --log-file="ugm_prima_bawang_putih_count_checksum_local_gdrive.txt"
+
+
+# Run on VM
+rclone hashsum MD5 "gdrive:01_Data Mentah/polije/netty/bawang_merah" --drive-shared-with-me --output-file polije_netty_bawang_merah_gdrive.txt
+rclone hashsum MD5 "gcs:transit_bucket_ysds/bawang_merah_netty_polije" --output-file polije_netty_bawang_merah_gcs.txt
+rclone check --checksum "gcs:transit_bucket_ysds/bawang_merah_netty_polije" "gdrive:01_Data Mentah/polije/netty/bawang_merah" --drive-shared-with-me --combined="polije_netty_bawang_merah_checksum_bucket_drive.txt" --log-file="polije_netty_bawang_merah_count_checksum_bucket_drive.txt"
+# Run on Local Machine
+rclone hashsum MD5 "/data/storageserver/0.RAW/WGS/revio call/Raw/bawang_merah" --output-file polije_netty_bawang_merah_local.txt
+rclone check --checksum "/data/storageserver/0.RAW/WGS/revio call/Raw/bawang_merah" "gdrive:01_Data Mentah/polije/netty/bawang_merah" --drive-shared-with-me --combined="polije_netty_bawang_merah_checksum_local_gdrive.txt" --log-file="polije_netty_bawang_merah_count_checksum_local_gdrive.txt"
diff --git a/app/Http/Controllers/BankDataController.php b/app/Http/Controllers/BankDataController.php
index d42ce24..3d74a27 100644
--- a/app/Http/Controllers/BankDataController.php
+++ b/app/Http/Controllers/BankDataController.php
@@ -3,8 +3,8 @@
namespace Modules\Lpj\Http\Controllers;
use Illuminate\Http\Request;
- use Illuminate\Routing\Controller;
- use Log;
+use Illuminate\Routing\Controller;
+use Illuminate\Support\Facades\Log;
use Modules\Location\Models\Province;
use Modules\Lpj\Http\Requests\BankDataRequest;
use Modules\Lpj\Models\BankData;
@@ -88,7 +88,8 @@
}
} else {
// Invalid coordinates
- Log::warning("Invalid coordinates: Lat: $_lat, Lng: $_lng");// Do something to handle this situation, such as logging an error or skipping the record
+ Log::warning("Invalid coordinates: Lat: $_lat, Lng: $_lng");
+ // Do something to handle this situation, such as logging an error or skipping the record
}
}
@@ -138,7 +139,8 @@
}
} else {
// Invalid coordinates
- Log::warning("Invalid coordinates: Lat: $lat, Lng: $lng");// Do something to handle this situation, such as logging an error or skipping the record
+ Log::warning("Invalid coordinates: Lat: $lat, Lng: $lng");
+ // Do something to handle this situation, such as logging an error or skipping the record
}
}
}
@@ -197,6 +199,24 @@
// Retrieve data from the database
$query = BankData::query();
+ // Check if show_all parameter is set
+ $showAll = $request->has('show_all') && $request->get('show_all') === 'true';
+
+ // If show_all is true, we'll get all data without pagination
+ if ($showAll) {
+ // Get all records without pagination
+ $data = $query->get();
+
+ return response()->json([
+ 'data' => $data,
+ 'recordsTotal' => $data->count(),
+ 'recordsFiltered' => $data->count(),
+ 'page' => 1,
+ 'pageSize' => $data->count(),
+ 'total' => 1
+ ]);
+ }
+
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
@@ -252,8 +272,10 @@
// Get the total count of records
$totalRecords = $query->count();
- // Apply pagination if provided
- if ($request->has('page') && $request->has('size')) {
+ // Apply pagination only if explicitly requested or not first load
+ $shouldPaginate = $request->has('page') && $request->has('size') && !$request->has('show_all');
+
+ if ($shouldPaginate) {
$page = $request->get('page');
$size = $request->get('size');
$offset = ($page - 1) * $size; // Calculate the offset
@@ -287,11 +309,11 @@
];
});
- // Calculate the page count
- $pageCount = ceil($totalRecords / $request->get('size'));
+ // Calculate the page count (1 if showing all data)
+ $pageCount = $shouldPaginate ? ceil($totalRecords / $request->get('size')) : 1;
// Calculate the current page number
- $currentPage = $request->get('page', 1);
+ $currentPage = $shouldPaginate ? $request->get('page', 1) : 1;
// Ensure current page doesn't exceed page count
$currentPage = min($currentPage, $pageCount);
diff --git a/app/Http/Controllers/ReferensiLinkController.php b/app/Http/Controllers/ReferensiLinkController.php
new file mode 100644
index 0000000..5a2e988
--- /dev/null
+++ b/app/Http/Controllers/ReferensiLinkController.php
@@ -0,0 +1,441 @@
+middleware('auth');
+ $this->middleware(function ($request, $next) {
+ $this->user = auth()->user();
+ return $next($request);
+ });
+ }
+
+ /**
+ * Display a listing of the resource.
+ */
+ public function index()
+ {
+ //$this->authorize('referensi-link.view', $this->user);
+
+ $data = [
+ 'title' => 'Referensi Link',
+ 'subtitle' => 'Daftar Referensi Link',
+ 'breadcrumb' => [
+ ['url' => route('dashboard'), 'text' => 'Dashboard'],
+ ['text' => 'Referensi Link']
+ ],
+ 'kategoriOptions' => $this->getKategoriOptions(),
+ ];
+
+ return view('lpj::referensi_link.index', $data);
+ }
+
+ /**
+ * Show the form for creating a new resource.
+ */
+ public function create()
+ {
+ //$this->authorize('referensi-link.create', $this->user);
+
+ $data = [
+ 'title' => 'Tambah Referensi Link',
+ 'subtitle' => 'Form Tambah Referensi Link Baru',
+ 'breadcrumb' => [
+ ['url' => route('dashboard'), 'text' => 'Dashboard'],
+ ['url' => route('basicdata.referensi-link.index'), 'text' => 'Referensi Link'],
+ ['text' => 'Tambah']
+ ],
+ 'kategoriOptions' => $this->getKategoriOptions(),
+ ];
+
+ return view('lpj::referensi_link.create', $data);
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ */
+ public function store(ReferensiLinkRequest $request)
+ {
+ //$this->authorize('referensi-link.create', $this->user);
+
+ try {
+ $validated = $request->validated();
+
+ // Set urutan otomatis jika belum diisi
+ if (empty($validated['urutan'])) {
+ $validated['urutan'] = ReferensiLink::max('urutan') + 1;
+ }
+
+ $referensiLink = ReferensiLink::create($validated);
+
+ return redirect()
+ ->route('basicdata.referensi-link.index')
+ ->with('success', 'Referensi Link berhasil ditambahkan');
+
+ } catch (Exception $e) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('error', 'Gagal menambahkan Referensi Link: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ */
+ public function edit($id)
+ {
+ //$this->authorize('referensi-link.update', $this->user);
+
+ $referensiLink = ReferensiLink::findOrFail($id);
+
+ $data = [
+ 'title' => 'Edit Referensi Link',
+ 'subtitle' => 'Form Edit Referensi Link',
+ 'breadcrumb' => [
+ ['url' => route('dashboard'), 'text' => 'Dashboard'],
+ ['url' => route('basicdata.referensi-link.index'), 'text' => 'Referensi Link'],
+ ['text' => 'Edit']
+ ],
+ 'referensiLink' => $referensiLink,
+ 'kategoriOptions' => $this->getKategoriOptions(),
+ ];
+
+ return view('lpj::referensi_link.create', $data);
+ }
+
+ /**
+ * Update the specified resource in storage.
+ */
+ public function update(ReferensiLinkRequest $request, $id)
+ {
+ //$this->authorize('referensi-link.update', $this->user);
+
+ try {
+ $referensiLink = ReferensiLink::findOrFail($id);
+ $validated = $request->validated();
+
+ $referensiLink->update($validated);
+
+ return redirect()
+ ->route('basicdata.referensi-link.index')
+ ->with('success', 'Referensi Link berhasil diperbarui');
+
+ } catch (Exception $e) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('error', 'Gagal memperbarui Referensi Link: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ */
+ public function destroy($id)
+ {
+ //$this->authorize('referensi-link.delete', $this->user);
+
+ try {
+ $referensiLink = ReferensiLink::findOrFail($id);
+ $referensiLink->delete();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Referensi Link berhasil dihapus'
+ ]);
+
+ } catch (Exception $e) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Gagal menghapus Referensi Link: ' . $e->getMessage()
+ ], 500);
+ }
+ }
+
+ /**
+ * Datatable API for KTDataTable
+ */
+ public function dataTable(Request $request)
+ {
+ //$this->authorize('referensi-link.view', $this->user);
+
+ $query = ReferensiLink::with(['createdBy', 'updatedBy'])
+ ->select('referensi_link.*');
+
+ // Search
+ $search = $request->input('search');
+ if (!empty($search)) {
+ $query->where(function ($q) use ($search) {
+ $q->where('name', 'LIKE', "%{$search}%")
+ ->orWhere('link', 'LIKE', "%{$search}%")
+ ->orWhere('kategori', 'LIKE', "%{$search}%");
+ });
+ }
+
+ // Optional filters (support multiple request shapes)
+ $filters = $request->input('filters', []);
+ $kategori = $request->input('kategori', $filters['kategori'] ?? null);
+ if (!empty($kategori)) {
+ if (is_array($kategori)) {
+ $query->whereIn('kategori', $kategori);
+ } else {
+ $values = preg_split('/[,|]/', (string) $kategori, -1, PREG_SPLIT_NO_EMPTY);
+ if (count($values) > 1) {
+ $query->whereIn('kategori', $values);
+ } else {
+ $query->where('kategori', $kategori);
+ }
+ }
+ }
+
+ $statusRaw = $request->input('status', $filters['status'] ?? $request->input('is_active'));
+ $statusParsed = $this->parseActiveFilter($statusRaw);
+ if ($statusParsed !== null) {
+ $query->where('is_active', $statusParsed);
+ }
+
+ // Sorting
+ $allowedSortFields = ['id', 'name', 'link', 'kategori', 'urutan', 'is_active', 'created_at', 'updated_at'];
+ $sortField = in_array($request->input('sortField', 'urutan'), $allowedSortFields, true)
+ ? $request->input('sortField', 'urutan')
+ : 'urutan';
+ $sortOrder = strtolower($request->input('sortOrder', 'asc'));
+ if (in_array($sortOrder, ['asc', 'desc'], true)) {
+ $query->orderBy($sortField, $sortOrder);
+ }
+
+ // Pagination
+ $page = max((int) $request->input('page', 1), 1);
+ $size = max((int) $request->input('size', 10), 1);
+ $totalRecords = (clone $query)->count();
+ $offset = ($page - 1) * $size;
+ $items = $query->skip($offset)->take($size)->get();
+
+ // Map data rows
+ $data = $items->map(function ($row) {
+ return [
+ 'id' => $row->id,
+ 'name' => $row->name,
+ 'link' => '' . Str::limit($row->link, 50) . ' ',
+ 'kategori' => $row->kategori,
+ 'status_badge' => $row->status_badge,
+ 'urutan' => $row->urutan,
+ 'actions' => (
+ (auth()->user()->can('referensi-link.update') ? ' Edit' : '') .
+ (auth()->user()->can('referensi-link.delete') ? ' Hapus' : '')
+ ),
+ ];
+ });
+
+ return response()->json([
+ 'draw' => (int) $request->input('draw'),
+ 'recordsTotal' => $totalRecords,
+ 'recordsFiltered' => $totalRecords,
+ 'pageCount' => (int) ceil($totalRecords / $size),
+ 'page' => $page,
+ 'totalCount' => $totalRecords,
+ 'data' => $data,
+ ]);
+ }
+
+ /**
+ * Export data to Excel
+ */
+ public function export(Request $request)
+ {
+ //$this->authorize('referensi-link.export', $this->user);
+
+ try {
+ $filename = 'referensi_link_' . date('YmdHis') . '.xlsx';
+
+ return Excel::download(new ReferensiLinkExport($request->all()), $filename);
+
+ } catch (Exception $e) {
+ return redirect()
+ ->back()
+ ->with('error', 'Gagal export data: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * Show import form
+ */
+ public function import()
+ {
+ //$this->authorize('referensi-link.import', $this->user);
+
+ $data = [
+ 'title' => 'Import Referensi Link',
+ 'subtitle' => 'Import data Referensi Link dari Excel',
+ 'breadcrumb' => [
+ ['url' => route('dashboard'), 'text' => 'Dashboard'],
+ ['url' => route('basicdata.referensi-link.index'), 'text' => 'Referensi Link'],
+ ['text' => 'Import']
+ ],
+ ];
+
+ return view('lpj::referensi_link.import', $data);
+ }
+
+ /**
+ * Process import
+ */
+ public function importProcess(Request $request)
+ {
+ //$this->authorize('referensi-link.import', $this->user);
+
+ $request->validate([
+ 'file' => 'required|mimes:xlsx,xls|max:10240', // max 10MB
+ ]);
+
+ try {
+ $import = new ReferensiLinkImport();
+ Excel::import($import, $request->file('file'));
+
+ $stats = $import->getImportStats();
+
+ $message = "Import berhasil! {$stats['success']} data berhasil diimport";
+ if ($stats['failed'] > 0) {
+ $message .= ", {$stats['failed']} data gagal diimport";
+ }
+
+ return redirect()
+ ->route('basicdata.referensi-link.index')
+ ->with('success', $message);
+
+ } catch (Exception $e) {
+ Log::error('ReferensiLink import error: ' . $e->getMessage());
+ return redirect()
+ ->back()
+ ->with('error', 'Gagal import data: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * Toggle status (active/inactive)
+ */
+ public function toggleStatus($id)
+ {
+ //$this->authorize('referensi-link.update', $this->user);
+
+ try {
+ $referensiLink = ReferensiLink::findOrFail($id);
+ $referensiLink->is_active = !$referensiLink->is_active;
+ $referensiLink->save();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Status berhasil diubah',
+ 'status' => $referensiLink->is_active
+ ]);
+
+ } catch (Exception $e) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Gagal mengubah status: ' . $e->getMessage()
+ ], 500);
+ }
+ }
+
+ /**
+ * Get kategori options for dropdown
+ */
+ private function getKategoriOptions()
+ {
+ return [
+ 'regulasi' => 'Regulasi',
+ 'panduan' => 'Panduan',
+ 'prosedur' => 'Prosedur',
+ 'formulir' => 'Formulir',
+ 'laporan' => 'Laporan',
+ 'lainnya' => 'Lainnya'
+ ];
+ }
+
+ private function parseActiveFilter($value): ?bool
+ {
+ if ($value === null || $value === '') {
+ return null;
+ }
+
+ if (is_bool($value)) {
+ return $value;
+ }
+
+ $val = strtolower(trim((string) $value));
+
+ if (in_array($val, ['1', 'true', 'aktif', 'active', 'yes', 'y'], true)) {
+ return true;
+ }
+ if (in_array($val, ['0', 'false', 'tidak', 'inactive', 'nonaktif', 'no', 'n'], true)) {
+ return false;
+ }
+
+ return null;
+ }
+
+ /**
+ * Download import template
+ */
+ public function downloadTemplate()
+ {
+ //$this->authorize('referensi-link.import', $this->user);
+
+ try {
+ $headers = [
+ 'Nama',
+ 'Link',
+ 'Kategori',
+ 'Deskripsi',
+ 'Status Aktif',
+ 'Urutan'
+ ];
+
+ $filename = 'template_referensi_link_' . date('YmdHis') . '.xlsx';
+
+ return Excel::download(new class($headers) implements \Maatwebsite\Excel\Concerns\FromArray, \Maatwebsite\Excel\Concerns\WithHeadings {
+ private $headers;
+
+ public function __construct($headers)
+ {
+ $this->headers = $headers;
+ }
+
+ public function array(): array
+ {
+ return [
+ ['Contoh Referensi', 'https://example.com', 'panduan', 'Deskripsi contoh referensi link', 'aktif', 1],
+ ['Contoh Regulasi', 'https://regulasi.example.com', 'regulasi', 'Deskripsi regulasi', 'aktif', 2],
+ ];
+ }
+
+ public function headings(): array
+ {
+ return $this->headers;
+ }
+ }, $filename);
+
+ } catch (Exception $e) {
+ return redirect()
+ ->back()
+ ->with('error', 'Gagal download template: ' . $e->getMessage());
+ }
+ }
+}
diff --git a/app/Http/Controllers/SurveyorController.php b/app/Http/Controllers/SurveyorController.php
index 5d17de3..4976b8a 100644
--- a/app/Http/Controllers/SurveyorController.php
+++ b/app/Http/Controllers/SurveyorController.php
@@ -883,11 +883,10 @@ class SurveyorController extends Controller
$penilaian = Penilaian::findOrFail($id);
$permohonan = Permohonan::where('nomor_registrasi', $penilaian->nomor_registrasi)->first();
- ;
if (Carbon::parse($validate['waktu_penilaian']) <= Carbon::parse($penilaian->tanggal_kunjungan)) {
return response()->json([
'success' => false,
- 'message' => 'Waktu penilaian harus lebih besar dari tanggal assign.'
+ 'message' => 'Waktu penilaian harus lebih besar dari tanggal assign.'.$penilaian->tanggal_kunjungan.' '.$validate['waktu_penilaian']
], 422);
}
@@ -2924,29 +2923,29 @@ class SurveyorController extends Controller
// Jika ada data tanpa dokument_id, cek apakah ada data dengan dokument_id yang sama
if ($dataWithoutDokument->isNotEmpty() && $dataWithDokument->isNotEmpty()) {
-
+
// Group data dengan dokument_id by created_by
$groupedDataWithDokument = $dataWithDokument->groupBy('created_by');
-
+
// Group data tanpa dokument_id by created_by
$groupedDataWithoutDokument = $dataWithoutDokument->groupBy('created_by');
// Proses cleanup untuk setiap user
foreach ($groupedDataWithDokument as $userId => $userDataWithDokument) {
-
+
// Cek apakah user ini juga memiliki data tanpa dokument_id
if (isset($groupedDataWithoutDokument[$userId])) {
-
+
// Ambil salah satu data dengan dokument_id sebagai referensi untuk logging
$referenceData = $userDataWithDokument->first();
-
+
Log::info('SurveyorController: Menemukan data lengkap untuk user, akan menghapus data tidak lengkap', [
'user_id' => $userId,
'permohonan_id' => $permohonanId,
'reference_dokument_id' => $referenceData->dokument_id,
'data_count_to_delete' => $groupedDataWithoutDokument[$userId]->count()
]);
-
+
// Ambil semua data tanpa dokument_id untuk user ini
$userDataWithoutDokument = $groupedDataWithoutDokument[$userId];
@@ -2956,7 +2955,7 @@ class SurveyorController extends Controller
try {
// Soft delete data
$dataToDelete->delete();
-
+
Log::info('SurveyorController: Data inspeksi berhasil di-soft delete', [
'id' => $dataToDelete->id,
'permohonan_id' => $dataToDelete->permohonan_id,
diff --git a/app/Http/Requests/ReferensiLinkRequest.php b/app/Http/Requests/ReferensiLinkRequest.php
new file mode 100644
index 0000000..5d7ecce
--- /dev/null
+++ b/app/Http/Requests/ReferensiLinkRequest.php
@@ -0,0 +1,114 @@
+
+ */
+ public function rules(): array
+ {
+ $rules = [
+ 'name' => 'required|string|max:255',
+ 'link' => 'required|string|max:500',
+ 'kategori' => 'nullable|string|max:100',
+ 'deskripsi' => 'nullable|string|max:2000',
+ 'is_active' => 'boolean',
+ 'urutan' => 'nullable|integer|min:0',
+ ];
+
+ // Validasi tambahan untuk link
+ $rules['link'] .= '|url';
+
+ return $rules;
+ }
+
+ /**
+ * Determine if the user is authorized to make this request.
+ *
+ * @return bool
+ */
+ public function authorize(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Get custom messages for validator errors.
+ *
+ * @return array
+ */
+ public function messages(): array
+ {
+ return [
+ 'name.required' => 'Nama referensi link wajib diisi',
+ 'name.string' => 'Nama referensi link harus berupa teks',
+ 'name.max' => 'Nama referensi link maksimal 255 karakter',
+ 'link.required' => 'Link wajib diisi',
+ 'link.string' => 'Link harus berupa teks',
+ 'link.max' => 'Link maksimal 500 karakter',
+ 'link.url' => 'Link harus berupa URL yang valid',
+ 'kategori.string' => 'Kategori harus berupa teks',
+ 'kategori.max' => 'Kategori maksimal 100 karakter',
+ 'deskripsi.string' => 'Deskripsi harus berupa teks',
+ 'deskripsi.max' => 'Deskripsi maksimal 2000 karakter',
+ 'is_active.boolean' => 'Status aktif harus berupa ya/tidak',
+ 'urutan.integer' => 'Urutan harus berupa angka',
+ 'urutan.min' => 'Urutan minimal 0',
+ ];
+ }
+
+ /**
+ * Get custom attributes for validator errors.
+ *
+ * @return array
+ */
+ public function attributes(): array
+ {
+ return [
+ 'name' => 'Nama Referensi Link',
+ 'link' => 'Link',
+ 'kategori' => 'Kategori',
+ 'deskripsi' => 'Deskripsi',
+ 'is_active' => 'Status Aktif',
+ 'urutan' => 'Urutan',
+ ];
+ }
+
+ /**
+ * Prepare the data for validation.
+ *
+ * @return void
+ */
+ protected function prepareForValidation(): void
+ {
+ // Format link jika belum memiliki protocol
+ if ($this->has('link')) {
+ $link = $this->input('link');
+ if ($link && !preg_match('/^(https?:\/\/)/i', $link)) {
+ $this->merge([
+ 'link' => 'https://' . $link
+ ]);
+ }
+ }
+
+ // Set default is_active jika tidak diset
+ if (!$this->has('is_active')) {
+ $this->merge([
+ 'is_active' => true
+ ]);
+ }
+
+ // Set default urutan jika tidak diset atau 0
+ if (!$this->has('urutan') || empty($this->input('urutan'))) {
+ $this->merge([
+ 'urutan' => 0
+ ]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Imports/ReferensiLinkImport.php b/app/Imports/ReferensiLinkImport.php
new file mode 100644
index 0000000..e82a94a
--- /dev/null
+++ b/app/Imports/ReferensiLinkImport.php
@@ -0,0 +1,166 @@
+importedCount++;
+
+ return new ReferensiLink([
+ 'name' => $row['nama'] ?? $row['name'],
+ 'link' => $this->formatLink($row['link'] ?? $row['url'] ?? ''),
+ 'kategori' => $row['kategori'] ?? $row['category'] ?? 'lainnya',
+ 'deskripsi' => $row['deskripsi'] ?? $row['description'] ?? null,
+ 'is_active' => $this->parseStatus($row['status_aktif'] ?? $row['is_active'] ?? 'aktif'),
+ 'urutan' => $this->parseUrutan($row['urutan'] ?? $row['order'] ?? 0),
+ 'created_by' => Auth::id(),
+ 'updated_by' => Auth::id(),
+ ]);
+
+ } catch (\Exception $e) {
+ $this->failedCount++;
+ $this->errors[] = "Baris {$row['row_number']}: " . $e->getMessage();
+ Log::error('Import ReferensiLink Error: ' . $e->getMessage(), ['row' => $row]);
+ return null;
+ }
+ }
+
+ /**
+ * Validation rules
+ */
+ public function rules(): array
+ {
+ return [
+ 'nama' => 'required|string|max:255',
+ 'name' => 'required|string|max:255',
+ 'link' => 'required|string|max:500',
+ 'url' => 'required|string|max:500',
+ 'kategori' => 'nullable|string|max:100',
+ 'category' => 'nullable|string|max:100',
+ 'deskripsi' => 'nullable|string|max:2000',
+ 'description' => 'nullable|string|max:2000',
+ 'status_aktif' => 'nullable|string|in:aktif,tidak aktif,1,0,true,false',
+ 'is_active' => 'nullable|string|in:aktif,tidak aktif,1,0,true,false',
+ 'urutan' => 'nullable|integer|min:0',
+ 'order' => 'nullable|integer|min:0',
+ ];
+ }
+
+ /**
+ * Custom validation messages
+ */
+ public function customValidationMessages()
+ {
+ return [
+ 'nama.required' => 'Nama wajib diisi',
+ 'name.required' => 'Nama wajib diisi',
+ 'link.required' => 'Link wajib diisi',
+ 'url.required' => 'Link wajib diisi',
+ 'link.url' => 'Link harus berupa URL yang valid',
+ 'url.url' => 'Link harus berupa URL yang valid',
+ 'kategori.max' => 'Kategori maksimal 100 karakter',
+ 'category.max' => 'Kategori maksimal 100 karakter',
+ 'deskripsi.max' => 'Deskripsi maksimal 2000 karakter',
+ 'description.max' => 'Deskripsi maksimal 2000 karakter',
+ ];
+ }
+
+ /**
+ * Batch size for inserts
+ */
+ public function batchSize(): int
+ {
+ return 100;
+ }
+
+ /**
+ * Chunk size for reading
+ */
+ public function chunkSize(): int
+ {
+ return 100;
+ }
+
+ /**
+ * Format link to ensure it has protocol
+ */
+ private function formatLink($link)
+ {
+ if (empty($link)) {
+ return null;
+ }
+
+ // Remove any whitespace
+ $link = trim($link);
+
+ // Add protocol if not present
+ if (!preg_match('/^(https?:\/\/)/i', $link)) {
+ $link = 'https://' . $link;
+ }
+
+ return $link;
+ }
+
+ /**
+ * Parse status value
+ */
+ private function parseStatus($status)
+ {
+ if (empty($status)) {
+ return true;
+ }
+
+ $status = strtolower(trim($status));
+
+ return in_array($status, ['aktif', '1', 'true', 'yes', 'ya']);
+ }
+
+ /**
+ * Parse order value
+ */
+ private function parseUrutan($urutan)
+ {
+ if (empty($urutan)) {
+ return 0;
+ }
+
+ return (int) $urutan;
+ }
+
+ /**
+ * Get import statistics
+ */
+ public function getImportStats(): array
+ {
+ return [
+ 'imported' => $this->importedCount,
+ 'failed' => $this->failedCount,
+ 'errors' => $this->errors,
+ 'success' => $this->importedCount - $this->failedCount,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Models/ReferensiLink.php b/app/Models/ReferensiLink.php
new file mode 100644
index 0000000..374fc85
--- /dev/null
+++ b/app/Models/ReferensiLink.php
@@ -0,0 +1,152 @@
+ 'boolean',
+ 'urutan' => 'integer',
+ 'created_at' => 'datetime',
+ 'updated_at' => 'datetime',
+ ];
+
+ /**
+ * Boot the model.
+ */
+ protected static function boot()
+ {
+ parent::boot();
+
+ static::creating(function ($model) {
+ if (Auth::check()) {
+ $model->created_by = Auth::id();
+ $model->updated_by = Auth::id();
+ }
+ });
+
+ static::updating(function ($model) {
+ if (Auth::check()) {
+ $model->updated_by = Auth::id();
+ }
+ });
+ }
+
+ /**
+ * Scope untuk filter data aktif
+ */
+ public function scopeActive($query)
+ {
+ return $query->where('is_active', true);
+ }
+
+ /**
+ * Scope untuk filter berdasarkan kategori
+ */
+ public function scopeByKategori($query, $kategori)
+ {
+ return $query->where('kategori', $kategori);
+ }
+
+ /**
+ * Scope untuk urutkan berdasarkan urutan
+ */
+ public function scopeOrdered($query)
+ {
+ return $query->orderBy('urutan', 'asc')->orderBy('name', 'asc');
+ }
+
+ /**
+ * Scope untuk pencarian
+ */
+ public function scopeSearch($query, $search)
+ {
+ return $query->where(function ($q) use ($search) {
+ $q->where('name', 'like', "%{$search}%")
+ ->orWhere('link', 'like', "%{$search}%")
+ ->orWhere('kategori', 'like', "%{$search}%")
+ ->orWhere('deskripsi', 'like', "%{$search}%");
+ });
+ }
+
+ /**
+ * Relasi ke user yang membuat
+ */
+ public function createdBy()
+ {
+ return $this->belongsTo(User::class, 'created_by');
+ }
+
+ /**
+ * Relasi ke user yang update
+ */
+ public function updatedBy()
+ {
+ return $this->belongsTo(User::class, 'updated_by');
+ }
+
+ /**
+ * Accessor untuk status badge
+ */
+ public function getStatusBadgeAttribute()
+ {
+ return $this->is_active
+ ? 'Aktif'
+ : 'Tidak Aktif';
+ }
+
+ /**
+ * Accessor untuk link yang diformat
+ */
+ public function getFormattedLinkAttribute()
+ {
+ return $this->link ? url($this->link) : null;
+ }
+
+ /**
+ * Mutator untuk memastikan link valid
+ */
+ public function setLinkAttribute($value)
+ {
+ // Validasi dan format link
+ if ($value && !preg_match('/^(https?:\/\/)/i', $value)) {
+ $value = 'https://' . $value;
+ }
+ $this->attributes['link'] = $value;
+ }
+}
diff --git a/commit-message-staged.txt b/commit-message-staged.txt
new file mode 100644
index 0000000..30c9b5a
--- /dev/null
+++ b/commit-message-staged.txt
@@ -0,0 +1,75 @@
+🐛 fix(lpj): Perbaikan format Rupiah, role access, dan validasi data
+
+## Ringkasan
+Melakukan perbaikan pada helper format Rupiah, akses role user, validasi data MIG, serta penyesuaian tampilan laporan dan dokumentasi.
+
+## Perubahan Detail
+
+### 🔧 Helper Function
+**app/Helpers/Lpj.php**:
+- Menambahkan parameter opsional `withSymbol` pada fungsi `formatRupiah()` untuk kontrol simbol Rp
+- Menambahkan handling untuk menghapus titik (.) dari input number sebelum proses
+- Memperbaiki return value untuk null/empty string sesuai parameter `withSymbol`
+- Mengganti `str_pad()` dengan `sprintf()` untuk generate random number (lebih efisien)
+
+### 🛠️ Service Layer
+**app/Services/PreviewLaporanService.php**:
+- Memperbaiki validasi data MIG dengan menambahkan pengecekan `is_mig` flag
+- Menambahkan null safety pada property `mig_mst_lpj_tot_nilai_pasar`
+- Memperbaiki kondisi logic untuk memo dan validasi nilai pasar
+
+### 🎨 View Components
+**resources/views/component/print-out-dokument.blade.php**:
+- Memperbaiki syntax Blade dari `@isset` menjadi `isset()` yang lebih proper
+
+**resources/views/debitur/components/debitur.blade.php**:
+- Memperbaiki role checking dari `hasRole()` menjadi `hasAnyRole()` untuk multiple roles
+
+**resources/views/debitur/index.blade.php**:
+- Menambahkan role 'admin' pada kondisi edit dan delete actions
+- Memperbaiki permission checking untuk administrator dan admin
+
+**resources/views/laporan/index.blade.php**:
+- Menyederhanakan logic tombol laporan dan resume
+- Menghapus logic role-based yang kompleks untuk tombol laporan
+- Memperbaiki route URL untuk print-out laporan
+- Menghapus function `generateLaporanButton()` yang tidak digunakan
+
+**resources/views/penilai/components/lpj-sederhana-standar.blade.php**:
+- Menambahkan role 'penilai' pada permission tombol simpan
+
+**resources/views/penilai/components/print-out-sederhana.blade.php**:
+- Memperbaiki tampilan data dokumen dengan menambahkan kolom nomor dokumen
+- Mengganti `number_format()` dengan `formatRupiah()` untuk konsistensi format
+- Menambahkan fallback untuk data tanah dan bangunan ketika `npw_tambahan` tidak tersedia
+- Memperbaiki perhitungan total nilai pasar wajar dengan proper parsing
+- Memperbaiki format tampilan nilai likuidasi
+- Memperbaiki struktur HTML tabel untuk dokumentasi
+
+**resources/views/penilai/components/signature-approval.blade.php**:
+- Memperbaiki route dan parameter untuk approval signature
+
+**resources/views/permohonan/index.blade.php**:
+- Menambahkan role 'admin' pada permission actions
+
+## Alasan Perubahan
+1. **Format Rupiah**: Menambahkan fleksibilitas untuk menampilkan nominal dengan atau tanpa simbol Rp sesuai kebutuhan tampilan
+2. **Validasi Data**: Memperkuat validasi data MIG untuk mencegah error pada data yang tidak lengkap
+3. **Role Access**: Memperbaiki permission checking untuk mencakup role admin yang sebelumnya terlewat
+4. **Tampilan Laporan**: Menyederhanakan UI dan memperbaiki format tampilan nilai untuk konsistensi
+5. **Fallback Data**: Menambahkan handling untuk kasus data tidak lengkap pada laporan penilaian
+
+## Dampak
+- ✅ Format Rupiah lebih fleksibel dengan opsi simbol
+- ✅ Validasi data MIG lebih kuat dan aman
+- ✅ Role admin sekarang memiliki akses yang sesuai
+- ✅ Tampilan laporan lebih konsisten dan rapi
+- ✅ Penanganan error untuk data tidak lengkap lebih baik
+
+## Testing
+Pastikan untuk:
+1. Test format Rupiah dengan berbagai skenario (dengan/ tanpa simbol)
+2. Test akses role admin pada semua fitur yang diperbarui
+3. Test validasi data MIG dengan data lengkap dan tidak lengkap
+4. Test tampilan laporan dengan data npw_tambahan kosong
+5. Verifikasi perhitungan total nilai pasar wajar tetap akurat
\ No newline at end of file
diff --git a/database/.DS_Store b/database/.DS_Store
new file mode 100644
index 0000000..cdbc7bb
Binary files /dev/null and b/database/.DS_Store differ
diff --git a/database/migrations/2025_11_26_153010_create_referensi_link_table.php b/database/migrations/2025_11_26_153010_create_referensi_link_table.php
new file mode 100644
index 0000000..599034c
--- /dev/null
+++ b/database/migrations/2025_11_26_153010_create_referensi_link_table.php
@@ -0,0 +1,45 @@
+id();
+ $table->string('name', 255)->nullable(false)->comment('Nama referensi link');
+ $table->text('link')->nullable(false)->comment('URL link referensi');
+ $table->string('kategori', 100)->nullable()->comment('Kategori referensi (misal: regulasi, panduan, dll)');
+ $table->text('deskripsi')->nullable()->comment('Deskripsi lengkap referensi');
+ $table->boolean('is_active')->default(true)->comment('Status aktif referensi');
+ $table->integer('urutan')->default(0)->comment('Urutan tampil referensi');
+ $table->unsignedBigInteger('created_by')->nullable()->comment('ID user yang membuat');
+ $table->unsignedBigInteger('updated_by')->nullable()->comment('ID user yang update terakhir');
+ $table->timestamps();
+
+ // Indexes
+ $table->index('kategori');
+ $table->index('is_active');
+ $table->index('urutan');
+ $table->index('created_by');
+
+ // Foreign keys
+ $table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
+ $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('referensi_link');
+ }
+};
diff --git a/module.json b/module.json
index b437699..2e0b569 100644
--- a/module.json
+++ b/module.json
@@ -290,15 +290,7 @@
"classes": "",
"attributes": [],
"permission": "",
- "roles": [
- "administrator",
- "admin",
- "DD Appraisal",
- "EO Appraisal",
- "senior-officer",
- "penilai",
- "surveyor"
- ]
+ "roles": []
},
{
"title": "Data Permohonan",
@@ -1231,21 +1223,43 @@
"system": [
{
"title": "Daftar Pustaka",
- "path": "daftar-pustaka",
+ "path": "",
"icon": "ki-filled ki-filter-tablet text-lg text-primary",
"classes": "",
"attributes": [],
"permission": "",
"roles": [
"administrator",
- "pemohon-ao",
- "pemohon-eo",
- "admin",
- "DD Appraisal",
- "EO Appraisal",
- "senior-officer"
+ "admin"
+ ],
+ "sub": [
+ {
+ "title": "Daftar Pustaka",
+ "path": "daftar-pustaka",
+ "icon": "ki-filled ki-external-link text-lg text-primary",
+ "classes": "",
+ "attributes": [],
+ "permission": "",
+ "roles": [
+ "administrator",
+ "admin"
+ ]
+ },
+ {
+ "title": "Referensi Link",
+ "path": "basicdata.referensi-link",
+ "icon": "ki-filled ki-external-link text-lg text-primary",
+ "classes": "",
+ "attributes": [],
+ "permission": "",
+ "roles": [
+ "administrator",
+ "admin"
+ ]
+ }
]
}
]
}
}
+
diff --git a/resources/.DS_Store b/resources/.DS_Store
new file mode 100644
index 0000000..9b32c64
Binary files /dev/null and b/resources/.DS_Store differ
diff --git a/resources/views/bank-data/index.blade.php b/resources/views/bank-data/index.blade.php
index 8ad4b42..e6527eb 100644
--- a/resources/views/bank-data/index.blade.php
+++ b/resources/views/bank-data/index.blade.php
@@ -5,17 +5,47 @@
@endsection
@section('content')
-