perbaikan fominspeksi upload foto
This commit is contained in:
@@ -11,6 +11,7 @@ use Maatwebsite\Excel\Facades\Excel;
|
|||||||
use Modules\Lpj\Exports\BasicDataSurveyorExport;
|
use Modules\Lpj\Exports\BasicDataSurveyorExport;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Modules\Lpj\Models\Debiture;
|
use Modules\Lpj\Models\Debiture;
|
||||||
@@ -313,7 +314,6 @@ class SurveyorController extends Controller
|
|||||||
'permohonan_id' => $request->input('permohonan_id'),
|
'permohonan_id' => $request->input('permohonan_id'),
|
||||||
'dokument_id' => $request->input('dokument_id')
|
'dokument_id' => $request->input('dokument_id')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Get existing foto_form data if it exists
|
// Get existing foto_form data if it exists
|
||||||
$existingData = $inspeksi->exists && $inspeksi->foto_form
|
$existingData = $inspeksi->exists && $inspeksi->foto_form
|
||||||
? json_decode($inspeksi->foto_form, true)
|
? json_decode($inspeksi->foto_form, true)
|
||||||
@@ -321,6 +321,12 @@ class SurveyorController extends Controller
|
|||||||
|
|
||||||
$formatFotojson = $existingData;
|
$formatFotojson = $existingData;
|
||||||
|
|
||||||
|
// Upload berbagai jenis foto
|
||||||
|
$this->handleFileUpload($request, 'rute_menuju_lokasi', $formatFotojson);
|
||||||
|
$this->handleFileUpload($request, 'foto_lingkungan', $formatFotojson);
|
||||||
|
|
||||||
|
$this->processFotoLantaiUnit($request, $formatFotojson);
|
||||||
|
|
||||||
if ($request->hasFile('foto_objek')) {
|
if ($request->hasFile('foto_objek')) {
|
||||||
$existingObjekJaminan = $formatFotojson['object_jaminan'] ?? [];
|
$existingObjekJaminan = $formatFotojson['object_jaminan'] ?? [];
|
||||||
$formatFotojson['object_jaminan'] = $this->processObjekJaminanPhotos(
|
$formatFotojson['object_jaminan'] = $this->processObjekJaminanPhotos(
|
||||||
@@ -329,27 +335,6 @@ class SurveyorController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('foto_lantai_unit')) {
|
|
||||||
$existingLantaiUnit = $formatFotojson['foto_lantai_unit'] ?? [];
|
|
||||||
$newLantaiUnit = $this->processFotoLantaiUnit($request);
|
|
||||||
|
|
||||||
foreach ($newLantaiUnit as $key => $newFiles) {
|
|
||||||
$existingLantaiUnit[$key] = array_merge($existingLantaiUnit[$key] ?? [], $newFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
$formatFotojson['foto_lantai_unit'] = $existingLantaiUnit;
|
|
||||||
$log['foto_lantai_unit'] = $formatFotojson['foto_lantai_unit'];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($photoCategories as $key) {
|
|
||||||
if ($request->has($key)) {
|
|
||||||
$newPhotos = $this->processPhotoCategories($request, $key);
|
|
||||||
$existingPhotos = $formatFotojson[$key] ?? [];
|
|
||||||
$formatFotojson[$key] = array_merge($existingPhotos, $newPhotos);
|
|
||||||
$log[$key] = $formatFotojson[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($lainnya as $category => $fields) {
|
foreach ($lainnya as $category => $fields) {
|
||||||
$photoField = $fields[0];
|
$photoField = $fields[0];
|
||||||
$nameField = $fields[1];
|
$nameField = $fields[1];
|
||||||
@@ -365,7 +350,7 @@ class SurveyorController extends Controller
|
|||||||
$formatFotojson[$category] = $newPhotos;
|
$formatFotojson[$category] = $newPhotos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Process single files
|
// // Process single files
|
||||||
$singleFiles = ['foto_basement', 'foto_gerbang', 'pendamping'];
|
$singleFiles = ['foto_basement', 'foto_gerbang', 'pendamping'];
|
||||||
foreach ($singleFiles as $file) {
|
foreach ($singleFiles as $file) {
|
||||||
if ($request->hasFile($file)) {
|
if ($request->hasFile($file)) {
|
||||||
@@ -381,56 +366,251 @@ class SurveyorController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update record
|
// Update record
|
||||||
$inspeksi->foto_form = json_encode($formatFotojson);
|
if (!empty($formatFotojson)) {
|
||||||
$inspeksi->save();
|
$inspeksi->foto_form = json_encode($formatFotojson);
|
||||||
|
$inspeksi->save();
|
||||||
|
|
||||||
return response()->json(['success' => true, 'message' => 'Data berhasil disimpan', 'data' => $formatFotojson, 'log' => $log ], 200);
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Data berhasil disimpan',
|
||||||
|
'data' => $formatFotojson,
|
||||||
|
'log' => $log
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Tidak ada data untuk disimpan'
|
||||||
|
], 400);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return response()->json(['success' => false, 'message' => 'Failed to upload: ' . $e->getMessage()], 500);
|
return response()->json(['success' => false, 'message' => 'Failed to upload: ' . $e->getMessage()], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a photo category and its subcategories
|
* Process a photo
|
||||||
*/
|
*/
|
||||||
private function processPhotoCategories(Request $request, string $categoryKey)
|
protected function processFotoLantaiUnit(Request $request, &$formatFotojson)
|
||||||
{
|
{
|
||||||
$processedPhotos = [];
|
// Pastikan foto_lantai_unit sudah ada di formatFotojson
|
||||||
$categoryPhotos = $request->file($categoryKey, []);
|
if (!isset($formatFotojson['foto_lantai_unit'])) {
|
||||||
if (is_array($categoryPhotos)) {
|
$formatFotojson['foto_lantai_unit'] = [];
|
||||||
$categoryPhotos = array_merge(...array_values($categoryPhotos));
|
}
|
||||||
}
|
|
||||||
$namePrefix = str_replace('_', ' ', $categoryKey);
|
// Ambil nama-nama lantai dari request
|
||||||
|
$lantaiNama = $request->input('lantai_nama', []);
|
||||||
|
|
||||||
|
// Tambahan: gunakan lantai_index jika tersedia
|
||||||
|
$lantaiIndex = $request->input('lantai_index', null);
|
||||||
|
|
||||||
|
// Cek apakah ada file foto lantai yang diunggah
|
||||||
|
$lantaiFiles = $request->file('foto_lantai_unit', []);
|
||||||
|
|
||||||
|
// Proses setiap file foto lantai
|
||||||
|
foreach ($lantaiFiles as $index => $files) {
|
||||||
|
// Pastikan $files adalah array
|
||||||
|
if (!is_array($files)) {
|
||||||
|
$files = [$files];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gunakan lantai_index jika tersedia, jika tidak gunakan cara sebelumnya
|
||||||
|
$lantaiNomor = $lantaiIndex ?? ($index + 1);
|
||||||
|
|
||||||
|
// Inisialisasi array untuk lantai ini jika belum ada
|
||||||
|
if (!isset($formatFotojson['foto_lantai_unit'][$lantaiNomor])) {
|
||||||
|
$formatFotojson['foto_lantai_unit'][$lantaiNomor] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($files as $fileIndex => $file) {
|
||||||
|
// Validasi file
|
||||||
|
if (!$file->isValid()) {
|
||||||
|
continue; // Lewati file yang tidak valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate nama file unik
|
||||||
|
$uniqueFileName = 'lantai_unit_' . $lantaiNomor . '_' . $fileIndex . '_' . Str::random(10) . '.' . $file->getClientOriginalExtension();
|
||||||
|
|
||||||
|
// Simpan file dengan nama asli
|
||||||
|
$path = $file->storeAs(
|
||||||
|
'surveyor/lantai_unit',
|
||||||
|
$uniqueFileName . '/' . time() . '_' . $file->getClientOriginalName(),
|
||||||
|
'public'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buat nama foto
|
||||||
|
$fotoName = "Foto Lantai {$lantaiNomor} - " . ($fileIndex + 1);
|
||||||
|
|
||||||
|
// Tambahkan detail foto ke array
|
||||||
|
$fotoDetail = [
|
||||||
|
'path' => $path,
|
||||||
|
'name' => $fotoName
|
||||||
|
];
|
||||||
|
|
||||||
|
// Tambahkan ke array foto lantai unit sesuai struktur
|
||||||
|
$formatFotojson['foto_lantai_unit'][$lantaiNomor][] = $fotoDetail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $formatFotojson;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($categoryPhotos as $index => $file) {
|
private function handleFileUpload(Request $request, $paramName, &$formatFotojson)
|
||||||
// Validate the file
|
{
|
||||||
if (!$file instanceof \Illuminate\Http\UploadedFile || !$file->isValid()) {
|
if ($request->hasFile($paramName)) {
|
||||||
continue;
|
$files = $request->file($paramName);
|
||||||
|
|
||||||
|
// Pastikan $files adalah array
|
||||||
|
if (!is_array($files)) {
|
||||||
|
$files = [$files];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a unique filename for the photo
|
$formatFotoData = [];
|
||||||
$filename = $this->generateUniqueFileName($file, "{$categoryKey}_{$index}");
|
|
||||||
|
|
||||||
if (isset($processedPhotos[$categoryKey])) {
|
foreach ($files as $index => $file) {
|
||||||
$processedPhotos[$categoryKey][0][] = [
|
$timestamp = time();
|
||||||
'path' => $this->uploadFile($file, $filename),
|
$originalName = $file->getClientOriginalName();
|
||||||
'name' => ucfirst($namePrefix) . ' - ' . ($index + 1),
|
$uniqueFileName = "{$timestamp}_{$originalName}";
|
||||||
|
|
||||||
|
// Simpan file
|
||||||
|
$path = $file->storeAs("surveyor/{$paramName}", $uniqueFileName, 'public');
|
||||||
|
|
||||||
|
$fotoData = [
|
||||||
|
'path' => $path,
|
||||||
|
'name' => ucwords(str_replace('_', ' ', $paramName)) . ' - ' . ($index + 1)
|
||||||
];
|
];
|
||||||
} else {
|
|
||||||
$processedPhotos[$categoryKey] = [
|
$formatFotoData[] = $fotoData;
|
||||||
[
|
}
|
||||||
[
|
|
||||||
'path' => $this->uploadFile($file, $filename),
|
// Struktur JSON yang konsisten
|
||||||
'name' => ucfirst($namePrefix) . ' - ' . ($index + 1),
|
if (!isset($formatFotojson[$paramName][$paramName][0])) {
|
||||||
]
|
$formatFotojson[$paramName] = [
|
||||||
|
$paramName => [
|
||||||
|
$formatFotoData
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
$formatFotojson[$paramName][$paramName][0] = array_merge(
|
||||||
|
$formatFotojson[$paramName][$paramName][0] ?? [],
|
||||||
|
$formatFotoData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $formatFotoData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hapusFoto(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$permohonanId = $request->permohonan_id;
|
||||||
|
$dokumentId = $request->dokument_id;
|
||||||
|
$paramName = $request->param_name;
|
||||||
|
|
||||||
|
$cleanRequestPath = str_replace('/storage/', '', $request->path);
|
||||||
|
|
||||||
|
$inspeksi = Inspeksi::firstOrNew(
|
||||||
|
['permohonan_id' => $permohonanId, 'dokument_id' => $dokumentId]
|
||||||
|
);
|
||||||
|
$fotoForm = json_decode($inspeksi->foto_form, true);
|
||||||
|
|
||||||
|
if (isset($fotoForm[$paramName][$paramName][0])) {
|
||||||
|
// Filter foto yang akan dihapus
|
||||||
|
|
||||||
|
$tes = null;
|
||||||
|
$fotoForm[$paramName][$paramName][0] = array_values(
|
||||||
|
array_filter(
|
||||||
|
$fotoForm[$paramName][$paramName][0],
|
||||||
|
function ($foto) use ($cleanRequestPath) {
|
||||||
|
if ($foto['path'] === $cleanRequestPath) {
|
||||||
|
// Hapus file dari storage
|
||||||
|
\Storage::disk('public')->delete($cleanRequestPath);
|
||||||
|
return false; // Hapus elemen ini dari array
|
||||||
|
}
|
||||||
|
$tes = $foto['path'];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Reset array index
|
||||||
|
$fotoForm[$paramName][$paramName][0] = array_values($fotoForm[$paramName][$paramName][0]);
|
||||||
|
|
||||||
|
$inspeksi->foto_form = $fotoForm;
|
||||||
|
$inspeksi->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Foto berhasil dihapus',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['success' => false], 400);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan foto yang sudah diupload
|
||||||
|
public function getFoto(Request $request)
|
||||||
|
{
|
||||||
|
$permohonanId = $request->permohonan_id;
|
||||||
|
$dokumentId = $request->dokument_id;
|
||||||
|
$paramName = $request->param_name;
|
||||||
|
|
||||||
|
$inspeksi = Inspeksi::firstOrNew(
|
||||||
|
['permohonan_id' => $permohonanId, 'dokument_id' => $dokumentId]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decode foto_form
|
||||||
|
$fotoForm = json_decode($inspeksi->foto_form, true) ?? [];
|
||||||
|
|
||||||
|
// Cari foto berdasarkan param name
|
||||||
|
$fotos = $this->findFotoByParamName($fotoForm, $paramName);
|
||||||
|
|
||||||
|
|
||||||
|
$param = $paramName == 'rute_menuju_lokasi' ? $fotos['rute_menuju_lokasi'][0] : $fotos['foto_lingkungan'][0];
|
||||||
|
$data = array_map(function ($foto) {
|
||||||
|
return [
|
||||||
|
'name' => $foto['name'] ?? 'Foto',
|
||||||
|
'path' => \Storage::url($foto['path']),
|
||||||
|
];
|
||||||
|
}, $param);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'fotos' => $data,
|
||||||
|
'success' => !empty($data)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findFotoByParamName($fotoForm, $paramName)
|
||||||
|
{
|
||||||
|
// Mapping parameter name ke struktur JSON
|
||||||
|
$paramMapping = [
|
||||||
|
'rute_menuju_lokasi' => ['rute_menuju_lokasi'],
|
||||||
|
'foto_lingkungan' => ['foto_lingkungan'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// // Traverse array menggunakan mapping
|
||||||
|
$fotos = $fotoForm;
|
||||||
|
if (isset($paramMapping[$paramName])) {
|
||||||
|
foreach ($paramMapping[$paramName] as $key) {
|
||||||
|
$fotos = $fotos[$key] ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $processedPhotos;
|
return is_array($fotos) ? $fotos : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function processObjekJaminanPhotos(Request $request, array $existingPhotos = [])
|
private function processObjekJaminanPhotos(Request $request, array $existingPhotos = [])
|
||||||
{
|
{
|
||||||
$photoField = 'foto_objek';
|
$photoField = 'foto_objek';
|
||||||
@@ -486,34 +666,79 @@ class SurveyorController extends Controller
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processFotoLantaiUnit(Request $request)
|
public function hapusLantai(Request $request)
|
||||||
{
|
{
|
||||||
$processedFotoLantaiUnit = [];
|
$permohonanId = $request->permohonan_id;
|
||||||
$fotoLantaiUnit = $request->file('foto_lantai_unit', []);
|
$dokumentId = $request->dokument_id;
|
||||||
|
|
||||||
|
// Normalisasi path foto
|
||||||
|
$cleanRequestPath = str_replace(['storage/', 'surveyor/'], '', $request->foto_path);
|
||||||
|
|
||||||
|
$inspeksi = Inspeksi::firstOrNew(
|
||||||
|
['permohonan_id' => $permohonanId, 'dokument_id' => $dokumentId]
|
||||||
|
);
|
||||||
|
$fotoLantaiUnit = json_decode($inspeksi->foto_form, true);
|
||||||
|
|
||||||
foreach ($fotoLantaiUnit as $lantaiKey => $files) {
|
// Konversi lantai ke string untuk konsistensi
|
||||||
$processedFiles = [];
|
$lantai = (string)$request->lantai;
|
||||||
|
|
||||||
foreach ($files as $index => $file) {
|
// Cek apakah lantai ada di array
|
||||||
if (!$file || !$file->isValid()) {
|
$pat = null;
|
||||||
continue;
|
if (isset($fotoLantaiUnit['foto_lantai_unit'][$lantai])) {
|
||||||
|
// Filter foto, hapus foto yang sesuai
|
||||||
|
$fotoLantaiUnit['foto_lantai_unit'][$lantai] = array_filter(
|
||||||
|
$fotoLantaiUnit['foto_lantai_unit'][$lantai],
|
||||||
|
function ($foto) use ($cleanRequestPath) {
|
||||||
|
// Normalisasi path foto yang tersimpan
|
||||||
|
$storedPath = str_replace(['storage/', 'surveyor/'], '', $foto['path']);
|
||||||
|
|
||||||
|
// Jika path cocok, hapus file dari storage
|
||||||
|
if ($storedPath === $cleanRequestPath) {
|
||||||
|
// Hapus file dari storage dengan berbagai kemungkinan path
|
||||||
|
$possiblePaths = [
|
||||||
|
'storage/surveyor/lantai_unit/' . $cleanRequestPath,
|
||||||
|
'storage/surveyor/' . $cleanRequestPath,
|
||||||
|
'storage/' . $cleanRequestPath,
|
||||||
|
$cleanRequestPath
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($possiblePaths as $path) {
|
||||||
|
if (Storage::disk('public')->exists($path)) {
|
||||||
|
Storage::disk('public')->delete($path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Hapus dari array
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$filename = $this->generateUniqueFileName($file, "lantai_unit_{$lantaiKey}_{$index}");
|
// Reset index array
|
||||||
$processedFiles[] = [
|
$fotoLantaiUnit['foto_lantai_unit'][$lantai] = array_values($fotoLantaiUnit['foto_lantai_unit'][$lantai]);
|
||||||
'path' => $this->uploadFile($file, $filename),
|
|
||||||
'name' => "Foto Lantai {$lantaiKey} - " . ($index + 1),
|
// Hapus lantai jika tidak ada foto
|
||||||
];
|
if (empty($fotoLantaiUnit['foto_lantai_unit'][$lantai])) {
|
||||||
}
|
unset($fotoLantaiUnit['foto_lantai_unit'][$lantai]);
|
||||||
|
|
||||||
if (!empty($processedFiles)) {
|
|
||||||
$processedFotoLantaiUnit[$lantaiKey] = $processedFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update inspeksi
|
||||||
|
$inspeksi->foto_form = json_encode($fotoLantaiUnit);
|
||||||
|
$inspeksi->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'foto_lantai_unit' => $pat
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $processedFotoLantaiUnit;
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'gagal menghapus'
|
||||||
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processPhotoLainnya(Request $request, array $fields, array $existingPhotos = [])
|
private function processPhotoLainnya(Request $request, array $fields, array $existingPhotos = [])
|
||||||
{
|
{
|
||||||
$result = $existingPhotos; // Start with existing photos
|
$result = $existingPhotos; // Start with existing photos
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if (isset($formFoto['foto_lantai_lainnya']))
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header" id="basic_settings">
|
<div class="card-header" id="basic_settings">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
@@ -181,6 +182,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if (isset($formFoto['foto_gerbang']))
|
@if (isset($formFoto['foto_gerbang']))
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -239,6 +241,25 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@php
|
||||||
|
$fotoTypes = ['foto_gistaru', 'foto_bhumi', 'foto_argis_region', 'foto_tempat'];
|
||||||
|
if (($key = array_search('foto_tempat', $fotoTypes)) !== false) {
|
||||||
|
unset($fotoTypes[$key]);
|
||||||
|
array_unshift($fotoTypes, 'foto_tempat');
|
||||||
|
}
|
||||||
|
$adaFoto = false;
|
||||||
|
if (isset($forminspeksi)) {
|
||||||
|
foreach ($fotoTypes as $type) {
|
||||||
|
$imagePath = $forminspeksi[$type] ?? null;
|
||||||
|
if ($imagePath && file_exists(storage_path('app/public/' . $imagePath))) {
|
||||||
|
$adaFoto = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if ($adaFoto)
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-agi-50" id="basic_settings">
|
<div class="card-header bg-agi-50" id="basic_settings">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
@@ -246,13 +267,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@php
|
|
||||||
$fotoTypes = ['foto_gistaru', 'foto_bhumi', 'foto_argis_region', 'foto_tempat'];
|
|
||||||
if (($key = array_search('foto_tempat', $fotoTypes)) !== false) {
|
|
||||||
unset($fotoTypes[$key]);
|
|
||||||
array_unshift($fotoTypes, 'foto_tempat');
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
<div>
|
<div>
|
||||||
@if (isset($forminspeksi))
|
@if (isset($forminspeksi))
|
||||||
@forelse ($fotoTypes as $type)
|
@forelse ($fotoTypes as $type)
|
||||||
@@ -274,3 +289,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
@@ -29,13 +29,17 @@
|
|||||||
$fileName = basename($penilai->kertas_kerja);
|
$fileName = basename($penilai->kertas_kerja);
|
||||||
}
|
}
|
||||||
@endphp
|
@endphp
|
||||||
<button type="button" class="btn btn-primary" onclick="window.open('{{ $url }}', '_blank')">
|
|
||||||
<i class="ki-outline ki-cloud-download"></i>
|
|
||||||
Lihat Kertas Kerja</button>
|
|
||||||
|
|
||||||
<p class="text-2sm text-gray-700">{{ $fileName }}</p>
|
@if ($penilai && $penilai->kertas_kerja)
|
||||||
|
<button type="button" class="btn btn-primary" onclick="window.open('{{ $url }}', '_blank')">
|
||||||
|
<i class="ki-outline ki-cloud-download"></i>
|
||||||
|
Lihat Kertas Kerja</button>
|
||||||
|
|
||||||
|
<p class="text-2sm text-gray-700">{{ $fileName }}</p>
|
||||||
|
@else
|
||||||
|
<p class="text-2sm text-gray-700">Belum ada kertas kerja</p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,76 @@
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@include('lpj::assetsku.includenya')
|
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
|
||||||
|
<style>
|
||||||
|
.dropzone {
|
||||||
|
border: 2px dashed #3498db;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-message {
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview {
|
||||||
|
margin: 10px;
|
||||||
|
width: 17rem;
|
||||||
|
height: 17rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone .dz-preview .dz-details {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HTML: <div class="loader"></div> */
|
||||||
|
.loader {
|
||||||
|
width: 60px;
|
||||||
|
aspect-ratio: 2;
|
||||||
|
--_g: no-repeat radial-gradient(circle closest-side, #35C1D0 90%, #0000);
|
||||||
|
background:
|
||||||
|
var(--_g) 0% 50%,
|
||||||
|
var(--_g) 50% 50%,
|
||||||
|
var(--_g) 100% 50%;
|
||||||
|
background-size: calc(100%/3) 50%;
|
||||||
|
animation: l3 1s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes l3 {
|
||||||
|
20% {
|
||||||
|
background-position: 0% 0%, 50% 50%, 100% 50%
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
background-position: 0% 100%, 50% 0%, 100% 50%
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
background-position: 0% 50%, 50% 100%, 100% 0%
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
background-position: 0% 50%, 50% 50%, 100% 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<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 border border-agi-100 min-w-full">
|
<div class="card border border-agi-100 min-w-full">
|
||||||
<div class="card border border-agi-100 min-w-full">
|
<div class="card border border-agi-100 min-w-full">
|
||||||
@@ -78,19 +147,21 @@
|
|||||||
<input type="hidden" name="permohonan_id" value="{{ $permohonan->id }}">
|
<input type="hidden" name="permohonan_id" value="{{ $permohonan->id }}">
|
||||||
<input type="hidden" name="dokument_id" value="{{ request('dokument') }}">
|
<input type="hidden" name="dokument_id" value="{{ request('dokument') }}">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="card border border-agi-100 bg-white rounded-lg shadow-md">
|
<div class="card border border-agi-100 bg-white rounded-lg shadow-md">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class=" py-4 flex items-center justify-between">
|
<div class=" py-4 flex items-center justify-between">
|
||||||
<h1 class="text-md font-medium text-gray-900">Rute Menuju Lokasi</h1>
|
<h1 class="text-md font-medium text-gray-900">Rute Menuju Lokasi</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dropzone" id="rute-dropzone">
|
||||||
<div id="inputContainerRute" style="margin-top: 10px">
|
<div class="dz-message" data-foto-type="rute_menuju_lokasi">
|
||||||
|
<span>Seret dan lepas file di sini atau klik untuk unggah</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="inputContainerGerbang" style="margin-top: 10px">
|
<div id="inputContainerGerbang" style="margin-top: 10px">
|
||||||
<div class="flex items-baseline flex-wrap lg:flex-nowrap w-full gap-4 mb-4">
|
<div class="flex items-baseline flex-wrap lg:flex-nowrap w-full gap-4 mb-4">
|
||||||
<label class="form-label max-w-56">
|
<label class="form-label max-w-56">
|
||||||
@@ -104,7 +175,8 @@
|
|||||||
style="{{ isset($formFoto['foto_gerbang']) ? '' : 'display: none;' }} width: 30rem;">
|
style="{{ isset($formFoto['foto_gerbang']) ? '' : 'display: none;' }} width: 30rem;">
|
||||||
<div class="input-group w-full flex gap-2">
|
<div class="input-group w-full flex gap-2">
|
||||||
<input id="inputRute" type="file" name="foto_gerbang"
|
<input id="inputRute" type="file" name="foto_gerbang"
|
||||||
class="file-input file-input-bordered w-full" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
class="file-input file-input-bordered w-full"
|
||||||
|
accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
||||||
onchange="previewImage(this, 'foto_gerbang-preview')">
|
onchange="previewImage(this, 'foto_gerbang-preview')">
|
||||||
<button type="button" id="btnCamera" class="btn btn-light"
|
<button type="button" id="btnCamera" class="btn btn-light"
|
||||||
data-modal-toggle="#cameraModal">
|
data-modal-toggle="#cameraModal">
|
||||||
@@ -137,7 +209,8 @@
|
|||||||
<div class="input-group w-full flex gap-2">
|
<div class="input-group w-full flex gap-2">
|
||||||
<input type="file" name="foto_rute_lainnya[]"
|
<input type="file" name="foto_rute_lainnya[]"
|
||||||
class="file-input file-input-bordered w-full"
|
class="file-input file-input-bordered w-full"
|
||||||
value="{{ $photo['foto_rute_lainnya'] }}" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
value="{{ $photo['foto_rute_lainnya'] }}"
|
||||||
|
accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
||||||
onchange="previewImage(this, 'foto_rute_lainnya-preview-{{ $index }}')">
|
onchange="previewImage(this, 'foto_rute_lainnya-preview-{{ $index }}')">
|
||||||
<button type="button" id="btnCamera-{{ $index }}"
|
<button type="button" id="btnCamera-{{ $index }}"
|
||||||
class="btn btn-light" data-modal-toggle="#cameraModal">
|
class="btn btn-light" data-modal-toggle="#cameraModal">
|
||||||
@@ -156,8 +229,7 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary btn-sm" id="btnAddMore"
|
<button type="button" class="btn btn-primary btn-sm" id="btnAddMore" style="margin-top: 10px">
|
||||||
style="margin-top: 10px">
|
|
||||||
<i class="ki-outline ki-plus text-2sm"></i> Lainnya
|
<i class="ki-outline ki-plus text-2sm"></i> Lainnya
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -251,25 +323,36 @@
|
|||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4 w-full">
|
<div id="lantaiContainer" class="mt-2">
|
||||||
<div class="w-full">
|
<!-- Lantai akan dinamis ditambahkan di sini -->
|
||||||
<div class=" py-4 flex items-center justify-between w-full">
|
<div class="lantai-item mb-4" id="lantai-item-1">
|
||||||
<label class="form-label">
|
|
||||||
<span class="form-label">Lantai</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
</div>
|
<div class=" d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Lantai 1</h5>
|
||||||
<div id="lantaiContainer" class="w-full">
|
<input type="hidden" name="lantai_nama[]" value="lantai_1">
|
||||||
</div>
|
<button type="button" class="btn btn-danger btn-sm btn-remove-lantai" data-id="1"
|
||||||
<div class="py-4 flex items-center justify-end w-full">
|
style="display:none;">
|
||||||
<button type="button" id="btnAddLantai" class="btn btn-primary btn-sm">
|
<i class="fa fa-trash"></i> Hapus Lantai
|
||||||
<i class="ki-filled ki-plus text-lg"></i> Tambah Lantai
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="dropzone" id="dropzone-lantai-1">
|
||||||
|
<div class="dz-message">
|
||||||
|
Seret dan lepas file di sini atau klik untuk unggah
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="button" id="btnAddLantai" class="btn btn-primary">
|
||||||
|
<i class="fa fa-plus"></i> Tambah Lantai
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="inputContainerBasement" class="w-full" style="margin-top: 10px">
|
<div id="inputContainerBasement" class="w-full" style="margin-top: 10px">
|
||||||
<div class="flex items-baseline flex-wrap lg:flex-nowrap w-full gap-4">
|
<div class="flex items-baseline flex-wrap lg:flex-nowrap w-full gap-4">
|
||||||
<label class="form-label max-w-56">
|
<label class="form-label max-w-56">
|
||||||
@@ -283,7 +366,8 @@
|
|||||||
onchange="previewImage(this, 'foto_basement_preview')">
|
onchange="previewImage(this, 'foto_basement_preview')">
|
||||||
<div class="input-group w-full flex gap-2">
|
<div class="input-group w-full flex gap-2">
|
||||||
<input id="inputBasement" type="file" name="foto_basement"
|
<input id="inputBasement" type="file" name="foto_basement"
|
||||||
class="file-input file-input-bordered w-full" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
class="file-input file-input-bordered w-full"
|
||||||
|
accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
||||||
onchange="previewImage(this, 'foto_basement_preview')" capture="camera">
|
onchange="previewImage(this, 'foto_basement_preview')" capture="camera">
|
||||||
<button type="button" id="btnCamera" class="btn btn-light"
|
<button type="button" id="btnCamera" class="btn btn-light"
|
||||||
data-modal-toggle="#cameraModal">
|
data-modal-toggle="#cameraModal">
|
||||||
@@ -315,11 +399,16 @@
|
|||||||
<h1 class="text-md font-medium text-gray-900">Lingkungan</h1>
|
<h1 class="text-md font-medium text-gray-900">Lingkungan</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="inputContainerLingkungan" style="margin-top: 10px">
|
<div class="dropzone" id="lingkungan-dropzone">
|
||||||
|
<div class="dz-message" data-foto-type="foto_lingkungan">
|
||||||
|
<span>Seret dan lepas file di sini atau klik untuk unggah</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="card rounded-lg shadow-md">
|
<div class="card rounded-lg shadow-md">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class=" py-4 flex items-center justify-between">
|
<div class=" py-4 flex items-center justify-between">
|
||||||
@@ -336,7 +425,8 @@
|
|||||||
|
|
||||||
<div class="input-group w-full flex gap-2">
|
<div class="input-group w-full flex gap-2">
|
||||||
<input id="inputPendamping" type="file" name="pendamping"
|
<input id="inputPendamping" type="file" name="pendamping"
|
||||||
class="file-input file-input-bordered w-full" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
class="file-input file-input-bordered w-full"
|
||||||
|
accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff,.tif,.webp,.svg"
|
||||||
onchange="previewImage(this, 'pendamping')" capture="camera">
|
onchange="previewImage(this, 'pendamping')" capture="camera">
|
||||||
<button type="button" id="btnCamera" class="btn btn-light"
|
<button type="button" id="btnCamera" class="btn btn-light"
|
||||||
data-modal-toggle="#cameraModal">
|
data-modal-toggle="#cameraModal">
|
||||||
@@ -372,73 +462,569 @@
|
|||||||
@include('lpj::surveyor.js.fotojs')
|
@include('lpj::surveyor.js.fotojs')
|
||||||
@include('lpj::surveyor.js.utils')
|
@include('lpj::surveyor.js.utils')
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
|
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// console.log('@json($formFoto)');
|
// console.log('@json($formFoto)');
|
||||||
|
let jsonDataContoh = @json($formFoto);
|
||||||
|
Dropzone.autoDiscover = false;
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
function initDropzone(selector, paramName) {
|
||||||
|
try {
|
||||||
|
// Pastikan elemen ada sebelum membuat Dropzone
|
||||||
|
const dropzoneElement = document.querySelector(selector);
|
||||||
|
if (!dropzoneElement) {
|
||||||
|
console.error(`Dropzone element not found: ${selector}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Inisialisasi saat dokumen siap
|
return new Dropzone(selector, {
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
url: "{{ route('surveyor.storeFoto') }}",
|
||||||
// Transform and initialize files for each category
|
paramName: paramName,
|
||||||
|
maxFilesize: 5,
|
||||||
|
acceptedFiles: 'image/*',
|
||||||
|
uploadMultiple: false,
|
||||||
|
parallelUploads: 1,
|
||||||
|
maxFiles: 5,
|
||||||
|
addRemoveLinks: true,
|
||||||
|
autoProcessQueue: true,
|
||||||
|
dictRemoveFile: 'Hapus',
|
||||||
|
dictDefaultMessage: 'Seret foto atau klik untuk unggah',
|
||||||
|
|
||||||
// Foto Lantai Unit
|
headers: {
|
||||||
const existingFilesLantai = @json($formFoto['foto_lantai_unit'] ?? []);
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
const processedFilesLantai = Object.entries(existingFilesLantai || {}).flatMap(([lantaiIndex, files]) =>
|
},
|
||||||
files.map(file => ({
|
|
||||||
...file,
|
|
||||||
item: lantaiIndex,
|
|
||||||
url: file.path ? `/storage/${file.path}` : null
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const lantaiUploader = new DynamicFileUploader({
|
params: {
|
||||||
containerId: 'lantaiContainer',
|
permohonan_id: {{ $permohonan->id ?? 0 }},
|
||||||
addButtonId: 'btnAddLantai',
|
dokument_id: '{{ request('dokument') ?? '' }}',
|
||||||
name: 'Lantai',
|
param_name: paramName
|
||||||
fileInputName: 'foto_lantai_unit',
|
},
|
||||||
existingFiles: processedFilesLantai,
|
|
||||||
maxFiles: 10,
|
|
||||||
accept: 'image/*',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Foto Lingkungan
|
error: function(file, response) {
|
||||||
const existingFilesLingkungan = @json($formFoto['foto_lingkungan']['foto_lingkungan'] ?? []);
|
console.error('Upload error:', response);
|
||||||
const processedFilesLingkungan = existingFilesLingkungan.flatMap(files =>
|
Swal.fire({
|
||||||
files.map(file => ({
|
icon: 'error',
|
||||||
...file,
|
title: 'Upload Gagal',
|
||||||
url: file.path ? `/storage/${file.path}` : null
|
text: response.message || 'Error tidak diketahui'
|
||||||
}))
|
});
|
||||||
);
|
},
|
||||||
|
|
||||||
const lingkunganUploader = new DynamicFileUploader({
|
success: function(file, response) {
|
||||||
containerId: 'inputContainerLingkungan',
|
if (response.success) {
|
||||||
fileInputName: 'foto_lingkungan',
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Upload Berhasil',
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
existingFiles: processedFilesLingkungan,
|
removedfile: function(file) {
|
||||||
maxFiles: 10,
|
if (file.originalPath) {
|
||||||
accept: 'image/*',
|
$.ajax({
|
||||||
});
|
url: "{{ route('surveyor.hapusFoto') }}",
|
||||||
|
method: 'DELETE',
|
||||||
|
data: {
|
||||||
|
path: file.originalPath,
|
||||||
|
permohonan_id: {{ $permohonan->id ?? 0 }},
|
||||||
|
dokument_id: '{{ request('dokument') ?? '' }}',
|
||||||
|
param_name: paramName
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
success: function(file, response) {
|
||||||
|
if (response.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Hapus Foto Berhasil',
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.previewElement) {
|
||||||
|
file.previewElement.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
var myDropzone = this;
|
||||||
|
|
||||||
|
var loadingIndicator = $(`
|
||||||
|
<div class="loading-overlay" style="
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255,255,255,0.7);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
">
|
||||||
|
<div class="loader"></div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
var $dropzoneElement = $(selector);
|
||||||
|
$dropzoneElement.css('position', 'relative');
|
||||||
|
$dropzoneElement.append(loadingIndicator);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "{{ route('surveyor.getFoto') }}",
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
permohonan_id: {{ $permohonan->id ?? 0 }},
|
||||||
|
dokument_id: '{{ request('dokument') ?? '' }}',
|
||||||
|
param_name: paramName
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
beforeSend: function() {
|
||||||
|
// Pastikan loading indicator terlihat
|
||||||
|
loadingIndicator.show();
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.fotos && response.fotos.length) {
|
||||||
|
response.fotos.forEach(function(foto) {
|
||||||
|
var mockFile = {
|
||||||
|
name: foto.name,
|
||||||
|
size: foto.size || 12345,
|
||||||
|
originalPath: foto.path
|
||||||
|
};
|
||||||
|
|
||||||
|
myDropzone.emit("addedfile", mockFile);
|
||||||
|
myDropzone.emit("thumbnail", mockFile,
|
||||||
|
foto.path);
|
||||||
|
myDropzone.emit("complete", mockFile);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Tambahkan pesan jika tidak ada foto
|
||||||
|
console.log('Tidak ada foto yang ditemukan');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Gagal memuat foto:', error);
|
||||||
|
|
||||||
|
// Tampilkan pesan error
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal Memuat Foto',
|
||||||
|
text: 'Terjadi kesalahan saat mengambil foto. Silakan coba lagi.',
|
||||||
|
confirmButtonText: 'OK'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
// Sembunyikan loading indicator
|
||||||
|
loadingIndicator.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Dropzone initialization error:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inisialisasi Dropzone untuk elemen awal dengan pengecekan
|
||||||
|
function safeInitDropzone(selector, paramName) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const dropzone = initDropzone(selector, paramName);
|
||||||
|
if (!dropzone) {
|
||||||
|
console.error(`Failed to initialize Dropzone for ${selector}`);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inisialisasi dropzone untuk elemen awal
|
||||||
|
safeInitDropzone('#rute-dropzone', 'rute_menuju_lokasi');
|
||||||
|
safeInitDropzone('#lingkungan-dropzone', 'foto_lingkungan');
|
||||||
|
|
||||||
|
// Event listener untuk menambah lantai
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Rute Menuju Lokasi
|
const lantaiContainer = document.getElementById('lantaiContainer');
|
||||||
const existingFilesRute = @json($formFoto['rute_menuju_lokasi']['rute_menuju_lokasi'] ?? []);
|
const btnAddLantai = document.getElementById('btnAddLantai');
|
||||||
const processedFilesRute = existingFilesRute.flatMap(files =>
|
let lantaiCounter = 1;
|
||||||
files.map(file => ({
|
let fotoLantaiUnit = {}; // Objek untuk menyimpan foto per lantai
|
||||||
...file,
|
|
||||||
url: file.path ? `/storage/${file.path}` : null
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const ruteUploader = new DynamicFileUploader({
|
// Objek untuk menyimpan instance Dropzone
|
||||||
containerId: 'inputContainerRute',
|
const dropzoneInstances = {};
|
||||||
fileInputName: 'rute_menuju_lokasi',
|
|
||||||
existingFiles: processedFilesRute,
|
// Fungsi untuk memuat foto berdasarkan JSON
|
||||||
maxFiles: 10,
|
function loadFotoFromJSON(jsonData) {
|
||||||
accept: 'image/*',
|
if (jsonData && jsonData.foto_lantai_unit) {
|
||||||
|
fotoLantaiUnit = jsonData.foto_lantai_unit;
|
||||||
|
|
||||||
|
// Render foto untuk setiap lantai
|
||||||
|
Object.keys(fotoLantaiUnit).forEach(lantai => {
|
||||||
|
// Pastikan dropzone untuk lantai ini sudah dibuat
|
||||||
|
if (!dropzoneInstances[lantai]) {
|
||||||
|
// Jika belum ada, tambahkan lantai baru
|
||||||
|
while (lantaiCounter < lantai) {
|
||||||
|
const newLantaiElement = createLantaiElement();
|
||||||
|
lantaiContainer.appendChild(newLantaiElement);
|
||||||
|
initDropzonelantai(lantaiCounter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropzoneElement = document.querySelector(`#dropzone-lantai-${lantai}`);
|
||||||
|
if (dropzoneElement) {
|
||||||
|
var dzInstance = dropzoneInstances[lantai];
|
||||||
|
|
||||||
|
fotoLantaiUnit[lantai].forEach(foto => {
|
||||||
|
// Normalisasi path
|
||||||
|
let normalizedPath = foto.path;
|
||||||
|
|
||||||
|
// Hapus prefix 'surveyor/' jika ada
|
||||||
|
if (normalizedPath.startsWith('surveyor/')) {
|
||||||
|
normalizedPath = normalizedPath.replace('surveyor/', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Buat mock file untuk ditampilkan di Dropzone
|
||||||
|
var mockFile = {
|
||||||
|
name: foto.name || 'Foto Lantai',
|
||||||
|
size: foto.size || 12345,
|
||||||
|
path: normalizedPath, // Simpan path yang sudah dinormalisasi
|
||||||
|
originalPath: 'storage/surveyor/' + normalizedPath
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pastikan instance Dropzone ada
|
||||||
|
if (dzInstance) {
|
||||||
|
dzInstance.emit("addedfile", mockFile);
|
||||||
|
dzInstance.emit("thumbnail", mockFile, 'storage/surveyor/' +
|
||||||
|
normalizedPath);
|
||||||
|
dzInstance.emit("complete", mockFile);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Dropzone instance for floor ${lantai} not found`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`Dropzone element for floor ${lantai} not found`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAllFloorsCreated(jsonData) {
|
||||||
|
if (jsonData && jsonData.foto_lantai_unit) {
|
||||||
|
const maxFloor = Math.max(...Object.keys(jsonData.foto_lantai_unit).map(Number));
|
||||||
|
|
||||||
|
while (lantaiCounter < maxFloor) {
|
||||||
|
const newLantaiElement = createLantaiElement();
|
||||||
|
lantaiContainer.appendChild(newLantaiElement);
|
||||||
|
initDropzonelantai(lantaiCounter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFoto(lantai, fotoPath) {
|
||||||
|
return fetch("{{ route('surveyor.hapusLantai') }}", {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
permohonan_id: {{ $permohonan->id ?? 0 }},
|
||||||
|
dokument_id: '{{ request('dokument') ?? '' }}',
|
||||||
|
lantai: lantai,
|
||||||
|
foto_path: fotoPath
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Hapus foto dari objek fotoLantaiUnit
|
||||||
|
fotoLantaiUnit[lantai] = fotoLantaiUnit[lantai].filter(
|
||||||
|
foto => foto.path !== fotoPath
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fungsi inisialisasi Dropzone
|
||||||
|
function initDropzonelantai(counter) {
|
||||||
|
dropzoneInstances[counter] = new Dropzone(`#dropzone-lantai-${counter}`, {
|
||||||
|
url: "{{ route('surveyor.storeFoto') }}",
|
||||||
|
paramName: "foto_lantai_unit[]",
|
||||||
|
maxFilesize: 5,
|
||||||
|
acceptedFiles: 'image/*',
|
||||||
|
maxFiles: 5,
|
||||||
|
addRemoveLinks: true,
|
||||||
|
autoProcessQueue: true,
|
||||||
|
parallelUploads: 5,
|
||||||
|
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
|
||||||
|
params: function(files, xhr, chunk) {
|
||||||
|
return {
|
||||||
|
permohonan_id: {{ $permohonan->id ?? 0 }},
|
||||||
|
dokument_id: '{{ request('dokument') ?? '' }}',
|
||||||
|
lantai_nama: [`lantai_${counter}`]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
init: function() {
|
||||||
|
this.lantaiCounter = counter;
|
||||||
|
this.on("sending", function(file, xhr, formData) {
|
||||||
|
formData.append('lantai_index', this.lantaiCounter);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
dictDefaultMessage: 'Seret foto atau klik untuk unggah',
|
||||||
|
dictRemoveFile: 'Hapus',
|
||||||
|
dictMaxFilesExceeded: 'Maksimal 5 file per lantai',
|
||||||
|
|
||||||
|
removedfile: function(file) {
|
||||||
|
// Gunakan lantaiCounter dari instance ini
|
||||||
|
const lantai = this.lantaiCounter;
|
||||||
|
if (file.path) {
|
||||||
|
deleteFoto(lantai, file.path).then(success => {
|
||||||
|
if (success) {
|
||||||
|
file.previewElement.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
file.previewElement.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function(file, response) {
|
||||||
|
const lantai = this.lantaiCounter;
|
||||||
|
|
||||||
|
if (!fotoLantaiUnit[lantai]) {
|
||||||
|
fotoLantaiUnit[lantai] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
fotoLantaiUnit[lantai].push({
|
||||||
|
path: response.foto_path,
|
||||||
|
name: file.name
|
||||||
|
});
|
||||||
|
if (response) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Upload Berhasil',
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(file, errorMessage) {
|
||||||
|
console.error('Upload error', errorMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi membuat elemen lantai baru
|
||||||
|
function createLantaiElement() {
|
||||||
|
lantaiCounter++;
|
||||||
|
|
||||||
|
const lantaiElement = document.createElement('div');
|
||||||
|
lantaiElement.classList.add('lantai-item', 'mb-4');
|
||||||
|
lantaiElement.id = `lantai-item-${lantaiCounter}`;
|
||||||
|
|
||||||
|
lantaiElement.innerHTML = `
|
||||||
|
<div class="flex justify-between align-items-center">
|
||||||
|
<h5 class="mb-0">Lantai ${lantaiCounter}</h5>
|
||||||
|
<input type="hidden" name="lantai_nama[]" value="lantai_${lantaiCounter}">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-lantai" data-id="${lantaiCounter}">
|
||||||
|
<i class="fa fa-trash"></i> Hapus Lantai
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="dropzone" id="dropzone-lantai-${lantaiCounter}">
|
||||||
|
<div class="dz-message">
|
||||||
|
Seret dan lepas file di sini atau klik untuk unggah
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Event listener untuk tombol hapus lantai
|
||||||
|
const removeButton = lantaiElement.querySelector('.btn-remove-lantai');
|
||||||
|
removeButton.addEventListener('click', function() {
|
||||||
|
const lantaiToRemove = parseInt(this.getAttribute('data-id'));
|
||||||
|
hapusLantai(lantaiToRemove);
|
||||||
|
});
|
||||||
|
|
||||||
|
return lantaiElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi hapus lantai yang diperbaiki
|
||||||
|
function hapusLantai(lantai) {
|
||||||
|
// Konfirmasi penghapusan
|
||||||
|
Swal.fire({
|
||||||
|
title: `Apakah Anda yakin ingin menghapus Lantai ${lantai}?`,
|
||||||
|
text: "Semua foto pada lantai ini akan dihapus permanen!",
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#3085d6',
|
||||||
|
cancelButtonColor: '#d33',
|
||||||
|
confirmButtonText: 'Ya, Hapus!',
|
||||||
|
cancelButtonText: 'Batal'
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// Cek apakah ada foto di lantai ini
|
||||||
|
const fotosLantai = fotoLantaiUnit[lantai] || [];
|
||||||
|
|
||||||
|
// Fungsi untuk menghapus foto-foto di lantai
|
||||||
|
const hapusFotoLantai = () => {
|
||||||
|
// Hapus instance Dropzone
|
||||||
|
if (dropzoneInstances[lantai]) {
|
||||||
|
dropzoneInstances[lantai].destroy();
|
||||||
|
delete dropzoneInstances[lantai];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hapus elemen lantai dari DOM
|
||||||
|
const lantaiElement = document.getElementById(`lantai-item-${lantai}`);
|
||||||
|
if (lantaiElement) {
|
||||||
|
lantaiElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hapus dari objek fotoLantaiUnit
|
||||||
|
delete fotoLantaiUnit[lantai];
|
||||||
|
|
||||||
|
// Perbarui counter dan tampilan
|
||||||
|
updateLantaiCounter();
|
||||||
|
|
||||||
|
// Tampilkan notifikasi sukses
|
||||||
|
Swal.fire(
|
||||||
|
'Dihapus!',
|
||||||
|
`Lantai ${lantai} telah dihapus.`,
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Jika ada foto, hapus terlebih dahulu
|
||||||
|
if (fotosLantai.length > 0) {
|
||||||
|
// Buat promise untuk menghapus setiap foto
|
||||||
|
const deletePromises = fotosLantai.map(foto =>
|
||||||
|
deleteFoto(lantai, foto.path)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tunggu semua foto terhapus
|
||||||
|
Promise.all(deletePromises)
|
||||||
|
.then(() => {
|
||||||
|
// Setelah semua foto dihapus, lanjutkan proses penghapusan lantai
|
||||||
|
hapusFotoLantai();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Gagal menghapus foto:', error);
|
||||||
|
Swal.fire(
|
||||||
|
'Gagal!',
|
||||||
|
'Terjadi kesalahan saat menghapus foto.',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Jika tidak ada foto, langsung hapus lantai
|
||||||
|
hapusFotoLantai();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi updateLantaiCounter yang diperbaiki
|
||||||
|
function updateLantaiCounter() {
|
||||||
|
// Dapatkan semua elemen lantai yang tersisa
|
||||||
|
const lantaiElements = document.querySelectorAll('.lantai-item');
|
||||||
|
|
||||||
|
// Perbarui nomor lantai dan ID
|
||||||
|
lantaiElements.forEach((element, index) => {
|
||||||
|
const currentIndex = index + 1;
|
||||||
|
|
||||||
|
// Perbarui judul lantai
|
||||||
|
const lantaiTitle = element.querySelector('h5');
|
||||||
|
if (lantaiTitle) {
|
||||||
|
lantaiTitle.textContent = `Lantai ${currentIndex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perbarui input hidden
|
||||||
|
const hiddenInput = element.querySelector('input[name="lantai_nama[]"]');
|
||||||
|
if (hiddenInput) {
|
||||||
|
hiddenInput.value = `lantai_${currentIndex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perbarui ID elemen
|
||||||
|
element.id = `lantai-item-${currentIndex}`;
|
||||||
|
|
||||||
|
// Perbarui data-id pada tombol hapus
|
||||||
|
const removeButton = element.querySelector('.btn-remove-lantai');
|
||||||
|
if (removeButton) {
|
||||||
|
removeButton.setAttribute('data-id', currentIndex);
|
||||||
|
|
||||||
|
// Tambahkan event listener baru
|
||||||
|
removeButton.onclick = function() {
|
||||||
|
hapusLantai(currentIndex);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sembunyikan tombol hapus pada lantai pertama jika hanya satu lantai
|
||||||
|
if (lantaiElements.length === 1) {
|
||||||
|
const firstRemoveButton = document.querySelector('.btn-remove-lantai[data-id="1"]');
|
||||||
|
if (firstRemoveButton) {
|
||||||
|
firstRemoveButton.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update lantaiCounter global
|
||||||
|
lantaiCounter = lantaiElements.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modifikasi event listener untuk tombol tambah lantai
|
||||||
|
btnAddLantai.addEventListener('click', function() {
|
||||||
|
const newLantaiElement = createLantaiElement();
|
||||||
|
lantaiContainer.appendChild(newLantaiElement);
|
||||||
|
|
||||||
|
// Inisialisasi Dropzone untuk lantai baru
|
||||||
|
initDropzonelantai(lantaiCounter);
|
||||||
|
|
||||||
|
// Tampilkan tombol hapus untuk lantai pertama
|
||||||
|
const firstFloorRemoveBtn = document.querySelector('.btn-remove-lantai[data-id="1"]');
|
||||||
|
if (firstFloorRemoveBtn) {
|
||||||
|
firstFloorRemoveBtn.style.display = 'block';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
initDropzonelantai(lantaiCounter);
|
||||||
|
|
||||||
|
// Pastikan semua lantai dibuat
|
||||||
|
ensureAllFloorsCreated(jsonDataContoh);
|
||||||
|
|
||||||
|
// Muat foto
|
||||||
|
loadFotoFromJSON(jsonDataContoh);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
function submitFoto() {
|
function submitFoto() {
|
||||||
@@ -446,13 +1032,6 @@
|
|||||||
|
|
||||||
const formElement = $('#formFoto')[0];
|
const formElement = $('#formFoto')[0];
|
||||||
const formData = new FormData(formElement);
|
const formData = new FormData(formElement);
|
||||||
for (let [key, value] of formData.entries()) {
|
|
||||||
if (value instanceof File) {
|
|
||||||
console.log(`${key}: File name = ${value.name}, File size = ${value.size}, File type = ${value.type}`);
|
|
||||||
} else {
|
|
||||||
console.log(`${key}: ${value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '{{ route('surveyor.storeFoto') }}',
|
url: '{{ route('surveyor.storeFoto') }}',
|
||||||
@@ -474,10 +1053,10 @@
|
|||||||
confirmButtonText: 'OK'
|
confirmButtonText: 'OK'
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
if (response.isConfirmed) {
|
if (response.isConfirmed) {
|
||||||
// window.location.href =
|
window.location.href =
|
||||||
// '{{ route('surveyor.show', ['id' => $permohonan->id]) }}';
|
'{{ route('surveyor.show', ['id' => $permohonan->id]) }}';
|
||||||
}
|
}
|
||||||
console.log(response);
|
// console.log(response);
|
||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -199,322 +199,4 @@
|
|||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class DynamicFileUploader {
|
|
||||||
constructor(options) {
|
|
||||||
const defaultOptions = {
|
|
||||||
containerId: 'lantaiContainer',
|
|
||||||
addButtonId: null,
|
|
||||||
fileInputName: 'foto_lantai_unit',
|
|
||||||
maxFiles: 4,
|
|
||||||
allowMultiple: true,
|
|
||||||
name: null,
|
|
||||||
accept: 'image/*',
|
|
||||||
existingFiles: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.options = {
|
|
||||||
...defaultOptions,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
this.container = document.getElementById(this.options.containerId);
|
|
||||||
|
|
||||||
if (this.options.addButtonId) {
|
|
||||||
this.addButton = document.getElementById(this.options.addButtonId);
|
|
||||||
|
|
||||||
this.addButton.addEventListener('click', () => {
|
|
||||||
this.addNewItem();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Inisialisasi global handlers
|
|
||||||
this.initGlobalHandlers();
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
initGlobalHandlers() {
|
|
||||||
// Simpan instance ke window untuk akses global
|
|
||||||
// window.dynamicFileUploader = this;
|
|
||||||
|
|
||||||
// Global handler untuk drag and drop
|
|
||||||
window.handleDrop = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const dropzone = event.currentTarget;
|
|
||||||
const fileInput = dropzone.querySelector('.file-input');
|
|
||||||
this.handleDrop(event, fileInput);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.allowDrop = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.handleFileInput = (fileInput) => {
|
|
||||||
this.handleFileInput(fileInput);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Proses file yang sudah ada
|
|
||||||
if (this.options.existingFiles && this.options.existingFiles.length > 0) {
|
|
||||||
this.processExistingFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inisialisasi item pertama jika belum ada file
|
|
||||||
if (this.container.children.length === 0) {
|
|
||||||
this.addNewItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inisialisasi event listener untuk item yang sudah ada
|
|
||||||
this.container.querySelectorAll(".dynamic-item").forEach(item => {
|
|
||||||
this.attachEventListeners(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateRemoveButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewItem() {
|
|
||||||
const currentItemCount = this.container.children.length + 1;
|
|
||||||
const newItem = document.createElement("div");
|
|
||||||
newItem.classList.add("dynamic-item", "w-full", "mb-4");
|
|
||||||
newItem.innerHTML = this.generateItemHTML(currentItemCount);
|
|
||||||
this.container.appendChild(newItem);
|
|
||||||
|
|
||||||
if (currentItemCount > 1) {
|
|
||||||
this.attachEventListeners(newItem);
|
|
||||||
}
|
|
||||||
this.updateRemoveButtonVisibility();
|
|
||||||
return newItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
generateItemHTML(itemCount) {
|
|
||||||
return `
|
|
||||||
<div class="flex w-full items-center justify-between">
|
|
||||||
${this.options.name ? `<label class="form-label">${this.options.name} ${itemCount}</label>` : ''}
|
|
||||||
<button type="button" class="btn btn-danger btn-sm btnRemoveLantai"
|
|
||||||
style="${itemCount === 1 ? 'display: none;' : ''}">
|
|
||||||
Hapus
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dropzone w-full border-2 border-dashed border-gray-400 rounded-lg p-4 mt-2"
|
|
||||||
ondrop="handleDrop(event)" ondragover="allowDrop(event)">
|
|
||||||
<div class="preview-container grid grid-cols-2 md:grid-cols-4 gap-4"></div>
|
|
||||||
<input type="file"
|
|
||||||
name="${this.options.fileInputName}[${itemCount}][]"
|
|
||||||
class="file-input hidden"
|
|
||||||
multiple
|
|
||||||
accept="${this.options.accept}"
|
|
||||||
onchange="handleFileInput(this)"
|
|
||||||
data-lantai="${itemCount}">
|
|
||||||
<button type="button" class="btn btn-light btn-sm btnUploadFiles mt-2">
|
|
||||||
<i class="ki-outline ki-upload"></i> Unggah Gambar
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachEventListeners(item) {
|
|
||||||
const uploadButton = item.querySelector('.btnUploadFiles');
|
|
||||||
const fileInput = item.querySelector('.file-input');
|
|
||||||
const previewContainer = item.querySelector('.preview-container');
|
|
||||||
|
|
||||||
if (!uploadButton.dataset.listenerAttached) {
|
|
||||||
uploadButton.addEventListener('click', () => {
|
|
||||||
fileInput.click();
|
|
||||||
});
|
|
||||||
uploadButton.dataset.listenerAttached = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeItemButton = item.querySelector('.btnRemoveLantai');
|
|
||||||
if (removeItemButton && !removeItemButton.dataset.listenerAttached) {
|
|
||||||
removeItemButton.addEventListener('click', () => {
|
|
||||||
this.removeItem(item);
|
|
||||||
});
|
|
||||||
removeItemButton.dataset.listenerAttached = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handleDrop(event, fileInput) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const files = event.dataTransfer.files;
|
|
||||||
const previewContainer = fileInput.closest('.dropzone').querySelector('.preview-container');
|
|
||||||
this.processFiles(files, fileInput, previewContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFileInput(fileInput) {
|
|
||||||
if (!fileInput || !fileInput.files) return;
|
|
||||||
|
|
||||||
const files = fileInput.files;
|
|
||||||
const dropzone = fileInput.closest('.dropzone');
|
|
||||||
const previewContainer = dropzone.querySelector('.preview-container');
|
|
||||||
|
|
||||||
// Proses file
|
|
||||||
this.processFiles(files, fileInput, previewContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
processFiles(files, fileInput, previewContainer) {
|
|
||||||
const existingPreviews = previewContainer.querySelectorAll('.preview-item img');
|
|
||||||
const existingFiles = Array.from(existingPreviews).map(img => img.src);
|
|
||||||
|
|
||||||
const maxFiles = this.options.maxFiles;
|
|
||||||
const remainingSlots = maxFiles - existingPreviews.length;
|
|
||||||
|
|
||||||
const filesToAdd = Array.from(files).filter(file => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
return !existingFiles.includes(e.target.result);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
return true;
|
|
||||||
}).slice(0, remainingSlots);
|
|
||||||
|
|
||||||
if (filesToAdd.length === 0) {
|
|
||||||
alert(`Maksimal ${maxFiles} foto per lantai`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
filesToAdd.forEach(file => {
|
|
||||||
const isValidType = this.options.accept === '*' || file.type.match(this.options.accept
|
|
||||||
.replace('*', '.*'));
|
|
||||||
|
|
||||||
if (isValidType) {
|
|
||||||
this.createFilePreview(file, previewContainer, fileInput);
|
|
||||||
} else {
|
|
||||||
alert(`File ${file.name} tidak valid. Hanya file gambar yang diperbolehkan.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
createFilePreview(file, previewContainer, fileInput) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
const imgWrapper = document.createElement('div');
|
|
||||||
imgWrapper.classList.add('preview-item', 'relative');
|
|
||||||
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = e.target.result;
|
|
||||||
img.classList.add('w-full', 'h-40', 'object-cover', 'rounded-lg');
|
|
||||||
|
|
||||||
const removeBtn = document.createElement('button');
|
|
||||||
removeBtn.type = 'button';
|
|
||||||
removeBtn.classList.add(
|
|
||||||
'absolute', 'top-2', 'right-2',
|
|
||||||
'btn', 'btn-sm', 'btn-danger', 'btn-icon',
|
|
||||||
'btnRemoveFoto'
|
|
||||||
);
|
|
||||||
removeBtn.innerHTML = '<i class="ki-solid ki-cross"></i>';
|
|
||||||
|
|
||||||
removeBtn.addEventListener('click', () => {
|
|
||||||
imgWrapper.remove();
|
|
||||||
this.updatePreviewContainerVisibility(previewContainer);
|
|
||||||
this.resetFileInput(fileInput);
|
|
||||||
});
|
|
||||||
|
|
||||||
imgWrapper.appendChild(img);
|
|
||||||
imgWrapper.appendChild(removeBtn);
|
|
||||||
previewContainer.appendChild(imgWrapper);
|
|
||||||
|
|
||||||
this.updatePreviewContainerVisibility(previewContainer);
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePreviewContainerVisibility(previewContainer) {
|
|
||||||
const previewItems = previewContainer.querySelectorAll('.preview-item');
|
|
||||||
previewContainer.style.display = previewItems.length > 0 ? 'grid' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
resetFileInput(fileInput) {
|
|
||||||
fileInput.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItem(item) {
|
|
||||||
this.container.removeChild(item);
|
|
||||||
this.updateRemoveButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRemoveButtonVisibility() {
|
|
||||||
const removeButtons = this.container.querySelectorAll('.btnRemoveLantai');
|
|
||||||
removeButtons.forEach((btn, index) => {
|
|
||||||
btn.style.display = index === 0 ? 'none' : 'block';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
processExistingFiles() {
|
|
||||||
this.container.innerHTML = '';
|
|
||||||
const groupedFiles = this.groupFilesByItem(this.options.existingFiles);
|
|
||||||
|
|
||||||
Object.keys(groupedFiles).forEach((itemKey, index) => {
|
|
||||||
const itemCount = index + 1;
|
|
||||||
const newItem = document.createElement("div");
|
|
||||||
newItem.classList.add("dynamic-item", "w-full", "mb-4");
|
|
||||||
newItem.innerHTML = this.generateItemHTML(itemCount);
|
|
||||||
this.container.appendChild(newItem);
|
|
||||||
|
|
||||||
const previewContainer = newItem.querySelector(".preview-container");
|
|
||||||
const fileInput = newItem.querySelector(".file-input");
|
|
||||||
|
|
||||||
groupedFiles[itemKey].forEach(file => {
|
|
||||||
this.addExistingFilePreview(previewContainer, fileInput, file, itemCount);
|
|
||||||
});
|
|
||||||
|
|
||||||
// this.attachEventListeners(newItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateRemoveButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
groupFilesByItem(files) {
|
|
||||||
return files.reduce((acc, file) => {
|
|
||||||
const itemKey = file.item || '1';
|
|
||||||
if (!acc[itemKey]) {
|
|
||||||
acc[itemKey] = [];
|
|
||||||
}
|
|
||||||
acc[itemKey].push(file);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
addExistingFilePreview(previewContainer, fileInput, file, itemCount) {
|
|
||||||
const imgWrapper = document.createElement("div");
|
|
||||||
imgWrapper.classList.add("relative", "preview-item");
|
|
||||||
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = file.url || file.path;
|
|
||||||
img.classList.add("w-full", "h-auto", "object-cover", "rounded-lg");
|
|
||||||
|
|
||||||
const removeBtn = document.createElement("button");
|
|
||||||
removeBtn.type = "button";
|
|
||||||
removeBtn.classList.add("absolute", "top-2", "right-2", "btn", "btn-danger", "btn-sm");
|
|
||||||
removeBtn.innerHTML = '<i class="ki-outline ki-cross"></i>';
|
|
||||||
|
|
||||||
removeBtn.addEventListener("click", () => {
|
|
||||||
imgWrapper.remove();
|
|
||||||
this.updatePreviewContainerVisibility(previewContainer);
|
|
||||||
});
|
|
||||||
|
|
||||||
imgWrapper.appendChild(img);
|
|
||||||
imgWrapper.appendChild(removeBtn);
|
|
||||||
previewContainer.appendChild(imgWrapper);
|
|
||||||
|
|
||||||
this.updatePreviewContainerVisibility(previewContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllUploadedFiles() {
|
|
||||||
const files = [];
|
|
||||||
this.container.querySelectorAll('.file-input').forEach(input => {
|
|
||||||
if (input.files.length > 0) {
|
|
||||||
files.push(...input.files);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -536,6 +536,10 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::put('storeAproved/{storeAproved}', [SurveyorController::class, 'storeAproved'])->name('storeAproved');
|
Route::put('storeAproved/{storeAproved}', [SurveyorController::class, 'storeAproved'])->name('storeAproved');
|
||||||
Route::put('storeFreeze', [SurveyorController::class, 'storeFreeze'])->name('storeFreeze');
|
Route::put('storeFreeze', [SurveyorController::class, 'storeFreeze'])->name('storeFreeze');
|
||||||
Route::post('storeFoto', [SurveyorController::class, 'storeFoto'])->name('storeFoto');
|
Route::post('storeFoto', [SurveyorController::class, 'storeFoto'])->name('storeFoto');
|
||||||
|
Route::delete('/surveyor/hapus-foto', [SurveyorController::class, 'hapusFoto'])->name('hapusFoto');
|
||||||
|
|
||||||
|
Route::delete('/hapus-lantai', [SurveyorController::class, 'hapusLantai'])->name('hapusLantai');
|
||||||
|
Route::get('/surveyor/get-foto/', [SurveyorController::class, 'getFoto'])->name('getFoto');
|
||||||
Route::post('storeDataPembanding', [SurveyorController::class, 'storeDataPembanding'])->name('storeDataPembanding');
|
Route::post('storeDataPembanding', [SurveyorController::class, 'storeDataPembanding'])->name('storeDataPembanding');
|
||||||
Route::get('checkButtonStatus/{id}', [SurveyorController::class, 'checkButtonStatus'])->name('checkButtonStatus');
|
Route::get('checkButtonStatus/{id}', [SurveyorController::class, 'checkButtonStatus'])->name('checkButtonStatus');
|
||||||
|
|
||||||
@@ -546,7 +550,6 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::get('data-pembanding/{id}', [SurveyorController::class, 'dataPembanding'])->name('data-pembanding');
|
Route::get('data-pembanding/{id}', [SurveyorController::class, 'dataPembanding'])->name('data-pembanding');
|
||||||
Route::post('submitSurveyor/{id}', [SurveyorController::class, 'submitSurveyor'])->name('submitSurveyor');
|
Route::post('submitSurveyor/{id}', [SurveyorController::class, 'submitSurveyor'])->name('submitSurveyor');
|
||||||
Route::post('update_analisa/{id}', [SurveyorController::class, 'update_analisa'])->name('update_analisa');
|
Route::post('update_analisa/{id}', [SurveyorController::class, 'update_analisa'])->name('update_analisa');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::name('penilai.')->prefix('penilai')->group(function () {
|
Route::name('penilai.')->prefix('penilai')->group(function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user