Fixes for required fields copying across

This commit is contained in:
David Bomba
2026-01-25 16:15:45 +11:00
parent 280aeaa4de
commit b05d10ff4e
3 changed files with 212 additions and 61 deletions

View File

@@ -186,8 +186,6 @@ class InvoiceCheckOverdue implements ShouldQueue
continue;
}
nlog($company_user->permissions);
$overdue_invoices_collection = $overdue_invoices;
$invoice = Invoice::withTrashed()->find($overdue_invoices[0]['id']);
@@ -205,7 +203,7 @@ class InvoiceCheckOverdue implements ShouldQueue
$overdue_invoices_collection = collect($overdue_invoices)
->filter(function ($overdue_invoice) use ($user) {
$invoice = Invoice::withTrashed()->find($overdue_invoice['id']);
nlog([$invoice->user_id, $user->id, $invoice->assigned_user_id, $user->id]);
// nlog([$invoice->user_id, $user->id, $invoice->assigned_user_id, $user->id]);
return $invoice->user_id == $user->id || $invoice->assigned_user_id == $user->id;
})
->toArray();
@@ -243,6 +241,7 @@ class InvoiceCheckOverdue implements ShouldQueue
/**
* Send notifications for an overdue invoice to all relevant company users.
* @deprecated in favour of sendOverdueNotifications to send a summary email to all users
*/
/** @phpstan-ignore-next-line */
private function notifyOverdueInvoice(Invoice $invoice): void

View File

@@ -39,7 +39,7 @@
@component('portal.ninja2020.components.general.card-element-single')
<div class="flex justify-end">
<button type="button" class="bg-gray-100 px-2 py-1 text-sm rounded" wire:click="handleCopyBilling">
<button type="button" id="copy-billing-button" class="bg-gray-100 px-2 py-1 text-sm rounded">
{{ ctrans('texts.copy_billing') }}
</button>
</div>
@@ -131,3 +131,106 @@
@endif
</div>
@script
<script>
(function() {
function copyBillingToShipping() {
const form = document.getElementById('required-client-info-form');
if (!form) return;
// Pure vanilla JavaScript - read directly from DOM and update DOM
// Mapping: billing field => shipping field
const fieldMappings = [
{ from: 'client_address_line_1', to: 'client_shipping_address_line_1' },
{ from: 'client_address_line_2', to: 'client_shipping_address_line_2' },
{ from: 'client_city', to: 'client_shipping_city' },
{ from: 'client_state', to: 'client_shipping_state' },
{ from: 'client_postal_code', to: 'client_shipping_postal_code' },
{ from: 'client_country_id', to: 'client_shipping_country_id' }
];
fieldMappings.forEach(function(mapping) {
var from = mapping.from;
var to = mapping.to;
// Find the billing input field
var billingField = form.querySelector('[name="' + from + '"]');
// Find the shipping input field
var shippingField = form.querySelector('[name="' + to + '"]');
if (!billingField || !shippingField) return;
// Try multiple methods to get the current value
var currentValue = '';
// Method 1: Direct .value property
var directValue = billingField.value || '';
// Method 2: Try getting from Livewire if available (for wire:model fields)
var livewireValue = null;
try {
// Check if Livewire is available and has the property
if (typeof window.Livewire !== 'undefined') {
var component = window.Livewire.find(billingField.closest('[wire\\:id]')?.getAttribute('wire:id'));
if (component) {
livewireValue = component.get(from);
}
}
} catch (e) {
// Livewire not available or error reading
}
// Method 3: Use FormData to get form values
var formData = new FormData(form);
var formDataValue = formData.get(from);
// Choose the best value - prioritize what user sees/types
// If direct value exists and is not empty, use it
// Otherwise try Livewire, then FormData
if (directValue !== '' && directValue !== null && directValue !== undefined) {
currentValue = directValue;
} else if (livewireValue !== null && livewireValue !== undefined && livewireValue !== '') {
currentValue = String(livewireValue);
} else if (formDataValue !== null && formDataValue !== undefined) {
currentValue = String(formDataValue);
} else {
currentValue = '';
}
// Directly set the shipping field's DOM .value property
shippingField.value = currentValue;
// Trigger the appropriate event so Livewire's wire:model can sync
if (shippingField.tagName === 'SELECT') {
// For select elements, trigger 'change' event
var changeEvent = new Event('change', { bubbles: true, cancelable: true });
shippingField.dispatchEvent(changeEvent);
} else {
// For input elements, trigger 'input' event
var inputEvent = new Event('input', { bubbles: true, cancelable: true });
shippingField.dispatchEvent(inputEvent);
}
});
}
// Wait for DOM to be ready, then attach event listener
function attachListener() {
var button = document.getElementById('copy-billing-button');
if (button) {
button.addEventListener('click', copyBillingToShipping);
} else {
// Try again after a short delay in case the button hasn't rendered yet
setTimeout(attachListener, 100);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', attachListener);
} else {
// DOM is already ready
attachListener();
}
})();
</script>
@endscript

View File

@@ -1,58 +1,4 @@
<div x-data="{
fields: @entangle('fields'),
copyBillingToShipping() {
// Mapping: billing field => shipping field
const mappings = {
'client_address_line_1': 'client_shipping_address_line_1',
'client_address_line_2': 'client_shipping_address_line_2',
'client_city': 'client_shipping_city',
'client_state': 'client_shipping_state',
'client_postal_code': 'client_shipping_postal_code',
'client_country_id': 'client_shipping_country_id'
};
// Copy values from billing fields to shipping fields
Object.entries(mappings).forEach(([billingField, shippingField]) => {
const billingInput = document.querySelector(`input[name='${billingField}'], select[name='${billingField}']`);
const shippingInput = document.querySelector(`input[name='${shippingField}'], select[name='${shippingField}']`);
if (billingInput && shippingInput) {
// Get the value from billing field (read from form, not database)
let value = billingInput.value;
// Handle country_id specially - ensure it's a valid number or null
if (billingField === 'client_country_id') {
if (value === 'none' || value === '' || value === null) {
value = null;
} else {
value = parseInt(value, 10);
}
}
// Update Livewire model property first
if (shippingInput.hasAttribute('wire:model')) {
const modelName = shippingInput.getAttribute('wire:model');
$wire.set(modelName, value);
}
// Update the DOM input/select value for immediate visual feedback
// For select, convert back to string for the option value
if (shippingInput.tagName === 'SELECT') {
shippingInput.value = value !== null ? String(value) : 'none';
// Trigger change event for select - this is important for Livewire
shippingInput.dispatchEvent(new Event('change', { bubbles: true }));
// Also trigger input event
shippingInput.dispatchEvent(new Event('input', { bubbles: true }));
} else {
shippingInput.value = value !== null ? value : '';
// Trigger Livewire's input event to ensure sync
shippingInput.dispatchEvent(new Event('input', { bubbles: true }));
}
}
});
}
}"
class="rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden px-4 py-5 bg-white sm:gap-4 sm:px-6">
<div class="rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden px-4 py-5 bg-white sm:gap-4 sm:px-6">
<p class="font-semibold tracking-tight group flex items-center gap-2 text-lg mb-3">
{{ ctrans('texts.required_fields') }}
@@ -72,7 +18,7 @@
@foreach($fields as $field)
@component('portal.ninja2020.components.general.card-element', ['title' => $field['label']])
@if($field['name'] == 'client_country_id' || $field['name'] == 'client_shipping_country_id')
<select id="client_country" class="input w-full form-select bg-white" name="{{ $field['name'] }}"
<select id="{{ $field['name'] }}" class="input w-full form-select bg-white" name="{{ $field['name'] }}"
wire:model="{{ $field['name'] }}">
<option value="none"></option>
@@ -98,7 +44,7 @@
<div class="bg-white px-4 py-5 flex items-center w-full justify-end">
<button type="button"
@click="copyBillingToShipping()"
id="copy-billing-button"
class="bg-gray-100 hover:bg-gray-200 px-4 py-2 text-sm rounded transition-colors">
{{ ctrans('texts.copy_billing') }}
</button>
@@ -120,3 +66,106 @@
</form>
@endif
</div>
@script
<script>
(function() {
function copyBillingToShipping() {
const form = document.getElementById('required-client-info-form');
if (!form) return;
// Pure vanilla JavaScript - read directly from DOM and update DOM
// Mapping: billing field => shipping field
const fieldMappings = [
{ from: 'client_address_line_1', to: 'client_shipping_address_line_1' },
{ from: 'client_address_line_2', to: 'client_shipping_address_line_2' },
{ from: 'client_city', to: 'client_shipping_city' },
{ from: 'client_state', to: 'client_shipping_state' },
{ from: 'client_postal_code', to: 'client_shipping_postal_code' },
{ from: 'client_country_id', to: 'client_shipping_country_id' }
];
fieldMappings.forEach(function(mapping) {
var from = mapping.from;
var to = mapping.to;
// Find the billing input field
var billingField = form.querySelector('[name="' + from + '"]');
// Find the shipping input field
var shippingField = form.querySelector('[name="' + to + '"]');
if (!billingField || !shippingField) return;
// Try multiple methods to get the current value
var currentValue = '';
// Method 1: Direct .value property
var directValue = billingField.value || '';
// Method 2: Try getting from Livewire if available (for wire:model fields)
var livewireValue = null;
try {
// Check if Livewire is available and has the property
if (typeof window.Livewire !== 'undefined') {
var component = window.Livewire.find(billingField.closest('[wire\\:id]')?.getAttribute('wire:id'));
if (component) {
livewireValue = component.get(from);
}
}
} catch (e) {
// Livewire not available or error reading
}
// Method 3: Use FormData to get form values
var formData = new FormData(form);
var formDataValue = formData.get(from);
// Choose the best value - prioritize what user sees/types
// If direct value exists and is not empty, use it
// Otherwise try Livewire, then FormData
if (directValue !== '' && directValue !== null && directValue !== undefined) {
currentValue = directValue;
} else if (livewireValue !== null && livewireValue !== undefined && livewireValue !== '') {
currentValue = String(livewireValue);
} else if (formDataValue !== null && formDataValue !== undefined) {
currentValue = String(formDataValue);
} else {
currentValue = '';
}
// Directly set the shipping field's DOM .value property
shippingField.value = currentValue;
// Trigger the appropriate event so Livewire's wire:model can sync
if (shippingField.tagName === 'SELECT') {
// For select elements, trigger 'change' event
var changeEvent = new Event('change', { bubbles: true, cancelable: true });
shippingField.dispatchEvent(changeEvent);
} else {
// For input elements, trigger 'input' event
var inputEvent = new Event('input', { bubbles: true, cancelable: true });
shippingField.dispatchEvent(inputEvent);
}
});
}
// Wait for DOM to be ready, then attach event listener
function attachListener() {
var button = document.getElementById('copy-billing-button');
if (button) {
button.addEventListener('click', copyBillingToShipping);
} else {
// Try again after a short delay in case the button hasn't rendered yet
setTimeout(attachListener, 100);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', attachListener);
} else {
// DOM is already ready
attachListener();
}
})();
</script>
@endscript