create feature sa create list claim and price to work per dealer
This commit is contained in:
655
public/js/pages/back/master/work-prices.js
Normal file
655
public/js/pages/back/master/work-prices.js
Normal file
@@ -0,0 +1,655 @@
|
||||
"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();
|
||||
});
|
||||
Reference in New Issue
Block a user