Merge branch 'staging' into feature/senior-officer

This commit is contained in:
majid
2024-12-24 13:48:58 +07:00
10 changed files with 872 additions and 726 deletions

View File

@@ -13,6 +13,7 @@ use Maatwebsite\Excel\Facades\Excel;
use Modules\Lpj\Exports\KertasKerjaExport; use Modules\Lpj\Exports\KertasKerjaExport;
use Modules\Lpj\Http\Controllers\SurveyorController; use Modules\Lpj\Http\Controllers\SurveyorController;
use Modules\Location\Models\Province; use Modules\Location\Models\Province;
use Illuminate\Support\Facades\Log;
class PenilaiController extends Controller class PenilaiController extends Controller
{ {
@@ -243,10 +244,13 @@ class PenilaiController extends Controller
} }
$query->whereHas('penilaian.userPenilai', function ($q) { if(!Auth::user()->hasRole('administrator')) {
$q->where('role', 'penilai') $query->whereHas('penilaian.userPenilai', function ($q) {
->where('user_id', Auth::user()->id); $q
}); ->where('role', 'penilai')
->where('user_id', Auth::user()->id);
});
}
// Apply sorting if provided // Apply sorting if provided
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) { if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
@@ -604,20 +608,24 @@ class PenilaiController extends Controller
$lpjData = json_decode($lpj->lpj, true); $lpjData = json_decode($lpj->lpj, true);
} }
try {
$pdf = PDF::loadView('lpj::penilai.components.print-out', compact(
'permohonan',
'forminspeksi',
'lpjData',
'formFoto',
'basicData',
'inspeksi',
'lpj'
));
$pdf = PDF::loadView('lpj::penilai.components.print-out', compact( $pdf->setPaper('A4', 'portrait');
'permohonan',
'forminspeksi',
'lpjData',
'formFoto',
'basicData',
'inspeksi',
'lpj'
));
$pdf->setPaper('A4', 'portrait'); return $pdf->download('laporan.pdf');
} catch (\Exception $e) {
return $pdf->download('laporan.pdf'); Log::error('PDF generation failed: ' . $e->getMessage());
return response()->json(['error' => 'Failed to generate PDF. Please check the log for details.'], 500);
}
// return view('lpj::penilai.components.print-out', compact( // return view('lpj::penilai.components.print-out', compact(
// 'permohonan', // 'permohonan',

View File

@@ -536,7 +536,7 @@
$filteredRecords = $query->count(); $filteredRecords = $query->count();
// Ambil data dengan relasi // Ambil data dengan relasi
$data = $query->with(['user', 'debiture', 'branch', 'tujuanPenilaian', 'region.teams.teamsUsers'])->get(); $data = $query->with(['user', 'debiture', 'branch', 'tujuanPenilaian', 'approveSo'])->get();
// Hitung jumlah halaman // Hitung jumlah halaman

View File

@@ -1412,10 +1412,12 @@ class SurveyorController extends Controller
$query->whereRaw('LOWER(status) = ?', ['assign']); $query->whereRaw('LOWER(status) = ?', ['assign']);
$query->whereHas('penilaian.userPenilai', function ($q) { if(!Auth::user()->hasRole('administrator')) {
$q->where('user_id', Auth::user()->id); $query->whereHas('penilaian.userPenilai', function ($q) {
$q->where('role', 'surveyor'); $q->where('user_id', Auth::user()->id);
}); $q->where('role', 'surveyor');
});
}
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) { if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {

View File

@@ -193,4 +193,16 @@
public function approveBayar(){ public function approveBayar(){
return $this->belongsTo(User::class, 'approve_bayar_by', 'id'); return $this->belongsTo(User::class, 'approve_bayar_by', 'id');
} }
public function approveEo(){
return $this->belongsTo(User::class, 'approval_eo', 'id');
}
public function approveDd(){
return $this->belongsTo(User::class, 'approval_dd', 'id');
}
public function approveSo(){
return $this->belongsTo(User::class, 'approval_so', 'id');
}
} }

View File

@@ -245,7 +245,9 @@
"permission": "", "permission": "",
"roles": [ "roles": [
"administrator", "administrator",
"senior-officer" "senior-officer",
"EO Appraisal",
"DD Appraisal"
], ],
"sub": [ "sub": [
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto"> <div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
<div class="card pb-2.5"> <div class="card pb-2.5">
<div class=" card-grid min-w-full" data-datatable="false" data-datatable-page-size="10" <div class=" card-grid min-w-full" data-datatable="false" data-datatable-page-size="10"
data-datatable-state-save="false" id="permohonan-table" data-datatable-state-save="false" id="permohonan-table"
data-api-url="{{ route('otorisator.datatables', ['otorisator' => $header]) }}"> data-api-url="{{ route('otorisator.datatables', ['otorisator' => $header]) }}">
<div class="card-header py-5 flex-wrap"> <div class="card-header py-5 flex-wrap">
<h3 class="card-title"> <h3 class="card-title">
Daftar {{ $header }} Daftar {{ $header }}
@@ -30,46 +30,59 @@
<div class="card-body"> <div class="card-body">
<div class="scrollable-x-auto"> <div class="scrollable-x-auto">
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" <table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm"
data-datatable-table="true"> data-datatable-table="true">
<thead> <thead>
<tr> <tr>
<th class="w-14"> <th class="w-14">
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox" /> <input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox"/>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="nomor_registrasi"> <th class="min-w-[150px]" data-datatable-column="nomor_registrasi">
<span class="sort"> <span class="sort-label"> Nomor Registrasi </span> <span class="sort"> <span class="sort-label"> Nomor Registrasi </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="tanggal_permohonan"> <th class="min-w-[150px]" data-datatable-column="tanggal_permohonan">
<span class="sort"> <span class="sort-label"> Tanggal Permohonan </span> <span class="sort"> <span class="sort-label"> Tanggal Permohonan </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="user_id"> <th class="min-w-[150px]" data-datatable-column="user_id">
<span class="sort"> <span class="sort-label"> User Pemohon </span> <span class="sort"> <span class="sort-label"> User Pemohon </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="branch_id"> <th class="min-w-[150px]" data-datatable-column="branch_id">
<span class="sort"> <span class="sort-label"> Cabang Pemohon </span> <span class="sort"> <span class="sort-label"> Cabang Pemohon </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="debitur_id"> <th class="min-w-[150px]" data-datatable-column="debitur_id">
<span class="sort"> <span class="sort-label"> Debitur </span> <span class="sort"> <span class="sort-label"> Debitur </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
<th class="min-w-[150px]" data-datatable-column="tujuan_penilaian_id"> <th class="min-w-[150px]" data-datatable-column="tujuan_penilaian_id">
<span class="sort"> <span class="sort-label"> Tujuan Penilaian </span> <span class="sort"> <span class="sort-label"> Tujuan Penilaian </span>
<span class="sort-icon"> </span> </span> <span class="sort-icon"> </span> </span>
</th> </th>
@if ($header == 'Pembayaran') <th class="min-w-[150px]" data-datatable-column="tujuan_penilaian_id">
<th class="min-w-[150px]" data-datatable-column="tujuan_penilaian_id"> <span class="sort"> <span class="sort-label"> Status Bayar </span>
<span class="sort"> <span class="sort-label"> Status Bayar </span> <span class="sort-icon"> </span> </span>
<span class="sort-icon"> </span> </span> </th>
</th>
@endif
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th> <th class="min-w-[150px]" data-datatable-column="approval_so">
</tr> <span class="sort"> <span class="sort-label"> Approval SO </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[150px]" data-datatable-column="approval_eo">
<span class="sort"> <span class="sort-label"> Approval EO </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[150px]" data-datatable-column="approval_dd">
<span class="sort"> <span class="sort-label"> Approval DD </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
</tr>
</thead> </thead>
</table> </table>
</div> </div>
@@ -182,34 +195,80 @@
return `${data.tujuan_penilaian.name}`; return `${data.tujuan_penilaian.name}`;
}, },
}, },
...(dataHeader === 'Pembayaran' && { status_bayar: {
status_bayar: { title: 'Status Bayar',
title: 'Status Bayar', render: (item, data) => {
render: (item, data) => { const status = data.status_bayar.replace(/_/g,
const status = data.status_bayar.replace(/_/g, ' ');
' '); const statusClass = data.status_bayar === 'belum_bayar' ? 'text-red-600' :
const statusClass = data.status_bayar === 'belum_bayar' ? 'text-red-600' : 'text-green-600';
'text-green-600'; return `<span class="text-md font-bold ${statusClass} uppercase">
return `<span class="text-md font-bold ${statusClass} uppercase">
${status} ${status}
</span>`; </span>`;
},
}, },
}), },
approval_so: {
title: 'Approval SO',
render: (item, data) => {
if(data.approve_so) {
return `${data.approve_so.name} | ${window.formatTanggalIndonesia(data.approval_so_at)}`;
}
return '';
},
},
approval_eo: {
title: 'Approval EO',
render: (item, data) => {
if(data.approve_eo) {
return `${data.approve_eo.name} | ${window.formatTanggalIndonesia(data.approval_eo_at)}`;
}
return '';
},
},
approval_dd: {
title: 'Approval DD',
render: (item, data) => {
if(data.approve_dd) {
return `${data.approve_dd.name} | ${window.formatTanggalIndonesia(data.approval_dd_at)}`;
}
return '';
},
},
actions: { actions: {
title: 'Status', title: 'Status',
render: (item, data) => { render: (item, data) => {
return `<div class="flex flex-nowrap justify-center"> const userRoles = @json(Auth::user()->getRoleNames());
<a class="btn btn-sm btn-icon btn-clear btn-warning " href="otorisator/show/${data.id}/${dataHeader}"> const isAdmin = userRoles.includes('administrator');
<i class="ki-outline ki-eye"></i>
</a>
<a class="btn btn-sm btn-icon btn-clear btn-primary " onclick="otorisatorData(${data.id})"> let buttons = `
<i class="ki-filled ki-double-check"></i> <div class="flex flex-nowrap justify-center">
</a> <a class="btn btn-sm btn-icon btn-clear btn-warning" href="otorisator/show/${data.id}/${dataHeader}">
</div>`; <i class="ki-outline ki-eye"></i>
</a>
`;
if ((isAdmin || userRoles.includes('senior-officer')) && !data.approval_so) {
buttons += `
<a class="btn btn-sm btn-icon btn-clear btn-primary" onclick="otorisatorData(${data.id})">
<i class="ki-filled ki-double-check"></i>
</a>
`;
} else if ((isAdmin || userRoles.includes('EO Appraisal')) && data.approval_so && !data.approval_eo) {
buttons += `
<a class="btn btn-sm btn-icon btn-clear btn-primary" onclick="otorisatorData(${data.id})">
<i class="ki-filled ki-double-check"></i>
</a>
`;
} else if ((isAdmin || userRoles.includes('DD Appraisal')) && data.approval_eo && !data.approval_dd) {
buttons += `
<a class="btn btn-sm btn-icon btn-clear btn-primary" onclick="otorisatorData(${data.id})">
<i class="ki-filled ki-double-check"></i>
</a>
`;
}
buttons += `</div>`;
return buttons;
}, },
} }
}, },
@@ -217,7 +276,7 @@
let dataTable = new KTDataTable(element, dataTableOptions); let dataTable = new KTDataTable(element, dataTableOptions);
// Custom search functionality // Custom search functionality
searchInput.addEventListener('input', function() { searchInput.addEventListener('input', function () {
const searchValue = this.value.trim(); const searchValue = this.value.trim();
dataTable.search(searchValue, true); dataTable.search(searchValue, true);
@@ -257,7 +316,7 @@
message: userMessage // Kirim pesan sebagai bagian dari data message: userMessage // Kirim pesan sebagai bagian dari data
}, },
success: (response) => { success: (response) => {
Swal.fire('Berhasil!', 'Data berhasil diotorisasi.', 'success').then(() => { Swal.fire('Berhasil!', 'Data berhasil diotorisasi. Menunggu Approval EO dan atau DD', 'success').then(() => {
window.location.reload(); window.location.reload();
}); });
console.log(response); console.log(response);
@@ -265,7 +324,7 @@
error: (error) => { error: (error) => {
console.error('Error:', error); console.error('Error:', error);
Swal.fire('Gagal!', 'Terjadi kesalahan saat melakukan otorisator.', Swal.fire('Gagal!', 'Terjadi kesalahan saat melakukan otorisator.',
'error'); 'error');
} }
}); });
} }

View File

@@ -16,16 +16,16 @@
@if($permohonan->approval_so) @if($permohonan->approval_so)
<div class="card pb-2.5"> <div class="card pb-2.5">
<div class="card-header" id="basic_settings"> <div class="card-header" id="basic_settings">
<h3 class="card-title"> <h3 class="card-title">
Approval Approval
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-border"> <table class="table table-border">
<tbody> <tbody>
<tr> <tr>
<td>Diperiksa Oleh</td> <td>Diperiksa Oleh</td>
<td>{{ getUser($permohonan->approval_so)->name ?? 'N/A' }}</td> <td>{{ getUser($permohonan->approval_so)->name ?? 'N/A' }}</td>
@@ -41,12 +41,13 @@
<td>{{ getUser($permohonan->approval_dd)->name ?? 'N/A' }}</td> <td>{{ getUser($permohonan->approval_dd)->name ?? 'N/A' }}</td>
<td>{{ $permohonan->approval_dd_at ? formatTanggalIndonesia($permohonan->approval_dd_at,1) : 'N/A' }}</td> <td>{{ $permohonan->approval_dd_at ? formatTanggalIndonesia($permohonan->approval_dd_at,1) : 'N/A' }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
</div>
@endif @endif
<div> <div>
<div>
<div class="flex gap-2.5"> <div class="flex gap-2.5">
@php @php
$inspeksiId = null; $inspeksiId = null;
@@ -68,10 +69,26 @@
</a> </a>
@endif @endif
<button onclick="otorisatorData({{ $permohonan->id }})" type="button" class="btn btn-primary"> @if(Auth::user()->hasAnyRole(['administrator','senior-officer']) && $permohonan->approval_so==null)
<i class="ki-filled ki-double-check"></i> <button onclick="otorisatorData({{ $permohonan->id }})" type="button" class="btn btn-primary">
Otorisator {{ $header ?? '' }} <i class="ki-filled ki-double-check"></i>
</button> Otorisator {{ $header ?? '' }}
</button>
@endif
@if(Auth::user()->hasAnyRole(['administrator','EO Appraisal']) && $permohonan->approval_so && $permohonan->approval_eo==null)
<button onclick="otorisatorData({{ $permohonan->id }})" type="button" class="btn btn-primary">
<i class="ki-filled ki-double-check"></i>
Otorisator {{ $header ?? '' }}
</button>
@endif
@if(Auth::user()->hasAnyRole(['administrator','DD Appraisal']) && $permohonan->approval_eo && $permohonan->approval_dd==null)
<button onclick="otorisatorData({{ $permohonan->id }})" type="button" class="btn btn-primary">
<i class="ki-filled ki-double-check"></i>
Otorisator {{ $header ?? '' }}
</button>
@endif
</div> </div>
</div> </div>
</div> </div>
@@ -111,7 +128,7 @@
message: userMessage message: userMessage
}, },
success: (response) => { success: (response) => {
Swal.fire('Berhasil!', 'Data berhasil diotorisasi.', 'success').then(() => { Swal.fire('Berhasil!', 'Data berhasil diotorisasi. Menunggu Approval EO dan atau DD', 'success').then(() => {
window.location.reload(); window.location.reload();
}); });
console.log(response); console.log(response);

View File

@@ -418,11 +418,6 @@
<div class="input-group w-full flex gap-2"> <div class="input-group w-full flex gap-2">
<input type="hidden" name="name_lantai_unit[]" value="lantai"> <input type="hidden" name="name_lantai_unit[]" value="lantai">
<div class="preview-container"> <div class="preview-container">
<img id="foto_lantai-preview-{{ $loop->index }}"
src="{{ asset('storage/' . old('foto_lantai_unit', $item['foto_lantai_unit'])) }}"
alt="Foto Lantai" class="mt-2 h-auto"
style="{{ old('foto_lantai_unit', $item['foto_lantai_unit']) ? 'display: block;' : 'display: none;' }} width: 30rem;">
</div> </div>
<input id="inputLantai" type="file" name="foto_lantai_unit[]" <input id="inputLantai" type="file" name="foto_lantai_unit[]"
class="file-input file-input-bordered w-full" accept="image/*" class="file-input file-input-bordered w-full" accept="image/*"

View File

@@ -133,7 +133,7 @@
}); });
</script> </script>
@@ -204,9 +204,9 @@
setupCanvas() { setupCanvas() {
// Set initial canvas size // Set initial canvas size
this.canvas.width = 1280; this.canvas.width = 1280;
this.canvas.height = 960; this.canvas.height = (this.canvas.width * 3) / 4;
this.drawingCanvas.width = 1280; this.drawingCanvas.width = this.canvas.width;
this.drawingCanvas.height = 960; this.drawingCanvas.height = this.canvas.height;
} }
setupDrawingContext() { setupDrawingContext() {
@@ -277,6 +277,43 @@
this.saveDrawingState(); this.saveDrawingState();
} }
takePhoto() {
const context = this.canvas.getContext('2d');
// Determine if the video is in portrait or landscape mode
const isPortrait = this.video.videoHeight > this.video.videoWidth;
if (isPortrait) {
// Portrait mode
this.canvas.width = this.video.videoWidth;
this.canvas.height = this.video.videoHeight;
context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
} else {
// Landscape mode
this.canvas.width = 1280;
this.canvas.height = (this.canvas.width * this.video.videoHeight) / this.video.videoWidth;
context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
}
// Adjust drawing canvas to match main canvas
this.drawingCanvas.width = this.canvas.width;
this.drawingCanvas.height = this.canvas.height;
// Hide video, show canvases
this.video.style.display = 'none';
this.canvas.style.display = 'block';
this.drawingCanvas.style.display = 'block';
// Show editor controls, hide camera controls
document.getElementById('editorControls').classList.remove('hidden');
document.getElementById('cameraControls').classList.add('hidden');
// Clear drawing history
this.drawingHistory = [];
this.historyIndex = -1;
this.saveDrawingState();
}
switchCamera() { switchCamera() {
this.currentFacingMode = this.currentFacingMode === 'environment' ? 'user' : 'environment'; this.currentFacingMode = this.currentFacingMode === 'environment' ? 'user' : 'environment';
this.startCamera(); this.startCamera();