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>';
}
}
// 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

@ -401,7 +401,7 @@
</div>
<!-- Insert Content -->
<div class="toolbar-section">
<div class="toolbar-section insert-content-section">
<div class="toolbar-section-title">Insert Content</div>
<div class="toolbar-group">
<button class="toolbar-button" onclick={insertText}>
@ -412,6 +412,8 @@
<lightning-icon icon-name="utility:image" size="x-small"></lightning-icon>
Image
</button>
</div>
<div class="toolbar-group">
<button class="toolbar-button" onclick={addShape}>
<lightning-icon icon-name="utility:shape" size="x-small"></lightning-icon>
Shape
@ -424,7 +426,7 @@
</div>
<!-- Text Formatting -->
<div class="toolbar-section">
<div class="toolbar-section text-formatting-section">
<div class="toolbar-section-title">Text Formatting</div>
<div class="toolbar-group">
<label>Font Family:</label>
@ -454,32 +456,28 @@
</div>
<!-- Text Styling -->
<div class="toolbar-section">
<div class="toolbar-section text-styling-section">
<div class="toolbar-section-title">Text Styling</div>
<div class="button-grid">
<div class="toolbar-group">
<button class="toolbar-button" onclick={handleBold}>
<lightning-icon icon-name="utility:bold" size="x-small"></lightning-icon>
B
<strong>B</strong>
</button>
<button class="toolbar-button" onclick={handleItalic}>
<lightning-icon icon-name="utility:italic" size="x-small"></lightning-icon>
I
<em>I</em>
</button>
<button class="toolbar-button" onclick={handleUnderline}>
<lightning-icon icon-name="utility:underline" size="x-small"></lightning-icon>
U
<u>U</u>
</button>
<button class="toolbar-button" onclick={handleHighlight}>
<lightning-icon icon-name="utility:highlighter" size="x-small"></lightning-icon>
Highlight
</button>
</div>
</div>
<!-- Text Alignment -->
<div class="toolbar-section">
<div class="toolbar-section text-alignment-section">
<div class="toolbar-section-title">Text Alignment</div>
<div class="button-grid">
<div class="toolbar-group">
<button class="toolbar-button" onclick={handleAlignLeft}>
<lightning-icon icon-name="utility:left_align_text" size="x-small"></lightning-icon>
Left
@ -492,10 +490,6 @@
<lightning-icon icon-name="utility:right_align_text" size="x-small"></lightning-icon>
Right
</button>
<button class="toolbar-button" onclick={handleJustify}>
<lightning-icon icon-name="utility:justify" size="x-small"></lightning-icon>
Justify
</button>
</div>
</div>
@ -551,24 +545,24 @@
</div>
</div>
<!-- Middle - Editor Content Area -->
<div class="editor-content-area">
<!-- Right Editor Area -->
<div class="editor-right">
<!-- Template Header Area with Export Button -->
<div class="template-header-area">
<!-- Export PDF Button -->
<div class="export-pdf-section">
<lightning-button
variant="brand"
label="Export PDF"
onclick={generatePdfViaPythonApi}
disabled={isNextButtonDisabled}
class="slds-m-top_medium">
</lightning-button>
<button class="export-pdf-btn" onclick={generatePdfSimple} disabled={isLoading}>
{exportPdfButtonText}
</button>
</div>
</div>
<!-- Single editable area for the template -->
<!-- Editable Content Area -->
<div class="preview-frame" contenteditable="true">
<!-- Template content will be loaded here -->
</div>
</div>
</div>
<!-- Right Toolbar - Removed, consolidated into left toolbar -->
</div>

View File

@ -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;
}
@ -558,18 +558,12 @@ export default class PropertyTemplateSelector extends LightningElement {
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;
@ -582,6 +576,58 @@ export default class PropertyTemplateSelector extends LightningElement {
}, 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
initializeEditor() {
console.log('=== initializeEditor called ===');
@ -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', `
<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
@ -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,40 +3183,24 @@ 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(() => {
if (!pdfWindow.closed) {
pdfWindow.close();
}, 2000);
}
} catch (e) {
console.log('Could not check window status');
}
}, 1000);
}, 2000);
} 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;
}
console.log('PDF generation initiated via Apex');
} catch (error) {
@ -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 `
<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>
<isActive>true</isActive>
<url>https://salesforce.tech4biz.io</url>
<description>Python PDF Generation API - Production</description>
<description>Python PDF Generator API</description>
</RemoteSiteSetting>