full functional toolbox

This commit is contained in:
rohit 2025-08-25 22:37:55 +05:30
parent ab0b08ee34
commit 355f787a81
5 changed files with 1295 additions and 432 deletions

View File

@ -152,4 +152,54 @@ public with sharing class PropertyPdfGeneratorController {
return '<div style="padding: 20px; color: red; font-family: Arial, sans-serif;">Error generating PDF content: ' + e.getMessage() + '</div>'; return '<div style="padding: 20px; color: red; font-family: Arial, sans-serif;">Error generating PDF content: ' + e.getMessage() + '</div>';
} }
} }
// 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<String, Object> requestPayload = new Map<String, Object>();
requestPayload.put('html_content', htmlContent);
requestPayload.put('template_name', templateName);
requestPayload.put('filename', 'property_brochure.pdf');
// Parse property data
Map<String, Object> propertyDataMap = (Map<String, Object>)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());
}
}
} }

View File

@ -397,11 +397,11 @@
<lightning-icon icon-name="utility:refresh" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:refresh" size="x-small"></lightning-icon>
Reset Reset
</button> </button>
</div> </div>
</div> </div>
<!-- Insert Content --> <!-- Insert Content -->
<div class="toolbar-section"> <div class="toolbar-section insert-content-section">
<div class="toolbar-section-title">Insert Content</div> <div class="toolbar-section-title">Insert Content</div>
<div class="toolbar-group"> <div class="toolbar-group">
<button class="toolbar-button" onclick={insertText}> <button class="toolbar-button" onclick={insertText}>
@ -412,6 +412,8 @@
<lightning-icon icon-name="utility:image" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:image" size="x-small"></lightning-icon>
Image Image
</button> </button>
</div>
<div class="toolbar-group">
<button class="toolbar-button" onclick={addShape}> <button class="toolbar-button" onclick={addShape}>
<lightning-icon icon-name="utility:shape" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:shape" size="x-small"></lightning-icon>
Shape Shape
@ -420,11 +422,11 @@
<lightning-icon icon-name="utility:table" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:table" size="x-small"></lightning-icon>
Table Table
</button> </button>
</div> </div>
</div> </div>
<!-- Text Formatting --> <!-- Text Formatting -->
<div class="toolbar-section"> <div class="toolbar-section text-formatting-section">
<div class="toolbar-section-title">Text Formatting</div> <div class="toolbar-section-title">Text Formatting</div>
<div class="toolbar-group"> <div class="toolbar-group">
<label>Font Family:</label> <label>Font Family:</label>
@ -434,8 +436,8 @@
<option value="Helvetica">Helvetica</option> <option value="Helvetica">Helvetica</option>
<option value="Georgia">Georgia</option> <option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option> <option value="Verdana">Verdana</option>
</select> </select>
</div> </div>
<div class="toolbar-group"> <div class="toolbar-group">
<label>Font Size:</label> <label>Font Size:</label>
<select onchange={handleFontSizeChange}> <select onchange={handleFontSizeChange}>
@ -449,37 +451,33 @@
<option value="24px">24px</option> <option value="24px">24px</option>
<option value="28px">28px</option> <option value="28px">28px</option>
<option value="32px">32px</option> <option value="32px">32px</option>
</select> </select>
</div> </div>
</div> </div>
<!-- Text Styling --> <!-- Text Styling -->
<div class="toolbar-section"> <div class="toolbar-section text-styling-section">
<div class="toolbar-section-title">Text Styling</div> <div class="toolbar-section-title">Text Styling</div>
<div class="button-grid"> <div class="toolbar-group">
<button class="toolbar-button" onclick={handleBold}> <button class="toolbar-button" onclick={handleBold}>
<lightning-icon icon-name="utility:bold" size="x-small"></lightning-icon> <strong>B</strong>
B
</button> </button>
<button class="toolbar-button" onclick={handleItalic}> <button class="toolbar-button" onclick={handleItalic}>
<lightning-icon icon-name="utility:italic" size="x-small"></lightning-icon> <em>I</em>
I
</button> </button>
<button class="toolbar-button" onclick={handleUnderline}> <button class="toolbar-button" onclick={handleUnderline}>
<lightning-icon icon-name="utility:underline" size="x-small"></lightning-icon> <u>U</u>
U
</button> </button>
<button class="toolbar-button" onclick={handleHighlight}> <button class="toolbar-button" onclick={handleHighlight}>
<lightning-icon icon-name="utility:highlighter" size="x-small"></lightning-icon>
Highlight Highlight
</button> </button>
</div> </div>
</div> </div>
<!-- Text Alignment --> <!-- Text Alignment -->
<div class="toolbar-section"> <div class="toolbar-section text-alignment-section">
<div class="toolbar-section-title">Text Alignment</div> <div class="toolbar-section-title">Text Alignment</div>
<div class="button-grid"> <div class="toolbar-group">
<button class="toolbar-button" onclick={handleAlignLeft}> <button class="toolbar-button" onclick={handleAlignLeft}>
<lightning-icon icon-name="utility:left_align_text" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:left_align_text" size="x-small"></lightning-icon>
Left Left
@ -492,12 +490,8 @@
<lightning-icon icon-name="utility:right_align_text" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:right_align_text" size="x-small"></lightning-icon>
Right Right
</button> </button>
<button class="toolbar-button" onclick={handleJustify}> </div>
<lightning-icon icon-name="utility:justify" size="x-small"></lightning-icon> </div>
Justify
</button>
</div>
</div>
<!-- Lists & Indentation --> <!-- Lists & Indentation -->
<div class="toolbar-section"> <div class="toolbar-section">
@ -551,22 +545,22 @@
</div> </div>
</div> </div>
<!-- Middle - Editor Content Area --> <!-- Right Editor Area -->
<div class="editor-content-area"> <div class="editor-right">
<!-- Export PDF Button --> <!-- Template Header Area with Export Button -->
<div class="export-pdf-section"> <div class="template-header-area">
<lightning-button <!-- Export PDF Button -->
variant="brand" <div class="export-pdf-section">
label="Export PDF" <button class="export-pdf-btn" onclick={generatePdfSimple} disabled={isLoading}>
onclick={generatePdfViaPythonApi} {exportPdfButtonText}
disabled={isNextButtonDisabled} </button>
class="slds-m-top_medium"> </div>
</lightning-button> </div>
</div>
<!-- Single editable area for the template --> <!-- Editable Content Area -->
<div class="preview-frame" contenteditable="true"> <div class="preview-frame" contenteditable="true">
<!-- Template content will be loaded here --> <!-- Template content will be loaded here -->
</div>
</div> </div>
</div> </div>

View File

@ -6,6 +6,7 @@ import getPropertyCount from '@salesforce/apex/PropertyDataController.getPropert
import generatePreview from '@salesforce/apex/PdfApiController.generatePreview'; import generatePreview from '@salesforce/apex/PdfApiController.generatePreview';
import generatePdf from '@salesforce/apex/PdfApiController.generatePdf'; import generatePdf from '@salesforce/apex/PdfApiController.generatePdf';
import generatePdfServerSide from '@salesforce/apex/PdfApiController.generatePdfServerSide'; import generatePdfServerSide from '@salesforce/apex/PdfApiController.generatePdfServerSide';
import callPythonApi from '@salesforce/apex/PropertyPdfGeneratorController.callPythonApi';
export default class PropertyTemplateSelector extends LightningElement { export default class PropertyTemplateSelector extends LightningElement {
@track currentStep = 1; @track currentStep = 1;
@ -541,15 +542,14 @@ export default class PropertyTemplateSelector extends LightningElement {
console.log('selectedTemplateData:', this.selectedTemplateData); console.log('selectedTemplateData:', this.selectedTemplateData);
console.log('selectedPropertyId:', this.selectedPropertyId); console.log('selectedPropertyId:', this.selectedPropertyId);
console.log('propertyData:', this.propertyData); console.log('propertyData:', this.propertyData);
console.log('templates array:', this.templates);
if (!this.selectedTemplateData) { if (!this.selectedTemplateData) {
this.showError('Please select a template first. Current value: ' + JSON.stringify(this.selectedTemplateData)); this.showError('Please select a template first.');
return; return;
} }
if (!this.selectedPropertyId) { if (!this.selectedPropertyId) {
this.showError('Please select a property first. Current value: ' + this.selectedPropertyId); this.showError('Please select a property first.');
return; return;
} }
@ -558,18 +558,12 @@ export default class PropertyTemplateSelector extends LightningElement {
return; return;
} }
// Validate property data for PDF generation
if (!this.validatePropertyDataForPdf()) {
return;
}
this.isLoading = true; this.isLoading = true;
// Generate HTML content based on template and property data // Generate simple HTML content for template preview
this.editorContent = this.createTemplateHTML(); this.editorContent = this.createSimpleTemplateHTML();
console.log('Generated editor content length:', this.editorContent.length); 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 // Move to step 3 (editor) and show header
this.currentStep = 3; this.currentStep = 3;
@ -582,6 +576,58 @@ export default class PropertyTemplateSelector extends LightningElement {
}, 100); }, 100);
} }
// Create simple template HTML for fast Step 2
createSimpleTemplateHTML() {
console.log('=== CREATING SIMPLE TEMPLATE HTML ===');
if (!this.propertyData) {
return '<div style="padding: 20px; text-align: center; color: #666;"><h2>No Property Data</h2><p>Please select a property first.</p></div>';
}
// Simple template with basic styling
return `
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 40px; text-align: center; border-radius: 15px; margin-bottom: 30px;">
<h1 style="margin: 0; font-size: 32px; font-weight: bold;">${this.propertyData.propertyName || 'Property Brochure'}</h1>
<p style="margin: 10px 0 0 0; font-size: 18px; opacity: 0.9;">${this.propertyData.propertyType || 'Property Type'} in ${this.propertyData.location || 'Location'}</p>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;">
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;">Price</div>
<div style="font-size: 18px; color: #333; font-weight: 600;">${this.propertyData.price || 'N/A'}</div>
</div>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;">Bedrooms</div>
<div style="font-size: 18px; color: #333; font-weight: 600;">${this.propertyData.bedrooms || 'N/A'}</div>
</div>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;">Bathrooms</div>
<div style="font-size: 18px; color: #333; font-weight: 600;">${this.propertyData.bathrooms || 'N/A'}</div>
</div>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;">Area</div>
<div style="font-size: 18px; color: #333; font-weight: 600;">${this.propertyData.area || 'N/A'}</div>
</div>
</div>
<div style="background: white; padding: 25px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 30px;">
<h2 style="color: #333; margin-bottom: 20px; font-size: 20px;">Property Details</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div><strong>Type:</strong> ${this.propertyData.propertyType || 'N/A'}</div>
<div><strong>Location:</strong> ${this.propertyData.location || 'N/A'}</div>
<div><strong>Price:</strong> ${this.propertyData.price || 'N/A'}</div>
<div><strong>Bedrooms:</strong> ${this.propertyData.bedrooms || 'N/A'}</div>
<div><strong>Bathrooms:</strong> ${this.propertyData.bathrooms || 'N/A'}</div>
<div><strong>Area:</strong> ${this.propertyData.area || 'N/A'}</div>
</div>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; text-align: center; color: #666;">
<p><em>Template generated successfully! You can now edit this content and export as PDF.</em></p>
</div>
`;
}
// Initialize the rich text editor // Initialize the rich text editor
initializeEditor() { initializeEditor() {
console.log('=== initializeEditor called ==='); console.log('=== initializeEditor called ===');
@ -594,12 +640,8 @@ export default class PropertyTemplateSelector extends LightningElement {
return; return;
} }
// Generate template HTML with property data // Use the already generated content from Step 2
const templateHTML = this.createTemplateHTML(); console.log('Using existing editor content:', this.editorContent);
console.log('Generated template HTML:', templateHTML);
// Set the editor content
this.editorContent = templateHTML;
// Initialize pages for multi-page editing // Initialize pages for multi-page editing
this.initializePages(); this.initializePages();
@ -2621,15 +2663,304 @@ export default class PropertyTemplateSelector extends LightningElement {
} }
insertText() { insertText() {
this.addTextBlock(); this.showTextInsertPopup();
} }
insertImage() { insertImage() {
this.insertImage(); this.showImageInsertPopup();
} }
addShape() { 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', `
<div class="popup-content">
<div class="form-group">
<label for="textInput">Enter your text:</label>
<textarea id="textInput" rows="4" placeholder="Type your text here..." class="form-control"></textarea>
</div>
<div class="form-group">
<label for="textStyle">Text Style:</label>
<select id="textStyle" class="form-control">
<option value="p">Paragraph</option>
<option value="h1">Heading 1</option>
<option value="h2">Heading 2</option>
<option value="h3">Heading 3</option>
<option value="h4">Heading 4</option>
<option value="h5">Heading 5</option>
<option value="h6">Heading 6</option>
</select>
</div>
<div class="popup-actions">
<button class="btn btn-secondary" onclick="this.closest('.custom-popup').remove()">Cancel</button>
<button class="btn btn-primary" onclick="this.insertTextFromPopup()">Insert Text</button>
</div>
</div>
`);
}
showImageInsertPopup() {
this.showCustomPopup('Insert Image', `
<div class="popup-content">
<div class="form-group">
<label for="imageUrl">Image URL:</label>
<input type="url" id="imageUrl" placeholder="https://example.com/image.jpg" class="form-control">
</div>
<div class="form-group">
<label for="imageAlt">Alt Text:</label>
<input type="text" id="imageAlt" placeholder="Image description" class="form-control">
</div>
<div class="form-group">
<label for="imageWidth">Width (px):</label>
<input type="number" id="imageWidth" placeholder="300" class="form-control">
</div>
<div class="popup-actions">
<button class="btn btn-secondary" onclick="this.closest('.custom-popup').remove()">Cancel</button>
<button class="btn btn-primary" onclick="this.insertImageFromPopup()">Insert Image</button>
</div>
</div>
`);
}
showShapeInsertPopup() {
this.showCustomPopup('Insert Shape', `
<div class="popup-content">
<div class="form-group">
<label for="shapeType">Shape Type:</label>
<select id="shapeType" class="form-control">
<option value="rectangle">Rectangle</option>
<option value="circle">Circle</option>
<option value="triangle">Triangle</option>
<option value="diamond">Diamond</option>
</select>
</div>
<div class="form-group">
<label for="shapeWidth">Width (px):</label>
<input type="number" id="shapeWidth" placeholder="100" class="form-control">
</div>
<div class="form-group">
<label for="shapeHeight">Height (px):</label>
<input type="number" id="shapeHeight" placeholder="100" class="form-control">
</div>
<div class="form-group">
<label for="shapeColor">Color:</label>
<input type="color" id="shapeColor" value="#667eea" class="form-control">
</div>
<div class="popup-actions">
<button class="btn btn-secondary" onclick="this.closest('.custom-popup').remove()">Cancel</button>
<button class="btn btn-primary" onclick="this.insertShapeFromPopup()">Insert Shape</button>
</div>
</div>
`);
}
showTableInsertPopup() {
this.showCustomPopup('Insert Table', `
<div class="popup-content">
<div class="form-group">
<label for="tableRows">Number of Rows:</label>
<input type="number" id="tableRows" placeholder="3" min="1" max="20" class="form-control">
</div>
<div class="form-group">
<label for="tableCols">Number of Columns:</label>
<input type="number" id="tableCols" placeholder="3" min="1" max="10" class="form-control">
</div>
<div class="form-group">
<label for="tableBorder">Border Style:</label>
<select id="tableBorder" class="form-control">
<option value="1">Solid Border</option>
<option value="0">No Border</option>
</select>
</div>
<div class="popup-actions">
<button class="btn btn-secondary" onclick="this.closest('.custom-popup').remove()">Cancel</button>
<button class="btn btn-primary" onclick="this.insertTableFromPopup()">Insert Table</button>
</div>
</div>
`);
}
// 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 = `
<div class="popup-overlay"></div>
<div class="popup-container">
<div class="popup-header">
<h3>${title}</h3>
<button class="popup-close" onclick="this.closest('.custom-popup').remove()">&times;</button>
</div>
${content}
</div>
`;
// 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}</${style}>`;
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 = `<img src="${url}" alt="${alt}" style="width: ${width}px; max-width: 100%; height: auto;">`;
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 = `<div style="width: ${width}px; height: ${height}px; background-color: ${color}; border-radius: 4px;"></div>`;
break;
case 'circle':
html = `<div style="width: ${width}px; height: ${height}px; background-color: ${color}; border-radius: 50%;"></div>`;
break;
case 'triangle':
html = `<div style="width: 0; height: 0; border-left: ${width/2}px solid transparent; border-right: ${width/2}px solid transparent; border-bottom: ${height}px solid ${color};"></div>`;
break;
case 'diamond':
html = `<div style="width: ${width}px; height: ${height}px; background-color: ${color}; transform: rotate(45deg);"></div>`;
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 = '<table style="border-collapse: collapse; width: 100%;">';
for (let i = 0; i < rows; i++) {
html += '<tr>';
for (let j = 0; j < cols; j++) {
html += `<td style="border: ${border}px solid #ddd; padding: 8px; text-align: left;">Cell ${i+1}-${j+1}</td>`;
}
html += '</tr>';
}
html += '</table>';
document.execCommand('insertHTML', false, html);
document.querySelector('.custom-popup').remove();
this.showSuccess('Table inserted successfully!');
}
} }
// Create safe property display string // Create safe property display string
@ -2717,13 +3048,13 @@ export default class PropertyTemplateSelector extends LightningElement {
return true; return true;
} }
// Generate PDF using Apex server-side generation // Generate PDF using Python API
async generatePdfSimple() { async generatePdfSimple() {
try { try {
console.log('=== GENERATING PDF VIA APEX ==='); console.log('=== GENERATING PDF VIA PYTHON API ===');
// Show progress // Show progress
this.showProgress('Generating PDF via server...'); this.showProgress('Generating PDF via Python API...');
// Get current editor content // Get current editor content
const editorFrame = this.template.querySelector('.preview-frame'); const editorFrame = this.template.querySelector('.preview-frame');
@ -2733,8 +3064,8 @@ export default class PropertyTemplateSelector extends LightningElement {
this.editorContent = editorFrame.innerHTML; this.editorContent = editorFrame.innerHTML;
// Generate PDF using Apex // Generate PDF using Python API
await this.generatePdfViaApex(); await this.generatePdfViaPythonApi();
this.showSuccess('PDF generated successfully!'); 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() { async generatePdfViaApex() {
try { try {
console.log('Calling Apex PDF generation...'); console.log('Calling Apex PDF generation...');
// Prepare property data - only essential fields to avoid URL length issues // Prepare property data
const propertyData = { const propertyData = {
propertyName: this.propertyData.propertyName || 'Property Brochure', propertyName: this.propertyData.propertyName || 'Property Brochure',
propertyType: this.propertyData.propertyType || 'Property Type', propertyType: this.propertyData.propertyType || 'Property Type',
@ -2767,7 +3173,7 @@ export default class PropertyTemplateSelector extends LightningElement {
const baseUrl = window.location.origin; const baseUrl = window.location.origin;
const visualforceUrl = `${baseUrl}/apex/PropertyPdfGenerator`; const visualforceUrl = `${baseUrl}/apex/PropertyPdfGenerator`;
// Add query parameters - simplified to avoid URL length issues // Add query parameters
const params = new URLSearchParams({ const params = new URLSearchParams({
template: this.selectedTemplate || 'default', template: this.selectedTemplate || 'default',
propertyData: JSON.stringify(propertyData), propertyData: JSON.stringify(propertyData),
@ -2777,38 +3183,22 @@ export default class PropertyTemplateSelector extends LightningElement {
const fullUrl = `${visualforceUrl}?${params.toString()}`; const fullUrl = `${visualforceUrl}?${params.toString()}`;
console.log('Opening PDF URL:', fullUrl); console.log('Opening PDF URL:', fullUrl);
console.log('URL length:', fullUrl.length);
// Try to open the URL and handle any errors // Open the Visualforce page in a new window/tab
try { const pdfWindow = window.open(fullUrl, '_blank');
// Open the Visualforce page in a new window/tab
const pdfWindow = window.open(fullUrl, '_blank');
if (pdfWindow) { if (pdfWindow) {
// Check if the window opened successfully setTimeout(() => {
setTimeout(() => { try {
try { if (!pdfWindow.closed) {
if (pdfWindow.closed) { pdfWindow.close();
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');
} }
}, 1000); } catch (e) {
} else { console.log('Could not check window status');
throw new Error('Could not open PDF window. Popup may be blocked.'); }
} }, 2000);
} else {
} catch (windowError) { throw new Error('Could not open PDF window. Popup may be blocked.');
console.error('Window error:', windowError);
// Fallback: try to navigate to the URL directly
window.location.href = fullUrl;
} }
console.log('PDF generation initiated via Apex'); console.log('PDF generation initiated via Apex');
@ -2818,120 +3208,4 @@ export default class PropertyTemplateSelector extends LightningElement {
throw error; 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 `
<div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 40px; text-align: center; border-radius: 15px; margin-bottom: 30px;" class="property-header">
<h1 style="margin: 0; font-size: 32px; font-weight: bold;">${propertyData.propertyName || 'Property Brochure'}</h1>
<p style="margin: 10px 0 0 0; font-size: 18px; opacity: 0.9;">${propertyData.propertyType || 'Property Type'} in ${propertyData.location || 'Location'}</p>
<p style="margin: 5px 0 0 0; font-size: 14px; opacity: 0.8;">Reference: ${propertyData.propertyName || 'N/A'}</p>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;" class="property-grid">
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);" class="property-card">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;" class="property-card-label">Price</div>
<div style="font-size: 18px; color: #333; font-weight: 600;" class="property-card-value">${propertyData.price || 'N/A'}</div>
</div>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);" class="property-card">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;" class="property-card-label">Bedrooms</div>
<div style="font-size: 18px; color: #333; font-weight: 600;" class="property-card-value">${propertyData.bedrooms || 'N/A'}</div>
</div>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);" class="property-card">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;" class="property-card-label">Bathrooms</div>
<div style="font-size: 18px; color: #333; font-weight: 600;" class="property-card-value">${propertyData.bathrooms || 'N/A'}</div>
</div>
<div style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);" class="property-card">
<div style="font-weight: bold; color: #667eea; font-size: 14px; margin-bottom: 10px; text-transform: uppercase;" class="property-card-label">Area</div>
<div style="font-size: 18px; color: #333; font-weight: 600;" class="property-card-value">${propertyData.area || 'N/A'}</div>
</div>
</div>
<div style="background: white; padding: 25px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 30px;" class="property-details">
<h2 style="color: #333; margin-bottom: 20px; font-size: 20px;">Property Details</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div><strong>Status:</strong> Pocket Listing / Off Market</div>
<div><strong>Type:</strong> ${propertyData.propertyType || 'N/A'}</div>
<div><strong>Floor:</strong> ${propertyData.floor || 'N/A'} of ${propertyData.totalFloors || 'N/A'}</div>
<div><strong>Parking:</strong> ${propertyData.parkingSpaces || '1'} spaces</div>
</div>
</div>
`;
}
} }

View File

@ -3,5 +3,5 @@
<disableProtocolSecurity>false</disableProtocolSecurity> <disableProtocolSecurity>false</disableProtocolSecurity>
<isActive>true</isActive> <isActive>true</isActive>
<url>https://salesforce.tech4biz.io</url> <url>https://salesforce.tech4biz.io</url>
<description>Python PDF Generation API - Production</description> <description>Python PDF Generator API</description>
</RemoteSiteSetting> </RemoteSiteSetting>