feat: Enhance software tool with country selection and tax calculation
- Added a helper function to generate country select options in software tool. - Updated user info modal and payment modal to use country dropdowns instead of text inputs. - Implemented tax calculation based on selected country in payment modal. - Improved software options loading behavior in debug mode. - Enhanced description formatting in payment modal. - Added log modal for equipment updates with a link to view logs. - Introduced a new countries settings file with tax rates for various countries. - Minor adjustments to various PHP files for better handling of equipment and payment processes.
This commit is contained in:
@@ -10,6 +10,26 @@ let deviceVersion = "";
|
||||
let deviceHwVersion = "";
|
||||
let selectedSoftwareUrl = "";
|
||||
|
||||
// Helper function to generate country select options
|
||||
function generateCountryOptions(selectedCountry = '') {
|
||||
if (typeof COUNTRIES === 'undefined' || !COUNTRIES) {
|
||||
return `<option value="">${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}</option>`;
|
||||
}
|
||||
|
||||
// Sort countries alphabetically
|
||||
const sortedCountries = Object.values(COUNTRIES).sort((a, b) => {
|
||||
return a.country.localeCompare(b.country);
|
||||
});
|
||||
|
||||
let options = '<option value="">Select country</option>';
|
||||
sortedCountries.forEach(data => {
|
||||
const selected = (selectedCountry === data.country) ? 'selected' : '';
|
||||
options += `<option value="${data.country}" ${selected}>${data.country}</option>`;
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// Serial port variables (port, writer, textEncoder, writableStreamClosed declared in PHP)
|
||||
let reader;
|
||||
let readableStreamClosed;
|
||||
@@ -528,8 +548,18 @@ async function fetchSoftwareOptions() {
|
||||
document.getElementById("softwareOptionsContainer").style.display = "block";
|
||||
progressBar("100", "Software options loaded", "#04AA6D");
|
||||
|
||||
// Show user info modal immediately
|
||||
showUserInfoModal();
|
||||
// Show user info modal immediately (skip in debug mode)
|
||||
if (typeof DEBUG === 'undefined' || !DEBUG) {
|
||||
showUserInfoModal();
|
||||
} else {
|
||||
// In debug mode, reveal software options immediately
|
||||
const softwareOptions = document.getElementById("softwareOptions");
|
||||
if (softwareOptions) {
|
||||
softwareOptions.style.filter = "none";
|
||||
softwareOptions.style.opacity = "1";
|
||||
softwareOptions.style.pointerEvents = "auto";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await logCommunication(`Software options error: ${error.message}`, 'error');
|
||||
@@ -665,7 +695,7 @@ function displaySoftwareOptions(options) {
|
||||
} else {
|
||||
priceText.innerHTML = isFree
|
||||
? 'Free'
|
||||
: `${option.currency || "€"} ${price.toFixed(2)}`;
|
||||
: `${option.currency || "€"} ${price.toFixed(2)} <small style="font-size: 12px; font-weight: 400; color: #888;">(excl. VAT)</small>`;
|
||||
}
|
||||
|
||||
priceSection.appendChild(priceText);
|
||||
@@ -777,7 +807,9 @@ function showUserInfoModal() {
|
||||
<input type="text" name="city" id="userInfoCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; margin-bottom: 10px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<input type="text" name="postal" id="userInfoPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
<input type="text" name="country" id="userInfoCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
<select name="country" id="userInfoCountry" required style="padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 14px; transition: border 0.3s;" onfocus="this.style.borderColor='#04AA6D'" onblur="this.style.borderColor='#ddd'">
|
||||
${generateCountryOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -967,7 +999,9 @@ function showFreeInstallModal(option) {
|
||||
<input type="text" name="city" id="freeInstallCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<input type="text" name="postal" id="freeInstallPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<input type="text" name="country" id="freeInstallCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<select name="country" id="freeInstallCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
${generateCountryOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1045,6 +1079,17 @@ function showPaymentModal(option) {
|
||||
const price = parseFloat(option.price || 0);
|
||||
const currency = option.currency || "€";
|
||||
|
||||
// Format description as bullet points
|
||||
const formatDescription = (desc) => {
|
||||
if (!desc) return '';
|
||||
// Split by bullet points or newlines and filter out empty lines
|
||||
const lines = desc.split(/[•·\n]/).map(line => line.trim()).filter(line => line.length > 0);
|
||||
if (lines.length <= 1) return desc; // Return as-is if no multiple lines
|
||||
return '<ul style="margin: 0; padding-left: 20px; color: #666; font-size: 13px; line-height: 1.6;">' +
|
||||
lines.map(line => `<li>${line}</li>`).join('') +
|
||||
'</ul>';
|
||||
};
|
||||
|
||||
// Create modal overlay
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "paymentModal";
|
||||
@@ -1082,9 +1127,20 @@ function showPaymentModal(option) {
|
||||
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #333;">${option.name || "Software Update"}</h4>
|
||||
<p style="margin: 0 0 5px 0; color: #666;">Version: <strong>${option.version || "N/A"}</strong></p>
|
||||
<p style="margin: 0 0 15px 0; color: #666;">${option.description || ""}</p>
|
||||
<div style="font-size: 24px; font-weight: bold; color: #04AA6D;">
|
||||
${currency} ${price.toFixed(2)}
|
||||
<div style="margin: 0 0 15px 0;">${formatDescription(option.description)}</div>
|
||||
<div id="priceDisplay" style="font-size: 14px; color: #666;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
||||
<span>Price (excl. VAT):</span>
|
||||
<span style="font-weight: 600;">${currency} ${price.toFixed(2)}</span>
|
||||
</div>
|
||||
<div id="taxDisplay" style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
||||
<span>VAT:</span>
|
||||
<span style="font-weight: 600;">-</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding-top: 10px; border-top: 2px solid #ddd; margin-top: 10px;">
|
||||
<span style="font-weight: bold;">Total:</span>
|
||||
<span id="totalDisplay" style="font-size: 24px; font-weight: bold; color: #04AA6D;">${currency} ${price.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1105,7 +1161,9 @@ function showPaymentModal(option) {
|
||||
<input type="text" name="city" id="paymentCity" required placeholder="${typeof TRANS_CITY !== 'undefined' ? TRANS_CITY : 'City'}" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; margin-bottom: 10px;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<input type="text" name="postal" id="paymentPostal" required placeholder="${typeof TRANS_POSTAL !== 'undefined' ? TRANS_POSTAL : 'Postal code'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<input type="text" name="country" id="paymentCountry" required placeholder="${typeof TRANS_COUNTRY !== 'undefined' ? TRANS_COUNTRY : 'Country'}" style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
<select name="country" id="paymentCountry" required style="padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
|
||||
${generateCountryOptions()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1134,6 +1192,45 @@ function showPaymentModal(option) {
|
||||
modal.appendChild(modalContent);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Function to calculate and update tax
|
||||
function updateTaxDisplay() {
|
||||
const selectedCountry = document.getElementById("paymentCountry").value;
|
||||
let taxRate = 0;
|
||||
|
||||
if (selectedCountry && typeof COUNTRIES !== 'undefined' && COUNTRIES) {
|
||||
const countryData = Object.values(COUNTRIES).find(c => c.country === selectedCountry);
|
||||
if (countryData) {
|
||||
taxRate = parseFloat(countryData.taxes) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const taxAmount = price * (taxRate / 100);
|
||||
const totalAmount = price + taxAmount;
|
||||
|
||||
// Update display
|
||||
const taxDisplay = document.getElementById("taxDisplay");
|
||||
const totalDisplay = document.getElementById("totalDisplay");
|
||||
|
||||
if (taxRate > 0) {
|
||||
taxDisplay.innerHTML = `
|
||||
<span>VAT (${taxRate}%):</span>
|
||||
<span style="font-weight: 600;">${currency} ${taxAmount.toFixed(2)}</span>
|
||||
`;
|
||||
} else {
|
||||
taxDisplay.innerHTML = `
|
||||
<span>VAT:</span>
|
||||
<span style="font-weight: 600;">-</span>
|
||||
`;
|
||||
}
|
||||
|
||||
totalDisplay.textContent = `${currency} ${totalAmount.toFixed(2)}`;
|
||||
|
||||
// Store tax info for form submission
|
||||
modal.taxRate = taxRate;
|
||||
modal.taxAmount = taxAmount;
|
||||
modal.totalAmount = totalAmount;
|
||||
}
|
||||
|
||||
// Prefill form with customer data from sessionStorage if available
|
||||
const savedCustomerData = sessionStorage.getItem('customerData');
|
||||
if (savedCustomerData) {
|
||||
@@ -1144,12 +1241,18 @@ function showPaymentModal(option) {
|
||||
if (customerData.address) document.getElementById("paymentAddress").value = customerData.address;
|
||||
if (customerData.city) document.getElementById("paymentCity").value = customerData.city;
|
||||
if (customerData.postal) document.getElementById("paymentPostal").value = customerData.postal;
|
||||
if (customerData.country) document.getElementById("paymentCountry").value = customerData.country;
|
||||
if (customerData.country) {
|
||||
document.getElementById("paymentCountry").value = customerData.country;
|
||||
updateTaxDisplay(); // Calculate tax based on saved country
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error parsing saved customer data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listener to country select to update tax
|
||||
document.getElementById("paymentCountry").addEventListener('change', updateTaxDisplay);
|
||||
|
||||
// Close modal on cancel
|
||||
document.getElementById("cancelPayment").onclick = () => {
|
||||
document.body.removeChild(modal);
|
||||
@@ -1160,15 +1263,15 @@ function showPaymentModal(option) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const paymentMethod = formData.get("payment_method");
|
||||
|
||||
|
||||
// Auto-determine payment provider based on payment method
|
||||
let paymentProvider = 'mollie'; // default
|
||||
if (paymentMethod === 'paypal') {
|
||||
if (paymentMethod === '3') { // PayPal payment method ID
|
||||
paymentProvider = 'paypal';
|
||||
} else if (paymentMethod === 'credit_card' || paymentMethod === 'bank_transfer') {
|
||||
} else if (paymentMethod === '1' || paymentMethod === 'bank_transfer') { // Mollie (Credit Card) or Bank Transfer
|
||||
paymentProvider = 'mollie';
|
||||
}
|
||||
|
||||
|
||||
const paymentData = {
|
||||
name: formData.get("name"),
|
||||
email: formData.get("email"),
|
||||
@@ -1179,7 +1282,9 @@ function showPaymentModal(option) {
|
||||
payment_method: paymentMethod,
|
||||
payment_provider: paymentProvider,
|
||||
version_id: option.version_id,
|
||||
price: price,
|
||||
item_price: price, // Price without VAT
|
||||
tax_amount: modal.taxAmount || 0, // Tax amount
|
||||
payment_amount: modal.totalAmount || price, // Total price including tax
|
||||
currency: currency
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user