From 355f787a818aa6b8f067bc208ea465bd4955b1ca Mon Sep 17 00:00:00 2001 From: rohit Date: Mon, 25 Aug 2025 22:37:55 +0530 Subject: [PATCH] full functional toolbox --- .../PropertyPdfGeneratorController.cls | 50 + .../propertyTemplateSelector.css | 981 ++++++++++++++---- .../propertyTemplateSelector.html | 72 +- .../propertyTemplateSelector.js | 622 +++++++---- .../Python_API.remoteSite-meta.xml | 2 +- 5 files changed, 1295 insertions(+), 432 deletions(-) diff --git a/force-app/main/default/classes/PropertyPdfGeneratorController.cls b/force-app/main/default/classes/PropertyPdfGeneratorController.cls index 5fe802b..707a6b6 100644 --- a/force-app/main/default/classes/PropertyPdfGeneratorController.cls +++ b/force-app/main/default/classes/PropertyPdfGeneratorController.cls @@ -152,4 +152,54 @@ public with sharing class PropertyPdfGeneratorController { return '
Error generating PDF content: ' + e.getMessage() + '
'; } } + + // Proxy method to call Python API through Apex + @AuraEnabled(cacheable=false) + public static String callPythonApi(String htmlContent, String propertyData, String templateName) { + try { + System.debug('Calling Python API via Apex proxy...'); + + // Prepare the request payload + Map requestPayload = new Map(); + requestPayload.put('html_content', htmlContent); + requestPayload.put('template_name', templateName); + requestPayload.put('filename', 'property_brochure.pdf'); + + // Parse property data + Map propertyDataMap = (Map)JSON.deserializeUntyped(propertyData); + requestPayload.put('property_data', propertyDataMap); + + // Convert to JSON + String jsonPayload = JSON.serialize(requestPayload); + System.debug('Request payload: ' + jsonPayload); + + // Make HTTP callout to Python API + Http http = new Http(); + HttpRequest request = new HttpRequest(); + request.setEndpoint('https://salesforce.tech4biz.io/api/generate-pdf'); + request.setMethod('POST'); + request.setHeader('Content-Type', 'application/json'); + request.setBody(jsonPayload); + request.setTimeout(120000); // 2 minutes timeout + + // Make the callout + HttpResponse response = http.send(request); + System.debug('Response status: ' + response.getStatusCode()); + System.debug('Response body length: ' + response.getBody().length()); + + if (response.getStatusCode() == 200) { + // Return the PDF content as base64 + String pdfContent = EncodingUtil.base64Encode(response.getBodyAsBlob()); + System.debug('PDF generated successfully, size: ' + pdfContent.length()); + return pdfContent; + } else { + throw new CalloutException('Python API returned status: ' + response.getStatusCode() + ' - ' + response.getBody()); + } + + } catch (Exception e) { + System.debug('Error calling Python API: ' + e.getMessage()); + System.debug('Stack trace: ' + e.getStackTraceString()); + throw new CalloutException('Failed to call Python API: ' + e.getMessage()); + } + } } \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css index 4c0e441..eba15a1 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css @@ -756,283 +756,196 @@ /* Editor Container */ .editor-container { display: flex; - height: calc(100vh - 200px); - background: #f8f9fa; - margin: 20px; - border-radius: 15px; - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); - overflow: hidden; + gap: 20px; + margin-top: 20px; } -/* Left Toolbar - Fixed positioning */ +/* Left Toolbar - Original Layout */ .editor-toolbar.left { width: 300px; - background: white; - border-right: 1px solid #e9ecef; + flex-shrink: 0; + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 8px; padding: 20px; - overflow: visible; /* Remove scrollbar */ - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05); - position: relative; - flex-shrink: 0; /* Prevent shrinking */ + height: fit-content; } -/* Toolbar Sections - Compact layout */ .toolbar-section { - margin-bottom: 20px; - padding-bottom: 15px; - border-bottom: 1px solid #f0f0f0; + margin-bottom: 25px; } .toolbar-section:last-child { - border-bottom: none; margin-bottom: 0; } .toolbar-section-title { - font-size: 11px; font-weight: 600; - color: #667eea; + color: #495057; + margin-bottom: 15px; + font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; - margin-bottom: 12px; - padding: 6px 10px; - background: #f8f9fa; - border-radius: 6px; - border-left: 3px solid #667eea; + border-bottom: 2px solid #dee2e6; + padding-bottom: 5px; } .toolbar-group { - margin-bottom: 12px; + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 15px; } .toolbar-group:last-child { margin-bottom: 0; } -.toolbar-group label { - display: block; - font-size: 10px; - font-weight: 500; - color: #555; - margin-bottom: 6px; - text-transform: uppercase; - letter-spacing: 0.3px; -} - -.toolbar-group select { - width: 100%; - padding: 6px 10px; - border: 1px solid #ddd; - border-radius: 6px; - font-size: 11px; - background: white; - color: #333; - transition: border-color 0.2s ease; -} - -.toolbar-group select:focus { - outline: none; - border-color: #667eea; - box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); -} - -.toolbar-group input[type="color"] { - width: 100%; - height: 35px; - border: 1px solid #ddd; - border-radius: 6px; - cursor: pointer; - transition: border-color 0.2s ease; -} - -.toolbar-group input[type="color"]:focus { - outline: none; - border-color: #667eea; - box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); -} - -/* Button Grid - Compact layout */ -.button-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; -} - -.button-grid-4 { - grid-template-columns: 1fr 1fr 1fr 1fr; -} - -/* Toolbar Buttons - Compact size */ .toolbar-button { - background: white; - border: 1px solid #e9ecef; - padding: 8px 10px; + background: #ffffff; + border: 1px solid #dee2e6; border-radius: 6px; - font-size: 10px; - font-weight: 500; - color: #555; + padding: 10px 15px; + font-size: 13px; + color: #495057; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; - justify-content: center; - gap: 4px; - text-transform: uppercase; - letter-spacing: 0.3px; - min-height: 32px; + gap: 8px; + width: 100%; + box-sizing: border-box; } .toolbar-button:hover { - background: #667eea; - color: white; - border-color: #667eea; + background: #e9ecef; + border-color: #adb5bd; transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .toolbar-button:active { + background: #dee2e6; transform: translateY(0); - box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } -.toolbar-button lightning-icon { - --sds-c-icon-color-foreground-default: currentColor; +/* Insert Content Section - Fixed Layout */ +.insert-content-section .toolbar-group { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; } -/* Editor Content Area - Fixed margins */ -.editor-content-area { +.insert-content-section .toolbar-group:first-child { + grid-template-columns: 1fr 1fr; +} + +.insert-content-section .toolbar-group:last-child { + grid-template-columns: 1fr 1fr; +} + +/* Text Formatting */ +.text-formatting-section .toolbar-group { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +.text-formatting-section label { + font-size: 12px; + color: #6c757d; + min-width: 80px; +} + +.text-formatting-section select { + background: white; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 6px 8px; + font-size: 12px; + color: #495057; + min-width: 120px; + flex: 1; +} + +/* Text Styling */ +.text-styling-section .toolbar-group { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +.text-styling-section .toolbar-button { + justify-content: center; + text-align: center; + min-height: 40px; + font-weight: 600; +} + +/* Text Alignment */ +.text-alignment-section .toolbar-group { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; +} + +.text-alignment-section .toolbar-button { + justify-content: center; + text-align: center; + min-height: 40px; +} + +/* Right Editor Area */ +.editor-right { flex: 1; background: white; - position: relative; - overflow: hidden; - margin-left: 20px; /* Fixed margin from toolbar */ - margin-right: 20px; /* Fixed margin from right side */ + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 20px; + min-height: 600px; } .preview-frame { width: 100%; - height: 100%; - padding: 40px; - overflow-y: auto; - background: white; + min-height: 600px; border: none; outline: none; - font-family: Arial, sans-serif; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; - margin-left: 10px; /* Additional spacing from toolbar */ - margin-right: 10px; /* Additional spacing from right side */ -} - -.preview-frame::-webkit-scrollbar { - width: 8px; -} - -.preview-frame::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 4px; -} - -.preview-frame::-webkit-scrollbar-thumb { - background: #c1c1c1; - border-radius: 4px; -} - -.preview-frame::-webkit-scrollbar-thumb:hover { - background: #a8a8a8; -} - -/* Export PDF Button */ -.export-pdf-section { - position: absolute; - top: 20px; - right: 20px; - z-index: 10; -} - -.export-pdf-btn { - background: linear-gradient(135deg, #667eea, #764ba2); - color: white; - border: none; - padding: 12px 24px; - border-radius: 8px; - font-weight: 600; - cursor: pointer; - box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); - text-transform: uppercase; - font-size: 14px; - transition: all 0.3s ease; - } - -.export-pdf-btn:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); - } - -.export-pdf-btn:active { - transform: translateY(0); - } - -/* Progress Bar for PDF Generation */ -.progress-bar { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - display: flex; - align-items: center; - justify-content: center; - z-index: 10000; - } - -.progress-content { - background: white; - padding: 40px; - border-radius: 15px; - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); - text-align: center; - max-width: 400px; - width: 90%; -} - -.progress-spinner { - width: 60px; - height: 60px; - border: 4px solid #f3f3f3; - border-top: 4px solid #667eea; - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto 20px; -} - -.progress-text { - font-size: 18px; - font-weight: 600; color: #333; - margin-bottom: 20px; + background: white; } -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Progress bar animation */ -.progress-bar .progress-content { - animation: slideIn 0.3s ease-out; -} - -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-20px); +/* Responsive Design */ +@media (max-width: 1200px) { + .editor-container { + flex-direction: column; } - to { - opacity: 1; - transform: translateY(0); + + .editor-toolbar.left { + width: 100%; + order: 2; } -} + + .editor-right { + order: 1; + } +} + +@media (max-width: 768px) { + .insert-content-section .toolbar-group { + grid-template-columns: 1fr; + } + + .text-styling-section .toolbar-group { + grid-template-columns: repeat(2, 1fr); + } + + .text-alignment-section .toolbar-group { + grid-template-columns: repeat(3, 1fr); + } +} /* Hide header in step 3 */ .step-3 .step-header { @@ -1776,3 +1689,635 @@ font-size: 11px; } +/* Editor Toolbar - Fixed Layout */ +.editor-toolbar { + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 15px; + margin-bottom: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + max-width: 100%; + overflow: hidden; +} + +.editor-toolbar.left { + width: 100%; + max-width: 100%; + box-sizing: border-box; +} + +.toolbar-section { + margin-bottom: 20px; + padding: 10px; + background: white; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.toolbar-section:last-child { + margin-bottom: 0; +} + +.toolbar-section-title { + font-weight: 600; + color: #495057; + margin-bottom: 10px; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.toolbar-group { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + margin-bottom: 10px; +} + +.toolbar-group:last-child { + margin-bottom: 0; +} + +.toolbar-button { + background: #ffffff; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 8px 12px; + font-size: 12px; + color: #495057; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 6px; + min-width: fit-content; + max-width: 100%; + box-sizing: border-box; +} + +.toolbar-button:hover { + background: #e9ecef; + border-color: #adb5bd; +} + +.toolbar-button:active { + background: #dee2e6; +} + +.button-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + width: 100%; +} + +.button-grid .toolbar-button { + justify-content: center; + text-align: center; + min-height: 36px; +} + +/* Form Controls */ +.toolbar-group label { + font-size: 12px; + color: #6c757d; + margin-right: 8px; + white-space: nowrap; +} + +.toolbar-group select { + background: white; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 6px 8px; + font-size: 12px; + color: #495057; + min-width: 100px; + max-width: 150px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .editor-toolbar { + padding: 10px; + } + + .toolbar-section { + padding: 8px; + } + + .toolbar-group { + gap: 6px; + } + + .toolbar-button { + padding: 6px 10px; + font-size: 11px; + } + + .button-grid { + grid-template-columns: 1fr; + } +} + +/* Step 3: HTML Editor - Fixed Layout */ +.step3 { + display: block; + position: relative; + height: calc(100vh - 200px); + overflow: hidden; +} + +.step3.hidden { + display: none; +} + +/* Editor Container - Fixed Layout */ +.editor-container { + display: flex; + gap: 20px; + height: 100%; + padding: 20px; + box-sizing: border-box; +} + +/* Left Toolbar - Fixed Position, No Scrollbar */ +.editor-toolbar.left { + width: 280px; + flex-shrink: 0; + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 15px; + height: fit-content; + max-height: 100%; + overflow: visible; +} + +.toolbar-section { + margin-bottom: 20px; +} + +.toolbar-section:last-child { + margin-bottom: 0; +} + +.toolbar-section-title { + font-weight: 600; + color: #495057; + margin-bottom: 12px; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 2px solid #dee2e6; + padding-bottom: 4px; +} + +.toolbar-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 12px; +} + +.toolbar-group:last-child { + margin-bottom: 0; +} + +.toolbar-button { + background: #ffffff; + border: 1px solid #dee2e6; + border-radius: 6px; + padding: 8px 12px; + font-size: 12px; + color: #495057; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 6px; + width: 100%; + box-sizing: border-box; +} + +.toolbar-button:hover { + background: #e9ecef; + border-color: #adb5bd; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.toolbar-button:active { + background: #dee2e6; + transform: translateY(0); +} + +/* Insert Content Section - Compact Layout */ +.insert-content-section .toolbar-group { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.insert-content-section .toolbar-group:first-child { + grid-template-columns: 1fr 1fr; +} + +.insert-content-section .toolbar-group:last-child { + grid-template-columns: 1fr 1fr; +} + +/* Text Formatting - Compact */ +.text-formatting-section .toolbar-group { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +} + +.text-formatting-section label { + font-size: 11px; + color: #6c757d; + min-width: 70px; +} + +.text-formatting-section select { + background: white; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 5px 6px; + font-size: 11px; + color: #495057; + min-width: 100px; + flex: 1; +} + +/* Text Styling - Compact */ +.text-styling-section .toolbar-group { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 6px; +} + +.text-styling-section .toolbar-button { + justify-content: center; + text-align: center; + min-height: 32px; + font-weight: 600; +} + +/* Text Alignment - Compact */ +.text-alignment-section .toolbar-group { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; +} + +.text-alignment-section .toolbar-button { + justify-content: center; + text-align: center; + min-height: 32px; +} + +/* Right Editor Area - Takes Remaining Space */ +.editor-right { + flex: 1; + background: white; + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 20px; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + position: relative; +} + +.preview-frame { + flex: 1; + width: 100%; + border: none; + outline: none; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: #333; + background: white; + resize: none; + overflow-y: auto; + padding: 20px; + box-sizing: border-box; + max-height: 100%; +} + +/* Export PDF Button - Positioned at Template Level */ +.export-pdf-section { + position: absolute; + top: 20px; + right: 20px; + z-index: 10; +} + +.export-pdf-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + text-transform: uppercase; + font-size: 14px; + transition: all 0.3s ease; +} + +.export-pdf-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); +} + +.export-pdf-btn:active { + transform: translateY(0); +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .editor-container { + flex-direction: column; + height: auto; + } + + .editor-toolbar.left { + width: 100%; + height: auto; + order: 2; + } + + .editor-right { + order: 1; + height: 500px; + } +} + +@media (max-width: 768px) { + .insert-content-section .toolbar-group { + grid-template-columns: 1fr; + } + + .text-styling-section .toolbar-group { + grid-template-columns: repeat(2, 1fr); + } + + .text-alignment-section .toolbar-group { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Export PDF Button - Positioned at Template Header with Animation */ +.export-pdf-section { + position: absolute; + top: 40px; + right: 40px; + z-index: 1000; + animation: pulse-grow 2s ease-in-out infinite; +} + +.export-pdf-btn { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + border: none; + padding: 12px 24px; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + text-transform: uppercase; + font-size: 14px; + transition: all 0.3s ease; + transform-origin: center; +} + +.export-pdf-btn:hover { + transform: translateY(-2px) scale(1.05); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); + animation: none; +} + +.export-pdf-btn:active { + transform: translateY(0) scale(0.95); +} + +/* Pulse Animation: Small -> Big -> Small -> Big */ +@keyframes pulse-grow { + 0% { + transform: scale(1); + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + } + 25% { + transform: scale(1.05); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); + } + 50% { + transform: scale(1); + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + } + 75% { + transform: scale(1.05); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); + } + 100% { + transform: scale(1); + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + } +} + +/* Template Header Area - Position for Export Button */ +.template-header-area { + position: relative; + margin-bottom: 20px; +} + +/* Custom Popup Styles - Matching Our Theme */ +.custom-popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; +} + +.popup-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(2px); +} + +.popup-container { + position: relative; + background: white; + border-radius: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + width: 90%; + max-width: 500px; + max-height: 80vh; + overflow-y: auto; + animation: popupSlideIn 0.3s ease-out; +} + +@keyframes popupSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.popup-header { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + padding: 20px; + border-radius: 12px 12px 0 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.popup-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.popup-close { + background: none; + border: none; + color: white; + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background-color 0.2s ease; +} + +.popup-close:hover { + background: rgba(255, 255, 255, 0.2); +} + +.popup-content { + padding: 25px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #495057; + font-size: 14px; +} + +.form-control { + width: 100%; + padding: 10px 12px; + border: 1px solid #dee2e6; + border-radius: 6px; + font-size: 14px; + color: #495057; + background: white; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + box-sizing: border-box; +} + +.form-control:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.form-control[type="color"] { + height: 40px; + padding: 5px; + cursor: pointer; +} + +.popup-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 25px; + padding-top: 20px; + border-top: 1px solid #e9ecef; +} + +.popup-actions .btn { + padding: 10px 20px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + min-width: 80px; +} + +.popup-actions .btn-secondary { + background: #6c757d; + color: white; +} + +.popup-actions .btn-secondary:hover { + background: #5a6268; + transform: translateY(-1px); +} + +.popup-actions .btn-primary { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; +} + +.popup-actions .btn-primary:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); +} + +/* Responsive popup */ +@media (max-width: 768px) { + .popup-container { + width: 95%; + margin: 20px; + } + + .popup-content { + padding: 20px; + } + + .popup-actions { + flex-direction: column; + } + + .popup-actions .btn { + width: 100%; + } +} + diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html index 48cac48..87f1aff 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html @@ -397,11 +397,11 @@ Reset - - + + -
+
Insert Content
+
+
-
-
+
+ -
+
Text Formatting
@@ -434,8 +436,8 @@ - -
+ +
+
-
+
Text Styling
-
+
-
+
Text Alignment
-
+
- -
-
+
+
@@ -551,22 +545,22 @@
- -
- -
- - -
+ +
+ +
+ +
+ +
+
- +
+
diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js index 99fdbc6..a22fa6e 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js @@ -6,6 +6,7 @@ import getPropertyCount from '@salesforce/apex/PropertyDataController.getPropert import generatePreview from '@salesforce/apex/PdfApiController.generatePreview'; import generatePdf from '@salesforce/apex/PdfApiController.generatePdf'; import generatePdfServerSide from '@salesforce/apex/PdfApiController.generatePdfServerSide'; +import callPythonApi from '@salesforce/apex/PropertyPdfGeneratorController.callPythonApi'; export default class PropertyTemplateSelector extends LightningElement { @track currentStep = 1; @@ -541,15 +542,14 @@ export default class PropertyTemplateSelector extends LightningElement { console.log('selectedTemplateData:', this.selectedTemplateData); console.log('selectedPropertyId:', this.selectedPropertyId); console.log('propertyData:', this.propertyData); - console.log('templates array:', this.templates); if (!this.selectedTemplateData) { - this.showError('Please select a template first. Current value: ' + JSON.stringify(this.selectedTemplateData)); + this.showError('Please select a template first.'); return; } if (!this.selectedPropertyId) { - this.showError('Please select a property first. Current value: ' + this.selectedPropertyId); + this.showError('Please select a property first.'); return; } @@ -557,19 +557,13 @@ export default class PropertyTemplateSelector extends LightningElement { this.showError('Property data not loaded. Please try selecting the property again.'); return; } - - // Validate property data for PDF generation - if (!this.validatePropertyDataForPdf()) { - return; - } this.isLoading = true; - // Generate HTML content based on template and property data - this.editorContent = this.createTemplateHTML(); + // Generate simple HTML content for template preview + this.editorContent = this.createSimpleTemplateHTML(); console.log('Generated editor content length:', this.editorContent.length); - console.log('Editor content preview:', this.editorContent.substring(0, 200) + '...'); // Move to step 3 (editor) and show header this.currentStep = 3; @@ -581,6 +575,58 @@ export default class PropertyTemplateSelector extends LightningElement { this.initializeEditor(); }, 100); } + + // Create simple template HTML for fast Step 2 + createSimpleTemplateHTML() { + console.log('=== CREATING SIMPLE TEMPLATE HTML ==='); + + if (!this.propertyData) { + return '

No Property Data

Please select a property first.

'; + } + + // Simple template with basic styling + return ` +
+

${this.propertyData.propertyName || 'Property Brochure'}

+

${this.propertyData.propertyType || 'Property Type'} in ${this.propertyData.location || 'Location'}

+
+ +
+
+
Price
+
${this.propertyData.price || 'N/A'}
+
+
+
Bedrooms
+
${this.propertyData.bedrooms || 'N/A'}
+
+
+
Bathrooms
+
${this.propertyData.bathrooms || 'N/A'}
+
+
+
Area
+
${this.propertyData.area || 'N/A'}
+
+
+ +
+

Property Details

+
+
Type: ${this.propertyData.propertyType || 'N/A'}
+
Location: ${this.propertyData.location || 'N/A'}
+
Price: ${this.propertyData.price || 'N/A'}
+
Bedrooms: ${this.propertyData.bedrooms || 'N/A'}
+
Bathrooms: ${this.propertyData.bathrooms || 'N/A'}
+
Area: ${this.propertyData.area || 'N/A'}
+
+
+ +
+

Template generated successfully! You can now edit this content and export as PDF.

+
+ `; + } // Initialize the rich text editor initializeEditor() { @@ -594,12 +640,8 @@ export default class PropertyTemplateSelector extends LightningElement { return; } - // Generate template HTML with property data - const templateHTML = this.createTemplateHTML(); - console.log('Generated template HTML:', templateHTML); - - // Set the editor content - this.editorContent = templateHTML; + // Use the already generated content from Step 2 + console.log('Using existing editor content:', this.editorContent); // Initialize pages for multi-page editing this.initializePages(); @@ -2621,15 +2663,304 @@ export default class PropertyTemplateSelector extends LightningElement { } insertText() { - this.addTextBlock(); + this.showTextInsertPopup(); } insertImage() { - this.insertImage(); + this.showImageInsertPopup(); } addShape() { - this.addShape(); + this.showShapeInsertPopup(); + } + + insertTable() { + this.showTableInsertPopup(); + } + + // Text formatting methods + setFontFamily(fontFamily) { + document.execCommand('fontName', false, fontFamily); + } + + setFontSize(fontSize) { + document.execCommand('fontSize', false, fontSize); + } + + setBold() { + document.execCommand('bold', false, null); + } + + setItalic() { + document.execCommand('italic', false, null); + } + + setUnderline() { + document.execCommand('underline', false, null); + } + + setTextColor(color) { + document.execCommand('foreColor', false, color); + } + + setTextAlign(align) { + document.execCommand('justify' + align.charAt(0).toUpperCase() + align.slice(1), false, null); + } + + // Template management methods + saveTemplate() { + const editorFrame = this.template.querySelector('.preview-frame'); + if (editorFrame) { + this.editorContent = editorFrame.innerHTML; + this.showSuccess('Template saved successfully!'); + } + } + + resetTemplate() { + if (confirm('Are you sure you want to reset the template? This will clear all changes.')) { + const editorFrame = this.template.querySelector('.preview-frame'); + if (editorFrame) { + editorFrame.innerHTML = this.editorContent || ''; + this.showSuccess('Template reset successfully!'); + } + } + } + + // Custom popup methods + showTextInsertPopup() { + this.showCustomPopup('Insert Text', ` + + `); + } + + showImageInsertPopup() { + this.showCustomPopup('Insert Image', ` + + `); + } + + showShapeInsertPopup() { + this.showCustomPopup('Insert Shape', ` + + `); + } + + showTableInsertPopup() { + this.showCustomPopup('Insert Table', ` + + `); + } + + // Helper method to show custom popups + showCustomPopup(title, content) { + // Remove any existing popup + const existingPopup = this.template.querySelector('.custom-popup'); + if (existingPopup) { + existingPopup.remove(); + } + + // Create popup container + const popup = document.createElement('div'); + popup.className = 'custom-popup'; + popup.innerHTML = ` + + + `; + + // Add to body + document.body.appendChild(popup); + + // Bind methods to the popup context + popup.insertTextFromPopup = () => this.insertTextFromPopup(); + popup.insertImageFromPopup = () => this.insertImageFromPopup(); + popup.insertShapeFromPopup = () => this.insertShapeFromPopup(); + popup.insertTableFromPopup = () => this.insertTableFromPopup(); + } + + // Popup action methods + insertTextFromPopup() { + const textInput = document.getElementById('textInput'); + const textStyle = document.getElementById('textStyle'); + + if (textInput && textInput.value.trim()) { + const text = textInput.value.trim(); + const style = textStyle ? textStyle.value : 'p'; + const html = `<${style}>${text}`; + + document.execCommand('insertHTML', false, html); + document.querySelector('.custom-popup').remove(); + this.showSuccess('Text inserted successfully!'); + } + } + + insertImageFromPopup() { + const imageUrl = document.getElementById('imageUrl'); + const imageAlt = document.getElementById('imageAlt'); + const imageWidth = document.getElementById('imageWidth'); + + if (imageUrl && imageUrl.value.trim()) { + const url = imageUrl.value.trim(); + const alt = imageAlt ? imageAlt.value.trim() : 'Image'; + const width = imageWidth ? imageWidth.value : '300'; + + const html = `${alt}`; + + document.execCommand('insertHTML', false, html); + document.querySelector('.custom-popup').remove(); + this.showSuccess('Image inserted successfully!'); + } + } + + insertShapeFromPopup() { + const shapeType = document.getElementById('shapeType'); + const shapeWidth = document.getElementById('shapeWidth'); + const shapeHeight = document.getElementById('shapeHeight'); + const shapeColor = document.getElementById('shapeColor'); + + if (shapeType && shapeWidth && shapeHeight) { + const type = shapeType.value; + const width = shapeWidth.value || '100'; + const height = shapeHeight.value || '100'; + const color = shapeColor ? shapeColor.value : '#667eea'; + + let html = ''; + switch (type) { + case 'rectangle': + html = `
`; + break; + case 'circle': + html = `
`; + break; + case 'triangle': + html = `
`; + break; + case 'diamond': + html = `
`; + break; + } + + document.execCommand('insertHTML', false, html); + document.querySelector('.custom-popup').remove(); + this.showSuccess('Shape inserted successfully!'); + } + } + + insertTableFromPopup() { + const tableRows = document.getElementById('tableRows'); + const tableCols = document.getElementById('tableCols'); + const tableBorder = document.getElementById('tableBorder'); + + if (tableRows && tableCols) { + const rows = parseInt(tableRows.value) || 3; + const cols = parseInt(tableCols.value) || 3; + const border = tableBorder ? tableBorder.value : '1'; + + let html = ''; + + for (let i = 0; i < rows; i++) { + html += ''; + for (let j = 0; j < cols; j++) { + html += ``; + } + html += ''; + } + + html += '
Cell ${i+1}-${j+1}
'; + + document.execCommand('insertHTML', false, html); + document.querySelector('.custom-popup').remove(); + this.showSuccess('Table inserted successfully!'); + } } // Create safe property display string @@ -2717,13 +3048,13 @@ export default class PropertyTemplateSelector extends LightningElement { return true; } - // Generate PDF using Apex server-side generation + // Generate PDF using Python API async generatePdfSimple() { try { - console.log('=== GENERATING PDF VIA APEX ==='); + console.log('=== GENERATING PDF VIA PYTHON API ==='); // Show progress - this.showProgress('Generating PDF via server...'); + this.showProgress('Generating PDF via Python API...'); // Get current editor content const editorFrame = this.template.querySelector('.preview-frame'); @@ -2733,8 +3064,8 @@ export default class PropertyTemplateSelector extends LightningElement { this.editorContent = editorFrame.innerHTML; - // Generate PDF using Apex - await this.generatePdfViaApex(); + // Generate PDF using Python API + await this.generatePdfViaPythonApi(); this.showSuccess('PDF generated successfully!'); @@ -2747,12 +3078,87 @@ export default class PropertyTemplateSelector extends LightningElement { } } - // Generate PDF via Apex server-side + // Generate PDF via Python API + async generatePdfViaPythonApi() { + try { + console.log('Calling Python API via Apex proxy...'); + + // Prepare the HTML content from editor + const editorFrame = this.template.querySelector('.preview-frame'); + if (!editorFrame) { + throw new Error('Editor frame not found'); + } + + const htmlContent = editorFrame.innerHTML; + + // Prepare property data + const propertyData = { + propertyName: this.propertyData.propertyName || 'Property Brochure', + propertyType: this.propertyData.propertyType || 'Property Type', + location: this.propertyData.location || 'Location', + price: this.propertyData.price || 'N/A', + bedrooms: this.propertyData.bedrooms || 'N/A', + bathrooms: this.propertyData.bathrooms || 'N/A', + area: this.propertyData.area || 'N/A' + }; + + console.log('Calling Apex proxy for Python API...'); + + // Call Apex method that will proxy the Python API call + const result = await callPythonApi({ + htmlContent: htmlContent, + propertyData: JSON.stringify(propertyData), + templateName: this.selectedTemplate || 'default' + }); + + if (result) { + console.log('PDF generated successfully via Apex proxy'); + + // Convert base64 to blob and download + const pdfBlob = this.base64ToBlob(result, 'application/pdf'); + const url = window.URL.createObjectURL(pdfBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `property_brochure_${Date.now()}.pdf`; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + window.URL.revokeObjectURL(url); + + this.showSuccess('PDF generated and downloaded successfully!'); + } else { + throw new Error('No PDF content returned from Apex proxy'); + } + + } catch (error) { + console.error('Error in generatePdfViaPythonApi:', error); + this.showError(`PDF generation failed: ${error.message}`); + + // Fallback to Apex method + console.log('Falling back to Apex PDF generation...'); + await this.generatePdfViaApex(); + } + } + + // Helper method to convert base64 to blob + base64ToBlob(base64, mimeType) { + const byteCharacters = atob(base64); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + return new Blob([byteArray], { type: mimeType }); + } + + // Fallback Apex method async generatePdfViaApex() { try { console.log('Calling Apex PDF generation...'); - // Prepare property data - only essential fields to avoid URL length issues + // Prepare property data const propertyData = { propertyName: this.propertyData.propertyName || 'Property Brochure', propertyType: this.propertyData.propertyType || 'Property Type', @@ -2767,7 +3173,7 @@ export default class PropertyTemplateSelector extends LightningElement { const baseUrl = window.location.origin; const visualforceUrl = `${baseUrl}/apex/PropertyPdfGenerator`; - // Add query parameters - simplified to avoid URL length issues + // Add query parameters const params = new URLSearchParams({ template: this.selectedTemplate || 'default', propertyData: JSON.stringify(propertyData), @@ -2777,38 +3183,22 @@ export default class PropertyTemplateSelector extends LightningElement { const fullUrl = `${visualforceUrl}?${params.toString()}`; console.log('Opening PDF URL:', fullUrl); - console.log('URL length:', fullUrl.length); - // Try to open the URL and handle any errors - try { - // Open the Visualforce page in a new window/tab - const pdfWindow = window.open(fullUrl, '_blank'); - - if (pdfWindow) { - // Check if the window opened successfully - setTimeout(() => { - try { - if (pdfWindow.closed) { - console.log('PDF window was closed'); - } else { - console.log('PDF window is still open'); - // Close it after a delay - setTimeout(() => { - pdfWindow.close(); - }, 2000); - } - } catch (e) { - console.log('Could not check window status'); + // Open the Visualforce page in a new window/tab + const pdfWindow = window.open(fullUrl, '_blank'); + + if (pdfWindow) { + setTimeout(() => { + try { + if (!pdfWindow.closed) { + pdfWindow.close(); } - }, 1000); - } else { - throw new Error('Could not open PDF window. Popup may be blocked.'); - } - - } catch (windowError) { - console.error('Window error:', windowError); - // Fallback: try to navigate to the URL directly - window.location.href = fullUrl; + } catch (e) { + console.log('Could not check window status'); + } + }, 2000); + } else { + throw new Error('Could not open PDF window. Popup may be blocked.'); } console.log('PDF generation initiated via Apex'); @@ -2818,120 +3208,4 @@ export default class PropertyTemplateSelector extends LightningElement { throw error; } } - - // Generate PDF via Python API - async generatePdfViaPythonApi() { - try { - console.log('Calling Python API for PDF generation...'); - - // Prepare the HTML content from the editor - const htmlContent = this.editorContent || this.generateDefaultTemplate(); - - // Prepare property data - const propertyData = { - propertyName: this.propertyData.propertyName || 'Property Brochure', - propertyType: this.propertyData.propertyType || 'Property Type', - location: this.propertyData.location || 'Location', - price: this.propertyData.price || 'N/A', - bedrooms: this.propertyData.bedrooms || 'N/A', - bathrooms: this.propertyData.bathrooms || 'N/A', - area: this.propertyData.area || 'N/A' - }; - - // Prepare request payload for Python API - const requestPayload = { - html_content: htmlContent, - property_data: propertyData, - template_name: this.selectedTemplate || 'default', - filename: `property_brochure_${Date.now()}.pdf` - }; - - console.log('Request payload prepared:', requestPayload); - console.log('HTML content length:', htmlContent.length); - - // Call Python API - const response = await fetch('https://salesforce.tech4biz.io/api/generate-pdf', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestPayload) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`HTTP ${response.status}: ${errorText}`); - } - - // Get the PDF blob - const pdfBlob = await response.blob(); - - // Create download link - const url = window.URL.createObjectURL(pdfBlob); - const link = document.createElement('a'); - link.href = url; - link.download = requestPayload.filename; - - // Trigger download - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - // Clean up - window.URL.revokeObjectURL(url); - - console.log('PDF downloaded successfully via Python API'); - this.showSuccess('PDF generated and downloaded successfully!'); - - } catch (error) { - console.error('Error calling Python API:', error); - this.showError(`PDF generation failed: ${error.message}`); - - // Fallback to Apex method - console.log('Falling back to Apex PDF generation...'); - await this.generatePdfViaApex(); - } - } - - // Generate default template if no editor content - generateDefaultTemplate() { - const propertyData = this.propertyData; - - return ` -
-

${propertyData.propertyName || 'Property Brochure'}

-

${propertyData.propertyType || 'Property Type'} in ${propertyData.location || 'Location'}

-

Reference: ${propertyData.propertyName || 'N/A'}

-
- -
-
-
Price
-
${propertyData.price || 'N/A'}
-
-
-
Bedrooms
-
${propertyData.bedrooms || 'N/A'}
-
-
-
Bathrooms
-
${propertyData.bathrooms || 'N/A'}
-
-
-
Area
-
${propertyData.area || 'N/A'}
-
-
- -
-

Property Details

-
-
Status: Pocket Listing / Off Market
-
Type: ${propertyData.propertyType || 'N/A'}
-
Floor: ${propertyData.floor || 'N/A'} of ${propertyData.totalFloors || 'N/A'}
-
Parking: ${propertyData.parkingSpaces || '1'} spaces
-
-
- `; - } } diff --git a/force-app/main/default/remoteSiteSettings/Python_API.remoteSite-meta.xml b/force-app/main/default/remoteSiteSettings/Python_API.remoteSite-meta.xml index 4499c34..38a3b64 100644 --- a/force-app/main/default/remoteSiteSettings/Python_API.remoteSite-meta.xml +++ b/force-app/main/default/remoteSiteSettings/Python_API.remoteSite-meta.xml @@ -3,5 +3,5 @@ false true https://salesforce.tech4biz.io - Python PDF Generation API - Production + Python PDF Generator API \ No newline at end of file