load address using ajax
This commit is contained in:
parent
10f3abc047
commit
a86ba91db7
|
|
@ -13,7 +13,32 @@ class AddressController extends Controller
|
|||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$addresses = auth()->user()->addresses;
|
||||
$addresses = auth()->user()->addresses()->orderBy('is_primary', 'desc')->get();
|
||||
|
||||
// If AJAX request, return JSON
|
||||
if ($request->ajax() || $request->wantsJson()) {
|
||||
$addressesData = $addresses->map(function ($address) {
|
||||
return [
|
||||
'id' => $address->id,
|
||||
'label' => $address->label,
|
||||
'location' => $address->location,
|
||||
'address' => $address->address,
|
||||
'is_primary' => $address->is_primary,
|
||||
'province_id' => $address->province_id,
|
||||
'city_id' => $address->city_id,
|
||||
'district_id' => $address->district_id,
|
||||
'subdistrict_id' => $address->subdistrict_id,
|
||||
'postal_code' => $address->postal_code,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'addresses' => $addressesData
|
||||
]);
|
||||
}
|
||||
|
||||
// For regular page load, return view
|
||||
return view('account.addresses', compact('addresses'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ return [
|
|||
'cannot_delete_only_address' => 'Cannot delete the only address',
|
||||
'delete' => 'Delete',
|
||||
'cancel' => 'Cancel',
|
||||
'loading' => 'Loading...',
|
||||
'notification' => 'Notification',
|
||||
'confirm_delete_address' => 'Are you sure you want to delete this address?',
|
||||
'this_action_cannot_be_undone' => 'This action cannot be undone.',
|
||||
'regions' => [
|
||||
|
|
|
|||
|
|
@ -37,8 +37,10 @@ return [
|
|||
'cannot_delete_only_address' => 'Tidak dapat menghapus satu-satunya alamat',
|
||||
'delete' => 'Hapus',
|
||||
'cancel' => 'Batal',
|
||||
'loading' => 'Memuat...',
|
||||
'confirm_delete_address' => 'Apakah Anda yakin ingin menghapus alamat ini?',
|
||||
'this_action_cannot_be_undone' => 'Tindakan ini tidak dapat dibatalkan.',
|
||||
'notification' => 'Notifikasi',
|
||||
'regions' => [
|
||||
'africa' => 'Afrika',
|
||||
'asia' => 'Asia',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,53 @@
|
|||
@extends('layouts.account', ['title' => __('addresses.page_title')])
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.shimmer-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
background: #f0f0f0;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.shimmer-line {
|
||||
height: 0.75rem;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e9ecef 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.shimmer-line.short {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
#addresses-loading {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
#addresses-container {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
<!-- Addresses content -->
|
||||
<div class="col-lg-9">
|
||||
<div class="ps-lg-3 ps-xl-0">
|
||||
|
|
@ -8,137 +55,31 @@
|
|||
<!-- Page title -->
|
||||
<h1 class="h2 mb-1 mb-sm-2">{{ __('addresses.page_title') }}</h1>
|
||||
|
||||
@foreach ($addresses as $address)
|
||||
<div class="border-bottom py-4">
|
||||
<div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3">
|
||||
<div class="d-flex align-items-center gap-3 me-4">
|
||||
<h2 class="h6 mb-0">{{ $address->label }}</h2>
|
||||
@if ($address->is_primary)
|
||||
<span class="badge text-bg-info rounded-pill">{{ __('addresses.primary') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<a class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed primaryAddressEditButton"
|
||||
data-address-id="{{ $address->id }}" href=".primary-address-{{ $address->id }}"
|
||||
data-bs-toggle="collapse" aria-expanded="false"
|
||||
aria-controls="primaryAddressPreview{{ $address->id }} primaryAddressEdit{{ $address->id }}">{{ __('addresses.edit') }}</a>
|
||||
<!-- Loading indicator -->
|
||||
<div id="addresses-loading" class="text-center py-4">
|
||||
<div class="shimmer-wrapper">
|
||||
<div class="shimmer">
|
||||
<div class="shimmer-line"></div>
|
||||
<div class="shimmer-line"></div>
|
||||
<div class="shimmer-line short"></div>
|
||||
</div>
|
||||
<div class="collapse primary-address-{{ $address->id }} show"
|
||||
id="primaryAddressPreview{{ $address->id }}">
|
||||
<ul class="list-unstyled fs-sm m-0">
|
||||
<li>{{ $address->location }}</li>
|
||||
<li>{{ $address->address }}</li>
|
||||
</ul>
|
||||
<div class="shimmer">
|
||||
<div class="shimmer-line"></div>
|
||||
<div class="shimmer-line"></div>
|
||||
<div class="shimmer-line short"></div>
|
||||
</div>
|
||||
<div class="collapse primary-address-{{ $address->id }}" id="primaryAddressEdit{{ $address->id }}">
|
||||
|
||||
{{-- add message on success or error --}}
|
||||
<div class="alert alert-danger d-none address-form-message" role="alert"></div>
|
||||
<div class="alert alert-success d-none address-form-success" role="alert"></div>
|
||||
|
||||
<form class="row g-3 g-sm-4 needs-validation address-form" novalidate
|
||||
action="{{ route('addresses.update', $address->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.province') }}</label>
|
||||
<select class="form-select province-select" required
|
||||
data-address-id="{{ $address->id }}"
|
||||
data-province-id="{{ $address->province_id }}"
|
||||
data-city-id="{{ $address->city_id }}"
|
||||
data-district-id="{{ $address->district_id }}"
|
||||
data-village-id="{{ $address->subdistrict_id }}">
|
||||
<option value="">{{ __('addresses.select_province') }}</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_province') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.city') }}</label>
|
||||
<select class="form-select city-select" required data-address-id="{{ $address->id }}">
|
||||
<option value="">{{ __('addresses.select_city') }}</option>
|
||||
</select>
|
||||
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_city') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.district') }}</label>
|
||||
<select class="form-select district-select" required
|
||||
data-address-id="{{ $address->id }}">
|
||||
<option value="">{{ __('addresses.select_district') }}</option>
|
||||
</select>
|
||||
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_district') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.village') }}</label>
|
||||
<select class="form-select village-select" required
|
||||
data-address-id="{{ $address->id }}">
|
||||
<option value="">{{ __('addresses.select_village') }}</option>
|
||||
</select>
|
||||
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_village') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="position-relative">
|
||||
<label for="psa-zip-{{ $address->id }}"
|
||||
class="form-label">{{ __('addresses.zip_code') }}</label>
|
||||
<input type="text" class="form-control" id="psa-zip-{{ $address->id }}"
|
||||
value="{{ $address->postal_code }}" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_zip_code') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<div class="position-relative">
|
||||
<label for="psa-address-{{ $address->id }}"
|
||||
class="form-label">{{ __('addresses.address') }}</label>
|
||||
<input type="text" class="form-control" id="psa-address-{{ $address->id }}"
|
||||
value="{{ $address->address }}" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_address') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check mb-0">
|
||||
<input type="checkbox" class="form-check-input" id="set-primary-{{ $address->id }}"
|
||||
{{ $address->is_primary ? 'checked' : '' }}>
|
||||
<label for="set-primary-{{ $address->id }}"
|
||||
class="form-check-label">{{ __('addresses.set_as_primary_address') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-between">
|
||||
<div class="d-flex gap-3 pt-2 pt-sm-0">
|
||||
<button type="submit"
|
||||
class="btn btn-primary">{{ __('addresses.save_changes') }}</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-toggle="collapse"
|
||||
data-bs-target=".primary-address-{{ $address->id }}" aria-expanded="true"
|
||||
aria-controls="primaryAddressPreview{{ $address->id }} primaryAddressEdit{{ $address->id }}">{{ __('addresses.close') }}</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-danger delete-address-btn"
|
||||
data-address-id="{{ $address->id }}"
|
||||
data-address-label="{{ $address->label }}">{{ __('addresses.delete') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="shimmer">
|
||||
<div class="shimmer-line"></div>
|
||||
<div class="shimmer-line"></div>
|
||||
<div class="shimmer-line short"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
<p class="text-muted mt-3">{{ __('addresses.loading') }}</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Add address button -->
|
||||
<div class="nav pt-4">
|
||||
<a class="nav-link animate-underline fs-base px-0" href="#newAddressModal" data-bs-toggle="modal">
|
||||
<i class="ci-plus fs-lg ms-n1 me-2"></i>
|
||||
<span class="animate-target">{{ __('addresses.add_address') }}</span>
|
||||
</a>
|
||||
<!-- Addresses container -->
|
||||
<div id="addresses-container">
|
||||
<!-- Addresses will be loaded here via AJAX -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -236,6 +177,26 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Modal -->
|
||||
<div class="modal fade" id="notificationModal" tabindex="-1" aria-labelledby="notificationModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="notificationModalLabel">{{ __('addresses.notification') }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="notification-icon me-3">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
</div>
|
||||
<div class="notification-message" id="notificationMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteAddressModal" tabindex="-1" aria-labelledby="deleteAddressModalLabel"
|
||||
aria-hidden="true">
|
||||
|
|
@ -270,6 +231,218 @@
|
|||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
/* ===============================
|
||||
* LOAD ADDRESSES VIA AJAX
|
||||
* =============================== */
|
||||
function loadAddresses() {
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
const loadingElement = document.getElementById('addresses-loading');
|
||||
const containerElement = document.getElementById('addresses-container');
|
||||
|
||||
// Show loading with shimmer
|
||||
if (loadingElement) {
|
||||
loadingElement.style.opacity = '1';
|
||||
loadingElement.style.display = 'block';
|
||||
}
|
||||
if (containerElement) {
|
||||
containerElement.style.opacity = '0';
|
||||
containerElement.style.display = 'none';
|
||||
}
|
||||
|
||||
fetch('/addresses', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success && data.addresses) {
|
||||
renderAddresses(data.addresses);
|
||||
} else {
|
||||
console.error('Failed to load addresses:', data);
|
||||
showToast('Failed to load addresses', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading addresses:', error);
|
||||
showToast('Error loading addresses', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
// Smooth transition from loading to content
|
||||
setTimeout(() => {
|
||||
if (loadingElement) {
|
||||
loadingElement.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
loadingElement.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
if (containerElement) {
|
||||
containerElement.style.opacity = '1';
|
||||
containerElement.style.display = 'block';
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function reloadAddresses() {
|
||||
loadAddresses();
|
||||
}
|
||||
|
||||
function renderAddresses(addresses) {
|
||||
const container = document.getElementById('addresses-container');
|
||||
if (!container) return;
|
||||
|
||||
let html = '';
|
||||
|
||||
addresses.forEach(address => {
|
||||
html += `
|
||||
<div class="border-bottom py-4">
|
||||
<div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3">
|
||||
<div class="d-flex align-items-center gap-3 me-4">
|
||||
<h2 class="h6 mb-0">${address.label}</h2>
|
||||
${address.is_primary ? '<span class="badge text-bg-info rounded-pill">{{ __("addresses.primary") }}</span>' : ''}
|
||||
</div>
|
||||
<a class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed primaryAddressEditButton"
|
||||
data-address-id="${address.id}" href=".primary-address-${address.id}"
|
||||
data-bs-toggle="collapse" aria-expanded="false"
|
||||
aria-controls="primaryAddressPreview${address.id} primaryAddressEdit${address.id}">{{ __('addresses.edit') }}</a>
|
||||
</div>
|
||||
<div class="collapse primary-address-${address.id} show"
|
||||
id="primaryAddressPreview${address.id}">
|
||||
<ul class="list-unstyled fs-sm m-0">
|
||||
<li>${address.location}</li>
|
||||
<li>${address.address}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="collapse primary-address-${address.id}" id="primaryAddressEdit${address.id}">
|
||||
<div class="alert alert-danger d-none address-form-message" role="alert"></div>
|
||||
<div class="alert alert-success d-none address-form-success" role="alert"></div>
|
||||
<form class="row g-3 g-sm-4 needs-validation address-form" novalidate
|
||||
action="/addresses/${address.id}" method="POST">
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
<input type="hidden" name="_token" value="${document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''}">
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.province') }}</label>
|
||||
<select class="form-select province-select" required
|
||||
data-address-id="${address.id}"
|
||||
data-province-id="${address.province_id}"
|
||||
data-city-id="${address.city_id}"
|
||||
data-district-id="${address.district_id}"
|
||||
data-village-id="${address.subdistrict_id}">
|
||||
<option value="">{{ __('addresses.select_province') }}</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_province') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.city') }}</label>
|
||||
<select class="form-select city-select" required data-address-id="${address.id}">
|
||||
<option value="">{{ __('addresses.select_city') }}</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_city') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.district') }}</label>
|
||||
<select class="form-select district-select" required data-address-id="${address.id}">
|
||||
<option value="">{{ __('addresses.select_district') }}</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_district') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.village') }}</label>
|
||||
<select class="form-select village-select" required data-address-id="${address.id}">
|
||||
<option value="">{{ __('addresses.select_village') }}</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_select_village') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.zip_code') }}</label>
|
||||
<input type="text" class="form-control postal_code" value="${address.postal_code}" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_zip_code') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="position-relative">
|
||||
<label class="form-label">{{ __('addresses.address') }}</label>
|
||||
<input type="text" class="form-control zip_code" value="${address.address}" required>
|
||||
<div class="invalid-feedback">{{ __('addresses.please_enter_address') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="form-check mb-0">
|
||||
<input type="checkbox" class="form-check-input" ${address.is_primary ? 'checked' : ''}>
|
||||
<label class="form-check-label">{{ __('addresses.set_as_primary_address') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-flex justify-content-between">
|
||||
<div class="d-flex gap-3 pt-2 pt-sm-0">
|
||||
<button type="submit" class="btn btn-primary">{{ __('addresses.save_changes') }}</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-toggle="collapse"
|
||||
data-bs-target=".primary-address-${address.id}" aria-expanded="true"
|
||||
aria-controls="primaryAddressPreview${address.id} primaryAddressEdit${address.id}">{{ __('addresses.close') }}</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger delete-address-btn"
|
||||
data-address-id="${address.id}"
|
||||
data-address-label="${address.label}">{{ __('addresses.delete') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
// Add address button
|
||||
html += `
|
||||
<div class="nav pt-4">
|
||||
<a class="nav-link animate-underline fs-base px-0" href="#newAddressModal" data-bs-toggle="modal">
|
||||
<i class="ci-plus fs-lg ms-n1 me-2"></i>
|
||||
<span class="animate-target">{{ __('addresses.add_address') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Initialize address forms after rendering
|
||||
initializeAddressForms();
|
||||
}
|
||||
|
||||
function initializeAddressForms() {
|
||||
// Initialize province selects for each address
|
||||
document.querySelectorAll('.province-select').forEach(select => {
|
||||
const addressId = select.dataset.addressId;
|
||||
const provinceId = select.dataset.provinceId;
|
||||
const cityId = select.dataset.cityId;
|
||||
const districtId = select.dataset.districtId;
|
||||
const villageId = select.dataset.villageId;
|
||||
|
||||
// Load provinces and set initial values
|
||||
loadProvinces(select, addressId, provinceId, cityId, districtId, villageId);
|
||||
});
|
||||
}
|
||||
|
||||
// Load addresses on page load
|
||||
loadAddresses();
|
||||
|
||||
/* ===============================
|
||||
* INIT EDIT BUTTON
|
||||
* =============================== */
|
||||
|
|
@ -721,19 +894,19 @@
|
|||
console.log(result);
|
||||
if (result.success) {
|
||||
// Show success message and reload page
|
||||
showToast(result.message, 'success');
|
||||
showNotification(result.message, 'success');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
reloadAddresses();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
showToast(result.message || '{{ __('addresses.error_saving_address') }}',
|
||||
'error');
|
||||
showNotification(result.message || '{{ __('addresses.error_saving_address') }}',
|
||||
'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showToast('{{ __('addresses.error_saving_address') }}', 'error');
|
||||
showNotification('{{ __('addresses.error_saving_address') }}', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
deleteAddressId = null;
|
||||
|
|
@ -764,30 +937,46 @@
|
|||
.addressId;
|
||||
|
||||
// Prepare form data for AJAX submission
|
||||
const submitData = {
|
||||
_token: formData.get('_token'),
|
||||
province_id: isNewAddress ?
|
||||
form.querySelector('.new-province-select').value : form.querySelector(
|
||||
'.province-select').value,
|
||||
city_id: isNewAddress ?
|
||||
form.querySelector('.new-city-select').value : form.querySelector(
|
||||
'.city-select').value,
|
||||
district_id: isNewAddress ?
|
||||
form.querySelector('.new-district-select').value : form.querySelector(
|
||||
'.district-select').value,
|
||||
subdistrict_id: isNewAddress ?
|
||||
form.querySelector('.new-village-select').value : form.querySelector(
|
||||
'.village-select').value,
|
||||
postal_code: isNewAddress ?
|
||||
form.querySelector('#new-zip').value : form.querySelector(
|
||||
'input[id^="psa-zip-"]').value,
|
||||
address: isNewAddress ?
|
||||
form.querySelector('#new-address').value : form.querySelector(
|
||||
'input[id^="psa-address-"]').value,
|
||||
is_primary: (isNewAddress ?
|
||||
form.querySelector('#set-primary-new') :
|
||||
form.querySelector('input[id^="set-primary-"]')).checked ? '1' : '0'
|
||||
};
|
||||
const submitData = {};
|
||||
|
||||
// Get CSRF token
|
||||
const tokenInput = form.querySelector('input[name="_token"]');
|
||||
if (tokenInput) submitData._token = tokenInput.value;
|
||||
|
||||
// Get province ID
|
||||
const provinceSelect = isNewAddress ?
|
||||
form.querySelector('.new-province-select') : form.querySelector('.province-select');
|
||||
if (provinceSelect) submitData.province_id = provinceSelect.value;
|
||||
|
||||
// Get city ID
|
||||
const citySelect = isNewAddress ?
|
||||
form.querySelector('.new-city-select') : form.querySelector('.city-select');
|
||||
if (citySelect) submitData.city_id = citySelect.value;
|
||||
|
||||
// Get district ID
|
||||
const districtSelect = isNewAddress ?
|
||||
form.querySelector('.new-district-select') : form.querySelector('.district-select');
|
||||
if (districtSelect) submitData.district_id = districtSelect.value;
|
||||
|
||||
// Get subdistrict ID
|
||||
const villageSelect = isNewAddress ?
|
||||
form.querySelector('.new-village-select') : form.querySelector('.village-select');
|
||||
if (villageSelect) submitData.subdistrict_id = villageSelect.value;
|
||||
|
||||
// Get postal code
|
||||
const zipInput = isNewAddress ?
|
||||
form.querySelector('#new-zip') : form.querySelector('.postal_code');
|
||||
if (zipInput) submitData.postal_code = zipInput.value;
|
||||
|
||||
// Get address
|
||||
const addressInput = isNewAddress ?
|
||||
form.querySelector('#new-address') : form.querySelector('.zip_code');
|
||||
if (addressInput) submitData.address = addressInput.value;
|
||||
|
||||
// Get primary checkbox
|
||||
const primaryCheckbox = isNewAddress ?
|
||||
form.querySelector('#set-primary-new') : form.querySelector('input[id^="set-primary-"]');
|
||||
if (primaryCheckbox) submitData.is_primary = primaryCheckbox.checked ? '1' : '0';
|
||||
|
||||
if (!isNewAddress) {
|
||||
submitData._method = 'PUT';
|
||||
|
|
@ -824,7 +1013,7 @@
|
|||
setTimeout(() => {
|
||||
const modal = bootstrap.Modal.getInstance(newAddressModal);
|
||||
if (modal) modal.hide();
|
||||
window.location.reload();
|
||||
reloadAddresses();
|
||||
}, 1500);
|
||||
} else {
|
||||
// Close the edit form and show preview after delay
|
||||
|
|
@ -840,7 +1029,7 @@
|
|||
}
|
||||
|
||||
// Reload page to show updated data
|
||||
window.location.reload();
|
||||
reloadAddresses();
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -886,13 +1075,15 @@
|
|||
|
||||
// Toast notification helper
|
||||
function showToast(message, type = 'info') {
|
||||
// Create toast element if it doesn't exist
|
||||
console.log('Showing toast:', message, type);
|
||||
|
||||
// Create toast container if it doesn't exist
|
||||
let toastContainer = document.getElementById('toast-container');
|
||||
if (!toastContainer) {
|
||||
toastContainer = document.createElement('div');
|
||||
toastContainer.id = 'toast-container';
|
||||
toastContainer.className = 'position-fixed top-0 end-0 p-3';
|
||||
toastContainer.style.zIndex = '1050';
|
||||
toastContainer.style.zIndex = '1055';
|
||||
document.body.appendChild(toastContainer);
|
||||
}
|
||||
|
||||
|
|
@ -903,25 +1094,52 @@
|
|||
<div class="toast-body">
|
||||
${message}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
||||
|
||||
// Show toast using Bootstrap if available, otherwise fallback to simple alert
|
||||
const toastElement = document.getElementById(toastId);
|
||||
const toast = new bootstrap.Toast(toastElement, {
|
||||
autohide: true,
|
||||
delay: 3000
|
||||
});
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Toast) {
|
||||
try {
|
||||
const toast = new bootstrap.Toast(toastElement, {
|
||||
autohide: true,
|
||||
delay: 3000
|
||||
});
|
||||
toast.show();
|
||||
|
||||
// Remove toast element after hidden
|
||||
toastElement.addEventListener('hidden.bs.toast', () => {
|
||||
toastElement.remove();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Bootstrap Toast error:', error);
|
||||
// Fallback to simple notification
|
||||
toastElement.style.display = 'block';
|
||||
toastElement.style.position = 'fixed';
|
||||
toastElement.style.top = '20px';
|
||||
toastElement.style.right = '20px';
|
||||
toastElement.style.zIndex = '1055';
|
||||
|
||||
setTimeout(() => {
|
||||
toastElement.style.display = 'none';
|
||||
toastElement.remove();
|
||||
}, 3000);
|
||||
}
|
||||
} else {
|
||||
// Fallback for when Bootstrap is not available
|
||||
console.warn('Bootstrap Toast not available, using fallback');
|
||||
alert(message);
|
||||
}
|
||||
}
|
||||
|
||||
toast.show();
|
||||
|
||||
// Remove toast element after hidden
|
||||
toastElement.addEventListener('hidden.bs.toast', () => {
|
||||
toastElement.remove();
|
||||
});
|
||||
// Toast notification helper (deprecated - use showNotification instead)
|
||||
function showToast(message, type = 'info') {
|
||||
console.warn('showToast is deprecated, use showNotification instead');
|
||||
showNotification(message, type);
|
||||
}
|
||||
|
||||
/* ===============================
|
||||
|
|
|
|||
Loading…
Reference in New Issue