fix: resolve existing photos preview in Dropzone Lahan&Tanaman

This commit is contained in:
2026-07-20 16:36:12 +07:00
parent 837e542851
commit 3e1d3a33ee
4 changed files with 327 additions and 32 deletions
@@ -389,7 +389,7 @@
</div>
<input type="file" id="foto-input" name="foto[]" multiple hidden>
<input type="file" id="foto-input" multiple hidden>
@error('file')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
@@ -759,8 +759,6 @@
tempInput.click();
}
});
// -----------------------------------
// FORM
$('#petani-lahan-tanaman').validate({
rules: {
@@ -805,21 +803,44 @@
submitHandler: function (form) {
const formData = new FormData(form);
const dz = $("#dropzone-foto")[0].dropzone;
if (myDropzone) {
myDropzone.files.forEach(function(file) {
formData.append("foto[]", file);
});
}
dz.getAcceptedFiles().forEach(function(file) {
formData.append("foto[]", file);
});
let submitBtn = $(form).find('button[type="submit"]');
let originalText = submitBtn.html();
submitBtn.prop('disabled', true).html('<i class="ti tabler-loader fa-spin me-2"></i> Menyimpan...');
$.ajax({
url: form.action,
url: $(form).attr('action'),
type: "POST",
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRF-TOKEN': $('input[name="_token"]').val(),
'Accept': 'application/json'
},
success: function(res) {
if (res.redirect) {
window.location.href = res.redirect;
alert('Data lahan berhasil ditambahkan!');
window.location.href = "{{ route('petani.index') }}";
},
error: function(xhr) {
submitBtn.prop('disabled', false).html(originalText);
if (xhr.status === 422) {
let errors = xhr.responseJSON.errors;
let errorMsg = "";
$.each(errors, function(key, val) {
errorMsg += val[0] + "\n";
});
alert("Penyimpanan Gagal:\n\n" + errorMsg);
} else {
let errorDetail = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : xhr.statusText;
alert("Error " + xhr.status + ": " + errorDetail);
console.error(xhr.responseText);
}
}
});
@@ -3,6 +3,7 @@
@push('style')
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
<style>
#map {
@@ -11,6 +12,28 @@
background-color: #f8f9fa;
z-index: 1;
}
.dropzone-custom-container { display: flex; flex-wrap: wrap; gap: 15px; padding: 15px; border: 2px dashed #ced4da; border-radius: 12px; background-color: #f8f9fa; min-height: 160px; align-items: center; }
.btn-tambah-foto { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 140px; height: 160px; border: 2px dashed #a8b1ba; border-radius: 12px; background: #fff; color: #6c757d; cursor: pointer; transition: 0.2s; order: 999; }
.btn-tambah-foto:hover { background: #e9ecef; border-color: #6c757d; color: #495057; }
.dropzone-custom-container .dz-preview { position: relative; background: #fff; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.08); border: 1px solid #e2e8f0; width: 140px; display: flex; flex-direction: column; }
.dropzone-custom-container .dz-image { width: 100%; height: 110px; background: #f1f5f9; cursor: zoom-in; display: flex; align-items: center; justify-content: center; border-radius: 12px 12px 0 0; overflow: hidden; }
.dropzone-custom-container .dz-image img { width: 100%; height: 100%; object-fit: cover; }
.dropzone-custom-container .dz-details { padding: 8px; text-align: center; background: #fff; border-radius: 0 0 12px 12px; border-top: 1px solid #e2e8f0; }
.dropzone-custom-container .dz-filename { font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #495057; }
.dropzone-custom-container .dz-size { font-size: 10px; color: #adb5bd; }
.dropzone-custom-container .dz-control-panel { position: absolute; top: -8px; right: -8px; display: flex; gap: 5px; z-index: 20; }
.dropzone-custom-container .dz-btn-action { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: 50%; color: #fff !important; text-decoration: none !important; box-shadow: 0 2px 5px rgba(0,0,0,0.2); cursor: pointer; font-size: 14px; }
.dropzone-custom-container .btn-edit { background: #ffc107; color: #000 !important; }
.dropzone-custom-container .btn-edit:hover { background: #7367f0; }
.dropzone-custom-container .btn-delete { background: #dc3545; }
.dropzone-custom-container .btn-delete:hover { background: #c82333; }
.dropzone-custom-container .dz-error-message, .dropzone-custom-container .dz-success-mark, .dropzone-custom-container .dz-error-mark, .dropzone-custom-container .progress { display: none !important; }
.foto-lightbox { display: none; position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background: rgba(0, 0, 0, 0.85) !important; z-index: 99999 !important; align-items: center !important; justify-content: center !important; padding: 30px !important; box-sizing: border-box !important; }
.foto-lightbox.active { display: flex !important; }
.foto-lightbox img { max-width: 90vw !important; max-height: 85vh !important; object-fit: contain !important; border-radius: 8px !important; box-shadow: 0 10px 40px rgba(0,0,0,0.5) !important; }
.foto-lightbox-close { position: absolute !important; top: 20px !important; right: 30px !important; color: #fff !important; font-size: 40px !important; font-weight: bold !important; cursor: pointer !important; z-index: 100000 !important; }
.foto-lightbox-close:hover { color: #dc3545 !important; }
</style>
@endpush
@@ -224,9 +247,8 @@
<div class="row">
<div class="col-md-12 mb-3 form-control-validation">
<label class="form-label">Raw Data Koordinat Lahan (Auto-generated JSON) <span class="text-danger">*</span></label>
<textarea name="path" id="path" class="form-control @error('path') is-invalid @enderror" rows="4" readonly
placeholder="Koordinat akan terisi otomatis setelah Anda menggambar polygon di peta...">{{ old('path', $lahanSelected->path) }}</textarea>
<small class="text-muted">Gunakan tombol edit (pensil) atau hapus (tong sampah) di peta untuk mengubah bentuk lahan.</small>
<textarea name="path" id="path" class="form-control @error('path') is-invalid @enderror" rows="4" readonly
placeholder="Koordinat akan terisi otomatis setelah Anda menggambar polygon di peta...">{{ old('path', is_string($lahanSelected->path) ? $lahanSelected->path : json_encode($lahanSelected->path)) }}</textarea>
@error('path')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
@@ -243,6 +265,24 @@
</div>
<hr class="my-4">
<div class="row">
<div class="col-md-12 mb-3 form-control-validation">
<h6 class="mb-5">Foto</h6>
<div id="dropzone-container" class="dropzone-custom-container">
<div id="btn-tambah-foto" class="btn-tambah-foto">
<i class="ti tabler-plus" style="font-size: 32px; margin-bottom: 8px;"></i>
<span style="font-size: 13px; font-weight: 600;">Tambah Foto</span>
</div>
</div>
<div id="deleted-photos-container"></div>
<input type="file" id="foto-input" multiple hidden>
@error('file')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="row mt-3">
<div class="col-12 text-end">
<a href="{{ route('petani.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
@@ -358,6 +398,7 @@
@endsection
@push('scripts')
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
@@ -546,7 +587,7 @@
let regionBoundaryLayer = L.featureGroup().addTo(map);
function focusToRegionBoundary(selectElement, shouldZoom = true) {
regionBoundaryLayer.clearLayers(); // Bersihkan garis wilayah sebelumnya
regionBoundaryLayer.clearLayers();
let selectedOption = selectElement.options[selectElement.selectedIndex];
if (!selectedOption || !selectedOption.hasAttribute('data-path')) return;
@@ -561,7 +602,7 @@
function formatCoordinates(coords) {
if (coords.length === 2 && typeof coords[0] === 'number') {
return [coords[0], coords[1]]; // JANGAN DIBALIK
return [coords[0], coords[1]];
}
return coords.map(formatCoordinates);
}
@@ -637,10 +678,8 @@
map.on(L.Draw.Event.CREATED, function (e) {
let layer = e.layer;
drawnItems.clearLayers();
drawnItems.addLayer(layer);
updatePathTextarea(layer);
});
@@ -660,16 +699,143 @@
return [latlng.lng, latlng.lat];
});
document.getElementById('path').value = JSON.stringify(pathData);
let area = L.GeometryUtil.geodesicArea(latlngs);
console.log(area);
$('#luas_lahan').val(area.toFixed(2));
}
// FORM
const previewTemplate = `
<div class="dz-preview dz-file-preview">
<div class="dz-control-panel">
<a href="javascript:void(0);" class="dz-btn-action btn-edit" title="Ganti Foto">
<i class="ti tabler-pencil"></i>
</a>
<a href="javascript:void(0);" class="dz-btn-action btn-delete" data-dz-remove title="Hapus Foto">
<i class="ti tabler-x"></i>
</a>
</div>
<div class="dz-image"><img data-dz-thumbnail /></div>
<div class="dz-details">
<div class="dz-filename"><span data-dz-name></span></div>
<div class="dz-size"><span data-dz-size></span></div>
</div>
</div>`;
Dropzone.autoDiscover = false;
let myDropzone = null;
const dropzoneElement = document.querySelector("#dropzone-container");
if (dropzoneElement) {
myDropzone = new Dropzone(dropzoneElement, {
url: "/",
autoProcessQueue: false,
previewTemplate: previewTemplate,
clickable: "#btn-tambah-foto",
previewsContainer: "#dropzone-container",
parallelUploads: 10,
maxFilesize: 5,
addRemoveLinks: false,
acceptedFiles: "image/jpeg,image/jpg,image/png",
init: function () {
let dz = this;
let rawPhotos = {!! json_encode($lahanSelected->foto ?? []) !!};
let existingPhotos = [];
if (typeof rawPhotos === 'string') {
try {
existingPhotos = JSON.parse(rawPhotos);
} catch(e) {
existingPhotos = [];
}
} else if (Array.isArray(rawPhotos)) {
existingPhotos = rawPhotos;
}
if (existingPhotos && existingPhotos.length > 0) {
existingPhotos.forEach(function (photo, index) {
let pathStr = typeof photo === 'object' ? (photo.path || photo[0] || '') : photo;
if (pathStr && pathStr !== '') {
let mockFile = {
name: "Foto Lahan " + (index + 1),
size: 1024,
id: index,
isServerFile: true,
accepted: true
};
dz.emit("addedfile", mockFile);
dz.emit("thumbnail", mockFile, "{{ asset('storage') }}/" + pathStr);
dz.emit("complete", mockFile);
dz.files.push(mockFile);
}
});
}
this.on("removedfile", function (file) {
if (file.isServerFile) {
let deleteInput = $('<input>').attr({
type: 'hidden',
name: 'deleted_photos[]',
value: file.id
});
$('#deleted-photos-container').append(deleteInput);
}
});
}
});
}
$(document).on('click', '.btn-edit', function(e) {
e.preventDefault();
e.stopPropagation();
let previewElement = $(this).closest('.dz-preview')[0];
let dz = myDropzone;
let fileToReplace = dz.files.find(f => f.previewElement === previewElement);
if (fileToReplace) {
let tempInput = document.createElement('input');
tempInput.type = 'file';
tempInput.accept = 'image/jpeg,image/jpg,image/png';
tempInput.onchange = function(event) {
let newFile = event.target.files[0];
if (newFile) {
if(fileToReplace.isServerFile){
let deleteInput = $('<input>').attr({
type: 'hidden',
name: 'deleted_photos[]',
value: fileToReplace.id
});
$('#deleted-photos-container').append(deleteInput);
}
dz.removeFile(fileToReplace);
dz.addFile(newFile);
}
};
tempInput.click();
}
});
$(document).on('click', '#dropzone-container .dz-image img', function (e) {
e.stopPropagation();
$('#foto-lightbox-img').attr('src', this.src);
$('#foto-lightbox').addClass('active');
});
$('#foto-lightbox').on('click', function (e) {
if (e.target === this || $(e.target).hasClass('foto-lightbox-close')) {
$(this).removeClass('active');
}
});
// FORM VALIDATION & SUBMIT
$('#petani-lahan-tanaman').validate({
rules: {
luas_lahan: { required: true },
@@ -697,6 +863,57 @@
},
unhighlight: function (element) {
$(element).removeClass('is-invalid');
},
submitHandler: function (form) {
const formData = new FormData(form);
formData.append('_method', 'PUT');
if (myDropzone) {
myDropzone.files.forEach(function(file) {
if (!file.isServerFile) {
formData.append("foto[]", file);
}
});
}
let submitBtn = $(form).find('button[type="submit"]');
let originalText = submitBtn.html();
submitBtn.prop('disabled', true).html('<i class="ti tabler-loader fa-spin me-2"></i> Menyimpan...');
$.ajax({
url: $(form).attr('action'),
type: "POST",
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRF-TOKEN': $('input[name="_token"]').val(),
'Accept': 'application/json'
},
success: function(res) {
alert('Data lahan berhasil disimpan!');
window.location.href = "{{ route('petani.index') }}";
},
error: function(xhr) {
submitBtn.prop('disabled', false).html(originalText);
if (xhr.status === 422) {
let errors = xhr.responseJSON.errors;
let errorMsg = "";
$.each(errors, function(key, val) {
errorMsg += val[0] + "\n";
});
alert("Penyimpanan Gagal:\n\n" + errorMsg);
} else {
let errorDetail = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : xhr.statusText;
alert("Error " + xhr.status + ": " + errorDetail);
console.error(xhr.responseText);
}
}
});
return false;
}
});