fix(pemohon/survey): penambahan resedule di awal pemohon dan resedule suveyor, dan perbaikkan tolak paparan, dan perbaikkan struktur foto

This commit is contained in:
majid
2025-03-06 05:16:48 +07:00
parent 3f3c384f9a
commit 65bfa9eeec
7 changed files with 349 additions and 229 deletions

View File

@@ -428,3 +428,22 @@ function getWilayahName($code, $type)
return null;
}
}
function formatLabel($key) {
$customLabels = [
'shgb_no_kel_desa' => 'SHGB',
'nib' => 'NIB',
'img_pbg_no' =>'IMB/PBG NO',
'no_surat_ukur_gs' => 'No Surat Ukur/GS',
];
// Jika ada dalam mapping, gunakan label custom
if (array_key_exists($key, $customLabels)) {
return $customLabels[$key];
}
// Default: Ubah underscore menjadi spasi dan kapitalisasi
return ucwords(str_replace('_', ' ', $key));
}

View File

@@ -754,9 +754,13 @@ class PenilaiController extends Controller
if (!$allComplete) {
$message = $rap
? 'Harap Mengisi laporan terlebih dahulu'
: 'Harap Mengisi laporan terlebih dahulu atau kertas kerja';
return response()->json([
'success' => false,
'message' => 'Harap Mengisi laporan terlebih dahulu atau kertas kerja',
'message' => $message,
], 400);
}

View File

@@ -962,7 +962,8 @@ class SurveyorController extends Controller
$validate = $request->validate([
'id' => 'required',
'waktu_penilaian' => 'required',
'deskripsi_penilaian' => 'required'
'deskripsi_penilaian' => 'required',
'keterangan' => 'required',
]);
$penilaian = Penilaian::findOrFail($id);
@@ -3713,7 +3714,7 @@ class SurveyorController extends Controller
return response()->json([
'status' => 'success',
'message' => 'Reschedule jadwal dengan nomor registrasi ' . $validatedData['nomor_registrasi'] . ' berhasil ditolak dengan alasan: ' . $validatedData['rejected_note'],
'message' => 'Reschedule jadwal dengan nomor registrasi ' . $validatedData['nomor_registrasi'] . ' berhasil dengan alasan: ' . $validatedData['rejected_note'],
]);
} catch (\Exception $e) {
// Rollback jika ada error

View File

@@ -46,36 +46,24 @@
/* Media Print */
@media print {
.photo-item {
width: 48%;
margin-right: 2%;
float: left;
padding: 5px;
box-sizing: border-box;
page-break-inside: avoid;
}
.photo-item img {
width: 300px;
height: 300px;
object-fit: contain;
background-color: #f0f0f0;
border-radius: 5px;
page-break-inside: avoid;
}
h2 {
font-size: 14px;
}
.photo-container {
page-break-inside: avoid;
}
.photo-row {
page-break-inside: avoid;
}
}
table {
width: 100%;
border-collapse: collapse;
}
td {
vertical-align: top;
text-align: center;
page-break-inside: avoid;
}
.photo-image {
width: auto;
height: 400px;
max-height: 400px;
}
.page-break {
page-break-after: always;
}
}
</style>
@isset($basicData['foto'])
@@ -93,6 +81,8 @@
@if ($groupedPhotos->isEmpty())
<p class="text-gray-500">Tidak ada foto yang tersedia.</p>
@else
@foreach ($mainPhotos as $category => $photos)
@php
$groupedBySubcategory = $photos->groupBy('sub');
@@ -100,142 +90,77 @@
@foreach ($groupedBySubcategory as $subcategory => $subPhotos)
@if (count($subPhotos) > 0)
<table width="100%" border="0" style="align-content: center; text-align: center">
<tr>
@if (count($subPhotos) === 1)
@php
$item = $subPhotos->first();
$imagePath = storage_path('app/public/' . $item['path']);
@endphp
<td style="width: 100%; vertical-align: top; text-align: center" colspan="2"
class="photo-item border">
<p style="font-weight: medium; font-size: 10px">{{ $category }} -
@isset($subcategory)
@if (trim($subcategory) !== '')
{{ $subcategory }} -
@endif
@endisset
{{ $item['name'] ?? '' }}
</p>
@if ($statusLpj || file_exists($imagePath))
<img src="{{ $imagePath }}" alt="{{ $item['path'] }}" class="photo-image"
style="width: 100%; max-width: 300px;">
@endif
@isset($item['description'])
<p style="font-size:9px">{{ $item['description'] }}</p>
@endisset
</td>
@else
@foreach ($subPhotos as $index => $item)
@foreach ($subPhotos->chunk(2) as $chunkedPhotos)
<table width="100%" border="0" style="align-content: center; text-align: center; margin-bottom: 20px">
@foreach ($chunkedPhotos as $item)
@php
$imagePath = storage_path('app/public/' . $item['path']);
@endphp
<td style="width: 50%; vertical-align: top; text-align: center"
class="photo-item border">
<tr>
<td style="width: 100%; padding: 10px;" class="photo-item border">
<p style="font-weight: medium; font-size: 10px">{{ $category }} -
@isset($subcategory)
@if (trim($subcategory) !== '')
{{ $subcategory }} -
@endif
@endisset
{{ $item['name'] ?? '' }}
</p>
@if ($statusLpj || file_exists($imagePath))
<img src="{{ $imagePath }}" alt="{{ $item['path'] }}" class="photo-image"
style="width: 100%; max-width: 300px;">
<img src="{{ $imagePath }}" alt="{{ $item['path'] }}" class="photo-image">
@endif
@isset($item['description'])
<p style="font-size:9px">{{ $item['description'] }}</p>
@endisset
</td>
@if (($index + 1) % 2 == 0)
</tr>
<tr>
</tr>
@endforeach
@if (count($chunkedPhotos) < 2)
<tr style="width: 100%;"></tr>
@endif
</table>
<div class="page-break"></div>
@endforeach
@endif
@endforeach
@if (count($subPhotos) % 2 != 0)
<td style="width: 50%;"></td>
@endif
@endif
</tr>
</table>
@endif
@endforeach
@endforeach
@endif
<div class="page-break"></div>
@if (!$otherPhotos->isEmpty())
@foreach ($otherPhotos->groupBy('sub') as $subcategory => $subPhotos)
@if (count($subPhotos) > 0)
<table width="100%" border="0" style="align-content: center; text-align: center">
<tr>
@if (count($subPhotos) === 1)
@php
$item = $subPhotos->first();
$imagePath = storage_path('app/public/' . $item['path']);
@endphp
<td style="width: 100%; vertical-align: top; text-align: center" colspan="2"
class="photo-item border">
<p style="font-weight: medium; font-size: 10px">Lainnya -
@isset($subcategory)
@if (trim($subcategory) !== '')
{{ $subcategory }} -
@endif
@endisset
{{ $item['name'] ?? '' }}
</p>
@if ($statusLpj || file_exists($imagePath))
<img src="{{ $imagePath }}" alt="{{ $item['path'] }}" class="photo-image"
style="width: 100%; max-width: 300px;">
@endif
@isset($item['description'])
<p style="font-size:9px">{{ $item['description'] }}</p>
@endisset
</td>
@else
@foreach ($subPhotos as $index => $item)
@php
$imagePath = storage_path('app/public/' . $item['path']);
@endphp
<td style="width: 50%; vertical-align: top; text-align: center" class="photo-item border">
<p style="font-weight: medium; font-size: 10px">Lainnya -
@isset($subcategory)
@if (trim($subcategory) !== '')
{{ $subcategory }} -
@endif
@endisset
{{ $item['name'] ?? '' }}
</p>
@if ($statusLpj || file_exists($imagePath))
<img src="{{ $imagePath }}" alt="{{ $item['path'] }}" class="photo-image"
style="width: 100%; max-width: 300px;">
@endif
@isset($item['description'])
<p style="font-size:9px">{{ $item['description'] }}</p>
@endisset
</td>
@if (($index + 1) % 2 == 0)
</tr>
<tr>
@endif
@endforeach
@if (count($subPhotos) % 2 != 0)
<td style="width: 50%;"></td>
@if (!$otherPhotos->isEmpty())
@foreach ($otherPhotos->groupBy('sub') as $subcategory => $subPhotos)
@if (count($subPhotos) > 0)
@foreach ($subPhotos->chunk(2) as $chunkedPhotos)
<table width="100%" border="0" style="align-content: center; text-align: center; margin-bottom: 20px">
@foreach ($chunkedPhotos as $item)
@php
$imagePath = storage_path('app/public/' . $item['path']);
@endphp
<tr>
<td style="width: 100%; padding: 10px;" class="photo-item border">
<p style="font-weight: medium; font-size: 10px">Lainnya -
@isset($subcategory)
@if (trim($subcategory) !== '')
{{ $subcategory }} -
@endif
@endisset
{{ $item['name'] ?? '' }}
</p>
@if ($statusLpj || file_exists($imagePath))
<img src="{{ $imagePath }}" alt="{{ $item['path'] }}" class="photo-image">
@endif
@isset($item['description'])
<p style="font-size:9px">{{ $item['description'] }}</p>
@endisset
</td>
@endforeach
</tr>
@if (count($chunkedPhotos) < 2)
<tr style="width: 100%;"></tr>
@endif
</table>
@endforeach
@endif
@endforeach
@endif
@endif
</tr>
</table>
@endif
@endforeach
@endif
@endisset

View File

@@ -216,24 +216,24 @@
</a>
@if (Auth::user()->hasAnyRole(['administrator', 'senior-officer']) && $authorization->approve_so == null)
<button onclick="otorisatorData({{ $authorization->id }},'SO')" type="button"
@if (Auth::user()->hasAnyRole(['administrator', 'senior-officer']) && $authorization->approve_so == null)
<button onclick="otorisatorData({{ $permohonan->id }},'SO')" type="button"
class="btn btn-primary">
<i class="ki-filled ki-double-check"></i>
Otorisator {{ $header ?? '' }}
</button>
@endif
@if (Auth::user()->hasAnyRole(['administrator', 'EO Appraisal']) && $authorization->approve_eo == null)
<button onclick="otorisatorData({{ $authorization->id }},'EO')" type="button"
@if (Auth::user()->hasAnyRole(['administrator', 'EO Appraisal']) && $authorization->approve_so && $authorization->approve_eo == null)
<button onclick="otorisatorData({{ $permohonan->id }},'EO')" 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']) && $authorization->approve_dd == null)
<button onclick="otorisatorData({{ $authorization->id }},'DD')" type="button"
@if (Auth::user()->hasAnyRole(['administrator', 'DD Appraisal']) && $authorization->approve_eo && $authorization->approve_dd == null)
<button onclick="otorisatorData({{ $permohonan->id }},'DD')" type="button"
class="btn btn-primary">
<i class="ki-filled ki-double-check"></i>
Otorisator {{ $header ?? '' }}

View File

@@ -206,24 +206,32 @@
if (data.status === 'proses-survey' || data.status == 'rejected-reschedule') {
actionHtml += `
<a
title="Reschedule Jadwal Survey"
data-permohonan-id="${data.id}"
data-id-penilaian="${data.penilaian.id}"
data-no-reg="${data.nomor_registrasi}"
data-debitur="${data.debiture ? data.debiture.name.replace(/'/g, "\\'") : ""}"
data-waktu-penilaian="${data.penilaian.waktu_penilaian}"
data-rejected-note="${data.penilaian.rejected_note ? JSON.stringify(data.penilaian.rejected_note).replace(/'/g, "\\'").replace(/"/g, '&quot;') : '{}'}"
onclick="surveyorRescheduleJadwalSurvey(this)"
class="delete btn btn-sm btn-outline btn-light">
<i class="ki-filled ki-calendar-remove"></i>
</a>`;
<a
title="Reschedule Jadwal Survey"
data-permohonan-id="${data.id}"
data-id-penilaian="${data.penilaian.id}"
data-no-reg="${data.nomor_registrasi}"
data-debitur="${data.debiture ? data.debiture.name.replace(/'/g, "\\'") : ""}"
data-waktu-penilaian="${data.penilaian.waktu_penilaian}"
data-rejected-note="${data.penilaian.rejected_note ? JSON.stringify(data.penilaian.rejected_note).replace(/'/g, "\\'").replace(/"/g, '&quot;') : '{}'}"
onclick="surveyorRescheduleJadwalSurvey(this)"
class="delete btn btn-sm btn-outline btn-light">
<i class="ki-filled ki-calendar-remove"></i>
</a>`;
}
if (data && data.penilaian && data.penilaian.waktu_penilaian !== null && data.status !==
'done' && data.penilaian.authorized_status == null || data.status === 'approved-reschedule') {
'done' && data.penilaian.authorized_status == null || data.status ===
'approved-reschedule') {
actionHtml += `
<a class="btn btn-sm btn-outline btn-primary" href="javascript:void(0)" onclick="surveyorApproveKunjungan(${data.id},${data.penilaian.id},'${data.nomor_registrasi}', '${data.debiture?.name}', '${data.penilaian.waktu_penilaian}')" title="Approve Jadwal Kunjungan No Reg ${data.nomor_registrasi}" >
<a class="btn btn-sm btn-outline btn-primary"
title="Approve Jadwal Kunjungan No Reg ${data.nomor_registrasi}"
data-permohonan-id="${data.id}"
data-id-penilaian="${data.penilaian.id}"
data-no-reg="${data.nomor_registrasi}"
data-debitur="${data.debiture ? data.debiture.name.replace(/'/g, "\\'") : ""}"
data-waktu-penilaian="${data.penilaian.waktu_penilaian}"
onclick="surveyorApproveKunjungan(this)" >
<i class="ki-filled ki-calendar-edit"></i>
</a>
`;
@@ -267,16 +275,26 @@
});
</script>
<script type="text/javascript">
function surveyorApproveKunjungan(permohonanId, idPenilaian, noReg, debitur, waktuPenilaian) {
function surveyorApproveKunjungan(element) {
const permohonanId = element.getAttribute('data-permohonan-id');
const idPenilaian = element.getAttribute('data-id-penilaian');
const noReg = element.getAttribute('data-no-reg');
const debitur = element.getAttribute('data-debitur');
const waktuPenilaian = element.getAttribute('data-waktu-penilaian');
Swal.fire({
title: ' ',
text: "Yakin akan Menyetujui Jadwal Kunjungan " + noReg + " untuk Debitur " + debitur +
" pada waktu " + window.formatTanggalWaktuIndonesia(waktuPenilaian) + "?",
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel',
showDenyButton: true,
denyButtonText: 'Reschedule',
confirmButtonColor: '#3085d6',
denyButtonColor: '#f4b400',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes'
}).then((result) => {
if (result.isConfirmed) {
//define variable
@@ -314,6 +332,94 @@
}
});
} else if (result.isDenied) {
Swal.fire({
title: 'Reschedule Jadwal Kunjungan',
html: `
<div class="text-left space-y-4">
<p class="text-gray-700">
Yakin akan Reschedule Jadwal Kunjungan
<span class="font-semibold text-blue-600">${noReg}</span>
untuk Debitur
<span class="font-semibold text-blue-600">${debitur}</span>
pada waktu
<span class="font-semibold">${window.formatTanggalWaktuIndonesia(waktuPenilaian)}</span>?
</p>
<div>
<label for="reschedule_date" class="block text-sm font-medium text-gray-700 mb-1">Tanggal Baru</label>
<input type="datetime-local" id="reschedule_date"
class="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<div>
<label for="reschedule_note" class="block text-sm font-medium text-gray-700 mb-1">Catatan</label>
<textarea id="reschedule_note"
class="block w-full px-4 py-2 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
placeholder="Masukkan alasan reschedule..."></textarea>
</div>
</div>
`,
showCancelButton: true,
confirmButtonText: 'Reschedule',
cancelButtonText: 'Batal',
preConfirm: () => {
const rescheduleDate = document.getElementById('reschedule_date').value;
const rescheduleNote = document.getElementById('reschedule_note').value;
if (!rescheduleDate || !rescheduleNote) {
Swal.showValidationMessage('Semua input wajib diisi!');
return false;
}
return {
rescheduleDate,
rescheduleNote
};
}
}).then((rescheduleResult) => {
if (rescheduleResult.isConfirmed) {
const {
rescheduleDate,
rescheduleNote
} = rescheduleResult.value;
const data = {
_token: "{{ csrf_token() }}",
penilaian_id: idPenilaian,
nomor_registrasi: noReg,
permohonan_id: permohonanId,
reschedule_date: rescheduleDate,
reschedule_note: rescheduleNote,
keterangan: rescheduleNote
};
$.ajax({
url: `{{ URL::to('/permohonan/store-reschedule-survey') }}/${idPenilaian}`,
type: "PUT",
cache: false,
data: data,
dataType: "json",
success: function(response) {
if (response.status === 'success') {
Swal.fire('Sukses Reschedule!', response.message,
'success').then(() => {
location.reload();
});
} else {
Swal.fire('Error!', response.message, 'error');
}
},
error: function(response) {
console.log(response);
}
});
}
});
}
})
@@ -333,11 +439,12 @@
console.warn('Failed to parse rejectedNote:', e);
rejectedNote = null;
}
const note = rejectedNote && typeof rejectedNote === 'string' && rejectedNote.trim() ? String(rejectedNote).trim() : '';
const note = rejectedNote && typeof rejectedNote === 'string' && rejectedNote.trim() ? String(rejectedNote)
.trim() : '';
Swal.fire({
title: 'Reschedule Jadwal Kunjungan',
html: `
Swal.fire({
title: 'Reschedule Jadwal Kunjungan',
html: `
<div class="text-left space-y-4">
<p class="text-gray-700">
@@ -362,65 +469,73 @@
placeholder="Masukkan alasan reschedule..."></textarea>
</div>
${note ? `
<p class="text-gray-700"><strong>Catatan Reject:</strong> ${note}</p>
` : ''}
<p class="text-gray-700"><strong>Catatan Reject:</strong> ${note}</p>
` : ''}
</div>
`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Reschedule',
preConfirm: () => {
const rescheduleDate = document.getElementById('reschedule_date').value;
const rescheduleNote = document.getElementById('reschedule_note').value;
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Reschedule',
preConfirm: () => {
const rescheduleDate = document.getElementById('reschedule_date').value;
const rescheduleNote = document.getElementById('reschedule_note').value;
if (!rescheduleDate || !rescheduleNote) {
Swal.showValidationMessage('Semua inputan wajib diisi!');
return false;
}
return { rescheduleDate, rescheduleNote };
}
}).then((result) => {
if (result.isConfirmed) {
const { rescheduleDate, rescheduleNote } = result.value;
// Data to send
const data = {
_token: "{{ csrf_token() }}",
penilaian_id: idPenilaian,
nomor_registrasi: noReg,
permohonan_id: permohonanId,
reschedule_date: rescheduleDate,
reschedule_note: rescheduleNote,
keterangan: rescheduleNote
};
$.ajax({
url: `{{ URL::to('/permohonan/store-reschedule-survey') }}/${idPenilaian}`,
type: "PUT",
cache: false,
data: data,
dataType: "json",
success: function(response) {
if (response.status === 'success') {
Swal.fire('Sukses Reschedule!', response.message, 'success').then(() => {
location.reload();
});
} else {
Swal.fire('Error!', response.message, 'error');
if (!rescheduleDate || !rescheduleNote) {
Swal.showValidationMessage('Semua inputan wajib diisi!');
return false;
}
},
error: function(response) {
const errorMessage = response.responseJSON?.message || 'Terjadi kesalahan saat memproses data.';
Swal.fire('Error!', errorMessage, 'error');
return {
rescheduleDate,
rescheduleNote
};
}
}).then((result) => {
if (result.isConfirmed) {
const {
rescheduleDate,
rescheduleNote
} = result.value;
// Data to send
const data = {
_token: "{{ csrf_token() }}",
penilaian_id: idPenilaian,
nomor_registrasi: noReg,
permohonan_id: permohonanId,
reschedule_date: rescheduleDate,
reschedule_note: rescheduleNote,
keterangan: rescheduleNote
};
$.ajax({
url: `{{ URL::to('/permohonan/store-reschedule-survey') }}/${idPenilaian}`,
type: "PUT",
cache: false,
data: data,
dataType: "json",
success: function(response) {
if (response.status === 'success') {
Swal.fire('Sukses Reschedule!', response.message, 'success').then(
() => {
location.reload();
});
} else {
Swal.fire('Error!', response.message, 'error');
}
},
error: function(response) {
const errorMessage = response.responseJSON?.message ||
'Terjadi kesalahan saat memproses data.';
Swal.fire('Error!', errorMessage, 'error');
}
});
}
});
}
});
}
// window.formatTanggalIndonesia(date)

View File

@@ -206,7 +206,7 @@
}
function prosesSurvey(permohonanId, nomor_registrasi) {
function prosesSurvey(permohonanId, nomor_registrasi, penilaianId) {
Swal.fire({
title: 'Konfirmasi',
text: `Yakin akan Melakukan Inspeksi dengan nomor registrasi ${nomor_registrasi}?`,
@@ -216,6 +216,9 @@
cancelButtonColor: '#d33',
confirmButtonText: 'Ya, Setujui',
cancelButtonText: 'Batal',
denyButtonText: 'Ubah Jadwal Kunjungan',
denyButtonColor: '#f4b400',
showDenyButton: true
}).then((result) => {
if (result.isConfirmed) {
// Mendefinisikan URL dan data
@@ -250,6 +253,58 @@
Swal.fire('Error!', errorMessage, 'error');
}
});
}else if (result.isDenied) {
// Reject action
Swal.fire({
title: 'Masukkan Keterangan',
input: 'textarea',
inputPlaceholder: 'Tuliskan alasan perubahan jadwal di sini dan masukkan tanggal kunjungan...',
inputAttributes: {
'aria-label': 'Tuliskan alasan perubahan jadwal di sini dan masukkan tanggal kunjungan'
},
showCancelButton: true,
confirmButtonText: 'Submit',
cancelButtonText: 'Batal'
}).then((rejectResult) => {
if (rejectResult.isConfirmed && rejectResult.value) {
let token = "{{ csrf_token() }}";
let useURL = "{{ URL::to('/surveyor/store-rejected-reschedule') }}" + "/" +
penilaianId;
var input_data = {
_token: token,
permohonan_id: permohonanId,
nomor_registrasi: nomor_registrasi,
rejected_note: rejectResult.value,
keterangan: rejectResult.value
}
$.ajax({
url: useURL,
type: "PUT",
cache: false,
data: input_data,
success: function(response) {
console.log(response);
if ('success' == response.status) {
Swal.fire('Ditolak!', response.message, 'success').then(
() => {
location.reload(true);
});
} else {
Swal.fire('Error!', response.message, 'error');
}
},
error: function(response, textStatus, errorThrown) {
console.log(response);
}
});
} else if (rejectResult.dismiss === Swal.DismissReason.cancel) {
Swal.fire('Dibatalkan', 'Aksi penolakan dibatalkan.', 'info');
}
});
}
});
}
@@ -444,7 +499,8 @@
_token: token,
id: permohonanId,
waktu_penilaian: tanggal,
deskripsi_penilaian: keterangan
deskripsi_penilaian: keterangan,
keterangan:'Tanggal Kunjungan: ' + tanggal + ', Keterangan: ' + keterangan
};
$.ajax({
@@ -580,7 +636,7 @@
} else if (data.status === 'proses-survey') {
actionHtml += `
<a class="btn btn-sm btn-icon btn-clear btn-clarity"
onclick="prosesSurvey(${data.id}, '${data.nomor_registrasi}')"
onclick="prosesSurvey(${data.id}, '${data.nomor_registrasi}', '${data.penilaian.id}')"
title="Masuk Form Inspeksi">
<i class="ki-filled ki-tablet-ok"></i>
</a>