full functional toolbox
This commit is contained in:
parent
ab0b08ee34
commit
355f787a81
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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()">×</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>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
Loading…
Reference in New Issue
Block a user