partial update close modal on all page and disable create transaction with no stock

This commit is contained in:
2025-06-25 14:01:21 +07:00
parent c3233ea6b2
commit e96ca0a83c
15 changed files with 693 additions and 572 deletions

View File

@@ -55,6 +55,7 @@
<div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">

View File

@@ -56,6 +56,7 @@
<div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
@@ -93,6 +94,7 @@
<div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">

View File

@@ -119,6 +119,7 @@
<div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
@@ -364,6 +365,16 @@ function bulkClose(url, selected) {
}
jQuery(document).ready(function () {
// Add event handlers for modal close buttons
$('.close, [data-dismiss="modal"]').on('click', function() {
$('#transactionModal').modal('hide');
});
// Also handle the "Batal" button
$('.btn-secondary[data-dismiss="modal"]').on('click', function() {
$('#transactionModal').modal('hide');
});
KTBootstrapDatepicker.init();
$('#kt_search').on('click', function (e) {

View File

@@ -48,18 +48,14 @@
<td>{{ $loop->iteration }}</td>
<td>{{ $role->name }}</td>
<td>
@can('update', $menus['roleprivileges.index'])
<button class="btn btn-sm btn-warning" onclick="editRole({{$role->id}})"><i class="fa fa-edit"></i> Edit</button>
@endcan
@can('delete', $menus['roleprivileges.index'])
<br>
<br>
<form action="{{ route('roleprivileges.delete', $role->id) }}" method="post" onsubmit="return confirm('Anda akan menghapus role {{ $role->name }}?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-trash"></i> Hapus</button>
</form>
@endcan
<div class="d-flex">
@can('update', $menus['roleprivileges.index'])
<button class="btn btn-sm btn-bold btn-warning mr-2" onclick="editRole({{$role->id}})"> Edit</button>
@endcan
@can('delete', $menus['roleprivileges.index'])
<button class="btn btn-sm btn-bold btn-danger" onclick="deleteRole({{$role->id}}, '{{$role->name}}')">Hapus</button>
@endcan
</div>
</td>
</tr>
@endforeach
@@ -191,6 +187,41 @@
@section('javascripts')
<script>
function deleteRole(id, name) {
Swal.fire({
title: 'Hapus Role?',
text: `Anda akan menghapus role ${name}?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Ya, Hapus!',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
// Create form and submit
let form = document.createElement('form');
form.method = 'POST';
form.action = '{{ route("roleprivileges.delete", ":id") }}'.replace(':id', id);
let csrfToken = document.createElement('input');
csrfToken.type = 'hidden';
csrfToken.name = '_token';
csrfToken.value = '{{ csrf_token() }}';
let methodField = document.createElement('input');
methodField.type = 'hidden';
methodField.name = '_method';
methodField.value = 'DELETE';
form.appendChild(csrfToken);
form.appendChild(methodField);
document.body.appendChild(form);
form.submit();
}
});
}
function editRole(id) {
let url = '{{ route("roleprivileges.edit", ":id") }}'
url = url.replace(':id', id);
@@ -265,5 +296,18 @@
}
})
}
$(document).ready(function () {
// Add event handlers for modal close buttons
$('.close, [data-dismiss="modal"]').on("click", function () {
$("#roleModal").modal("hide");
$("#roleEditModal").modal("hide");
});
// Also handle the "Close" button
$('.btn-secondary[data-dismiss="modal"]').on("click", function () {
$("#roleModal").modal("hide");
$("#roleEditModal").modal("hide");
});
});
</script>
@endsection

View File

@@ -51,6 +51,7 @@
<div class="modal-header">
<h5 class="modal-title" id="modalHeading"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">

View File

@@ -87,14 +87,7 @@ use Illuminate\Support\Facades\Auth;
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.physical-stock.is-invalid {
border-color: #dc3545;
background-color: #fff5f5;
}
.physical-stock.is-invalid:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
.quantity-input-mutasi.is-invalid {
border-color: #dc3545;
background-color: #fff5f5;
@@ -424,16 +417,7 @@ use Illuminate\Support\Facades\Auth;
<input type="hidden" name="mechanic_id" value="{{ $mechanic->id }}">
<input type="hidden" name="dealer_id" value="{{ $mechanic->dealer_id }}">
<!-- Stock Error Display -->
@if($errors->has('stock'))
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<strong><i class="fa fa-exclamation-triangle"></i> Peringatan Stock:</strong>
<br>{!! $errors->first('stock') !!}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
@endif
@if($errors->has('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
@@ -473,7 +457,7 @@ use Illuminate\Support\Facades\Auth;
<div class="col-6">
<label>Warranty</label>
<select name="warranty" class="form-control @if(old('form') == 'work') @error('warranty') is-invalid @enderror @endif">
<option selected>Warranty</option>
<option value="" @if(old('form') == 'work') @if(old('warranty') == '' || old('warranty') == null) selected @endif @else selected @endif>Warranty</option>
<option value="1" @if(old('form') == 'work') @if(old('warranty') == 1) selected @endif @endif>Ya</option>
<option value="0" @if(old('form') == 'work') @if(old('warranty') == 0) selected @endif @endif>Tidak</option>
</select>
@@ -676,15 +660,17 @@ use Illuminate\Support\Facades\Auth;
<div class="col-6">
<label>Warranty</label>
<select name="warranty" class="form-control @if(old('form') == 'wash') @error('warranty') is-invalid @enderror @endif">
<option selected>Warranty</option>
<option value="" @if(old('form') == 'wash') @if(old('warranty') == '' || old('warranty') == null) selected @endif @else selected @endif>Warranty</option>
<option value="1" @if(old('form') == 'wash') @if(old('warranty') == 1) selected @endif @endif>Ya</option>
<option value="0" @if(old('form') == 'wash') @if(old('warranty') == 0) selected @endif @endif>Tidak</option>
</select>
@if(old('form') == 'wash')
@error('warranty')
<span class="invalid-feedback" role="alert">
<strong>{!! $message !!}</strong>
</span>
@enderror
@endif
</div>
<div class="col-6">
<label>Tanggal Pekerjaan</label>
@@ -988,112 +974,8 @@ use Illuminate\Support\Facades\Auth;
}
});
// Global variables for stock checking
let dealerId = {{ $mechanic->dealer_id }};
let stockWarnings = {};
// Function to check stock availability for selected work
function checkStockAvailability(workId, quantity, callback) {
if (!workId || !quantity || quantity < 1) {
if (callback) callback(null);
return;
}
$.ajax({
url: "{{ route('transaction.check-stock') }}",
method: 'POST',
data: {
work_id: workId,
dealer_id: dealerId,
quantity: quantity,
_token: $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
if (callback) callback(response.data);
},
error: function(xhr) {
console.error('Error checking stock:', xhr);
if (callback) callback(null);
}
});
}
// Function to display stock warning
function displayStockWarning(fieldId, stockData) {
// Remove existing warning
$(`#stock-warning-${fieldId}`).remove();
if (!stockData || stockData.available) {
return; // No warning needed
}
let warningHtml = `
<div id="stock-warning-${fieldId}" class="alert alert-warning mt-2 mb-0" style="font-size: 12px;">
<strong><i class="fa fa-exclamation-triangle"></i> Peringatan Stock:</strong> ${stockData.message}
<ul class="mb-0 mt-1" style="font-size: 11px;">
`;
stockData.details.forEach(function(detail) {
if (!detail.is_available) {
warningHtml += `
<li>${detail.product_name}: Butuh ${detail.required_quantity}, Tersedia ${detail.available_stock}</li>
`;
}
});
warningHtml += `
</ul>
</div>
`;
$(`#work_field${fieldId.replace('work_work', '')}`).append(warningHtml);
}
// Function to handle work selection change
function handleWorkSelectionChange(selectElement) {
let workId = $(selectElement).val();
let fieldId = $(selectElement).attr('id');
let quantityInput = $(selectElement).closest('.form-group').find('input[name="quantity[]"]');
let quantity = parseInt(quantityInput.val()) || 1;
if (workId) {
checkStockAvailability(workId, quantity, function(stockData) {
displayStockWarning(fieldId, stockData);
// Store warning data for form submission validation
if (stockData && !stockData.available) {
stockWarnings[fieldId] = stockData;
} else {
delete stockWarnings[fieldId];
}
});
} else {
// Remove warning when no work selected
$(`#stock-warning-${fieldId}`).remove();
delete stockWarnings[fieldId];
}
}
// Function to handle quantity change
function handleQuantityChange(quantityInput) {
let workSelect = $(quantityInput).closest('.form-group').find('select[name="work_id[]"]');
let workId = workSelect.val();
let quantity = parseInt($(quantityInput).val()) || 1;
if (workId && quantity > 0) {
let fieldId = workSelect.attr('id');
checkStockAvailability(workId, quantity, function(stockData) {
displayStockWarning(fieldId, stockData);
// Store warning data for form submission validation
if (stockData && !stockData.available) {
stockWarnings[fieldId] = stockData;
} else {
delete stockWarnings[fieldId];
}
});
}
}
// Global variables (stock checking removed)
let dealerId = {{ $mechanic->dealer_id }};
function logout(event){
event.preventDefault();
@@ -1191,24 +1073,8 @@ use Illuminate\Support\Facades\Auth;
})
}
// Add event listeners for existing fields
// Setup form fields without stock checking
$(document).ready(function() {
// Initial fields (work1, work2, etc.)
$('select[name="work_id[]"]').on('change', function() {
handleWorkSelectionChange(this);
});
$('input[name="quantity[]"]').on('input', function() {
handleQuantityChange(this);
});
// Check stock for pre-filled fields
$('select[name="work_id[]"]').each(function() {
if ($(this).val()) {
handleWorkSelectionChange(this);
}
});
// Ensure placeholder options are properly disabled and not selectable
$('select[name="work_id[]"]').each(function() {
var $select = $(this);
@@ -1241,19 +1107,24 @@ use Illuminate\Support\Facades\Auth;
if ($placeholder.length) {
$placeholder.prop('selected', true);
}
} else {
// Trigger change event to update any dependent fields
$select.trigger('change');
}
});
// Update field counter to match the actual number of work fields when validation fails
@if(old('form') == 'work' && old('work_id'))
var actualFieldCount = $('select[name="work_id[]"]').length;
$('.work_field_counter').val(actualFieldCount);
console.log('Validation failed - updating field counter to:', actualFieldCount);
console.log('Old work_id values:', @json(old('work_id')));
@endif
@endif
});
// Override addFormField function to include event listeners
// Function to add form fields (stock checking removed)
function addFormFieldWithStockCheck(form) {
addFormField(form); // Call original function
// Add event listeners to new field
// Setup placeholder for new field
setTimeout(function() {
var id = $(`.${form}_field_counter`).val();
var $newSelect = $(`#${form}_work${id}`);
@@ -1264,14 +1135,6 @@ use Illuminate\Support\Facades\Auth;
$placeholder.prop('disabled', true).prop('selected', true).val('');
}
$newSelect.on('change', function() {
handleWorkSelectionChange(this);
});
$newSelect.closest('.form-group').find('input[name="quantity[]"]').on('input', function() {
handleQuantityChange(this);
});
// Prevent auto-selection of first non-disabled option
$newSelect.on('focus', function() {
if (!$(this).val()) {
@@ -1287,6 +1150,7 @@ use Illuminate\Support\Facades\Auth;
var policeNumber = $('input[name="police_number"]').val().trim();
var userSaId = $('select[name="user_sa_id"]').val();
var date = $('input[name="date"]').val().trim();
var warranty = $('select[name="warranty"]').val();
var errorMessages = [];
@@ -1318,6 +1182,13 @@ use Illuminate\Support\Facades\Auth;
$('input[name="date"]').removeClass('is-invalid');
}
if (!warranty || warranty === '' || warranty === 'Warranty') {
errorMessages.push('Warranty harus dipilih');
$('select[name="warranty"]').addClass('is-invalid');
} else {
$('select[name="warranty"]').removeClass('is-invalid');
}
if (errorMessages.length > 0) {
e.preventDefault();
Swal.fire({
@@ -1336,68 +1207,66 @@ use Illuminate\Support\Facades\Auth;
return false;
}
// Validate that at least one work is selected
var hasSelectedWork = false;
$('select[name="work_id[]"]').each(function() {
if ($(this).val() && $(this).val() !== '') {
hasSelectedWork = true;
return false; // break loop
// Validate that at least one work is selected with valid quantity
var hasValidWorkQuantity = false;
var validCount = 0;
var emptyWorkSelects = [];
var emptyQuantityInputs = [];
$('select[name="work_id[]"]').each(function(index) {
var workId = $(this).val();
var $quantityInput = $(this).closest('.form-group').find('input[name="quantity[]"]');
var quantity = $quantityInput.val();
// Remove previous error styling
$(this).removeClass('is-invalid');
$quantityInput.removeClass('is-invalid');
// Check if this pair is valid (both work and quantity filled)
if (workId && workId !== '' && quantity && parseInt(quantity) > 0) {
hasValidWorkQuantity = true;
validCount++;
} else if (workId && workId !== '' && (!quantity || parseInt(quantity) <= 0)) {
// Work selected but no valid quantity
emptyQuantityInputs.push($quantityInput);
} else if ((!workId || workId === '') && quantity && parseInt(quantity) > 0) {
// Quantity filled but no work selected
emptyWorkSelects.push($(this));
}
});
if (!hasSelectedWork) {
if (!hasValidWorkQuantity) {
e.preventDefault();
// Highlight problematic fields
emptyWorkSelects.forEach(function($select) {
$select.addClass('is-invalid');
});
emptyQuantityInputs.forEach(function($input) {
$input.addClass('is-invalid');
});
var message = 'Minimal pilih satu pekerjaan dan isi quantity-nya sebelum menyimpan!';
if (emptyWorkSelects.length > 0) {
message += '\n\n• Ada quantity yang diisi tanpa memilih pekerjaan';
}
if (emptyQuantityInputs.length > 0) {
message += '\n\n• Ada pekerjaan yang dipilih tanpa mengisi quantity';
}
Swal.fire({
title: 'Peringatan',
text: 'Minimal pilih satu pekerjaan sebelum menyimpan!',
title: 'Peringatan Validasi',
text: message,
icon: 'warning',
confirmButtonText: 'OK'
});
return false;
}
// Check if there are stock warnings
if (Object.keys(stockWarnings).length > 0) {
e.preventDefault();
let warningMessages = [];
Object.values(stockWarnings).forEach(function(warning) {
warningMessages.push(warning.message);
warning.details.forEach(function(detail) {
if (!detail.is_available) {
warningMessages.push(`- ${detail.product_name}: Butuh ${detail.required_quantity}, Tersedia ${detail.available_stock}`);
}
});
});
// Show info about how many valid pairs will be saved
console.log(`Akan menyimpan ${validCount} pekerjaan`);
Swal.fire({
title: 'Peringatan Stock Tidak Mencukupi',
html: `
<div class="text-left">
<p class="mb-3">Ada beberapa pekerjaan yang memerlukan produk dengan stock tidak mencukupi:</p>
<div class="alert alert-warning text-left">
${warningMessages.join('<br>')}
</div>
<p class="mb-0"><strong>Apakah Anda yakin ingin melanjutkan?</strong></p>
<small class="text-muted">Transaksi akan tetap dibuat, namun stock akan menjadi negatif.</small>
</div>
`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ffc107',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Lanjutkan',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
$(".button-save").attr("disabled", true);
$(".button-save").addClass("disabled");
$("#workForm")[0].submit(); // Submit the form
}
});
return false;
}
$(".button-save").attr("disabled", true);
$(".button-save").addClass("disabled");
@@ -1405,40 +1274,42 @@ use Illuminate\Support\Facades\Auth;
})
$("#washForm").submit(function(e) {
// Validate required fields
var spkNo = $('input[name="spk_no"]').val().trim();
var policeNumber = $('input[name="police_number"]').val().trim();
var userSaId = $('select[name="user_sa_id"]').val();
var date = $('input[name="date"]').val().trim();
// Validate required fields within the wash form context
var $form = $(this);
var spkNo = $form.find('input[name="spk_no"]').val();
var policeNumber = $form.find('input[name="police_number"]').val();
var userSaId = $form.find('select[name="user_sa_id"]').val();
var date = $form.find('input[name="date"]').val();
var warranty = $form.find('select[name="warranty"]').val();
var errorMessages = [];
if (!spkNo) {
// Reset all field error states
$form.find('input, select').removeClass('is-invalid');
if (!spkNo || spkNo.trim() === '') {
errorMessages.push('No. SPK harus diisi');
$('input[name="spk_no"]').addClass('is-invalid');
} else {
$('input[name="spk_no"]').removeClass('is-invalid');
$form.find('input[name="spk_no"]').addClass('is-invalid');
}
if (!policeNumber) {
if (!policeNumber || policeNumber.trim() === '') {
errorMessages.push('No. Polisi harus diisi');
$('input[name="police_number"]').addClass('is-invalid');
} else {
$('input[name="police_number"]').removeClass('is-invalid');
$form.find('input[name="police_number"]').addClass('is-invalid');
}
if (!userSaId || userSaId === '') {
if (!userSaId || userSaId === '' || userSaId === null) {
errorMessages.push('Service Advisor harus dipilih');
$('select[name="user_sa_id"]').addClass('is-invalid');
} else {
$('select[name="user_sa_id"]').removeClass('is-invalid');
$form.find('select[name="user_sa_id"]').addClass('is-invalid');
}
if (!date) {
if (!date || date.trim() === '') {
errorMessages.push('Tanggal Pekerjaan harus diisi');
$('input[name="date"]').addClass('is-invalid');
} else {
$('input[name="date"]').removeClass('is-invalid');
$form.find('input[name="date"]').addClass('is-invalid');
}
if (!warranty || warranty === 'Warranty') {
errorMessages.push('Warranty harus dipilih');
$form.find('select[name="warranty"]').addClass('is-invalid');
}
if (errorMessages.length > 0) {
@@ -1456,11 +1327,23 @@ use Illuminate\Support\Facades\Auth;
icon: 'warning',
confirmButtonText: 'OK'
});
// Scroll to first invalid field
var firstInvalid = $form.find('.is-invalid:first');
if (firstInvalid.length) {
$('html, body').animate({
scrollTop: firstInvalid.offset().top - 100
}, 500);
}
return false;
}
$(".button-save").attr("disabled", true);
$(".button-save").addClass("disabled");
// Disable submit button to prevent double submission
$form.find(".button-save").attr("disabled", true);
$form.find(".button-save").addClass("disabled");
$form.find(".button-save").text("Menyimpan...");
return true;
})
@@ -1714,7 +1597,7 @@ use Illuminate\Support\Facades\Auth;
if (typeof Swal !== 'undefined') {
Swal.fire({
type: 'warning',
icon: 'warning',
title: 'Validasi Gagal',
text: errorMessages.join(', ')
});
@@ -1729,7 +1612,7 @@ use Illuminate\Support\Facades\Auth;
Swal.fire({
title: 'Kirim Mutasi?',
text: "Mutasi akan dikirim ke dealer tujuan",
type: 'question',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#ffc107',
cancelButtonColor: '#6c757d',
@@ -1874,6 +1757,19 @@ use Illuminate\Support\Facades\Auth;
$(document).on('change', 'select[name="user_sa_id"]', function() {
$(this).removeClass('is-invalid');
});
$(document).on('change', 'select[name="warranty"]', function() {
$(this).removeClass('is-invalid');
});
// Remove invalid styling from work/quantity fields when user interacts with them
$(document).on('change', 'select[name="work_id[]"]', function() {
$(this).removeClass('is-invalid');
});
$(document).on('input', 'input[name="quantity[]"]', function() {
$(this).removeClass('is-invalid');
});
// Handle server-side errors - scroll to first error and highlight
$(document).ready(function() {
@@ -1938,7 +1834,7 @@ use Illuminate\Support\Facades\Auth;
// Show alert for validation errors
@if(session('error'))
Swal.fire({
type: 'error',
icon: 'error',
title: 'Terjadi Kesalahan',
text: '{{ session("error") }}',
confirmButtonText: 'OK'
@@ -1951,7 +1847,7 @@ use Illuminate\Support\Facades\Auth;
// Handle success/error messages for both opname and mutasi
@if(session('success'))
Swal.fire({
type: 'success',
icon: 'success',
title: 'Berhasil!',
text: '{{ session("success") }}',
timer: 3000,
@@ -2055,7 +1951,7 @@ use Illuminate\Support\Facades\Auth;
@if(session('error'))
Swal.fire({
type: 'error',
icon: 'error',
title: 'Terjadi Kesalahan',
text: '{{ session("error") }}',
confirmButtonText: 'OK'
@@ -2084,7 +1980,7 @@ use Illuminate\Support\Facades\Auth;
@endforeach
Swal.fire({
type: 'error',
icon: 'error',
title: 'Validasi Gagal',
html: errorMessages.join('<br>'),
confirmButtonText: 'OK'
@@ -2648,7 +2544,7 @@ use Illuminate\Support\Facades\Auth;
var hasInvalidInput = $('.quantity-approved-input.is-invalid').length > 0;
if (hasInvalidInput) {
Swal.fire({
type: 'error',
icon: 'error',
title: 'Validasi Gagal',
text: 'Perbaiki quantity yang tidak valid sebelum melanjutkan'
});
@@ -2666,7 +2562,7 @@ use Illuminate\Support\Facades\Auth;
if (!hasApprovedQuantity) {
Swal.fire({
type: 'warning',
icon: 'warning',
title: 'Peringatan',
text: 'Minimal satu produk harus memiliki quantity yang disetujui'
});