optimize dockerfile and copy js library used
This commit is contained in:
@@ -154,5 +154,289 @@
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script src="{{ mix('js/warehouse_management/mutations/create.js') }}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
let productIndex = 1;
|
||||
let originalProductOptions = ""; // Store original product options
|
||||
|
||||
// Initialize Select2
|
||||
$(".select2").select2({
|
||||
placeholder: "Pilih...",
|
||||
allowClear: true,
|
||||
});
|
||||
|
||||
// Store original product options on page load
|
||||
const firstSelect = $(".product-select").first();
|
||||
if (firstSelect.length > 0) {
|
||||
originalProductOptions = firstSelect.html();
|
||||
}
|
||||
|
||||
// Prevent same dealer selection
|
||||
$("#from_dealer_id, #to_dealer_id").on("change", function () {
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
const toDealerId = $("#to_dealer_id").val();
|
||||
|
||||
if (fromDealerId && toDealerId && fromDealerId === toDealerId) {
|
||||
$(this).val("").trigger("change");
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Dealer asal dan tujuan tidak boleh sama",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update available stock when dealer changes
|
||||
updateAllAvailableStock();
|
||||
});
|
||||
|
||||
// Add new product row
|
||||
$("#add-product").on("click", function () {
|
||||
const newRow = createProductRow(productIndex);
|
||||
$("#products-tbody").append(newRow);
|
||||
|
||||
// Initialize Select2 for new row after it's added to DOM
|
||||
const newSelect = $(
|
||||
`#products-tbody tr[data-index="${productIndex}"] .product-select`
|
||||
);
|
||||
newSelect.select2({
|
||||
placeholder: "Pilih...",
|
||||
allowClear: true,
|
||||
});
|
||||
|
||||
productIndex++;
|
||||
updateRemoveButtons();
|
||||
});
|
||||
|
||||
// Remove product row
|
||||
$(document).on("click", ".remove-product", function () {
|
||||
$(this).closest("tr").remove();
|
||||
updateRemoveButtons();
|
||||
reindexRows();
|
||||
});
|
||||
|
||||
// Handle product selection change
|
||||
$(document).on("change", ".product-select", function () {
|
||||
const row = $(this).closest("tr");
|
||||
const productId = $(this).val();
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
|
||||
if (productId && fromDealerId) {
|
||||
getAvailableStock(productId, fromDealerId, row);
|
||||
} else {
|
||||
row.find(".available-stock").text("-");
|
||||
row.find(".quantity-input").attr("max", "");
|
||||
}
|
||||
});
|
||||
|
||||
// Validate quantity input
|
||||
$(document).on("input", ".quantity-input", function () {
|
||||
const maxValue = parseFloat($(this).attr("max"));
|
||||
const currentValue = parseFloat($(this).val());
|
||||
|
||||
if (maxValue && currentValue > maxValue) {
|
||||
$(this).val(maxValue);
|
||||
$(this).addClass("is-invalid");
|
||||
|
||||
if (!$(this).siblings(".invalid-feedback").length) {
|
||||
$(this).after(
|
||||
'<div class="invalid-feedback">Quantity melebihi stock yang tersedia</div>'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass("is-invalid");
|
||||
$(this).siblings(".invalid-feedback").remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission
|
||||
$("#mutation-form").on("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const submitBtn = $("#submit-btn");
|
||||
const originalText = submitBtn.html();
|
||||
|
||||
submitBtn
|
||||
.prop("disabled", true)
|
||||
.html('<i class="la la-spinner la-spin"></i> Menyimpan...');
|
||||
|
||||
// Submit form
|
||||
this.submit();
|
||||
});
|
||||
|
||||
function createProductRow(index) {
|
||||
return `
|
||||
<tr class="product-row" data-index="${index}">
|
||||
<td>
|
||||
<select name="products[${index}][product_id]" class="form-control product-select" required>
|
||||
${originalProductOptions}
|
||||
</select>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="available-stock text-muted">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
name="products[${index}][quantity_requested]"
|
||||
class="form-control quantity-input"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
placeholder="0"
|
||||
required>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm remove-product">
|
||||
<i class="la la-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateRemoveButtons() {
|
||||
const rows = $(".product-row");
|
||||
$(".remove-product").prop("disabled", rows.length <= 1);
|
||||
}
|
||||
|
||||
function reindexRows() {
|
||||
$(".product-row").each(function (index) {
|
||||
$(this).attr("data-index", index);
|
||||
$(this)
|
||||
.find('select[name*="product_id"]')
|
||||
.attr("name", `products[${index}][product_id]`);
|
||||
$(this)
|
||||
.find('input[name*="quantity_requested"]')
|
||||
.attr("name", `products[${index}][quantity_requested]`);
|
||||
});
|
||||
productIndex = $(".product-row").length;
|
||||
}
|
||||
|
||||
function getAvailableStock(productId, dealerId, row) {
|
||||
$.ajax({
|
||||
url: "/warehouse/mutations/get-product-stock",
|
||||
method: "GET",
|
||||
data: {
|
||||
product_id: productId,
|
||||
dealer_id: dealerId,
|
||||
},
|
||||
beforeSend: function () {
|
||||
row.find(".available-stock").html(
|
||||
'<i class="la la-spinner la-spin"></i>'
|
||||
);
|
||||
},
|
||||
success: function (response) {
|
||||
const stock = parseFloat(response.current_stock);
|
||||
row.find(".available-stock").text(stock.toLocaleString());
|
||||
row.find(".quantity-input").attr("max", stock);
|
||||
|
||||
// Set max value message
|
||||
if (stock <= 0) {
|
||||
row.find(".available-stock")
|
||||
.addClass("text-danger")
|
||||
.removeClass("text-muted");
|
||||
row.find(".quantity-input").attr("readonly", true).val("");
|
||||
} else {
|
||||
row.find(".available-stock")
|
||||
.removeClass("text-danger")
|
||||
.addClass("text-muted");
|
||||
row.find(".quantity-input").attr("readonly", false);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
row.find(".available-stock")
|
||||
.text("Error")
|
||||
.addClass("text-danger");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateAllAvailableStock() {
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
|
||||
$(".product-row").each(function () {
|
||||
const row = $(this);
|
||||
const productId = row.find(".product-select").val();
|
||||
|
||||
if (productId && fromDealerId) {
|
||||
getAvailableStock(productId, fromDealerId, row);
|
||||
} else {
|
||||
row.find(".available-stock").text("-");
|
||||
row.find(".quantity-input").attr("max", "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
const fromDealerId = $("#from_dealer_id").val();
|
||||
const toDealerId = $("#to_dealer_id").val();
|
||||
|
||||
// Check dealers
|
||||
if (!fromDealerId) {
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Pilih dealer asal",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!toDealerId) {
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Pilih dealer tujuan",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fromDealerId === toDealerId) {
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Dealer asal dan tujuan tidak boleh sama",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check products
|
||||
const productRows = $(".product-row");
|
||||
if (productRows.length === 0) {
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Tambahkan minimal satu produk",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let hasValidProduct = false;
|
||||
productRows.each(function () {
|
||||
const productId = $(this).find(".product-select").val();
|
||||
const quantity = $(this).find(".quantity-input").val();
|
||||
|
||||
if (productId && quantity && parseFloat(quantity) > 0) {
|
||||
hasValidProduct = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasValidProduct) {
|
||||
Swal.fire({
|
||||
type: "error",
|
||||
title: "Oops...",
|
||||
text: "Pilih minimal satu produk dengan quantity yang valid",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
@@ -126,5 +126,426 @@ input.datepicker {
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
<script src="{{ mix('js/warehouse_management/mutations/index.js') }}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
console.log("Mutations index.js loaded");
|
||||
|
||||
// Check if DataTables is available
|
||||
if (typeof $.fn.DataTable === "undefined") {
|
||||
console.error("DataTables not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize components
|
||||
initializeSelect2();
|
||||
initializeDatepickers();
|
||||
|
||||
// Wait for DOM to be fully ready
|
||||
setTimeout(function () {
|
||||
initializeDataTable();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
function initializeSelect2() {
|
||||
console.log("Initializing Select2...");
|
||||
|
||||
// Initialize Select2 for dealer filter - same as stock audit
|
||||
if (typeof $.fn.select2 !== "undefined") {
|
||||
$("#dealer_filter").select2({
|
||||
placeholder: "Pilih...",
|
||||
allowClear: true,
|
||||
width: "100%",
|
||||
});
|
||||
} else {
|
||||
console.warn("Select2 not available, using regular select");
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDatepickers() {
|
||||
console.log("Initializing datepickers...");
|
||||
|
||||
// Check if bootstrap datepicker is available
|
||||
if (typeof $.fn.datepicker === "undefined") {
|
||||
console.error("Bootstrap Datepicker not available!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize start date picker
|
||||
$("#date_from")
|
||||
.datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
autoclose: true,
|
||||
todayHighlight: true,
|
||||
orientation: "bottom left",
|
||||
templates: {
|
||||
leftArrow: '<i class="la la-angle-left"></i>',
|
||||
rightArrow: '<i class="la la-angle-right"></i>',
|
||||
},
|
||||
endDate: new Date(), // Don't allow future dates
|
||||
clearBtn: true,
|
||||
})
|
||||
.on("changeDate", function (e) {
|
||||
console.log("Start date selected:", e.format());
|
||||
enableEndDatePicker(e.format());
|
||||
})
|
||||
.on("clearDate", function (e) {
|
||||
console.log("Start date cleared");
|
||||
resetEndDatePicker();
|
||||
});
|
||||
|
||||
// Initialize end date picker
|
||||
initializeEndDatePicker();
|
||||
|
||||
// Initially disable end date input
|
||||
$("#date_to").prop("disabled", true);
|
||||
}
|
||||
|
||||
function enableEndDatePicker(startDate) {
|
||||
console.log("Enabling end date picker with min date:", startDate);
|
||||
|
||||
// Enable the input
|
||||
$("#date_to").prop("disabled", false);
|
||||
|
||||
// Remove existing datepicker
|
||||
$("#date_to").datepicker("remove");
|
||||
|
||||
// Re-initialize with new startDate constraint
|
||||
$("#date_to")
|
||||
.datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
autoclose: true,
|
||||
todayHighlight: true,
|
||||
orientation: "bottom left",
|
||||
templates: {
|
||||
leftArrow: '<i class="la la-angle-left"></i>',
|
||||
rightArrow: '<i class="la la-angle-right"></i>',
|
||||
},
|
||||
startDate: startDate, // Set minimum date to selected start date
|
||||
endDate: new Date(), // Don't allow future dates
|
||||
clearBtn: true,
|
||||
})
|
||||
.on("changeDate", function (e) {
|
||||
console.log("End date selected:", e.format());
|
||||
})
|
||||
.on("clearDate", function (e) {
|
||||
console.log("End date cleared");
|
||||
});
|
||||
|
||||
console.log("End date picker enabled with startDate:", startDate);
|
||||
}
|
||||
|
||||
function initializeEndDatePicker() {
|
||||
$("#date_to")
|
||||
.datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
autoclose: true,
|
||||
todayHighlight: true,
|
||||
orientation: "bottom left",
|
||||
templates: {
|
||||
leftArrow: '<i class="la la-angle-left"></i>',
|
||||
rightArrow: '<i class="la la-angle-right"></i>',
|
||||
},
|
||||
endDate: new Date(), // Don't allow future dates
|
||||
clearBtn: true,
|
||||
})
|
||||
.on("changeDate", function (e) {
|
||||
console.log("End date selected:", e.format());
|
||||
})
|
||||
.on("clearDate", function (e) {
|
||||
console.log("End date cleared");
|
||||
});
|
||||
}
|
||||
|
||||
// Calendar icons and click handlers removed since bootstrap datepicker handles these automatically
|
||||
|
||||
function initializeDataTable() {
|
||||
console.log("Initializing DataTable...");
|
||||
|
||||
// Destroy existing table if any
|
||||
if ($.fn.DataTable.isDataTable("#mutations-table")) {
|
||||
$("#mutations-table").DataTable().destroy();
|
||||
}
|
||||
|
||||
// Initialize DataTable
|
||||
const table = $("#mutations-table").DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
destroy: true,
|
||||
ajax: {
|
||||
url: $("#mutations-table").data("url"),
|
||||
type: "GET",
|
||||
data: function (d) {
|
||||
// Add filter parameters
|
||||
d.dealer_filter = $("#dealer_filter").val();
|
||||
d.date_from = $("#date_from").val();
|
||||
d.date_to = $("#date_to").val();
|
||||
|
||||
console.log("AJAX data being sent:", {
|
||||
dealer_filter: d.dealer_filter,
|
||||
date_from: d.date_from,
|
||||
date_to: d.date_to,
|
||||
});
|
||||
|
||||
return d;
|
||||
},
|
||||
error: function (xhr, error, code) {
|
||||
console.error("DataTables AJAX error:", error, code);
|
||||
console.error("Response:", xhr.responseText);
|
||||
},
|
||||
},
|
||||
columnDefs: [
|
||||
{ targets: 0, width: "5%" }, // No. column
|
||||
{ targets: 8, width: "20%", className: "text-center" }, // Action column
|
||||
{ targets: [6, 7], className: "text-center" }, // Total Items and Status columns
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
data: "DT_RowIndex",
|
||||
name: "DT_RowIndex",
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
data: "mutation_number",
|
||||
name: "mutation_number",
|
||||
orderable: true,
|
||||
},
|
||||
{ data: "created_at", name: "created_at", orderable: true },
|
||||
{ data: "from_dealer", name: "from_dealer", orderable: true },
|
||||
{ data: "to_dealer", name: "to_dealer", orderable: true },
|
||||
{ data: "requested_by", name: "requested_by", orderable: true },
|
||||
{ data: "total_items", name: "total_items", orderable: true },
|
||||
{ data: "status", name: "status", orderable: true },
|
||||
{
|
||||
data: "action",
|
||||
name: "action",
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
},
|
||||
],
|
||||
order: [[1, "desc"]], // Order by mutation_number desc
|
||||
pageLength: 10,
|
||||
responsive: true,
|
||||
ordering: true,
|
||||
orderMulti: false,
|
||||
});
|
||||
|
||||
// Setup filter button handlers
|
||||
setupFilterHandlers(table);
|
||||
|
||||
// Setup other event handlers
|
||||
setupTableEventHandlers(table);
|
||||
}
|
||||
|
||||
function setupFilterHandlers(table) {
|
||||
// Handle Filter Search Button
|
||||
$("#kt_search").on("click", function () {
|
||||
console.log("Filter button clicked");
|
||||
|
||||
const dealerFilter = $("#dealer_filter").val();
|
||||
const dateFrom = $("#date_from").val();
|
||||
const dateTo = $("#date_to").val();
|
||||
|
||||
console.log("Filtering with:", {
|
||||
dealer: dealerFilter,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
});
|
||||
|
||||
table.ajax.reload();
|
||||
});
|
||||
|
||||
// Handle Filter Reset Button
|
||||
$("#kt_reset").on("click", function () {
|
||||
console.log("Reset button clicked");
|
||||
|
||||
// Reset select2 elements properly - same as stock audit
|
||||
$("#dealer_filter").val(null).trigger("change.select2");
|
||||
|
||||
// Clear datepicker values using bootstrap datepicker method
|
||||
$("#date_from").datepicker("clearDates");
|
||||
$("#date_to").datepicker("clearDates");
|
||||
|
||||
// Reset end date picker and disable it
|
||||
resetEndDatePicker();
|
||||
|
||||
// Reload table
|
||||
table.ajax.reload();
|
||||
});
|
||||
|
||||
// Handle Enter key on date inputs
|
||||
$("#date_from, #date_to").on("keypress", function (e) {
|
||||
if (e.which === 13) {
|
||||
// Enter key
|
||||
$("#kt_search").click();
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-filter when dealer selection changes
|
||||
$("#dealer_filter").on("change", function () {
|
||||
console.log("Dealer filter changed:", $(this).val());
|
||||
// Uncomment the line below if you want auto-filter on dealer change
|
||||
// table.ajax.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function resetEndDatePicker() {
|
||||
// Remove existing datepicker
|
||||
$("#date_to").datepicker("remove");
|
||||
|
||||
// Clear the input value
|
||||
$("#date_to").val("");
|
||||
|
||||
// Re-initialize without startDate constraint
|
||||
initializeEndDatePicker();
|
||||
|
||||
// Disable the input
|
||||
$("#date_to").prop("disabled", true);
|
||||
|
||||
console.log("End date picker reset and disabled");
|
||||
}
|
||||
|
||||
function setupTableEventHandlers(table) {
|
||||
// Debug ordering events
|
||||
table.on("order.dt", function () {
|
||||
console.log("Order changed:", table.order());
|
||||
});
|
||||
|
||||
// Add loading indicator for ordering
|
||||
table.on("processing.dt", function (e, settings, processing) {
|
||||
if (processing) {
|
||||
console.log("DataTable processing started");
|
||||
} else {
|
||||
console.log("DataTable processing finished");
|
||||
}
|
||||
});
|
||||
|
||||
// Manual click handler for column headers (fallback)
|
||||
$("#mutations-table thead th").on("click", function () {
|
||||
const columnIndex = $(this).index();
|
||||
console.log("Column header clicked:", columnIndex, $(this).text());
|
||||
|
||||
// Skip if it's the first (No.) or last (Action) column
|
||||
if (columnIndex === 0 || columnIndex === 8) {
|
||||
console.log("Non-sortable column clicked, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if DataTables is handling the click
|
||||
if (
|
||||
$(this).hasClass("sorting") ||
|
||||
$(this).hasClass("sorting_asc") ||
|
||||
$(this).hasClass("sorting_desc")
|
||||
) {
|
||||
console.log("DataTables should handle this click");
|
||||
} else {
|
||||
console.log("DataTables not handling click, manual trigger needed");
|
||||
// Force DataTables to handle the ordering
|
||||
table.order([columnIndex, "asc"]).draw();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Cancel Button Click with SweetAlert
|
||||
$(document).on("click", ".btn-cancel", function () {
|
||||
const mutationId = $(this).data("id");
|
||||
handleCancelMutation(mutationId, table);
|
||||
});
|
||||
|
||||
// Handle form submissions with loading state
|
||||
$(document).on("submit", ".approve-form", function () {
|
||||
$(this)
|
||||
.find('button[type="submit"]')
|
||||
.prop("disabled", true)
|
||||
.html("Memproses...");
|
||||
});
|
||||
|
||||
// Validate quantity approved in receive modal
|
||||
$(document).on("input", 'input[name*="quantity_approved"]', function () {
|
||||
validateQuantityInput($(this));
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancelMutation(mutationId, table) {
|
||||
if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Batalkan Mutasi?",
|
||||
text: "Apakah Anda yakin ingin membatalkan mutasi ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#d33",
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonText: "Ya, Batalkan",
|
||||
cancelButtonText: "Batal",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
cancelMutation(mutationId, table);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (confirm("Apakah Anda yakin ingin membatalkan mutasi ini?")) {
|
||||
cancelMutation(mutationId, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateQuantityInput(input) {
|
||||
const maxValue = parseFloat(input.attr("max"));
|
||||
const currentValue = parseFloat(input.val());
|
||||
|
||||
if (maxValue && currentValue > maxValue) {
|
||||
input.val(maxValue);
|
||||
input.addClass("is-invalid");
|
||||
|
||||
if (!input.siblings(".invalid-feedback").length) {
|
||||
input.after(
|
||||
'<div class="invalid-feedback">Quantity tidak boleh melebihi yang diminta</div>'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
input.removeClass("is-invalid");
|
||||
input.siblings(".invalid-feedback").remove();
|
||||
}
|
||||
}
|
||||
|
||||
function cancelMutation(mutationId, table) {
|
||||
$.ajax({
|
||||
url: "/warehouse/mutations/" + mutationId + "/cancel",
|
||||
type: "POST",
|
||||
data: {
|
||||
_token: $('meta[name="csrf-token"]').attr("content"),
|
||||
},
|
||||
success: function (response) {
|
||||
if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Berhasil!",
|
||||
text: "Mutasi berhasil dibatalkan",
|
||||
icon: "success",
|
||||
timer: 2000,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
} else {
|
||||
alert("Mutasi berhasil dibatalkan");
|
||||
}
|
||||
|
||||
// Reload table
|
||||
table.ajax.reload();
|
||||
},
|
||||
error: function (xhr) {
|
||||
const errorMsg =
|
||||
xhr.responseJSON?.message || "Gagal membatalkan mutasi";
|
||||
|
||||
if (typeof Swal !== "undefined") {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: errorMsg,
|
||||
icon: "error",
|
||||
});
|
||||
} else {
|
||||
alert("Error: " + errorMsg);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user