656 lines
24 KiB
JavaScript
656 lines
24 KiB
JavaScript
"use strict";
|
|
|
|
// Class definition
|
|
var WorkPrices = (function () {
|
|
// Private variables
|
|
var workId;
|
|
var dealersTable;
|
|
var saveTimeout = {}; // For debouncing save requests
|
|
|
|
// Loading overlay functions
|
|
var showLoadingOverlay = function (message) {
|
|
if ($("#loading-overlay").length === 0) {
|
|
$("body").append(`
|
|
<div id="loading-overlay" style="
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
z-index: 9999;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
flex-direction: column;
|
|
">
|
|
<div class="spinner-border text-light" role="status" style="width: 3rem; height: 3rem;">
|
|
<span class="sr-only">Loading...</span>
|
|
</div>
|
|
<div class="text-light mt-3" style="font-size: 1.1rem; font-weight: 500;">${message}</div>
|
|
</div>
|
|
`);
|
|
} else {
|
|
$("#loading-overlay").show();
|
|
$("#loading-overlay .text-light:last").text(message);
|
|
}
|
|
};
|
|
|
|
var hideLoadingOverlay = function () {
|
|
$("#loading-overlay").hide();
|
|
};
|
|
|
|
// Private functions
|
|
var initTable = function () {
|
|
dealersTable = $("#dealers-table").DataTable({
|
|
pageLength: -1, // Show all records
|
|
lengthChange: false, // Hide length change dropdown
|
|
searching: true,
|
|
ordering: true,
|
|
info: false, // Hide "Showing X of Y entries"
|
|
responsive: true,
|
|
dom: '<"top"f>rt<"bottom"p>', // Only show search and pagination
|
|
paging: false, // Disable pagination
|
|
});
|
|
};
|
|
|
|
var initEvents = function () {
|
|
// Get work ID from URL
|
|
var pathArray = window.location.pathname.split("/");
|
|
workId = pathArray[pathArray.length - 2]; // work/{id}/set-prices
|
|
|
|
// Save single price with debouncing
|
|
$(document).on("click", ".save-single", function () {
|
|
var dealerId = $(this).data("dealer-id");
|
|
|
|
// Clear existing timeout for this dealer
|
|
if (saveTimeout[dealerId]) {
|
|
clearTimeout(saveTimeout[dealerId]);
|
|
}
|
|
|
|
// Set new timeout to prevent rapid clicks
|
|
saveTimeout[dealerId] = setTimeout(function () {
|
|
saveSinglePrice(dealerId);
|
|
}, 300); // 300ms delay
|
|
});
|
|
|
|
// Delete price
|
|
$(document).on("click", ".delete-price", function () {
|
|
var priceId = $(this).data("price-id");
|
|
var dealerId = $(this).data("dealer-id");
|
|
deletePrice(priceId, dealerId);
|
|
});
|
|
|
|
// Save all prices
|
|
$("#btn-save-all").on("click", function () {
|
|
saveAllPrices();
|
|
});
|
|
|
|
// Status toggle
|
|
$(document).on("change", ".status-input", function () {
|
|
var dealerId = $(this).data("dealer-id");
|
|
var isChecked = $(this).is(":checked");
|
|
var label = $(this).siblings("label");
|
|
var checkbox = $(this);
|
|
|
|
// Update visual immediately
|
|
if (isChecked) {
|
|
label.text("Aktif").removeClass("inactive").addClass("active");
|
|
} else {
|
|
label
|
|
.text("Nonaktif")
|
|
.removeClass("active")
|
|
.addClass("inactive");
|
|
}
|
|
|
|
// Send AJAX request to update database
|
|
toggleStatus(dealerId, isChecked, checkbox, label);
|
|
});
|
|
|
|
// Format price input with thousand separator
|
|
$(document).on("input", ".price-input", function () {
|
|
var input = $(this);
|
|
var value = input.val().replace(/[^\d]/g, "");
|
|
|
|
if (value === "") {
|
|
input.val("0");
|
|
} else {
|
|
var numValue = parseInt(value);
|
|
input.val(numValue.toLocaleString("id-ID"));
|
|
// Don't update original value here - let it be updated only when saving
|
|
}
|
|
});
|
|
|
|
// Format price inputs on page load
|
|
$(".price-input").each(function () {
|
|
var input = $(this);
|
|
var value = input.val();
|
|
if (value && value !== "0") {
|
|
var numValue = parseInt(value.replace(/[^\d]/g, ""));
|
|
input.val(numValue.toLocaleString("id-ID"));
|
|
// Store the original numeric value for comparison
|
|
input.data("original-value", numValue.toString());
|
|
console.log(
|
|
"Initialized price for dealer",
|
|
input.attr("name").replace("price_", ""),
|
|
":",
|
|
numValue
|
|
);
|
|
}
|
|
});
|
|
};
|
|
|
|
var saveSinglePrice = function (dealerId) {
|
|
// Prevent multiple clicks
|
|
var saveButton = $('.save-single[data-dealer-id="' + dealerId + '"]');
|
|
if (saveButton.hasClass("loading")) {
|
|
return; // Already processing
|
|
}
|
|
|
|
var priceInput = $('input[name="price_' + dealerId + '"]');
|
|
var statusInput = $('input[name="status_' + dealerId + '"]');
|
|
var price = priceInput.val().replace(/[^\d]/g, ""); // Remove non-numeric characters
|
|
var isActive = statusInput.is(":checked");
|
|
|
|
if (!price || parseInt(price) <= 0) {
|
|
toastr.error("Harga harus lebih dari 0");
|
|
return;
|
|
}
|
|
|
|
// Get original price from data attribute (without separator)
|
|
var originalPrice = priceInput.data("original-value") || "0";
|
|
var currentPrice = parseInt(price);
|
|
var originalPriceInt = parseInt(originalPrice);
|
|
|
|
console.log(
|
|
"Debug - Original price:",
|
|
originalPriceInt,
|
|
"Current price:",
|
|
currentPrice
|
|
);
|
|
|
|
// If price hasn't actually changed, don't update
|
|
if (currentPrice === originalPriceInt && originalPrice !== "0") {
|
|
toastr.info("Harga tidak berubah, tidak perlu update");
|
|
return;
|
|
}
|
|
|
|
// If price has changed, update original value for next comparison
|
|
if (currentPrice !== originalPriceInt) {
|
|
priceInput.data("original-value", currentPrice.toString());
|
|
console.log(
|
|
"Price changed from",
|
|
originalPriceInt,
|
|
"to",
|
|
currentPrice
|
|
);
|
|
}
|
|
|
|
// Disable button and show loading state
|
|
saveButton.addClass("loading").prop("disabled", true);
|
|
var originalText = saveButton.text();
|
|
saveButton.text("Menyimpan...");
|
|
|
|
var data = {
|
|
work_id: parseInt(workId),
|
|
dealer_id: parseInt(dealerId),
|
|
price: currentPrice, // Use the validated price
|
|
currency: "IDR",
|
|
is_active: isActive ? 1 : 0,
|
|
};
|
|
|
|
// Debug: Log the data being sent
|
|
console.log("Sending data:", data);
|
|
console.log("Original price:", originalPriceInt);
|
|
console.log("Current price:", currentPrice);
|
|
|
|
$.ajax({
|
|
url: "/admin/work/" + workId + "/prices",
|
|
method: "POST",
|
|
data: data,
|
|
headers: {
|
|
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
|
|
},
|
|
beforeSend: function () {
|
|
console.log("Sending AJAX request with data:", data);
|
|
},
|
|
success: function (response) {
|
|
// Re-enable button
|
|
saveButton
|
|
.removeClass("loading")
|
|
.prop("disabled", false)
|
|
.text(originalText);
|
|
|
|
if (response.status === 200) {
|
|
toastr.success(response.message);
|
|
// Update UI
|
|
updateRowAfterSave(dealerId, response.data);
|
|
|
|
// Ensure consistent formatting after update
|
|
var updatedPrice = priceInput.val().replace(/[^\d]/g, "");
|
|
if (updatedPrice && updatedPrice !== "0") {
|
|
var formattedPrice =
|
|
parseInt(updatedPrice).toLocaleString("id-ID");
|
|
priceInput.val(formattedPrice);
|
|
}
|
|
|
|
// Show brief loading message
|
|
toastr.info(
|
|
"Data berhasil disimpan, memperbarui tampilan..."
|
|
);
|
|
} else {
|
|
toastr.error(response.message || "Terjadi kesalahan");
|
|
}
|
|
},
|
|
error: function (xhr) {
|
|
// Re-enable button
|
|
saveButton
|
|
.removeClass("loading")
|
|
.prop("disabled", false)
|
|
.text(originalText);
|
|
|
|
var message = "Terjadi kesalahan";
|
|
if (xhr.responseJSON) {
|
|
if (xhr.responseJSON.message) {
|
|
message = xhr.responseJSON.message;
|
|
}
|
|
if (xhr.responseJSON.errors) {
|
|
// Show validation errors
|
|
var errorMessages = [];
|
|
for (var field in xhr.responseJSON.errors) {
|
|
var fieldName = field;
|
|
switch (field) {
|
|
case "work_id":
|
|
fieldName = "ID Pekerjaan";
|
|
break;
|
|
case "dealer_id":
|
|
fieldName = "ID Dealer";
|
|
break;
|
|
case "price":
|
|
fieldName = "Harga";
|
|
break;
|
|
case "currency":
|
|
fieldName = "Mata Uang";
|
|
break;
|
|
case "is_active":
|
|
fieldName = "Status Aktif";
|
|
break;
|
|
}
|
|
errorMessages.push(
|
|
fieldName +
|
|
": " +
|
|
xhr.responseJSON.errors[field][0]
|
|
);
|
|
}
|
|
message = errorMessages.join("\n");
|
|
}
|
|
}
|
|
toastr.error(message);
|
|
},
|
|
});
|
|
};
|
|
|
|
var saveAllPrices = function () {
|
|
// Prevent multiple clicks
|
|
var saveAllButton = $("#btn-save-all");
|
|
if (saveAllButton.hasClass("loading")) {
|
|
return; // Already processing
|
|
}
|
|
|
|
var prices = [];
|
|
var hasValidPrice = false;
|
|
|
|
$(".price-input").each(function () {
|
|
var dealerId = $(this).attr("name").replace("price_", "");
|
|
var price = $(this).val().replace(/[^\d]/g, ""); // Remove non-numeric characters
|
|
var statusInput = $('input[name="status_' + dealerId + '"]');
|
|
var isActive = statusInput.is(":checked");
|
|
|
|
if (price && parseInt(price) > 0) {
|
|
hasValidPrice = true;
|
|
prices.push({
|
|
dealer_id: dealerId,
|
|
price: parseInt(price),
|
|
currency: "IDR",
|
|
is_active: isActive,
|
|
});
|
|
}
|
|
});
|
|
|
|
if (!hasValidPrice) {
|
|
toastr.error("Minimal satu dealer harus memiliki harga yang valid");
|
|
return;
|
|
}
|
|
|
|
// Disable button and show loading state
|
|
saveAllButton.addClass("loading").prop("disabled", true);
|
|
var originalText = saveAllButton.text();
|
|
saveAllButton.text("Menyimpan...");
|
|
|
|
// Show confirmation
|
|
$("#confirmMessage").text(
|
|
"Apakah Anda yakin ingin menyimpan semua harga?"
|
|
);
|
|
$("#confirmModal").modal("show");
|
|
|
|
$("#confirmAction")
|
|
.off("click")
|
|
.on("click", function () {
|
|
$("#confirmModal").modal("hide");
|
|
|
|
$.ajax({
|
|
url: "/admin/work/" + workId + "/prices/bulk",
|
|
method: "POST",
|
|
data: {
|
|
prices: prices,
|
|
},
|
|
headers: {
|
|
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr(
|
|
"content"
|
|
),
|
|
},
|
|
success: function (response) {
|
|
// Re-enable button
|
|
saveAllButton
|
|
.removeClass("loading")
|
|
.prop("disabled", false)
|
|
.text(originalText);
|
|
|
|
if (response.status === 200) {
|
|
toastr.success(response.message);
|
|
|
|
// Show loading overlay
|
|
showLoadingOverlay("Memperbarui data...");
|
|
|
|
// Reload page to update all data
|
|
setTimeout(function () {
|
|
location.reload();
|
|
}, 1000);
|
|
} else {
|
|
toastr.error(
|
|
response.message || "Terjadi kesalahan"
|
|
);
|
|
}
|
|
},
|
|
error: function (xhr) {
|
|
// Re-enable button
|
|
saveAllButton
|
|
.removeClass("loading")
|
|
.prop("disabled", false)
|
|
.text(originalText);
|
|
|
|
var message = "Terjadi kesalahan";
|
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
message = xhr.responseJSON.message;
|
|
}
|
|
toastr.error(message);
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
var deletePrice = function (priceId, dealerId) {
|
|
$("#confirmMessage").text(
|
|
"Apakah Anda yakin ingin menghapus harga ini? Harga yang dihapus dapat dipulihkan dengan menyimpan ulang."
|
|
);
|
|
$("#confirmModal").modal("show");
|
|
|
|
$("#confirmAction")
|
|
.off("click")
|
|
.on("click", function () {
|
|
$("#confirmModal").modal("hide");
|
|
|
|
// Show loading overlay
|
|
showLoadingOverlay("Menghapus harga...");
|
|
|
|
$.ajax({
|
|
url: "/admin/work/" + workId + "/prices/" + priceId,
|
|
method: "DELETE",
|
|
headers: {
|
|
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr(
|
|
"content"
|
|
),
|
|
},
|
|
success: function (response) {
|
|
hideLoadingOverlay();
|
|
|
|
if (response.status === 200) {
|
|
toastr.success(response.message);
|
|
// Reset the row
|
|
resetRow(dealerId);
|
|
} else {
|
|
toastr.error(
|
|
response.message || "Terjadi kesalahan"
|
|
);
|
|
}
|
|
},
|
|
error: function (xhr) {
|
|
hideLoadingOverlay();
|
|
|
|
var message = "Terjadi kesalahan";
|
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
message = xhr.responseJSON.message;
|
|
}
|
|
toastr.error(message);
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
// Handle delete button click
|
|
$(document).on("click", ".delete-price", function () {
|
|
var priceId = $(this).data("price-id");
|
|
var dealerId = $(this).data("dealer-id");
|
|
deletePrice(priceId, dealerId);
|
|
});
|
|
|
|
var updateRowAfterSave = function (dealerId, data) {
|
|
var row = $('[data-dealer-id="' + dealerId + '"]');
|
|
var priceInput = row.find('input[name="price_' + dealerId + '"]');
|
|
var statusInput = row.find('input[name="status_' + dealerId + '"]');
|
|
var label = statusInput.siblings("label").find(".status-text");
|
|
var actionCell = row.find("td:last");
|
|
|
|
// Update price input if data contains price
|
|
if (data.price !== undefined) {
|
|
// Only update if the price actually changed
|
|
var currentDisplayValue = priceInput.val().replace(/[^\d]/g, "");
|
|
var newPrice = parseInt(data.price);
|
|
|
|
if (parseInt(currentDisplayValue) !== newPrice) {
|
|
priceInput.val(newPrice.toLocaleString("id-ID"));
|
|
// Update the original value for future comparisons
|
|
priceInput.data("original-value", newPrice.toString());
|
|
}
|
|
}
|
|
|
|
// If this is a new record (price = 0), update the save button
|
|
if (data.price === 0) {
|
|
actionCell
|
|
.find(".save-single")
|
|
.text("Simpan")
|
|
.removeClass("btn-warning")
|
|
.addClass("btn-success");
|
|
}
|
|
|
|
// Update status if data contains is_active
|
|
if (data.is_active !== undefined) {
|
|
statusInput.prop("checked", data.is_active);
|
|
if (data.is_active) {
|
|
label.text("Aktif").removeClass("inactive").addClass("active");
|
|
} else {
|
|
label
|
|
.text("Nonaktif")
|
|
.removeClass("active")
|
|
.addClass("inactive");
|
|
}
|
|
}
|
|
|
|
// Update save button if this is a new price save (not just status toggle)
|
|
if (data.price !== undefined) {
|
|
actionCell
|
|
.find(".save-single")
|
|
.text("Update")
|
|
.removeClass("btn-success")
|
|
.addClass("btn-warning");
|
|
|
|
// Update delete button
|
|
if (actionCell.find(".delete-price").length === 0) {
|
|
// Add delete button if it doesn't exist
|
|
var deleteBtn =
|
|
'<button type="button" class="btn btn-sm btn-danger delete-price" data-price-id="' +
|
|
data.id +
|
|
'" data-dealer-id="' +
|
|
dealerId +
|
|
'" title="Hapus Harga">Hapus</button>';
|
|
actionCell.find(".d-flex.flex-row.gap-1").append(deleteBtn);
|
|
} else {
|
|
// Update existing delete button with new price ID
|
|
actionCell.find(".delete-price").attr("data-price-id", data.id);
|
|
}
|
|
}
|
|
};
|
|
|
|
var toggleStatus = function (dealerId, isActive, checkbox, label) {
|
|
var data = {
|
|
dealer_id: parseInt(dealerId),
|
|
is_active: isActive,
|
|
};
|
|
|
|
$.ajax({
|
|
url: "/admin/work/" + workId + "/prices/toggle-status",
|
|
method: "POST",
|
|
data: data,
|
|
headers: {
|
|
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
|
|
},
|
|
beforeSend: function () {
|
|
// Show brief loading indicator on checkbox
|
|
checkbox.prop("disabled", true);
|
|
},
|
|
success: function (response) {
|
|
// Re-enable checkbox
|
|
checkbox.prop("disabled", false);
|
|
|
|
if (response.status === 200) {
|
|
toastr.success(response.message);
|
|
|
|
// Update UI if needed
|
|
if (response.data) {
|
|
// If this is a new record, update the row to show save button
|
|
if (response.data.price === 0) {
|
|
updateRowAfterSave(dealerId, response.data);
|
|
}
|
|
}
|
|
} else {
|
|
toastr.error(response.message || "Terjadi kesalahan");
|
|
// Revert checkbox state
|
|
checkbox.prop("checked", !isActive);
|
|
if (!isActive) {
|
|
label.text("Aktif");
|
|
} else {
|
|
label.text("Nonaktif");
|
|
}
|
|
}
|
|
},
|
|
error: function (xhr) {
|
|
// Re-enable checkbox
|
|
checkbox.prop("disabled", false);
|
|
|
|
var message = "Terjadi kesalahan";
|
|
if (xhr.responseJSON) {
|
|
if (xhr.responseJSON.message) {
|
|
message = xhr.responseJSON.message;
|
|
}
|
|
if (xhr.responseJSON.errors) {
|
|
var errorMessages = [];
|
|
for (var field in xhr.responseJSON.errors) {
|
|
var fieldName = field;
|
|
switch (field) {
|
|
case "dealer_id":
|
|
fieldName = "ID Dealer";
|
|
break;
|
|
case "is_active":
|
|
fieldName = "Status Aktif";
|
|
break;
|
|
}
|
|
errorMessages.push(
|
|
fieldName +
|
|
": " +
|
|
xhr.responseJSON.errors[field][0]
|
|
);
|
|
}
|
|
message = errorMessages.join("\n");
|
|
}
|
|
}
|
|
toastr.error(message);
|
|
|
|
// Revert checkbox state
|
|
checkbox.prop("checked", !isActive);
|
|
if (!isActive) {
|
|
label.text("Aktif");
|
|
} else {
|
|
label.text("Nonaktif");
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
var resetRow = function (dealerId) {
|
|
var row = $('[data-dealer-id="' + dealerId + '"]');
|
|
var priceInput = row.find('input[name="price_' + dealerId + '"]');
|
|
var statusInput = row.find('input[name="status_' + dealerId + '"]');
|
|
var label = statusInput.siblings("label").find(".status-text");
|
|
var actionCell = row.find("td:last");
|
|
|
|
// Reset price input
|
|
priceInput.val("0");
|
|
|
|
// Reset status
|
|
statusInput.prop("checked", false);
|
|
label.text("Nonaktif").removeClass("active").addClass("inactive");
|
|
|
|
// Remove delete button and update save button
|
|
actionCell.find(".delete-price").remove();
|
|
actionCell
|
|
.find(".save-single")
|
|
.text("Simpan")
|
|
.removeClass("btn-warning")
|
|
.addClass("btn-success");
|
|
};
|
|
|
|
// Public methods
|
|
return {
|
|
init: function () {
|
|
initTable();
|
|
initEvents();
|
|
// Initialize price formatting on page load
|
|
setTimeout(function () {
|
|
$(".price-input").each(function () {
|
|
var value = $(this).val();
|
|
if (value && value !== "0") {
|
|
var numValue = parseInt(value.replace(/[^\d]/g, ""));
|
|
if (!isNaN(numValue)) {
|
|
$(this).val(numValue.toLocaleString("id-ID"));
|
|
}
|
|
}
|
|
});
|
|
}, 100);
|
|
|
|
// Cleanup timeouts on page unload
|
|
$(window).on("beforeunload", function () {
|
|
for (var dealerId in saveTimeout) {
|
|
if (saveTimeout[dealerId]) {
|
|
clearTimeout(saveTimeout[dealerId]);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
};
|
|
})();
|
|
|
|
// On document ready
|
|
jQuery(document).ready(function () {
|
|
WorkPrices.init();
|
|
});
|