feat(penilai): tambahkan fitur unggah foto dan simpan memo dengan foto
- Menambahkan metode `uploadTempPhoto` untuk mengunggah foto sementara. - Menambahkan metode `storeMemoWithPhotos` untuk menyimpan memo beserta foto. - Memperbarui rute untuk mendukung pengunggahan foto dan penyimpanan memo. - Memperbarui tampilan untuk menampilkan foto yang sudah ada dan mengubah ID dropzone.
This commit is contained in:
@@ -4,6 +4,7 @@ namespace Modules\Lpj\Http\Controllers;
|
|||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Barryvdh\DomPDF\Facade\Pdf; // https://github.com/barryvdh/laravel-dompdf
|
use Barryvdh\DomPDF\Facade\Pdf; // https://github.com/barryvdh/laravel-dompdf
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\Lpj\Models\Permohonan;
|
use Modules\Lpj\Models\Permohonan;
|
||||||
use Modules\Lpj\Models\Inspeksi;
|
use Modules\Lpj\Models\Inspeksi;
|
||||||
use Modules\Lpj\Models\Penilai;
|
use Modules\Lpj\Models\Penilai;
|
||||||
@@ -801,6 +802,18 @@ class PenilaiController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function uploadTempPhoto(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->hasFile('file')) {
|
||||||
|
$file = $request->file('file');
|
||||||
|
$filename = time() . '_' . $file->getClientOriginalName();
|
||||||
|
$path = $file->storeAs('temp_photos', $filename, 'public');
|
||||||
|
return response()->json(['success' => true, 'id' => $path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['success' => false], 400);
|
||||||
|
}
|
||||||
|
|
||||||
public function storeResume(Request $request)
|
public function storeResume(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -919,6 +932,59 @@ class PenilaiController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function storeMemoWithPhotos(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validatedData = $request->validate([
|
||||||
|
'permohonan_id' => 'required|integer',
|
||||||
|
'document_id' => 'required|integer',
|
||||||
|
'inspeksi_id' => 'required|integer',
|
||||||
|
'memo' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$memoData = json_decode($validatedData['memo'], true);
|
||||||
|
|
||||||
|
$memo = Penilai::updateOrCreate(
|
||||||
|
[
|
||||||
|
'permohonan_id' => $validatedData['permohonan_id'],
|
||||||
|
'dokument_id' => $validatedData['document_id'],
|
||||||
|
'inspeksi_id' => $validatedData['inspeksi_id'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'memo' => json_encode($memoData),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simpan foto-foto
|
||||||
|
if ($request->hasFile('foto_0')) {
|
||||||
|
$photoUrls = [];
|
||||||
|
$index = 0;
|
||||||
|
while ($request->hasFile("foto_$index")) {
|
||||||
|
$file = $request->file("foto_$index");
|
||||||
|
$fileName = time() . '_' . $file->getClientOriginalName();
|
||||||
|
$filePath = $file->storeAs('public/memo_photos', $fileName);
|
||||||
|
$photoUrls[] = Storage::url($filePath);
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan URL foto ke data memo
|
||||||
|
$memoData['foto'] = $photoUrls;
|
||||||
|
$memo->memo = json_encode($memoData);
|
||||||
|
$memo->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Memo dan foto berhasil disimpan',
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function storeLpjSederhanadanStandard(Request $request)
|
public function storeLpjSederhanadanStandard(Request $request)
|
||||||
{
|
{
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|||||||
@@ -295,7 +295,7 @@
|
|||||||
<h1 class="text-md font-medium text-gray-900">Upload Foto</h1>
|
<h1 class="text-md font-medium text-gray-900">Upload Foto</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dropzone" id="upload-dropzone">
|
<div class="dropzone" id="dropzone-upload">
|
||||||
<div class="dz-message needsclick" data-foto-type="upload_foto">
|
<div class="dz-message needsclick" data-foto-type="upload_foto">
|
||||||
<i class="ki-duotone ki-file-up text-primary text-3xl"><span class="path1"></span><span
|
<i class="ki-duotone ki-file-up text-primary text-3xl"><span class="path1"></span><span
|
||||||
class="path2"></span></i>
|
class="path2"></span></i>
|
||||||
@@ -306,6 +306,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div id="existing-photos" class="flex gap-5"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- @include('lpj::penilai.components.foto-lampiran') --}}
|
{{-- @include('lpj::penilai.components.foto-lampiran') --}}
|
||||||
@@ -337,7 +340,75 @@
|
|||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@include('lpj::surveyor.js.utils')
|
@include('lpj::surveyor.js.utils')
|
||||||
|
@push('scripts')
|
||||||
<script>
|
<script>
|
||||||
|
Dropzone.autoDiscover = false;
|
||||||
|
let myDropzone;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
myDropzone = new Dropzone("#dropzone-upload", {
|
||||||
|
url: "{{ route('penilai.uploadTempPhoto') }}", // Temporary upload route
|
||||||
|
paramName: "file",
|
||||||
|
maxFilesize: 5, // MB
|
||||||
|
acceptedFiles: "image/*",
|
||||||
|
addRemoveLinks: true,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
init: function() {
|
||||||
|
this.on("success", function(file, response) {
|
||||||
|
file.serverId = response.id; // Store the server's file ID
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load existing photos
|
||||||
|
loadExistingPhotos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadExistingPhotos() {
|
||||||
|
const existingPhotosContainer = document.getElementById('existing-photos');
|
||||||
|
if (!existingPhotosContainer) return;
|
||||||
|
|
||||||
|
@if(isset($memo) && isset($memo->foto))
|
||||||
|
let existingPhotos;
|
||||||
|
try {
|
||||||
|
existingPhotos = @json($memo->foto);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing existing photos:', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(existingPhotos)) {
|
||||||
|
existingPhotos.forEach(function(photoPath) {
|
||||||
|
if (typeof photoPath === 'string') {
|
||||||
|
const photoDiv = document.createElement('div');
|
||||||
|
photoDiv.className = 'col-md-3 mb-3';
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = photoPath;
|
||||||
|
img.className = 'img-fluid';
|
||||||
|
img.style.maxHeight = '150px';
|
||||||
|
|
||||||
|
photoDiv.appendChild(img);
|
||||||
|
existingPhotosContainer.appendChild(photoDiv);
|
||||||
|
|
||||||
|
if (myDropzone) {
|
||||||
|
let mockFile = { name: photoPath.split('/').pop(), size: 12345 };
|
||||||
|
myDropzone.emit("addedfile", mockFile);
|
||||||
|
myDropzone.emit("thumbnail", mockFile, photoPath);
|
||||||
|
myDropzone.emit("complete", mockFile);
|
||||||
|
mockFile.previewElement.classList.add("dz-success");
|
||||||
|
mockFile.previewElement.classList.add("dz-complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Existing photos is not an array:', existingPhotos);
|
||||||
|
}
|
||||||
|
@endif
|
||||||
|
}
|
||||||
|
|
||||||
function saveMemo() {
|
function saveMemo() {
|
||||||
const form = document.getElementById('form-memo');
|
const form = document.getElementById('form-memo');
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
@@ -371,17 +442,26 @@
|
|||||||
const documentId = urlParams.get('documentId');
|
const documentId = urlParams.get('documentId');
|
||||||
const inspeksiId = urlParams.get('inspeksiId');
|
const inspeksiId = urlParams.get('inspeksiId');
|
||||||
|
|
||||||
const requestUrl = `{{ route('penilai.storeMemo') }}`;
|
// Create a new FormData object to send both JSON and files
|
||||||
|
const sendFormData = new FormData();
|
||||||
|
sendFormData.append('permohonan_id', permohonanId);
|
||||||
|
sendFormData.append('document_id', documentId);
|
||||||
|
sendFormData.append('inspeksi_id', inspeksiId);
|
||||||
|
sendFormData.append('memo', JSON.stringify(jsonData));
|
||||||
|
|
||||||
|
// Append all files from Dropzone
|
||||||
|
myDropzone.getAcceptedFiles().forEach((file, index) => {
|
||||||
|
sendFormData.append(`foto_${index}`, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestUrl = `{{ route('penilai.storeMemoWithPhotos') }}`;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({
|
data: sendFormData,
|
||||||
permohonan_id: permohonanId,
|
processData: false,
|
||||||
document_id: documentId,
|
contentType: false,
|
||||||
inspeksi_id: inspeksiId,
|
|
||||||
memo: jsonData,
|
|
||||||
}),
|
|
||||||
contentType: 'application/json',
|
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
},
|
},
|
||||||
@@ -409,17 +489,15 @@
|
|||||||
console.log(response);
|
console.log(response);
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
let errors = xhr.responseJSON?.errors;
|
|
||||||
$('.alert').text('');
|
|
||||||
if (errors) {
|
|
||||||
$.each(errors, function(key, value) {
|
|
||||||
$(`#error-${key}`).text(value[0]);
|
|
||||||
toastrErrorBuild(value[0]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
hideLoadingSwal();
|
hideLoadingSwal();
|
||||||
console.log(errors);
|
Swal.fire({
|
||||||
|
title: 'Error!',
|
||||||
|
text: 'Terjadi kesalahan saat mengirim data',
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: 'OK'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@endpush
|
||||||
|
|||||||
@@ -616,7 +616,11 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::post('/preoses-laporan/{id}', [PenilaiController::class, 'storePenilaian'])->name('proses.laporan');
|
Route::post('/preoses-laporan/{id}', [PenilaiController::class, 'storePenilaian'])->name('proses.laporan');
|
||||||
|
|
||||||
Route::post('storeResume', [PenilaiController::class, 'storeResume'])->name('storeResume');
|
Route::post('storeResume', [PenilaiController::class, 'storeResume'])->name('storeResume');
|
||||||
|
|
||||||
Route::post('storeMemo', [PenilaiController::class, 'storeMemo'])->name('storeMemo');
|
Route::post('storeMemo', [PenilaiController::class, 'storeMemo'])->name('storeMemo');
|
||||||
|
Route::post('store-memo-with-photos', [PenilaiController::class, 'storeMemoWithPhotos'])->name('storeMemoWithPhotos');
|
||||||
|
Route::post('upload-temp-photo', [PenilaiController::class, 'uploadTempPhoto'])->name('uploadTempPhoto');
|
||||||
|
|
||||||
Route::post('storeRap', [PenilaiController::class, 'storeRap'])->name('storeRap');
|
Route::post('storeRap', [PenilaiController::class, 'storeRap'])->name('storeRap');
|
||||||
Route::post('storeLpjSederhanadanStandard', [PenilaiController::class, 'storeLpjSederhanadanStandard'])->name('storeLpjSederhanadanStandard');
|
Route::post('storeLpjSederhanadanStandard', [PenilaiController::class, 'storeLpjSederhanadanStandard'])->name('storeLpjSederhanadanStandard');
|
||||||
Route::put('revisi-surveyor/{id}', [PenilaiController::class, 'revisiSurveyor'])->name('revisiSurveyor');
|
Route::put('revisi-surveyor/{id}', [PenilaiController::class, 'revisiSurveyor'])->name('revisiSurveyor');
|
||||||
|
|||||||
Reference in New Issue
Block a user