diff --git a/app/static/css/calculator.css b/app/static/css/calculator.css new file mode 100644 index 0000000..f1648a5 --- /dev/null +++ b/app/static/css/calculator.css @@ -0,0 +1,91 @@ +/* Calculator Page Styles */ +.container-main { + max-width: 1200px; + margin: 2rem auto; + padding: 0 1rem; +} + +.calculator-card { + background-color: #ffffff; + border-radius: 0.5rem; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); +} + +.section-header { + background-color: #f8f9fa; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1.5rem; + border-left: 4px solid #0d6efd; +} + +.section-content { + background-color: #ffffff; + padding: 1.5rem; + border-radius: 0.5rem; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + margin-bottom: 2rem; +} + +.form-label { + color: #495057; + font-weight: 500; +} + +.form-select, .form-control { + border: 1px solid #ced4da; +} + +.form-select:focus, .form-control:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +/* Table Styles */ +.table { + margin-bottom: 0; +} + +.table th { + background-color: #f8f9fa; + font-weight: 600; + color: #495057; +} + +.table td { + vertical-align: middle; +} + +/* Button Styles */ +.btn-primary { + background-color: #0d6efd; + border-color: #0d6efd; + font-weight: 500; +} + +.btn-primary:hover { + background-color: #0b5ed7; + border-color: #0a58ca; +} + +/* Form Text */ +.form-text { + color: #6c757d; + font-size: 0.875rem; + margin-top: 0.25rem; +} + +/* Responsive Adjustments */ +@media (max-width: 768px) { + .container-main { + margin: 1rem auto; + } + + .section-content { + padding: 1rem; + } + + .col-md-3 { + margin-bottom: 1rem; + } +} \ No newline at end of file diff --git a/app/static/js/mold_calculator.js b/app/static/js/mold_calculator.js new file mode 100755 index 0000000..4c8990f --- /dev/null +++ b/app/static/js/mold_calculator.js @@ -0,0 +1,638 @@ +// static/js/mold_calculator.js +// Check if script is already initialized +if (window.moldCalculatorInitialized) { + console.log('Mold calculator already initialized, skipping...'); +} else { + window.moldCalculatorInitialized = true; + + // Wait for DOM to be fully loaded + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeCalculator); + } else { + initializeCalculator(); + } +} + +function initializeCalculator() { + console.log('DOM Content Loaded - Initializing mold calculator'); + + // Get form elements + const form = document.getElementById('calcForm'); + const mainSelect = document.getElementById('mold_main_type'); + const subSelect = document.getElementById('mold_sub_type'); + const specificTypeSelect = document.getElementById('mold_specific_type'); + const stageSelect = document.getElementById('stage'); + const typeSelect = document.getElementById('type'); + const toolingIdInput = document.getElementById('tooling_id'); + const submitButton = document.getElementById('calculateBtn'); + + console.log('Main select element:', mainSelect); + console.log('Sub select element:', subSelect); + console.log('Specific type select element:', specificTypeSelect); + console.log('Stage select element:', stageSelect); + console.log('Type select element:', typeSelect); + console.log('Tooling ID input:', toolingIdInput); + console.log('Submit button:', submitButton); + + // Check if submit button exists + if (!submitButton) { + console.error('Submit button not found! Looking for element with ID "calculateBtn"'); + // Try alternative selectors + const alternativeButton = form.querySelector('button[type="submit"]'); + if (alternativeButton) { + console.log('Found submit button using alternative selector:', alternativeButton); + } else { + console.error('No submit button found with any selector!'); + return; // Exit initialization if we can't find the submit button + } + } + + // Define mappings +const moldTypeMapping = { + 'Rubber': ['Flat Outsole', 'Cupsole', 'Rubber Sole'], + 'CMEVA': ['2 Plates Mold', '3 Plates Mold', '2 Plates with in-mold channel', 'Breathable + in-mold channel'], + 'IMEVA': ['2 Plates Mold', '3 Plates Mold'], + 'Sandal': ['Sandal with Upper Bandage', '2 Plate without Upper Bandage'], + 'Co-shot mold': ['Co-shot mold', 'Foaming mold'], + 'PU': ['2 Plates Mold', '3 Plates Mold'] +}; + + const specificTypeOptions = { + 'Flat Outsole': ['Flat', 'Flat with Top/Heel Tip'], + 'Cupsole': ['Top PL', 'Visible PL'], + 'Rubber Sole': ['Top PL', 'Short PL (Heel/Forefoot)', 'Visible PL', 'Wave PL'] +}; + +const stageTypeMapping = { + 'CR0': ['Sample'], + 'CR1': ['Sample'], + 'CR2': ['Sample'], + 'SMS': ['A Set'], + 'commercialization': ['A Set'], + 'production': ['Additional'] +}; + + // Function to update sub-type options + function updateSubTypeOptions() { + console.log('Populating sub-types'); + const selectedType = mainSelect.value; + console.log('Selected type:', selectedType); + + // Clear current options + subSelect.innerHTML = ''; + + if (selectedType && moldTypeMapping[selectedType]) { + console.log('Found mapping for type:', selectedType); + const options = moldTypeMapping[selectedType]; + console.log('Adding options:', options); + + options.forEach(option => { + const opt = document.createElement('option'); + opt.value = option; + opt.textContent = option; + subSelect.appendChild(opt); + }); + } + + // Reset specific type + specificTypeSelect.innerHTML = ''; + specificTypeSelect.disabled = true; + } + + // Function to update specific type options + function updateSpecificTypeOptions() { + const selectedSubType = subSelect.value; + console.log('Selected sub-type:', selectedSubType); + + // Clear current options + specificTypeSelect.innerHTML = ''; + + if (selectedSubType && specificTypeOptions[selectedSubType]) { + console.log('Found specific type options for:', selectedSubType); + const options = specificTypeOptions[selectedSubType]; + console.log('Adding specific type options:', options); + + options.forEach(option => { + const opt = document.createElement('option'); + opt.value = option; + opt.textContent = option; + specificTypeSelect.appendChild(opt); + }); + + specificTypeSelect.disabled = false; + } else { + specificTypeSelect.disabled = true; + } + } + + // Function to update type options based on stage + function updateTypeOptions() { + const selectedStage = stageSelect.value; + console.log('Selected stage:', selectedStage); + + // Clear current options + typeSelect.innerHTML = ''; + + if (selectedStage && stageTypeMapping[selectedStage]) { + const options = stageTypeMapping[selectedStage]; + console.log('Adding type options:', options); + + options.forEach(option => { + const opt = document.createElement('option'); + opt.value = option; + opt.textContent = option; + typeSelect.appendChild(opt); + }); + + // Enable the type select + typeSelect.disabled = false; + } else { + // Disable the type select if no stage is selected + typeSelect.disabled = true; + } + } + + // Add event listeners + mainSelect.addEventListener('change', function() { + console.log('Main type changed:', this.value); + updateSubTypeOptions(); + }); + + subSelect.addEventListener('change', function() { + console.log('Sub type changed:', this.value); + updateSpecificTypeOptions(); + }); + + stageSelect.addEventListener('change', function() { + console.log('Stage changed:', this.value); + updateTypeOptions(); + }); + + // Add tooling ID validation + if (toolingIdInput) { + console.log('Tooling ID input found, adding validation'); + toolingIdInput.addEventListener('input', function(e) { + console.log('Tooling ID input event triggered, value:', this.value); + // Remove any non-numeric characters + let value = this.value.replace(/\D/g, ''); + + // Limit to 5 digits + if (value.length > 5) { + value = value.substring(0, 5); + } + + this.value = value; + console.log('Tooling ID value after validation:', this.value); + }); + + toolingIdInput.addEventListener('paste', function(e) { + console.log('Tooling ID paste event triggered'); + e.preventDefault(); + const pastedText = (e.clipboardData || window.clipboardData).getData('text'); + const numericOnly = pastedText.replace(/\D/g, '').substring(0, 5); + this.value = numericOnly; + console.log('Tooling ID value after paste validation:', this.value); + }); + } else { + console.error('Tooling ID input element not found!'); + // Try alternative selectors + const alternativeSelectors = [ + 'input[name="tooling_id"]', + 'input[placeholder*="tooling"]', + 'input[placeholder*="Tooling"]' + ]; + + for (let selector of alternativeSelectors) { + const element = document.querySelector(selector); + if (element) { + console.log('Found tooling ID input using selector:', selector, element); + break; + } + } + } + + // Initialize options + updateSubTypeOptions(); + updateTypeOptions(); + + // Function to reset form + function resetForm(form) { + console.log('Resetting form...'); + + // Reset all select elements to their first option + const selects = form.querySelectorAll('select'); + selects.forEach(select => { + select.selectedIndex = 0; + select.dispatchEvent(new Event('change')); + }); + + // Reset all input elements + const inputs = form.querySelectorAll('input'); + inputs.forEach(input => { + input.value = ''; + }); + + // Reset all checkboxes and radio buttons + const checkboxes = form.querySelectorAll('input[type="checkbox"], input[type="radio"]'); + checkboxes.forEach(checkbox => { + checkbox.checked = false; + }); + + // Disable dependent dropdowns + if (subSelect) subSelect.disabled = true; + if (specificTypeSelect) specificTypeSelect.disabled = true; + if (typeSelect) typeSelect.disabled = true; + + // Show reset notification + const notification = document.createElement('div'); + notification.className = 'alert alert-info alert-dismissible fade show mt-3'; + notification.innerHTML = ` + + Form has been reset. You can now create a new quotation. + + `; + form.insertBefore(notification, form.firstChild); + + console.log('Form reset complete'); + } + + // Handle form submission + form.addEventListener('submit', async function(e) { + e.preventDefault(); + console.log('Form submitted'); + + // Get submit button robustly + let submitButton = document.getElementById('calculateBtn') || form.querySelector('button[type="submit"]'); + + if (!submitButton) { + console.error('Submit button not found during form submission!'); + alert('Error: Submit button not found. Please refresh the page and try again.'); + return; + } + + // Check if submission is already in progress + if (submitButton.disabled || form.dataset.submitting === 'true') { + console.log('Form submission already in progress or cooldown active, ignoring duplicate click'); + return; + } + + // Set submission state + form.dataset.submitting = 'true'; + + // Disable submit button and show loading state + submitButton.disabled = true; + const originalButtonText = submitButton.innerHTML; + submitButton.innerHTML = 'Calculating...'; + submitButton.classList.add('btn-secondary'); + submitButton.classList.remove('btn-primary'); + submitButton.title = 'Please wait while calculating...'; + + try { + // Log all form fields + console.log('Form fields:'); + for (let [key, value] of new FormData(form).entries()) { + console.log(`${key}: ${value}`); + } + + // Validate required fields + const mainType = mainSelect.value; + const subType = subSelect.value; + const specificType = specificTypeSelect.value; + const stage = stageSelect.value; + const type = typeSelect.value; + const toolingId = toolingIdInput ? toolingIdInput.value : ''; + + console.log('Required fields:', { + mainType, + subType, + specificType, + stage, + type, + toolingId + }); + + if (!mainType) { + alert('Please select a main type'); + return; + } + if (!subType) { + alert('Please select a sub type'); + return; + } + if (!stage) { + alert('Please select a stage'); + return; + } + if (!type) { + alert('Please select a type'); + return; + } + + // Validate tooling ID + if (!toolingId) { + alert('Please enter a Tooling ID'); + return; + } + if (!/^\d{5}$/.test(toolingId)) { + alert('Tooling ID must be exactly 5 digits'); + return; + } + + // Check if specific type is required + const validSpecificTypes = specificTypeOptions[subType] || []; + if (validSpecificTypes.length > 0 && !specificType) { + alert('Please select a specific type'); + return; + } + + // Create form data + const formData = new FormData(form); + + // Add CSRF token to form data + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + formData.append('csrf_token', csrfToken); + + // Remove specific type if it's not needed + if (validSpecificTypes.length === 0) { + formData.delete('mold_specific_type'); + } + + // Log the final form data being sent + console.log('Final form data being sent:'); + for (let [key, value] of formData.entries()) { + console.log(`${key}: ${value}`); + } + + const response = await fetch('/calculate', { + method: 'POST', + body: formData + }); + + console.log('Response status:', response.status); + const responseText = await response.text(); + console.log('Response text:', responseText); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}, body: ${responseText}`); + } + + const data = JSON.parse(responseText); + console.log('Calculation result:', data); + + // Get the result element + const resultElement = document.getElementById('result'); + + // Update the result display + updateResultsDisplay(data); + + // Save the quotation to the database + try { + console.log('Saving quotation to database...'); + const saveResponse = await fetch('/save-quotation', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify(data) + }); + + const saveResult = await saveResponse.json(); + console.log('Save result:', saveResult); + + if (saveResult.success) { + console.log('Quotation saved successfully with ID:', saveResult.quotation_id); + + // Show success message + const successMessage = document.createElement('div'); + successMessage.className = 'alert alert-success alert-dismissible fade show'; + successMessage.innerHTML = ` + + Quotation saved successfully! ID: ${saveResult.quotation_id} + + `; + resultElement.insertBefore(successMessage, resultElement.firstChild); + + // Reset form after successful save + resetForm(form); + + // Refresh quote history + setTimeout(() => { + refreshQuoteHistory(); + }, 1000); + } else if (saveResult.duplicate) { + console.log('Duplicate submission detected:', saveResult.message); + const duplicateMessage = document.createElement('div'); + duplicateMessage.className = 'alert alert-warning alert-dismissible fade show'; + duplicateMessage.innerHTML = ` + + ${saveResult.message} + + `; + resultElement.insertBefore(duplicateMessage, resultElement.firstChild); + + // Reset form even on duplicate to prevent repeated attempts + resetForm(form); + } + } catch (saveError) { + console.error('Error saving quotation:', saveError); + const errorMessage = document.createElement('div'); + errorMessage.className = 'alert alert-danger alert-dismissible fade show'; + errorMessage.innerHTML = ` + + Error saving quotation. Please try again later. + + `; + resultElement.insertBefore(errorMessage, resultElement.firstChild); + } + } catch (error) { + console.error('Error:', error); + alert('An error occurred while calculating. Please check the console for details.'); + } finally { + // Re-enable submit button and restore original state after a delay + setTimeout(() => { + let submitButton = document.getElementById('calculateBtn') || form.querySelector('button[type="submit"]'); + + if (submitButton) { + submitButton.disabled = false; + submitButton.innerHTML = originalButtonText; + submitButton.classList.remove('btn-secondary'); + submitButton.classList.add('btn-primary'); + submitButton.title = 'Calculate mold cost'; + } + + // Reset submission state + form.dataset.submitting = 'false'; + }, 2000); // 2 second cooldown before allowing new submission + } + }); +} + +// Function to update results display +function updateResultsDisplay(data) { + const resultElement = document.getElementById('result'); + if (!resultElement) { + console.error('Result element not found'); + return; + } + + // Format the results to match the provided screenshot + const formattedResults = ` +
| Base Price | $${Number(data.base_price).toFixed(2)} |
| Complexity Adjustment | $${Number(data.complexity_cost).toFixed(2)} |
| Digital Texture Cost | $${Number(data.texture_cost).toFixed(2)} |
| Total Cost | $${Number(data.total_cost).toFixed(2)} |