diff --git a/.deploy_last.json b/.deploy_last.json new file mode 100644 index 0000000..75c0470 --- /dev/null +++ b/.deploy_last.json @@ -0,0 +1,328 @@ +{ + "status": 0, + "result": { + "checkOnly": false, + "completedDate": "2025-09-07T02:50:58.000Z", + "createdBy": "005a3000000Uv2T", + "createdByName": "Property Master", + "createdDate": "2025-09-07T02:50:52.000Z", + "details": { + "componentSuccesses": [ + { + "changed": false, + "componentType": "CustomField", + "created": false, + "createdDate": "2025-09-07T02:50:56.000Z", + "deleted": false, + "fileName": "objects/Property_Template__c.object", + "fullName": "Property_Template__c.Description__c", + "id": "00NFV000001x1kH2AQ", + "success": true + }, + { + "changed": false, + "componentType": "CustomField", + "created": false, + "createdDate": "2025-09-07T02:50:56.000Z", + "deleted": false, + "fileName": "objects/Property_Template__c.object", + "fullName": "Property_Template__c.Is_Active__c", + "id": "00NFV000001x1kI2AQ", + "success": true + }, + { + "changed": false, + "componentType": "CustomField", + "created": false, + "createdDate": "2025-09-07T02:50:56.000Z", + "deleted": false, + "fileName": "objects/Property_Template__c.object", + "fullName": "Property_Template__c.Preview_Image_URL__c", + "id": "00NFV000001x1kJ2AQ", + "success": true + }, + { + "changed": false, + "componentType": "CustomField", + "created": false, + "createdDate": "2025-09-07T02:50:56.000Z", + "deleted": false, + "fileName": "objects/Property_Template__c.object", + "fullName": "Property_Template__c.Tags__c", + "id": "00NFV000001x1kK2AQ", + "success": true + }, + { + "changed": false, + "componentType": "CustomField", + "created": false, + "createdDate": "2025-09-07T02:50:56.000Z", + "deleted": false, + "fileName": "objects/Property_Template__c.object", + "fullName": "Property_Template__c.Template_Definition__c", + "id": "00NFV000001x1kL2AQ", + "success": true + }, + { + "changed": false, + "componentType": "ApexClass", + "created": false, + "createdDate": "2025-09-07T02:50:57.000Z", + "deleted": false, + "fileName": "classes/PdfApiController.cls", + "fullName": "PdfApiController", + "id": "01pFV000001hBIzYAM", + "success": true + }, + { + "changed": false, + "componentType": "ApexClass", + "created": false, + "createdDate": "2025-09-07T02:50:57.000Z", + "deleted": false, + "fileName": "classes/PropertyDataController.cls", + "fullName": "PropertyDataController", + "id": "01pFV000001hATNYA2", + "success": true + }, + { + "changed": false, + "componentType": "ApexClass", + "created": false, + "createdDate": "2025-09-07T02:50:57.000Z", + "deleted": false, + "fileName": "classes/PropertyPdfGeneratorController.cls", + "fullName": "PropertyPdfGeneratorController", + "id": "01pFV000001hDhNYAU", + "success": true + }, + { + "changed": false, + "componentType": "ApexClass", + "created": false, + "createdDate": "2025-09-07T02:50:57.000Z", + "deleted": false, + "fileName": "classes/PropertyTemplateController.cls", + "fullName": "PropertyTemplateController", + "id": "01pFV000001hAA1YAM", + "success": true + }, + { + "changed": true, + "componentType": "LightningComponentBundle", + "created": false, + "createdDate": "2025-09-07T02:50:58.000Z", + "deleted": false, + "fileName": "lwc/propertyTemplateSelector", + "fullName": "propertyTemplateSelector", + "id": "0RbFV0000008L7J0AU", + "success": true + }, + { + "changed": true, + "componentType": "CustomObject", + "created": false, + "createdDate": "2025-09-07T02:50:58.000Z", + "deleted": false, + "fileName": "objects/Property_Template__c.object", + "fullName": "Property_Template__c", + "id": "01IFV000000CbQP2A0", + "success": true + }, + { + "changed": true, + "componentType": "CspTrustedSite", + "created": false, + "createdDate": "2025-09-07T02:50:58.000Z", + "deleted": false, + "fileName": "cspTrustedSites/PDF_API_Trusted_Site.cspTrustedSite", + "fullName": "PDF_API_Trusted_Site", + "id": "08yFV0000000U1JYAU", + "success": true + }, + { + "changed": true, + "componentType": "", + "created": false, + "createdDate": "2025-09-07T02:50:58.000Z", + "deleted": false, + "fileName": "package.xml", + "fullName": "package.xml", + "success": true + } + ], + "runTestResult": { + "numFailures": 0, + "numTestsRun": 0, + "totalTime": 0, + "codeCoverage": [], + "codeCoverageWarnings": [], + "failures": [], + "flowCoverage": [], + "flowCoverageWarnings": [], + "successes": [] + }, + "componentFailures": [] + }, + "done": true, + "id": "0AfFV000003mAxp0AE", + "ignoreWarnings": false, + "lastModifiedDate": "2025-09-07T02:50:58.000Z", + "numberComponentErrors": 0, + "numberComponentsDeployed": 12, + "numberComponentsTotal": 12, + "numberFiles": "24", + "numberTestErrors": 0, + "numberTestsCompleted": 0, + "numberTestsTotal": 0, + "rollbackOnError": true, + "runTestsEnabled": false, + "startDate": "2025-09-07T02:50:53.000Z", + "status": "Succeeded", + "success": true, + "zipSize": 286681, + "files": [ + { + "fullName": "PdfApiController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PdfApiController.cls" + }, + { + "fullName": "PdfApiController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PdfApiController.cls-meta.xml" + }, + { + "fullName": "PropertyDataController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PropertyDataController.cls" + }, + { + "fullName": "PropertyDataController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PropertyDataController.cls-meta.xml" + }, + { + "fullName": "PropertyPdfGeneratorController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PropertyPdfGeneratorController.cls" + }, + { + "fullName": "PropertyPdfGeneratorController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PropertyPdfGeneratorController.cls-meta.xml" + }, + { + "fullName": "PropertyTemplateController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PropertyTemplateController.cls" + }, + { + "fullName": "PropertyTemplateController", + "type": "ApexClass", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/classes/PropertyTemplateController.cls-meta.xml" + }, + { + "fullName": "PDF_API_Trusted_Site", + "type": "CspTrustedSite", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/cspTrustedSites/PDF_API_Trusted_Site.cspTrustedSite-meta.xml" + }, + { + "fullName": "Property_Template__c.Description__c", + "type": "CustomField", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/objects/Property_Template__c/fields/Description__c.field-meta.xml" + }, + { + "fullName": "Property_Template__c.Is_Active__c", + "type": "CustomField", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/objects/Property_Template__c/fields/Is_Active__c.field-meta.xml" + }, + { + "fullName": "Property_Template__c.Preview_Image_URL__c", + "type": "CustomField", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/objects/Property_Template__c/fields/Preview_Image_URL__c.field-meta.xml" + }, + { + "fullName": "Property_Template__c.Tags__c", + "type": "CustomField", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/objects/Property_Template__c/fields/Tags__c.field-meta.xml" + }, + { + "fullName": "Property_Template__c.Template_Definition__c", + "type": "CustomField", + "state": "Unchanged", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/objects/Property_Template__c/fields/Template_Definition__c.field-meta.xml" + }, + { + "fullName": "Property_Template__c", + "type": "CustomObject", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/objects/Property_Template__c/Property_Template__c.object-meta.xml" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/production-config.js" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css.backup" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html.backup" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js-meta.xml" + }, + { + "fullName": "propertyTemplateSelector", + "type": "LightningComponentBundle", + "state": "Changed", + "filePath": "/home/ubuntu/salesforce/PDF_Generation_and_Automation/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js.backup" + } + ], + "zipFileCount": 19, + "deployUrl": "https://tso3--r1.sandbox.my.salesforce.com/lightning/setup/DeployStatus/page?address=%2Fchangemgmt%2FmonitorDeploymentsDetails.apexp%3FasyncId%3D0AfFV000003mAxp0AE%26retURL%3D%252Fchangemgmt%252FmonitorDeployment.apexp" + }, + "warnings": [] +} diff --git a/force-app/main/default/classes/PDFGenerationProxy.cls b/force-app/main/default/classes/PDFGenerationProxy.cls index 4068939..2055cce 100644 --- a/force-app/main/default/classes/PDFGenerationProxy.cls +++ b/force-app/main/default/classes/PDFGenerationProxy.cls @@ -8,9 +8,6 @@ public with sharing class PDFGenerationProxy { throw new AuraHandledException('HTML content cannot be empty. Please provide valid HTML content.'); } - System.debug('=== PDF GENERATION DEBUG ==='); - System.debug('HTML Content Length: ' + htmlContent.length()); - System.debug('Page Size: ' + pageSize); // Call the Node.js API with return_download_link: true Http http = new Http(); @@ -22,24 +19,43 @@ public with sharing class PDFGenerationProxy { request.setHeader('Content-Type', 'application/json'); request.setTimeout(120000); // 2 minutes timeout + // Ensure relative resource URLs resolve to Salesforce domain by injecting + String modifiedHtml = htmlContent; + String baseUrl = null; + try { + baseUrl = URL.getOrgDomainUrl().toExternalForm(); + if (modifiedHtml != null && !modifiedHtml.toLowerCase().contains(''); + if (headIdx >= 0) { + Integer insertPos = headIdx + 6; // after + modifiedHtml = modifiedHtml.substring(0, insertPos) + '' + modifiedHtml.substring(insertPos); + } else { + // Prepend base if no head tag found + modifiedHtml = '' + modifiedHtml; + } + } + } catch (Exception e) { + // best-effort only + } + // Prepare the request body Map requestBody = new Map(); - requestBody.put('input', htmlContent); + requestBody.put('input', modifiedHtml); requestBody.put('return_download_link', true); - if (String.isNotBlank(pageSize)) { - requestBody.put('page_size', pageSize); - } + // Use 'format' for the Node API, keep page_size for backward-compat + String formatVal = String.isNotBlank(pageSize) ? pageSize : 'A4'; + requestBody.put('format', formatVal); + requestBody.put('page_size', formatVal); + String jsonBody = JSON.serialize(requestBody); request.setBody(jsonBody); - System.debug('Request body: ' + jsonBody); // Make the HTTP call HttpResponse response = http.send(request); - System.debug('Response status: ' + response.getStatusCode()); - System.debug('Response body: ' + response.getBody()); if (response.getStatusCode() == 200) { // Parse the response @@ -63,8 +79,6 @@ public with sharing class PDFGenerationProxy { } } catch (Exception e) { - System.debug('Exception in generatePDFFromHTML: ' + e.getMessage()); - System.debug('Exception stack trace: ' + e.getStackTraceString()); Map errorResult = new Map(); errorResult.put('success', false); @@ -84,9 +98,6 @@ public with sharing class PDFGenerationProxy { throw new AuraHandledException('HTML content cannot be empty. Please provide valid HTML content.'); } - System.debug('=== COMPRESSED PDF GENERATION DEBUG ==='); - System.debug('HTML Content Length: ' + htmlContent.length()); - System.debug('Page Size: ' + pageSize); // Call the Node.js API with return_download_link: true Http http = new Http(); @@ -109,13 +120,10 @@ public with sharing class PDFGenerationProxy { String jsonBody = JSON.serialize(requestBody); request.setBody(jsonBody); - System.debug('Request body: ' + jsonBody); // Make the HTTP call HttpResponse response = http.send(request); - System.debug('Response status: ' + response.getStatusCode()); - System.debug('Response body: ' + response.getBody()); if (response.getStatusCode() == 200) { // Parse the response @@ -142,8 +150,6 @@ public with sharing class PDFGenerationProxy { } } catch (Exception e) { - System.debug('Exception in generateCompressedPDF: ' + e.getMessage()); - System.debug('Exception stack trace: ' + e.getStackTraceString()); Map errorResult = new Map(); errorResult.put('success', false); diff --git a/force-app/main/default/lwc/developmentPage/developmentPage.css b/force-app/main/default/lwc/developmentPage/developmentPage.css new file mode 100644 index 0000000..5074011 --- /dev/null +++ b/force-app/main/default/lwc/developmentPage/developmentPage.css @@ -0,0 +1,234 @@ +.development-page { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); + z-index: 9999; + overflow-y: auto; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + color: #ffffff; +} + +.dev-header { + background: rgba(0, 0, 0, 0.8); + padding: 20px; + text-align: center; + border-bottom: 2px solid #ff6b6b; + position: sticky; + top: 0; + z-index: 10000; +} + +.dev-header h1 { + margin: 0; + font-size: 2.5rem; + color: #ff6b6b; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); +} + +.dev-warning { + margin: 10px 0; + font-size: 1.2rem; + color: #ffd93d; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 2px; +} + +.dev-instruction { + margin: 10px 0; + font-size: 1rem; + color: #74b9ff; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; +} + +.close-dev-btn { + position: absolute; + top: 20px; + right: 20px; + background: #ff4757; + color: white; + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + font-size: 1.5rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.close-dev-btn:hover { + background: #ff3742; + transform: scale(1.1); +} + +.dev-content { + padding: 30px; + max-width: 1200px; + margin: 0 auto; +} + +.dev-section { + background: rgba(255, 255, 255, 0.1); + border-radius: 15px; + padding: 25px; + margin-bottom: 25px; + border: 1px solid rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); +} + +.dev-section h2 { + margin: 0 0 20px 0; + color: #74b9ff; + font-size: 1.8rem; + border-bottom: 2px solid #74b9ff; + padding-bottom: 10px; +} + +.dev-tools { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; +} + +.dev-btn { + background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + padding: 15px 20px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.dev-btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +.dev-btn.primary { + background: linear-gradient(45deg, #00b894 0%, #00a085 100%); +} + +.dev-btn.primary:hover { + box-shadow: 0 5px 15px rgba(0, 184, 148, 0.4); +} + +.dev-btn.secondary { + background: linear-gradient(45deg, #6c5ce7 0%, #a29bfe 100%); +} + +.dev-btn.secondary:hover { + box-shadow: 0 5px 15px rgba(108, 92, 231, 0.4); +} + +.dev-btn.warning { + background: linear-gradient(45deg, #e17055 0%, #d63031 100%); +} + +.dev-btn.warning:hover { + box-shadow: 0 5px 15px rgba(225, 112, 85, 0.4); +} + +.status-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 15px; +} + +.status-item { + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + padding: 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-left: 4px solid #74b9ff; +} + +.status-label { + font-weight: 600; + color: #ddd; +} + +.status-value { + font-weight: bold; + padding: 5px 10px; + border-radius: 20px; + font-size: 0.9rem; +} + +.status-value.success { + background: rgba(0, 184, 148, 0.2); + color: #00b894; + border: 1px solid #00b894; +} + +.debug-info { + background: rgba(0, 0, 0, 0.3); + border-radius: 8px; + padding: 20px; + font-family: 'Courier New', monospace; +} + +.debug-info p { + margin: 8px 0; + color: #ddd; +} + +.debug-info strong { + color: #74b9ff; +} + +.quick-actions { + display: flex; + gap: 15px; + flex-wrap: wrap; +} + +/* Responsive design */ +@media (max-width: 768px) { + .dev-content { + padding: 15px; + } + + .dev-header h1 { + font-size: 2rem; + } + + .dev-tools { + grid-template-columns: 1fr; + } + + .quick-actions { + flex-direction: column; + } + + .status-grid { + grid-template-columns: 1fr; + } +} + +/* Animation for page entrance */ +@keyframes slideInFromTop { + from { + transform: translateY(-100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.development-page { + animation: slideInFromTop 0.5s ease-out; +} diff --git a/force-app/main/default/lwc/developmentPage/developmentPage.html b/force-app/main/default/lwc/developmentPage/developmentPage.html new file mode 100644 index 0000000..a036442 --- /dev/null +++ b/force-app/main/default/lwc/developmentPage/developmentPage.html @@ -0,0 +1,53 @@ + diff --git a/force-app/main/default/lwc/developmentPage/developmentPage.js b/force-app/main/default/lwc/developmentPage/developmentPage.js new file mode 100644 index 0000000..c6f12d1 --- /dev/null +++ b/force-app/main/default/lwc/developmentPage/developmentPage.js @@ -0,0 +1,162 @@ +import { LightningElement, track } from 'lwc'; + +export default class DevelopmentPage extends LightningElement { + @track showDevPage = true; + @track currentStep = 1; + @track selectedTemplateId = ''; + @track selectedPropertyId = ''; + @track selectedPageSize = 'A4'; + @track currentTimestamp = ''; + @track debugMode = false; + + // V-key click counter + vKeyCount = 0; + vKeyTimeout = null; + + connectedCallback() { + this.updateTimestamp(); + this.addKeyListener(); + } + + disconnectedCallback() { + this.removeKeyListener(); + if (this.vKeyTimeout) { + clearTimeout(this.vKeyTimeout); + } + } + + addKeyListener() { + document.addEventListener('keydown', this.handleKeyPress.bind(this)); + } + + removeKeyListener() { + document.removeEventListener('keydown', this.handleKeyPress.bind(this)); + } + + handleKeyPress(event) { + // Only listen for 'V' key (case insensitive) + if (event.key.toLowerCase() === 'v') { + this.vKeyCount++; + + // Clear any existing timeout + if (this.vKeyTimeout) { + clearTimeout(this.vKeyTimeout); + } + + // Reset counter after 3 seconds of inactivity + this.vKeyTimeout = setTimeout(() => { + this.vKeyCount = 0; + }, 3000); + + // Hide dev page after 4 V key presses + if (this.vKeyCount >= 4) { + this.showDevPage = false; + this.vKeyCount = 0; // Reset counter + } + } + } + + closeDevPage() { + this.showDevPage = false; + this.vKeyCount = 0; + if (this.vKeyTimeout) { + clearTimeout(this.vKeyTimeout); + } + } + + updateTimestamp() { + this.currentTimestamp = new Date().toLocaleString(); + } + + // Development actions + clearAllData() { + if (confirm('Are you sure you want to clear all data? This action cannot be undone.')) { + // Clear all component data + this.currentStep = 1; + this.selectedTemplateId = ''; + this.selectedPropertyId = ''; + this.selectedPageSize = 'A4'; + this.updateTimestamp(); + + // Dispatch event to parent component + this.dispatchEvent(new CustomEvent('cleardata')); + } + } + + resetTemplates() { + if (confirm('Reset all templates to default state?')) { + // Dispatch event to parent component + this.dispatchEvent(new CustomEvent('resettemplates')); + } + } + + exportDebugInfo() { + const debugData = { + timestamp: this.currentTimestamp, + currentStep: this.currentStep, + selectedTemplateId: this.selectedTemplateId, + selectedPropertyId: this.selectedPropertyId, + selectedPageSize: this.selectedPageSize, + userAgent: navigator.userAgent, + url: window.location.href + }; + + const blob = new Blob([JSON.stringify(debugData, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `debug-info-${Date.now()}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + } + + testPdfGeneration() { + // Dispatch event to parent component + this.dispatchEvent(new CustomEvent('testpdf')); + } + + forceReload() { + if (confirm('Force reload the entire application?')) { + window.location.reload(); + } + } + + toggleDebugMode() { + this.debugMode = !this.debugMode; + // Dispatch event to parent component + this.dispatchEvent(new CustomEvent('toggledebug', { + detail: { debugMode: this.debugMode } + })); + } + + clearCache() { + if (confirm('Clear browser cache and localStorage?')) { + // Clear localStorage + localStorage.clear(); + sessionStorage.clear(); + + // Clear any cached data + if ('caches' in window) { + caches.keys().then(names => { + names.forEach(name => { + caches.delete(name); + }); + }); + } + + alert('Cache cleared successfully!'); + } + } + + // Method to update data from parent component + updateData(data) { + if (data.currentStep !== undefined) this.currentStep = data.currentStep; + if (data.selectedTemplateId !== undefined) this.selectedTemplateId = data.selectedTemplateId; + if (data.selectedPropertyId !== undefined) this.selectedPropertyId = data.selectedPropertyId; + if (data.selectedPageSize !== undefined) this.selectedPageSize = data.selectedPageSize; + this.updateTimestamp(); + } +} diff --git a/force-app/main/default/lwc/developmentPage/developmentPage.js-meta.xml b/force-app/main/default/lwc/developmentPage/developmentPage.js-meta.xml new file mode 100644 index 0000000..05279af --- /dev/null +++ b/force-app/main/default/lwc/developmentPage/developmentPage.js-meta.xml @@ -0,0 +1,10 @@ + + + 58.0 + false + + lightning__AppPage + lightning__RecordPage + lightning__HomePage + + diff --git a/force-app/main/default/lwc/propertyTemplateSelector/professional-editor.css b/force-app/main/default/lwc/propertyTemplateSelector/professional-editor.css new file mode 100644 index 0000000..c3ac645 --- /dev/null +++ b/force-app/main/default/lwc/propertyTemplateSelector/professional-editor.css @@ -0,0 +1,560 @@ +/* Professional Editor Styles */ +.professional-editor-container { + display: flex; + flex-direction: column; + height: 100%; + background: #ffffff; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +/* Editor Header */ +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 24px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.editor-title { + display: flex; + align-items: center; + gap: 12px; +} + +.editor-title h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 600; +} + +.editor-actions { + display: flex; + gap: 12px; +} + +.header-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 6px; + color: white; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.header-btn:hover { + background: rgba(255, 255, 255, 0.3); + transform: translateY(-1px); +} + +.header-btn.save { + background: rgba(46, 204, 113, 0.8); + border-color: rgba(46, 204, 113, 0.9); +} + +.header-btn.save:hover { + background: rgba(46, 204, 113, 1); +} + +/* Editor Layout */ +.editor-layout { + display: flex; + flex: 1; + min-height: 0; +} + +/* Professional Sidebar */ +.editor-sidebar { + width: 320px; + background: #f8f9fa; + border-right: 1px solid #e9ecef; + display: flex; + flex-direction: column; + transition: width 0.3s ease; +} + +.editor-sidebar.collapsed { + width: 60px; +} + +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + background: #ffffff; + border-bottom: 1px solid #e9ecef; +} + +.sidebar-header h4 { + margin: 0; + font-size: 1rem; + font-weight: 600; + color: #495057; +} + +.sidebar-toggle { + background: none; + border: none; + color: #6c757d; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; +} + +.sidebar-toggle:hover { + background: #e9ecef; + color: #495057; +} + +.sidebar-content { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +/* Tool Sections */ +.tool-section { + margin-bottom: 24px; + background: #ffffff; + border-radius: 8px; + border: 1px solid #e9ecef; + overflow: hidden; +} + +.section-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; + font-size: 0.875rem; + font-weight: 600; + color: #495057; +} + +.tool-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + padding: 16px; +} + +.tool-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 12px 8px; + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 6px; + color: #495057; + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; +} + +.tool-btn:hover { + background: #f8f9fa; + border-color: #667eea; + color: #667eea; + transform: translateY(-1px); +} + +.tool-btn.primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-color: #667eea; +} + +.tool-btn.primary:hover { + background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +.tool-btn.secondary { + background: #f8f9fa; + border-color: #dee2e6; +} + +.tool-btn.secondary:hover { + background: #e9ecef; + border-color: #adb5bd; +} + +/* Formatting Controls */ +.formatting-controls { + padding: 16px; + border-bottom: 1px solid #e9ecef; +} + +.control-group { + margin-bottom: 12px; +} + +.control-group:last-child { + margin-bottom: 0; +} + +.control-group label { + display: block; + margin-bottom: 4px; + font-size: 0.75rem; + font-weight: 600; + color: #6c757d; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.control-select { + width: 100%; + padding: 8px 12px; + border: 1px solid #e9ecef; + border-radius: 4px; + background: #ffffff; + font-size: 0.875rem; + color: #495057; + transition: border-color 0.2s ease; +} + +.control-select:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +/* Style Buttons */ +.style-buttons { + display: flex; + gap: 8px; + padding: 16px; + justify-content: center; +} + +.style-btn { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 6px; + color: #495057; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.style-btn:hover { + background: #f8f9fa; + border-color: #667eea; + color: #667eea; + transform: translateY(-1px); +} + +.style-btn.active { + background: #667eea; + border-color: #667eea; + color: white; +} + +.highlight-icon { + background: #ffd93d; + color: #333; + padding: 2px 4px; + border-radius: 2px; + font-weight: bold; +} + +/* Alignment & List Buttons */ +.alignment-buttons, .list-buttons { + display: flex; + gap: 8px; + padding: 16px; + justify-content: center; +} + +.align-btn, .list-btn { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 6px; + color: #495057; + cursor: pointer; + transition: all 0.2s ease; +} + +.align-btn:hover, .list-btn:hover { + background: #f8f9fa; + border-color: #667eea; + color: #667eea; + transform: translateY(-1px); +} + +/* Media Buttons */ +.media-buttons { + display: flex; + flex-direction: column; + gap: 8px; + padding: 16px; +} + +.media-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 6px; + color: #495057; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.media-btn:hover { + background: #f8f9fa; + border-color: #667eea; + color: #667eea; + transform: translateY(-1px); +} + +/* Action Buttons */ +.action-buttons { + display: flex; + flex-direction: column; + gap: 8px; + padding: 16px; +} + +.action-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 6px; + color: #495057; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.action-btn:hover { + background: #f8f9fa; + border-color: #667eea; + color: #667eea; + transform: translateY(-1px); +} + +.action-btn.undo { + border-color: #ffc107; + color: #856404; +} + +.action-btn.undo:hover { + background: #fff3cd; + border-color: #ffb300; +} + +.action-btn.redo { + border-color: #17a2b8; + color: #0c5460; +} + +.action-btn.redo:hover { + background: #d1ecf1; + border-color: #138496; +} + +.action-btn.save { + background: #28a745; + border-color: #28a745; + color: white; +} + +.action-btn.save:hover { + background: #218838; + border-color: #1e7e34; +} + +.action-btn.reset { + border-color: #dc3545; + color: #721c24; +} + +.action-btn.reset:hover { + background: #f8d7da; + border-color: #c82333; +} + +/* Professional Viewport */ +.editor-viewport { + flex: 1; + display: flex; + flex-direction: column; + background: #f8f9fa; +} + +.viewport-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 20px; + background: #ffffff; + border-bottom: 1px solid #e9ecef; +} + +.viewport-controls { + display: flex; + align-items: center; + gap: 12px; +} + +.viewport-btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 4px; + color: #495057; + cursor: pointer; + transition: all 0.2s ease; +} + +.viewport-btn:hover { + background: #f8f9fa; + border-color: #667eea; + color: #667eea; +} + +.zoom-level { + font-size: 0.875rem; + font-weight: 500; + color: #495057; + min-width: 40px; + text-align: center; +} + +.viewport-info { + display: flex; + align-items: center; + gap: 8px; +} + +.page-size { + padding: 4px 8px; + background: #e9ecef; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 600; + color: #495057; +} + +.viewport-content { + flex: 1; + padding: 20px; + overflow: auto; + display: flex; + justify-content: center; + align-items: flex-start; +} + +.editor-canvas { + background: #ffffff; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + overflow: hidden; + min-width: 800px; + max-width: 100%; +} + +.enhanced-editor-content { + min-height: 600px; + padding: 40px; + outline: none; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; +} + +.enhanced-editor-content:focus { + box-shadow: inset 0 0 0 2px #667eea; +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .editor-sidebar { + width: 280px; + } + + .tool-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .editor-layout { + flex-direction: column; + } + + .editor-sidebar { + width: 100%; + height: auto; + max-height: 300px; + } + + .editor-canvas { + min-width: 100%; + } + + .enhanced-editor-content { + padding: 20px; + min-height: 400px; + } +} + +/* Animation for smooth transitions */ +.tool-section { + animation: slideInUp 0.3s ease-out; +} + +@keyframes slideInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/force-app/main/default/lwc/propertyTemplateSelector/professional-editor.html b/force-app/main/default/lwc/propertyTemplateSelector/professional-editor.html new file mode 100644 index 0000000..c6aa5de --- /dev/null +++ b/force-app/main/default/lwc/propertyTemplateSelector/professional-editor.html @@ -0,0 +1,231 @@ + +
+ +
+
+ +

Template Editor

+
+
+ + +
+
+ + +
+ +
+ + + +
+ + +
+
+
+ + 100% + +
+
+ {selectedPageSize} +
+
+ +
+
+
+ {htmlContent} +
+
+
+
+
+
diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css index 6871a42..f664bf3 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css @@ -1,3 +1,226 @@ +/* Enhanced Editor Styles */ +.enhanced-editor-content { + position: relative; + min-height: 400px; + padding: 20px; + border: 1px solid #e0e0e0; + border-radius: 8px; + background: white; + outline: none; + overflow: auto; +} + +.enhanced-editor-content:focus { + border-color: #007bff; + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); +} + +/* Enhanced List Styles */ +.enhanced-editor-content ul, +.enhanced-editor-content ol { + margin: 10px 0; + padding-left: 20px; + list-style-position: outside; +} + +.enhanced-editor-content li { + margin-bottom: 5px; + line-height: 1.6; + display: list-item; +} + +.enhanced-editor-content ul { + list-style-type: disc; +} + +.enhanced-editor-content ul li { + list-style-type: disc; +} + +.enhanced-editor-content ol { + list-style-type: decimal; +} + +.enhanced-editor-content ol li { + list-style-type: decimal; +} + +/* Ensure list items are properly displayed */ +.enhanced-editor-content ul li::marker, +.enhanced-editor-content ol li::marker { + color: #333; + font-weight: normal; +} + +/* Enhanced Image Styles */ +.enhanced-editor-content img { + max-width: 100%; + height: auto; + border-radius: 4px; + transition: all 0.2s ease; +} + +.enhanced-editor-content img:hover { + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +/* Enhanced Resize Handle Styles */ +.resize-handle { + position: absolute; + width: 16px; + height: 16px; + background: #007bff; + border: 3px solid white; + border-radius: 50%; + cursor: pointer; + z-index: 1002; + box-shadow: 0 3px 6px rgba(0,0,0,0.3); + transition: all 0.2s ease; +} + +.resize-handle:hover { + background: #0056b3; + transform: scale(1.2); + box-shadow: 0 4px 8px rgba(0,0,0,0.4); +} + +/* Specific handle positions */ +.resize-handle.resize-nw { + top: -8px; + left: -8px; + cursor: nw-resize; +} + +.resize-handle.resize-ne { + top: -8px; + right: -8px; + cursor: ne-resize; +} + +.resize-handle.resize-sw { + bottom: -8px; + left: -8px; + cursor: sw-resize; +} + +.resize-handle.resize-se { + bottom: -8px; + right: -8px; + cursor: se-resize; +} + +/* Delete Button Styles */ +.delete-image-btn { + position: absolute; + top: -8px; + right: -8px; + width: 20px; + height: 20px; + background: #dc3545; + color: white; + border: 2px solid white; + border-radius: 50%; + cursor: pointer; + z-index: 1002; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: bold; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.2s ease; +} + +.delete-image-btn:hover { + background: #c82333; + transform: scale(1.1); +} + +/* Text Close Button Styles */ +.text-close-btn { + position: absolute; + top: -8px; + right: -8px; + width: 20px; + height: 20px; + background: #dc3545; + color: white; + border: 2px solid white; + border-radius: 50%; + cursor: pointer; + z-index: 1002; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: bold; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.2s ease; + opacity: 0; +} + +.text-close-btn:hover { + background: #c82333; + transform: scale(1.1); +} + +/* Floating Image Toolbar */ +.floating-image-toolbar { + position: absolute; + top: -45px; + left: 50%; + transform: translateX(-50%); + background: white; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1003; + display: flex; + gap: 8px; + align-items: center; +} + +.floating-image-toolbar button { + width: 32px; + height: 32px; + border: none; + background: #f8f9fa; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + transition: all 0.2s ease; +} + +.floating-image-toolbar button:hover { + background: #e9ecef; + transform: scale(1.1); +} + +/* Draggable Table Container Styles */ +.draggable-table-container .resize-handle.resize-nw { top: -4px; left: -4px; cursor: nw-resize; } +.draggable-table-container .resize-handle.resize-ne { top: -4px; right: -4px; cursor: ne-resize; } +.draggable-table-container .resize-handle.resize-sw { bottom: -4px; left: -4px; cursor: sw-resize; } +.draggable-table-container .resize-handle.resize-se { bottom: -4px; right: -4px; cursor: se-resize; } +/* Draggable table container (mirrors images) */ +.draggable-table-container { position: absolute; cursor: move; user-select: none; z-index: 1000; border: 2px dashed #667eea; border-radius: 8px; background: #ffffff; } +.draggable-table-container .resize-handle { opacity: 1; } +.asgar1-preview .cover-hero { position: relative; height: 180px; overflow: hidden; border-radius: 10px; margin: 12px; background: #222; background-size: cover; background-position: center; } +.asgar1-preview .cover-hero::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to bottom, rgba(0,0,0,0.15), rgba(0,0,0,0.35)); } +.preview-page { width: 210mm; min-height: 297mm; margin: 0 auto 12px; background: white; box-shadow: 0 2px 10px rgba(0,0,0,0.06); border: 1px solid #eee; overflow: hidden; } +.page-size-a4 { width: 210mm; min-height: 297mm; } +.page-size-a3 { width: 297mm; min-height: 420mm; } +.enhanced-editor-content[data-page-size="A4"] .preview-page { width: 210mm; min-height: 297mm; } +.enhanced-editor-content[data-page-size="A3"] .preview-page { width: 297mm; min-height: 420mm; } +.preview-page + .preview-page { page-break-before: always; } + +/* Modern Home card elegance tweaks */ +.modern-home-preview .hero-details { backdrop-filter: saturate(120%) blur(2px); background: rgba(0,0,0,0.25); padding: 10px 12px; border-radius: 8px; display: inline-block; } +.modern-home-preview .stats .stat-item { background: rgba(255,255,255,0.18); padding: 4px 8px; border-radius: 6px; } +.modern-home-preview .agent-footer { border-top: 1px solid rgba(255,255,255,0.15); padding-top: 8px; } .property-brochure-generator { /* International Standard Font Stack - 2024 Best Practices */ font-family: 'Inter Variable', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable', 'Segoe UI', system-ui, 'Roboto', 'Helvetica Neue', Arial, sans-serif; @@ -5,8 +228,8 @@ min-height: 100vh; padding: 0; margin: 0; - max-width: 95%; - margin: 0 auto; + width: 100%; + max-width: 100%; /* Industry Standard Base Font Size - 16px (1rem) */ font-size: 1rem; /* Optimal Line Height for Readability (WCAG AA) */ @@ -260,12 +483,25 @@ late particularay /* Step Content */ .step-content { display: none; - max-width: 1400px; - margin: 0 auto 0 auto; + width: 100%; + max-width: 100%; + margin: 0; padding: 0 3rem 2rem 3rem; background: transparent; } +/* Step Inner Container */ +.step-inner-container { + background: #ffffff; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 2rem; + margin: 1rem 0; + max-width: 90%; + margin-left: auto; + margin-right: auto; +} + .step-content.active { display: block; animation: fadeInUp 0.5s ease; @@ -389,17 +625,9 @@ late particularay } /* Masonry Zigzag Effect - Alternating Heights */ -.template-card:nth-child(odd) { - transform: translateY(10px); -} - -.template-card:nth-child(even) { - transform: translateY(-5px); -} - -.template-card:hover { - transform: translateY(-8px) !important; -} +.template-card:nth-child(odd) { transform: none; } +.template-card:nth-child(even) { transform: none; } +.template-card:hover { transform: none !important; } /* Enhanced Template Content Styles */ .template-content { @@ -497,8 +725,8 @@ late particularay } .cta-btn { - background: #007bff; - color: white; + background: #ffffff; + color: #000000; border: none; padding: 12px 24px; border-radius: 6px; @@ -513,8 +741,8 @@ late particularay } .cta-btn { - background: #007bff; - color: white; + background: #ffffff; + color: #000000; border: none; padding: 15px 35px; border-radius: 25px; @@ -798,7 +1026,6 @@ late particularay height: 100%; background: linear-gradient(180deg, rgba(18, 18, 18, 0.8) 0%, rgba(18, 18, 18, 0.3) 100%); } - .asgar1-preview .cover-header { position: relative; padding: 30px; @@ -1594,7 +1821,6 @@ late particularay color: #ffffff; /* All text should be white as shown in image */ text-shadow: 0 2px 4px rgba(0,0,0,0.3); } - .preview-luxury-title { font-size: 18px; font-weight: 600; @@ -2390,7 +2616,6 @@ late particularay background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: 20px 20px 0 0; } - .image-replacement-header h3 { margin: 0; font-size: 1.25rem; @@ -2547,6 +2772,13 @@ late particularay background: #f0f4ff; } +.upload-dropzone.drag-over { + border-color: #10b981; /* Green for drag over */ + background: #ecfdf5; + border-style: solid; + transform: scale(1.02); +} + .upload-icon { font-size: 3rem; margin-bottom: 1rem; @@ -3174,7 +3406,6 @@ late particularay letter-spacing: 0.01em; line-height: 1.4; } - .property-field .value { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro Text', 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #2c3e50; @@ -3186,7 +3417,6 @@ late particularay letter-spacing: 0.01em; line-height: 1.5; } - /* Property Description Special Styling */ .property-description { display: flex; @@ -3244,6 +3474,36 @@ late particularay line-height: 1.6; } +/* Offering Type Display */ +.offering-type-display { + margin: 1.5rem 0; + text-align: center; +} + +.offering-type-badge { + display: inline-flex; + align-items: center; + gap: 0.75rem; + padding: 1rem 2rem; + background: #007bff; + color: white; + border-radius: 25px; + font-weight: 600; + box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3); +} + +.offering-type-label { + font-size: 0.9rem; + opacity: 0.9; +} + +.offering-type-value { + font-size: 1.1rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; +} + /* Category Navigation for Step 2 - Industry Standard Design */ .category-navigation-step2 { display: flex; @@ -3394,7 +3654,7 @@ late particularay object-fit: cover; margin-bottom: 1.5rem; position: relative; - box-shadow: + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 10px 20px -8px rgba(0, 0, 0, 0.1); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); @@ -3843,6 +4103,235 @@ late particularay margin-top: 20px; } +/* Step 3 viewport toolbar and canvas */ +.viewport-toolbar { display:flex; justify-content:space-between; align-items:center; padding:8px 12px; background:#fff; border:1px solid #e5e5e5; border-radius:8px; } +.zoom-controls { display:flex; gap:8px; align-items:center; } +.viewport-btn { padding:6px 10px; border:1px solid #e5e5e5; background:#fff; border-radius:6px; cursor:pointer; transition: all 0.2s ease; } +.viewport-btn:hover { background:#f8f9fa; border-color:#007bff; } +.zoom-level { min-width:48px; text-align:center; font-weight:500; } +.page-size-display { color:#666; font-size:12px; } +.pdf-viewport { + position:relative; + flex:1; + width: 100%; + height: calc(100vh - 120px); /* Increased height to match toolbar */ + overflow-y: auto; /* Only vertical scroll */ + overflow-x: hidden; /* Remove horizontal scroll */ + display: flex; /* Use flexbox for reliable centering */ + justify-content: center; /* Center horizontally */ + align-items: flex-start; /* Align to top */ + padding: 20px; + background:#f5f5f7; + border:1px solid #e5e5e5; + border-radius:8px; +} +.pdf-canvas { + transform-origin: center top; /* Center the zoom origin for better centering */ + margin: 0; /* Remove margin since flexbox handles centering */ + box-shadow: 0 8px 24px rgba(0,0,0,0.08); + background:transparent; + border: none; + position: relative; + width: 100%; + max-width: 100%; /* Ensure it fits within viewport */ + box-sizing: border-box; + display: block; /* Block display for proper sizing */ +} +.pdf-canvas[data-page-size="A4"] { max-width: 794px; /* A4 width in pixels */ } +.pdf-canvas[data-page-size="A3"] { max-width: 1123px; /* A3 width in pixels */ } +.pdf-canvas .enhanced-editor-content { + width:100%; + min-height: inherit; + margin:0; + padding:24px; + box-sizing:border-box; + position: relative; +} + +/* Custom scrollbar styling for better UX */ +.pdf-viewport::-webkit-scrollbar { + width: 8px; + height: 8px; +} +.pdf-viewport::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} +.pdf-viewport::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} +.pdf-viewport::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* PDF Page Break Styles for Viewport - Separate Pages Like PDF */ +.pdf-canvas .preview-page { + width: 100%; + background: white; + border: 1px solid #ddd; + margin: 0 auto 30px auto; /* Center each page with spacing */ + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + position: relative; + page-break-after: always; + display: block; + text-align: left; /* Reset text alignment for page content */ +} + +.pdf-canvas .preview-page[data-page-size="A4"] { + width: 210mm; + min-height: 297mm; + max-width: 794px; +} + +.pdf-canvas .preview-page[data-page-size="A3"] { + width: 297mm; + min-height: 420mm; + max-width: 1123px; +} + +.pdf-canvas .preview-page:last-child { + margin-bottom: 0; + page-break-after: avoid; +} + +/* Page break indicators */ +.pdf-canvas .preview-page::after { + content: "Page " attr(data-page-number); + position: absolute; + bottom: -25px; + left: 50%; + transform: translateX(-50%); + font-size: 12px; + color: #666; + background: #f8f9fa; + padding: 4px 12px; + border-radius: 6px; + border: 1px solid #dee2e6; + font-weight: 500; +} + +.pdf-canvas .preview-page:last-child::after { + display: none; +} + +/* Page content styling - Match PDF output exactly */ +.pdf-canvas .preview-page .enhanced-editor-content { + width: 100%; + height: 100%; + margin: 0; + padding: 0; /* Full width - no margins */ + box-sizing: border-box; + background: white; + font-size: 12px; + line-height: 1.3; + color: #000; +} + +/* Optimize content flow to match PDF */ +.pdf-canvas .preview-page .enhanced-editor-content h1, +.pdf-canvas .preview-page .enhanced-editor-content h2, +.pdf-canvas .preview-page .enhanced-editor-content h3, +.pdf-canvas .preview-page .enhanced-editor-content h4, +.pdf-canvas .preview-page .enhanced-editor-content h5, +.pdf-canvas .preview-page .enhanced-editor-content h6 { + margin: 0 0 8px 0; + padding: 0; + font-weight: bold; +} + +.pdf-canvas .preview-page .enhanced-editor-content p { + margin: 0 0 6px 0; + padding: 0; +} + +.pdf-canvas .preview-page .enhanced-editor-content img { + max-width: 100%; + height: auto; + display: block; + margin: 0 0 6px 0; + padding: 0; +} + +.pdf-canvas .preview-page .enhanced-editor-content table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + margin: 0 0 8px 0; + padding: 0; +} + +.pdf-canvas .preview-page .enhanced-editor-content td, +.pdf-canvas .preview-page .enhanced-editor-content th { + margin: 0; + padding: 3px; + border: none; + vertical-align: top; +} + +/* Gallery optimization - Match PDF exactly */ +.pdf-canvas .preview-page .enhanced-editor-content .gallery-grid, +.pdf-canvas .preview-page .enhanced-editor-content .image-gallery { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 0 0 8px 0; + width: 100%; +} + +.pdf-canvas .preview-page .enhanced-editor-content .gallery-grid > div, +.pdf-canvas .preview-page .enhanced-editor-content .gallery-grid > img, +.pdf-canvas .preview-page .enhanced-editor-content .image-gallery > div, +.pdf-canvas .preview-page .enhanced-editor-content .image-gallery > img { + flex: 1 1 calc(50% - 3px); + max-width: calc(50% - 3px); + margin: 0; + padding: 0; +} + +.pdf-canvas .preview-page .enhanced-editor-content .gallery-grid img, +.pdf-canvas .preview-page .enhanced-editor-content .image-gallery img { + width: 100%; + height: auto; + object-fit: cover; + display: block; +} + +/* Property details optimization */ +.pdf-canvas .preview-page .enhanced-editor-content .property-details, +.pdf-canvas .preview-page .enhanced-editor-content .property-info { + margin: 0 0 8px 0; + padding: 0; +} + +/* Gallery optimization for main editor content - Prevent breaking */ +.enhanced-editor-content .gallery-grid, +.enhanced-editor-content .image-gallery { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 0 0 8px 0; + width: 100%; +} + +.enhanced-editor-content .gallery-grid > div, +.enhanced-editor-content .gallery-grid > img, +.enhanced-editor-content .image-gallery > div, +.enhanced-editor-content .image-gallery > img { + flex: 1 1 calc(50% - 3px); + max-width: calc(50% - 3px); + margin: 0; + padding: 0; +} + +.enhanced-editor-content .gallery-grid img, +.enhanced-editor-content .image-gallery img { + width: 100%; + height: auto; + object-fit: cover; + display: block; +} + /* Left Toolbar - Original Layout */ .editor-toolbar.left { width: 300px; @@ -3973,13 +4462,11 @@ late particularay grid-template-columns: repeat(3, 1fr); gap: 4px; } - .text-alignment-section .toolbar-button { justify-content: center; text-align: center; min-height: 40px; } - /* Right Editor Area */ .editor-right { flex: 1; @@ -4768,7 +5255,6 @@ late particularay min-height: 400px; overflow: visible; } - .page-content[contenteditable="true"] { outline: none; cursor: text; @@ -4781,7 +5267,6 @@ late particularay border: 2px solid #6f42c1; border-radius: 6px; } - /* Page Navigation */ .page-navigation { display: none; @@ -5566,7 +6051,6 @@ late particularay border-radius: 15px; display: inline-block; } - /* Responsive design for smaller screens */ @media (max-width: 768px) { .export-pdf-section { @@ -6051,7 +6535,7 @@ late particularay padding: 10px 20px; box-sizing: border-box; margin-bottom: 0; - max-height: calc(100vh - 180px); + max-height: calc(100vh - 120px); background: transparent; } @@ -6070,11 +6554,12 @@ late particularay border: 1px solid #dee2e6; border-radius: 12px; padding: 15px; - height: calc(100vh - 120px); - max-height: calc(100vh - 120px); + height: calc(100vh - 200px); /* Decreased height for better scrolling */ + max-height: calc(100vh - 200px); overflow-y: auto; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - position: relative; + position: sticky; + top: 20px; /* Stop when touches header area */ z-index: 10; } @@ -6341,7 +6826,10 @@ late particularay justify-content: flex-start; } - +/* Z-Index controls */ +.zindex-controls { display: flex; flex-direction: column; gap: 6px; } +.zindex-controls .zindex-input { width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; } +.zindex-controls .zindex-buttons { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; } /* Property Insert Section */ .property-insert-section { @@ -6352,7 +6840,6 @@ late particularay border: 1px solid #e9ecef; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } - .property-insert-title { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro Text', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 1rem; @@ -6366,7 +6853,6 @@ late particularay background-clip: text; letter-spacing: 0.02em; } - .property-insert-grid { display: grid; grid-template-columns: repeat(2, 1fr); @@ -6536,15 +7022,15 @@ late particularay background: transparent !important; border: 1px solid #dee2e6; border-radius: 12px; - height: calc(85vh - 120px); - max-height: calc(85vh - 120px); + height: auto; + max-height: none; padding: 15px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'SF Pro Display', Roboto, 'Helvetica Neue', Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #333; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); - overflow-y: auto; + overflow-y: visible; position: relative; z-index: 10; scroll-behavior: smooth; @@ -6552,26 +7038,52 @@ late particularay -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; + white-space: pre-wrap; } +/* Normalize block elements inside editor for better auto-format */ +.enhanced-editor-content p { margin: 0 0 8px 0; white-space: normal; } +.enhanced-editor-content ul, .enhanced-editor-content ol { margin: 0 0 8px 24px; padding-left: 18px; } +.enhanced-editor-content li { margin: 4px 0; } +.enhanced-editor-content div { display: block; } + +/* Prevent list items from breaking across pages in PDF */ +.enhanced-editor-content ul, .enhanced-editor-content ol { break-inside: avoid; page-break-inside: avoid; } +.enhanced-editor-content li { break-inside: avoid; page-break-inside: avoid; } + .enhanced-editor-content:focus { outline: none; } /* Image Click Detection Enhancements */ .enhanced-editor-content img { - cursor: pointer; - transition: all 0.3s ease; + cursor: grab; position: relative; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; } .enhanced-editor-content img:hover { opacity: 0.9; - transform: scale(1.02); box-shadow: 0 4px 15px rgba(79, 70, 229, 0.2); border-radius: 4px; } +.enhanced-editor-content img:active { + cursor: grabbing; + opacity: 0.8; +} + +/* Smooth dragging styles */ +.enhanced-editor-content img.dragging { + transition: none !important; + z-index: 1000; + opacity: 0.8; + cursor: grabbing; +} + /* Enhanced hover effects for draggable image containers */ .enhanced-editor-content .draggable-element:has(img):hover { outline: 2px dashed #4f46e5; @@ -7151,7 +7663,6 @@ late particularay grid-template-columns: repeat(3, 1fr); } } - /* Export PDF Button - Positioned at Template Header with Animation */ .export-pdf-section { position: absolute; @@ -7160,7 +7671,6 @@ late particularay z-index: 1000; animation: pulse-grow 2s ease-in-out infinite; } - .export-pdf-btn { background: #007bff; /* Blue theme */ color: white; @@ -7520,6 +8030,118 @@ button, .btn, .toolbar-button, .export-pdf-btn { margin-bottom: 0.3rem !important; } + +/* Step Progress Stepper */ +.step-stepper-container { + padding: 1rem 0; + margin: 1.5rem 0 0.5rem; +} + +.step-stepper { + display: flex; + align-items: center; + justify-content: center; + max-width: 300px; + margin: 1.25rem auto 0; /* add space from top, not container */ +} + +.step-item { + display: flex; + align-items: center; + cursor: pointer; + transition: all 0.3s ease; +} + +.step-item:hover .step-circle { + transform: scale(1.1); +} + +.step-circle { + width: 30px; + height: 30px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + border: 2px solid #d1d5db; + background: #f9fafb; + color: #6b7280; + font-weight: 600; + font-size: 0.9rem; +} + +.step-circle.active, +.step-circle.active-circle, +.step-circle.step-nav-item.active { + background: #1e88e5 !important; + border-color: #1e88e5 !important; + color: #ffffff !important; +} + +.step-circle.active .step-number, +.step-circle.active-circle .step-number, +.step-circle.step-nav-item.active .step-number { + color: #ffffff !important; +} + +/* Ensure the header stepper wins over any generic .step-nav-item styles */ +.step-stepper .step-circle.step-nav-item.active, +.step-stepper .step-circle.active-circle, +.step-stepper .step-circle.active { + background: #1e88e5 !important; + border-color: #1e88e5 !important; + color: #ffffff !important; +} + +.step-stepper .step-circle.step-nav-item.active .step-number, +.step-stepper .step-circle.active-circle .step-number { + color: #ffffff !important; +} + +.step-number { + font-size: 0.8rem; + font-weight: 700; +} + +.step-connector { + height: 2px; + background: #d1d5db; + width: 40px; + margin: 0 10px; +} + +/* Responsive stepper */ +@media (max-width: 768px) { + .step-stepper-container { + padding: 1rem; + margin: 0.5rem 0; + } + + .step-stepper { + flex-direction: column; + gap: 1rem; + } + + .step-connector { + width: 2px; + height: 30px; + margin: 0.5rem 0; + top: 0; + } + + .step-label { + font-size: 0.8rem; + } + + .step-circle { + width: 40px; + height: 40px; + font-size: 1rem; + } +} + + /* Draggable image container styles */ .draggable-image-container { position: absolute; @@ -7535,6 +8157,7 @@ button, .btn, .toolbar-button, .export-pdf-btn { transition: all 0.2s ease; transform: translate3d(0, 0, 0); } +.draggable-image-container img { display: block; } .draggable-image-container:hover { border-color: #764ba2; @@ -7543,6 +8166,11 @@ button, .btn, .toolbar-button, .export-pdf-btn { box-shadow: 0 4px 15px rgba(102, 126, 234, 0.2); } +/* Minimal frame variant to avoid visual/positional shifts when wrapping existing images */ +.draggable-image-container.no-frame { border: none; background: transparent; padding: 0; box-shadow: none; } +.draggable-image-container.no-frame:hover { border: none; background: transparent; box-shadow: none; transform: none; } +.draggable-image-container.no-frame.dragging { border: none; background: transparent; box-shadow: none; transform: none; } + .draggable-image-container.dragging { border-color: #764ba2; background: rgba(102, 126, 234, 0.2); @@ -7836,7 +8464,6 @@ button, .btn, .toolbar-button, .export-pdf-btn { box-shadow: 0 6px 20px rgba(102,126,234,0.15); padding: 30px 25px; } - .sample-preview { display: flex; flex-direction: column; @@ -8635,7 +9262,6 @@ button, .btn, .toolbar-button, .export-pdf-btn { margin-bottom: 5px; opacity: 0.9; } - .preview-luxury-subtitle { font-size: 12px; opacity: 0.8; @@ -9114,10 +9740,9 @@ img[draggable="true"] { } /* Bullet and numbering styles */ -ul, ol { - list-style-type: none; - padding-left: 0; -} +/* Allow bullets and numbers by default inside content */ +.enhanced-editor-content ul { list-style-type: disc; padding-left: 22px; } +.enhanced-editor-content ol { list-style-type: decimal; padding-left: 22px; } ul li, ol li { margin-left: 20px; @@ -9423,7 +10048,6 @@ ol li:before { background: #dc2626; transform: scale(1.1); } - /* Enhanced Draggable Table Styles */ .draggable-table-container { position: absolute; @@ -9438,7 +10062,6 @@ ol li:before { border-radius: 8px; overflow: hidden; } - .draggable-table-container.selected { border-color: #C0A062; /* Gold accent theme */ box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2); @@ -9821,3 +10444,1335 @@ ol li:before { max-width: 250px; } } + +/* Grand Oak Villa Grid Preview Styles */ +.grand-oak-preview { + height: 200%; /* Double height */ + display: flex; + flex-direction: column; +} + +.preview-container { + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; +} + +.preview-cover { + flex: 1; + position: relative; + border-radius: 8px; + overflow: hidden; + min-height: 400px; /* Double height */ + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.preview-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 100%); +} + +.preview-header { + position: relative; + padding: 16px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 2; +} + +.preview-logo { + font-family: 'Playfair Display', serif; + font-size: 1rem; + font-weight: 700; + letter-spacing: 1px; + border: 2px solid #C0A062; + padding: 6px 12px; + color: white; + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.3); +} + +.preview-status { + background-color: #C0A062; + color: #000000; + padding: 6px 12px; + font-weight: 700; + font-size: 0.8rem; + text-transform: uppercase; + border-radius: 4px; + letter-spacing: 1px; +} + +.preview-content { + position: relative; + padding: 16px; + z-index: 2; + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; +} + +.preview-title { + font-family: 'Playfair Display', serif; + font-size: 1.8rem; + font-weight: 700; + line-height: 1.1; + margin: 0 0 8px 0; + color: #C0A062; /* Golden color for title */ + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8); +} + +.preview-address { + font-size: 0.9rem; + font-weight: 500; + display: flex; + align-items: center; + gap: 8px; + color: white; + margin: 0; +} + +.preview-address i { + color: #C0A062; + font-size: 0.8rem; +} + +.preview-footer { + position: relative; + background-color: rgba(0, 0, 0, 0.9); + padding: 12px 16px; + z-index: 2; +} + +.preview-features { + display: flex; + justify-content: space-between; + gap: 12px; +} + +.preview-feature { + font-size: 0.8rem; + color: white; + font-weight: 600; + text-align: center; + flex: 1; + background-color: rgba(192, 160, 98, 0.1); + padding: 6px 8px; + border-radius: 4px; + border: 1px solid rgba(192, 160, 98, 0.3); +} + +.preview-content-page { + flex: 1; + background-color: #000000; + border-radius: 8px; + padding: 16px; + display: flex; + flex-direction: column; + min-height: 200px; /* Increased height for double content */ + border: 2px solid #C0A062; +} + +.preview-content-header { + margin-bottom: 12px; +} + +.preview-page-title { + font-family: 'Playfair Display', serif; + font-size: 1.2rem; + color: white; + margin: 0; + font-weight: 600; +} + +.preview-content-body { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 12px; +} + +.preview-section { + flex: 1; +} + +.preview-section-title { + font-size: 0.8rem; + color: #C0A062; + margin: 0 0 6px 0; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; +} + +.preview-text { + font-size: 0.75rem; + line-height: 1.5; + color: white; + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.preview-amenities { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.preview-amenity { + font-size: 0.7rem; + color: white; + background-color: rgba(192, 160, 98, 0.2); + padding: 4px 8px; + border-radius: 4px; + border: 1px solid #C0A062; + font-weight: 500; +} + +/* Responsive adjustments for grid preview */ +@media (max-width: 768px) { + .preview-title { + font-size: 1.5rem; + } + + .preview-features { + flex-direction: column; + gap: 6px; + } + + .preview-feature { + font-size: 0.75rem; + } + + .preview-content-page { + min-height: 120px; + } +} + +/* Serenity House Grid Preview Styles */ +.serenity-preview { + height: 100%; + display: flex; + flex-direction: column; +} + +.serenity-preview-container { + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; +} + +.serenity-preview-cover { + flex: 1; + position: relative; + border-radius: 8px; + overflow: hidden; + min-height: 300px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.serenity-preview-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.1) 100%); +} + +.serenity-preview-header { + position: relative; + padding: 16px; + z-index: 2; +} + +.serenity-preview-collection { + font-size: 0.8rem; + color: #888888; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 1px; +} + +.serenity-preview-content { + position: relative; + padding: 16px; + z-index: 2; + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; +} + +.serenity-preview-title { + font-family: 'Playfair Display', serif; + font-size: 1.8rem; + font-weight: 700; + line-height: 1.1; + margin: 0 0 8px 0; + color: #2c3e50; +} + +.serenity-preview-address { + font-size: 0.9rem; + color: #7f8c8d; + margin: 0 0 4px 0; +} + +.serenity-preview-ref { + font-size: 0.8rem; + color: #95a5a6; + margin: 0; +} + +.serenity-preview-footer { + position: relative; + background-color: rgba(255, 255, 255, 0.95); + padding: 12px 16px; + z-index: 2; +} + +.serenity-preview-area { + font-size: 0.8rem; + color: #2c3e50; + font-weight: 600; + margin-bottom: 4px; +} + +.serenity-preview-desc { + font-size: 0.75rem; + color: #7f8c8d; + margin-bottom: 4px; +} + +.serenity-preview-price { + font-size: 0.8rem; + color: #2c3e50; + font-weight: 700; +} + +.serenity-preview-content-page { + flex: 1; + background-color: #ffffff; + border-radius: 8px; + padding: 16px; + display: flex; + flex-direction: column; + min-height: 150px; + border: 1px solid #e0e0e0; +} + +.serenity-preview-content-header { + margin-bottom: 12px; +} + +.serenity-preview-page-number { + font-size: 0.7rem; + color: #95a5a6; + font-weight: 500; +} + +.serenity-preview-page-title { + font-family: 'Playfair Display', serif; + font-size: 1.2rem; + color: #2c3e50; + margin: 4px 0; + font-weight: 600; +} + +.serenity-preview-page-subtitle { + font-size: 0.8rem; + color: #7f8c8d; + margin: 0; + line-height: 1.4; +} + +.serenity-preview-content-body { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 12px; +} + +.serenity-preview-section { + flex: 1; +} + +.serenity-preview-text { + font-size: 0.75rem; + line-height: 1.5; + color: #2c3e50; + margin: 0 0 8px 0; +} + +.serenity-preview-quote { + font-size: 0.75rem; + line-height: 1.4; + color: #7f8c8d; + font-style: italic; + margin: 0; +} + +.serenity-preview-image { + width: 100%; + height: 60px; + border-radius: 4px; + background-size: cover; + background-position: center; +} + +/* Vertice Grid Preview Styles */ +.vertice-preview { + height: 100%; + display: flex; + flex-direction: column; +} + +.vertice-preview-container { + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; +} + +.vertice-preview-cover { + flex: 1; + position: relative; + border-radius: 8px; + overflow: hidden; + min-height: 300px; + display: flex; + flex-direction: column; + justify-content: space-between; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.vertice-preview-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.1) 100%); +} + +.vertice-preview-content { + position: relative; + padding: 16px; + z-index: 2; + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; +} + +.vertice-preview-subtitle { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.8); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 8px; +} + +.vertice-preview-title { + font-family: 'Playfair Display', serif; + font-size: 1.8rem; + font-weight: 700; + line-height: 1.1; + margin: 0 0 8px 0; + color: white; +} + +.vertice-preview-address { + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.9); + margin: 0; +} + +.vertice-preview-footer { + position: relative; + background-color: rgba(0, 0, 0, 0.3); + padding: 12px 16px; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; +} + +.vertice-preview-price { + font-size: 0.8rem; + color: white; + font-weight: 600; +} + +.vertice-preview-ref { + font-size: 0.7rem; + color: rgba(255, 255, 255, 0.8); +} + +.vertice-preview-content-page { + flex: 1; + background-color: #f8f9fa; + border-radius: 8px; + padding: 16px; + display: flex; + flex-direction: column; + min-height: 150px; + border: 1px solid #e9ecef; +} + +.vertice-preview-content-header { + margin-bottom: 12px; +} + +.vertice-preview-page-title { + font-family: 'Playfair Display', serif; + font-size: 1.2rem; + color: #FFFFFF; + margin: 0 0 4px 0; + font-weight: 600; +} + +.vertice-preview-page-title span { + color: #667eea; +} + +.vertice-preview-page-subtitle { + font-size: 0.8rem; + color: #FFFFFF; + margin: 0; + line-height: 1.4; +} + +.vertice-preview-content-body { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 12px; +} + +.vertice-preview-section { + flex: 1; +} + +.vertice-preview-section-title { + font-size: 0.8rem; + color: #FFFFFF; + margin: 0 0 6px 0; + font-weight: 600; +} + +.vertice-preview-text { + font-size: 0.75rem; + line-height: 1.5; + color: #FFFFFF; + margin: 0; +} + +.vertice-preview-footer-bar { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; + padding-top: 8px; + border-top: 1px solid #e9ecef; +} + +.vertice-preview-property-name { + font-size: 0.7rem; + color: #667eea; + font-weight: 600; +} + +.vertice-preview-gallery { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-bottom: 12px; +} + +.vertice-preview-gallery-item { + background-color: #e9ecef; + padding: 8px; + border-radius: 4px; + text-align: center; +} + +.vertice-preview-gallery-item span { + font-size: 0.7rem; + color: #FFFFFF; + font-weight: 500; +} + +/* Enhanced Grid Template Styles */ +.preview-gallery-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + margin-top: 10px; +} + +.preview-gallery-item { + height: 60px; + border-radius: 4px; + background-size: cover; + background-position: center; + border: 1px solid #ddd; +} + +.preview-amenities { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 10px; +} + +.preview-amenity-item { + background: #f8f9fa; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + color: #495057; + border: 1px solid #e9ecef; +} + +/* Serenity Preview Gallery Grid */ +.serenity-preview-gallery-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + margin-top: 10px; +} + +.serenity-preview-gallery-item { + height: 60px; + border-radius: 4px; + background-size: cover; + background-position: center; + border: 1px solid #ddd; +} + +/* Enhanced Vertice Preview Gallery */ +.vertice-preview-gallery { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(3, 1fr); + gap: 8px; + margin-top: 10px; + height: 120px; +} + +.vertice-preview-gallery-item { + border-radius: 4px; + background-size: cover; + background-position: center; + border: 1px solid #ddd; + position: relative; + display: flex; + align-items: flex-end; + padding: 8px; +} + +.vertice-preview-gallery-item span { + background: rgba(0, 0, 0, 0.7); + color: white; + padding: 2px 6px; + border-radius: 3px; + font-size: 10px; + font-weight: 600; +} + +.vertice-preview-gallery-item:first-child { + grid-column: 1 / -1; + grid-row: 1 / 2; +} + +.vertice-preview-amenities { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 10px; +} + +.vertice-preview-amenity-item { + background: #f8f9fa; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + color: #FFFFFF; + border: 1px solid #e9ecef; +} + +/* Grand Oak Villa Preview Styles - Exact from preview-grand-oak.html */ +.grand-oak-preview .brochure-page { + width: 100%; + height: auto; + min-height: 200px; + background-color: #FFFFFF; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + overflow: hidden; + margin-bottom: 8px; +} + +.grand-oak-preview .cover-page { + position: relative; + background-image: url('https://images.unsplash.com/photo-1580587771525-78b9dba3b914?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + background-size: cover; + background-position: center; + color: #FFFFFF; + justify-content: space-between; + flex: 1; + min-height: 120px; +} + +.grand-oak-preview .cover-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(180deg, rgba(18, 18, 18, 0.8) 0%, rgba(18, 18, 18, 0.3) 100%); +} + +.grand-oak-preview .cover-header { + position: relative; + padding: 15px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.grand-oak-preview .logo { + font-family: 'Playfair Display', serif; + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 1px; + border: 2px solid #FFFFFF; + padding: 4px 8px; +} + +.grand-oak-preview .property-status { + background-color: #C0A062; + color: #121212; + padding: 4px 8px; + font-weight: 600; + font-size: 0.6rem; + text-transform: uppercase; +} + +.grand-oak-preview .cover-content { + position: relative; + padding: 15px; + max-width: 70%; +} + +.grand-oak-preview .cover-title { + font-family: 'Playfair Display', serif; + font-size: 1.5rem; + font-weight: 700; + line-height: 1.1; + margin: 0 0 5px 0; + color: #FFFFFF; +} + +.grand-oak-preview .cover-address { + font-size: 0.7rem; + font-weight: 400; + display: flex; + align-items: center; + gap: 4px; + color: #FFFFFF; +} + +.grand-oak-preview .cover-address i { + color: #C0A062; +} + +.grand-oak-preview .cover-footer { + position: relative; + background-color: rgba(18, 18, 18, 0.9); + padding: 10px 15px; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + text-align: center; +} + +.grand-oak-preview .feature-item { + border-right: 1px solid #2a2a2a; +} + +.grand-oak-preview .feature-item:last-child { + border-right: none; +} + +.grand-oak-preview .feature-item .value { + font-size: 0.8rem; + font-weight: 600; + color: #FFFFFF; + margin-bottom: 2px; +} + +.grand-oak-preview .feature-item .label { + font-size: 0.5rem; + color: #888888; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.grand-oak-preview .content-body { + background-color: #121212; + color: #D1D1D1; + flex-grow: 1; + padding: 15px; + display: flex; + flex-direction: column; +} + +.grand-oak-preview .page-header { + display: flex; + justify-content: space-between; + align-items: baseline; + padding-bottom: 8px; + margin-bottom: 10px; + border-bottom: 1px solid #2a2a2a; +} + +.grand-oak-preview .page-header .title { + font-family: 'Playfair Display', serif; + font-size: 1rem; + color: #FFFFFF; +} + +.grand-oak-preview .page-header .title span { + color: #C0A062; +} + +.grand-oak-preview .page-header .property-name { + font-size: 0.6rem; + font-weight: 600; + color: #888888; +} + +.grand-oak-preview .section-title { + font-weight: 600; + font-size: 0.7rem; + color: #FFFFFF; + margin: 0 0 8px 0; + text-transform: uppercase; + letter-spacing: 1px; + position: relative; + padding-bottom: 4px; +} + +.grand-oak-preview .section-title::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 20px; + height: 2px; + background-color: #C0A062; +} + +.grand-oak-preview .main-content { + flex-grow: 1; +} + +.grand-oak-preview .page-footer { + background-color: #0A0A0A; + padding: 8px 15px; + font-size: 0.6rem; + color: #888888; + border-top: 1px solid #2a2a2a; + display: flex; + justify-content: space-between; + align-items: center; +} + +.grand-oak-preview .page-footer strong { + color: #C0A062; + font-weight: 600; +} + +.grand-oak-preview .details-grid { + display: flex; + flex-direction: column; + gap: 10px; +} + +.grand-oak-preview .description p { + font-size: 0.6rem; + line-height: 1.4; + margin: 0 0 8px 0; + color: #D1D1D1; +} + +.grand-oak-preview .specs-and-amenities { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 15px; +} + +.grand-oak-preview .spec-list .item { + display: flex; + justify-content: space-between; + font-size: 0.6rem; + padding: 4px 0; + border-bottom: 1px solid #2a2a2a; +} + +.grand-oak-preview .spec-list .item:first-child { + padding-top: 0; +} + +.grand-oak-preview .spec-list .item .key { + font-weight: 600; + color: #888888; +} + +.grand-oak-preview .spec-list .item .value { + font-weight: 400; + color: #FFFFFF; +} + +.grand-oak-preview .amenities-list { + list-style: none; + padding: 0; + margin: 0; + columns: 1; + gap: 4px; +} + +.grand-oak-preview .amenities-list li { + font-size: 0.6rem; + margin-bottom: 4px; + display: flex; + align-items: center; +} + +.grand-oak-preview .amenities-list i { + color: #C0A062; + margin-right: 4px; + font-size: 0.6rem; +} + +/* Override any blue colors in Grand Oak Villa preview */ +.grand-oak-preview * { + color: #FFFFFF !important; +} + +.grand-oak-preview .cover-title, +.grand-oak-preview .cover-address, +.grand-oak-preview .feature-item .value, +.grand-oak-preview .feature-item .label, +.grand-oak-preview .page-header .title, +.grand-oak-preview .page-header .property-name, +.grand-oak-preview .section-title, +.grand-oak-preview .description p, +.grand-oak-preview .spec-list .item .key, +.grand-oak-preview .spec-list .item .value, +.grand-oak-preview .amenities-list li, +.grand-oak-preview .page-footer { + color: #FFFFFF !important; +} + +.grand-oak-preview .page-header .title span, +.grand-oak-preview .cover-address i, +.grand-oak-preview .amenities-list i, +.grand-oak-preview .page-footer strong { + color: #C0A062 !important; +} + +/* Serenity House Preview Styles - Exact from preview-serenity-house.html */ +.serenity-preview .brochure-page { + width: 100%; + height: auto; + min-height: 200px; + background-color: #FFFFFF; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + overflow: hidden; + margin-bottom: 8px; + position: relative; +} + +.serenity-preview .p1-container { + display: flex; + height: 100%; +} + +.serenity-preview .p1-image-side { + flex: 1.2; + background-image: url('https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + background-size: cover; + background-position: center; +} + +.serenity-preview .p1-content-side { + flex: 1; + padding: 25px 20px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.serenity-preview .p1-header .collection { + font-size: 0.4rem; + letter-spacing: 1px; + color: #777777; + text-transform: uppercase; +} + +.serenity-preview .p1-main-title { + font-family: 'Cormorant Garamond', serif; + font-size: 1.8rem; + font-weight: 600; + line-height: 1.1; + color: #333333; + margin: 8px 0; +} + +.serenity-preview .p1-address { + font-size: 0.5rem; + color: #777777; + border-left: 2px solid #D4C7B8; + padding-left: 8px; +} + +.serenity-preview .p1-ref-id { + font-size: 0.4rem; + color: #777777; + margin-top: 5px; + padding-left: 10px; +} + +.serenity-preview .p1-footer { + font-size: 0.4rem; + color: #777777; +} + +.serenity-preview .p1-footer strong { + color: #333333; +} + +.serenity-preview .p1-footer .area { + font-size: 0.5rem; + color: #333333; + font-weight: 700; + margin-bottom: 4px; +} + +.serenity-preview .page-layout { + padding: 25px; + display: flex; + flex-direction: column; + height: 100%; + box-sizing: border-box; +} + +.serenity-preview .page-number { + position: absolute; + top: 25px; right: 25px; + font-family: 'Cormorant Garamond', serif; + font-size: 0.5rem; + color: #777777; +} + +.serenity-preview .page-title-main { + font-family: 'Cormorant Garamond', serif; + font-size: 1.3rem; + font-weight: 600; + color: #333333; + margin: 0 0 5px 0; + line-height: 1; +} + +.serenity-preview .page-title-sub { + font-size: 0.5rem; + color: #777777; + margin-bottom: 15px; +} + +.serenity-preview .p2-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + flex-grow: 1; +} + +.serenity-preview .p2-image { + background-image: url('https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); + background-size: cover; + background-position: center; +} + +.serenity-preview .p2-text p { + font-size: 0.5rem; + line-height: 1.4; + color: #777777; +} + +.serenity-preview .p2-text p:first-of-type::first-letter { + font-family: 'Cormorant Garamond', serif; + font-size: 1.5rem; + float: left; + line-height: 1; + margin-right: 5px; + color: #D4C7B8; +} + +.serenity-preview .pull-quote { + border-left: 2px solid #D4C7B8; + padding-left: 10px; + margin: 10px 0; + font-family: 'Cormorant Garamond', serif; + font-size: 0.6rem; + font-style: italic; + color: #333333; +} + +.serenity-preview .contact-info { + margin-top: 15px; + padding: 12px; + background-color: #F5F5F5; + border-radius: 4px; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto; + gap: 4px 8px; + font-family: 'Lato', sans-serif; +} + +.serenity-preview .contact-name { + font-weight: 700; + color: #1e3a8a; + font-size: 0.5rem; + grid-column: 1; + grid-row: 1; +} + +.serenity-preview .contact-phone { + font-weight: 600; + color: #1e3a8a; + font-size: 0.5rem; + grid-column: 2; + grid-row: 1; + text-align: right; +} + +.serenity-preview .contact-title { + font-weight: 400; + color: #1e3a8a; + font-size: 0.4rem; + grid-column: 1; + grid-row: 2; +} + +.serenity-preview .contact-email { + font-weight: 400; + color: #1e3a8a; + font-size: 0.4rem; + grid-column: 2; + grid-row: 2; + text-align: right; + background-color: #dbeafe; + padding: 2px 4px; + border-radius: 2px; +} + +/* Vertice Preview Styles - Exact from preview-vertice.html */ +.vertice-preview .brochure-page { + width: 100%; + height: auto; + min-height: 200px; + background-color: #FFFFFF; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + overflow: hidden; + margin-bottom: 8px; + position: relative; +} + +.vertice-preview .cover-page { + background-image: url('https://plus.unsplash.com/premium_photo-1677474827617-6a7269f97574?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'); + background-size: cover; + background-position: center; + color: #FFFFFF; + justify-content: center; + align-items: center; + text-align: center; + position: relative; + flex: 1; +} + +.vertice-preview .cover-overlay { + position: absolute; top: 0; left: 0; width: 100%; height: 100%; + background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.3)); +} + +.vertice-preview .cover-content { + position: relative; z-index: 2; padding: 20px; +} + +.vertice-preview .cover-content .subtitle { + font-size: 0.5rem; + font-weight: 500; + letter-spacing: 2px; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.8); +} + +.vertice-preview .cover-content .main-title { + font-size: 2rem; + font-weight: 800; + line-height: 1.1; + margin: 4px 0 8px 0; + text-shadow: 0 2px 5px rgba(0,0,0,0.3); +} + +.vertice-preview .cover-content .address { + font-size: 0.5rem; + font-weight: 400; + border-top: 1px solid #0A6847; + display: inline-block; + padding-top: 8px; +} + +.vertice-preview .cover-footer { + position: absolute; + bottom: 15px; + left: 15px; right: 15px; + z-index: 2; + display: flex; + justify-content: space-between; + font-size: 0.4rem; + font-weight: 500; +} + +.vertice-preview .page-container { + padding: 25px; + height: 100%; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +.vertice-preview .page-header { + display: flex; + justify-content: space-between; + align-items: baseline; + border-bottom: 1px solid #DDDDDD; + margin-bottom: 8px; +} + +.vertice-preview .page-title { + font-size: 1rem; + font-weight: 800; + color: #FFFFFF; +} + +.vertice-preview .page-title span { + color: #0A6847; +} + +.vertice-preview .page-subtitle { + font-size: 0.5rem; + font-weight: 500; + color: #FFFFFF; +} + +.vertice-preview .page-footer-bar { + margin-top: auto; + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 8px; + border-top: 1px solid #DDDDDD; + font-size: 0.4rem; + font-weight: 500; + color: #FFFFFF; +} + +.vertice-preview .page-footer-bar .property-name { + color: #FFFFFF; +} + +.vertice-preview .vision-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + flex-grow: 1; + margin-bottom: 8px; +} + +.vertice-preview .vision-image { + background-image: url('https://images.unsplash.com/photo-1626704359446-0de90350b4e7?q=80&w=736&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'); + background-size: cover; + background-position: center; +} + +.vertice-preview .vision-text h3 { + font-size: 0.8rem; + font-weight: 700; + color: #FFFFFF; + margin-bottom: 8px; +} + +.vertice-preview .vision-text p { + font-size: 0.5rem; + line-height: 1.4; + color: #FFFFFF; + margin-bottom: 8px; +} + +.vertice-preview .gallery-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 75px 75px 75px; + gap: 8px; + flex-grow: 1; + padding-top: 3px; +} + +.vertice-preview .gallery-item { + background-size: cover; + background-position: center; + position: relative; + display: flex; + align-items: flex-end; + color: #FFFFFF; + padding: 6px; +} + +.vertice-preview .gallery-item::after { + content: ''; position: absolute; top:0; left: 0; width: 100%; height: 100%; + background: linear-gradient(to top, rgba(0,0,0,0.7), transparent); +} + +.vertice-preview .gallery-item span { font-weight: 600; z-index: 2; font-size: 0.4rem; } +.vertice-preview .g-item-1 { grid-column: 1 / 3; grid-row: 1 / 2; background-image: url('https://images.unsplash.com/photo-1616046229478-9901c5536a45?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } +.vertice-preview .g-item-2 { grid-column: 3 / 4; grid-row: 1 / 3; background-image: url('https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } +.vertice-preview .g-item-3 { grid-column: 1 / 2; grid-row: 2 / 4; background-image: url('https://images.unsplash.com/photo-1600121848594-d8644e57abab?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } +.vertice-preview .g-item-4 { grid-column: 2 / 3; grid-row: 2 / 3; background-image: url('https://images.unsplash.com/photo-1595526114035-0d45ed16433d?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } +.vertice-preview .g-item-5 { grid-column: 2 / 4; grid-row: 3 / 4; background-image: url('https://images.unsplash.com/photo-1512918728675-ed5a71a580a9?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html index b5ca48b..940f7ee 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html @@ -1,6 +1,14 @@ + + @@ -369,7 +429,7 @@ @@ -449,10 +509,6 @@ Status: {propertyData.status} -
- Reference Number: - {propertyData.referenceNumber} -
@@ -706,38 +762,15 @@ @@ -784,9 +817,9 @@
- - -
+
+ +
@@ -1062,14 +1095,36 @@
- -
- - {htmlContent} + + + +
+
+ + + +
+
@@ -1108,7 +1163,7 @@
@@ -1178,9 +1233,16 @@
-
+
-
+
πŸ“

Choose Image File

diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js index 7d35cb8..fcf7b4e 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js @@ -6,7 +6,7 @@ import getPropertyImages from '@salesforce/apex/PropertyDataController.getProper export default class PropertyTemplateSelector extends LightningElement { @track currentStep = 1; - @track htmlContent = ''; + htmlContent = ''; // Remove @track to prevent reactive updates // Lifecycle method - called when component is rendered renderedCallback() { @@ -35,7 +35,10 @@ export default class PropertyTemplateSelector extends LightningElement { @track pageCount = 0; @track progressMessage = ''; @track selectedPageSize = 'A4'; // Default page size + @track zoom = 1.0; // Step 3 viewport zoom @track isGeneratingPdf = false; // Loading state for PDF generation + @track previewPages = []; // Array of pages for viewport display + cachedTemplateContent = null; // Cache template content to prevent regeneration // Image review properties @track showImageReview = false; @@ -135,6 +138,7 @@ export default class PropertyTemplateSelector extends LightningElement { @track showDownloadModal = false; @track downloadInfo = {}; @track selectedElement = null; + // z-index controls removed per request // Undo functionality @track undoStack = []; @@ -146,6 +150,8 @@ export default class PropertyTemplateSelector extends LightningElement { return this.replacementActiveTab === 'property' ? 'source-tab active' : 'source-tab'; } + // z-index functions removed + get localUploadTabClass() { return this.replacementActiveTab === 'upload' ? 'source-tab active' : 'source-tab'; } @@ -179,17 +185,12 @@ export default class PropertyTemplateSelector extends LightningElement { } get filteredPropertyImages() { - console.log('filteredPropertyImages called'); - console.log('propertyImages:', this.propertyImages); - console.log('selectedImageCategory:', this.selectedImageCategory); if (!this.propertyImages || this.propertyImages.length === 0) { - console.log('No property images available'); return []; } if (this.selectedImageCategory === 'all') { - console.log('Returning all images:', this.propertyImages); return this.propertyImages; } @@ -198,7 +199,6 @@ export default class PropertyTemplateSelector extends LightningElement { return category === this.selectedImageCategory; }); - console.log('Filtered images:', filtered); return filtered; } @@ -224,7 +224,6 @@ export default class PropertyTemplateSelector extends LightningElement { get isInsertButtonDisabled() { const disabled = !this.selectedImageUrl || this.selectedImageUrl === ''; - console.log('isInsertButtonDisabled check:', disabled, 'selectedImageUrl:', this.selectedImageUrl, 'renderKey:', this.renderKey); return disabled; } @@ -261,8 +260,8 @@ export default class PropertyTemplateSelector extends LightningElement { return this.selectedTemplateId === 'modern-home-template'; } - get isAsgar1TemplateSelected() { - return this.selectedTemplateId === 'asgar-1-template'; + get isGrandOakVillaTemplateSelected() { + return this.selectedTemplateId === 'grand-oak-villa-template'; } get isSampleTemplateSelected() { @@ -303,17 +302,76 @@ export default class PropertyTemplateSelector extends LightningElement { return this.currentStep === 3 ? 'step-content active' : 'step-content'; } - // Step navigation classes + // Step navigation classes (for header stepper circles only) get step1NavClass() { - return this.currentStep === 1 ? 'step-nav-item active' : 'step-nav-item'; + return this.currentStep === 1 ? 'active-circle active' : ''; } get step2NavClass() { - return this.currentStep === 2 ? 'step-nav-item active' : 'step-nav-item'; + return this.currentStep === 2 ? 'active-circle active' : ''; } get step3NavClass() { - return this.currentStep === 3 ? 'step-nav-item active' : 'step-nav-item'; + return this.currentStep === 3 ? 'active-circle active' : ''; + } + + // Inline styles to hard-force blue fill for active circles + get step1NavStyle() { + return this.currentStep === 1 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; + } + get step2NavStyle() { + return this.currentStep === 2 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; + } + get step3NavStyle() { + return this.currentStep === 3 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; + } + + originalTemplateGridHTML = null; + originalStylesText = null; + + renderedCallback() { + if (this.currentStep === 1) { + const gridLive = this.template.querySelector('#all-templates'); + if (gridLive) { + // Always keep a clean snapshot from the DOM the first time we hit step 1 in a render pass + if (this.originalTemplateGridHTML === null || this.originalTemplateGridHTML.length < 50) { + this.originalTemplateGridHTML = gridLive.innerHTML; + } + } + } + if (this.originalTemplateGridHTML === null) { + const grid = this.template.querySelector('#all-templates'); + if (grid) { + this.originalTemplateGridHTML = grid.innerHTML; + } + } + if (this.originalStylesText === null) { + const styles = this.template.querySelectorAll('style'); + this.originalStylesText = Array.from(styles).map(s => s.textContent); + } + } + + resetStep1Grid() { + const grid = this.template.querySelector('#all-templates'); + if (!grid) return; + // Clear any selected states on cards + const cards = grid.querySelectorAll('.template-card'); + cards.forEach(card => { + card.classList.remove('selected'); + }); + // DON'T clear selectedTemplateId - preserve the selection + // this.selectedTemplateId = ''; + } + + restoreComponentStyles() { + if (!this.originalStylesText) return; + const styles = this.template.querySelectorAll('style'); + const snapshots = this.originalStylesText; + styles.forEach((styleEl, idx) => { + if (snapshots[idx] !== undefined && styleEl.textContent !== snapshots[idx]) { + styleEl.textContent = snapshots[idx]; + } + }); } get isNextButtonDisabled() { @@ -337,81 +395,62 @@ export default class PropertyTemplateSelector extends LightningElement { // Template selection handler handleTemplateSelect(event) { const templateId = event.currentTarget.dataset.templateId; - console.log('=== TEMPLATE SELECTION DEBUG ==='); - console.log('Selected template ID:', templateId); - console.log('Event target:', event.currentTarget); - console.log('Dataset:', event.currentTarget.dataset); - console.log('BEFORE - selectedTemplateId:', this.selectedTemplateId); + + // Clear cached content when selecting a new template + this.cachedTemplateContent = null; // Set the selected template switch (templateId) { case 'blank-template': this.selectedTemplateId = 'blank-template'; - console.log('Set selectedTemplateId to blank-template'); break; case 'everkind-template': this.selectedTemplateId = 'everkind-template'; - console.log('Set selectedTemplateId to everkind-template'); break; case 'shift-template': this.selectedTemplateId = 'shift-template'; - console.log('Set selectedTemplateId to shift-template'); break; case 'saintbarts-template': this.selectedTemplateId = 'saintbarts-template'; - console.log('Set selectedTemplateId to saintbarts-template'); break; case 'learnoy-template': this.selectedTemplateId = 'learnoy-template'; - console.log('Set selectedTemplateId to learnoy-template'); break; case 'leafamp-template': this.selectedTemplateId = 'leafamp-template'; - console.log('Set selectedTemplateId to leafamp-template'); break; case 'coreshift-template': this.selectedTemplateId = 'coreshift-template'; - console.log('Set selectedTemplateId to coreshift-template'); break; case 'modern-home-template': this.selectedTemplateId = 'modern-home-template'; - console.log('Set selectedTemplateId to modern-home-template'); break; - case 'asgar-1-template': - this.selectedTemplateId = 'asgar-1-template'; - console.log('Set selectedTemplateId to asgar-1-template'); + case 'grand-oak-villa-template': + this.selectedTemplateId = 'grand-oak-villa-template'; break; case 'serenity-house-template': this.selectedTemplateId = 'serenity-house-template'; - console.log('Set selectedTemplateId to serenity-house-template'); break; case 'sample-template': this.selectedTemplateId = 'sample-template'; - console.log('Set selectedTemplateId to sample-template'); break; case 'luxury-mansion-template': this.selectedTemplateId = 'luxury-mansion-template'; - console.log('Set selectedTemplateId to luxury-mansion-template'); break; default: - console.log('No matching case found for template ID:', templateId); break; } - console.log('AFTER switch - selectedTemplateId:', this.selectedTemplateId); - console.log('Final state - selectedTemplateId:', this.selectedTemplateId); - console.log('Final state - isModernHomeTemplateSelected:', this.isModernHomeTemplateSelected); - console.log('Final state - isAsgar1TemplateSelected:', this.isAsgar1TemplateSelected); - console.log('Final state - isSampleTemplateSelected:', this.isSampleTemplateSelected); - console.log('Final state - isLuxuryMansionTemplateSelected:', this.isLuxuryMansionTemplateSelected); - console.log('=== END TEMPLATE SELECTION DEBUG ==='); - // Force a re-render to ensure the UI updates + // Visually mark the selected template card with a black border + try { this.template.querySelectorAll('.template-card').forEach(card => { card.classList.remove('selected'); }); - if (event.currentTarget) { + if (event.currentTarget && event.currentTarget.classList) { event.currentTarget.classList.add('selected'); + } + } catch (e) { } } @@ -422,11 +461,116 @@ export default class PropertyTemplateSelector extends LightningElement { // Page size change handler handlePageSizeChange(event) { const newPageSize = event.target.value; - console.log('Page size changed to:', newPageSize); this.selectedPageSize = newPageSize; // Update the preview frame with new dimensions this.updatePreviewFrameSize(newPageSize); + + // Re-fit to width when page size changes + setTimeout(() => this.fitToWidth(), 0); + } + + // ===== Step 3 viewport zoom controls ===== + get zoomPercent() { + try { return `${Math.round((this.zoom || 1) * 100)}%`; } catch (e) { return '100%'; } + } + + get pdfCanvasStyle() { + const scale = this.zoom || 1; + return `transform: scale(${scale}) !important;`; + } + + zoomIn() { this.zoom = Math.min((this.zoom || 1) + 0.1, 3); } + zoomOut() { this.zoom = Math.max((this.zoom || 1) - 0.1, 0.3); } + resetZoom() { this.zoom = 1; } + + // Handler methods for HTML onclick events + handleZoomIn() { this.zoomIn(); } + handleZoomOut() { this.zoomOut(); } + handleZoom100() { this.resetZoom(); } + handleFitWidth() { this.fitToWidth(); } + handleFitPage() { this.fitToPage(); } + + // Page management methods + addNewPage() { + const newPage = { + id: `page-${Date.now()}`, + content: '

New page content...

' + }; + this.previewPages = [...this.previewPages, newPage]; + this.updatePageCount(); + } + + updatePageCount() { + const pageInfo = this.template?.querySelector('.page-info'); + if (pageInfo) { + const pageCount = this.previewPages.length || 1; + pageInfo.innerHTML = `${this.selectedPageSize} - ${pageCount} page${pageCount > 1 ? 's' : ''}`; + } + } + + // Split content into pages based on page breaks + splitContentIntoPages(content) { + if (!content) return []; + + // Look for page break markers or split by content length + const pageBreakMarkers = ['
', '
']; + let pages = []; + + // Check if content has explicit page breaks + let hasPageBreaks = false; + pageBreakMarkers.forEach(marker => { + if (content.includes(marker)) { + hasPageBreaks = true; + } + }); + + if (hasPageBreaks) { + // Split by page breaks + const pageContent = content.split(/]*page-break[^>]*><\/div>/i); + pages = pageContent.map((pageContent, index) => ({ + id: `page-${index + 1}`, + content: pageContent.trim() || '

Empty page

' + })); + } else { + // Single page for now - can be enhanced to auto-split based on content length + pages = [{ + id: 'page-1', + content: content + }]; + } + + return pages; + } + + // Update preview pages when content changes + updatePreviewPages() { + if (this.htmlContent) { + this.previewPages = this.splitContentIntoPages(this.htmlContent); + } else { + this.previewPages = []; + } + this.updatePageCount(); + } + + fitToWidth() { + const container = this.template?.querySelector('.pdf-viewport'); + if (!container) { return; } + const baseWidth = this.selectedPageSize === 'A3' ? 1123 : 794; + const available = Math.max((container.clientWidth || baseWidth) - 32, 100); + this.zoom = Math.max(Math.min(available / baseWidth, 4), 0.3); + } + + fitToPage() { + const container = this.template?.querySelector('.pdf-viewport'); + if (!container) { return; } + const baseWidth = this.selectedPageSize === 'A3' ? 1123 : 794; + const baseHeight = this.selectedPageSize === 'A3' ? 1587 : 1123; + const availableW = Math.max((container.clientWidth || baseWidth) - 32, 100); + const availableH = Math.max((container.clientHeight || baseHeight) - 32, 100); + const scaleW = availableW / baseWidth; + const scaleH = availableH / baseHeight; + this.zoom = Math.max(Math.min(Math.min(scaleW, scaleH), 4), 0.3); } // Update preview frame size based on selected page size @@ -436,7 +580,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Update the data attribute for the CSS content previewFrame.setAttribute('data-page-size', pageSize); - console.log('Preview frame updated for page size:', pageSize); // Re-render content with new page size this.renderContentInPages(pageSize); @@ -470,7 +613,6 @@ export default class PropertyTemplateSelector extends LightningElement { previewFrame.appendChild(pageContainer); }); - console.log(`Rendered ${pages.length} pages for ${pageSize} size`); } // Split HTML content into pages based on page size @@ -575,7 +717,6 @@ export default class PropertyTemplateSelector extends LightningElement { const pagesNeeded = Math.ceil(contentHeightMm / pageHeight); this.pageCount = Math.max(1, Math.min(pagesNeeded, 20)); // Limit to 1-20 pages - console.log(`Estimated ${this.pageCount} ${pageSize} pages based on content height`); } } @@ -588,6 +729,9 @@ export default class PropertyTemplateSelector extends LightningElement { // If moving to step 3, automatically load the template if (this.currentStep === 3) { this.loadTemplateInStep3(); + requestAnimationFrame(() => { + this.updatePreviewFrameSize(this.selectedPageSize || 'A4'); + }); } this.scrollToTop(); } @@ -598,18 +742,50 @@ export default class PropertyTemplateSelector extends LightningElement { this.currentStep--; // Reset click tracking when changing steps this.resetImageClickTracking(); + if (this.currentStep === 1) { + this.resetStep1Grid(); + } this.scrollToTop(); } } + goToStep(event) { const step = parseInt(event.currentTarget.dataset.step); this.currentStep = step; // Reset click tracking when changing steps this.resetImageClickTracking(); - // If going directly to step 3, load the template - if (this.currentStep === 3) { + if (this.currentStep === 1) { + this.resetStep1Grid(); + // Also reconstruct grid HTML from original snapshot if available to fully reset content + if (this.originalTemplateGridHTML && this.originalTemplateGridHTML.length > 50) { + const grid = this.template.querySelector('#all-templates'); + if (grid) { + grid.innerHTML = this.originalTemplateGridHTML; + } + } + // Restore any styles that could have been mutated during step 3 + this.restoreComponentStyles(); + // Rebind click handlers for fresh grid without page reload + requestAnimationFrame(() => { + const cards = this.template.querySelectorAll('#all-templates .template-card'); + cards.forEach(card => { + card.onclick = this.handleTemplateSelect.bind(this); + // Restore selected state if this card matches the selected template + if (this.selectedTemplateId && card.dataset.templateId === this.selectedTemplateId) { + card.classList.add('selected'); + } + }); + }); + } + // If going directly to step 3, load the template only if not already loaded + if (this.currentStep === 3 && (!this.htmlContent || this.htmlContent.trim() === '')) { this.loadTemplateInStep3(); + requestAnimationFrame(() => { + this.updatePreviewFrameSize(this.selectedPageSize || 'A4'); + // Auto fit width for better initial experience + this.fitToWidth && this.fitToWidth(); + }); } this.scrollToTop(); } @@ -626,62 +802,41 @@ export default class PropertyTemplateSelector extends LightningElement { async loadTemplateInStep3() { if (this.selectedTemplateId && this.selectedPropertyId) { try { - console.log('=== LOADING TEMPLATE IN STEP 3 ==='); - console.log('Template ID:', this.selectedTemplateId); - console.log('Property ID:', this.selectedPropertyId); - console.log('Property data available:', !!this.propertyData); - console.log('Real property images available:', this.realPropertyImages?.length || 0); + // Use cached content if available to prevent regeneration + if (this.cachedTemplateContent && this.cachedTemplateContent.templateId === this.selectedTemplateId && this.cachedTemplateContent.propertyId === this.selectedPropertyId) { + this.htmlContent = this.cachedTemplateContent.html; + this.updatePreviewPages(); + this.updatePreviewFrameSize(this.selectedPageSize); + setTimeout(() => { + this.updatePageCountForSize(this.selectedPageSize); + this.updatePageCount(); + this.fitToWidth && this.fitToWidth(); + }, 100); + return; + } // Ensure property images are loaded before creating template if (this.realPropertyImages.length === 0) { - console.log('No property images loaded, loading them now...'); await this.loadPropertyImages(); } const templateHTML = this.createTemplateHTML(); - console.log('Template HTML length:', templateHTML.length); - console.log('Template HTML preview:', templateHTML.substring(0, 500) + '...'); // Replace any hardcoded background-image URLs with property images const processedTemplateHTML = this.replaceBackgroundImagesInHTML(templateHTML); - console.log('Processed template HTML length:', processedTemplateHTML.length); + + // Cache the template content + this.cachedTemplateContent = { + templateId: this.selectedTemplateId, + propertyId: this.selectedPropertyId, + html: processedTemplateHTML + }; // Set the HTML content for the template binding this.htmlContent = processedTemplateHTML; - // Also find the enhanced editor content and load the template - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - // Clear any existing content - editorContent.innerHTML = ''; - - // Create a temporary container to parse the HTML - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = processedTemplateHTML; - - // Append the parsed content to the editor - while (tempDiv.firstChild) { - editorContent.appendChild(tempDiv.firstChild); - } - - console.log('Template loaded successfully into enhanced editor'); - - // Update any remaining CSS background-image rules - setTimeout(() => { - this.updateCSSBackgroundImages(); - }, 200); - - // Make images draggable and resizable - setTimeout(() => { - this.makeImagesDraggableAndResizable(); - }, 300); - - // Save initial state for undo functionality - setTimeout(() => { - this.saveUndoState(); - }, 100); - - // Images are now displayed without edit icons for clean presentation + // Update preview pages with the new content + this.updatePreviewPages(); // Set initial page size class and data attribute this.updatePreviewFrameSize(this.selectedPageSize); @@ -689,13 +844,11 @@ export default class PropertyTemplateSelector extends LightningElement { // Update page count after template is loaded setTimeout(() => { this.updatePageCountForSize(this.selectedPageSize); + this.updatePageCount(); // Update page count display + // After content settles, fit viewport to width + this.fitToWidth && this.fitToWidth(); }, 100); - - } else { - console.error('Editor content not found'); - } } catch (error) { - console.error('Error loading template in step 3:', error); this.error = 'Error loading template: ' + error.message; // Show error message in preview frame @@ -718,13 +871,16 @@ export default class PropertyTemplateSelector extends LightningElement { previewFrame.innerHTML = '

No Property Selected

Please go back to step 2 and select a property.

'; } } - console.log('Template or property not selected, cannot load template'); } } // Property selection handler handlePropertySelection(event) { this.selectedPropertyId = event.target.value; + + // Clear cached content when selecting a new property + this.cachedTemplateContent = null; + if (this.selectedPropertyId) { this.loadPropertyData(); // Auto-scroll to property preview section after a short delay to ensure data is loaded @@ -773,7 +929,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Handle direct field access return property[fieldPath] || defaultValue; } catch (error) { - console.warn(`Error accessing field ${fieldPath}:`, error); return defaultValue; } } @@ -782,8 +937,6 @@ export default class PropertyTemplateSelector extends LightningElement { async loadPropertyData() { const selectedProperty = this.properties.find(prop => prop.Id === this.selectedPropertyId); if (selectedProperty) { - console.log('=== LOADING PROPERTY DATA ==='); - console.log('Selected property:', selectedProperty); // Set the selectedProperty for use in PDF generation this.selectedProperty = selectedProperty; @@ -793,7 +946,7 @@ export default class PropertyTemplateSelector extends LightningElement { this.propertyData = { // Basic Information - propertyName: get('Name', 'Property Name'), + propertyName: (get('pcrm__Title_English__c') !== 'N/A' ? get('pcrm__Title_English__c') : (get('Title_English__c') !== 'N/A' ? get('Title_English__c') : get('Name', 'Property Name'))), propertyType: get('pcrm__Property_Type__c', 'Property Type'), status: get('pcrm__Status__c', 'Available'), referenceNumber: get('Name', 'REF-001'), @@ -894,8 +1047,6 @@ export default class PropertyTemplateSelector extends LightningElement { furnishing: get('pcrm__Furnished__c') }; - console.log('=== PROPERTY DATA MAPPED ==='); - console.log('Mapped property data:', this.propertyData); // Load property images await this.loadPropertyImages(); @@ -911,11 +1062,8 @@ export default class PropertyTemplateSelector extends LightningElement { } try { - console.log('=== LOADING PROPERTY IMAGES ==='); - console.log('Property ID:', this.selectedPropertyId); const images = await getPropertyImages({ propertyId: this.selectedPropertyId }); - console.log('Loaded images:', images); // Transform the data to match expected format this.realPropertyImages = images.map(img => ({ @@ -926,12 +1074,10 @@ export default class PropertyTemplateSelector extends LightningElement { url: img.url })); - console.log('Real property images loaded:', this.realPropertyImages); if (this.realPropertyImages && this.realPropertyImages.length > 0) { // Find the first category that has images const firstAvailableCategory = this.findFirstAvailableCategory(); - console.log('Loading images, auto-selecting first available category:', firstAvailableCategory); this.filterImagesByCategory(firstAvailableCategory); this.selectedCategory = firstAvailableCategory; this.initialCategorySelected = true; @@ -949,7 +1095,6 @@ export default class PropertyTemplateSelector extends LightningElement { } } catch (error) { - console.error('Error loading property images:', error); this.realPropertyImages = []; } } @@ -976,12 +1121,10 @@ export default class PropertyTemplateSelector extends LightningElement { let htmlContent = ''; if (editorFrame && editorFrame.innerHTML) { - htmlContent = this.cleanHtmlForPdf(editorFrame.innerHTML); - console.log('Using HTML content from editor, length:', htmlContent.length); + htmlContent = editorFrame.innerHTML; } else { // Fallback: generate template HTML if editor is empty htmlContent = this.createCompleteTemplateHTML(); - console.log('Generated fallback HTML content, length:', htmlContent.length); } // Generate PDF using the template HTML @@ -994,14 +1137,12 @@ export default class PropertyTemplateSelector extends LightningElement { if (result.success) { // Handle successful PDF generation - console.log('PDF generated successfully:', result.pdfUrl); this.showSuccess('PDF generated successfully!'); // You can add logic here to display the PDF or provide download link } else { this.error = result.message || result.error || 'Failed to generate PDF.'; } } catch (error) { - console.error('Error in generateTemplateContent:', error); this.error = 'Error generating PDF: ' + (error.body?.message || error.message || 'Unknown error'); } finally { this.isLoading = false; @@ -1026,12 +1167,9 @@ export default class PropertyTemplateSelector extends LightningElement { generatePdfButton.style.boxShadow = ''; }, 2000); - console.log('Scrolled to Generate PDF button successfully'); } else { - console.warn('Generate PDF button not found'); } } catch (error) { - console.error('Error scrolling to Generate PDF button:', error); } } @@ -1043,7 +1181,6 @@ export default class PropertyTemplateSelector extends LightningElement { } try { - console.log('=== GENERATING PDF VIA EXTERNAL API ==='); // Set loading state this.isGeneratingPdf = true; @@ -1065,8 +1202,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Success message is handled in generatePdfViaExternalApi } catch (error) { - console.error('=== PDF GENERATION ERROR ==='); - console.error('Error generating PDF:', error); // Provide more user-friendly error messages let errorMessage = 'PDF generation failed. '; @@ -1089,11 +1224,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Clean HTML content for PDF generation by removing editor controls cleanHtmlForPdf(htmlContent) { - if (!htmlContent) return ''; - - // Create a temporary div to parse and clean the HTML - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = htmlContent; + return htmlContent; // Remove only specific editor control elements, NOT the content containers const elementsToRemove = [ @@ -1187,6 +1318,143 @@ export default class PropertyTemplateSelector extends LightningElement { } }); + // Ensure list styling is preserved in output + const lists = tempDiv.querySelectorAll('ul, ol'); + lists.forEach(list => { + if (list.tagName.toLowerCase() === 'ul') { + list.style.listStyleType = 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + } else { + list.style.listStyleType = 'decimal'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + } + }); + + // Handle
-separated bullet/number lines inside a single block + const brBlocks = tempDiv.querySelectorAll('p, div'); + brBlocks.forEach(block => { + if (block.closest('ul,ol')) return; + const html = block.innerHTML || ''; + if (!/br\s*\/?/i.test(html)) return; + const parts = html.split(/(?:\s*)/i).map(s => s.trim()).filter(Boolean); + if (parts.length < 2) return; + const bulletMarker = /^\s*(?: \s*)*(\*|\-|β€’)\s+/i; + const numberMarker = /^\s*(?: \s*)*\d+[\.)]\s+/i; + const allBullets = parts.every(p => bulletMarker.test(p.replace(/<[^>]+>/g, ''))); + const allNumbers = parts.every(p => numberMarker.test(p.replace(/<[^>]+>/g, ''))); + if (!(allBullets || allNumbers)) return; + const list = document.createElement(allNumbers ? 'ol' : 'ul'); + list.style.listStyleType = allNumbers ? 'decimal' : 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + list.style.breakInside = 'avoid'; + list.style.pageBreakInside = 'avoid'; + parts.forEach(line => { + const li = document.createElement('li'); + li.innerHTML = line.replace(/^\s*(?: \s*)*(\*|\-|β€’|\d+[\.)])\s+/i, ''); + li.style.breakInside = 'avoid'; + li.style.pageBreakInside = 'avoid'; + list.appendChild(li); + }); + block.replaceWith(list); + }); + + // Handle inline asterisk-separated or dot-number-separated items within a single paragraph + const textBlocks = tempDiv.querySelectorAll('p, div'); + textBlocks.forEach(block => { + if (block.closest('ul,ol')) return; + const text = (block.textContent || '').trim(); + if (!text) return; + // Detect at least two bullet markers like " * item" or " β€’ item" + const bulletSeqMatches = text.match(/(?:^|\s)[*β€’\-]\s+\S+/g); + const numberSeqMatches = text.match(/(?:^|\s)\d+[\.)]\s+\S+/g); + const hasBulletRun = bulletSeqMatches && bulletSeqMatches.length >= 2; + const hasNumberRun = !hasBulletRun && numberSeqMatches && numberSeqMatches.length >= 2; + if (!(hasBulletRun || hasNumberRun)) return; + + // Split text into prefix + items + const firstMarkerIndex = text.search(hasBulletRun ? /(?:^|\s)[*β€’\-]\s+\S+/ : /(?:^|\s)\d+[\.)]\s+\S+/); + if (firstMarkerIndex < 0) return; + const prefix = text.slice(0, firstMarkerIndex).trim(); + const run = text.slice(firstMarkerIndex).trim(); + const items = run.split(hasBulletRun ? /\s[*β€’\-]\s+/g : /\s\d+[\.)]\s+/g).filter(Boolean); + if (items.length < 2) return; + + const frag = document.createDocumentFragment(); + if (prefix) { + const p = document.createElement('p'); + p.textContent = prefix; + frag.appendChild(p); + } + const list = document.createElement(hasNumberRun ? 'ol' : 'ul'); + list.style.listStyleType = hasNumberRun ? 'decimal' : 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + list.style.breakInside = 'avoid'; + list.style.pageBreakInside = 'avoid'; + items.forEach(item => { + const li = document.createElement('li'); + li.textContent = item.trim(); + li.style.breakInside = 'avoid'; + li.style.pageBreakInside = 'avoid'; + list.appendChild(li); + }); + frag.appendChild(list); + block.replaceWith(frag); + }); + + // Convert text marker lines ("* ", "1. ") to real UL/OL so saved HTML matches preview + const bulletMarker = /^\s*(?: \s*)*(\*|\-|β€’)\s+/i; + const numberMarker = /^\s*(?: \s*)*\d+[\.)]\s+/i; + const stripMarker = (html) => html.replace(/^\s*(?: \s*)*(\*|\-|β€’|\d+[\.)])\s+/i, ''); + + const processContainer = (container) => { + const children = Array.from(container.children); + for (let i = 0; i < children.length; i++) { + const el = children[i]; + // Recurse first + if (el.children && el.children.length) processContainer(el); + if (el.tagName && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div')) { + const text = (el.textContent || '').trim(); + const isBulletStart = bulletMarker.test(text); + const isNumberStart = numberMarker.test(text); + if (isBulletStart || isNumberStart) { + const list = document.createElement(isNumberStart ? 'ol' : 'ul'); + list.style.listStyleType = isNumberStart ? 'decimal' : 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + const startIndex = i; + // Collect consecutive items + while (i < children.length) { + const cand = children[i]; + if (!cand || !(cand.tagName && (cand.tagName.toLowerCase() === 'p' || cand.tagName.toLowerCase() === 'div'))) break; + const candText = (cand.textContent || '').trim(); + if ((isNumberStart && numberMarker.test(candText)) || (!isNumberStart && bulletMarker.test(candText))) { + const li = document.createElement('li'); + // Preserve inline formatting by using innerHTML and stripping marker + li.innerHTML = stripMarker(cand.innerHTML || candText); + list.appendChild(li); + i++; + } else { + break; + } + } + // Insert list and remove original items + const first = children[startIndex]; + first.parentNode.insertBefore(list, first); + for (let j = startIndex; j < i; j++) { + if (children[j] && children[j].parentNode) children[j].parentNode.removeChild(children[j]); + } + // Reset children and position after replacement + return processContainer(container); + } + } + } + }; + processContainer(tempDiv); + // Clean up any remaining editor-specific styles on all elements const allElements = tempDiv.querySelectorAll('*'); allElements.forEach(element => { @@ -1214,7 +1482,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Generate PDF via external API using Apex proxy async generatePdfViaExternalApi() { try { - console.log('Calling external PDF generation API via Apex proxy...'); // Show loading state this.isLoading = true; @@ -1229,33 +1496,26 @@ export default class PropertyTemplateSelector extends LightningElement { throw new Error('Editor content not found'); } - htmlContent = this.cleanHtmlForPdf(previewFrame.innerHTML); - console.log('Initial HTML content from preview frame:', htmlContent); - console.log('HTML content length:', htmlContent.length, 'characters'); + htmlContent = previewFrame.innerHTML; // Debug: Check if draggable elements are present const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlContent; const draggableElements = tempDiv.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); - console.log('Draggable elements found in cleaned HTML:', draggableElements.length); draggableElements.forEach((el, index) => { - console.log(`Draggable element ${index + 1}:`, el.className, el.tagName); }); // If preview frame is empty, generate the template HTML first if (!htmlContent || htmlContent.trim() === '' || htmlContent.length < 100) { - console.log('Preview frame is empty or has minimal content, generating template HTML...'); this.showProgress('Generating template content...'); // Generate the template HTML using the selected template and property if (this.selectedTemplateId && this.selectedPropertyId) { // Create a complete HTML template with property data htmlContent = this.createCompleteTemplateHTML(); - console.log('Generated template HTML length:', htmlContent.length, 'characters'); // Load it into the preview frame so user can see it previewFrame.innerHTML = htmlContent; - console.log('Template HTML loaded into preview frame'); } else { throw new Error('No template or property selected'); } @@ -1269,6 +1529,7 @@ export default class PropertyTemplateSelector extends LightningElement { Property Brochure - ${this.selectedPageSize} +
Property Exterior

${propertyName}

${location}

${price}

EXTERIOR IMAGE TEST: ${exteriorImage}

About Shift Property

Experience the future of living with Shift Property, where innovation meets comfort in perfect harmony.

Interior View

Modern Features

Innovation
Smart Design
Eco-Friendly
Connected Living
Kitchen View

Contact Information

Reference ID: ${referenceId}

Agent: ${data.agentName || "Innovation Specialist"}

Phone: ${data.agentPhone || "(555) 789-0123"}

${propertyGallery}
`; } @@ -2114,7 +2370,7 @@ export default class PropertyTemplateSelector extends LightningElement { const propertyName = data.Name || data.propertyName || "SAINT BARTS VILLA"; const location = data.Address__c || data.location || "Caribbean Paradise"; const price = data.Price__c || data.price || "Starting from $3,200,000"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "SB-2025-001"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const size = data.Square_Feet__c || data.size || "N/A"; @@ -2142,7 +2398,7 @@ export default class PropertyTemplateSelector extends LightningElement { const size = data.Square_Feet__c || data.size || "N/A"; const propertyType = data.Property_Type__c || data.propertyType || "N/A"; const description = data.Description_English__c || data.descriptionEnglish || "Property description not available."; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; // Get smart images - use direct method for exterior const exteriorImage = this.getExteriorImageUrl(); @@ -2163,7 +2419,7 @@ export default class PropertyTemplateSelector extends LightningElement { const propertyName = data.Name || data.propertyName || "LEAFAMP URBAN"; const location = data.Address__c || data.location || "City Living Experience"; const price = data.Price__c || data.price || "Starting from $1,200,000"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "LF-2025-001"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const size = data.Square_Feet__c || data.size || "N/A"; @@ -2177,8 +2433,6 @@ export default class PropertyTemplateSelector extends LightningElement { const mapsImage = this.getMapsImageUrl(); // Debug logging for maps image - console.log('=== LEAFAMP TEMPLATE MAPS DEBUG ==='); - console.log('Maps image URL:', mapsImage); // Generate property gallery for uncategorized images const propertyGallery = this.generatePropertyGalleryHTML(); @@ -2191,7 +2445,6 @@ export default class PropertyTemplateSelector extends LightningElement { } createModernHomeTemplate() { - console.log("=== CREATING REAL ESTATE MODERN HOME TEMPLATE ==="); const data = this.propertyData || {}; const propertyName = data.Name || data.propertyName || "Property Name"; @@ -2201,8 +2454,8 @@ export default class PropertyTemplateSelector extends LightningElement { const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const area = data.Square_Feet__c || data.area || "N/A"; - const description = data.Description_English__c || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const description = this.formatDescriptionForPDF(data.Description_English__c || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."); + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; // Contact information const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; @@ -2220,7 +2473,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Generate amenities from property data const amenitiesHTML = this.generateAmenitiesHTML(data); - return `Property Brochure

${propertyName}

${location}

${price}
${bedrooms} Beds ${bathrooms} Baths ${area} sq. ft.

Description

${data.descriptionEnglish || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."}

Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}

In-depth Details

A closer look at the property's features and specifications.

Specifications

Status: ${data.status || "Available"}
Type: ${propertyType}
Floor: ${data.Floor__c || data.floor || "N/A"}
Parking: ${data.Parking_Spaces__c || data.parking || "N/A"}
Year Built: ${data.Build_Year__c || data.yearBuilt || "N/A"}
Furnishing: ${data.Furnished__c || data.furnishing || "N/A"}
Maintenance Fee: ${data.Maintenance_Fee__c || data.maintenanceFee || "N/A"}
Service Charge: ${data.Service_Charge__c || data.serviceCharge || "N/A"}

Amenities & Features

${amenitiesHTML}
Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}

Location & Nearby

Landmarks: ${data.Landmarks__c || data.nearbyLandmarks || "N/A"}
Transportation: ${data.Transportation__c || data.transportation || "N/A"}
Schools: ${data.Schools__c || data.schools || "N/A"}
Hospitals: ${data.Hospitals__c || data.hospitals || "N/A"}
Shopping: ${data.Shopping_Centers__c || data.shoppingCenters || "N/A"}
Airport: ${data.Airport_Distance__c || data.airportDistance || "N/A"}
City: ${data.City__c || data.cityBayut || data.cityPropertyfinder || "N/A"}
Community: ${data.Community__c || data.communityBayut || "N/A"}
Sub Community: ${data.Sub_Community__c || data.subCommunityBayut || "N/A"}
Locality: ${data.Locality__c || data.localityBayut || "N/A"}
Sub Locality: ${data.Sub_Locality__c || data.subLocalityBayut || "N/A"}
Tower: ${data.Tower__c || data.towerBayut || "N/A"}
Beach Distance: ${data.Beach_Distance__c || data.beachDistance || "N/A"}
Metro Distance: ${data.Metro_Distance__c || data.metroDistance || "N/A"}
Country Club: ${data.Country_Club__c || data.countryClub || "N/A"}
Property Location Map

Additional Information

Pet Friendly: ${data.petFriendly || "Pet Friendly Status"}
Smoking: ${data.smokingAllowed || "Smoking Allowed"}
Available From: ${data.availableFrom || "Available From Date"}
Minimum Contract: ${data.minimumContract || "Minimum Contract Duration"}
Security Deposit: ${data.securityDeposit || "Security Deposit"}
Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}
`; + return `Property Brochure

${propertyName}

${location}

${price}
${bedrooms} Beds ${bathrooms} Baths ${area} sq. ft.

Description

${data.descriptionEnglish || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."}

Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}

In-depth Details

A closer look at the property's features and specifications.

Specifications

Status: ${data.status || "Available"}
Type: ${propertyType}
Floor: ${data.Floor__c || data.floor || "N/A"}
Parking: ${data.Parking_Spaces__c || data.parking || "N/A"}
Year Built: ${data.Build_Year__c || data.yearBuilt || "N/A"}
Furnishing: ${data.Furnished__c || data.furnishing || "N/A"}
Maintenance Fee: ${data.Maintenance_Fee__c || data.maintenanceFee || "N/A"}
Service Charge: ${data.Service_Charge__c || data.serviceCharge || "N/A"}

Amenities & Features

${amenitiesHTML}
Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}

Location & Nearby

Landmarks: ${data.Landmarks__c || data.nearbyLandmarks || "N/A"}
Transportation: ${data.Transportation__c || data.transportation || "N/A"}
Schools: ${data.Schools__c || data.schools || "N/A"}
Hospitals: ${data.Hospitals__c || data.hospitals || "N/A"}
Shopping: ${data.Shopping_Centers__c || data.shoppingCenters || "N/A"}
Airport: ${data.Airport_Distance__c || data.airportDistance || "N/A"}
City: ${data.City__c || data.cityBayut || data.cityPropertyfinder || "N/A"}
Community: ${data.Community__c || data.communityBayut || "N/A"}
Sub Community: ${data.Sub_Community__c || data.subCommunityBayut || "N/A"}
Locality: ${data.Locality__c || data.localityBayut || "N/A"}
Sub Locality: ${data.Sub_Locality__c || data.subLocalityBayut || "N/A"}
Tower: ${data.Tower__c || data.towerBayut || "N/A"}
Beach Distance: ${data.Beach_Distance__c || data.beachDistance || "N/A"}
Metro Distance: ${data.Metro_Distance__c || data.metroDistance || "N/A"}
Country Club: ${data.Country_Club__c || data.countryClub || "N/A"}
Property Location Map

Additional Information

Pet Friendly: ${data.petFriendly || "Pet Friendly Status"}
Smoking: ${data.smokingAllowed || "Smoking Allowed"}
Available From: ${data.availableFrom || "Available From Date"}
Minimum Contract: ${data.minimumContract || "Minimum Contract Duration"}
Security Deposit: ${data.securityDeposit || "Security Deposit"}
Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}
`; } createAsgar1Template() { @@ -2230,7 +2483,7 @@ export default class PropertyTemplateSelector extends LightningElement { const propertyName = data.Name || data.propertyName || "Property Name"; const location = data.Address__c || data.location || "Location"; const price = data.Price__c || data.price || "Price"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const area = data.Square_Feet__c || data.area || "N/A"; @@ -2316,7 +2569,130 @@ export default class PropertyTemplateSelector extends LightningElement { const minimumContract = data.Minimum_Contract__c || data.minimumContract || "N/A"; const securityDeposit = data.Security_Deposit__c || data.securityDeposit || "N/A"; - return `The Grand Oak Villa - Template Preview
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Parking: ${parking}
Floor: ${floor}
Furnishing: ${furnishing}

Amenities

  • Infinity Pool
  • Home Theater
  • Wine Cellar
  • Smart Home
  • Spa & Sauna
  • Landscaped Gardens
Agent: ${contactName} | ${contactPhone}
Owner: ${ownerName}
`; + return `Prestige Real Estate Brochure - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}

Amenities & Features

    ${amenitiesHTML}
Agent: ${contactName} | ${contactPhone} | ${contactEmail}
Owner: ${ownerName} | ${ownerPhone}
`; + } + + createGrandOakVillaTemplate() { + const data = this.propertyData || {}; + + // Enhanced property data extraction with better fallbacks + const propertyName = data.Name || data.propertyName || data.pcrm__Title_English__c || "The Grand Oak Villa"; + const location = data.Address__c || data.location || "123 Luxury Lane, Prestige City, PC 45678"; + const price = data.Sale_Price_Min__c || data.Rent_Price_Min__c || data.Price__c || data.price || "$4,500,000"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "5"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "6"; + const squareFeet = data.Square_Feet__c || data.squareFeet || data.area || "6,200"; + const status = (data.Status__c || data.status || "FOR SALE").toString(); + + // Enhanced property details + const propertyType = data.Property_Type__c || data.propertyType || "Villa"; + const yearBuilt = data.Build_Year__c || data.yearBuilt || "2020"; + const furnishing = data.Furnished__c || data.furnishing || "Fully Furnished"; + const parking = data.Parking_Spaces__c || data.parking || "2"; + const description = this.formatDescriptionForPDF(data.Description_English__c || data.descriptionEnglish || data.description || "An exquisite villa offering unparalleled luxury and sophistication in one of the most prestigious locations."); + const floor = data.Floor__c || data.floor || "Ground Floor"; + const maintenanceFee = data.Maintenance_Fee__c || data.maintenanceFee || "N/A"; + const serviceCharge = data.Service_Charge__c || data.serviceCharge || "N/A"; + + // Additional property details + const lotSize = data.Lot_Size__c || data.lotSize || "0.5 acres"; + const heating = data.Heating__c || data.heating || "Central Air"; + const cooling = data.Cooling__c || data.cooling || "Central Air"; + const roof = data.Roof__c || data.roof || "Tile"; + const exterior = data.Exterior__c || data.exterior || "Stone & Brick"; + const foundation = data.Foundation__c || data.foundation || "Concrete"; + const utilities = data.Utilities__c || data.utilities || "All Connected"; + const zoning = data.Zoning__c || data.zoning || "Residential"; + const hoa = data.HOA__c || data.hoa || "Yes"; + const hoaFee = data.HOA_Fee__c || data.hoaFee || "$500/month"; + const taxYear = data.Tax_Year__c || data.taxYear || "2024"; + const taxAmount = data.Tax_Amount__c || data.taxAmount || "$12,000/year"; + const lastSold = data.Last_Sold__c || data.lastSold || "2020"; + const lastSoldPrice = data.Last_Sold_Price__c || data.lastSoldPrice || "$3,200,000"; + + // Location and POI data + const schools = data.Schools__c || data.schools || "5 min drive"; + const shoppingCenters = data.Shopping_Centers__c || data.shoppingCenters || "10 min drive"; + const airportDistance = data.Airport_Distance__c || data.airportDistance || "25 min drive"; + const nearbyLandmarks = data.Nearby_Landmarks__c || data.nearbyLandmarks || "City Center 15 min"; + const transportation = data.Transportation__c || data.transportation || "Metro 5 min walk"; + const hospitals = data.Hospitals__c || data.hospitals || "10 min drive"; + const beachDistance = data.Beach_Distance__c || data.beachDistance || "30 min drive"; + const metroDistance = data.Metro_Distance__c || data.metroDistance || "5 min walk"; + + // Additional information + const petFriendly = data.Pet_Friendly__c || data.petFriendly || "Yes"; + const smokingAllowed = data.Smoking_Allowed__c || data.smokingAllowed || "No"; + const availableFrom = data.Available_From__c || data.availableFrom || "Immediate"; + const minimumContract = data.Minimum_Contract__c || data.minimumContract || "12 months"; + const securityDeposit = data.Security_Deposit__c || data.securityDeposit || "2 months rent"; + const utilitiesIncluded = data.Utilities_Included__c || data.utilitiesIncluded || "Water, Electricity"; + const internetIncluded = data.Internet_Included__c || data.internetIncluded || "Yes"; + const cableIncluded = data.Cable_Included__c || data.cableIncluded || "Yes"; + + // Agent and owner information + const agentName = data.Agent_Name__c || data.agentName || "Olivia Sterling"; + const agentPhone = data.Agent_Phone__c || data.agentPhone || "(555) 987-6543"; + const agentEmail = data.Agent_Email__c || data.agentEmail || "olivia@elysianestates.com"; + const ownerName = data.Owner_Name__c || data.ownerName || "John & Jane Doe"; + const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "(555) 111-2222"; + const ownerEmail = data.Owner_Email__c || data.ownerEmail || "owner@email.com"; + + // Get smart images + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage1 = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + const interiorImage2 = this.getSmartImageForSection('living', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + const kitchenImage = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1600585152225-3579fe9d7ae2?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + const bedroomImage = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + + // Generate amenities HTML + const amenitiesHTML = this.generateAmenitiesHTML(data); + + // Generate property gallery HTML + const propertyGalleryHTML = this.generatePropertyGalleryHTML(data); + + // Return the complete Grand Oak Villa template with all dynamic data + return `Grand Oak Villa - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}
Lot Size: ${lotSize}
Heating: ${heating}
Cooling: ${cooling}
Roof: ${roof}
Exterior: ${exterior}
Foundation: ${foundation}
Utilities: ${utilities}
Zoning: ${zoning}
HOA: ${hoa}
HOA Fee: ${hoaFee}

Amenities & Features

    ${amenitiesHTML}
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}

Nearby Locations

Schools: ${schools}
Shopping Centers: ${shoppingCenters}
Airport: ${airportDistance}
Landmarks: ${nearbyLandmarks}
Transportation: ${transportation}
Hospitals: ${hospitals}
Beach: ${beachDistance}
Metro: ${metroDistance}

Financial Information

Tax Year: ${taxYear}
Tax Amount: ${taxAmount}
Last Sold: ${lastSold}
Last Sold Price: ${lastSoldPrice}
Maintenance Fee: ${maintenanceFee}
Service Charge: ${serviceCharge}

Additional Information

Pet Friendly: ${petFriendly}
Smoking Allowed: ${smokingAllowed}
Available From: ${availableFrom}
Minimum Contract: ${minimumContract}
Security Deposit: ${securityDeposit}
Utilities Included: ${utilitiesIncluded}
Internet Included: ${internetIncluded}
Cable Included: ${cableIncluded}
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}

Image Gallery

Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}
`; + } + + createSampleTemplate() { + // Return the template HTML with all dynamic data + return `
+
+
+
+
+
+
Elysian Estates Collection
+

${propertyName}

+

${location}

+

Reference ID: ${referenceId}

+
+
+
6,200 Sq. Ft. β€’ 5 Bedrooms β€’ 6 Bathrooms
+ An architectural marvel of curated living space.
+ Offered at ${price} +
+
+
+
+
+
+ 02 +

A Sanctuary of Modern Design

+

Where light, space, and nature converge to create an unparalleled living experience.

+
+
+

Designed by the world-renowned architect, Helena Vance, ${propertyName} is more than a home; it is a living sculpture. Every line, material, and detail has been thoughtfully considered to evoke a sense of peace and connection with the surrounding landscape.

+

A timeless residence built not just for living, but for thriving.

+

The interior palette is a harmonious blend of natural oak, Italian travertine, and warm bronze accents, creating an atmosphere of understated luxury.

+
+
+
+
+
+
`; } createSampleTemplate() { @@ -2332,7 +2708,452 @@ export default class PropertyTemplateSelector extends LightningElement { const ownerPhone = data.ownerPhone || "(555) 111-2222"; const ownerEmail = data.ownerEmail || "owner.serenity@email.com"; - return `Editorial Real Estate Brochure - Updated
Elysian Estates Collection

${propertyName}

${location}

Reference ID: ${referenceId}

6,200 Sq. Ft. β€’ 5 Bedrooms β€’ 6 Bathrooms
An architectural marvel of curated living space.
Offered at ${price}
02

A Sanctuary of Modern Design

Where light, space, and nature converge to create an unparalleled living experience.

Designed by the world-renowned architect, Helena Vance, ${propertyName} is more than a home; it is a living sculpture. Every line, material, and detail has been thoughtfully considered to evoke a sense of peace and connection with the surrounding landscape. Soaring ceilings and floor-to-ceiling glass walls dissolve the boundaries between inside and out, flooding the space with natural light.

A timeless residence built not just for living, but for thriving.

The interior palette is a harmonious blend of natural oak, Italian travertine, and warm bronze accents, creating an atmosphere of understated luxury. This property represents a unique opportunity to own a piece of architectural history.

03

Property Specifications

A comprehensive overview of the property's features, details, and amenities.

5
Bedrooms
6
Bathrooms
6,200
Square Feet
0.75
Acres

Property Details

Status${data.status || "For Sale"}
Year Built${data.yearBuilt || "2023"}
Type${data.propertyType || "Single-Family Home"}
Furnishing${data.furnishing || "Partially Furnished"}
Floor${data.floor || "2 Levels"}
Maintenance Fee${data.maintenanceFee || "$1,200 / month"}
Parking${data.parking || "3-Car Garage"}
Service Charge${data.serviceCharge || "Included"}

Amenities & Features

  • Infinity Pool
  • Private Home Theater
  • Gourmet Chef's Kitchen
  • Wine Cellar
  • Smart Home Automation
  • Spa & Sauna Room
  • Landscaped Gardens
  • Outdoor Fire Pit
04

Floor Plan & Details

Location & Nearby

Schools ${data.schools || "5 min drive"}
Shopping ${data.shoppingCenters || "10 min drive"}
Hospitals ${data.hospitals || "12 min drive"}
Country Club ${data.countryClub || "8 min drive"}
Airport ${data.airportDistance || "20 min drive"}

Additional Information

Pet-Friendly ${data.petFriendly || "By Approval"}
Smoking ${data.smokingAllowed || "Not Permitted"}
Availability ${data.availableFrom || "Immediate"}
Utilities ${data.utilitiesIncluded || "Not Included"}

Floor Plan & Location

Owner Information
${ownerName}

${ownerPhone}

Agent Information
${agentName}

${agentPhone}

`; + // Return the complete Grand Oak Villa template with all dynamic data + return `
+ + + +
+
+
+ +
${data.status || 'FOR SALE'}
+
+
+

${propertyName}

+

${location}

+
+
+
+
${bedrooms}
+
Bedrooms
+
+
+
${bathrooms}
+
Bathrooms
+
+
+
${squareFeet}
+
Sq. Ft.
+
+
+
${price}
+
Price
+
+
+
+ + +
+
+ +
+
+

Description

+
+

${data.description || 'Nestled in the heart of Prestige City, The Grand Oak Villa is a masterpiece of modern architecture and timeless elegance. This expansive 6,200 sq. ft. residence offers unparalleled luxury and privacy.'}

+

${data.descriptionEnglish || 'With soaring ceilings, bespoke finishes, and panoramic views from every room, this home is designed for those who appreciate the finer things in life. The open-plan living space is perfect for entertaining, featuring a gourmet chef\'s kitchen, a formal dining area, and a grand living room with a statement fireplace.'}

+
+
+ +
+
+

Specifications

+
+
Reference ID: ${referenceId}
+
Status: ${data.status || 'For Sale'}
+
Type: ${data.propertyType || 'Villa'}
+
Year Built: ${data.yearBuilt || '2023'}
+
Floor: ${data.floor || '2 Levels'}
+
Parking: ${data.parking || '3-Car Garage'}
+
Furnishing: ${data.furnishing || 'Partially Furnished'}
+
Maintenance Fee: ${data.maintenanceFee || 'N/A'}
+
Service Charge: ${data.serviceCharge || 'Included'}
+
+
+
+

Amenities & Features

+
    +
  • Infinity Pool
  • +
  • Private Home Theater
  • +
  • Gourmet Chef's Kitchen
  • +
  • Wine Cellar
  • +
  • Smart Home Automation
  • +
  • Spa & Sauna Room
  • +
  • Landscaped Gardens
  • +
  • Outdoor Fire Pit
  • +
+
+
+
+
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
+
Owner: ${ownerName} | ${ownerPhone}
+
+
+ + +
+
+
+
+ +
+
+
+
Schools
+
${data.schools || '5 min drive'}
+
+
+
+
Shopping
+
${data.shoppingCenters || '10 min drive'}
+
+
+
+
Airport
+
${data.airportDistance || '20 min drive'}
+
+
+
+
Landmarks
+
${data.nearbyLandmarks || '15 min drive'}
+
+
+
+
Transportation
+
${data.transportation || '5 min walk'}
+
+
+
+
Hospitals
+
${data.hospitals || '12 min drive'}
+
+
+
+
Beach
+
${data.beachDistance || '25 min drive'}
+
+
+
+
Metro
+
${data.metroDistance || '8 min walk'}
+
+
+
+
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
+
Owner: ${ownerName} | ${ownerPhone}
+
+
+ + +
+
+ +
+ +
+

Additional Information

+
+
Pet Friendly: ${data.petFriendly || 'By Approval'}
+
Smoking: ${data.smokingAllowed || 'Not Permitted'}
+
Available From: ${data.availableFrom || 'Immediate'}
+
Minimum Contract: ${data.minimumContract || '12 months'}
+
Security Deposit: ${data.securityDeposit || '1 month rent'}
+
Utilities Included: ${data.utilitiesIncluded || 'Not Included'}
+
Internet Included: ${data.internetIncluded || 'Not Included'}
+
Cable Included: ${data.cableIncluded || 'Not Included'}
+
+
+
+
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
+
Owner: ${ownerName} | ${ownerPhone}
+
+
+
`; } createLuxuryMansionTemplate() { @@ -2341,7 +3162,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Extract all available property data with fallbacks const propertyName = data.Name || data.propertyName || "Property Name"; const location = data.Address__c || data.location || "Location"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; const agentPhone = data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; const agentEmail = data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; @@ -2360,7 +3181,7 @@ export default class PropertyTemplateSelector extends LightningElement { const parking = data.Parking_Spaces__c || data.parking || "N/A"; // Dynamic description - const description = data.Description_English__c || data.descriptionEnglish || data.description || "Property description not available."; + const description = this.formatDescriptionForPDF(data.Description_English__c || data.descriptionEnglish || data.description || "Property description not available."); // Get smart images const exteriorImage = this.getExteriorImageUrl(); @@ -2389,7 +3210,7 @@ export default class PropertyTemplateSelector extends LightningElement { - return `Modern Urban Residences Brochure - ${propertyName}
An Urban Oasis

${propertyName}

${location}

Where Design Meets Desire.

${propertyName} is not just a building; it's a bold statement on modern urban living. ${description}

Every residence is a testament to quality, featuring panoramic city views from floor-to-ceiling windows, intelligent home systems, and finishes selected from the finest materials around the globe. This is more than a home; it's a new perspective.

${propertyName}Page 02 / 07
${propertyName}Page 03 / 07

An unrivaled collection of amenities offers residents a resort-style living experience. From the serene rooftop pool to the state-of-the-art wellness center, every detail is crafted for comfort, convenience, and luxury.

Lifestyle Amenities

    ${amenitiesHTML}

Key Specifications

Status ${status}
Property Type ${propertyType}
Year Built ${yearBuilt}
Floor ${data.Floor__c || "N/A"}
Maintenance Fee ${data.Maintenance_Fee__c || "N/A"}
Bedrooms ${bedrooms}
Bathrooms ${bathrooms}
Square Feet ${squareFeet}
${propertyName}Page 04 / 07

${bedrooms}-Bedroom Residence

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${parking}
PARKING

A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

Premium ${propertyType}

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${furnishing}
FURNISHING

The pinnacle of luxury living, this residence offers expansive spaces, premium finishes, and exclusive access to premium amenities.

Additional Information

Pets
${petFriendly}
Smoking
${smoking}
Availability
${availability}
Parking
${parking}
Furnishing
${furnishing}
Utilities
${utilities}
${propertyName}Page 05 / 07

Schedule a Private Viewing

Experience ${propertyName} firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

${agentName}
Sales Executive, Elysian Properties
${agentPhone}
${agentEmail}

Neighborhood Highlights

  • Landmarks: ${landmarks}
  • Transportation: ${transportation}
  • Schools: ${schools}
  • Shopping: ${shopping}
  • Airport: ${airport}
${propertyName}Page 06 / 07
${propertyGallery}
${propertyName}Page 07 / 07
`; + return `Modern Urban Residences Brochure - ${propertyName}
Cover
An Urban Oasis

${propertyName}

${location}

Where Design Meets Desire.

${propertyName} is not just a building; it's a bold statement on modern urban living. ${description}

Every residence is a testament to quality, featuring panoramic city views from floor-to-ceiling windows, intelligent home systems, and finishes selected from the finest materials around the globe. This is more than a home; it's a new perspective.

Interior
${propertyName}Page 02 / 07
${propertyName}Page 03 / 07

An unrivaled collection of amenities offers residents a resort-style living experience. From the serene rooftop pool to the state-of-the-art wellness center, every detail is crafted for comfort, convenience, and luxury.

Feature

Lifestyle Amenities

    ${amenitiesHTML}

Key Specifications

Status ${status}
Property Type ${propertyType}
Year Built ${yearBuilt}
Floor ${data.Floor__c || "N/A"}
Maintenance Fee ${data.Maintenance_Fee__c || "N/A"}
Bedrooms ${bedrooms}
Bathrooms ${bathrooms}
Square Feet ${squareFeet}
${propertyName}Page 04 / 07
Residence Plan

${bedrooms}-Bedroom Residence

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${parking}
PARKING

A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

Penthouse Plan

Premium ${propertyType}

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${furnishing}
FURNISHING

The pinnacle of luxury living, this residence offers expansive spaces, premium finishes, and exclusive access to premium amenities.

Additional Information

Pets
${petFriendly}
Smoking
${smoking}
Availability
${availability}
Parking
${parking}
Furnishing
${furnishing}
Utilities
${utilities}
${propertyName}Page 05 / 07

Schedule a Private Viewing

Experience ${propertyName} firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

${agentName}
Sales Executive, Elysian Properties
${agentPhone}
${agentEmail}

Neighborhood Highlights

  • Landmarks: ${landmarks}
  • Transportation: ${transportation}
  • Schools: ${schools}
  • Shopping: ${shopping}
  • Airport: ${airport}
${propertyName}Page 06 / 07
${propertyGallery}
${propertyName}Page 07 / 07
`; } createSerenityHouseTemplate() { @@ -2398,7 +3219,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Extract all available property data with fallbacks const propertyName = data.Name || data.propertyName || "Property Name"; const location = data.Address__c || data.location || "Location"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; const agentPhone = data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; const agentEmail = data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; @@ -2421,7 +3242,7 @@ export default class PropertyTemplateSelector extends LightningElement { const parking = data.Parking_Spaces__c || data.parking || "N/A"; // Dynamic description - const description = data.Description_English__c || data.descriptionEnglish || data.description || "Property description not available."; + const description = this.formatDescriptionForPDF(data.Description_English__c || data.descriptionEnglish || data.description || "Property description not available."); // Get smart images const exteriorImage = this.getExteriorImageUrl(); @@ -2524,7 +3345,7 @@ export default class PropertyTemplateSelector extends LightningElement { .p1-main-title { font-family: var(--font-serif); - font-size: 5rem; + font-size: 3.5rem; font-weight: 600; line-height: 1.1; color: var(--color-text-primary); @@ -2623,6 +3444,30 @@ export default class PropertyTemplateSelector extends LightningElement { min-height: 297mm; } + .p2-image-only { + width: 100%; + height: calc(100% - 100px); + background-image: url('https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + background-size: cover; + background-position: center; + border-radius: 8px; + margin-top: 50px; + } + + .p3-description { + margin-top: 30px; + } + + .p3-description p { + font-size: 15px; + line-height: 1.7; + color: var(--color-text-secondary); + margin: 0; + text-align: justify; + white-space: pre-wrap; + word-wrap: break-word; + } + .p2-text p { font-size: 1rem; line-height: 1.8; @@ -2733,11 +3578,11 @@ export default class PropertyTemplateSelector extends LightningElement { } .p4-floorplan-container { - height: 280px; + height: 320px; background-color: var(--color-off-white); border: 1px solid var(--color-border); - background-image: url('${this.getMapsImageUrl()}'); - background-size: cover; + background-image: url('https://cdn.shopify.com/s/files/1/0024/0495/3953/files/Architect_s_floor_plan_for_a_house_in_black_and_white_large.jpg'); + background-size: contain; background-position: center; background-repeat: no-repeat; margin-bottom: 40px; @@ -2817,7 +3662,7 @@ export default class PropertyTemplateSelector extends LightningElement {
${data.collection || "Elysian Estates Collection"}

${propertyName}

${location}

-

Reference ID: ${referenceId}

+

Property: ${propertyName}

${squareFeet} Sq. Ft. β€’ ${bedrooms} Bedrooms β€’ ${bathrooms} Bathrooms
@@ -2828,25 +3673,30 @@ export default class PropertyTemplateSelector extends LightningElement {
- +
02 +
+
+
+ + +
+
+ 03

${propertyName}

A Sanctuary of Modern Design

-
-
+

${description}

-
-
- +
- 03 + 04

Property Specifications

A comprehensive overview of the property's features, details, and amenities.

@@ -2918,10 +3768,10 @@ export default class PropertyTemplateSelector extends LightningElement {
- +
- 04 + 05

${propertyName} - Floor Plan & Details

@@ -2982,14 +3832,17 @@ export default class PropertyTemplateSelector extends LightningElement {
- +
- 05 + 06

${propertyName} - Property Gallery

-
@@ -3002,6 +3855,52 @@ export default class PropertyTemplateSelector extends LightningElement { this.error = ''; } + // Development mode properties + @track debugMode = false; + + // Development page event handlers + handleClearData() { + this.currentStep = 1; + this.selectedTemplateId = ''; + this.selectedPropertyId = ''; + this.propertyData = {}; + this.htmlContent = ''; + this.editorContent = ''; + this.error = ''; + this.showPdfPreview = false; + this.showImageReview = false; + this.showImageReplacement = false; + this.showSaveDialog = false; + this.undoStack = []; + this.redoStack = []; + this.showSuccess('All data cleared'); + } + + handleResetTemplates() { + this.currentStep = 1; + this.selectedTemplateId = ''; + this.selectedPropertyId = ''; + this.propertyData = {}; + this.htmlContent = ''; + this.editorContent = ''; + this.showSuccess('Templates reset to default'); + } + + handleTestPdf() { + if (this.selectedTemplateId && this.selectedPropertyId) { + this.generatePdfViaExternalApi(); + } else { + this.showError('Please select a template and property first'); + } + } + + handleToggleDebug(event) { + this.debugMode = event.detail.debugMode; + if (this.debugMode) { + this.showSuccess('Debug mode enabled - check console for detailed logs'); + } + } + // PDF Preview methods closePdfPreview() { this.showPdfPreview = false; @@ -3026,7 +3925,6 @@ export default class PropertyTemplateSelector extends LightningElement { } handleReset() { - console.log('Reset functionality - to be implemented'); // Reload the template this.loadTemplateInStep3(); } @@ -3056,7 +3954,6 @@ export default class PropertyTemplateSelector extends LightningElement { } handleFontFamilyChange(event) { - console.log('Font family changed to:', event.target.value); } handleFontSizeChange(event) { @@ -3078,7 +3975,6 @@ export default class PropertyTemplateSelector extends LightningElement { } else { this.showError('Please select text first'); } - console.log('Bold functionality - to be implemented'); } handleItalic() { @@ -3089,7 +3985,6 @@ export default class PropertyTemplateSelector extends LightningElement { } else { this.showError('Please select text first'); } - console.log('Italic functionality - to be implemented'); } handleUnderline() { @@ -3100,7 +3995,6 @@ export default class PropertyTemplateSelector extends LightningElement { } else { this.showError('Please select text first'); } - console.log('Underline functionality - to be implemented'); } handleHighlight() { @@ -3152,68 +4046,70 @@ export default class PropertyTemplateSelector extends LightningElement { // NEW CLEAN BULLET FUNCTION handleBulletList() { const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - this.showError('Editor not found'); - return; - } - + if (!editorContent) { this.showError('Editor not found'); return; } editorContent.focus(); - + const ok = document.execCommand('insertUnorderedList', false, null); + if (!ok) { const selection = window.getSelection(); - const selectedText = selection.toString(); - - if (selectedText) { - // Replace selected text with "* " + selected text + if (selection.rangeCount) { const range = selection.getRangeAt(0); - range.deleteContents(); - range.insertNode(document.createTextNode('* ' + selectedText)); - this.showSuccess('Bullet (*) added to selected text'); + if (range.collapsed) { + // Wrap current block + const ul = document.createElement('ul'); + const li = document.createElement('li'); + li.innerHTML = ' '; + ul.appendChild(li); + range.insertNode(ul); + // place caret inside LI + const newRange = document.createRange(); + newRange.selectNodeContents(li); + newRange.collapse(true); + selection.removeAllRanges(); + selection.addRange(newRange); } else { - // Insert "* " at cursor - const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null; - if (range) { - range.insertNode(document.createTextNode('* ')); - this.showSuccess('Bullet (*) inserted'); - } else { - editorContent.innerHTML = '* '; - this.showSuccess('Bullet (*) added'); + const li = document.createElement('li'); + li.appendChild(range.extractContents()); + const ul = document.createElement('ul'); + ul.appendChild(li); + range.insertNode(ul); + selection.removeAllRanges(); + } } } - editorContent.dispatchEvent(new Event('input', { bubbles: true })); } // NEW CLEAN NUMBER FUNCTION handleNumberedList() { const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - this.showError('Editor not found'); - return; - } - + if (!editorContent) { this.showError('Editor not found'); return; } editorContent.focus(); - + const ok = document.execCommand('insertOrderedList', false, null); + if (!ok) { const selection = window.getSelection(); - const selectedText = selection.toString(); - - if (selectedText) { - // Replace selected text with "1. " + selected text + if (selection.rangeCount) { const range = selection.getRangeAt(0); - range.deleteContents(); - range.insertNode(document.createTextNode('1. ' + selectedText)); - this.showSuccess('Number (1.) added to selected text'); + if (range.collapsed) { + const ol = document.createElement('ol'); + const li = document.createElement('li'); + li.innerHTML = ' '; + ol.appendChild(li); + range.insertNode(ol); + const newRange = document.createRange(); + newRange.selectNodeContents(li); + newRange.collapse(true); + selection.removeAllRanges(); + selection.addRange(newRange); } else { - // Insert "1. " at cursor - const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null; - if (range) { - range.insertNode(document.createTextNode('1. ')); - this.showSuccess('Number (1.) inserted'); - } else { - editorContent.innerHTML = '1. '; - this.showSuccess('Number (1.) added'); + const li = document.createElement('li'); + li.appendChild(range.extractContents()); + const ol = document.createElement('ol'); + ol.appendChild(li); + range.insertNode(ol); + selection.removeAllRanges(); + } } } - editorContent.dispatchEvent(new Event('input', { bubbles: true })); } @@ -3283,6 +4179,10 @@ export default class PropertyTemplateSelector extends LightningElement { highlightSelectedElement(element) { element.style.outline = '2px solid #6b7280'; element.style.outlineOffset = '2px'; + // Reflect current z-index in toolbox + const target = element.classList && element.classList.contains('draggable-image-container') ? element : (element.closest && element.closest('.draggable-image-container')) || element; + const currentZ = (target && target.style && target.style.zIndex) ? target.style.zIndex : ''; + this.zIndexInput = currentZ; } // Clear selection @@ -3775,20 +4675,80 @@ export default class PropertyTemplateSelector extends LightningElement { const images = editor.querySelectorAll('img'); images.forEach(img => { - // Make draggable - img.draggable = true; + // Prevent position changes on click img.style.position = 'relative'; img.style.zIndex = '1000'; + img.style.transition = 'none'; // Disable transitions during drag // Add resize handles this.addResizeHandles(img); - // Add drag event listeners - img.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - img.addEventListener('dragend', this.handleImageDragEnd.bind(this)); + // Add smooth drag event listeners + img.addEventListener('mousedown', this.handleImageMouseDown.bind(this)); + img.addEventListener('mousemove', this.handleImageMouseMove.bind(this)); + img.addEventListener('mouseup', this.handleImageMouseUp.bind(this)); + img.addEventListener('mouseleave', this.handleImageMouseUp.bind(this)); }); } + // Smooth drag handlers for images + handleImageMouseDown(e) { + if (e.target.tagName !== 'IMG') return; + + e.preventDefault(); + this.isDraggingImage = true; + this.dragStartX = e.clientX; + this.dragStartY = e.clientY; + this.dragElement = e.target; + + // Store initial position + const rect = this.dragElement.getBoundingClientRect(); + const editor = this.template.querySelector('.enhanced-editor-content'); + const editorRect = editor.getBoundingClientRect(); + + this.initialLeft = rect.left - editorRect.left; + this.initialTop = rect.top - editorRect.top; + + // Add dragging class for visual feedback + this.dragElement.style.cursor = 'grabbing'; + this.dragElement.style.opacity = '0.8'; + + // Prevent text selection during drag + document.body.style.userSelect = 'none'; + } + + handleImageMouseMove(e) { + if (!this.isDraggingImage || !this.dragElement) return; + + e.preventDefault(); + + const deltaX = e.clientX - this.dragStartX; + const deltaY = e.clientY - this.dragStartY; + + // Update position smoothly + this.dragElement.style.left = (this.initialLeft + deltaX) + 'px'; + this.dragElement.style.top = (this.initialTop + deltaY) + 'px'; + this.dragElement.style.position = 'absolute'; + } + + handleImageMouseUp(e) { + if (!this.isDraggingImage || !this.dragElement) return; + + this.isDraggingImage = false; + + // Restore cursor and opacity + this.dragElement.style.cursor = 'grab'; + this.dragElement.style.opacity = '1'; + + // Re-enable text selection + document.body.style.userSelect = ''; + + // Save undo state after drag + this.saveUndoState(); + + this.dragElement = null; + } + // Add resize handles to image addResizeHandles(img) { const handles = ['nw', 'ne', 'sw', 'se']; @@ -3847,13 +4807,14 @@ export default class PropertyTemplateSelector extends LightningElement { } // Start resize operation - startResize(event, img, handle) { + startResize(event, target, handle) { + const container = target.classList.contains('draggable-image-container') ? target : target.parentElement; const startX = event.clientX; const startY = event.clientY; - const startWidth = img.offsetWidth; - const startHeight = img.offsetHeight; - const startLeft = img.offsetLeft; - const startTop = img.offsetTop; + const startWidth = container.offsetWidth; + const startHeight = container.offsetHeight; + const startLeft = container.offsetLeft; + const startTop = container.offsetTop; const handleMouseMove = (e) => { const deltaX = e.clientX - startX; @@ -3887,10 +4848,10 @@ export default class PropertyTemplateSelector extends LightningElement { break; } - img.style.width = Math.max(50, newWidth) + 'px'; - img.style.height = Math.max(50, newHeight) + 'px'; - img.style.left = newLeft + 'px'; - img.style.top = newTop + 'px'; + container.style.width = Math.max(50, newWidth) + 'px'; + container.style.height = Math.max(50, newHeight) + 'px'; + container.style.left = newLeft + 'px'; + container.style.top = newTop + 'px'; }; const handleMouseUp = () => { @@ -3958,71 +4919,19 @@ export default class PropertyTemplateSelector extends LightningElement { handleIndent() { const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - this.showError('Editor not found'); - return; - } - - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const selectedElement = range.commonAncestorContainer; - - // Find the closest block element - let blockElement = selectedElement; - while (blockElement && blockElement !== editorContent) { - if (blockElement.nodeType === Node.ELEMENT_NODE && - ['P', 'DIV', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(blockElement.tagName)) { - break; - } - blockElement = blockElement.parentElement; - } - - if (blockElement && blockElement !== editorContent) { - const currentMarginLeft = parseInt(blockElement.style.marginLeft) || 0; - blockElement.style.marginLeft = (currentMarginLeft + 20) + 'px'; - this.showSuccess('Text indented'); - } else { - this.showError('Please select text to indent'); - } - } else { - this.showError('Please select text to indent'); - } + if (!editorContent) { this.showError('Editor not found'); return; } + editorContent.focus(); + // For list contexts, execCommand handles nesting properly + document.execCommand('indent', false, null); + editorContent.dispatchEvent(new Event('input', { bubbles: true })); } handleOutdent() { const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - this.showError('Editor not found'); - return; - } - - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const selectedElement = range.commonAncestorContainer; - - // Find the closest block element - let blockElement = selectedElement; - while (blockElement && blockElement !== editorContent) { - if (blockElement.nodeType === Node.ELEMENT_NODE && - ['P', 'DIV', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(blockElement.tagName)) { - break; - } - blockElement = blockElement.parentElement; - } - - if (blockElement && blockElement !== editorContent) { - const currentMarginLeft = parseInt(blockElement.style.marginLeft) || 0; - const newMarginLeft = Math.max(0, currentMarginLeft - 20); - blockElement.style.marginLeft = newMarginLeft + 'px'; - this.showSuccess('Text outdented'); - } else { - this.showError('Please select text to outdent'); - } - } else { - this.showError('Please select text to outdent'); - } + if (!editorContent) { this.showError('Editor not found'); return; } + editorContent.focus(); + document.execCommand('outdent', false, null); + editorContent.dispatchEvent(new Event('input', { bubbles: true })); } handleFontFamilyChange(event) { @@ -4100,8 +5009,11 @@ export default class PropertyTemplateSelector extends LightningElement { } insertPropertyDescription() { - const description = this.propertyData.Description__c || 'Property Description'; - this.insertTextAtCursor(description); + const description = this.propertyData.Description_English__c || this.propertyData.pcrm__Description_English__c || this.propertyData.Description__c || 'Property Description'; + // Wrap into paragraphs and basic formatting + const lines = String(description).split(/\n+/).map(l => l.trim()).filter(Boolean); + const html = lines.map(l => `

${l}

`).join(''); + this.insertHtmlAtCursor(html); } // Helper function to insert text at cursor position @@ -4122,6 +5034,27 @@ export default class PropertyTemplateSelector extends LightningElement { } } + // Helper to insert HTML at cursor + insertHtmlAtCursor(html) { + const selection = window.getSelection(); + if (!selection.rangeCount) { this.showError('Please place cursor in the editor first'); return; } + const range = selection.getRangeAt(0); + range.deleteContents(); + const temp = document.createElement('div'); + temp.innerHTML = html; + const fragment = document.createDocumentFragment(); + while (temp.firstChild) { + fragment.appendChild(temp.firstChild); + } + range.insertNode(fragment); + // Move caret to end of inserted content + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); + const editorContent = this.template.querySelector('.enhanced-editor-content'); + if (editorContent) editorContent.dispatchEvent(new Event('input', { bubbles: true })); + } + // Setup editor click handler to deselect elements setupEditorClickHandler() { const editor = this.template.querySelector('.enhanced-editor-content'); @@ -4228,7 +5161,6 @@ export default class PropertyTemplateSelector extends LightningElement { this.handleImageClick(clickedImage, e); return; } else { - console.log('Invalid image element detected, ignoring:', clickedImage); } } @@ -4359,7 +5291,6 @@ export default class PropertyTemplateSelector extends LightningElement { }); } - console.log('Property images populated:', this.propertyImages); } // Close image insertion modal @@ -4418,10 +5349,8 @@ export default class PropertyTemplateSelector extends LightningElement { const imageUrl = imageItem ? imageItem.dataset.imageUrl : null; const imageName = event.target.alt || event.target.textContent || 'Property Image'; - console.log('Property image selected:', imageUrl, imageName); if (!imageUrl) { - console.error('No image URL found in selected item'); return; } @@ -4442,8 +5371,6 @@ export default class PropertyTemplateSelector extends LightningElement { this.uploadedImageData = ''; this.insertButtonDisabled = false; - console.log('After selection - selectedImageUrl:', this.selectedImageUrl); - console.log('Button disabled state:', this.insertButtonDisabled); // Log current state for debugging this.logCurrentState(); @@ -4475,11 +5402,12 @@ export default class PropertyTemplateSelector extends LightningElement { } } - // Trigger file upload + // Trigger file upload for main image modal triggerFileUpload() { const fileInput = this.template.querySelector('.file-input'); if (fileInput) { fileInput.click(); + } else { } } @@ -4487,7 +5415,6 @@ export default class PropertyTemplateSelector extends LightningElement { handleFileUpload(event) { const file = event.target.files[0]; if (file) { - console.log('File selected:', file.name); const reader = new FileReader(); reader.onload = (e) => { this.uploadedImageData = e.target.result; @@ -4495,8 +5422,6 @@ export default class PropertyTemplateSelector extends LightningElement { this.selectedImageName = file.name; this.insertButtonDisabled = false; - console.log('Image loaded, selectedImageUrl:', this.selectedImageUrl); - console.log('Button disabled state:', this.insertButtonDisabled); // Log current state for debugging this.logCurrentState(); @@ -4513,9 +5438,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Update upload area to show selected image updateUploadAreaWithSelectedImage(imageUrl, fileName) { - console.log('updateUploadAreaWithSelectedImage called with:', imageUrl, fileName); const uploadArea = this.template.querySelector('.upload-area'); - console.log('Upload area found:', uploadArea); if (uploadArea) { // Remove existing preview if any const existingPreview = uploadArea.querySelector('.uploaded-image-preview'); @@ -4584,27 +5507,18 @@ export default class PropertyTemplateSelector extends LightningElement { // Handle insert button click with debugging handleInsertButtonClick() { - console.log('=== INSERT BUTTON CLICKED ==='); this.logCurrentState(); - console.log('============================='); this.insertSelectedImage(); } // Insert selected image insertSelectedImage() { - console.log('insertSelectedImage called'); - console.log('selectedImageUrl:', this.selectedImageUrl); - console.log('insertButtonDisabled:', this.insertButtonDisabled); // Check if we have a valid image URL const imageUrl = this.selectedImageUrl || this.uploadedImageData; const imageName = this.selectedImageName || 'Uploaded Image'; if (this.insertButtonDisabled || !imageUrl) { - console.error('Please select an image first'); - console.error('selectedImageUrl:', this.selectedImageUrl); - console.error('uploadedImageData:', this.uploadedImageData); - console.error('insertButtonDisabled:', this.insertButtonDisabled); alert('Please select an image first'); return; } @@ -4724,10 +5638,21 @@ export default class PropertyTemplateSelector extends LightningElement { // Add resize handles to element addResizeHandles(element) { + // Avoid duplicate handles + const existing = element.querySelectorAll('.resize-handle'); + if (existing && existing.length > 0) return; + const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e']; handles.forEach(direction => { const handle = document.createElement('div'); handle.className = `resize-handle ${direction}`; + handle.style.position = 'absolute'; + handle.style.width = '8px'; + handle.style.height = '8px'; + handle.style.background = '#6c63ff'; + handle.style.border = '2px solid white'; + handle.style.borderRadius = '50%'; + handle.style.zIndex = '1001'; handle.addEventListener('mousedown', (e) => this.startResize(e, element, direction)); element.appendChild(handle); }); @@ -5361,7 +6286,6 @@ export default class PropertyTemplateSelector extends LightningElement { } addShape() { - console.log('Add shape functionality - to be implemented'); } @@ -5474,62 +6398,37 @@ export default class PropertyTemplateSelector extends LightningElement { }); event.currentTarget.classList.add('active'); } else { - console.error('Invalid category selection event'); return; } - console.log('=== MANUAL CATEGORY SELECTION ==='); - console.log('Selected category:', category); - console.log('Previous category:', this.selectedCategory); this.selectedCategory = category; - console.log('Updated selectedCategory:', this.selectedCategory); // Filter real property images by category this.filterImagesByCategory(category); } - // Add new method to filter images by category + // Add new method to show all images (no filtering) filterImagesByCategory(category) { - console.log('=== FILTERING IMAGES BY CATEGORY ==='); - console.log('Category:', category); - console.log('Real property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No real property images available'); this.currentImage = null; this.totalImages = 0; this.currentImageIndex = 0; return; } - // Filter images by category - const filteredImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - console.log('Image category:', imgCategory, 'Selected category:', category); - - // Handle "None" category - show images with no category or empty category - if (category === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === category; - }); + // Show all images instead of filtering by category + this.propertyImages = this.realPropertyImages; + this.totalImages = this.realPropertyImages.length; - console.log('Filtered images for category', category, ':', filteredImages); - - if (filteredImages.length > 0) { - this.currentImage = filteredImages[0]; - this.totalImages = filteredImages.length; + if (this.realPropertyImages.length > 0) { + this.currentImage = this.realPropertyImages[0]; this.currentImageIndex = 0; - console.log('Set current image:', this.currentImage); - console.log('Total images:', this.totalImages); - console.log('Current index reset to 0 for category:', category); } else { this.currentImage = null; this.totalImages = 0; this.currentImageIndex = 0; - console.log('No images found for category:', category); } } @@ -5569,8 +6468,6 @@ export default class PropertyTemplateSelector extends LightningElement { })); if (categoryImages.length > 0) { - console.log(`Found ${categoryImages.length} real images for category: ${category}`); - console.log('Category images:', categoryImages); return categoryImages; } } @@ -5673,28 +6570,18 @@ export default class PropertyTemplateSelector extends LightningElement { } nextImage() { - console.log('=== NEXT IMAGE CLICKED ==='); - console.log('Current index:', this.currentImageIndex); - console.log('Total images:', this.totalImages); - console.log('Selected category:', this.selectedCategory); if (this.currentImageIndex < this.totalImages - 1) { this.currentImageIndex++; this.updateCurrentImage(); } else { - console.log('Already at last image'); } } previousImage() { - console.log('=== PREVIOUS IMAGE CLICKED ==='); - console.log('Current index:', this.currentImageIndex); - console.log('Total images:', this.totalImages); - console.log('Selected category:', this.selectedCategory); if (this.currentImageIndex > 0) { this.currentImageIndex--; this.updateCurrentImage(); } else { - console.log('Already at first image'); } } @@ -5704,22 +6591,22 @@ export default class PropertyTemplateSelector extends LightningElement { return; } - // Use the same filtering logic as filterImagesByCategory - const filteredImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - // Handle "None" category - show images with no category or empty category - if (this.selectedCategory === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; + // Use all images instead of filtering by category + if (this.realPropertyImages.length > 0 && this.currentImageIndex < this.realPropertyImages.length) { + this.currentImage = this.realPropertyImages[this.currentImageIndex]; + // Revert: only enable drag & drop; no auto-wrap on click + const imgEl = this.template.querySelector('.property-image-step2, .review-image'); + if (imgEl) { + imgEl.setAttribute('draggable', 'true'); + imgEl.addEventListener('dragstart', this.handleImageDragStart.bind(this)); + imgEl.style.cursor = 'zoom-in'; + imgEl.onclick = () => { + const w = window.open(); + if (w && w.document) { + w.document.write(``); + } + }; } - - return imgCategory === this.selectedCategory; - }); - - if (filteredImages.length > 0 && this.currentImageIndex < filteredImages.length) { - this.currentImage = filteredImages[this.currentImageIndex]; - console.log('Updated current image:', this.currentImage); - console.log('Current index:', this.currentImageIndex, 'of', filteredImages.length); } } @@ -5746,22 +6633,49 @@ export default class PropertyTemplateSelector extends LightningElement { }); editor.addEventListener('keyup', this.handleContentChange.bind(this)); editor.addEventListener('paste', this.handleContentChange.bind(this)); + // NEW: single-click any image to show resize controls + editor.addEventListener('click', (e) => { + const target = e.target; + if (target && target.tagName && target.tagName.toLowerCase() === 'img') { + e.preventDefault(); + e.stopPropagation(); + this.selectDraggableElement(target); + } + }, true); editor.hasEditListeners = true; } - console.log('Editor made editable with event listeners'); } } // Connected callback to initialize connectedCallback() { - console.log('PropertyTemplateSelector connected'); // Ensure editor is editable after component loads setTimeout(() => { this.ensureEditorEditable(); }, 1000); + + // Keyboard shortcuts for Word-like experience + this._keyHandler = (e) => { + if (this.currentStep !== 3) return; + const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + const mod = isMac ? e.metaKey : e.ctrlKey; + if (!mod) return; + switch (e.key.toLowerCase()) { + case 'b': e.preventDefault(); this.handleBold(); break; + case 'i': e.preventDefault(); this.handleItalic(); break; + case 'u': e.preventDefault(); this.handleUnderline(); break; + case 'z': e.preventDefault(); this.undo(); break; + case 'y': e.preventDefault(); this.redo(); break; + } + }; + window.addEventListener('keydown', this._keyHandler); + + // Auto-fit when window resizes in Step 3 + this._resizeHandler = () => { if (this.currentStep === 3 && this.fitToWidth) this.fitToWidth(); }; + window.addEventListener('resize', this._resizeHandler); } // Called after template loads @@ -5773,7 +6687,10 @@ export default class PropertyTemplateSelector extends LightningElement { setTimeout(() => { this.saveUndoState(); }, 100); - + // Ensure initial fit + if (this.currentStep === 3 && this.fitToWidth) { + setTimeout(() => this.fitToWidth(), 0); + } } @@ -5783,9 +6700,6 @@ export default class PropertyTemplateSelector extends LightningElement { if (editor) { editor.focus(); this.ensureEditorEditable(); - console.log('Editor focused and made editable'); - console.log('ContentEditable:', editor.contentEditable); - console.log('Can edit:', editor.isContentEditable); } } @@ -5940,13 +6854,13 @@ export default class PropertyTemplateSelector extends LightningElement { } // Debug logging for image detection - console.log('Image detected:', { + const debugInfo = { tagName: clickedImage.tagName, isBackgroundImage: clickedImage.isBackgroundImage, src: clickedImage.src, backgroundImage: clickedImage.style.backgroundImage, originalElement: clickedImage.originalElement - }); + }; // Check if this is the same image as the last click const isSameImage = this.lastClickedImage && @@ -5957,24 +6871,20 @@ export default class PropertyTemplateSelector extends LightningElement { if (isSameImage) { // Same image clicked, increment counter this.imageClickCount++; - console.log(`Image clicked ${this.imageClickCount} times`); } else { // Different image clicked, reset counter this.imageClickCount = 1; this.lastClickedImage = clickedImage; - console.log('New image clicked, starting count'); } // Set timeout to reset counter after 1 second this.clickTimeout = setTimeout(() => { this.imageClickCount = 0; this.lastClickedImage = null; - console.log('Click counter reset due to timeout'); }, 1000); // Check if we've reached exactly 3 clicks if (this.imageClickCount === 3) { - console.log('Triple click detected! Opening image replacement popup'); event.preventDefault(); event.stopPropagation(); this.openImageReplacement(clickedImage); @@ -6003,11 +6913,9 @@ export default class PropertyTemplateSelector extends LightningElement { // Image Replacement Methods openImageReplacement(imageElement) { if (!imageElement) { - console.error('No image element provided for replacement'); return; } - console.log('Opening image replacement for:', imageElement); this.selectedImageElement = imageElement; this.showImageReplacement = true; @@ -6015,7 +6923,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Use smart category selection like Step 2 this.replacementSelectedCategory = this.findFirstAvailableCategory(); - console.log('Opening image replacement with smart category selection:', this.replacementSelectedCategory); this.uploadedImagePreview = null; this.filterReplacementImages(); @@ -6036,13 +6943,8 @@ export default class PropertyTemplateSelector extends LightningElement { // Log the selected image details for debugging if (imageElement.isBackgroundImage) { - console.log('Background image selected for replacement:', imageElement.src); - console.log('Background image style:', imageElement.style.backgroundImage); } else if (imageElement.tagName === 'IMG') { - console.log('IMG element selected for replacement:', imageElement.src); - console.log('IMG element classes:', imageElement.className); } else { - console.log('Unknown image type selected:', imageElement); } } @@ -6075,12 +6977,21 @@ export default class PropertyTemplateSelector extends LightningElement { selectLocalUploadTab() { this.replacementActiveTab = 'upload'; this.uploadedImagePreview = null; + + // Force re-render to ensure the upload area is visible + this.forceRerender(); + + // Add a small delay to ensure DOM is updated + setTimeout(() => { + const uploadDropzone = this.template.querySelector('.upload-dropzone'); + if (uploadDropzone) { + } else { + } + }, 100); } selectReplacementCategory(event) { const category = event.target.dataset.category; - console.log('=== REPLACEMENT CATEGORY SELECTION ==='); - console.log('Selected category:', category); this.replacementSelectedCategory = category; this.filterReplacementImages(); @@ -6096,12 +7007,8 @@ export default class PropertyTemplateSelector extends LightningElement { } filterReplacementImages() { - console.log('=== FILTERING REPLACEMENT IMAGES ==='); - console.log('Category:', this.replacementSelectedCategory); - console.log('Real property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No real property images available for replacement'); this.filteredReplacementImages = []; return; } @@ -6118,7 +7025,6 @@ export default class PropertyTemplateSelector extends LightningElement { return imgCategory === this.replacementSelectedCategory; }); - console.log('Filtered replacement images:', filteredImages.length); this.filteredReplacementImages = filteredImages.map((img, index) => ({ id: `${this.replacementSelectedCategory}-${index}`, @@ -6130,11 +7036,8 @@ export default class PropertyTemplateSelector extends LightningElement { selectReplacementImage(event) { const imageUrl = event.currentTarget.dataset.imageUrl; - console.log('Selected replacement image URL:', imageUrl); - console.log('Current selected image element:', this.selectedImageElement); if (!imageUrl) { - console.error('No image URL found in event data'); this.showError('Failed to get image URL. Please try again.'); return; } @@ -6143,15 +7046,15 @@ export default class PropertyTemplateSelector extends LightningElement { this.closeImageReplacement(); } - triggerFileUpload() { + triggerImageReplacementFileUpload() { + + // Try to find the image upload input in the replacement modal const fileInput = this.template.querySelector('.image-upload-input'); if (fileInput) { // Reset the input to allow selecting the same file again fileInput.value = ''; fileInput.click(); - console.log('File input triggered'); } else { - console.error('File input not found'); // Fallback: create a new input programmatically const input = document.createElement('input'); input.type = 'file'; @@ -6160,27 +7063,52 @@ export default class PropertyTemplateSelector extends LightningElement { input.onchange = (e) => this.handleImageUpload(e); document.body.appendChild(input); input.click(); + // Don't remove immediately, let the handler process first + setTimeout(() => { + if (document.body.contains(input)) { document.body.removeChild(input); + } + }, 100); } } handleImageUpload(event) { const file = event.target.files[0]; - if (file && file.type.startsWith('image/')) { + + if (!file) { + return; + } + + + // Validate file type + if (!file.type.startsWith('image/')) { + this.showError('Please select a valid image file (JPG, PNG, GIF, WebP)'); + return; + } + + // Validate file size (e.g., max 10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError('File size must be less than 10MB'); + return; + } + const reader = new FileReader(); reader.onload = (e) => { this.uploadedImagePreview = e.target.result; - console.log('Image uploaded successfully, preview set'); - }; + + // Show success message + this.showSuccess('βœ… Image uploaded successfully! Click "Use This Image" to apply it.'); + + // Force re-render to show the preview + this.forceRerender(); + }; + reader.onerror = (e) => { - console.error('Error reading file:', e); + this.showError('Error reading the selected file. Please try again.'); }; + reader.readAsDataURL(file); - } else if (file) { - console.error('Selected file is not an image:', file.type); - // Show error message to user - this.showError('Please select a valid image file (JPG, PNG, GIF, WebP)'); - } } useUploadedImage() { @@ -6190,9 +7118,67 @@ export default class PropertyTemplateSelector extends LightningElement { } } + // Drag and drop handlers for image upload + handleDragOver(event) { + event.preventDefault(); + event.stopPropagation(); + const dropzone = event.currentTarget; + dropzone.classList.add('drag-over'); + } + + handleDragLeave(event) { + event.preventDefault(); + event.stopPropagation(); + const dropzone = event.currentTarget; + dropzone.classList.remove('drag-over'); + } + + handleDrop(event) { + event.preventDefault(); + event.stopPropagation(); + + const dropzone = event.currentTarget; + dropzone.classList.remove('drag-over'); + + const files = event.dataTransfer.files; + if (files.length > 0) { + const file = files[0]; + + // Validate file type + if (!file.type.startsWith('image/')) { + this.showError('Please drop a valid image file (JPG, PNG, GIF, WebP)'); + return; + } + + // Validate file size (e.g., max 10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError('File size must be less than 10MB'); + return; + } + + // Process the dropped file + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImagePreview = e.target.result; + + // Show success message + this.showSuccess('βœ… Image uploaded successfully! Click "Use This Image" to apply it.'); + + // Force re-render to show the preview + this.forceRerender(); + }; + + reader.onerror = (e) => { + this.showError('Error reading the dropped file. Please try again.'); + }; + + reader.readAsDataURL(file); + } + } + replaceImageSrc(newImageUrl) { if (!this.selectedImageElement || !newImageUrl) { - console.error('Invalid image element or URL for replacement'); return; } @@ -6205,7 +7191,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Use the stored original element reference if available if (this.selectedImageElement.originalElement) { this.selectedImageElement.originalElement.style.backgroundImage = `url("${newImageUrl}")`; - console.log('Background image replaced successfully using original element:', newImageUrl); this.showSuccess('Background image updated successfully!'); return; } @@ -6223,14 +7208,12 @@ export default class PropertyTemplateSelector extends LightningElement { const currentBgUrl = currentBgImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); if (currentBgUrl === this.selectedImageElement.src) { element.style.backgroundImage = `url("${newImageUrl}")`; - console.log('Background image replaced successfully via fallback:', newImageUrl); this.showSuccess('Background image updated successfully!'); return; } } } } - console.error('Could not find the background image element to replace'); this.showError('Failed to update background image. Please try again.'); return; } @@ -6248,14 +7231,11 @@ export default class PropertyTemplateSelector extends LightningElement { this.selectedImageElement.style.objectFit = 'cover'; } - console.log('Image replaced successfully:', newImageUrl); this.showSuccess('Image updated successfully!'); } else { - console.error('Selected element is not a valid image element'); this.showError('Failed to update image: Invalid element type'); } } catch (error) { - console.error('Error replacing image:', error); this.showError('Failed to update image. Please try again.'); } } @@ -6384,7 +7364,8 @@ export default class PropertyTemplateSelector extends LightningElement { return; } - let htmlContent = editor.innerHTML; + // Use the raw HTML content + const htmlContent = editor.innerHTML; // Create a complete HTML document const fullHtml = ` @@ -6415,6 +7396,9 @@ export default class PropertyTemplateSelector extends LightningElement { height: auto; display: block; } + ul { list-style-type: disc; padding-left: 22px; margin: 0 0 8px 0; } + ol { list-style-type: decimal; padding-left: 22px; margin: 0 0 8px 0; } + li { margin: 4px 0; } .draggable-element { position: relative; } @@ -6492,13 +7476,11 @@ export default class PropertyTemplateSelector extends LightningElement { // Table Dialog Methods openTableDialog() { - console.log('Opening table dialog'); this.showTableDialog = true; document.body.style.overflow = 'hidden'; } closeTableDialog() { - console.log('Closing table dialog'); this.showTableDialog = false; document.body.style.overflow = ''; } @@ -6525,20 +7507,16 @@ export default class PropertyTemplateSelector extends LightningElement { // Save undo state this.saveUndoState(); - // Create table element using our new method + // Create table element using our new method (draggable/resizeable container like images) const tableContainer = this.createTableElement(); - - // Get current cursor position - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - // Insert at cursor position - range.deleteContents(); - range.insertNode(tableContainer); - } else { - // If no selection, insert at the end editor.appendChild(tableContainer); - } + // Default placement similar to images + tableContainer.style.left = '50px'; + tableContainer.style.top = '50px'; + // Enable drag + resize + this.addTableResizeHandles(tableContainer); + this.makeDraggable(tableContainer); + this.setupTableEventListeners(tableContainer); this.closeTableDialog(); this.showSuccess('Table inserted successfully!'); @@ -6548,12 +7526,8 @@ export default class PropertyTemplateSelector extends LightningElement { // Get first image from a specific category getFirstImageByCategory(category) { - console.log('=== GET FIRST IMAGE BY CATEGORY ==='); - console.log('Category:', category); - console.log('Real property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No real property images available'); return null; } @@ -6562,19 +7536,14 @@ export default class PropertyTemplateSelector extends LightningElement { return imgCategory && imgCategory.toLowerCase() === category.toLowerCase(); }); - console.log('Category images found:', categoryImages.length); - console.log('First category image:', categoryImages[0]); return categoryImages.length > 0 ? categoryImages[0] : null; } // Direct method to get exterior image URL getExteriorImageUrl() { - console.log('=== GET EXTERIOR IMAGE URL ==='); - console.log('Real property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No property images, using fallback'); return 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; } @@ -6585,74 +7554,57 @@ export default class PropertyTemplateSelector extends LightningElement { }); if (exteriorImages.length > 0) { - console.log('Found exterior image:', exteriorImages[0].url); return exteriorImages[0].url; } // If no exterior, use first available image if (this.realPropertyImages.length > 0) { - console.log('No exterior image, using first available:', this.realPropertyImages[0].url); return this.realPropertyImages[0].url; } - console.log('No images available, using fallback'); return 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; } // Direct method to get maps image URL getMapsImageUrl() { - console.log('=== GET MAPS IMAGE URL ==='); - console.log('Real property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No property images, using fallback'); return 'https://images.unsplash.com/photo-1524661135-423995f22d0b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; } // Look for maps images first - check both exact match and contains const mapsImages = this.realPropertyImages.filter(img => { const category = img.category || img.pcrm__Category__c; - console.log('Checking image category:', category, 'for maps'); return category && (category.toLowerCase() === 'maps' || category.toLowerCase().includes('maps')); }); - console.log('Maps images found:', mapsImages.length); - console.log('Maps images:', mapsImages); if (mapsImages.length > 0) { - console.log('Found maps image:', mapsImages[0].url); return mapsImages[0].url; } // Look for anchor images as fallback const anchorImages = this.realPropertyImages.filter(img => { const category = img.category || img.pcrm__Category__c; - console.log('Checking image category:', category, 'for anchor'); return category && (category.toLowerCase() === 'anchor' || category.toLowerCase().includes('anchor')); }); - console.log('Anchor images found:', anchorImages.length); if (anchorImages.length > 0) { - console.log('No maps image, using anchor image:', anchorImages[0].url); return anchorImages[0].url; } - console.log('No maps or anchor images available, using fallback'); return 'https://images.unsplash.com/photo-1524661135-423995f22d0b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; } // Method to replace background-image URLs in CSS at runtime replaceBackgroundImagesInHTML(htmlContent) { - console.log('=== REPLACING BACKGROUND IMAGES IN HTML ==='); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No property images available for replacement'); return htmlContent; } const exteriorImageUrl = this.getExteriorImageUrl(); - console.log('Using exterior image URL for replacement:', exteriorImageUrl); // Replace any hardcoded background-image URLs with the property's exterior image let updatedHTML = htmlContent; @@ -6663,48 +7615,30 @@ export default class PropertyTemplateSelector extends LightningElement { // Replace all background-image URLs with the property's exterior image updatedHTML = updatedHTML.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - console.log('Background images replaced in HTML'); return updatedHTML; } // Method to dynamically update CSS background-image rules after template loads updateCSSBackgroundImages() { - console.log('=== UPDATING CSS BACKGROUND IMAGES ==='); - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No property images available for CSS update'); - return; - } - + const editor = this.template.querySelector('.enhanced-editor-content'); + if (!editor) return; + if (!this.realPropertyImages || this.realPropertyImages.length === 0) return; const exteriorImageUrl = this.getExteriorImageUrl(); - console.log('Updating CSS with exterior image URL:', exteriorImageUrl); - - // Find all style elements in the document - const styleElements = document.querySelectorAll('style'); + // Scope to styles inside the editor only + const styleElements = editor.querySelectorAll('style'); styleElements.forEach(styleElement => { - let cssText = styleElement.textContent; - - // Replace background-image URLs in CSS + const cssText = styleElement.textContent || ''; const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; const updatedCSS = cssText.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - - if (updatedCSS !== cssText) { - styleElement.textContent = updatedCSS; - console.log('Updated CSS background-image rules'); - } + if (updatedCSS !== cssText) styleElement.textContent = updatedCSS; }); - - // Also update any elements with inline background-image styles - const elementsWithBackground = document.querySelectorAll('[style*="background-image"]'); + // Update inline background-image styles only within editor + const elementsWithBackground = editor.querySelectorAll('[style*="background-image"]'); elementsWithBackground.forEach(element => { - const currentStyle = element.getAttribute('style'); + const currentStyle = element.getAttribute('style') || ''; const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; const updatedStyle = currentStyle.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - - if (updatedStyle !== currentStyle) { - element.setAttribute('style', updatedStyle); - console.log('Updated inline background-image style'); - } + if (updatedStyle !== currentStyle) element.setAttribute('style', updatedStyle); }); } @@ -6721,11 +7655,8 @@ export default class PropertyTemplateSelector extends LightningElement { // Get uncategorized images (no category or category is null/empty) getUncategorizedImages() { - console.log('=== GET UNCATEGORIZED IMAGES ==='); - console.log('Real property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No real property images available'); return []; } @@ -6733,18 +7664,12 @@ export default class PropertyTemplateSelector extends LightningElement { !img.category || img.category.trim() === '' || img.category.toLowerCase() === 'none' ); - console.log('Uncategorized images found:', uncategorized.length); - console.log('Uncategorized images:', uncategorized); return uncategorized; } // Smart image replacement - tries multiple categories in order of preference getSmartImageForSection(sectionType, fallbackUrl) { - console.log('=== GET SMART IMAGE FOR SECTION ==='); - console.log('Section type:', sectionType); - console.log('Fallback URL:', fallbackUrl); - console.log('Real property images available:', this.realPropertyImages?.length || 0); const categoryPriority = { 'exterior': ['Exterior', 'Anchor', 'None'], @@ -6759,59 +7684,53 @@ export default class PropertyTemplateSelector extends LightningElement { }; const categories = categoryPriority[sectionType] || ['Interior', 'Exterior', 'None']; - console.log('Categories to try:', categories); for (const category of categories) { const image = this.getFirstImageByCategory(category); if (image && image.url) { - console.log('Found image for category:', category, 'URL:', image.url); return image.url; } } - console.log('No property image found, using fallback:', fallbackUrl); return fallbackUrl; } // Generate Property Gallery HTML for uncategorized images generatePropertyGalleryHTML() { - console.log('=== GENERATING PROPERTY GALLERY HTML ==='); - console.log('All property images:', this.realPropertyImages); if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - console.log('No property images available, returning empty gallery'); return '
No images available
'; } - console.log('Property images found:', this.realPropertyImages.length); let galleryHTML = ''; - this.realPropertyImages.forEach((image, index) => { - const category = image.category || image.pcrm__Category__c || 'Uncategorized'; const title = image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; - - galleryHTML += ` -
- ${title} -
-
${title}
-
${category}
-
-
- `; + galleryHTML += `${title}`; }); - console.log('Generated gallery HTML length:', galleryHTML.length); return galleryHTML; } + + // Generate gallery HTML for a provided subset of images + generatePropertyGalleryHTMLForImages(imagesSubset) { + if (!imagesSubset || imagesSubset.length === 0) { + return '
No images available
'; + } + let galleryHTML = ''; + imagesSubset.forEach((image, index) => { + const title = image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; + galleryHTML += `${title}`; + }); + return galleryHTML; + } + + // ===== TABLE DRAG AND DROP FUNCTIONALITY ===== // Handle table drag start handleTableDragStart(event) { - console.log('Starting table drag'); this.isDraggingTable = true; // Store table configuration data @@ -6839,31 +7758,49 @@ export default class PropertyTemplateSelector extends LightningElement { // Handle editor drag over handleEditorDragOver(event) { - if (this.isDraggingTable) { + // Allow dropping tables and images event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; - } } // Handle editor drop handleEditorDrop(event) { event.preventDefault(); + const editor = this.template.querySelector('.enhanced-editor-content'); + if (!editor) { this.showError("Editor not found"); return; } + + const dataType = event.dataTransfer.getData('text/plain'); + if (dataType === 'image' && this.currentImage) { + // Insert draggable image at drop + const img = document.createElement('img'); + img.src = this.currentImage.url; + img.style.maxWidth = '300px'; + img.style.height = 'auto'; + img.className = 'draggable-image'; + + const container = document.createElement('div'); + container.className = 'draggable-image-container'; + container.style.position = 'absolute'; + container.style.left = (event.clientX - editor.getBoundingClientRect().left) + 'px'; + container.style.top = (event.clientY - editor.getBoundingClientRect().top) + 'px'; + container.appendChild(img); + editor.appendChild(container); + this.makeImagesDraggableAndResizable([img]); + this.showSuccess('Image inserted via drag and drop!'); + return; + } + if (!this.isDraggingTable || !this.draggedTableData) { return; } - console.log('Dropping table at position:', event.clientX, event.clientY); // Remove visual feedback this.removeTableDragFeedback(); // Get drop position - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError("Editor not found"); - return; - } + // editor already resolved above // Save undo state before making changes this.saveUndoState(); @@ -6890,12 +7827,11 @@ export default class PropertyTemplateSelector extends LightningElement { // Create table element directly using DOM methods (same as insertTable) const tableId = `table-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - // Create container div + // Create container div (use draggable-table-container to get drag/resize behavior) const container = document.createElement('div'); - container.className = 'editable-table-container'; + container.className = 'draggable-table-container'; container.setAttribute('data-table-id', tableId); - container.setAttribute('draggable', 'true'); - container.style.cssText = 'border: 2px solid #4f46e5; margin: 20px 0; padding: 10px; background-color: #f8f9fa; position: relative;'; + container.style.cssText = 'position: absolute; left: 0; top: 0; width: 400px; min-width: 200px; min-height: 150px; z-index: 1000; border: 2px dashed #667eea; border-radius: 8px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;'; // Create table controls const controls = document.createElement('div'); @@ -7025,16 +7961,15 @@ export default class PropertyTemplateSelector extends LightningElement { } } - if (bestNode) { - // Insert after the closest text node - range.setStartAfter(bestNode); - range.insertNode(tableElement); - } else { - // Fallback: append to editor + // Insert into editor (append at end for simplicity) editor.appendChild(tableElement); - } + // Position at drop point + tableElement.style.left = Math.max(0, Math.min(x, editor.clientWidth - tableElement.offsetWidth)) + 'px'; + tableElement.style.top = Math.max(0, Math.min(y, editor.scrollHeight - tableElement.offsetHeight)) + 'px'; - // Add event listeners to the new table + // Add drag/resize to the new table + this.addTableResizeHandles(tableElement); + this.makeDraggable(tableElement); this.setupTableEventListeners(tableElement); } @@ -7166,7 +8101,6 @@ export default class PropertyTemplateSelector extends LightningElement { } const tableId = event.currentTarget.dataset.tableId; - console.log('Starting table container drag:', tableId); event.dataTransfer.setData('text/plain', 'table-container'); event.dataTransfer.setData('table-id', tableId); @@ -7300,7 +8234,6 @@ export default class PropertyTemplateSelector extends LightningElement { selection.addRange(range); }, 100); - console.log('Draggable text inserted'); } // Undo/Redo functionality @@ -7344,7 +8277,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Re-setup event handlers for any dynamic elements this.setupEditorEventHandlers(); - console.log('Undo performed'); } redo() { @@ -7367,7 +8299,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Re-setup event handlers for any dynamic elements this.setupEditorEventHandlers(); - console.log('Redo performed'); } // Setup editor event handlers after undo/redo @@ -7398,7 +8329,6 @@ export default class PropertyTemplateSelector extends LightningElement { }); if (hasImages) { - console.log(`Found images in category: ${category}`); return category; } } @@ -7412,7 +8342,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Only run if initial category selection hasn't been done yet if (!this.initialCategorySelected && this.realPropertyImages && this.realPropertyImages.length > 0) { const firstAvailableCategory = this.findFirstAvailableCategory(); - console.log('Initial smart category selection:', firstAvailableCategory); this.selectedCategory = firstAvailableCategory; this.filterImagesByCategory(firstAvailableCategory); this.initialCategorySelected = true; @@ -7685,6 +8614,51 @@ export default class PropertyTemplateSelector extends LightningElement { } else if (element.classList.contains('draggable-table-container')) { this.addTableResizeHandles(element); this.addDeleteButton(element); + } else if (element.tagName && element.tagName.toLowerCase() === 'img') { + // If already wrapped, ensure handles on container + if (element.parentElement && element.parentElement.classList && element.parentElement.classList.contains('draggable-image-container')) { + const container = element.parentElement; + this.addResizeHandles(container); + this.makeDraggable(container); + this.addDeleteButton(container); + this.highlightSelectedElement(container); + return; + } + // Wrap plain image and add handles; preserve on-screen size and position + const editor = this.template.querySelector('.enhanced-editor-content'); + const rect = element.getBoundingClientRect(); + const editorRect = editor ? editor.getBoundingClientRect() : { left: 0, top: 0 }; + const currentWidth = element.offsetWidth; + const currentHeight = element.offsetHeight; + + const container = document.createElement('div'); + container.className = 'draggable-image-container'; + container.style.position = 'absolute'; + container.style.left = (rect.left - editorRect.left + (editor ? editor.scrollLeft : 0)) + 'px'; + container.style.top = (rect.top - editorRect.top + (editor ? editor.scrollTop : 0)) + 'px'; + container.style.zIndex = window.getComputedStyle(element).zIndex || 'auto'; + container.style.display = 'inline-block'; + + // Move the image into container and preserve size + // Set container and image sizing + container.style.width = currentWidth + 'px'; + container.style.height = currentHeight + 'px'; + element.style.width = '100%'; + element.style.height = '100%'; + element.style.display = 'block'; + element.style.objectFit = 'cover'; + + if (editor) { + editor.appendChild(container); + } else { + element.parentNode.insertBefore(container, element); + } + container.appendChild(element); + container.classList.add('no-frame'); + this.addResizeHandles(container); + this.makeDraggable(container); + this.addDeleteButton(container); + this.highlightSelectedElement(container); } } @@ -7739,7 +8713,7 @@ export default class PropertyTemplateSelector extends LightningElement { const positions = ['nw', 'ne', 'sw', 'se']; positions.forEach(pos => { const handle = document.createElement('div'); - handle.className = 'resize-handle'; + handle.className = `resize-handle resize-${pos}`; handle.dataset.position = pos; handle.style.cssText = ` position: absolute; @@ -7771,6 +8745,13 @@ export default class PropertyTemplateSelector extends LightningElement { break; } + // Enable resizing using shared startResize + handle.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.startResize(e, tableContainer, pos); + }); + tableContainer.appendChild(handle); }); } @@ -7783,19 +8764,10 @@ export default class PropertyTemplateSelector extends LightningElement { // Debug method to log current state logCurrentState() { - console.log('=== CURRENT STATE ==='); - console.log('selectedImageUrl:', this.selectedImageUrl); - console.log('selectedImageName:', this.selectedImageName); - console.log('uploadedImageData:', this.uploadedImageData); - console.log('insertButtonDisabled:', this.insertButtonDisabled); - console.log('imageSource:', this.imageSource); - console.log('propertyImages length:', this.propertyImages ? this.propertyImages.length : 0); - console.log('===================='); } // Test method to manually set an image (for debugging) testSetImage() { - console.log('Testing manual image set'); this.selectedImageUrl = 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; this.selectedImageName = 'Test Image'; this.insertButtonDisabled = false; @@ -7805,4 +8777,59 @@ export default class PropertyTemplateSelector extends LightningElement { connectedCallback() { this.loadSavedTemplates(); } + + // Helper method for generating amenities HTML + generateAmenitiesHTML(data) { + const amenities = [ + { key: 'Swimming Pool', icon: 'fa-swimming-pool', value: data.Swimming_Pool__c || 'Yes' }, + { key: 'Gym', icon: 'fa-dumbbell', value: data.Gym__c || 'Yes' }, + { key: 'Parking', icon: 'fa-car', value: data.Parking_Spaces__c || '2' }, + { key: 'Garden', icon: 'fa-tree', value: data.Garden__c || 'Yes' }, + { key: 'Security', icon: 'fa-shield-alt', value: data.Security__c || '24/7' }, + { key: 'Balcony', icon: 'fa-home', value: data.Balcony__c || 'Yes' }, + { key: 'Air Conditioning', icon: 'fa-snowflake', value: data.AC__c || 'Central' }, + { key: 'WiFi', icon: 'fa-wifi', value: data.WiFi__c || 'Included' } + ]; + + return amenities.map(amenity => ` +
+ + ${amenity.key}: ${amenity.value} +
+ `).join(''); + } + + // Helper method for generating property gallery HTML + generatePropertyGalleryHTML() { + const images = this.realPropertyImages || []; + if (images.length === 0) { + const exteriorImage = this.getExteriorImageUrl(); + return ` + + + + `; + } + + return images.slice(0, 6).map(image => ` + + `).join(''); + } + + // Helper method for getting smart images for sections + getSmartImageForSection(section, fallback) { + const images = this.realPropertyImages || []; + const sectionImage = images.find(img => + img.category && img.category.toLowerCase().includes(section.toLowerCase()) + ); + return sectionImage ? sectionImage.url : fallback; + } } \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js.backup b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js.backup index 46e00bc..68019f1 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js.backup +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js.backup @@ -6,7 +6,7 @@ import getPropertyImages from '@salesforce/apex/PropertyDataController.getProper export default class PropertyTemplateSelector extends LightningElement { @track currentStep = 1; - @track htmlContent = ''; + htmlContent = ''; // Remove @track to prevent reactive updates // Lifecycle method - called when component is rendered renderedCallback() { @@ -35,7 +35,10 @@ export default class PropertyTemplateSelector extends LightningElement { @track pageCount = 0; @track progressMessage = ''; @track selectedPageSize = 'A4'; // Default page size + @track zoom = 1.0; // Step 3 viewport zoom @track isGeneratingPdf = false; // Loading state for PDF generation + @track previewPages = []; // Array of pages for viewport display + cachedTemplateContent = null; // Cache template content to prevent regeneration // Image review properties @track showImageReview = false; @@ -132,7 +135,10 @@ export default class PropertyTemplateSelector extends LightningElement { @track isDraggingTable = false; @track draggedTableData = null; @track selectorMode = false; + @track showDownloadModal = false; + @track downloadInfo = {}; @track selectedElement = null; + // z-index controls removed per request // Undo functionality @track undoStack = []; @@ -144,6 +150,8 @@ export default class PropertyTemplateSelector extends LightningElement { return this.replacementActiveTab === 'property' ? 'source-tab active' : 'source-tab'; } + // z-index functions removed + get localUploadTabClass() { return this.replacementActiveTab === 'upload' ? 'source-tab active' : 'source-tab'; } @@ -177,17 +185,12 @@ export default class PropertyTemplateSelector extends LightningElement { } get filteredPropertyImages() { - console.log('filteredPropertyImages called'); - console.log('propertyImages:', this.propertyImages); - console.log('selectedImageCategory:', this.selectedImageCategory); if (!this.propertyImages || this.propertyImages.length === 0) { - console.log('No property images available'); return []; } if (this.selectedImageCategory === 'all') { - console.log('Returning all images:', this.propertyImages); return this.propertyImages; } @@ -196,7 +199,6 @@ export default class PropertyTemplateSelector extends LightningElement { return category === this.selectedImageCategory; }); - console.log('Filtered images:', filtered); return filtered; } @@ -222,7 +224,6 @@ export default class PropertyTemplateSelector extends LightningElement { get isInsertButtonDisabled() { const disabled = !this.selectedImageUrl || this.selectedImageUrl === ''; - console.log('isInsertButtonDisabled check:', disabled, 'selectedImageUrl:', this.selectedImageUrl, 'renderKey:', this.renderKey); return disabled; } @@ -301,17 +302,76 @@ export default class PropertyTemplateSelector extends LightningElement { return this.currentStep === 3 ? 'step-content active' : 'step-content'; } - // Step navigation classes + // Step navigation classes (for header stepper circles only) get step1NavClass() { - return this.currentStep === 1 ? 'step-nav-item active' : 'step-nav-item'; + return this.currentStep === 1 ? 'active-circle active' : ''; } get step2NavClass() { - return this.currentStep === 2 ? 'step-nav-item active' : 'step-nav-item'; + return this.currentStep === 2 ? 'active-circle active' : ''; } get step3NavClass() { - return this.currentStep === 3 ? 'step-nav-item active' : 'step-nav-item'; + return this.currentStep === 3 ? 'active-circle active' : ''; + } + + // Inline styles to hard-force blue fill for active circles + get step1NavStyle() { + return this.currentStep === 1 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; + } + get step2NavStyle() { + return this.currentStep === 2 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; + } + get step3NavStyle() { + return this.currentStep === 3 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; + } + + originalTemplateGridHTML = null; + originalStylesText = null; + + renderedCallback() { + if (this.currentStep === 1) { + const gridLive = this.template.querySelector('#all-templates'); + if (gridLive) { + // Always keep a clean snapshot from the DOM the first time we hit step 1 in a render pass + if (this.originalTemplateGridHTML === null || this.originalTemplateGridHTML.length < 50) { + this.originalTemplateGridHTML = gridLive.innerHTML; + } + } + } + if (this.originalTemplateGridHTML === null) { + const grid = this.template.querySelector('#all-templates'); + if (grid) { + this.originalTemplateGridHTML = grid.innerHTML; + } + } + if (this.originalStylesText === null) { + const styles = this.template.querySelectorAll('style'); + this.originalStylesText = Array.from(styles).map(s => s.textContent); + } + } + + resetStep1Grid() { + const grid = this.template.querySelector('#all-templates'); + if (!grid) return; + // Clear any selected states on cards + const cards = grid.querySelectorAll('.template-card'); + cards.forEach(card => { + card.classList.remove('selected'); + }); + // DON'T clear selectedTemplateId - preserve the selection + // this.selectedTemplateId = ''; + } + + restoreComponentStyles() { + if (!this.originalStylesText) return; + const styles = this.template.querySelectorAll('style'); + const snapshots = this.originalStylesText; + styles.forEach((styleEl, idx) => { + if (snapshots[idx] !== undefined && styleEl.textContent !== snapshots[idx]) { + styleEl.textContent = snapshots[idx]; + } + }); } get isNextButtonDisabled() { @@ -335,81 +395,62 @@ export default class PropertyTemplateSelector extends LightningElement { // Template selection handler handleTemplateSelect(event) { const templateId = event.currentTarget.dataset.templateId; - console.log('=== TEMPLATE SELECTION DEBUG ==='); - console.log('Selected template ID:', templateId); - console.log('Event target:', event.currentTarget); - console.log('Dataset:', event.currentTarget.dataset); - console.log('BEFORE - selectedTemplateId:', this.selectedTemplateId); + + // Clear cached content when selecting a new template + this.cachedTemplateContent = null; // Set the selected template switch (templateId) { case 'blank-template': this.selectedTemplateId = 'blank-template'; - console.log('Set selectedTemplateId to blank-template'); break; case 'everkind-template': this.selectedTemplateId = 'everkind-template'; - console.log('Set selectedTemplateId to everkind-template'); break; case 'shift-template': this.selectedTemplateId = 'shift-template'; - console.log('Set selectedTemplateId to shift-template'); break; case 'saintbarts-template': this.selectedTemplateId = 'saintbarts-template'; - console.log('Set selectedTemplateId to saintbarts-template'); break; case 'learnoy-template': this.selectedTemplateId = 'learnoy-template'; - console.log('Set selectedTemplateId to learnoy-template'); break; case 'leafamp-template': this.selectedTemplateId = 'leafamp-template'; - console.log('Set selectedTemplateId to leafamp-template'); break; case 'coreshift-template': this.selectedTemplateId = 'coreshift-template'; - console.log('Set selectedTemplateId to coreshift-template'); break; case 'modern-home-template': this.selectedTemplateId = 'modern-home-template'; - console.log('Set selectedTemplateId to modern-home-template'); break; case 'asgar-1-template': this.selectedTemplateId = 'asgar-1-template'; - console.log('Set selectedTemplateId to asgar-1-template'); break; case 'serenity-house-template': this.selectedTemplateId = 'serenity-house-template'; - console.log('Set selectedTemplateId to serenity-house-template'); break; case 'sample-template': this.selectedTemplateId = 'sample-template'; - console.log('Set selectedTemplateId to sample-template'); break; case 'luxury-mansion-template': this.selectedTemplateId = 'luxury-mansion-template'; - console.log('Set selectedTemplateId to luxury-mansion-template'); break; default: - console.log('No matching case found for template ID:', templateId); break; } - console.log('AFTER switch - selectedTemplateId:', this.selectedTemplateId); - console.log('Final state - selectedTemplateId:', this.selectedTemplateId); - console.log('Final state - isModernHomeTemplateSelected:', this.isModernHomeTemplateSelected); - console.log('Final state - isAsgar1TemplateSelected:', this.isAsgar1TemplateSelected); - console.log('Final state - isSampleTemplateSelected:', this.isSampleTemplateSelected); - console.log('Final state - isLuxuryMansionTemplateSelected:', this.isLuxuryMansionTemplateSelected); - console.log('=== END TEMPLATE SELECTION DEBUG ==='); - // Force a re-render to ensure the UI updates + // Visually mark the selected template card with a black border + try { this.template.querySelectorAll('.template-card').forEach(card => { card.classList.remove('selected'); }); - if (event.currentTarget) { + if (event.currentTarget && event.currentTarget.classList) { event.currentTarget.classList.add('selected'); + } + } catch (e) { } } @@ -420,11 +461,116 @@ export default class PropertyTemplateSelector extends LightningElement { // Page size change handler handlePageSizeChange(event) { const newPageSize = event.target.value; - console.log('Page size changed to:', newPageSize); this.selectedPageSize = newPageSize; // Update the preview frame with new dimensions this.updatePreviewFrameSize(newPageSize); + + // Re-fit to width when page size changes + setTimeout(() => this.fitToWidth(), 0); + } + + // ===== Step 3 viewport zoom controls ===== + get zoomPercent() { + try { return `${Math.round((this.zoom || 1) * 100)}%`; } catch (e) { return '100%'; } + } + + get pdfCanvasStyle() { + const scale = this.zoom || 1; + return `transform: scale(${scale});`; + } + + zoomIn() { this.zoom = Math.min((this.zoom || 1) + 0.1, 3); } + zoomOut() { this.zoom = Math.max((this.zoom || 1) - 0.1, 0.3); } + resetZoom() { this.zoom = 1; } + + // Handler methods for HTML onclick events + handleZoomIn() { this.zoomIn(); } + handleZoomOut() { this.zoomOut(); } + handleZoom100() { this.resetZoom(); } + handleFitWidth() { this.fitToWidth(); } + handleFitPage() { this.fitToPage(); } + + // Page management methods + addNewPage() { + const newPage = { + id: `page-${Date.now()}`, + content: '

New page content...

' + }; + this.previewPages = [...this.previewPages, newPage]; + this.updatePageCount(); + } + + updatePageCount() { + const pageInfo = this.template?.querySelector('.page-info'); + if (pageInfo) { + const pageCount = this.previewPages.length || 1; + pageInfo.innerHTML = `${this.selectedPageSize} - ${pageCount} page${pageCount > 1 ? 's' : ''}`; + } + } + + // Split content into pages based on page breaks + splitContentIntoPages(content) { + if (!content) return []; + + // Look for page break markers or split by content length + const pageBreakMarkers = ['
', '
']; + let pages = []; + + // Check if content has explicit page breaks + let hasPageBreaks = false; + pageBreakMarkers.forEach(marker => { + if (content.includes(marker)) { + hasPageBreaks = true; + } + }); + + if (hasPageBreaks) { + // Split by page breaks + const pageContent = content.split(/]*page-break[^>]*><\/div>/i); + pages = pageContent.map((pageContent, index) => ({ + id: `page-${index + 1}`, + content: pageContent.trim() || '

Empty page

' + })); + } else { + // Single page for now - can be enhanced to auto-split based on content length + pages = [{ + id: 'page-1', + content: content + }]; + } + + return pages; + } + + // Update preview pages when content changes + updatePreviewPages() { + if (this.htmlContent) { + this.previewPages = this.splitContentIntoPages(this.htmlContent); + } else { + this.previewPages = []; + } + this.updatePageCount(); + } + + fitToWidth() { + const container = this.template?.querySelector('.pdf-viewport'); + if (!container) { return; } + const baseWidth = this.selectedPageSize === 'A3' ? 1123 : 794; + const available = Math.max((container.clientWidth || baseWidth) - 32, 100); + this.zoom = Math.max(Math.min(available / baseWidth, 4), 0.3); + } + + fitToPage() { + const container = this.template?.querySelector('.pdf-viewport'); + if (!container) { return; } + const baseWidth = this.selectedPageSize === 'A3' ? 1123 : 794; + const baseHeight = this.selectedPageSize === 'A3' ? 1587 : 1123; + const availableW = Math.max((container.clientWidth || baseWidth) - 32, 100); + const availableH = Math.max((container.clientHeight || baseHeight) - 32, 100); + const scaleW = availableW / baseWidth; + const scaleH = availableH / baseHeight; + this.zoom = Math.max(Math.min(Math.min(scaleW, scaleH), 4), 0.3); } // Update preview frame size based on selected page size @@ -434,7 +580,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Update the data attribute for the CSS content previewFrame.setAttribute('data-page-size', pageSize); - console.log('Preview frame updated for page size:', pageSize); // Re-render content with new page size this.renderContentInPages(pageSize); @@ -468,7 +613,6 @@ export default class PropertyTemplateSelector extends LightningElement { previewFrame.appendChild(pageContainer); }); - console.log(`Rendered ${pages.length} pages for ${pageSize} size`); } // Split HTML content into pages based on page size @@ -573,7 +717,6 @@ export default class PropertyTemplateSelector extends LightningElement { const pagesNeeded = Math.ceil(contentHeightMm / pageHeight); this.pageCount = Math.max(1, Math.min(pagesNeeded, 20)); // Limit to 1-20 pages - console.log(`Estimated ${this.pageCount} ${pageSize} pages based on content height`); } } @@ -586,6 +729,9 @@ export default class PropertyTemplateSelector extends LightningElement { // If moving to step 3, automatically load the template if (this.currentStep === 3) { this.loadTemplateInStep3(); + requestAnimationFrame(() => { + this.updatePreviewFrameSize(this.selectedPageSize || 'A4'); + }); } this.scrollToTop(); } @@ -596,18 +742,50 @@ export default class PropertyTemplateSelector extends LightningElement { this.currentStep--; // Reset click tracking when changing steps this.resetImageClickTracking(); + if (this.currentStep === 1) { + this.resetStep1Grid(); + } this.scrollToTop(); } } + goToStep(event) { const step = parseInt(event.currentTarget.dataset.step); this.currentStep = step; // Reset click tracking when changing steps this.resetImageClickTracking(); - // If going directly to step 3, load the template - if (this.currentStep === 3) { + if (this.currentStep === 1) { + this.resetStep1Grid(); + // Also reconstruct grid HTML from original snapshot if available to fully reset content + if (this.originalTemplateGridHTML && this.originalTemplateGridHTML.length > 50) { + const grid = this.template.querySelector('#all-templates'); + if (grid) { + grid.innerHTML = this.originalTemplateGridHTML; + } + } + // Restore any styles that could have been mutated during step 3 + this.restoreComponentStyles(); + // Rebind click handlers for fresh grid without page reload + requestAnimationFrame(() => { + const cards = this.template.querySelectorAll('#all-templates .template-card'); + cards.forEach(card => { + card.onclick = this.handleTemplateSelect.bind(this); + // Restore selected state if this card matches the selected template + if (this.selectedTemplateId && card.dataset.templateId === this.selectedTemplateId) { + card.classList.add('selected'); + } + }); + }); + } + // If going directly to step 3, load the template only if not already loaded + if (this.currentStep === 3 && (!this.htmlContent || this.htmlContent.trim() === '')) { this.loadTemplateInStep3(); + requestAnimationFrame(() => { + this.updatePreviewFrameSize(this.selectedPageSize || 'A4'); + // Auto fit width for better initial experience + this.fitToWidth && this.fitToWidth(); + }); } this.scrollToTop(); } @@ -624,62 +802,41 @@ export default class PropertyTemplateSelector extends LightningElement { async loadTemplateInStep3() { if (this.selectedTemplateId && this.selectedPropertyId) { try { - console.log('=== LOADING TEMPLATE IN STEP 3 ==='); - console.log('Template ID:', this.selectedTemplateId); - console.log('Property ID:', this.selectedPropertyId); - console.log('Property data available:', !!this.propertyData); - console.log('Real property images available:', this.realPropertyImages?.length || 0); + // Use cached content if available to prevent regeneration + if (this.cachedTemplateContent && this.cachedTemplateContent.templateId === this.selectedTemplateId && this.cachedTemplateContent.propertyId === this.selectedPropertyId) { + this.htmlContent = this.cachedTemplateContent.html; + this.updatePreviewPages(); + this.updatePreviewFrameSize(this.selectedPageSize); + setTimeout(() => { + this.updatePageCountForSize(this.selectedPageSize); + this.updatePageCount(); + this.fitToWidth && this.fitToWidth(); + }, 100); + return; + } // Ensure property images are loaded before creating template if (this.realPropertyImages.length === 0) { - console.log('No property images loaded, loading them now...'); await this.loadPropertyImages(); } const templateHTML = this.createTemplateHTML(); - console.log('Template HTML length:', templateHTML.length); - console.log('Template HTML preview:', templateHTML.substring(0, 500) + '...'); // Replace any hardcoded background-image URLs with property images const processedTemplateHTML = this.replaceBackgroundImagesInHTML(templateHTML); - console.log('Processed template HTML length:', processedTemplateHTML.length); + + // Cache the template content + this.cachedTemplateContent = { + templateId: this.selectedTemplateId, + propertyId: this.selectedPropertyId, + html: processedTemplateHTML + }; // Set the HTML content for the template binding this.htmlContent = processedTemplateHTML; - // Also find the enhanced editor content and load the template - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - // Clear any existing content - editorContent.innerHTML = ''; - - // Create a temporary container to parse the HTML - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = processedTemplateHTML; - - // Append the parsed content to the editor - while (tempDiv.firstChild) { - editorContent.appendChild(tempDiv.firstChild); - } - - console.log('Template loaded successfully into enhanced editor'); - - // Update any remaining CSS background-image rules - setTimeout(() => { - this.updateCSSBackgroundImages(); - }, 200); - - // Make images draggable and resizable - setTimeout(() => { - this.makeImagesDraggableAndResizable(); - }, 300); - - // Save initial state for undo functionality - setTimeout(() => { - this.saveUndoState(); - }, 100); - - // Images are now displayed without edit icons for clean presentation + // Update preview pages with the new content + this.updatePreviewPages(); // Set initial page size class and data attribute this.updatePreviewFrameSize(this.selectedPageSize); @@ -687,13 +844,11 @@ export default class PropertyTemplateSelector extends LightningElement { // Update page count after template is loaded setTimeout(() => { this.updatePageCountForSize(this.selectedPageSize); + this.updatePageCount(); // Update page count display + // After content settles, fit viewport to width + this.fitToWidth && this.fitToWidth(); }, 100); - - } else { - console.error('Editor content not found'); - } } catch (error) { - console.error('Error loading template in step 3:', error); this.error = 'Error loading template: ' + error.message; // Show error message in preview frame @@ -716,13 +871,16 @@ export default class PropertyTemplateSelector extends LightningElement { previewFrame.innerHTML = '

No Property Selected

Please go back to step 2 and select a property.

'; } } - console.log('Template or property not selected, cannot load template'); } } // Property selection handler handlePropertySelection(event) { this.selectedPropertyId = event.target.value; + + // Clear cached content when selecting a new property + this.cachedTemplateContent = null; + if (this.selectedPropertyId) { this.loadPropertyData(); // Auto-scroll to property preview section after a short delay to ensure data is loaded @@ -771,7 +929,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Handle direct field access return property[fieldPath] || defaultValue; } catch (error) { - console.warn(`Error accessing field ${fieldPath}:`, error); return defaultValue; } } @@ -780,8 +937,6 @@ export default class PropertyTemplateSelector extends LightningElement { async loadPropertyData() { const selectedProperty = this.properties.find(prop => prop.Id === this.selectedPropertyId); if (selectedProperty) { - console.log('=== LOADING PROPERTY DATA ==='); - console.log('Selected property:', selectedProperty); // Set the selectedProperty for use in PDF generation this.selectedProperty = selectedProperty; @@ -791,7 +946,7 @@ export default class PropertyTemplateSelector extends LightningElement { this.propertyData = { // Basic Information - propertyName: get('Name', 'Property Name'), + propertyName: (get('pcrm__Title_English__c') !== 'N/A' ? get('pcrm__Title_English__c') : (get('Title_English__c') !== 'N/A' ? get('Title_English__c') : get('Name', 'Property Name'))), propertyType: get('pcrm__Property_Type__c', 'Property Type'), status: get('pcrm__Status__c', 'Available'), referenceNumber: get('Name', 'REF-001'), @@ -892,8 +1047,6 @@ export default class PropertyTemplateSelector extends LightningElement { furnishing: get('pcrm__Furnished__c') }; - console.log('=== PROPERTY DATA MAPPED ==='); - console.log('Mapped property data:', this.propertyData); // Load property images await this.loadPropertyImages(); @@ -909,11 +1062,8 @@ export default class PropertyTemplateSelector extends LightningElement { } try { - console.log('=== LOADING PROPERTY IMAGES ==='); - console.log('Property ID:', this.selectedPropertyId); const images = await getPropertyImages({ propertyId: this.selectedPropertyId }); - console.log('Loaded images:', images); // Transform the data to match expected format this.realPropertyImages = images.map(img => ({ @@ -924,12 +1074,10 @@ export default class PropertyTemplateSelector extends LightningElement { url: img.url })); - console.log('Real property images loaded:', this.realPropertyImages); if (this.realPropertyImages && this.realPropertyImages.length > 0) { // Find the first category that has images const firstAvailableCategory = this.findFirstAvailableCategory(); - console.log('Loading images, auto-selecting first available category:', firstAvailableCategory); this.filterImagesByCategory(firstAvailableCategory); this.selectedCategory = firstAvailableCategory; this.initialCategorySelected = true; @@ -947,7 +1095,6 @@ export default class PropertyTemplateSelector extends LightningElement { } } catch (error) { - console.error('Error loading property images:', error); this.realPropertyImages = []; } } @@ -975,11 +1122,9 @@ export default class PropertyTemplateSelector extends LightningElement { if (editorFrame && editorFrame.innerHTML) { htmlContent = editorFrame.innerHTML; - console.log('Using HTML content from editor, length:', htmlContent.length); } else { // Fallback: generate template HTML if editor is empty htmlContent = this.createCompleteTemplateHTML(); - console.log('Generated fallback HTML content, length:', htmlContent.length); } // Generate PDF using the template HTML @@ -992,14 +1137,12 @@ export default class PropertyTemplateSelector extends LightningElement { if (result.success) { // Handle successful PDF generation - console.log('PDF generated successfully:', result.pdfUrl); this.showSuccess('PDF generated successfully!'); // You can add logic here to display the PDF or provide download link } else { this.error = result.message || result.error || 'Failed to generate PDF.'; } } catch (error) { - console.error('Error in generateTemplateContent:', error); this.error = 'Error generating PDF: ' + (error.body?.message || error.message || 'Unknown error'); } finally { this.isLoading = false; @@ -1024,12 +1167,9 @@ export default class PropertyTemplateSelector extends LightningElement { generatePdfButton.style.boxShadow = ''; }, 2000); - console.log('Scrolled to Generate PDF button successfully'); } else { - console.warn('Generate PDF button not found'); } } catch (error) { - console.error('Error scrolling to Generate PDF button:', error); } } @@ -1041,7 +1181,6 @@ export default class PropertyTemplateSelector extends LightningElement { } try { - console.log('=== GENERATING PDF VIA EXTERNAL API ==='); // Set loading state this.isGeneratingPdf = true; @@ -1063,8 +1202,6 @@ export default class PropertyTemplateSelector extends LightningElement { // Success message is handled in generatePdfViaExternalApi } catch (error) { - console.error('=== PDF GENERATION ERROR ==='); - console.error('Error generating PDF:', error); // Provide more user-friendly error messages let errorMessage = 'PDF generation failed. '; @@ -1085,10 +1222,266 @@ export default class PropertyTemplateSelector extends LightningElement { } } + // Clean HTML content for PDF generation by removing editor controls + cleanHtmlForPdf(htmlContent) { + return htmlContent; + + // Remove only specific editor control elements, NOT the content containers + const elementsToRemove = [ + // Only remove actual control elements, not content containers + '.resize-handle', + '.delete-handle', + '.delete-image-btn', + '.table-controls-overlay', + '.quill-toolbar', + '.quill-editor', + '.editor-toolbar', + '.editor-controls', + '.selection-handle', + '.outline-border', + '.editor-outline' + ]; + + // Remove only the control elements + elementsToRemove.forEach(selector => { + const elements = tempDiv.querySelectorAll(selector); + elements.forEach(element => { + element.remove(); + }); + }); + + // Clean up draggable containers but preserve their content + const draggableContainers = tempDiv.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); + draggableContainers.forEach(container => { + // Remove selection classes and editor-specific attributes + container.classList.remove('selected'); + container.removeAttribute('draggable'); + container.removeAttribute('contenteditable'); + + // Remove editor-specific styles but keep positioning and sizing + const stylesToRemove = [ + 'outline', + 'outline-offset', + 'cursor', + 'user-select', + 'pointer-events', + 'z-index', + 'border', + 'border-style', + 'border-color', + 'border-width', + 'border-dasharray', + 'border-dashoffset' + ]; + + stylesToRemove.forEach(style => { + container.style.removeProperty(style); + }); + + // For image containers, ensure the image is properly displayed + if (container.classList.contains('draggable-image-container')) { + const img = container.querySelector('img'); + if (img) { + // Remove any editor-specific attributes from the image + img.removeAttribute('draggable'); + img.removeAttribute('contenteditable'); + img.style.cursor = 'default'; + img.style.userSelect = 'none'; + } + } + + // For text containers, remove any dashed borders or editor styling + if (container.classList.contains('draggable-element') && container.classList.contains('draggable-text')) { + // Remove any dashed border styling + container.style.border = 'none'; + container.style.borderStyle = 'none'; + container.style.borderColor = 'transparent'; + container.style.borderWidth = '0'; + container.style.outline = 'none'; + container.style.outlineStyle = 'none'; + + // Ensure text is visible and properly styled + container.style.color = '#333333'; + container.style.backgroundColor = 'transparent'; + } + + // For table containers, ensure the table is properly displayed + if (container.classList.contains('draggable-table-container')) { + const table = container.querySelector('table'); + if (table) { + // Remove any editor-specific attributes from the table + table.removeAttribute('draggable'); + table.removeAttribute('contenteditable'); + table.style.cursor = 'default'; + table.style.userSelect = 'none'; + } + } + }); + + // Ensure list styling is preserved in output + const lists = tempDiv.querySelectorAll('ul, ol'); + lists.forEach(list => { + if (list.tagName.toLowerCase() === 'ul') { + list.style.listStyleType = 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + } else { + list.style.listStyleType = 'decimal'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + } + }); + + // Handle
-separated bullet/number lines inside a single block + const brBlocks = tempDiv.querySelectorAll('p, div'); + brBlocks.forEach(block => { + if (block.closest('ul,ol')) return; + const html = block.innerHTML || ''; + if (!/br\s*\/?/i.test(html)) return; + const parts = html.split(/(?:\s*)/i).map(s => s.trim()).filter(Boolean); + if (parts.length < 2) return; + const bulletMarker = /^\s*(?: \s*)*(\*|\-|β€’)\s+/i; + const numberMarker = /^\s*(?: \s*)*\d+[\.)]\s+/i; + const allBullets = parts.every(p => bulletMarker.test(p.replace(/<[^>]+>/g, ''))); + const allNumbers = parts.every(p => numberMarker.test(p.replace(/<[^>]+>/g, ''))); + if (!(allBullets || allNumbers)) return; + const list = document.createElement(allNumbers ? 'ol' : 'ul'); + list.style.listStyleType = allNumbers ? 'decimal' : 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + list.style.breakInside = 'avoid'; + list.style.pageBreakInside = 'avoid'; + parts.forEach(line => { + const li = document.createElement('li'); + li.innerHTML = line.replace(/^\s*(?: \s*)*(\*|\-|β€’|\d+[\.)])\s+/i, ''); + li.style.breakInside = 'avoid'; + li.style.pageBreakInside = 'avoid'; + list.appendChild(li); + }); + block.replaceWith(list); + }); + + // Handle inline asterisk-separated or dot-number-separated items within a single paragraph + const textBlocks = tempDiv.querySelectorAll('p, div'); + textBlocks.forEach(block => { + if (block.closest('ul,ol')) return; + const text = (block.textContent || '').trim(); + if (!text) return; + // Detect at least two bullet markers like " * item" or " β€’ item" + const bulletSeqMatches = text.match(/(?:^|\s)[*β€’\-]\s+\S+/g); + const numberSeqMatches = text.match(/(?:^|\s)\d+[\.)]\s+\S+/g); + const hasBulletRun = bulletSeqMatches && bulletSeqMatches.length >= 2; + const hasNumberRun = !hasBulletRun && numberSeqMatches && numberSeqMatches.length >= 2; + if (!(hasBulletRun || hasNumberRun)) return; + + // Split text into prefix + items + const firstMarkerIndex = text.search(hasBulletRun ? /(?:^|\s)[*β€’\-]\s+\S+/ : /(?:^|\s)\d+[\.)]\s+\S+/); + if (firstMarkerIndex < 0) return; + const prefix = text.slice(0, firstMarkerIndex).trim(); + const run = text.slice(firstMarkerIndex).trim(); + const items = run.split(hasBulletRun ? /\s[*β€’\-]\s+/g : /\s\d+[\.)]\s+/g).filter(Boolean); + if (items.length < 2) return; + + const frag = document.createDocumentFragment(); + if (prefix) { + const p = document.createElement('p'); + p.textContent = prefix; + frag.appendChild(p); + } + const list = document.createElement(hasNumberRun ? 'ol' : 'ul'); + list.style.listStyleType = hasNumberRun ? 'decimal' : 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + list.style.breakInside = 'avoid'; + list.style.pageBreakInside = 'avoid'; + items.forEach(item => { + const li = document.createElement('li'); + li.textContent = item.trim(); + li.style.breakInside = 'avoid'; + li.style.pageBreakInside = 'avoid'; + list.appendChild(li); + }); + frag.appendChild(list); + block.replaceWith(frag); + }); + + // Convert text marker lines ("* ", "1. ") to real UL/OL so saved HTML matches preview + const bulletMarker = /^\s*(?: \s*)*(\*|\-|β€’)\s+/i; + const numberMarker = /^\s*(?: \s*)*\d+[\.)]\s+/i; + const stripMarker = (html) => html.replace(/^\s*(?: \s*)*(\*|\-|β€’|\d+[\.)])\s+/i, ''); + + const processContainer = (container) => { + const children = Array.from(container.children); + for (let i = 0; i < children.length; i++) { + const el = children[i]; + // Recurse first + if (el.children && el.children.length) processContainer(el); + if (el.tagName && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div')) { + const text = (el.textContent || '').trim(); + const isBulletStart = bulletMarker.test(text); + const isNumberStart = numberMarker.test(text); + if (isBulletStart || isNumberStart) { + const list = document.createElement(isNumberStart ? 'ol' : 'ul'); + list.style.listStyleType = isNumberStart ? 'decimal' : 'disc'; + list.style.paddingLeft = '22px'; + list.style.margin = '0 0 8px 0'; + const startIndex = i; + // Collect consecutive items + while (i < children.length) { + const cand = children[i]; + if (!cand || !(cand.tagName && (cand.tagName.toLowerCase() === 'p' || cand.tagName.toLowerCase() === 'div'))) break; + const candText = (cand.textContent || '').trim(); + if ((isNumberStart && numberMarker.test(candText)) || (!isNumberStart && bulletMarker.test(candText))) { + const li = document.createElement('li'); + // Preserve inline formatting by using innerHTML and stripping marker + li.innerHTML = stripMarker(cand.innerHTML || candText); + list.appendChild(li); + i++; + } else { + break; + } + } + // Insert list and remove original items + const first = children[startIndex]; + first.parentNode.insertBefore(list, first); + for (let j = startIndex; j < i; j++) { + if (children[j] && children[j].parentNode) children[j].parentNode.removeChild(children[j]); + } + // Reset children and position after replacement + return processContainer(container); + } + } + } + }; + processContainer(tempDiv); + + // Clean up any remaining editor-specific styles on all elements + const allElements = tempDiv.querySelectorAll('*'); + allElements.forEach(element => { + if (element.style) { + // Remove only editor-related styles, keep content styles + const editorStyles = [ + 'outline', + 'outline-offset', + 'cursor', + 'user-select', + 'pointer-events' + ]; + + editorStyles.forEach(style => { + if (element.style[style]) { + element.style.removeProperty(style); + } + }); + } + }); + + return tempDiv.innerHTML; + } + // Generate PDF via external API using Apex proxy async generatePdfViaExternalApi() { try { - console.log('Calling external PDF generation API via Apex proxy...'); // Show loading state this.isLoading = true; @@ -1104,23 +1497,25 @@ export default class PropertyTemplateSelector extends LightningElement { } htmlContent = previewFrame.innerHTML; - console.log('Initial HTML content from preview frame:', htmlContent); - console.log('HTML content length:', htmlContent.length, 'characters'); + + // Debug: Check if draggable elements are present + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = htmlContent; + const draggableElements = tempDiv.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); + draggableElements.forEach((el, index) => { + }); // If preview frame is empty, generate the template HTML first if (!htmlContent || htmlContent.trim() === '' || htmlContent.length < 100) { - console.log('Preview frame is empty or has minimal content, generating template HTML...'); this.showProgress('Generating template content...'); // Generate the template HTML using the selected template and property if (this.selectedTemplateId && this.selectedPropertyId) { // Create a complete HTML template with property data htmlContent = this.createCompleteTemplateHTML(); - console.log('Generated template HTML length:', htmlContent.length, 'characters'); // Load it into the preview frame so user can see it previewFrame.innerHTML = htmlContent; - console.log('Template HTML loaded into preview frame'); } else { throw new Error('No template or property selected'); } @@ -1134,6 +1529,7 @@ export default class PropertyTemplateSelector extends LightningElement { Property Brochure - ${this.selectedPageSize} + + +
@@ -1350,13 +1792,13 @@ export default class PropertyTemplateSelector extends LightningElement {

${new Date().toLocaleDateString()}

+ + `; - console.log('Complete template HTML generated successfully'); return html; } catch (error) { - console.error('Error creating template HTML:', error); // Return a fallback HTML if there's an error return `
@@ -1373,7 +1815,6 @@ export default class PropertyTemplateSelector extends LightningElement { showProgress(message) { this.isLoading = true; this.progressMessage = message; - console.log('Progress:', message); } hideProgress() { @@ -1384,19 +1825,16 @@ export default class PropertyTemplateSelector extends LightningElement { showSuccess(message) { this.progressMessage = message; this.error = ''; - console.log('Success:', message); } showError(message) { this.error = message; this.progressMessage = ''; - console.error('Error:', message); } // Handle PDF response using the working approach from first prompt handlePdfResponse(result) { try { - console.log('Handling PDF response:', result); if (result.pdfUrl) { // For the working approach, we need to create a proper download @@ -1405,7 +1843,6 @@ export default class PropertyTemplateSelector extends LightningElement { this.error = 'PDF generated but no download URL received'; } } catch (error) { - console.error('Error handling PDF response:', error); this.error = 'Error handling PDF response: ' + error.message; } } @@ -1424,7 +1861,6 @@ export default class PropertyTemplateSelector extends LightningElement { document.body.removeChild(link); this.progressMessage = 'PDF download started!'; - console.log('PDF download initiated:', pdfUrl); // Fallback: If download doesn't work, show instructions setTimeout(() => { @@ -1444,7 +1880,6 @@ export default class PropertyTemplateSelector extends LightningElement { } }, 2000); } catch (error) { - console.error('Error downloading PDF:', error); this.error = 'Error downloading PDF: ' + error.message; } } @@ -1453,7 +1888,6 @@ export default class PropertyTemplateSelector extends LightningElement { async handleLargePDFResponse(decodedResponse) { try { const responseData = JSON.parse(decodedResponse); - console.log('Handling large PDF response:', responseData); this.hideProgress(); this.isLoading = false; @@ -1463,7 +1897,6 @@ export default class PropertyTemplateSelector extends LightningElement { this.showLargePDFOptions(responseData); } } catch (error) { - console.error('Error handling large PDF response:', error); this.showError('Error handling large PDF response: ' + error.message); } } @@ -1546,7 +1979,6 @@ export default class PropertyTemplateSelector extends LightningElement { } } catch (error) { - console.error('Error generating compressed PDF:', error); this.showError('Failed to generate compressed PDF: ' + error.message); } } @@ -1554,141 +1986,103 @@ export default class PropertyTemplateSelector extends LightningElement { // Handle PDF download ready response async handlePDFDownloadReady(pdfResult) { try { - console.log("Handling PDF download ready:", pdfResult); // Hide loading state this.isLoading = false; this.hideProgress(); - // Create download message based on compression level - let message = ""; + // Set the download info for the modal + this.downloadInfo = { + filename: pdfResult.filename || "Unknown", + fileSize: pdfResult.file_size_mb ? pdfResult.file_size_mb + " MB" : "Unknown", + generatedAt: this.formatDate(pdfResult.generated_at), + expiresAt: this.formatDate(pdfResult.expires_at), + downloadUrl: pdfResult.download_url + }; + // Automatically open download URL in new tab + window.open(pdfResult.download_url, "_blank"); - if (pdfResult.status === "compressed_download_ready") { - message = ` -
-

πŸ“¦ Compressed PDF Ready!

-

PDF ID: ${pdfResult.pdf_id}

-

Page Size: ${pdfResult.page_size}

-

Generated: ${pdfResult.generated_at}

-

This is a compressed version optimized for smaller file size.

- -
- -
- -
-

πŸ’‘ About Compressed PDFs

-
    -
  • Images are optimized for smaller file size
  • -
  • All content is preserved but with reduced quality
  • -
  • Perfect for email sharing and quick downloads
  • -
-
-
- `; - } else { - message = ` -
-

πŸ“„ PDF Ready for Download!

-

PDF ID: ${pdfResult.pdf_id}

-

Page Size: ${pdfResult.page_size}

-

Generated: ${pdfResult.generated_at}

-

Your PDF has been generated successfully and is ready for download.

- -
- -
- -
-

βœ… Download Instructions

-
    -
  • Click the download button above
  • -
  • Your PDF will open in a new tab
  • -
  • Use your browser's save function to download
  • -
-
-
- `; - } - - this.showSuccess(message); - - // Also provide a direct download link as fallback - setTimeout(() => { - if (this.template.querySelector('.success-message')) { - const successMsg = this.template.querySelector('.success-message'); - - // Add a fallback download link - const fallbackLink = document.createElement('a'); - fallbackLink.href = pdfResult.download_url; - fallbackLink.textContent = 'πŸ”— Direct Download Link'; - fallbackLink.className = 'slds-button slds-button_neutral'; - fallbackLink.style.marginTop = '10px'; - fallbackLink.style.display = 'inline-block'; - fallbackLink.target = '_blank'; - - successMsg.appendChild(fallbackLink); - } - }, 1000); + // Show simple success message + this.showSuccess(`βœ… PDF generated successfully! File: ${pdfResult.filename} (${pdfResult.file_size_mb} MB) - Download opened in new tab.`); + this.showSuccess(`βœ… PDF generated successfully! File: ${pdfResult.filename} (${pdfResult.file_size_mb} MB)`); } catch (error) { - console.error('Error handling PDF download ready:', error); - this.showError('Error handling PDF download: ' + error.message); + this.showError("Error handling PDF download: " + error.message); } } + // Modal control methods + closeDownloadModal() { + this.showDownloadModal = false; + } + + stopPropagation(event) { + event.stopPropagation(); + } + + copyDownloadLink() { + if (navigator.clipboard && this.downloadInfo) { + navigator.clipboard.writeText(this.downloadInfo.downloadUrl).then(() => { + // Show feedback + const copyBtn = this.template.querySelector(".copy-btn"); + if (copyBtn) { + const originalText = copyBtn.textContent; + copyBtn.textContent = "βœ… Copied!"; + copyBtn.classList.add("copied"); + + setTimeout(() => { + copyBtn.textContent = originalText; + copyBtn.classList.remove("copied"); + }, 2000); + } + }).catch(err => { + alert("Failed to copy link to clipboard"); + }); + } + } + + openInNewTab() { + if (this.downloadInfo && this.downloadInfo.downloadUrl) { + window.open(this.downloadInfo.downloadUrl, "_blank"); + } + } + + // Helper method to format dates + formatDate(dateString) { + if (!dateString) return "Unknown"; + const date = new Date(dateString); + return date.toLocaleString(); + } // Create template HTML based on selection createTemplateHTML() { - console.log('=== CREATE TEMPLATE HTML DEBUG ==='); - console.log('this.selectedTemplateId:', this.selectedTemplateId); - console.log('typeof this.selectedTemplateId:', typeof this.selectedTemplateId); - console.log('this.propertyData:', this.propertyData); - console.log('this.realPropertyImages:', this.realPropertyImages); switch (this.selectedTemplateId) { case 'blank-template': - console.log('Returning blank template'); return this.createBlankTemplate(); case 'everkind-template': - console.log('Returning everkind template'); return this.createEverkindTemplate(); case 'shift-template': - console.log('Returning shift template'); return this.createShiftTemplate(); case 'saintbarts-template': - console.log('Returning saintbarts template'); return this.createSaintbartsTemplate(); case 'learnoy-template': - console.log('Returning learnoy template'); return this.createLearnoyTemplate(); case 'leafamp-template': - console.log('Returning leafamp template'); return this.createLeafampTemplate(); case 'coreshift-template': - console.log('Returning coreshift template'); return this.createCoreshiftTemplate(); case 'modern-home-template': - console.log('Returning modern home template'); return this.createModernHomeTemplate(); case 'asgar-1-template': - console.log('Returning asgar1 template'); - return this.createAsgar1Template(); + // Grand Oak Villa (black theme with gold accents) + return this.createGrandOakVillaTemplate(); case 'sample-template': - console.log('Returning sample template'); return this.createSampleTemplate(); case 'serenity-house-template': - console.log('Returning serenity house template'); return this.createSerenityHouseTemplate(); case 'luxury-mansion-template': - console.log('Returning luxury mansion template'); return this.createLuxuryMansionTemplate(); default: - console.log('No matching case found, returning blank template'); - console.log('Available cases: blank-template, everkind-template, shift-template, saintbarts-template, learnoy-template, leafamp-template, coreshift-template, modern-home-template, asgar-1-template, sample-template, luxury-mansion-template'); return this.createBlankTemplate(); } } @@ -1899,15 +2293,12 @@ export default class PropertyTemplateSelector extends LightningElement { } createShiftTemplate() { - console.log('=== CREATE SHIFT TEMPLATE CALLED ==='); - console.log('Property data:', this.propertyData); - console.log('Real property images:', this.realPropertyImages); const data = this.propertyData || {}; const propertyName = data.Name || data.propertyName || "SHIFT PROPERTY"; const location = data.Address__c || data.location || "Modern Living"; const price = data.Price__c || data.price || "Starting from $1,500,000"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "SP-2025-001"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const size = data.Square_Feet__c || data.size || "N/A"; @@ -1916,17 +2307,37 @@ export default class PropertyTemplateSelector extends LightningElement { // Get smart images - use direct method for exterior const exteriorImage = this.getExteriorImageUrl(); + // Gallery HTML generated once in each template scope when needed + const allGalleryImages = this.realPropertyImages || []; + const firstGalleryCount = Math.min(4, allGalleryImages.length); + const firstPageImages = allGalleryImages.slice(0, firstGalleryCount); + let additionalGalleryPagesHTML = ''; + if (allGalleryImages.length > firstGalleryCount) { + const remainingImages = allGalleryImages.slice(firstGalleryCount); + const imagesPerPage = 8; + for (let start = 0; start < remainingImages.length; start += imagesPerPage) { + const chunk = remainingImages.slice(start, start + imagesPerPage); + additionalGalleryPagesHTML += ` +
+
+ +
+ +
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}
+
`; + } + } + const propertyGallery = this.generatePropertyGalleryHTML(); const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); const kitchenImage = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); // Debug logging - console.log('=== SHIFT TEMPLATE DEBUG ==='); - console.log('Exterior image URL:', exteriorImage); - console.log('Interior image URL:', interiorImage); - console.log('Kitchen image URL:', kitchenImage); - // Generate property gallery for uncategorized images - const propertyGallery = this.generatePropertyGalleryHTML(); + // Generate property gallery for uncategorized images (already declared above) return `Shift Property - Modern Living
Property Exterior

${propertyName}

${location}

${price}

EXTERIOR IMAGE TEST: ${exteriorImage}

About Shift Property

Experience the future of living with Shift Property, where innovation meets comfort in perfect harmony.

Interior View

Modern Features

Innovation
Smart Design
Eco-Friendly
Connected Living
Kitchen View

Contact Information

Reference ID: ${referenceId}

Agent: ${data.agentName || "Innovation Specialist"}

Phone: ${data.agentPhone || "(555) 789-0123"}

${propertyGallery}
`; } @@ -1936,7 +2347,7 @@ export default class PropertyTemplateSelector extends LightningElement { const propertyName = data.Name || data.propertyName || "SAINT BARTS VILLA"; const location = data.Address__c || data.location || "Caribbean Paradise"; const price = data.Price__c || data.price || "Starting from $3,200,000"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "SB-2025-001"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const size = data.Square_Feet__c || data.size || "N/A"; @@ -1964,7 +2375,7 @@ export default class PropertyTemplateSelector extends LightningElement { const size = data.Square_Feet__c || data.size || "N/A"; const propertyType = data.Property_Type__c || data.propertyType || "N/A"; const description = data.Description_English__c || data.descriptionEnglish || "Property description not available."; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; // Get smart images - use direct method for exterior const exteriorImage = this.getExteriorImageUrl(); @@ -1985,7 +2396,7 @@ export default class PropertyTemplateSelector extends LightningElement { const propertyName = data.Name || data.propertyName || "LEAFAMP URBAN"; const location = data.Address__c || data.location || "City Living Experience"; const price = data.Price__c || data.price || "Starting from $1,200,000"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "LF-2025-001"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const size = data.Square_Feet__c || data.size || "N/A"; @@ -1999,8 +2410,6 @@ export default class PropertyTemplateSelector extends LightningElement { const mapsImage = this.getMapsImageUrl(); // Debug logging for maps image - console.log('=== LEAFAMP TEMPLATE MAPS DEBUG ==='); - console.log('Maps image URL:', mapsImage); // Generate property gallery for uncategorized images const propertyGallery = this.generatePropertyGalleryHTML(); @@ -2013,7 +2422,6 @@ export default class PropertyTemplateSelector extends LightningElement { } createModernHomeTemplate() { - console.log("=== CREATING REAL ESTATE MODERN HOME TEMPLATE ==="); const data = this.propertyData || {}; const propertyName = data.Name || data.propertyName || "Property Name"; @@ -2024,7 +2432,7 @@ export default class PropertyTemplateSelector extends LightningElement { const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const area = data.Square_Feet__c || data.area || "N/A"; const description = data.Description_English__c || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; // Contact information const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; @@ -2052,7 +2460,7 @@ export default class PropertyTemplateSelector extends LightningElement { const propertyName = data.Name || data.propertyName || "Property Name"; const location = data.Address__c || data.location || "Location"; const price = data.Price__c || data.price || "Price"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; const area = data.Square_Feet__c || data.area || "N/A"; @@ -2138,7 +2546,629 @@ export default class PropertyTemplateSelector extends LightningElement { const minimumContract = data.Minimum_Contract__c || data.minimumContract || "N/A"; const securityDeposit = data.Security_Deposit__c || data.securityDeposit || "N/A"; - return `Prestige Real Estate Brochure - 4 Page
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${titleEnglish ? `Title: ${titleEnglish}` : ''}

${descriptionEnglish ? `Description: ${descriptionEnglish}` : ''}

${amenities ? `Amenities: ${amenities}` : ''}

${features ? `Features: ${features}` : ''}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}
Maintenance Fee: ${maintenanceFee}
Service Charge: ${serviceCharge}
Size: ${size}
Parking Spaces: ${parkingSpaces}
Build Year: ${buildYear}
Offering Type: ${offeringType}
Bedrooms: ${bedrooms}
Bathrooms: ${bathrooms}
Area: ${area} ${sizeUnit}
Price: ${price}
Rent Price (Min): ${rentPriceMin}
Sale Price (Min): ${salePriceMin}
Rent Available From: ${rentAvailableFrom}
Rent Available To: ${rentAvailableTo}
Available From: ${availableFrom}
Minimum Contract: ${minimumContract}
Security Deposit: ${securityDeposit}
Pet Friendly: ${petFriendly}
Smoking Allowed: ${smokingAllowed}

Amenities & Features

${amenitiesHTML}
Agent: ${data.agentName || "Agent Name"} | ${data.agentPhone || "Agent Phone"} | ${data.agentEmail || "Agent Email"}
Owner: ${ownerName} | ${ownerPhone} | ${ownerEmail}
Schools
${schools}
Shopping
${shoppingCenters}
Airport
${airportDistance}
Landmarks
${nearbyLandmarks}
Transportation
${transportation}
Hospitals
${hospitals}
Beach
${beachDistance}
Metro
${metroDistance}
Agent: ${data.agentName || "Agent Name"} | ${data.agentPhone || "Agent Phone"} | ${data.agentEmail || "Agent Email"}
Owner: ${ownerName} | ${ownerPhone} | ${ownerEmail}

Additional Information

Pet Friendly: ${petFriendly}
Smoking: ${smokingAllowed}
Available From: ${availableFrom}
Minimum Contract: ${minimumContract}
Security Deposit: ${securityDeposit}
Utilities Included: ${utilitiesIncluded}
Internet Included: ${internetIncluded}
Cable Included: ${cableIncluded}
Agent: ${data.agentName || "Agent Name"} | ${data.agentPhone || "Agent Phone"} | ${data.agentEmail || "Agent Email"}
Owner: ${ownerName} | ${ownerPhone} | ${ownerEmail}
Agent: ${data.agentName || "Agent Name"} | ${data.agentPhone || "Agent Phone"} | ${data.agentEmail || "Agent Email"}
Owner: ${ownerName} | ${ownerPhone} | ${ownerEmail}
`; + return `Prestige Real Estate Brochure - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}

Amenities & Features

    ${amenitiesHTML}
Agent: ${contactName} | ${contactPhone} | ${contactEmail}
Owner: ${ownerName} | ${ownerPhone}
`; + } + + createGrandOakVillaTemplate() { + const data = this.propertyData || {}; + + // Enhanced property data extraction with better fallbacks + const propertyName = data.Name || data.propertyName || data.pcrm__Title_English__c || "The Grand Oak Villa"; + const location = data.Address__c || data.location || "123 Luxury Lane, Prestige City, PC 45678"; + const price = data.Sale_Price_Min__c || data.Rent_Price_Min__c || data.Price__c || data.price || "$4,500,000"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "5"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "6"; + const squareFeet = data.Square_Feet__c || data.squareFeet || data.area || "6,200"; + const status = (data.Status__c || data.status || "FOR SALE").toString(); + + // Enhanced property details + const propertyType = data.Property_Type__c || data.propertyType || "Villa"; + const yearBuilt = data.Build_Year__c || data.yearBuilt || "2020"; + const furnishing = data.Furnished__c || data.furnishing || "Fully Furnished"; + const parking = data.Parking_Spaces__c || data.parking || "2"; + const description = data.Description_English__c || data.descriptionEnglish || data.description || "An exquisite villa offering unparalleled luxury and sophistication in one of the most prestigious locations."; + const floor = data.Floor__c || data.floor || "Ground Floor"; + const maintenanceFee = data.Maintenance_Fee__c || data.maintenanceFee || "N/A"; + const serviceCharge = data.Service_Charge__c || data.serviceCharge || "N/A"; + + // Location and POI data + const schools = data.Schools__c || data.schools || "5 min drive"; + const shoppingCenters = data.Shopping_Centers__c || data.shoppingCenters || "10 min drive"; + const airportDistance = data.Airport_Distance__c || data.airportDistance || "25 min drive"; + const nearbyLandmarks = data.Nearby_Landmarks__c || data.nearbyLandmarks || "City Center 15 min"; + const transportation = data.Transportation__c || data.transportation || "Metro 5 min walk"; + const hospitals = data.Hospitals__c || data.hospitals || "10 min drive"; + const beachDistance = data.Beach_Distance__c || data.beachDistance || "30 min drive"; + const metroDistance = data.Metro_Distance__c || data.metroDistance || "5 min walk"; + + // Additional information + const petFriendly = data.Pet_Friendly__c || data.petFriendly || "Yes"; + const smokingAllowed = data.Smoking_Allowed__c || data.smokingAllowed || "No"; + const availableFrom = data.Available_From__c || data.availableFrom || "Immediate"; + const minimumContract = data.Minimum_Contract__c || data.minimumContract || "12 months"; + const securityDeposit = data.Security_Deposit__c || data.securityDeposit || "2 months rent"; + const utilitiesIncluded = data.Utilities_Included__c || data.utilitiesIncluded || "Water, Electricity"; + const internetIncluded = data.Internet_Included__c || data.internetIncluded || "Yes"; + const cableIncluded = data.Cable_Included__c || data.cableIncluded || "Yes"; + + // Agent and owner information + const agentName = data.Agent_Name__c || data.agentName || "John Smith"; + const agentPhone = data.Agent_Phone__c || data.agentPhone || "+1 (555) 123-4567"; + const agentEmail = data.Agent_Email__c || data.agentEmail || "john.smith@realestate.com"; + const ownerName = data.Owner_Name__c || data.ownerName || "Property Owner"; + const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "+1 (555) 987-6543"; + const ownerEmail = data.Owner_Email__c || data.ownerEmail || "owner@email.com"; + + // Get smart images + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage1 = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + const interiorImage2 = this.getSmartImageForSection('living', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + const interiorImage3 = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + const kitchenImage1 = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1600585152225-3579fe9d7ae2?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + + // Generate amenities HTML + const amenitiesHTML = this.generateAmenitiesHTML(data); + + // Return the template HTML with all dynamic data + return this.template.querySelector('grand-oak-villa-template')?.innerHTML || ''; + } + + + + + Grand Oak Villa - ${propertyName} + + + + + + + +
+
+
${status.toUpperCase()}
+
+

${propertyName}

+

${location}

+
+
+
${bedrooms}
Bedrooms
+
${bathrooms}
Bathrooms
+
${squareFeet}
Sq. Ft.
+
${price}
Price
+
+
+ + + +
+
+ +
+
+

Description

+
+

${data.Description_English__c || data.descriptionEnglish || data.description || "Nestled in the heart of Prestige City, The Grand Oak Villa is a masterpiece of modern architecture and timeless elegance."}

+
+
+
+
+

Specifications

+
+
Property: ${propertyName}
+
Status: ${status}
+
Type: ${data.Property_Type__c || data.propertyType || "Villa"}
+
Year Built: ${data.Build_Year__c || data.yearBuilt || "N/A"}
+
Floor: ${data.Floor__c || data.floor || "N/A"}
+
Parking: ${data.Parking_Spaces__c || data.parking || "N/A"}
+
Furnishing: ${data.Furnished__c || data.furnishing || "N/A"}
+
Maintenance Fee: ${data.Maintenance_Fee__c || data.maintenanceFee || "N/A"}
+
Service Charge: ${data.Service_Charge__c || data.serviceCharge || "N/A"}
+
+
+
+

Amenities & Features

+
    + ${this.generateAmenitiesHTML(data)} +
+
+
+
+
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
+
Owner: ${ownerName} | ${ownerPhone}
+
+
+ +
+
+
+ +
+
Schools
${schools}
+
Shopping
${shoppingCenters}
+
Airport
${airportDistance}
+
Landmarks
${nearbyLandmarks}
+
Transportation
${transportation}
+
Hospitals
${hospitals}
+
Beach
${beachDistance}
+
Metro
${metroDistance}
+
+ +
+

Location Details

+
+
City${city}
+
Community${community}
+
Sub Community${subCommunity}
+
Locality${locality}
+
Sub Locality${subLocality}
+
Tower${tower}
+
+
+
+
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
+
Owner: ${ownerName} | ${ownerPhone}
+
+
+ +
+
+ +
+
+

A Glimpse Inside

+ +
+
+

Additional Information

+
+
Pet Friendly: ${petFriendly}
+
Smoking: ${smokingAllowed}
+
Available From: ${availableFrom}
+
Minimum Contract: ${minimumContract}
+
Security Deposit: ${securityDeposit}
+
Utilities Included: ${utilitiesIncluded}
+
Internet Included: ${internetIncluded}
+
Cable Included: ${cableIncluded}
+
+
+
+
+
+
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
+
Owner: ${ownerName} | ${ownerPhone}
+
+
+ + +`; } createSampleTemplate() { @@ -2163,7 +3193,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Extract all available property data with fallbacks const propertyName = data.Name || data.propertyName || "Property Name"; const location = data.Address__c || data.location || "Location"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; const agentPhone = data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; const agentEmail = data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; @@ -2211,7 +3241,7 @@ export default class PropertyTemplateSelector extends LightningElement { - return `Modern Urban Residences Brochure - ${propertyName}
An Urban Oasis

${propertyName}

${location}

Where Design Meets Desire.

${propertyName} is not just a building; it's a bold statement on modern urban living. ${description}

Every residence is a testament to quality, featuring panoramic city views from floor-to-ceiling windows, intelligent home systems, and finishes selected from the finest materials around the globe. This is more than a home; it's a new perspective.

${propertyName}Page 02 / 07
${propertyName}Page 03 / 07

An unrivaled collection of amenities offers residents a resort-style living experience. From the serene rooftop pool to the state-of-the-art wellness center, every detail is crafted for comfort, convenience, and luxury.

Lifestyle Amenities

    ${amenitiesHTML}

Key Specifications

Status ${status}
Property Type ${propertyType}
Year Built ${yearBuilt}
Floor ${data.Floor__c || "N/A"}
Maintenance Fee ${data.Maintenance_Fee__c || "N/A"}
Bedrooms ${bedrooms}
Bathrooms ${bathrooms}
Square Feet ${squareFeet}
${propertyName}Page 04 / 07

${bedrooms}-Bedroom Residence

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${parking}
PARKING

A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

Premium ${propertyType}

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${furnishing}
FURNISHING

The pinnacle of luxury living, this residence offers expansive spaces, premium finishes, and exclusive access to premium amenities.

Additional Information

Pets
${petFriendly}
Smoking
${smoking}
Availability
${availability}
Parking
${parking}
Furnishing
${furnishing}
Utilities
${utilities}
${propertyName}Page 05 / 07

Schedule a Private Viewing

Experience ${propertyName} firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

${agentName}
Sales Executive, Elysian Properties
${agentPhone}
${agentEmail}

Neighborhood Highlights

  • Landmarks: ${landmarks}
  • Transportation: ${transportation}
  • Schools: ${schools}
  • Shopping: ${shopping}
  • Airport: ${airport}
${propertyName}Page 06 / 07
${propertyGallery}
${propertyName}Page 07 / 07
`; + return `Modern Urban Residences Brochure - ${propertyName}
Cover
An Urban Oasis

${propertyName}

${location}

Where Design Meets Desire.

${propertyName} is not just a building; it's a bold statement on modern urban living. ${description}

Every residence is a testament to quality, featuring panoramic city views from floor-to-ceiling windows, intelligent home systems, and finishes selected from the finest materials around the globe. This is more than a home; it's a new perspective.

Interior
${propertyName}Page 02 / 07
${propertyName}Page 03 / 07

An unrivaled collection of amenities offers residents a resort-style living experience. From the serene rooftop pool to the state-of-the-art wellness center, every detail is crafted for comfort, convenience, and luxury.

Feature

Lifestyle Amenities

    ${amenitiesHTML}

Key Specifications

Status ${status}
Property Type ${propertyType}
Year Built ${yearBuilt}
Floor ${data.Floor__c || "N/A"}
Maintenance Fee ${data.Maintenance_Fee__c || "N/A"}
Bedrooms ${bedrooms}
Bathrooms ${bathrooms}
Square Feet ${squareFeet}
${propertyName}Page 04 / 07
Residence Plan

${bedrooms}-Bedroom Residence

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${parking}
PARKING

A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

Penthouse Plan

Premium ${propertyType}

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${furnishing}
FURNISHING

The pinnacle of luxury living, this residence offers expansive spaces, premium finishes, and exclusive access to premium amenities.

Additional Information

Pets
${petFriendly}
Smoking
${smoking}
Availability
${availability}
Parking
${parking}
Furnishing
${furnishing}
Utilities
${utilities}
${propertyName}Page 05 / 07

Schedule a Private Viewing

Experience ${propertyName} firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

${agentName}
Sales Executive, Elysian Properties
${agentPhone}
${agentEmail}

Neighborhood Highlights

  • Landmarks: ${landmarks}
  • Transportation: ${transportation}
  • Schools: ${schools}
  • Shopping: ${shopping}
  • Airport: ${airport}
${propertyName}Page 06 / 07
${propertyGallery}
${propertyName}Page 07 / 07
`; } createSerenityHouseTemplate() { @@ -2220,7 +3250,7 @@ export default class PropertyTemplateSelector extends LightningElement { // Extract all available property data with fallbacks const propertyName = data.Name || data.propertyName || "Property Name"; const location = data.Address__c || data.location || "Location"; - const referenceId = data.Reference_Number__c || data.referenceNumber || "N/A"; + const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; const agentPhone = data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; const agentEmail = data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; @@ -2555,11 +3585,11 @@ export default class PropertyTemplateSelector extends LightningElement { } .p4-floorplan-container { - height: 280px; + height: 320px; background-color: var(--color-off-white); border: 1px solid var(--color-border); - background-image: url('${this.getMapsImageUrl()}'); - background-size: cover; + background-image: url('https://cdn.shopify.com/s/files/1/0024/0495/3953/files/Architect_s_floor_plan_for_a_house_in_black_and_white_large.jpg'); + background-size: contain; background-position: center; background-repeat: no-repeat; margin-bottom: 40px; @@ -2639,7 +3669,7 @@ export default class PropertyTemplateSelector extends LightningElement {
${data.collection || "Elysian Estates Collection"}

${propertyName}

${location}

-

Reference ID: ${referenceId}

+

Property: ${propertyName}