sementara

This commit is contained in:
Daeng Deni Mardaeni
2026-01-30 14:44:14 +07:00
parent f402c0831a
commit aceff4f006
29 changed files with 2144 additions and 93 deletions

BIN
app/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,128 @@
<?php
namespace Modules\Lpj\Exports;
use Modules\Lpj\app\Models\ReferensiLink;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class ReferensiLinkExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize, WithColumnFormatting
{
protected $filters;
public function __construct($filters = [])
{
$this->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';
}
}

View File

@@ -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"

View File

@@ -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);

View File

@@ -0,0 +1,441 @@
<?php
namespace Modules\Lpj\Http\Controllers;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Lpj\Models\ReferensiLink;
use Modules\Lpj\Http\Requests\ReferensiLinkRequest;
use Modules\Lpj\Exports\ReferensiLinkExport;
use Modules\Lpj\Imports\ReferensiLinkImport;
class ReferensiLinkController extends Controller
{
public $user;
public function __construct()
{
$this->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' => '<a href="' . $row->link . '" target="_blank" class="text-primary">' . Str::limit($row->link, 50) . ' <i class="fas fa-external-link-alt"></i></a>',
'kategori' => $row->kategori,
'status_badge' => $row->status_badge,
'urutan' => $row->urutan,
'actions' => (
(auth()->user()->can('referensi-link.update') ? '<a class="dropdown-item" href="' . route('basicdata.referensi-link.edit', $row->id) . '"><i class="fas fa-edit"></i> Edit</a>' : '') .
(auth()->user()->can('referensi-link.delete') ? '<a class="dropdown-item text-danger" href="javascript:void(0)" onclick="deleteData(' . $row->id . ')"><i class="fas fa-trash"></i> Hapus</a>' : '')
),
];
});
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());
}
}
}

View File

@@ -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,

View File

@@ -0,0 +1,114 @@
<?php
namespace Modules\Lpj\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ReferensiLinkRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
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<string, string>
*/
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<string, string>
*/
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
]);
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace Modules\Lpj\Imports;
use Modules\Lpj\app\Models\ReferensiLink;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\SkipsFailures;
use Maatwebsite\Excel\Concerns\SkipsOnFailure;
use Maatwebsite\Excel\Concerns\WithBatchInserts;
use Maatwebsite\Excel\Concerns\WithChunkReading;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class ReferensiLinkImport implements ToModel, WithHeadingRow, WithValidation, SkipsOnFailure, WithBatchInserts, WithChunkReading
{
use Importable, SkipsFailures;
protected $importedCount = 0;
protected $failedCount = 0;
protected $errors = [];
/**
* Convert row to model
*/
public function model(array $row)
{
try {
$this->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,
];
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace Modules\Lpj\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Auth;
use Modules\Usermanagement\Models\User;
class ReferensiLink extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'referensi_link';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'link',
'kategori',
'deskripsi',
'is_active',
'urutan',
'created_by',
'updated_by',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'is_active' => '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
? '<span class="badge bg-success">Aktif</span>'
: '<span class="badge bg-danger">Tidak Aktif</span>';
}
/**
* 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;
}
}