+
Text Styling
-
-
@@ -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}${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 = `

`;
+
+ 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 += `| Cell ${i+1}-${j+1} | `;
+ }
+ html += '
';
+ }
+
+ html += '
';
+
+ 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 `
-
-
-
-
-
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