v1.0.1-beta

This commit is contained in:
rohit 2025-09-01 00:27:07 +05:30
parent 3907ae4b7e
commit de4347330f
8 changed files with 2700 additions and 17939 deletions

View File

@ -1,93 +0,0 @@
# 🏠 Property Brochure Generator
Professional PDF generation system for real estate properties with Salesforce integration.
## 🚀 **Quick Start**
### **1. Deploy LWC to Salesforce**
```bash
chmod +x deploy-lwc-production.sh
./deploy-lwc-production.sh
```
### **2. Deploy PDF API to Your Server**
```bash
cd python-pdf-generator
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python3 api_server.py
```
### **3. Update LWC with Your API URL**
Edit these files with your actual server IP:
**LWC JavaScript:**
```javascript
// force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js
pdfApiBaseUrl = 'https://YOUR-ACTUAL-IP:8000/api';
```
**Apex Controller:**
```apex
// force-app/main/default/classes/PropertyTemplateController.cls
String apiEndpoint = 'https://YOUR-ACTUAL-IP:8000/api/generate-pdf';
```
**Production Config:**
```javascript
// force-app/main/default/lwc/propertyTemplateSelector/production-config.js
PDF_API_BASE_URL: 'https://YOUR-ACTUAL-IP:8000/api'
```
## 📁 **Project Structure**
```
├── force-app/ # Salesforce LWC Components
│ └── main/default/
│ ├── lwc/propertyTemplateSelector/ # Main LWC Component
│ ├── classes/PropertyTemplateController.cls # Apex Controller
│ └── objects/ # Custom Objects
├── python-pdf-generator/ # PDF Generation API
│ ├── api_server.py # FastAPI Server
│ ├── property_pdf_generator.py # PDF Generation Logic
│ └── requirements.txt # Python Dependencies
├── deploy-lwc-production.sh # LWC Deployment Script
└── README.md # This File
```
## 🔧 **Features**
- **5-Step Wizard**: Template → Property → Details → Preview → Download
- **Multiple Templates**: 1-page, 3-page, 5-page, Luxury, Modern
- **Real-time Preview**: Instant customization updates
- **Image Management**: Multiple images with room names
- **Salesforce Integration**: Direct access to pcrm__Property__c data
- **Responsive Design**: Works on all devices
## 📊 **API Endpoints**
- **Base URL**: `https://YOUR-IP:8000/api`
- **Preview**: `POST /preview`
- **Generate PDF**: `POST /generate-pdf`
- **Health Check**: `GET /health`
- **Templates**: `GET /templates`
## 🔒 **Security**
- Configure CORS for your Salesforce domain
- Use HTTPS in production
- Implement authentication if needed
- Configure firewall for port 8000
## 📞 **Support**
For issues or questions, check the deployment logs and ensure:
- Salesforce CLI is installed
- Python dependencies are installed
- API server is running on your IP
- LWC has correct API URL
---
**🎯 Production-ready system for generating professional property brochures!**

View File

@ -1,7 +1,7 @@
public with sharing class PDFGenerationProxy { public with sharing class PDFGenerationProxy {
@AuraEnabled @AuraEnabled
public static String generatePDFFromHTML(String htmlContent) { public static String generatePDFFromHTML(String htmlContent, String pageSize) {
try { try {
// Prepare the request // Prepare the request
Http http = new Http(); Http http = new Http();
@ -10,17 +10,23 @@ public with sharing class PDFGenerationProxy {
request.setMethod('POST'); request.setMethod('POST');
request.setHeader('Content-Type', 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'); request.setHeader('Content-Type', 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW');
// Create multipart form data // Create multipart form data with page size
String boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'; String boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW';
String body = ''; String body = '';
body += '--' + boundary + '\r\n'; body += '--' + boundary + '\r\n';
body += 'Content-Disposition: form-data; name="input"; filename="template.html"\r\n'; body += 'Content-Disposition: form-data; name="input"; filename="template.html"\r\n';
body += 'Content-Type: text/html\r\n\r\n'; body += 'Content-Type: text/html\r\n\r\n';
body += htmlContent + '\r\n'; body += htmlContent + '\r\n';
body += '--' + boundary + '\r\n';
body += 'Content-Disposition: form-data; name="pageSize"\r\n\r\n';
body += (pageSize != null ? pageSize : 'A4') + '\r\n';
body += '--' + boundary + '\r\n';
body += 'Content-Disposition: form-data; name="maxSize"\r\n\r\n';
body += '50000000\r\n'; // 50MB size limit
body += '--' + boundary + '--\r\n'; body += '--' + boundary + '--\r\n';
request.setBody(body); request.setBody(body);
request.setTimeout(120000); // 2 minutes timeout request.setTimeout(120000); // 2 minutes timeout (Salesforce maximum)
// Make the callout // Make the callout
HttpResponse response = http.send(request); HttpResponse response = http.send(request);
@ -43,7 +49,7 @@ public with sharing class PDFGenerationProxy {
try { try {
// Test with simple HTML // Test with simple HTML
String testHtml = '<!DOCTYPE html><html><head><title>Test</title></head><body><h1>Test PDF Generation</h1></body></html>'; String testHtml = '<!DOCTYPE html><html><head><title>Test</title></head><body><h1>Test PDF Generation</h1></body></html>';
return generatePDFFromHTML(testHtml); return generatePDFFromHTML(testHtml, 'A4');
} catch (Exception e) { } catch (Exception e) {
throw new AuraHandledException('API test failed: ' + e.getMessage()); throw new AuraHandledException('API test failed: ' + e.getMessage());
} }

View File

@ -5751,6 +5751,30 @@
color: white; color: white;
} }
.doc-action-btn.undo-btn {
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
border-color: #9ca3af;
color: #374151;
}
.doc-action-btn.undo-btn:hover {
background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
border-color: #6b7280;
color: #111827;
}
.doc-action-btn.redo-btn {
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
border-color: #9ca3af;
color: #374151;
}
.doc-action-btn.redo-btn:hover {
background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
border-color: #6b7280;
color: #111827;
}
/* Generate PDF Section in Template Header */ /* Generate PDF Section in Template Header */
.generate-pdf-section { .generate-pdf-section {
display: flex; display: flex;
@ -6474,28 +6498,7 @@
flex-shrink: 0; flex-shrink: 0;
} }
/* Scroll hint for areas */
.scroll-hint {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(102, 126, 234, 0.1);
color: #667eea;
padding: 4px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 5;
}
.enhanced-toolbar:hover .scroll-hint,
.editor-right:hover .scroll-hint {
opacity: 1;
}
/* Enhanced visual feedback for scrollable areas */ /* Enhanced visual feedback for scrollable areas */
.enhanced-toolbar-scroll:hover, .enhanced-toolbar-scroll:hover,
@ -8131,3 +8134,293 @@ button, .btn, .toolbar-button, .export-pdf-btn {
gap: 1rem; gap: 1rem;
} }
} }
/* Draggable Table Button Styles */
.draggable-table-btn {
position: relative;
transition: all 0.2s ease;
}
.draggable-table-btn:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
}
.draggable-table-btn:active {
cursor: grabbing;
transform: scale(0.95);
}
.draggable-table-btn.dragging {
opacity: 0.5;
transform: rotate(5deg);
}
/* Editor Drag Over Styles */
.enhanced-editor-content.drag-over {
background: linear-gradient(45deg, #f0f4ff 25%, transparent 25%),
linear-gradient(-45deg, #f0f4ff 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #f0f4ff 75%),
linear-gradient(-45deg, transparent 75%, #f0f4ff 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
border: 2px dashed #4f46e5;
border-radius: 8px;
}
/* Table Drop Indicator */
.table-drop-indicator {
position: absolute;
border: 2px dashed #4f46e5;
background: rgba(79, 70, 229, 0.1);
border-radius: 4px;
pointer-events: none;
z-index: 1000;
display: none;
}
.table-drop-indicator.show {
display: block;
}
/* Editable Table Container Styles */
.editable-table-container {
position: relative;
margin: 1rem 0;
border: 2px solid transparent;
border-radius: 8px;
transition: all 0.2s ease;
}
.editable-table-container:hover {
border-color: #4f46e5;
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.15);
}
.editable-table-container.dragging {
opacity: 0.5;
transform: rotate(2deg);
z-index: 1000;
}
/* Table Controls */
.table-controls {
position: absolute;
top: -40px;
left: 0;
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.5rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
gap: 0.5rem;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 100;
}
.editable-table-container:hover .table-controls {
opacity: 1;
}
.table-control-group {
display: flex;
gap: 0.25rem;
align-items: center;
}
.table-control-group:not(:last-child) {
border-right: 1px solid #e2e8f0;
padding-right: 0.5rem;
margin-right: 0.5rem;
}
.table-control-btn {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 4px;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
color: #475569;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
user-select: none;
}
.table-control-btn:hover {
background: #e2e8f0;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
.table-control-btn:active {
transform: translateY(0);
background: #cbd5e1;
}
.table-control-btn.delete {
background: #fef2f2;
border-color: #fecaca;
color: #dc2626;
}
.table-control-btn.delete:hover {
background: #fee2e2;
border-color: #fca5a5;
color: #b91c1c;
}
.table-control-btn.delete:active {
background: #fecaca;
}
/* Table Container Drag Styles */
.editable-table-container[draggable="true"] {
cursor: move;
}
.editable-table-container[draggable="true"]:hover {
cursor: grab;
}
.editable-table-container[draggable="true"]:active {
cursor: grabbing;
}
/* Prevent text selection on controls */
.table-controls * {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
/* Responsive table controls */
@media (max-width: 768px) {
.table-controls {
position: relative;
top: 0;
left: 0;
opacity: 1;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.table-control-btn {
font-size: 0.6875rem;
padding: 0.25rem 0.375rem;
}
.table-control-group:not(:last-child) {
border-right: none;
padding-right: 0;
margin-right: 0;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
}
/* Draggable and resizable image styles */
img[draggable="true"] {
position: relative;
z-index: 1000;
cursor: move;
}
.resize-handle {
position: absolute;
width: 8px;
height: 8px;
background: #007bff;
border: 1px solid white;
z-index: 1001;
}
.resize-handle.resize-nw {
top: -4px;
left: -4px;
cursor: nw-resize;
}
.resize-handle.resize-ne {
top: -4px;
right: -4px;
cursor: ne-resize;
}
.resize-handle.resize-sw {
bottom: -4px;
left: -4px;
cursor: sw-resize;
}
.resize-handle.resize-se {
bottom: -4px;
right: -4px;
cursor: se-resize;
}
/* Selector mode styles */
.enhanced-editor-content.selector-mode {
cursor: crosshair;
}
.selector-options-panel {
position: fixed;
top: 10px;
right: 10px;
background: white;
border: 2px solid #007bff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 10000;
min-width: 200px;
max-width: 250px;
}
.property-image-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border: 2px solid #007bff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 10001;
max-width: 400px;
max-height: 500px;
overflow-y: auto;
}
/* Bullet and numbering styles */
ul, ol {
list-style-type: none;
padding-left: 0;
}
ul li, ol li {
margin-left: 20px;
position: relative;
}
ul li:before {
content: "* ";
position: absolute;
left: -20px;
}
ol li:before {
content: "1. ";
position: absolute;
left: -20px;
}

View File

@ -1,3 +1,5 @@
<template> <template>
<div class="property-brochure-generator"> <div class="property-brochure-generator">
<!-- Header Section - Editable --> <!-- Header Section - Editable -->
@ -685,6 +687,9 @@
<!-- Category Navigation --> <!-- Category Navigation -->
<div class="category-navigation-step2"> <div class="category-navigation-step2">
<button class="category-btn-step2" onclick={selectCategory} data-category="None">
None
</button>
<button class="category-btn-step2" onclick={selectCategory} data-category="Interior"> <button class="category-btn-step2" onclick={selectCategory} data-category="Interior">
Interior Interior
</button> </button>
@ -774,7 +779,6 @@
<!-- Enhanced Editor Toolbar --> <!-- Enhanced Editor Toolbar -->
<div class="enhanced-toolbar"> <div class="enhanced-toolbar">
<div class="scroll-hint">⬇️ Scroll for more tools</div>
<div class="enhanced-toolbar-scroll"> <div class="enhanced-toolbar-scroll">
<!-- Property Insert Section --> <!-- Property Insert Section -->
<div class="toolbar-section"> <div class="toolbar-section">
@ -814,13 +818,39 @@
</div> </div>
</div> </div>
<!-- Document Actions -->
<div class="toolbar-section">
<div class="toolbar-section-title">Document Actions</div>
<div class="document-actions-grid">
<button class="doc-action-btn undo-btn" onclick={undo} title="Undo (Ctrl+Z)">
<lightning-icon icon-name="utility:undo" size="x-small"></lightning-icon>
Undo
</button>
<button class="doc-action-btn redo-btn" onclick={redo} title="Redo (Ctrl+Y)">
<lightning-icon icon-name="utility:redo" size="x-small"></lightning-icon>
Redo
</button>
<button class="doc-action-btn save-btn" onclick={handleSave} title="Save Template">
<lightning-icon icon-name="utility:save" size="x-small"></lightning-icon>
Save
</button>
<button class="doc-action-btn load-btn" onclick={handleLoad} title="Load Template">
<lightning-icon icon-name="utility:upload" size="x-small"></lightning-icon>
Load
</button>
<button class="doc-action-btn reset-btn" onclick={handleReset} title="Reset Template">
<lightning-icon icon-name="utility:refresh" size="x-small"></lightning-icon>
Reset
</button>
</div>
</div>
<!-- Text Formatting Section --> <!-- Text Formatting Section -->
<div class="toolbar-section"> <div class="toolbar-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>
<select onchange={handleFontFamilyChange}> <select onchange={handleFontFamilyChange}>
<option value="Inter">Inter</option>
<option value="Arial">Arial</option> <option value="Arial">Arial</option>
<option value="Times New Roman">Times New Roman</option> <option value="Times New Roman">Times New Roman</option>
<option value="Helvetica">Helvetica</option> <option value="Helvetica">Helvetica</option>
@ -831,6 +861,13 @@
<div class="toolbar-group"> <div class="toolbar-group">
<label>Font Size:</label> <label>Font Size:</label>
<select onchange={handleFontSizeChange}> <select onchange={handleFontSizeChange}>
<option value="1px">1px</option>
<option value="2px">2px</option>
<option value="3px">3px</option>
<option value="4px">4px</option>
<option value="5px">5px</option>
<option value="6px">6px</option>
<option value="7px">7px</option>
<option value="8px">8px</option> <option value="8px">8px</option>
<option value="10px">10px</option> <option value="10px">10px</option>
<option value="12px">12px</option> <option value="12px">12px</option>
@ -938,11 +975,30 @@
</button> </button>
</div> </div>
<div class="toolbar-group"> <div class="toolbar-group">
<button class="toolbar-button" onclick={addShape}> <button class="toolbar-button" onclick={toggleSelectorMode}>
<lightning-icon icon-name="utility:shape" size="x-small"></lightning-icon> <lightning-icon icon-name="utility:target" size="x-small"></lightning-icon>
Shape <span class="selector-mode-text">Selector Mode</span>
</button> </button>
<button class="toolbar-button" onclick={openTableDialog}> </div>
<div class="toolbar-group selector-controls" style="display: none;">
<button class="toolbar-button" onclick={moveElementUp} title="Move Up">
<lightning-icon icon-name="utility:up" size="x-small"></lightning-icon>
Move Up
</button>
<button class="toolbar-button" onclick={moveElementDown} title="Move Down">
<lightning-icon icon-name="utility:down" size="x-small"></lightning-icon>
Move Down
</button>
<button class="toolbar-button" onclick={removeSelectedElement} title="Delete Element" style="background: #dc3545; color: white;">
<lightning-icon icon-name="utility:delete" size="x-small"></lightning-icon>
Delete
</button>
</div>
<div class="toolbar-group">
<button class="toolbar-button draggable-table-btn"
draggable="true"
ondragstart={handleTableDragStart}
onclick={openTableDialog}>
<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>
@ -951,24 +1007,7 @@
<!-- Document Actions -->
<div class="toolbar-section">
<div class="toolbar-section-title">Document Actions</div>
<div class="document-actions-grid">
<button class="doc-action-btn save-btn" onclick={handleSave} title="Save Template">
<lightning-icon icon-name="utility:save" size="x-small"></lightning-icon>
Save
</button>
<button class="doc-action-btn load-btn" onclick={handleLoad} title="Load Template">
<lightning-icon icon-name="utility:upload" size="x-small"></lightning-icon>
Load
</button>
<button class="doc-action-btn reset-btn" onclick={handleReset} title="Reset Template">
<lightning-icon icon-name="utility:refresh" size="x-small"></lightning-icon>
Reset
</button>
</div>
</div>
</div> <!-- Close enhanced-toolbar-scroll --> </div> <!-- Close enhanced-toolbar-scroll -->
</div> </div>
</div> </div>
@ -1013,7 +1052,7 @@
<!-- Enhanced Content Editor --> <!-- Enhanced Content Editor -->
<div class="enhanced-editor-content" contenteditable="true" onkeyup={handleContentChange} <div class="enhanced-editor-content" contenteditable="true" onkeyup={handleContentChange}
onpaste={handleContentChange}> onpaste={handleContentChange} ondragover={handleEditorDragOver} ondrop={handleEditorDrop}>
<!-- Template content will be loaded here --> <!-- Template content will be loaded here -->
{htmlContent} {htmlContent}
</div> </div>
@ -1034,6 +1073,7 @@
<div class="image-review-content"> <div class="image-review-content">
<!-- Category Navigation --> <!-- Category Navigation -->
<div class="category-navigation"> <div class="category-navigation">
<button class="category-btn" onclick={selectCategory} data-category="None">None</button>
<button class="category-btn" onclick={selectCategory} data-category="Interior">Interior</button> <button class="category-btn" onclick={selectCategory} data-category="Interior">Interior</button>
<button class="category-btn" onclick={selectCategory} data-category="Exterior">Exterior</button> <button class="category-btn" onclick={selectCategory} data-category="Exterior">Exterior</button>
<button class="category-btn" onclick={selectCategory} data-category="Kitchen">Kitchen</button> <button class="category-btn" onclick={selectCategory} data-category="Kitchen">Kitchen</button>
@ -1098,15 +1138,16 @@
<!-- Property Images Tab --> <!-- Property Images Tab -->
<div if:true={showPropertyImagesTab} class="property-images-section"> <div if:true={showPropertyImagesTab} class="property-images-section">
<!-- Category Navigation --> <!-- Category Navigation -->
<div class="category-navigation-replacement"> <div class="category-navigation-step2">
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Interior">Interior</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="None">None</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Exterior">Exterior</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Interior">Interior</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Kitchen">Kitchen</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Exterior">Exterior</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Bedroom">Bedroom</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Kitchen">Kitchen</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Living Area">Living Area</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Bedroom">Bedroom</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Parking">Parking</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Living Area">Living Area</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Anchor">Anchor</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Parking">Parking</button>
<button class="category-btn-replacement" onclick={selectReplacementCategory} data-category="Maps">Maps</button> <button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Anchor">Anchor</button>
<button class="category-btn-step2" onclick={selectReplacementCategory} data-category="Maps">Maps</button>
</div> </div>
<!-- Image Grid --> <!-- Image Grid -->

File diff suppressed because one or more lines are too long