Ringkasan: - Menambahkan halaman hasil inspeksi dan tampilan cetak laporan. - Mengekstrak komponen detail lokasi ke partial baru agar reusable. - Menambahkan null-safe access dan perbaikan binding data di view. - Merapikan tombol cetak dan navigasi agar konsisten antar halaman. Perubahan utama: 1. activitydetail.blade.php → ubah tombol print jadi route, tambah null-safe user/branch. 2. detail-lokasi.blade.php (baru) → komponen reusable untuk detail lokasi dengan formatLabel & tanggal. 3. form-penilai.blade.php → refactor luas menggunakan match, hapus fungsi debug & Swal loading. 4. print-out-dokument.blade.php → gunakan partial lpj::component.detail-lokasi untuk detail lokasi. 5. show-laporan-inspeksi.blade.php (baru) → tab 'Laporan' & 'Hasil Inspeksi' + tombol cetak dan back. 6. print-out-sederhana / print-out-standar → penyesuaian tampilan & binding data. 7. signature-approval.blade.php → perbaikan layout area tanda tangan. 8. surveyor/components/* → normalisasi tampilan, validasi gambar, dan penyelarasan fakta/lingkungan. 9. routes/web.php → tambah dan ubah rute untuk laporan inspeksi dan cetak laporan. Catatan: - Tidak ada perubahan query database; semua modifikasi bersifat tampilan. - Logging tambahan untuk observabilitas proses render laporan.
435 lines
21 KiB
PHP
435 lines
21 KiB
PHP
@extends('layouts.main')
|
|
|
|
@section('breadcrumbs')
|
|
{{ Breadcrumbs::render(request()->route()->getName()) }}
|
|
@endsection
|
|
|
|
@section('content')
|
|
<style>
|
|
.pdf-preview {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
border: 1px solid #ddd;
|
|
margin-top: 10px;
|
|
}
|
|
</style>
|
|
|
|
@include('lpj::assetsku.includenya')
|
|
|
|
<div class="grid gap-5 mx-auto w-full lg:gap-7.5">
|
|
<div class="min-w-full border card border-agi-100">
|
|
<div class="card-header bg-agi-50">
|
|
<h3 class="card-title">Denah</h3>
|
|
<div class="flex gap-2 items-center">
|
|
<a href="{{ route('surveyor.show', ['id' => $permohonan->id]) }}?form=denah" class="btn btn-xs btn-info">
|
|
<i class="ki-filled ki-exit-left"></i> Back
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="mx-8">
|
|
@include('lpj::component.detail-jaminan', ['status' => true])
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<form id="formDenah" method="POST" enctype="multipart/form-data" class="w-full">
|
|
@csrf
|
|
<input type="hidden" value="{{ $permohonan->id }}" name="permohonan_id">
|
|
<input type="hidden" name="dokument_id" value="{{ request('documentId') }}">
|
|
<input type="hidden" name="nomor_registrasi" value="{{ $permohonan->nomor_registrasi }}">
|
|
<!-- Container untuk daftar denah -->
|
|
<div id="denah-container">
|
|
@if (isset($formDenah['denahs']) && is_array($formDenah['denahs']) && count($formDenah['denahs']) > 0)
|
|
@foreach ($formDenah['denahs'] as $index => $denah)
|
|
<div class="grid gap-5 p-4 mb-5 rounded border denah-item">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex flex-wrap gap-4 items-baseline w-full lg:flex-nowrap">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Upload Denah (Foto/PDF)</span>
|
|
</label>
|
|
<div class="grid gap-5 w-full">
|
|
<!-- Preview Container -->
|
|
<div class="preview-container-{{ $index }}">
|
|
@if (isset($denah['foto_denah']))
|
|
@php
|
|
$fileExtension = pathinfo(
|
|
$denah['foto_denah'],
|
|
PATHINFO_EXTENSION,
|
|
);
|
|
@endphp
|
|
|
|
@if (in_array($fileExtension, ['jpg', 'jpeg', 'png']))
|
|
<img src="{{ asset('storage/' . $denah['foto_denah']) }}"
|
|
style="max-width: 30rem;">
|
|
@elseif($fileExtension === 'pdf')
|
|
<embed src="{{ asset('storage/' . $denah['foto_denah']) }}"
|
|
type="application/pdf" width="100%" height="500px">
|
|
@endif
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex gap-2 w-full input-group">
|
|
<input type="file" name="foto_denah[]"
|
|
class="w-full file-input file-input-bordered"
|
|
data-index="{{ $index }}" accept=".jpg,.jpeg,.png,.pdf"
|
|
onchange="previewFile(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-4 w-full lg:flex-nowrap">
|
|
<div class="w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Nama Denah</span>
|
|
</label>
|
|
<input type="text" name="nama_denah[]" class="w-full input"
|
|
value="{{ $denah['nama_denah'] ?? '' }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label"> Luas</span>
|
|
</label>
|
|
<input type="text" name="luas_denah[]"
|
|
class="w-full input number-format"
|
|
value="{{ isset($denah['luas_denah']) ? $denah['luas_denah'] . ' m²' : '' }}"
|
|
onkeyup="formatNumber(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@if ($index > 0)
|
|
<div class="flex items-center">
|
|
<button type="button" class="btn btn-danger remove-denah">
|
|
<i class="ki-filled ki-minus"></i>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<!-- Tambahkan satu elemen default jika tidak ada data -->
|
|
<div class="grid gap-5 p-4 mb-5 rounded border denah-item">
|
|
<!-- Isi dengan elemen default seperti sebelumnya -->
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex flex-wrap gap-4 items-baseline w-full lg:flex-nowrap">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Upload Denah (Foto/PDF)</span>
|
|
</label>
|
|
<div class="grid gap-5 w-full">
|
|
<div class="preview-container-0">
|
|
<!-- Preview akan ditampilkan di sini -->
|
|
</div>
|
|
|
|
<div class="flex gap-2 w-full input-group">
|
|
<input type="file" name="foto_denah[]"
|
|
class="w-full file-input file-input-bordered" data-index="0"
|
|
accept=".jpg,.jpeg,.png,.pdf" onchange="previewFile(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-4 w-full lg:flex-nowrap">
|
|
<div class="w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Nama Denah</span>
|
|
</label>
|
|
<input type="text" name="nama_denah[]" class="w-full input">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label"> Luas</span>
|
|
</label>
|
|
<input type="text" name="luas_denah[]" class="w-full input number-format"
|
|
onkeyup="formatNumber(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
|
|
<div class="p-4 w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Total Luas</span>
|
|
</label>
|
|
<input type="text" name="total_luas" id="totalLuas" class="w-full input number-format" readonly>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-2 justify-end" style="margin-right: 20px; margin-top: 20px">
|
|
<button type="button" class="btn btn-success" id="saveButton" onclick="submitDenah()">
|
|
<span id="saveButtonText">Save</span>
|
|
</button>
|
|
<button class="btn btn-primary" type="button" id="tambahDenah">
|
|
Tambah Denah
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
@include('lpj::surveyor.js.utils')
|
|
<script>
|
|
const datas = @json($denah);
|
|
|
|
let denahIndex = 1; // Mulai dari 1 karena sudah ada satu elemen default
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Format number input
|
|
document.querySelectorAll('.number-format').forEach(input => {
|
|
input.addEventListener('input', function() {
|
|
formatNumber(this);
|
|
});
|
|
input.addEventListener('focus', function() {
|
|
if (this.value === 'm²') {
|
|
this.setSelectionRange(0, 0); // Kursor di awal
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Tambah Denah
|
|
document.getElementById('tambahDenah').addEventListener('click', function() {
|
|
const denahContainer = document.getElementById('denah-container');
|
|
const newDenah = createDenahElement(denahIndex);
|
|
denahContainer.appendChild(newDenah);
|
|
denahIndex++;
|
|
|
|
const newInputs = newDenah.querySelectorAll('.number-format');
|
|
newInputs.forEach(input => {
|
|
input.addEventListener('input', function() {
|
|
formatNumber(this);
|
|
});
|
|
|
|
input.addEventListener('focus', function() {
|
|
if (this.value === 'm²') {
|
|
this.setSelectionRange(0, 0); // Kursor di awal
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// Event delegation untuk tombol hapus denah
|
|
document.getElementById('denah-container').addEventListener('click', function(e) {
|
|
if (e.target.closest('.remove-denah')) {
|
|
e.target.closest('.denah-item').remove();
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Menghitung total luas dari semua input denah
|
|
* Mendukung format angka dengan koma sebagai pemisah desimal
|
|
* dan menghapus format ribuan (titik) sebelum perhitungan
|
|
*/
|
|
function calculateTotalLuas() {
|
|
let totalLuas = 0;
|
|
|
|
const luasInputs = document.querySelectorAll('input[name="luas_denah[]"]');
|
|
luasInputs.forEach(input => {
|
|
// Hapus format ribuan (titik) dan spasi
|
|
let cleanValue = input.value.replace(/\./g, '').replace(/\s/g, '');
|
|
|
|
// Ganti koma dengan titik untuk desimal
|
|
cleanValue = cleanValue.replace(',', '.');
|
|
|
|
// Parse ke float
|
|
const value = parseFloat(cleanValue);
|
|
if (!isNaN(value)) {
|
|
totalLuas += value;
|
|
}
|
|
});
|
|
|
|
const totalLuasInput = document.getElementById('totalLuas');
|
|
totalLuasInput.value = totalLuas ? `${totalLuas.toLocaleString('id-ID')} m²` : '';
|
|
|
|
// Log untuk debugging
|
|
console.log('Total luas dihitung:', totalLuas);
|
|
}
|
|
|
|
// Tambahkan event listener untuk setiap input luas_denah[]
|
|
document.addEventListener('input', function(e) {
|
|
if (e.target && e.target.name === 'luas_denah[]') {
|
|
calculateTotalLuas();
|
|
}
|
|
});
|
|
|
|
// Jalankan hitung ulang saat halaman dimuat (jika ada nilai default)
|
|
document.addEventListener('DOMContentLoaded', calculateTotalLuas);
|
|
|
|
|
|
function createDenahElement(index) {
|
|
const denahItem = document.createElement('div');
|
|
denahItem.className = 'denah-item grid gap-5 mb-5 border p-4 rounded';
|
|
denahItem.innerHTML = `
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex flex-wrap gap-4 items-baseline w-full lg:flex-nowrap">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Upload Denah (Foto/PDF)</span>
|
|
</label>
|
|
<div class="grid gap-5 w-full">
|
|
<div class="preview-container-${index}">
|
|
<!-- Preview akan ditampilkan di sini -->
|
|
</div>
|
|
<div class="flex gap-2 w-full input-group">
|
|
<input type="file" name="foto_denah[]"
|
|
class="w-full file-input file-input-bordered"
|
|
data-index="${index}"
|
|
accept=".jpg,.jpeg,.png,.pdf"
|
|
onchange="previewFile(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap gap-4 w-full lg:flex-nowrap">
|
|
<div class="w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label">Nama Denah</span>
|
|
</label>
|
|
<input type="text" name="nama_denah[]" class="w-full input">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex gap-4 justify-center items-center w-full">
|
|
<label class="form-label max-w-56">
|
|
<span class="form-label"> Luas</span>
|
|
</label>
|
|
<input type="text" name="luas_denah[]"
|
|
class="w-full input number-format"
|
|
onkeyup="formatNumber(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<button type="button" class="btn btn-danger remove-denah">
|
|
<i class="ki-filled ki-minus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
return denahItem;
|
|
}
|
|
|
|
function previewFile(input) {
|
|
const index = input.getAttribute('data-index');
|
|
const previewContainer = document.querySelector(`.preview-container-${index}`);
|
|
const file = input.files[0];
|
|
|
|
// Hapus preview sebelumnya
|
|
previewContainer.innerHTML = '';
|
|
|
|
if (file) {
|
|
if (file.type.startsWith('image/')) {
|
|
// Jika file adalah gambar
|
|
const img = document.createElement('img');
|
|
img.style.maxWidth = '30rem';
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
img.src = e.target.result;
|
|
}
|
|
reader.readAsDataURL(file);
|
|
|
|
previewContainer.appendChild(img);
|
|
} else if (file.type === 'application/pdf') {
|
|
// Jika file adalah PDF
|
|
const pdfEmbed = document.createElement('embed');
|
|
pdfEmbed.src = URL.createObjectURL(file);
|
|
pdfEmbed.type = 'application/pdf';
|
|
pdfEmbed.width = '100%';
|
|
pdfEmbed.height = '500px';
|
|
pdfEmbed.className = 'pdf-preview';
|
|
|
|
previewContainer.appendChild(pdfEmbed);
|
|
}
|
|
}
|
|
}
|
|
|
|
function submitDenah() {
|
|
showLoadingSwal('Mengirim data ke server...');
|
|
|
|
document.querySelectorAll('.number-format').forEach(input => {
|
|
if (input.value.includes('m²')) {
|
|
input.value = input.value.replace('m²', '').trim();
|
|
}
|
|
});
|
|
|
|
const formElement = $('#formDenah')[0];
|
|
const formData = new FormData(formElement);
|
|
|
|
$.ajax({
|
|
url: '{{ route('surveyor.storeDenah') }}',
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
hideLoadingSwal();
|
|
Swal.fire({
|
|
title: 'Berhasil!',
|
|
text: response.message,
|
|
icon: 'success',
|
|
confirmButtonText: 'OK'
|
|
}).then((response) => {
|
|
if (response.isConfirmed) {
|
|
// window.location.href =
|
|
// '{{ route('surveyor.show', ['id' => $permohonan->id]) }}';
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
title: 'Error!',
|
|
text: response.message || 'Terjadi kesalahan',
|
|
icon: 'error',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
let errors = xhr.responseJSON?.errors;
|
|
$('.alert').text('');
|
|
if (errors) {
|
|
$.each(errors, function(key, value) {
|
|
$(`#error-${key}`).text(value[0]);
|
|
});
|
|
}
|
|
hideLoadingSwal();
|
|
toastrErrorBuild(error);
|
|
},
|
|
});
|
|
}
|
|
</script>
|
|
@endpush
|