commit e284e4ace60d15cf4916e8a3547fe7b682a60c6c Author: rohit Date: Sat Aug 23 22:55:07 2025 +0530 v1.0.0-alpha diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e3f0e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual Environment +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ + +# Generated PDFs +generated_pdfs/ +*.pdf + +# Environment variables +.env +.env.local +.env.production + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Temporary files +*.tmp +*.temp +temp/ +tmp/ + +# Salesforce +.sf/ +.sfdx/ +*.zip \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..a4d9c6d --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,122 @@ +# ๐Ÿš€ Production Deployment Guide + +## ๐Ÿ“‹ **Prerequisites** + +- Salesforce CLI (sf) installed +- Python 3.8+ on your server +- Access to your Salesforce sandbox + +## ๐ŸŽฏ **Step 1: Deploy LWC to Salesforce** + +```bash +# Make script executable +chmod +x deploy-lwc-production.sh + +# Run deployment +./deploy-lwc-production.sh +``` + +**Expected Output:** +``` +โœ… LWC deployment successful! +โœ… Custom objects deployed! +โœ… Permission set created! +โœ… Lightning App Page created! +``` + +## ๐ŸŒ **Step 2: Deploy PDF API to Your Server** + +```bash +# On your server +cd python-pdf-generator + +# Create virtual environment +python3 -m venv venv +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Start API server +python3 api_server.py +``` + +**Server will start on:** `http://0.0.0.0:8000` + +## ๐Ÿ”ง **Step 3: Configure LWC with Your API URL** + +Edit `force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js`: + +```javascript +// Change this line +pdfApiBaseUrl = 'https://YOUR-ACTUAL-IP:8000/api'; +``` + +**Replace `YOUR-ACTUAL-IP` with your server's IP address.** + +## ๐Ÿ”’ **Step 4: Security Configuration** + +### **Firewall Setup:** +```bash +# Open port 8000 +sudo ufw allow 8000 +``` + +### **CORS Configuration (if needed):** +Edit `python-pdf-generator/api_server.py`: +```python +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "https://tso3--r1.sandbox.lightning.force.com", + "https://your-salesforce-domain.com" + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +``` + +## ๐Ÿงช **Step 5: Test Your Deployment** + +1. **Open Salesforce Sandbox:** `https://tso3--r1.sandbox.lightning.force.com` +2. **Login:** `contact+tso3@propertycrm.ae.r1` / `Demo@123` +3. **Search for:** "Property Brochure Generator" +4. **Test the flow:** Template โ†’ Property โ†’ Preview โ†’ Download + +## ๐Ÿ“Š **Expected Results** + +- โœ… LWC loads in Salesforce +- โœ… Properties load from your data +- โœ… PDF preview generates +- โœ… PDF downloads successfully +- โœ… All 23+ properties accessible + +## ๐Ÿšจ **Troubleshooting** + +### **LWC Not Loading:** +- Check deployment logs +- Verify permission sets +- Check user access + +### **API Connection Failed:** +- Verify IP address in LWC +- Check firewall settings +- Ensure API server is running + +### **No Properties Found:** +- Verify `pcrm__Property__c` object exists +- Check field permissions +- Verify data in sandbox + +## ๐Ÿ“ž **Support** + +For deployment issues: +1. Check Salesforce CLI logs +2. Verify API server status +3. Check browser console for errors +4. Ensure all prerequisites are met + +--- + +**๐ŸŽฏ Your system is now production-ready!** \ No newline at end of file diff --git a/PRODUCTION-CHECKLIST.md b/PRODUCTION-CHECKLIST.md new file mode 100644 index 0000000..7ea9a16 --- /dev/null +++ b/PRODUCTION-CHECKLIST.md @@ -0,0 +1,137 @@ +# ๐Ÿš€ Production Deployment Checklist - Property Brochure Generator LWC + +## ๐Ÿ“‹ **Pre-Deployment Checklist** + +### **โœ… LWC Component Ready** +- [ ] All LLM-style emojis removed from UI +- [ ] Professional SVG icons implemented +- [ ] Error handling and validation implemented +- [ ] Toast notifications configured +- [ ] Loading states implemented +- [ ] Responsive design tested + +### **โœ… Configuration Updated** +- [ ] PDF API URL updated in `propertyTemplateSelector.js` +- [ ] PDF API URL updated in `PropertyTemplateController.cls` +- [ ] Production configuration file created +- [ ] Error messages customized for production +- [ ] Success messages customized for production + +### **โœ… Code Quality** +- [ ] No console.log statements in production code +- [ ] Error boundaries implemented +- [ ] Performance optimizations applied +- [ ] Accessibility features implemented +- [ ] Cross-browser compatibility tested + +## ๐ŸŒ **PDF API Server Deployment** + +### **โœ… Server Configuration** +- [ ] Python API server deployed to your IP +- [ ] Port 8000 opened in firewall +- [ ] HTTPS configured (recommended for production) +- [ ] CORS configured for Salesforce domains +- [ ] Environment variables set + +### **โœ… API Endpoints Working** +- [ ] `/api/health` - Health check endpoint +- [ ] `/api/preview` - PDF preview generation +- [ ] `/api/generate-pdf` - PDF generation and download +- [ ] `/api/templates` - Available templates + +## ๐Ÿ”ง **Salesforce Configuration** + +### **โœ… Custom Objects Deployed** +- [ ] `Property_Template__c` object deployed +- [ ] `Property__c` object fields updated +- [ ] Permission sets configured +- [ ] User access granted + +### **โœ… LWC Deployment** +- [ ] Component deployed to sandbox +- [ ] Lightning App Page created +- [ ] Component added to page layouts +- [ ] User permissions verified + +## ๐Ÿ“ฑ **Testing Checklist** + +### **โœ… Functionality Testing** +- [ ] Template selection working +- [ ] Property data loading from Salesforce +- [ ] Form validation working +- [ ] Image upload functionality +- [ ] PDF preview generation +- [ ] PDF download working + +### **โœ… User Experience Testing** +- [ ] 5-step wizard flow smooth +- [ ] Error messages clear and helpful +- [ ] Loading states informative +- [ ] Responsive on mobile devices +- [ ] Accessibility features working + +### **โœ… Integration Testing** +- [ ] Salesforce data integration working +- [ ] PDF API communication successful +- [ ] Error handling graceful +- [ ] Performance acceptable + +## ๐Ÿšจ **Production Security** + +### **โœ… Security Measures** +- [ ] API endpoints secured +- [ ] CORS properly configured +- [ ] Input validation implemented +- [ ] File upload restrictions set +- [ ] Error messages don't expose sensitive data + +### **โœ… Monitoring & Logging** +- [ ] Error logging configured +- [ ] Performance monitoring enabled +- [ ] User activity tracking +- [ ] API usage monitoring + +## ๐Ÿ“Š **Performance Optimization** + +### **โœ… Performance Settings** +- [ ] Image compression enabled +- [ ] PDF generation optimized +- [ ] Caching implemented +- [ ] Database queries optimized +- [ ] API response times acceptable + +## ๐Ÿ”„ **Post-Deployment** + +### **โœ… Verification** +- [ ] All users can access component +- [ ] PDF generation working for all templates +- [ ] Error handling working correctly +- [ ] Performance meets requirements + +### **โœ… Documentation** +- [ ] User manual created +- [ ] Admin guide prepared +- [ ] Troubleshooting guide available +- [ ] Support contact information provided + +## ๐Ÿ“ž **Support & Maintenance** + +### **โœ… Support Plan** +- [ ] Support team trained +- [ ] Escalation procedures defined +- [ ] Maintenance schedule planned +- [ ] Backup and recovery procedures + +--- + +## ๐ŸŽฏ **Final Steps Before Go-Live** + +1. **Update PDF API URL** in both LWC and Apex controller +2. **Test complete workflow** end-to-end +3. **Verify user permissions** and access +4. **Monitor system performance** for first 24 hours +5. **Provide user training** and documentation + +--- + +**๐ŸŽ‰ Your Property Brochure Generator is now Production Ready!** \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee33814 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# ๐Ÿ  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!** \ No newline at end of file diff --git a/deploy-lwc-production.sh b/deploy-lwc-production.sh new file mode 100644 index 0000000..cae9f1a --- /dev/null +++ b/deploy-lwc-production.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +echo "๐Ÿš€ LWC Production Deployment to Salesforce Sandbox" +echo "==================================================" +echo "" + +# Configuration +SANDBOX_URL="https://tso3--r1.sandbox.lightning.force.com" +USERNAME="contact+tso3@propertycrm.ae.r1" +PASSWORD="Demo@123" + +# Check if Salesforce CLI is installed +if ! command -v sf &> /dev/null; then + echo "โŒ Salesforce CLI (sf) is not installed!" + echo "" + echo "๐Ÿ“‹ Please install Salesforce CLI first:" + echo " 1. Visit: https://developer.salesforce.com/tools/sfdxcli" + echo " 2. Install the CLI for your operating system" + echo " 3. Run this script again" + echo "" + exit 1 +fi + +echo "โœ… Salesforce CLI found" +echo "" + +# Check if already authenticated +echo "๐Ÿ” Checking authentication status..." +if sf org list --json | grep -q "tso3--r1"; then + echo "โœ… Already authenticated to sandbox: tso3--r1" + ORG_ALIAS="tso3--r1" +else + echo "โš ๏ธ Not authenticated to sandbox" + echo "" + echo "๐Ÿ”‘ Authenticating to sandbox..." + + # Authenticate to sandbox + sf org login web --instance-url "$SANDBOX_URL" --alias "tso3--r1" --set-default-dev-hub + + if [ $? -eq 0 ]; then + echo "โœ… Authentication successful!" + ORG_ALIAS="tso3--r1" + else + echo "โŒ Authentication failed!" + echo "Please check your credentials and try again." + exit 1 + fi +fi + +echo "" +echo "๐Ÿ—๏ธ Deploying LWC components to sandbox..." + +# Deploy the source code +echo "๐Ÿ“ฆ Deploying source code..." +sf project deploy start --source-dir force-app --target-org "$ORG_ALIAS" + +if [ $? -eq 0 ]; then + echo "โœ… LWC deployment successful!" +else + echo "โŒ LWC deployment failed!" + echo "Please check the error messages above." + exit 1 +fi + +echo "" +echo "๐Ÿ”ง Setting up custom objects and fields..." + +# Deploy custom objects +echo "๐Ÿ“Š Deploying Property Template object..." +sf project deploy start --source-dir force-app/main/default/objects --target-org "$ORG_ALIAS" + +echo "๐Ÿ“‹ Deploying Property object fields..." +sf project deploy start --source-dir force-app/main/default/objects/Property__c --target-org "$ORG_ALIAS" + +echo "๐Ÿ“‹ Deploying Property Template object fields..." +sf project deploy start --source-dir force-app/main/default/objects/Property_Template__c --target-org "$ORG_ALIAS" + +echo "" +echo "๐ŸŽฏ Setting up permission sets..." + +# Create permission set for the LWC +echo "๐Ÿ” Creating permission set..." +sf data upsert --sobjecttype PermissionSet --sobjectid Id --external-id Name --values "Name='Property_Brochure_Generator_Access' Label='Property Brochure Generator Access' Description='Access to Property Brochure Generator LWC'" --target-org "$ORG_ALIAS" + +# Assign permissions to the permission set +echo "๐Ÿ”‘ Assigning permissions..." +sf data upsert --sobjecttype PermissionSet --sobjectid Id --external-id Name --values "Name='Property_Brochure_Generator_Access' pcrm__Property__c=true pcrm__Property_Template__c=true" --target-org "$ORG_ALIAS" + +echo "" +echo "๐Ÿ“ฑ Setting up Lightning App Page..." + +# Create a Lightning App Page +echo "๐Ÿ“„ Creating Lightning App Page..." +sf data upsert --sobjecttype LightningPage --sobjectid Id --external-id DeveloperName --values "DeveloperName='Property_Brochure_Generator' MasterLabel='Property Brochure Generator' LightningComponent__c='propertyTemplateSelector' IsAvailableInTouch=true IsAvailableInLightning=true IsAvailableInClassic=true" --target-org "$ORG_ALIAS" + +echo "" +echo "๐ŸŽ‰ Deployment completed successfully!" +echo "====================================" +echo "" +echo "๐Ÿ“‹ What was deployed:" +echo " โœ… LWC Component: propertyTemplateSelector" +echo " โœ… Apex Controller: PropertyTemplateController" +echo " โœ… Custom Objects: Property__c, Property_Template__c" +echo " โœ… Permission Set: Property_Brochure_Generator_Access" +echo " โœ… Lightning App Page: Property Brochure Generator" +echo "" +echo "๐Ÿš€ Next Steps:" +echo " 1. Open your sandbox: $SANDBOX_URL" +echo " 2. Login with: $USERNAME / $PASSWORD" +echo " 3. Go to Setup โ†’ App Manager โ†’ Property Brochure Generator" +echo " 4. Or search for 'Property Brochure Generator' in the app launcher" +echo "" +echo "๐Ÿ”ง Important Configuration:" +echo " โš ๏ธ Update the PDF API URL in the LWC JavaScript file:" +echo " - Open: force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js" +echo " - Change: pdfApiBaseUrl = 'https://your-ip-address:8000/api'" +echo " - Replace 'your-ip-address' with your actual server IP" +echo "" +echo "๐Ÿ“– For PDF generation API setup, see: README.md" +echo "" +echo "๐ŸŽฏ Your LWC is now ready for production use!" \ No newline at end of file diff --git a/force-app/main/default/classes/PropertyTemplateController.cls b/force-app/main/default/classes/PropertyTemplateController.cls new file mode 100644 index 0000000..d2297cb --- /dev/null +++ b/force-app/main/default/classes/PropertyTemplateController.cls @@ -0,0 +1,153 @@ +public with sharing class PropertyTemplateController { + + @AuraEnabled(cacheable=true) + public static List getPropertyTemplates() { + try { + return [SELECT Id, Name, Description__c, Preview_Image_URL__c, Tags__c, + Is_Active__c, Template_Definition__c + FROM Property_Template__c + WHERE Is_Active__c = true + ORDER BY Name]; + } catch (Exception e) { + throw new AuraHandledException('Error fetching templates: ' + e.getMessage()); + } + } + + @AuraEnabled(cacheable=true) + public static Property_Template__c getPropertyTemplateById(String templateId) { + try { + return [SELECT Id, Name, Description__c, Preview_Image_URL__c, Tags__c, + Is_Active__c, Template_Definition__c + FROM Property_Template__c + WHERE Id = :templateId AND Is_Active__c = true + LIMIT 1]; + } catch (Exception e) { + throw new AuraHandledException('Error fetching template: ' + e.getMessage()); + } + } + + @AuraEnabled(cacheable=true) + public static List getPropertyData() { + try { + return [SELECT Id, Name, pcrm__Property_Type__c, pcrm__Sub_Locality_Bayut_Dubizzle__c, + pcrm__Sale_Price_max__c, pcrm__Rent_Price_max__c, pcrm__Bedrooms__c, + pcrm__Bathrooms__c, pcrm__Size__c, pcrm__Description_English__c, + pcrm__Title_English__c, pcrm__Unit_Number__c, pcrm__Completion_Status__c, + pcrm__Furnished__c, pcrm__View__c, pcrm__Tower_Bayut_Dubizzle__c, + pcrm__Community_Propertyfinder__c, pcrm__Sub_Community_Propertyfinder__c, + pcrm__City_Propertyfinder__c, pcrm__City_Bayut_Dubizzle__c + FROM pcrm__Property__c + ORDER BY Name]; + } catch (Exception e) { + throw new AuraHandledException('Error fetching properties: ' + e.getMessage()); + } + } + + @AuraEnabled(cacheable=true) + public static pcrm__Property__c getPropertyById(String propertyId) { + try { + return [SELECT Id, Name, pcrm__Property_Type__c, pcrm__Sub_Locality_Bayut_Dubizzle__c, + pcrm__Sale_Price_max__c, pcrm__Rent_Price_max__c, pcrm__Bedrooms__c, + pcrm__Bathrooms__c, pcrm__Size__c, pcrm__Description_English__c, + pcrm__Title_English__c, pcrm__Unit_Number__c, pcrm__Completion_Status__c, + pcrm__Furnished__c, pcrm__View__c, pcrm__Tower_Bayut_Dubizzle__c, + pcrm__Community_Propertyfinder__c, pcrm__Sub_Community_Propertyfinder__c, + pcrm__City_Propertyfinder__c, pcrm__City_Bayut_Dubizzle__c, + pcrm__Private_Amenities__c, pcrm__Commercial_Amenities__c, + pcrm__Coordinates__c, pcrm__Build_Year__c, pcrm__Stories__c, + pcrm__Parking_Spaces__c, pcrm__Lot_Size__c, pcrm__Service_Charge__c + FROM pcrm__Property__c + WHERE Id = :propertyId + LIMIT 1]; + } catch (Exception e) { + throw new AuraHandledException('Error fetching property: ' + e.getMessage()); + } + } + + @AuraEnabled(cacheable=true) + public static Map getMarketData() { + try { + // For now, return default market data since you might not have Market_Analytics__c + Map result = new Map(); + + // Default Dubai market data + result.put('marketTrend', 'Rising'); + result.put('roiPotential', '8.5'); + result.put('avgPricePerSqft', '1200'); + result.put('marketDemand', 'High'); + result.put('investmentType', 'Buy-to-Rent'); + result.put('rentalYield', '6.2'); + result.put('investmentHighlights', 'Prime location with high rental demand and capital appreciation potential'); + result.put('locationAdvantages', 'Excellent connectivity, premium amenities, and strong infrastructure'); + result.put('contentModules', ['Market Analysis', 'Investment Overview', 'Location Highlights']); + result.put('additionalContent', 'Dubai real estate market shows strong growth potential with government initiatives and Expo 2020 legacy'); + + return result; + } catch (Exception e) { + throw new AuraHandledException('Error fetching market data: ' + e.getMessage()); + } + } + + @AuraEnabled + public static Map generatePropertyPDF(String propertyData, String templateName, Boolean generatePDF) { + try { + // Parse property data + Map propertyMap = (Map) JSON.deserializeUntyped(propertyData); + + // Call external Python API for PDF generation + String apiEndpoint = 'https://YOUR-ACTUAL-IP:8000/api/generate-pdf'; // TODO: Replace with your actual server IP + + // Prepare request body + Map requestBody = new Map(); + requestBody.put('property_data', propertyMap); + requestBody.put('template_name', templateName); + + // Make HTTP callout to Python API + Http http = new Http(); + HttpRequest request = new HttpRequest(); + request.setEndpoint(apiEndpoint); + request.setMethod('POST'); + request.setHeader('Content-Type', 'application/json'); + request.setBody(JSON.serialize(requestBody)); + + HttpResponse response = http.send(request); + + if (response.getStatusCode() == 200) { + Map responseMap = (Map) JSON.deserializeUntyped(response.getBody()); + + Map result = new Map(); + result.put('success', true); + result.put('pdfUrl', responseMap.get('pdf_url')); + result.put('message', 'PDF generated successfully'); + + return result; + } else { + Map result = new Map(); + result.put('success', false); + result.put('message', 'Failed to generate PDF: ' + response.getStatus()); + return result; + } + + } catch (Exception e) { + Map result = new Map(); + result.put('success', false); + result.put('message', 'Error: ' + e.getMessage()); + return result; + } + } + + @AuraEnabled + public static List searchPropertyTemplates(String searchTerm) { + try { + String searchQuery = '%' + searchTerm + '%'; + return [SELECT Id, Name, Description__c, Preview_Image_URL__c, Tags__c, + Is_Active__c, Template_Definition__c + FROM Property_Template__c + WHERE Is_Active__c = true + AND (Name LIKE :searchQuery OR Description__c LIKE :searchQuery OR Tags__c LIKE :searchQuery) + ORDER BY Name]; + } catch (Exception e) { + throw new AuraHandledException('Error searching templates: ' + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/force-app/main/default/data/Property_Template__c.json b/force-app/main/default/data/Property_Template__c.json new file mode 100644 index 0000000..405f73b --- /dev/null +++ b/force-app/main/default/data/Property_Template__c.json @@ -0,0 +1,50 @@ +[ + { + "Name": "Modern Apartment", + "Description__c": "Contemporary apartment template with clean lines and modern aesthetics. Perfect for showcasing urban living spaces in Dubai's vibrant neighborhoods.", + "Preview_Image_URL__c": "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&h=300&fit=crop", + "Tags__c": "Modern, Apartment, Urban, Contemporary", + "Is_Active__c": true, + "Template_Definition__c": "{\"layout\":\"modern\",\"colorScheme\":\"blue\",\"fontFamily\":\"Arial\"}" + }, + { + "Name": "Luxury Villa", + "Description__c": "Premium villa template featuring elegant design elements and spacious layouts. Ideal for high-end properties in exclusive Dubai communities.", + "Preview_Image_URL__c": "https://images.unsplash.com/photo-1613490493576-7fde63acd811?w=400&h=300&fit=crop", + "Tags__c": "Luxury, Villa, Premium, Spacious", + "Is_Active__c": true, + "Template_Definition__c": "{\"layout\":\"luxury\",\"colorScheme\":\"gold\",\"fontFamily\":\"Times New Roman\"}" + }, + { + "Name": "Premium Penthouse", + "Description__c": "Exclusive penthouse template with sophisticated styling and panoramic views emphasis. Perfect for Dubai's most prestigious properties.", + "Preview_Image_URL__c": "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=400&h=300&fit=crop", + "Tags__c": "Premium, Penthouse, Exclusive, Sophisticated", + "Is_Active__c": true, + "Template_Definition__c": "{\"layout\":\"premium\",\"colorScheme\":\"black\",\"fontFamily\":\"Helvetica\"}" + }, + { + "Name": "Family Townhouse", + "Description__c": "Family-friendly townhouse template with warm, inviting design. Great for suburban Dubai communities and family-oriented properties.", + "Preview_Image_URL__c": "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?w=400&h=300&fit=crop", + "Tags__c": "Family, Townhouse, Warm, Inviting", + "Is_Active__c": true, + "Template_Definition__c": "{\"layout\":\"family\",\"colorScheme\":\"warm\",\"fontFamily\":\"Georgia\"}" + }, + { + "Name": "Business Office", + "Description__c": "Professional office template with corporate styling and business-focused layout. Perfect for commercial properties in Dubai's business districts.", + "Preview_Image_URL__c": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=400&h=300&fit=crop", + "Tags__c": "Business, Office, Corporate, Professional", + "Is_Active__c": true, + "Template_Definition__c": "{\"layout\":\"business\",\"colorScheme\":\"gray\",\"fontFamily\":\"Arial\"}" + }, + { + "Name": "Retail Space", + "Description__c": "Retail property template designed for commercial spaces and shopping areas. Ideal for Dubai's retail and entertainment districts.", + "Preview_Image_URL__c": "https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=400&h=300&fit=crop", + "Tags__c": "Retail, Commercial, Shopping, Entertainment", + "Is_Active__c": true, + "Template_Definition__c": "{\"layout\":\"retail\",\"colorScheme\":\"vibrant\",\"fontFamily\":\"Arial\"}" + } +] \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/production-config.js b/force-app/main/default/lwc/propertyTemplateSelector/production-config.js new file mode 100644 index 0000000..f6ef0f3 --- /dev/null +++ b/force-app/main/default/lwc/propertyTemplateSelector/production-config.js @@ -0,0 +1,69 @@ +// Production Configuration for Property Brochure Generator LWC +// Update these values before deploying to production + +export const PRODUCTION_CONFIG = { + // PDF Generation API Configuration + PDF_API_BASE_URL: 'https://YOUR-ACTUAL-IP:8000/api', // Replace with your actual server IP + + // Salesforce API Configuration + SALESFORCE_API_VERSION: 'v59.0', + + // PDF Generation Settings + PDF_SETTINGS: { + maxImageSize: 5 * 1024 * 1024, // 5MB + supportedImageFormats: ['jpg', 'jpeg', 'png', 'webp'], + maxImagesPerBrochure: 20, + defaultPageSize: 'A4', + defaultOrientation: 'portrait' + }, + + // Error Handling + ERROR_MESSAGES: { + API_CONNECTION_FAILED: 'Unable to connect to PDF generation service. Please try again later.', + PROPERTY_LOAD_FAILED: 'Failed to load property data. Please refresh and try again.', + TEMPLATE_LOAD_FAILED: 'Failed to load templates. Please refresh and try again.', + PDF_GENERATION_FAILED: 'PDF generation failed. Please check your data and try again.', + IMAGE_UPLOAD_FAILED: 'Image upload failed. Please check file format and size.', + VALIDATION_FAILED: 'Please complete all required fields before proceeding.' + }, + + // Success Messages + SUCCESS_MESSAGES: { + PROPERTY_LOADED: 'Property data loaded successfully', + TEMPLATE_SELECTED: 'Template selected successfully', + PREVIEW_GENERATED: 'Preview generated successfully', + PDF_DOWNLOADED: 'PDF downloaded successfully', + BROCHURE_CREATED: 'Property brochure created successfully' + }, + + // Validation Rules + VALIDATION: { + requiredFields: ['propertyName', 'propertyType', 'location', 'price', 'description'], + minPrice: 100000, // Minimum price in AED + maxPrice: 100000000, // Maximum price in AED + minArea: 100, // Minimum area in sq ft + maxArea: 50000 // Maximum area in sq ft + }, + + // Performance Settings + PERFORMANCE: { + debounceDelay: 300, // ms + maxRetries: 3, + timeout: 30000 // 30 seconds + } +}; + +// Helper function to get configuration value +export function getConfig(key) { + return PRODUCTION_CONFIG[key] || null; +} + +// Helper function to get error message +export function getErrorMessage(key) { + return PRODUCTION_CONFIG.ERROR_MESSAGES[key] || 'An unexpected error occurred.'; +} + +// Helper function to get success message +export function getSuccessMessage(key) { + return PRODUCTION_CONFIG.SUCCESS_MESSAGES[key] || 'Operation completed successfully.'; +} \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css new file mode 100644 index 0000000..194d434 --- /dev/null +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css @@ -0,0 +1,321 @@ +.progress-container { + margin-bottom: 2rem; + padding: 1rem; + background: #f8f9fa; + border-radius: 8px; +} + +.progress-bar { + width: 100%; + height: 8px; + background: #e9ecef; + border-radius: 4px; + overflow: hidden; + margin-bottom: 1rem; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #007bff, #0056b3); + transition: width 0.3s ease; + border-radius: 4px; +} + +.step-indicators { + display: flex; + justify-content: space-between; + align-items: center; +} + +.step { + padding: 0.5rem 1rem; + background: #fff; + border: 2px solid #e9ecef; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 500; + color: #6c757d; + transition: all 0.3s ease; +} + +.step.active { + background: #007bff; + border-color: #007bff; + color: #fff; + transform: scale(1.05); +} + +.step-content { + padding: 1rem 0; +} + +.step-content h2 { + color: #2c3e50; + margin-bottom: 0.5rem; + font-size: 1.75rem; +} + +.step-content p { + color: #6c757d; + margin-bottom: 2rem; + font-size: 1rem; +} + +/* Template Grid */ +.template-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; + margin-top: 1.5rem; +} + +.template-card { + background: #fff; + border: 2px solid #e9ecef; + border-radius: 12px; + overflow: hidden; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.template-card:hover { + border-color: #007bff; + transform: translateY(-4px); + box-shadow: 0 8px 25px rgba(0, 123, 255, 0.15); +} + +.template-preview { + height: 200px; + overflow: hidden; + background: #f8f9fa; +} + +.template-preview img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.template-card:hover .template-preview img { + transform: scale(1.05); +} + +.template-info { + padding: 1.5rem; +} + +.template-info h3 { + color: #2c3e50; + margin-bottom: 0.5rem; + font-size: 1.25rem; +} + +.template-info p { + color: #6c757d; + margin-bottom: 1rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.template-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.tag { + background: #e3f2fd; + color: #1976d2; + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 500; +} + +/* Form Styling */ +.form-container { + max-width: 800px; +} + +.form-section { + background: #fff; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +.form-section h3 { + color: #2c3e50; + margin-bottom: 1rem; + font-size: 1.125rem; + border-bottom: 2px solid #f8f9fa; + padding-bottom: 0.5rem; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-bottom: 1rem; +} + +.form-row:last-child { + margin-bottom: 0; +} + +/* Amenities Grid */ +.amenities-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.75rem; +} + +.amenity-item { + display: flex; + align-items: center; +} + +/* Image Upload */ +.image-upload { + margin-bottom: 1rem; +} + +.image-preview { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 1rem; + margin-top: 1rem; +} + +.image-item { + position: relative; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.image-item img { + width: 100%; + height: 150px; + object-fit: cover; +} + +.remove-btn { + position: absolute; + top: 0.5rem; + right: 0.5rem; + background: #dc3545; + color: #fff; + border: none; + border-radius: 50%; + width: 24px; + height: 24px; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.3s ease; +} + +.remove-btn:hover { + background: #c82333; +} + +/* Preview and Download */ +.preview-actions, +.download-actions { + text-align: center; + margin: 2rem 0; +} + +.pdf-preview { + margin-top: 2rem; + border: 1px solid #e9ecef; + border-radius: 8px; + overflow: hidden; +} + +.pdf-preview h3 { + background: #f8f9fa; + padding: 1rem; + margin: 0; + border-bottom: 1px solid #e9ecef; +} + +.pdf-preview iframe { + border: none; +} + +/* Loading Spinner */ +.loading-spinner { + text-align: center; + margin: 2rem 0; +} + +/* Navigation Buttons */ +.navigation-buttons { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 1px solid #e9ecef; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .step-indicators { + flex-direction: column; + gap: 0.5rem; + } + + .step { + font-size: 0.75rem; + padding: 0.375rem 0.75rem; + } + + .template-grid { + grid-template-columns: 1fr; + } + + .form-row { + grid-template-columns: 1fr; + } + + .amenities-grid { + grid-template-columns: 1fr; + } + + .navigation-buttons { + flex-direction: column; + gap: 1rem; + } +} + +/* Custom Lightning Component Overrides */ +:host { + --lwc-colorTextLabel: #2c3e50; + --lwc-colorTextPlaceholder: #6c757d; + --lwc-colorBorderInput: #e9ecef; + --lwc-colorBorderInputFocus: #007bff; +} + +/* Animation for step transitions */ +.step-content { + animation: fadeIn 0.3s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html new file mode 100644 index 0000000..c34d289 --- /dev/null +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html @@ -0,0 +1,544 @@ + \ No newline at end of file diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js new file mode 100644 index 0000000..297b65d --- /dev/null +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js @@ -0,0 +1,494 @@ +import { LightningElement, track, wire, api } from 'lwc'; +import { getRecord, getFieldValue } from 'lightning/uiRecordApi'; +import { ShowToastEvent } from 'lightning/platformShowToastEvent'; +import getPropertyTemplates from '@salesforce/apex/PropertyTemplateController.getPropertyTemplates'; +import generatePropertyPDF from '@salesforce/apex/PropertyTemplateController.generatePropertyPDF'; +import getPropertyData from '@salesforce/apex/PropertyTemplateController.getPropertyData'; +import getPropertyById from '@salesforce/apex/PropertyTemplateController.getPropertyById'; +import getMarketData from '@salesforce/apex/PropertyTemplateController.getMarketData'; + +export default class PropertyTemplateSelector extends LightningElement { + @api recordId; // Current record ID if used in record page + @track currentStep = 1; + @track selectedTemplate = null; + @track selectedLayout = null; + @track propertyData = {}; + @track templates = []; + @track isLoading = false; + @track pdfPreviewUrl = ''; + @track showPreview = false; + @track salesforceData = null; + @track availableProperties = []; + @track selectedPropertyId = null; + @track showPropertySelector = false; + @track uploadedImages = []; + @track customizationOptions = { + headerStyle: 'modern', + colorScheme: 'professional', + fontStyle: 'clean' + }; + + // PDF Generation API Configuration + pdfApiBaseUrl = 'https://YOUR-ACTUAL-IP:8000/api'; // TODO: Replace with your actual server IP + + // Property types and locations for Dubai + propertyTypes = [ + 'AP', 'VI', 'BU', 'BW', 'OF', 'SH', 'WH' + ]; + + dubaiLocations = [ + 'Astron Towers', 'Downtown Dubai', 'Palm Jumeirah', 'Dubai Marina', 'JBR', 'Business Bay', + 'Dubai Hills Estate', 'Emirates Hills', 'Arabian Ranches', 'Meadows', 'Springs' + ]; + + availableAmenities = [ + 'Swimming Pool', 'Gym', 'Parking', 'Security', 'Garden', 'Balcony', 'Terrace', + 'Concierge', 'Maintenance', '24/7 Support', 'High-Speed Internet', 'Smart Home System' + ]; + + // Wire service to get property templates + @wire(getPropertyTemplates) + wiredTemplates({ error, data }) { + if (data) { + this.templates = data; + this.error = undefined; + } else if (error) { + this.error = error; + this.templates = []; + } + } + + // Wire service to get current property data if recordId is provided + @wire(getRecord, { + recordId: '$recordId', + fields: ['pcrm__Property__c.Name', 'pcrm__Property__c.pcrm__Property_Type__c', 'pcrm__Property__c.pcrm__Sub_Locality_Bayut_Dubizzle__c', 'pcrm__Property__c.pcrm__Sale_Price_max__c', 'pcrm__Property__c.pcrm__Bedrooms__c', 'pcrm__Property__c.pcrm__Bathrooms__c', 'pcrm__Property__c.pcrm__Size__c', 'pcrm__Property__c.pcrm__Description_English__c', 'pcrm__Property__c.pcrm__Title_English__c'] + }) + wiredProperty({ error, data }) { + if (data) { + this.populatePropertyDataFromRecord(data); + } else if (error) { + console.error('Error loading property data:', error); + this.showToast('Error', 'Failed to load property data. Please try again.', 'error'); + } + } + + // Wire service to get available properties for selection + @wire(getPropertyData) + wiredProperties({ error, data }) { + if (data) { + this.availableProperties = data.map(prop => ({ + label: `${prop.Name} - ${prop.pcrm__Title_English__c || 'No Title'} - ${prop.pcrm__Sub_Locality_Bayut_Dubizzle__c || 'No Location'}`, + value: prop.Id, + property: prop + })); + } else if (error) { + console.error('Error loading properties:', error); + this.showToast('Error', 'Failed to load available properties. Please try again.', 'error'); + } + } + + // Wire service to get market data + @wire(getMarketData) + wiredMarketData({ error, data }) { + if (data) { + this.salesforceData = data; + this.populateMarketData(); + } else if (error) { + console.error('Error loading market data:', error); + this.showToast('Error', 'Failed to load market data. Please try again.', 'error'); + } + } + + // Populate property data from Salesforce record + populatePropertyDataFromRecord(record) { + this.propertyData = { + propertyName: getFieldValue(record, 'pcrm__Property__c.Name') || '', + propertyType: getFieldValue(record, 'pcrm__Property__c.pcrm__Property_Type__c') || '', + location: getFieldValue(record, 'pcrm__Property__c.pcrm__Sub_Locality_Bayut_Dubizzle__c') || '', + price: getFieldValue(record, 'pcrm__Property__c.pcrm__Sale_Price_max__c') || getFieldValue(record, 'pcrm__Property__c.pcrm__Rent_Price_max__c') || '', + bedrooms: getFieldValue(record, 'pcrm__Property__c.pcrm__Bedrooms__c') || '', + bathrooms: getFieldValue(record, 'pcrm__Property__c.pcrm__Bathrooms__c') || '', + area: getFieldValue(record, 'pcrm__Property__c.pcrm__Size__c') || '', + description: getFieldValue(record, 'pcrm__Property__c.pcrm__Description_English__c') || '', + titleEnglish: getFieldValue(record, 'pcrm__Property__c.pcrm__Title_English__c') || '', + externalId: getFieldValue(record, 'pcrm__Property__c.pcrm__External_ID__c') || '', + unitNumber: getFieldValue(record, 'pcrm__Property__c.pcrm__Unit_Number__c') || '', + completionStatus: getFieldValue(record, 'pcrm__Property__c.pcrm__Completion_Status__c') || '', + furnished: getFieldValue(record, 'pcrm__Property__c.pcrm__Furnished__c') || '', + view: getFieldValue(record, 'pcrm__Property__c.pcrm__View__c') || '', + tower: getFieldValue(record, 'pcrm__Property__c.pcrm__Tower_Bayut_Dubizzle__c') || '', + city: getFieldValue(record, 'pcrm__Property__c.pcrm__City_Bayut_Dubizzle__c') || '', + subCommunity: getFieldValue(record, 'pcrm__Property__c.pcrm__Sub_Community_Propertyfinder__c') || '', + buildYear: getFieldValue(record, 'pcrm__Property__c.pcrm__Build_Year__c') || '', + stories: getFieldValue(record, 'pcrm__Property__c.pcrm__Stories__c') || '', + parkingSpaces: getFieldValue(record, 'pcrm__Property__c.pcrm__Parking_Spaces__c') || '', + lotSize: getFieldValue(record, 'pcrm__Property__c.pcrm__Lot_Size__c') || '', + serviceCharge: getFieldValue(record, 'pcrm__Property__c.pcrm__Service_Charge__c') || '', + privateAmenities: getFieldValue(record, 'pcrm__Property__c.pcrm__Private_Amenities__c') || '', + commercialAmenities: getFieldValue(record, 'pcrm__Property__c.pcrm__Commercial_Amenities__c') || '', + coordinates: getFieldValue(record, 'pcrm__Property__c.pcrm__Coordinates__c') || '', + // Market and Investment Data + marketTrend: '', + roiPotential: '', + avgPricePerSqft: '', + marketDemand: '', + investmentType: '', + rentalYield: '', + investmentHighlights: '', + locationAdvantages: '', + additionalContent: '', + // Content Modules + contentModules: [], + // Customization Options + headerStyle: 'modern', + colorScheme: 'professional', + fontStyle: 'clean', + // Images and Amenities + amenities: [], + images: [], + imageNames: [] + }; + } + + // Populate market data from Salesforce + populateMarketData() { + if (this.salesforceData) { + // Auto-populate market data fields + this.propertyData = { + ...this.propertyData, + marketTrend: this.salesforceData.marketTrend || '', + roiPotential: this.salesforceData.roiPotential || '', + avgPricePerSqft: this.salesforceData.avgPricePerSqft || '', + marketDemand: this.salesforceData.marketDemand || '', + investmentType: this.salesforceData.investmentType || '', + rentalYield: this.salesforceData.rentalYield || '', + investmentHighlights: this.salesforceData.investmentHighlights || '', + locationAdvantages: this.salesforceData.locationAdvantages || '', + contentModules: this.salesforceData.contentModules || [], + additionalContent: this.salesforceData.additionalContent || '' + }; + } + } + + // Handle property selection from dropdown + async handlePropertySelection(event) { + const selectedPropertyId = event.detail.value; + if (selectedPropertyId) { + this.isLoading = true; + try { + const selectedProperty = await getPropertyById({ propertyId: selectedPropertyId }); + if (selectedProperty) { + this.populatePropertyDataFromSelectedProperty(selectedProperty); + this.selectedPropertyId = selectedPropertyId; + this.showToast('Success', 'Property data loaded successfully', 'success'); + } + } catch (error) { + console.error('Error loading property:', error); + this.showToast('Error', 'Failed to load property data', 'error'); + } finally { + this.isLoading = false; + } + } + } + + // Populate form with selected property data + populatePropertyDataFromSelectedProperty(property) { + this.propertyData = { + propertyName: property.Name || '', + propertyType: property.pcrm__Property_Type__c || '', + location: property.pcrm__Sub_Locality_Bayut_Dubizzle__c || property.pcrm__Community_Propertyfinder__c || '', + price: property.pcrm__Sale_Price_max__c || property.pcrm__Rent_Price_max__c || '', + bedrooms: property.pcrm__Bedrooms__c || '', + bathrooms: property.pcrm__Bathrooms__c || '', + area: property.pcrm__Size__c || '', + description: property.pcrm__Description_English__c || '', + titleEnglish: property.pcrm__Title_English__c || '', + unitNumber: property.pcrm__Unit_Number__c || '', + completionStatus: property.pcrm__Completion_Status__c || '', + furnished: property.pcrm__Furnished__c || '', + view: property.pcrm__View__c || '', + tower: property.pcrm__Tower_Bayut_Dubizzle__c || '', + city: property.pcrm__City_Bayut_Dubizzle__c || property.pcrm__City_Propertyfinder__c || '', + subCommunity: property.pcrm__Sub_Community_Propertyfinder__c || '', + buildYear: property.pcrm__Build_Year__c || '', + stories: property.pcrm__Stories__c || '', + parkingSpaces: property.pcrm__Parking_Spaces__c || '', + lotSize: property.pcrm__Lot_Size__c || '', + serviceCharge: property.pcrm__Service_Charge__c || '', + privateAmenities: property.pcrm__Private_Amenities__c || '', + commercialAmenities: property.pcrm__Commercial_Amenities__c || '', + coordinates: property.pcrm__Coordinates__c || '', + amenities: [], + images: [] + }; + } + + // Navigation methods + nextStep() { + if (this.currentStep < 5 && this.validateCurrentStep()) { + this.currentStep++; + this.updateStepDisplay(); + } + } + + previousStep() { + if (this.currentStep > 1) { + this.currentStep--; + this.updateStepDisplay(); + } + } + + // Template selection + handleTemplateSelect(event) { + const templateId = event.currentTarget.dataset.templateId; + this.selectedTemplate = templateId; + + // Set default layout based on template + if (templateId === 'professional-1pager') { + this.selectedLayout = '1-page'; + } else if (templateId === 'professional-3pager') { + this.selectedLayout = '3-page'; + } else if (templateId === 'professional-5pager') { + this.selectedLayout = '5-page'; + } else { + this.selectedLayout = 'custom'; + } + + this.updateStepDisplay(); + } + + // Input change handlers + handleInputChange(event) { + const { name, value } = event.target; + this.propertyData[name] = value; + } + + handleLocationChange(event) { + this.propertyData.location = event.detail.value; + } + + handlePropertyTypeChange(event) { + this.propertyData.propertyType = event.detail.value; + } + + handleAmenityChange(event) { + const amenity = event.target.value; + if (event.target.checked) { + if (!this.propertyData.amenities) this.propertyData.amenities = []; + this.propertyData.amenities.push(amenity); + } else { + this.propertyData.amenities = this.propertyData.amenities.filter(a => a !== amenity); + } + } + + // Enhanced Image handling with room names + handleImageUpload(event) { + const files = event.target.files; + if (files.length > 0) { + Array.from(files).forEach((file, index) => { + const reader = new FileReader(); + reader.onload = (e) => { + if (!this.propertyData.images) this.propertyData.images = []; + if (!this.propertyData.imageNames) this.propertyData.imageNames = []; + + this.propertyData.images.push(e.target.result); + this.propertyData.imageNames.push(`Room ${index + 1}`); + }; + reader.readAsDataURL(file); + }); + } + } + + removeImage(index) { + this.propertyData.images.splice(index, 1); + this.propertyData.imageNames.splice(index, 1); + } + + // Update image name + handleImageNameChange(event) { + const index = parseInt(event.target.dataset.index); + const newName = event.target.value; + this.propertyData.imageNames[index] = newName; + } + + // Customization options + handleCustomizationChange(event) { + const { name, value } = event.target; + this.customizationOptions[name] = value; + this.propertyData[name] = value; + this.updateCustomizationPreview(); + } + + updateCustomizationPreview() { + // Real-time customization preview updates + const previewContainer = this.template.querySelector('.preview-content'); + if (previewContainer) { + // Update preview styling based on customization options + previewContainer.style.setProperty('--header-style', this.customizationOptions.headerStyle); + previewContainer.style.setProperty('--color-scheme', this.customizationOptions.colorScheme); + previewContainer.style.setProperty('--font-style', this.customizationOptions.fontStyle); + } + } + + // Preview and PDF generation using external API + async handlePreview() { + this.isLoading = true; + try { + // Prepare data for external PDF API + const pdfRequestData = { + template: this.selectedTemplate, + layout: this.selectedLayout || 'standard', + propertyData: this.propertyData, + customizationOptions: this.customizationOptions, + generatePreview: true + }; + + // Call external PDF generation API + const response = await fetch(`${this.pdfApiBaseUrl}/preview`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(pdfRequestData) + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + this.pdfPreviewUrl = result.previewUrl; + this.showPreview = true; + this.currentStep = 4; + this.updateStepDisplay(); + this.showToast('Success', 'Preview generated successfully', 'success'); + } else { + this.showToast('Error', result.message || 'Failed to generate preview', 'error'); + } + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + } catch (error) { + console.error('Error generating preview:', error); + this.showToast('Error', 'Failed to generate preview. Please check your API connection.', 'error'); + } finally { + this.isLoading = false; + } + } + + async handleDownload() { + this.isLoading = true; + try { + // Prepare data for external PDF API + const pdfRequestData = { + template: this.selectedTemplate, + layout: this.selectedLayout || 'standard', + propertyData: this.propertyData, + customizationOptions: this.customizationOptions, + generatePDF: true + }; + + // Call external PDF generation API + const response = await fetch(`${this.pdfApiBaseUrl}/generate-pdf`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(pdfRequestData) + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + // Trigger download + const link = document.createElement('a'); + link.href = result.pdfUrl; + link.download = `${this.propertyData.propertyName || 'Property'}_Brochure.pdf`; + link.click(); + this.showToast('Success', 'PDF downloaded successfully', 'success'); + } else { + this.showToast('Error', result.message || 'Failed to generate PDF', 'error'); + } + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + } catch (error) { + console.error('Error downloading PDF:', error); + this.showToast('Error', 'Failed to download PDF. Please check your API connection.', 'error'); + } finally { + this.isLoading = false; + } + } + + // Validation + validateCurrentStep() { + switch (this.currentStep) { + case 1: + return this.selectedTemplate; + case 2: + return this.propertyData.propertyName && this.propertyData.propertyType && this.propertyData.location; + case 3: + return this.propertyData.description; + default: + return true; + } + } + + // Utility methods + updateStepDisplay() { + // Update step navigation + const stepItems = this.template.querySelectorAll('.step-item'); + stepItems.forEach((item, index) => { + if (index + 1 === this.currentStep) { + item.classList.add('active'); + } else if (index + 1 < this.currentStep) { + item.classList.remove('active', 'completed'); + } else { + item.classList.remove('active', 'completed'); + } + }); + } + + showToast(title, message, variant) { + this.dispatchEvent(new ShowToastEvent({ + title, + message, + variant + })); + } + + // Production-ready error handling + handleError(error, context) { + console.error(`Error in ${context}:`, error); + this.showToast('Error', `An error occurred: ${error.message || 'Unknown error'}`, 'error'); + this.isLoading = false; + } + + // Production-ready success handling + handleSuccess(message, context) { + console.log(`Success in ${context}:`, message); + this.showToast('Success', message, 'success'); + } + + // Getter for computed properties + get isStep1() { return this.currentStep === 1; } + get isStep2() { return this.currentStep === 2; } + get isStep3() { return this.currentStep === 3; } + get isStep4() { return this.currentStep === 4; } + get isStep5() { return this.currentStep === 5; } + + get canProceed() { + return this.validateCurrentStep(); + } + + get selectedTemplateData() { + return this.templates.find(t => t.Id === this.selectedTemplate); + } + + get hasProperties() { + return this.availableProperties && this.availableProperties.length > 0; + } + + // Handle create new brochure + handleCreateNew() { + window.location.reload(); + } +} \ No newline at end of file diff --git a/force-app/main/default/objects/Property_Template__c/Property_Template__c.object-meta.xml b/force-app/main/default/objects/Property_Template__c/Property_Template__c.object-meta.xml new file mode 100644 index 0000000..bbbff02 --- /dev/null +++ b/force-app/main/default/objects/Property_Template__c/Property_Template__c.object-meta.xml @@ -0,0 +1,188 @@ + + + + Accept + Action override created by Lightning App Builder during activation. + Property_Template__cAccept + Large + false + Default + + + Accept + Action override created by Lightning App Builder during activation. + Property_Template__cAccept + Small + false + Default + + + CancelEdit + Action override created by Lightning App Builder during activation. + Property_Template__cCancelEdit + Large + false + Default + + + CancelEdit + Action override created by Lightning App Builder during activation. + Property_Template__cCancelEdit + Small + false + Default + + + Clone + Action override created by Lightning App Builder during activation. + Property_Template__cClone + Large + false + Default + + + Clone + Action override created by Lightning App Builder during activation. + Property_Template__cClone + Small + false + Default + + + Delete + Action override created by Lightning App Builder during activation. + Property_Template__cDelete + Large + false + Default + + + Delete + Action override created by Lightning App Builder during activation. + Property_Template__cDelete + Small + false + Default + + + Edit + Action override created by Lightning App Builder during activation. + Property_Template__cEdit + Large + false + Default + + + Edit + Action override created by Lightning App Builder during activation. + Property_Template__cEdit + Small + false + Default + + + List + Action override created by Lightning App Builder during activation. + Property_Template__cList + Large + false + Default + + + List + Action override created by Lightning App Builder during activation. + Property_Template__cList + Small + false + Default + + + New + Action override created by Lightning App Builder during activation. + Property_Template__cNew + Large + false + Default + + + New + Action override created by Lightning App Builder during activation. + Property_Template__cNew + Small + false + Default + + + SaveEdit + Action override created by Lightning App Builder during activation. + Property_Template__cSaveEdit + Large + false + Default + + + SaveEdit + Action override created by Lightning App Builder during activation. + Property_Template__cSaveEdit + Small + false + Default + + + Tab + Action override created by Lightning App Builder during activation. + Property_Template__cTab + Large + false + Default + + + Tab + Action override created by Lightning App Builder during activation. + Property_Template__cTab + Small + false + Default + + + View + Action override created by Lightning App Builder during activation. + Property_Template__cView + Large + false + Default + + + View + Action override created by Lightning App Builder during activation. + Property_Template__cView + Small + false + Default + + false + true + SYSTEM + Deployed + false + true + false + false + false + true + true + true + true + Private + + + T-{0000} + + false + AutoNumber + + Property Templates + + ReadWrite + Public + \ No newline at end of file diff --git a/force-app/main/default/objects/Property_Template__c/fields/Description__c.field-meta.xml b/force-app/main/default/objects/Property_Template__c/fields/Description__c.field-meta.xml new file mode 100644 index 0000000..cda5383 --- /dev/null +++ b/force-app/main/default/objects/Property_Template__c/fields/Description__c.field-meta.xml @@ -0,0 +1,12 @@ + + + Description__c + Detailed description of the property template + false + + 32768 + false + false + LongTextArea + 3 + \ No newline at end of file diff --git a/force-app/main/default/objects/Property_Template__c/fields/Is_Active__c.field-meta.xml b/force-app/main/default/objects/Property_Template__c/fields/Is_Active__c.field-meta.xml new file mode 100644 index 0000000..a90fb62 --- /dev/null +++ b/force-app/main/default/objects/Property_Template__c/fields/Is_Active__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Is_Active__c + true + Indicates if this template is active and available for use + false + + false + false + Checkbox + \ No newline at end of file diff --git a/force-app/main/default/objects/Property_Template__c/fields/Preview_Image_URL__c.field-meta.xml b/force-app/main/default/objects/Property_Template__c/fields/Preview_Image_URL__c.field-meta.xml new file mode 100644 index 0000000..d5b3ad7 --- /dev/null +++ b/force-app/main/default/objects/Property_Template__c/fields/Preview_Image_URL__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Preview_Image_URL__c + URL to the preview image for this template + false + + 255 + false + false + Url + \ No newline at end of file diff --git a/force-app/main/default/objects/Property_Template__c/fields/Tags__c.field-meta.xml b/force-app/main/default/objects/Property_Template__c/fields/Tags__c.field-meta.xml new file mode 100644 index 0000000..e65aa45 --- /dev/null +++ b/force-app/main/default/objects/Property_Template__c/fields/Tags__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Tags__c + Comma-separated tags for categorizing this template + false + + 255 + false + false + Text + \ No newline at end of file diff --git a/force-app/main/default/objects/Property_Template__c/fields/Template_Definition__c.field-meta.xml b/force-app/main/default/objects/Property_Template__c/fields/Template_Definition__c.field-meta.xml new file mode 100644 index 0000000..3ff4217 --- /dev/null +++ b/force-app/main/default/objects/Property_Template__c/fields/Template_Definition__c.field-meta.xml @@ -0,0 +1,12 @@ + + + Template_Definition__c + JSON definition for the pdfmake template + false + + 131072 + false + false + LongTextArea + 10 + \ No newline at end of file diff --git a/force-app/main/default/objects/Property__c/Property__c.object-meta.xml b/force-app/main/default/objects/Property__c/Property__c.object-meta.xml new file mode 100644 index 0000000..04148b0 --- /dev/null +++ b/force-app/main/default/objects/Property__c/Property__c.object-meta.xml @@ -0,0 +1,106 @@ + + + + Accept + Action override created by Lightning App Builder during activation. + Accept + Large + false + Default + + + CancelEdit + Action override created by Lightning App Builder during activation. + CancelEdit + Large + false + Default + + + Clone + Action override created by Lightning App Builder during activation. + Clone + Large + false + Default + + + Delete + Action override created by Lightning App Builder during activation. + Delete + Large + false + Default + + + Edit + Action override created by Lightning App Builder during activation. + Edit + Large + false + Default + + + List + Action override created by Lightning App Builder during activation. + List + Large + false + Default + + + New + Action override created by Lightning App Builder during activation. + New + Large + false + Default + + + SaveEdit + Action override created by Lightning App Builder during activation. + SaveEdit + Large + false + Default + + + Tab + Action override created by Lightning App Builder during activation. + Tab + Large + false + Default + + + View + Action override created by Lightning App Builder during activation. + View + Large + false + Default + + false + SYSTEM + Deployed + true + true + false + false + false + true + true + true + true + Private + + + P-{0000} + + AutoNumber + + Properties + + ReadWrite + Public + \ No newline at end of file diff --git a/force-app/main/default/objects/Property__c/fields/Location__c.field-meta.xml b/force-app/main/default/objects/Property__c/fields/Location__c.field-meta.xml new file mode 100644 index 0000000..ed16ab3 --- /dev/null +++ b/force-app/main/default/objects/Property__c/fields/Location__c.field-meta.xml @@ -0,0 +1,65 @@ + + + Location__c + false + + false + false + Picklist + + true + + false + + Downtown Dubai + false + + + + Palm Jumeirah + false + + + + Dubai Marina + false + + + + JBR + false + + + + Business Bay + false + + + + Dubai Hills Estate + false + + + + Emirates Hills + false + + + + Arabian Ranches + false + + + + Meadows + false + + + + Springs + false + + + + + \ No newline at end of file diff --git a/force-app/main/default/objects/Property__c/fields/Property_Type__c.field-meta.xml b/force-app/main/default/objects/Property__c/fields/Property_Type__c.field-meta.xml new file mode 100644 index 0000000..1df127d --- /dev/null +++ b/force-app/main/default/objects/Property__c/fields/Property_Type__c.field-meta.xml @@ -0,0 +1,50 @@ + + + Property_Type__c + false + + false + false + Picklist + + true + + false + + Apartment + false + + + + Villa + false + + + + Penthouse + false + + + + Townhouse + false + + + + Office + false + + + + Retail Space + false + + + + Warehouse + false + + + + + \ No newline at end of file diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..3111766 --- /dev/null +++ b/package.xml @@ -0,0 +1,24 @@ + + + + Property_Template__c + CustomObject + + + Property_Template__c.Description__c + Property_Template__c.Preview_Image_URL__c + Property_Template__c.Tags__c + Property_Template__c.Template_Definition__c + Property_Template__c.Is_Active__c + CustomField + + + PropertyTemplateController + ApexClass + + + propertyTemplateSelector + LightningComponentBundle + + 58.0 + \ No newline at end of file diff --git a/python-pdf-generator/advanced_templates.py b/python-pdf-generator/advanced_templates.py new file mode 100644 index 0000000..b63cda4 --- /dev/null +++ b/python-pdf-generator/advanced_templates.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python3 +""" +Advanced Property PDF Templates +Highly sophisticated templates with real estate images and professional layouts +""" + +import os +from datetime import datetime +from typing import Dict, List, Any +from reportlab.lib.pagesizes import A4 +from reportlab.lib.styles import ParagraphStyle +from reportlab.lib.units import inch, cm +from reportlab.lib import colors +from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle, PageBreak +from reportlab.platypus.flowables import KeepTogether +from reportlab.pdfgen import canvas +from reportlab.lib.colors import HexColor +from PIL import Image as PILImage +import io +import base64 + +class AdvancedPropertyTemplates: + """Advanced property PDF templates with sophisticated designs""" + + def __init__(self): + self.setup_advanced_styles() + + def setup_advanced_styles(self): + """Setup advanced paragraph styles""" + self.styles = {} + + # Ultra Premium Title + self.styles['UltraTitle'] = ParagraphStyle( + name='UltraTitle', + fontSize=48, + textColor=HexColor('#1a1a1a'), + alignment=TA_CENTER, + spaceAfter=35, + fontName='Helvetica-Bold', + leading=56 + ) + + # Premium Subtitle + self.styles['PremiumSubtitle'] = ParagraphStyle( + name='PremiumSubtitle', + fontSize=22, + textColor=HexColor('#666666'), + alignment=TA_CENTER, + spaceAfter=30, + fontName='Helvetica', + leading=26 + ) + + # Section Headers + self.styles['SectionHeader'] = ParagraphStyle( + name='SectionHeader', + fontSize=28, + textColor=HexColor('#1f2937'), + alignment=TA_LEFT, + spaceAfter=20, + fontName='Helvetica-Bold', + leading=32 + ) + + # Content Text + self.styles['ContentText'] = ParagraphStyle( + name='ContentText', + fontSize=13, + textColor=HexColor('#374151'), + alignment=TA_JUSTIFY, + spaceAfter=15, + fontName='Helvetica', + leading=18 + ) + + # Feature Text + self.styles['FeatureText'] = ParagraphStyle( + name='FeatureText', + fontSize=14, + textColor=HexColor('#1f2937'), + alignment=TA_LEFT, + spaceAfter=12, + fontName='Helvetica-Bold', + leading=18 + ) + + # Price Display + self.styles['PriceDisplay'] = ParagraphStyle( + name='PriceDisplay', + fontSize=36, + textColor=HexColor('#dc2626'), + alignment=TA_CENTER, + spaceAfter=30, + fontName='Helvetica-Bold', + leading=42 + ) + + # Amenity Item + self.styles['AmenityItem'] = ParagraphStyle( + name='AmenityItem', + fontSize=13, + textColor=HexColor('#4b5563'), + alignment=TA_LEFT, + spaceAfter=10, + fontName='Helvetica', + leading=17 + ) + + def create_luxury_villa_template(self, data: Dict[str, Any], output_path: str) -> str: + """Create ultra-luxury villa template with sophisticated design""" + doc = SimpleDocTemplate( + output_path, + pagesize=A4, + rightMargin=0.3*cm, + leftMargin=0.3*cm, + topMargin=0.3*cm, + bottomMargin=0.3*cm + ) + + story = [] + + # Page 1: Cover Page + story.extend(self._create_cover_page(data)) + story.append(PageBreak()) + + # Page 2: Property Overview + story.extend(self._create_property_overview(data)) + story.append(PageBreak()) + + # Page 3: Features & Amenities + story.extend(self._create_features_page(data)) + story.append(PageBreak()) + + # Page 4: Location & Investment + story.extend(self._create_investment_page(data)) + + # Build PDF with custom header/footer + doc.build(story, onFirstPage=lambda c, d: self._create_luxury_header_footer(c, 1, "LUXURY VILLA"), + onLaterPages=lambda c, d: self._create_luxury_header_footer(c, d.page, "LUXURY VILLA")) + + return output_path + + def _create_cover_page(self, data: Dict[str, Any]) -> List: + """Create sophisticated cover page""" + story = [] + + # Main Title + story.append(Paragraph("LUXURY VILLA COLLECTION", self.styles['UltraTitle'])) + story.append(Spacer(1, 40)) + + # Property Name + story.append(Paragraph(f"{data.get('propertyName', 'Exclusive Villa')}", self.styles['UltraTitle'])) + story.append(Spacer(1, 30)) + + # Location + story.append(Paragraph(f"Located in {data.get('location', 'Dubai')}", self.styles['PremiumSubtitle'])) + story.append(Spacer(1, 50)) + + # Price Highlight + story.append(Paragraph("INVESTMENT VALUE", self.styles['SectionHeader'])) + story.append(Paragraph(f"AED {data.get('price', 'N/A')}", self.styles['PriceDisplay'])) + story.append(Spacer(1, 60)) + + # Property Stats + stats_data = [ + ['BEDROOMS', 'BATHROOMS', 'AREA', 'LOCATION'], + [ + data.get('bedrooms', 'N/A'), + data.get('bathrooms', 'N/A'), + f"{data.get('area', 'N/A')} sq ft", + data.get('location', 'N/A') + ] + ] + + stats_table = Table(stats_data, colWidths=[1.8*inch, 1.8*inch, 1.8*inch, 1.8*inch]) + stats_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (-1, 0), HexColor('#2c1810')), + ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 16), + ('FONTSIZE', (0, 1), (-1, 1), 18), + ('BOTTOMPADDING', (0, 0), (-1, -1), 20), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#2c1810')), + ('ROUNDEDCORNERS', [15, 15, 15, 15]) + ])) + + story.append(stats_table) + story.append(Spacer(1, 60)) + + # Footer Text + story.append(Paragraph("EXCLUSIVE โ€ข PRESTIGIOUS โ€ข SOPHISTICATED", self.styles['PremiumSubtitle'])) + story.append(Paragraph("Where luxury meets lifestyle", self.styles['ContentText'])) + + return story + + def _create_property_overview(self, data: Dict[str, Any]) -> List: + """Create property overview page""" + story = [] + + # Page Title + story.append(Paragraph("PROPERTY OVERVIEW", self.styles['SectionHeader'])) + story.append(Spacer(1, 30)) + + # Description + if data.get('description'): + story.append(Paragraph("ABOUT THIS PROPERTY", self.styles['FeatureText'])) + story.append(Paragraph(data['description'], self.styles['ContentText'])) + story.append(Spacer(1, 30)) + + # Property Highlights + story.append(Paragraph("PROPERTY HIGHLIGHTS", self.styles['FeatureText'])) + highlights = [ + "โ€ข Premium finishes throughout", + "โ€ข High-end appliances and fixtures", + "โ€ข Smart home technology integration", + "โ€ข Energy-efficient design", + "โ€ข Premium security systems", + "โ€ข Landscaped gardens and outdoor spaces" + ] + + for highlight in highlights: + story.append(Paragraph(highlight, self.styles['ContentText'])) + + story.append(Spacer(1, 30)) + + # Additional Features + story.append(Paragraph("ADDITIONAL FEATURES", self.styles['FeatureText'])) + additional_features = [ + "โ€ข Premium finishes throughout", + "โ€ข Premium flooring materials", + "โ€ข Designer lighting fixtures", + "โ€ข High-quality windows and doors", + "โ€ข Advanced HVAC systems", + "โ€ข Premium insulation and soundproofing" + ] + + for feature in additional_features: + story.append(Paragraph(feature, self.styles['ContentText'])) + + return story + + def _create_features_page(self, data: Dict[str, Any]) -> List: + """Create features and amenities page""" + story = [] + + # Page Title + story.append(Paragraph("FEATURES & AMENITIES", self.styles['SectionHeader'])) + story.append(Spacer(1, 30)) + + # Interior Features + story.append(Paragraph("INTERIOR FEATURES", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + interior_features = [ + "โ€ข Master suite with walk-in closet", + "โ€ข En-suite bathrooms with premium fixtures", + "โ€ข Open-concept living areas", + "โ€ข Gourmet kitchen with island", + "โ€ข Formal dining room", + "โ€ข Home office/study", + "โ€ข Media room/home theater", + "โ€ข Wine cellar/storage" + ] + + for feature in interior_features: + story.append(Paragraph(feature, self.styles['ContentText'])) + + story.append(Spacer(1, 30)) + + # Exterior Features + story.append(Paragraph("EXTERIOR FEATURES", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + exterior_features = [ + "โ€ข Private swimming pool", + "โ€ข Outdoor kitchen and dining area", + "โ€ข Landscaped gardens", + "โ€ข Private parking/garage", + "โ€ข Security gate and fencing", + "โ€ข Outdoor entertainment areas", + "โ€ข Garden sheds/storage", + "โ€ข Professional landscaping" + ] + + for feature in exterior_features: + story.append(Paragraph(feature, self.styles['ContentText'])) + + return story + + def _create_investment_page(self, data: Dict[str, Any]) -> List: + """Create investment and location page""" + story = [] + + # Page Title + story.append(Paragraph("INVESTMENT & LOCATION", self.styles['SectionHeader'])) + story.append(Spacer(1, 30)) + + # Location Benefits + story.append(Paragraph("LOCATION BENEFITS", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + location_benefits = [ + "โ€ข Prime location in prestigious area", + "โ€ข Easy access to major highways", + "โ€ข Close to shopping and dining", + "โ€ข Excellent schools nearby", + "โ€ข Public transportation access", + "โ€ข Healthcare facilities nearby", + "โ€ข Recreational facilities close by", + "โ€ข High appreciation potential" + ] + + for benefit in location_benefits: + story.append(Paragraph(benefit, self.styles['ContentText'])) + + story.append(Spacer(1, 30)) + + # Investment Highlights + story.append(Paragraph("INVESTMENT HIGHLIGHTS", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + investment_highlights = [ + "โ€ข Strong rental yield potential", + "โ€ข High capital appreciation", + "โ€ข Low maintenance costs", + "โ€ข Premium tenant attraction", + "โ€ข Stable market conditions", + "โ€ข Excellent resale value", + "โ€ข Tax benefits available", + "โ€ข Professional property management" + ] + + for highlight in investment_highlights: + story.append(Paragraph(highlight, self.styles['ContentText'])) + + story.append(Spacer(1, 40)) + + # Contact Information + story.append(Paragraph("CONTACT US", self.styles['FeatureText'])) + story.append(Paragraph("For more information about this exclusive property,", self.styles['ContentText'])) + story.append(Paragraph("please contact our luxury property specialists.", self.styles['ContentText'])) + story.append(Spacer(1, 20)) + story.append(Paragraph("LUXURY REAL ESTATE", self.styles['FeatureText'])) + story.append(Paragraph("Premium Property Solutions", self.styles['ContentText'])) + + return story + + def _create_luxury_header_footer(self, canvas_obj, page_num: int, template_name: str): + """Create luxury header and footer""" + # Header + canvas_obj.setFillColor(HexColor('#2c1810')) + canvas_obj.setFont("Helvetica-Bold", 18) + canvas_obj.drawString(50, A4[1] - 40, "LUXURY REAL ESTATE") + + canvas_obj.setFont("Helvetica", 14) + canvas_obj.drawString(50, A4[1] - 60, "Premium Property Brochure") + + # Template indicator + canvas_obj.setFillColor(HexColor('#8b4513')) + canvas_obj.setFont("Helvetica-Bold", 16) + canvas_obj.drawRightString(A4[0] - 50, A4[1] - 40, template_name) + + # Footer + canvas_obj.setFillColor(HexColor('#8b4513')) + canvas_obj.setFont("Helvetica", 12) + canvas_obj.drawCentredString(A4[0]/2, 35, f"Generated on {datetime.now().strftime('%B %d, %Y')}") + canvas_obj.drawCentredString(A4[0]/2, 20, "Luxury Real Estate - Premium Property Solutions") + + # Page number + canvas_obj.drawRightString(A4[0] - 50, 20, f"Page {page_num}") + + def create_modern_apartment_template(self, data: Dict[str, Any], output_path: str) -> str: + """Create modern apartment template with contemporary design""" + doc = SimpleDocTemplate( + output_path, + pagesize=A4, + rightMargin=0.4*cm, + leftMargin=0.4*cm, + topMargin=0.4*cm, + bottomMargin=0.4*cm + ) + + story = [] + + # Page 1: Modern Cover + story.extend(self._create_modern_cover(data)) + story.append(PageBreak()) + + # Page 2: Modern Features + story.extend(self._create_modern_features(data)) + story.append(PageBreak()) + + # Page 3: Modern Amenities + story.extend(self._create_modern_amenities(data)) + + # Build PDF + doc.build(story, onFirstPage=lambda c, d: self._create_modern_header_footer(c, 1, "MODERN APARTMENT"), + onLaterPages=lambda c, d: self._create_modern_header_footer(c, d.page, "MODERN APARTMENT")) + + return output_path + + def _create_modern_cover(self, data: Dict[str, Any]) -> List: + """Create modern cover page""" + story = [] + + # Main Title + story.append(Paragraph("THE MODERN COLLECTION", self.styles['UltraTitle'])) + story.append(Spacer(1, 35)) + + # Property Name + story.append(Paragraph(f"{data.get('propertyName', 'Modern Apartment')}", self.styles['UltraTitle'])) + story.append(Spacer(1, 25)) + + # Location + story.append(Paragraph(f"Located in {data.get('location', 'Dubai')}", self.styles['PremiumSubtitle'])) + story.append(Spacer(1, 45)) + + # Price + story.append(Paragraph("INVESTMENT VALUE", self.styles['SectionHeader'])) + story.append(Paragraph(f"AED {data.get('price', 'N/A')}", self.styles['PriceDisplay'])) + story.append(Spacer(1, 50)) + + # Modern Stats + stats_data = [ + ['BEDROOMS', 'BATHROOMS', 'AREA', 'LOCATION'], + [ + data.get('bedrooms', 'N/A'), + data.get('bathrooms', 'N/A'), + f"{data.get('area', 'N/A')} sq ft", + data.get('location', 'N/A') + ] + ] + + stats_table = Table(stats_data, colWidths=[1.8*inch, 1.8*inch, 1.8*inch, 1.8*inch]) + stats_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (-1, 0), HexColor('#1e3a8a')), + ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 16), + ('FONTSIZE', (0, 1), (-1, 1), 18), + ('BOTTOMPADDING', (0, 0), (-1, -1), 20), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#1e3a8a')), + ('ROUNDEDCORNERS', [15, 15, 15, 15]) + ])) + + story.append(stats_table) + story.append(Spacer(1, 50)) + + # Footer + story.append(Paragraph("CONTEMPORARY โ€ข ELEGANT โ€ข URBAN", self.styles['PremiumSubtitle'])) + story.append(Paragraph("Experience the future of urban living", self.styles['ContentText'])) + + return story + + def _create_modern_features(self, data: Dict[str, Any]) -> List: + """Create modern features page""" + story = [] + + story.append(Paragraph("MODERN FEATURES", self.styles['SectionHeader'])) + story.append(Spacer(1, 30)) + + # Design Features + story.append(Paragraph("DESIGN FEATURES", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + design_features = [ + "โ€ข Open-concept floor plan", + "โ€ข Floor-to-ceiling windows", + "โ€ข High ceilings", + "โ€ข Modern minimalist design", + "โ€ข Smart home integration", + "โ€ข Energy-efficient appliances", + "โ€ข Premium materials and finishes", + "โ€ข Custom lighting design" + ] + + for feature in design_features: + story.append(Paragraph(feature, self.styles['ContentText'])) + + story.append(Spacer(1, 30)) + + # Technology Features + story.append(Paragraph("TECHNOLOGY FEATURES", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + tech_features = [ + "โ€ข Smart home automation", + "โ€ข High-speed internet", + "โ€ข Security camera systems", + "โ€ข Digital door locks", + "โ€ข Climate control systems", + "โ€ข Entertainment systems", + "โ€ข Mobile app control", + "โ€ข Energy monitoring" + ] + + for feature in tech_features: + story.append(Paragraph(feature, self.styles['ContentText'])) + + return story + + def _create_modern_amenities(self, data: Dict[str, Any]) -> List: + """Create modern amenities page""" + story = [] + + story.append(Paragraph("MODERN AMENITIES", self.styles['SectionHeader'])) + story.append(Spacer(1, 30)) + + # Building Amenities + story.append(Paragraph("BUILDING AMENITIES", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + building_amenities = [ + "โ€ข Rooftop swimming pool", + "โ€ข Fitness center with latest equipment", + "โ€ข Co-working spaces", + "โ€ข Rooftop terrace and gardens", + "โ€ข Concierge services", + "โ€ข Package delivery lockers", + "โ€ข Bike storage", + "โ€ข Electric vehicle charging" + ] + + for amenity in building_amenities: + story.append(Paragraph(f"๐Ÿข {amenity}", self.styles['ContentText'])) + + story.append(Spacer(1, 30)) + + # Lifestyle Amenities + story.append(Paragraph("LIFESTYLE AMENITIES", self.styles['FeatureText'])) + story.append(Spacer(1, 15)) + + lifestyle_amenities = [ + "โ€ข Community lounge areas", + "โ€ข Outdoor dining spaces", + "โ€ข Children's play areas", + "โ€ข Pet-friendly facilities", + "โ€ข Guest parking", + "โ€ข 24/7 security", + "โ€ข Maintenance services", + "โ€ข Community events" + ] + + for amenity in lifestyle_amenities: + story.append(Paragraph(f"๐ŸŒŸ {amenity}", self.styles['ContentText'])) + + return story + + def _create_modern_header_footer(self, canvas_obj, page_num: int, template_name: str): + """Create modern header and footer""" + # Header + canvas_obj.setFillColor(HexColor('#1e3a8a')) + canvas_obj.setFont("Helvetica-Bold", 18) + canvas_obj.drawString(50, A4[1] - 40, "MODERN REAL ESTATE") + + canvas_obj.setFont("Helvetica", 14) + canvas_obj.drawString(50, A4[1] - 60, "Contemporary Property Solutions") + + # Template indicator + canvas_obj.setFillColor(HexColor('#3b82f6')) + canvas_obj.setFont("Helvetica-Bold", 16) + canvas_obj.drawRightString(A4[0] - 50, A4[1] - 40, template_name) + + # Footer + canvas_obj.setFillColor(HexColor('#475569')) + canvas_obj.setFont("Helvetica", 12) + canvas_obj.drawCentredString(A4[0]/2, 35, f"Generated on {datetime.now().strftime('%B %d, %Y')}") + canvas_obj.drawCentredString(A4[0]/2, 20, "Modern Real Estate - Contemporary Living") + + # Page number + canvas_obj.drawRightString(A4[0] - 50, 20, f"Page {page_num}") + +def main(): + """Test the advanced templates""" + templates = AdvancedPropertyTemplates() + + # Sample data + sample_data = { + 'propertyName': 'Luxury Marina Villa', + 'propertyType': 'Villa', + 'location': 'Dubai Marina', + 'price': '5,500,000', + 'bedrooms': '5', + 'bathrooms': '6', + 'area': '4,200', + 'description': 'Stunning luxury villa with panoramic marina views, premium finishes, and exclusive amenities.', + 'amenities': ['Private Pool', 'Gym', 'Security', 'Garden', 'Garage', 'Smart Home'] + } + + # Test luxury villa template + try: + result = templates.create_luxury_villa_template(sample_data, 'luxury_villa_brochure.pdf') + print(f"Luxury villa PDF generated: {result}") + except Exception as e: + print(f"Error: {str(e)}") + + # Test modern apartment template + try: + result = templates.create_modern_apartment_template(sample_data, 'modern_apartment_brochure.pdf') + print(f"Modern apartment PDF generated: {result}") + except Exception as e: + print(f"Error: {str(e)}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/python-pdf-generator/api_server.py b/python-pdf-generator/api_server.py new file mode 100644 index 0000000..8d80d7d --- /dev/null +++ b/python-pdf-generator/api_server.py @@ -0,0 +1,626 @@ +#!/usr/bin/env python3 +""" +FastAPI server for Property PDF Generator +Provides REST API endpoints for generating high-quality property brochures +""" + +from fastapi import FastAPI, HTTPException, UploadFile, File, Form +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import FileResponse, JSONResponse +from pydantic import BaseModel, Field +from typing import List, Optional, Dict, Any +import uvicorn +import os +import tempfile +import json +from datetime import datetime +from property_pdf_generator import PropertyPDFGenerator + +# Initialize FastAPI app +app = FastAPI( + title="Property PDF Generator API", + description="High-quality property brochure PDF generation service", + version="1.0.0" +) + +# Add CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, restrict to specific domains + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Initialize PDF generator +pdf_generator = PropertyPDFGenerator() + +# Store generated PDFs temporarily (in production, use proper file storage) +generated_pdfs = {} + +# Cleanup old PDFs periodically +import asyncio +import time + +async def cleanup_old_pdfs(): + """Clean up old PDF files to prevent disk space issues""" + while True: + try: + current_time = time.time() + expired_files = [] + + for filename, filepath in generated_pdfs.items(): + # Remove files older than 1 hour + if current_time - os.path.getctime(filepath) > 3600: + try: + if os.path.exists(filepath): + os.unlink(filepath) + expired_files.append(filename) + except Exception: + pass + + # Remove expired entries from dictionary + for filename in expired_files: + generated_pdfs.pop(filename, None) + + # Run cleanup every 30 minutes + await asyncio.sleep(1800) + + except Exception: + # Continue cleanup even if there's an error + await asyncio.sleep(1800) + +# Start cleanup task +cleanup_task = None + +@app.on_event("startup") +async def startup_event(): + """Start cleanup task on startup""" + global cleanup_task + cleanup_task = asyncio.create_task(cleanup_old_pdfs()) + +@app.on_event("shutdown") +async def shutdown_event(): + """Stop cleanup task and clean up files on shutdown""" + global cleanup_task + if cleanup_task: + cleanup_task.cancel() + + # Clean up all stored PDFs + for filepath in generated_pdfs.values(): + try: + if os.path.exists(filepath): + os.unlink(filepath) + except Exception: + pass + generated_pdfs.clear() + +# Data models +class PropertyData(BaseModel): + """Enhanced property information model with market data and analytics""" + # Template & Layout + template: str = Field(..., description="Template name to use") + layout: Optional[str] = Field(None, description="Layout configuration for custom templates") + + # Basic Property Information + propertyName: str = Field(..., description="Name of the property") + propertyType: str = Field(..., description="Type of property") + location: str = Field(..., description="Property location") + price: str = Field(..., description="Property price") + bedrooms: str = Field(..., description="Number of bedrooms") + bathrooms: str = Field(..., description="Number of bathrooms") + area: str = Field(..., description="Property area in sq ft") + description: Optional[str] = Field(None, description="Property description") + amenities: List[str] = Field(default=[], description="List of amenities") + images: List[str] = Field(default=[], description="Base64 encoded images") + imageNames: List[str] = Field(default=[], description="Room names for each image") + + # Market Data & Analytics + marketTrend: Optional[str] = Field(None, description="Market trend (rising/stable/declining)") + roiPotential: Optional[str] = Field(None, description="Expected ROI percentage") + avgPricePerSqft: Optional[str] = Field(None, description="Average market price per sq ft") + marketDemand: Optional[str] = Field(None, description="Market demand level (high/medium/low)") + locationAdvantages: Optional[str] = Field(None, description="Location benefits and advantages") + + # Investment Information + investmentType: Optional[str] = Field(None, description="Investment type (buy-to-live/rent/sell)") + rentalYield: Optional[str] = Field(None, description="Expected rental yield percentage") + investmentHighlights: Optional[str] = Field(None, description="Key investment benefits") + + # Content Modules + contentModules: List[str] = Field(default=[], description="Selected content modules") + additionalContent: Optional[str] = Field(None, description="Additional custom content") + + # Customization Options + headerStyle: Optional[str] = Field("modern", description="Header style (modern/classic/luxury)") + colorScheme: Optional[str] = Field("blue", description="Color scheme (blue/green/purple/gold)") + fontStyle: Optional[str] = Field("sans-serif", description="Font style (sans-serif/serif/modern)") + +class TemplateInfo(BaseModel): + """Template information model""" + name: str + display_name: str + description: str + category: str + preview_color: str + +class GeneratePDFRequest(BaseModel): + """PDF generation request model""" + property_data: PropertyData + template_name: str + +class GeneratePDFResponse(BaseModel): + """PDF generation response model""" + success: bool + message: str + pdf_url: Optional[str] = None + error: Optional[str] = None + +# Available templates +AVAILABLE_TEMPLATES = { + # Professional Templates + "professional-1pager": { + "name": "professional-1pager", + "display_name": "Professional 1-Pager", + "description": "Compact single-page brochure with 2x2 image grid", + "category": "Professional", + "preview_color": "#667eea", + "pages": 1, + "image_grid": "2x2" + }, + "professional-3pager": { + "name": "professional-3pager", + "display_name": "Professional 3-Pager", + "description": "Comprehensive three-page brochure with detailed analysis", + "category": "Professional", + "preview_color": "#1e3a8a", + "pages": 3, + "image_grid": "4x4" + }, + "professional-5pager": { + "name": "professional-5pager", + "display_name": "Professional 5-Pager", + "description": "Premium five-page brochure with comprehensive analysis", + "category": "Professional", + "preview_color": "#059669", + "pages": 5, + "image_grid": "6x6" + }, + + # Luxury Templates + "luxury-villa": { + "name": "luxury-villa", + "display_name": "Luxury Villa Brochure", + "description": "Exclusive villa template with premium styling", + "category": "Luxury", + "preview_color": "#2c1810", + "pages": 4, + "image_grid": "5x4" + }, + "dubai-penthouse": { + "name": "dubai-penthouse", + "display_name": "Dubai Penthouse", + "description": "Dubai-specific luxury penthouse template", + "category": "Luxury", + "preview_color": "#dc2626", + "pages": 6, + "image_grid": "6x5" + }, + + # Modern Templates + "modern-apartment": { + "name": "modern-apartment", + "display_name": "Modern Apartment", + "description": "Contemporary apartment template with clean lines", + "category": "Modern", + "preview_color": "#7c3aed", + "pages": 3, + "image_grid": "3x5" + }, + + # Custom Template + "custom": { + "name": "custom", + "display_name": "Custom Template", + "description": "Build your own template with custom layouts", + "category": "Custom", + "preview_color": "#1f2937", + "pages": "flexible", + "image_grid": "configurable" + } +} + +@app.get("/") +async def root(): + """Root endpoint""" + return { + "message": "Property PDF Generator API", + "version": "1.0.0", + "status": "running", + "endpoints": { + "templates": "/api/templates", + "preview": "/api/preview", + "generate": "/api/generate-pdf", + "health": "/api/health" + } + } + +@app.get("/api/health") +async def health_check(): + """Health check endpoint""" + return { + "status": "healthy", + "timestamp": datetime.now().isoformat(), + "service": "Property PDF Generator" + } + +@app.get("/api/templates", response_model=List[TemplateInfo]) +async def get_templates(): + """Get all available templates""" + try: + templates = [] + for template_id, template_info in AVAILABLE_TEMPLATES.items(): + templates.append(TemplateInfo(**template_info)) + return templates + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error fetching templates: {str(e)}") + +@app.get("/api/templates/{template_name}") +async def get_template(template_name: str): + """Get specific template information""" + try: + if template_name not in AVAILABLE_TEMPLATES: + raise HTTPException(status_code=404, detail="Template not found") + + return AVAILABLE_TEMPLATES[template_name] + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error fetching template: {str(e)}") + +@app.post("/api/preview") +async def generate_preview(property_data: PropertyData): + """Generate a preview of the property brochure""" + try: + # Validate template + if property_data.template not in AVAILABLE_TEMPLATES: + raise HTTPException(status_code=400, detail="Invalid template selected") + + # Generate preview content + preview_content = { + "template": property_data.template, + "template_info": AVAILABLE_TEMPLATES[property_data.template], + "property_data": property_data.dict(), + "preview_html": generate_preview_html(property_data), + "generated_at": datetime.now().isoformat() + } + + return { + "success": True, + "preview": preview_content, + "message": "Preview generated successfully" + } + + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error generating preview: {str(e)}") + +@app.post("/api/generate-pdf", response_model=GeneratePDFResponse) +async def generate_pdf(request: GeneratePDFRequest): + """Generate a PDF property brochure""" + try: + # Validate template + if request.template_name not in AVAILABLE_TEMPLATES: + return GeneratePDFResponse( + success=False, + message="Invalid template selected", + error="Template not found" + ) + + # Validate property data + if not request.property_data.propertyName or not request.property_data.propertyType: + return GeneratePDFResponse( + success=False, + message="Missing required property information", + error="Property name and type are required" + ) + + # Create temporary file for PDF + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + pdf_path = tmp_file.name + + try: + # Generate PDF + result_path = pdf_generator.generate_property_pdf( + request.property_data.dict(), + request.template_name, + pdf_path + ) + + # Store the generated PDF for later download + filename = os.path.basename(result_path) + generated_pdfs[filename] = result_path + + # Generate download URL + pdf_url = f"/download-pdf/{filename}" + + return GeneratePDFResponse( + success=True, + message="PDF generated successfully", + pdf_url=pdf_url + ) + + except Exception as e: + # Clean up temporary file on error + if os.path.exists(pdf_path): + os.unlink(pdf_path) + raise e + + except Exception as e: + return GeneratePDFResponse( + success=False, + message="Error generating PDF", + error=str(e) + ) + +@app.get("/api/download-pdf/{filename}") +async def download_pdf(filename: str): + """Download generated PDF file""" + try: + # Check if the PDF exists in our storage + if filename in generated_pdfs and os.path.exists(generated_pdfs[filename]): + pdf_path = generated_pdfs[filename] + + # Return the actual generated PDF + return FileResponse( + pdf_path, + media_type='application/pdf', + filename=f"property_brochure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" + ) + else: + # Fallback: generate a demo PDF + with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file: + demo_pdf_path = tmp_file.name + + # Generate a demo PDF + demo_data = { + "propertyName": "Demo Property", + "propertyType": "Apartment", + "location": "Dubai", + "price": "1,500,000", + "bedrooms": "2", + "bathrooms": "2", + "area": "1,200", + "description": "This is a demo property brochure generated by the Property PDF Generator API.", + "amenities": ['Swimming Pool', 'Gym', 'Parking'], + "images": [] + } + + pdf_generator.generate_property_pdf(demo_data, "modern", demo_pdf_path) + + # Return the demo file + return FileResponse( + demo_pdf_path, + media_type='application/pdf', + filename=f"demo_property_brochure.pdf" + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error downloading PDF: {str(e)}") + +def generate_preview_html(property_data: PropertyData) -> str: + """Generate comprehensive HTML preview of the property brochure with all new data""" + template_info = AVAILABLE_TEMPLATES.get(property_data.template, {}) + color = template_info.get('preview_color', '#333') + + # Market trend styling + trend_color = { + 'rising': '#28a745', + 'stable': '#ffc107', + 'declining': '#dc3545' + }.get(property_data.marketTrend, '#6c757d') + + # Investment type styling + investment_color = { + 'buy-to-live': '#007bff', + 'buy-to-rent': '#28a745', + 'buy-to-sell': '#ffc107' + }.get(property_data.investmentType, '#6c757d') + + preview_html = f""" +
+ +
+
+

+ {property_data.propertyName} +

+

+ {property_data.propertyType} in {property_data.location} +

+
+
+
+

Template

+

{template_info.get('display_name', 'Unknown')}

+
+
+

Pages

+

{template_info.get('pages', 'N/A')}

+
+
+

Layout

+

{property_data.layout or template_info.get('image_grid', 'Default')}

+
+
+
+ + +
+
+

Property Details

+
+
+ Type: {property_data.propertyType} +
+
+ Location: {property_data.location} +
+
+ Price: AED {property_data.price} +
+
+ Bedrooms: {property_data.bedrooms} +
+
+ Bathrooms: {property_data.bathrooms} +
+
+ Area: {property_data.area} sq ft +
+
+
+ + {f''' +
+

Market Analytics

+
+
+ Market Trend: + + {property_data.marketTrend.title() if property_data.marketTrend else 'N/A'} + +
+
+ ROI Potential: {property_data.roiPotential}% +
+
+ Avg Price/sq ft: AED {property_data.avgPricePerSqft} +
+
+ Market Demand: + + {property_data.marketDemand.title() if property_data.marketDemand else 'N/A'} + +
+
+
+ ''' if any([property_data.marketTrend, property_data.roiPotential, property_data.avgPricePerSqft, property_data.marketDemand]) else ''} + + {f''' +
+

Investment Details

+
+
+ Investment Type: + + {property_data.investmentType.replace('-', ' ').title() if property_data.investmentType else 'N/A'} + +
+
+ Rental Yield: {property_data.rentalYield}% +
+
+
+ ''' if any([property_data.investmentType, property_data.rentalYield]) else ''} +
+ + {f''' +
+

Property Description

+

{property_data.description}

+
+ ''' if property_data.description else ''} + + {f''' +
+

Location Advantages

+

{property_data.locationAdvantages}

+
+ ''' if property_data.locationAdvantages else ''} + + {f''' +
+

Investment Highlights

+

{property_data.investmentHighlights}

+
+ ''' if property_data.investmentHighlights else ''} + + {f''' +
+

Amenities

+
+ {''.join([f'{amenity}' for amenity in property_data.amenities])} +
+
+ ''' if property_data.amenities else ''} + + {f''' +
+

Content Modules

+
+ {''.join([f'{module.replace("-", " ").title()}' for module in property_data.contentModules])} +
+
+ ''' if property_data.contentModules else ''} + + {f''' +
+

Additional Content

+

{property_data.additionalContent}

+
+ ''' if property_data.additionalContent else ''} + + +
+

Preview Information

+

Template: {template_info.get('display_name', 'Unknown')}

+

Pages: {template_info.get('pages', 'N/A')} | Layout: {property_data.layout or template_info.get('image_grid', 'Default')}

+

This is a preview. The final PDF will be generated with professional styling and layouts.

+
+
+ """ + + return preview_html + +@app.post("/api/upload-images") +async def upload_images(files: List[UploadFile] = File(...)): + """Upload property images""" + try: + uploaded_images = [] + + for file in files: + if file.content_type.startswith('image/'): + # Read image content + content = await file.read() + + # Convert to base64 for storage + import base64 + base64_content = base64.b64encode(content).decode('utf-8') + + uploaded_images.append({ + "filename": file.filename, + "content_type": file.content_type, + "size": len(content), + "base64_data": f"data:{file.content_type};base64,{base64_content}" + }) + + return { + "success": True, + "message": f"Successfully uploaded {len(uploaded_images)} images", + "images": uploaded_images + } + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error uploading images: {str(e)}") + +if __name__ == "__main__": + # Run the server + uvicorn.run( + "api_server:app", + host="0.0.0.0", + port=8000, + reload=True, + log_level="info" + ) \ No newline at end of file diff --git a/python-pdf-generator/property_pdf_generator.py b/python-pdf-generator/property_pdf_generator.py new file mode 100644 index 0000000..49c8aad --- /dev/null +++ b/python-pdf-generator/property_pdf_generator.py @@ -0,0 +1,1561 @@ +#!/usr/bin/env python3 +""" +High-Quality Property PDF Generator using ReportLab +Generates professional property brochures with market analysis and investment insights +""" + +import os +import io +import base64 +from datetime import datetime +from typing import Dict, Any, List, Optional +from reportlab.lib.pagesizes import A4, letter +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle, Image +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.units import inch +from reportlab.lib import colors +from reportlab.lib.colors import HexColor +from PIL import Image as PILImage +import tempfile + +class PropertyPDFGenerator: + """High-quality property PDF generator with professional templates""" + + def __init__(self): + """Initialize the PDF generator with professional styles""" + self.styles = getSampleStyleSheet() + self._setup_premium_styles() + + def _setup_premium_styles(self): + """Setup premium styling for professional brochures""" + # Ultra Premium Title Style + self.styles.add(ParagraphStyle( + name='UltraPremiumTitle', + parent=self.styles['Title'], + fontSize=36, + fontName='Helvetica-Bold', + textColor=HexColor('#1a202c'), + spaceAfter=20, + alignment=1, # Center + leading=40 + )) + + # Luxury Collection Title Style + self.styles.add(ParagraphStyle( + name='LuxuryCollectionTitle', + parent=self.styles['Heading1'], + fontSize=24, + fontName='Helvetica-Bold', + textColor=HexColor('#2d3748'), + spaceAfter=15, + alignment=1, # Center + leading=28 + )) + + # Modern Collection Title Style + self.styles.add(ParagraphStyle( + name='ModernCollectionTitle', + parent=self.styles['Heading1'], + fontSize=22, + fontName='Helvetica-Bold', + textColor=HexColor('#4a5568'), + spaceAfter=15, + alignment=1, # Center + leading=26 + )) + + # Dubai Collection Title Style + self.styles.add(ParagraphStyle( + name='DubaiCollectionTitle', + parent=self.styles['Heading1'], + fontSize=26, + fontName='Helvetica-Bold', + textColor=HexColor('#c53030'), + spaceAfter=15, + alignment=1, # Center + leading=30 + )) + + # Premium Subtitle Style + self.styles.add(ParagraphStyle( + name='PremiumSubtitle', + parent=self.styles['Heading2'], + fontSize=18, + fontName='Helvetica-Bold', + textColor=HexColor('#4a5568'), + spaceAfter=12, + alignment=1, # Center + leading=22 + )) + + # Section Header Style + self.styles.add(ParagraphStyle( + name='SectionHeader', + parent=self.styles['Heading3'], + fontSize=16, + fontName='Helvetica-Bold', + textColor=HexColor('#2d3748'), + spaceAfter=10, + spaceBefore=15, + leading=20 + )) + + # Premium Content Text Style + self.styles.add(ParagraphStyle( + name='PremiumContentText', + parent=self.styles['Normal'], + fontSize=12, + fontName='Helvetica', + textColor=HexColor('#4a5568'), + spaceAfter=8, + leading=16 + )) + + # Premium Feature Text Style + self.styles.add(ParagraphStyle( + name='PremiumFeatureText', + parent=self.styles['Normal'], + fontSize=12, + fontName='Helvetica-Bold', + textColor=HexColor('#2d3748'), + spaceAfter=6, + leading=16 + )) + + # Premium Price Display Style + self.styles.add(ParagraphStyle( + name='PremiumPriceDisplay', + parent=self.styles['Normal'], + fontSize=28, + fontName='Helvetica-Bold', + textColor=HexColor('#2c5530'), + spaceAfter=15, + alignment=1, # Center + leading=32 + )) + + # Location Text Style + self.styles.add(ParagraphStyle( + name='LocationText', + parent=self.styles['Normal'], + fontSize=16, + fontName='Helvetica-Bold', + textColor=HexColor('#718096'), + spaceAfter=10, + alignment=1, # Center + leading=20 + )) + + # Amenity Text Style + self.styles.add(ParagraphStyle( + name='AmenityText', + parent=self.styles['Normal'], + fontSize=11, + fontName='Helvetica', + textColor=HexColor('#4a5568'), + spaceAfter=4, + leading=15 + )) + + def _create_professional_header_footer(self, canvas_obj, page_num: int, template_name: str): + """Create professional header and footer for each page""" + # Header + canvas_obj.setFillColor(HexColor('#1f2937')) + canvas_obj.setFont("Helvetica-Bold", 18) + canvas_obj.drawString(50, A4[1] - 40, "LUXURY REAL ESTATE") + + canvas_obj.setFont("Helvetica", 14) + canvas_obj.drawString(50, A4[1] - 60, "Premium Property Brochure") + + # Footer + canvas_obj.setFillColor(HexColor('#6b7280')) + canvas_obj.setFont("Helvetica", 12) + canvas_obj.drawCentredString(A4[0]/2, 35, f"Generated on {datetime.now().strftime('%B %d, %Y')}") + canvas_obj.drawCentredString(A4[0]/2, 20, "Luxury Real Estate - Premium Property Solutions") + + # Page number + canvas_obj.drawRightString(A4[0] - 50, 20, f"Page {page_num}") + + def generate_property_pdf(self, property_data: Dict[str, Any], template_type: str = 'professional-3pager', output_file: str = None) -> str: + """Generate high-quality property PDF based on template type""" + try: + # Create output file path + if not output_file: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_file = f"property_brochure_{timestamp}.pdf" + + # Create PDF document + doc = SimpleDocTemplate( + output_file, + pagesize=A4, + rightMargin=0.75*inch, + leftMargin=0.75*inch, + topMargin=1*inch, + bottomMargin=1*inch + ) + + # Process images if available + processed_images = [] + if property_data.get('images') and len(property_data.get('images', [])) > 0: + processed_images = self.process_images(property_data.get('images', [])) + + # Build story based on template type + if template_type == 'professional-1pager': + story = self._build_professional_1pager_brochure(property_data, processed_images) + elif template_type == 'professional-3pager': + story = self._build_professional_3pager_brochure(property_data, processed_images) + elif template_type == 'professional-5pager': + story = self._build_professional_5pager_brochure(property_data, processed_images) + elif template_type == 'luxury-villa': + story = self._build_luxury_villa_brochure(property_data, processed_images) + elif template_type == 'dubai-penthouse': + story = self._build_dubai_penthouse_brochure(property_data, processed_images) + elif template_type == 'modern-apartment': + story = self._build_modern_apartment_brochure(property_data, processed_images) + elif template_type == 'custom': + story = self._build_custom_template_brochure(property_data, processed_images) + else: + # Default to professional 3-pager + story = self._build_professional_3pager_brochure(property_data, processed_images) + + # Build PDF with header/footer + doc.build(story, onFirstPage=lambda canvas, doc: self._create_professional_header_footer(canvas, 1, template_type), + onLaterPages=lambda canvas, doc: self._create_professional_header_footer(canvas, doc.page, template_type)) + + return output_file + + except Exception as e: + print(f"Error generating PDF: {str(e)}") + raise + + def process_images(self, images: List[str]) -> List[Image]: + """Process and resize images for PDF inclusion with high quality""" + processed_images = [] + + for img_data in images: + try: + # Remove data URL prefix if present + if img_data.startswith('data:image'): + img_data = img_data.split(',')[1] + + # Decode base64 image + img_bytes = base64.b64decode(img_data) + img = PILImage.open(io.BytesIO(img_bytes)) + + # Convert to RGB if necessary + if img.mode != 'RGB': + img = img.convert('RGB') + + # Resize image to fit PDF with high quality + max_width = 6 * inch # Maximum width for luxury brochures + aspect_ratio = img.width / img.height + new_width = min(max_width, img.width) + new_height = new_width / aspect_ratio + + # Ensure minimum height for quality + min_height = 2 * inch + if new_height < min_height: + new_height = min_height + new_width = new_height * aspect_ratio + + img = img.resize((int(new_width), int(new_height)), PILImage.Resampling.LANCZOS) + + # Save to bytes with maximum quality + img_buffer = io.BytesIO() + img.save(img_buffer, format='JPEG', quality=95, optimize=True) + img_buffer.seek(0) + + # Create ReportLab Image with proper dimensions + reportlab_img = Image(img_buffer, width=new_width, height=new_height) + processed_images.append(reportlab_img) + + except Exception as e: + print(f"Error processing image: {str(e)}") + continue + + return processed_images + + def _build_professional_1pager_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a professional 1-page brochure with all content and images""" + story = [] + + # Cover/Header Section + story.append(Paragraph(f"{property_data.get('propertyName', 'Luxury Property')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Premium Property')} โ€ข {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle'])) + story.append(Spacer(1, 20)) + + # Price Section + price_color = "#2c5530" + story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay'])) + story.append(Spacer(1, 15)) + + # Property Details Table + details_data = [ + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + # Add market data if available + if property_data.get('marketTrend'): + details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + if property_data.get('avgPricePerSqft'): + details_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"]) + if property_data.get('marketDemand'): + details_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()]) + + details_table = Table(details_data, colWidths=[3*inch, 3*inch]) + details_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 8), + ('BOTTOMPADDING', (0, 0), (-1, -1), 8), + ])) + story.append(details_table) + story.append(Spacer(1, 20)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Investment highlights + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Location Advantages + if property_data.get('locationAdvantages'): + story.append(Paragraph('Location Advantages', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Content Modules + if property_data.get('contentModules'): + story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader'])) + for module in property_data.get('contentModules', []): + module_name = module.replace('-', ' ').title() + story.append(Paragraph(f"โœ“ {module_name}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # All Property Images with Room Names (2x3 grid for 1-pager) + if processed_images and len(processed_images) > 0: + story.append(Paragraph('Property Gallery', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + image_names = property_data.get('imageNames', []) + + # Create 2x3 grid for images with room names + for i in range(0, min(len(processed_images), 6), 2): # Max 6 images on 1 page + row_images = processed_images[i:i+2] + row_names = image_names[i:i+2] if len(image_names) > i else [] + + # Create table with images and names + row_data = [] + for j, img in enumerate(row_images): + room_name = row_names[j] if j < len(row_names) else f'Room {i+j+1}' + row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + # Pad row to 2 columns if needed + while len(row_data) < 2: + row_data.append(['', '']) + + row_table = Table(row_data, colWidths=[3*inch, 3*inch]) + row_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row_table) + story.append(Spacer(1, 10)) + + story.append(Spacer(1, 15)) + + # All Amenities + if property_data.get('amenities'): + story.append(Paragraph('Premium Amenities', self.styles['SectionHeader'])) + # Show all amenities in a compact format + amenities_text = ' โ€ข '.join(property_data.get('amenities', [])) + story.append(Paragraph(amenities_text, self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Additional Content + if property_data.get('additionalContent'): + story.append(Paragraph('Additional Information', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Layout and Customization info + customization_info = [] + if property_data.get('layout'): + customization_info.append(f"Layout: {property_data.get('layout', 'Default').title()}") + if property_data.get('headerStyle'): + customization_info.append(f"Header: {property_data.get('headerStyle', 'Modern').title()}") + if property_data.get('colorScheme'): + customization_info.append(f"Colors: {property_data.get('colorScheme', 'Blue').title()}") + if property_data.get('fontStyle'): + customization_info.append(f"Font: {property_data.get('fontStyle', 'Sans-Serif').title()}") + + if customization_info: + story.append(Paragraph('Document Customization', self.styles['SectionHeader'])) + story.append(Paragraph(' โ€ข '.join(customization_info), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Contact Information + story.append(Paragraph('Contact Information', self.styles['SectionHeader'])) + story.append(Paragraph("๐Ÿ“ง Email: info@luxuryrealestate.com โ€ข ๐Ÿ“ž Phone: +971 4 XXX XXXX โ€ข ๐ŸŒ www.luxuryrealestate.com", self.styles['PremiumContentText'])) + + return story + + def _build_professional_3pager_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a comprehensive 3-page professional brochure with market analysis""" + story = [] + + # PAGE 1: Cover & Overview + story.append(Paragraph(f"{property_data.get('propertyName', 'Premium Property')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Luxury Property')} in {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle'])) + story.append(Spacer(1, 30)) + + # Add main property image if available + if processed_images and len(processed_images) > 0: + story.append(processed_images[0]) + story.append(Spacer(1, 20)) + + # Executive Summary + story.append(Paragraph('Executive Summary', self.styles['SectionHeader'])) + exec_summary = f"This exceptional {property_data.get('propertyType', 'property')} represents a unique investment opportunity in {property_data.get('location', 'a prime location')}. " + if property_data.get('roiPotential'): + exec_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, this property offers excellent investment potential. " + exec_summary += property_data.get('description', 'A premium property with outstanding features and amenities.') + story.append(Paragraph(exec_summary, self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Key Features Table + features_data = [ + ['Price', f"AED {property_data.get('price', 'On Request')}"], + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Location', property_data.get('location', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + features_table = Table(features_data, colWidths=[2.5*inch, 3.5*inch]) + features_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#e3f2fd')), + ('BACKGROUND', (0, 0), (0, 0), HexColor('#1976d2')), + ('TEXTCOLOR', (0, 0), (0, 0), colors.white), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 11), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#1976d2')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 10), + ('BOTTOMPADDING', (0, 0), (-1, -1), 10), + ])) + story.append(features_table) + story.append(Spacer(1, 30)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Basic Amenities on Page 1 + if property_data.get('amenities'): + story.append(Paragraph('Key Amenities', self.styles['SectionHeader'])) + # Show first 6 amenities on page 1 + key_amenities = property_data.get('amenities', [])[:6] + for amenity in key_amenities: + story.append(Paragraph(f"โ€ข {amenity}", self.styles['PremiumContentText'])) + + # PAGE BREAK TO START PAGE 2 + story.append(PageBreak()) + + # PAGE 2: Market Analysis & Investment Details + story.append(Paragraph('Market Analysis & Investment Overview', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # Add more property images if available (2x2 grid with room names) + if processed_images and len(processed_images) > 1: + story.append(Paragraph('Property Gallery', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create 2x2 image grid for page 2 with room names + remaining_images = processed_images[1:5] # Take next 4 images + image_names = property_data.get('imageNames', []) + + if len(remaining_images) >= 2: + # First row + row1_images = remaining_images[:2] + row1_names = image_names[1:3] if len(image_names) > 1 else ['Room 2', 'Room 3'] + + # Create table with images and names + row1_data = [] + for i, img in enumerate(row1_images): + room_name = row1_names[i] if i < len(row1_names) else f'Room {i+2}' + row1_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + row1_table = Table(row1_data, colWidths=[2.5*inch, 2.5*inch]) + row1_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row1_table) + story.append(Spacer(1, 15)) + + # Second row if we have more images + if len(remaining_images) >= 4: + row2_images = remaining_images[2:4] + row2_names = image_names[3:5] if len(image_names) > 3 else ['Room 4', 'Room 5'] + + row2_data = [] + for i, img in enumerate(row2_images): + room_name = row2_names[i] if i < len(row2_names) else f'Room {i+4}' + row2_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + row2_table = Table(row2_data, colWidths=[2.5*inch, 2.5*inch]) + row2_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row2_table) + story.append(Spacer(1, 20)) + + # Market Data Section + if any([property_data.get('marketTrend'), property_data.get('roiPotential'), property_data.get('avgPricePerSqft')]): + story.append(Paragraph('Market Intelligence', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + market_data = [] + if property_data.get('marketTrend'): + market_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + market_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + if property_data.get('avgPricePerSqft'): + market_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"]) + if property_data.get('marketDemand'): + market_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()]) + + market_table = Table(market_data, colWidths=[3*inch, 3*inch]) + market_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#e8f5e8')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#388e3c')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 10), + ('BOTTOMPADDING', (0, 0), (-1, -1), 10), + ])) + story.append(market_table) + story.append(Spacer(1, 25)) + + # Investment Analysis + if any([property_data.get('investmentType'), property_data.get('rentalYield'), property_data.get('investmentHighlights')]): + story.append(Paragraph('Investment Analysis', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + if property_data.get('investmentType'): + story.append(Paragraph(f"Investment Strategy: {property_data.get('investmentType', '').replace('-', ' ').title()}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + + if property_data.get('rentalYield'): + story.append(Paragraph(f"Expected Rental Yield: {property_data.get('rentalYield')}% annually", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights:', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + + story.append(Spacer(1, 25)) + + # Location Advantages + if property_data.get('locationAdvantages'): + story.append(Paragraph('Location Advantages', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Content Modules on Page 2 + if property_data.get('contentModules'): + story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader'])) + for module in property_data.get('contentModules', []): + module_name = module.replace('-', ' ').title() + story.append(Paragraph(f"โœ“ {module_name}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # PAGE BREAK TO START PAGE 3 + story.append(PageBreak()) + + # PAGE 3: Complete Amenities & Additional Information + story.append(Paragraph('Complete Property Features & Amenities', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # Add remaining images if available (4x4 grid for page 3 with room names) + if processed_images and len(processed_images) > 5: + story.append(Paragraph('Additional Property Views', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create 4x4 grid for remaining images with room names + remaining_images = processed_images[5:13] # Take next 8 images + remaining_names = image_names[5:13] if len(image_names) > 5 else [] + + if remaining_images: + # Process images in rows of 4 with room names + for i in range(0, len(remaining_images), 4): + row_images = remaining_images[i:i+4] + row_names = remaining_names[i:i+4] if len(remaining_names) > i else [] + + # Create table with images and names + row_data = [] + for j, img in enumerate(row_images): + room_name = row_names[j] if j < len(row_names) else f'Room {i+j+6}' + row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + # Pad row to 4 columns if needed + while len(row_data) < 4: + row_data.append(['', '']) + + row_table = Table(row_data, colWidths=[1.5*inch, 1.5*inch, 1.5*inch, 1.5*inch]) + row_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 3), + ('RIGHTPADDING', (0, 0), (-1, -1), 3), + ])) + story.append(row_table) + story.append(Spacer(1, 10)) + + story.append(Spacer(1, 20)) + + # All Amenities + if property_data.get('amenities'): + story.append(Paragraph('Complete Amenities List', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create a comprehensive amenities table + amenities_list = property_data.get('amenities', []) + if amenities_list: + # Split amenities into two columns for better layout + mid_point = len(amenities_list) // 2 + col1_amenities = amenities_list[:mid_point] + col2_amenities = amenities_list[mid_point:] + + # Create table data + amenities_data = [] + max_rows = max(len(col1_amenities), len(col2_amenities)) + + for i in range(max_rows): + col1_item = f"โ€ข {col1_amenities[i]}" if i < len(col1_amenities) else "" + col2_item = f"โ€ข {col2_amenities[i]}" if i < len(col2_amenities) else "" + amenities_data.append([col1_item, col2_item]) + + amenities_table = Table(amenities_data, colWidths=[3*inch, 3*inch]) + amenities_table.setStyle(TableStyle([ + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 11), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ('TOPPADDING', (0, 0), (-1, -1), 5), + ('BOTTOMPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(amenities_table) + story.append(Spacer(1, 25)) + + # Additional Content + if property_data.get('additionalContent'): + story.append(Paragraph('Additional Information', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Layout information if custom template + if property_data.get('layout'): + story.append(Paragraph('Layout Configuration', self.styles['SectionHeader'])) + story.append(Paragraph(f"Selected Layout: {property_data.get('layout', 'Default').title()}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Customization details + customization_info = [] + if property_data.get('headerStyle'): + customization_info.append(f"Header Style: {property_data.get('headerStyle', 'Modern').title()}") + if property_data.get('colorScheme'): + customization_info.append(f"Color Scheme: {property_data.get('colorScheme', 'Blue').title()}") + if property_data.get('fontStyle'): + customization_info.append(f"Font Style: {property_data.get('fontStyle', 'Sans-Serif').title()}") + + if customization_info: + story.append(Paragraph('Document Customization', self.styles['SectionHeader'])) + for info in customization_info: + story.append(Paragraph(f"โ€ข {info}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # PAGE BREAK TO START PAGE 5 + story.append(PageBreak()) + + # PAGE 5: Final Images & Contact Information + story.append(Paragraph('Final Property Views & Contact', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # Add remaining images if available (3x3 grid for page 5 with room names) + if processed_images and len(processed_images) > 11: + story.append(Paragraph('Final Property Gallery', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create 3x3 grid for remaining images with room names + remaining_images = processed_images[11:20] # Take next 9 images + remaining_names = image_names[11:20] if len(image_names) > 11 else [] + + if remaining_images: + # Process images in rows of 3 with room names + for i in range(0, len(remaining_images), 3): + row_images = remaining_images[i:i+3] + row_names = remaining_names[i:i+3] if len(remaining_names) > i else [] + + # Create table with images and names + row_data = [] + for j, img in enumerate(row_images): + room_name = row_names[j] if j < len(row_names) else f'Room {i+j+12}' + row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + # Pad row to 3 columns if needed + while len(row_data) < 3: + row_data.append(['', '']) + + row_table = Table(row_data, colWidths=[2*inch, 2*inch, 2*inch]) + row_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 3), + ('RIGHTPADDING', (0, 0), (-1, -1), 3), + ])) + story.append(row_table) + story.append(Spacer(1, 10)) + + story.append(Spacer(1, 20)) + + # Investment Summary + story.append(Paragraph('Investment Summary', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + investment_summary = f"This {property_data.get('propertyType', 'property')} in {property_data.get('location', 'prime location')} represents an excellent investment opportunity. " + if property_data.get('roiPotential'): + investment_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, " + if property_data.get('rentalYield'): + investment_summary += f"and a rental yield of {property_data.get('rentalYield')}%, " + investment_summary += "this property offers strong potential for both capital appreciation and rental income." + + story.append(Paragraph(investment_summary, self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Contact Information / Footer + story.append(Paragraph('Contact Information', self.styles['SectionHeader'])) + story.append(Paragraph("For more information about this property or to schedule a viewing, please contact our property specialists.", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + story.append(Paragraph("๐Ÿ“ง Email: info@luxuryrealestate.com", self.styles['PremiumContentText'])) + story.append(Paragraph("๐Ÿ“ž Phone: +971 4 XXX XXXX", self.styles['PremiumContentText'])) + story.append(Paragraph("๐ŸŒ Website: www.luxuryrealestate.com", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + story.append(Paragraph("Thank you for considering this exceptional property investment opportunity.", self.styles['PremiumContentText'])) + + return story + + def _build_luxury_villa_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a luxury villa brochure with detailed information and images""" + story = [] + + # Cover/Header Section + story.append(Paragraph(f"{property_data.get('propertyName', 'Luxury Villa')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Luxury Villa')} โ€ข {property_data.get('location', 'Prime Location')}", self.styles['DubaiCollectionTitle'])) + story.append(Spacer(1, 20)) + + # Price Section + price_color = "#c53030" + story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay'])) + story.append(Spacer(1, 15)) + + # Add main property image if available + if processed_images and len(processed_images) > 0: + story.append(processed_images[0]) + story.append(Spacer(1, 20)) + + # Property Details Table + details_data = [ + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + # Add market data if available + if property_data.get('marketTrend'): + details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + if property_data.get('avgPricePerSqft'): + details_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"]) + if property_data.get('marketDemand'): + details_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()]) + + details_table = Table(details_data, colWidths=[3*inch, 3*inch]) + details_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 8), + ('BOTTOMPADDING', (0, 0), (-1, -1), 8), + ])) + story.append(details_table) + story.append(Spacer(1, 20)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Investment highlights + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Location Advantages + if property_data.get('locationAdvantages'): + story.append(Paragraph('Location Advantages', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Content Modules + if property_data.get('contentModules'): + story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader'])) + for module in property_data.get('contentModules', []): + module_name = module.replace('-', ' ').title() + story.append(Paragraph(f"โœ“ {module_name}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Property Images with Room Names (2x3 grid) + if processed_images and len(processed_images) > 1: + story.append(Paragraph('Property Gallery', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + image_names = property_data.get('imageNames', []) + + # Create 2x3 grid for images with room names + for i in range(1, min(len(processed_images), 7), 2): # Skip first image, take next 6 + row_images = processed_images[i:i+2] + row_names = image_names[i:i+2] if len(image_names) > i else [] + + # Create table with images and names + row_data = [] + for j, img in enumerate(row_images): + room_name = row_names[j] if j < len(row_names) else f'Room {i+j}' + row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + # Pad row to 2 columns if needed + while len(row_data) < 2: + row_data.append(['', '']) + + row_table = Table(row_data, colWidths=[3*inch, 3*inch]) + row_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row_table) + story.append(Spacer(1, 10)) + + story.append(Spacer(1, 15)) + + # All Amenities + if property_data.get('amenities'): + story.append(Paragraph('Premium Amenities', self.styles['SectionHeader'])) + # Show all amenities in a compact format + amenities_text = ' โ€ข '.join(property_data.get('amenities', [])) + story.append(Paragraph(amenities_text, self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Additional Content + if property_data.get('additionalContent'): + story.append(Paragraph('Additional Information', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Layout and Customization info + customization_info = [] + if property_data.get('layout'): + customization_info.append(f"Layout: {property_data.get('layout', 'Default').title()}") + if property_data.get('headerStyle'): + customization_info.append(f"Header: {property_data.get('headerStyle', 'Modern').title()}") + if property_data.get('colorScheme'): + customization_info.append(f"Colors: {property_data.get('colorScheme', 'Blue').title()}") + if property_data.get('fontStyle'): + customization_info.append(f"Font: {property_data.get('fontStyle', 'Sans-Serif').title()}") + + if customization_info: + story.append(Paragraph('Document Customization', self.styles['SectionHeader'])) + story.append(Paragraph(' โ€ข '.join(customization_info), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Contact Information + story.append(Paragraph('Contact Information', self.styles['SectionHeader'])) + story.append(Paragraph("๐Ÿ“ง Email: info@luxuryrealestate.com โ€ข ๐Ÿ“ž Phone: +971 4 XXX XXXX โ€ข ๐ŸŒ www.luxuryrealestate.com", self.styles['PremiumContentText'])) + + return story + + def _build_professional_5pager_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a professional 5-page brochure with comprehensive property details""" + story = [] + + # PAGE 1: Cover & Executive Summary + story.append(Paragraph(f"{property_data.get('propertyName', 'Premium Property')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Luxury Property')} in {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle'])) + story.append(Spacer(1, 30)) + + # Add main property image if available + if processed_images and len(processed_images) > 0: + story.append(processed_images[0]) + story.append(Spacer(1, 20)) + + # Executive Summary + story.append(Paragraph('Executive Summary', self.styles['SectionHeader'])) + exec_summary = f"This exceptional {property_data.get('propertyType', 'property')} represents a unique investment opportunity in {property_data.get('location', 'a prime location')}. " + if property_data.get('roiPotential'): + exec_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, this property offers excellent investment potential. " + exec_summary += property_data.get('description', 'A premium property with outstanding features and amenities.') + story.append(Paragraph(exec_summary, self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Key Features Table + features_data = [ + ['Price', f"AED {property_data.get('price', 'On Request')}"], + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Location', property_data.get('location', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + features_table = Table(features_data, colWidths=[2.5*inch, 3.5*inch]) + features_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#e3f2fd')), + ('BACKGROUND', (0, 0), (0, 0), HexColor('#1976d2')), + ('TEXTCOLOR', (0, 0), (0, 0), colors.white), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 11), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#1976d2')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 10), + ('BOTTOMPADDING', (0, 0), (-1, -1), 10), + ])) + story.append(features_table) + story.append(Spacer(1, 30)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Basic Amenities on Page 1 + if property_data.get('amenities'): + story.append(Paragraph('Key Amenities', self.styles['SectionHeader'])) + # Show first 8 amenities on page 1 + key_amenities = property_data.get('amenities', [])[:8] + for amenity in key_amenities: + story.append(Paragraph(f"โ€ข {amenity}", self.styles['PremiumContentText'])) + + # PAGE BREAK TO START PAGE 2 + story.append(PageBreak()) + + # PAGE 2: Market Analysis & Investment Details + story.append(Paragraph('Market Analysis & Investment Overview', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # Add property images (2x2 grid with room names) + if processed_images and len(processed_images) > 1: + story.append(Paragraph('Property Gallery - Main Views', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create 2x2 image grid for page 2 with room names + remaining_images = processed_images[1:5] # Take next 4 images + image_names = property_data.get('imageNames', []) + + if len(remaining_images) >= 2: + # First row + row1_images = remaining_images[:2] + row1_names = image_names[1:3] if len(image_names) > 1 else ['Room 2', 'Room 3'] + + row1_data = [] + for i, img in enumerate(row1_images): + room_name = row1_names[i] if i < len(row1_names) else f'Room {i+2}' + row1_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + row1_table = Table(row1_data, colWidths=[2.5*inch, 2.5*inch]) + row1_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row1_table) + story.append(Spacer(1, 15)) + + # Second row if we have more images + if len(remaining_images) >= 4: + row2_images = remaining_images[2:4] + row2_names = image_names[3:5] if len(image_names) > 3 else ['Room 4', 'Room 5'] + + row2_data = [] + for i, img in enumerate(row2_images): + room_name = row2_names[i] if i < len(row2_names) else f'Room {i+4}' + row2_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + row2_table = Table(row2_data, colWidths=[2.5*inch, 2.5*inch]) + row2_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row2_table) + story.append(Spacer(1, 20)) + + # Market Data Section + if any([property_data.get('marketTrend'), property_data.get('roiPotential'), property_data.get('avgPricePerSqft')]): + story.append(Paragraph('Market Intelligence', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + market_data = [] + if property_data.get('marketTrend'): + market_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + market_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + if property_data.get('avgPricePerSqft'): + market_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"]) + if property_data.get('marketDemand'): + market_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()]) + + market_table = Table(market_data, colWidths=[3*inch, 3*inch]) + market_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#e8f5e8')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#388e3c')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 10), + ('BOTTOMPADDING', (0, 0), (-1, -1), 10), + ])) + story.append(market_table) + story.append(Spacer(1, 25)) + + # Investment Analysis + if any([property_data.get('investmentType'), property_data.get('rentalYield'), property_data.get('investmentHighlights')]): + story.append(Paragraph('Investment Analysis', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + if property_data.get('investmentType'): + story.append(Paragraph(f"Investment Strategy: {property_data.get('investmentType', '').replace('-', ' ').title()}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + + if property_data.get('rentalYield'): + story.append(Paragraph(f"Expected Rental Yield: {property_data.get('rentalYield')}% annually", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights:', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + + story.append(Spacer(1, 25)) + + # Location Advantages + if property_data.get('locationAdvantages'): + story.append(Paragraph('Location Advantages', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Content Modules on Page 2 + if property_data.get('contentModules'): + story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader'])) + for module in property_data.get('contentModules', []): + module_name = module.replace('-', ' ').title() + story.append(Paragraph(f"โœ“ {module_name}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # PAGE BREAK TO START PAGE 3 + story.append(PageBreak()) + + # PAGE 3: Additional Images & Detailed Features + story.append(Paragraph('Property Features & Additional Views', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # Add more property images (2x3 grid for page 3 with room names) + if processed_images and len(processed_images) > 5: + story.append(Paragraph('Additional Property Views', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create 2x3 grid for remaining images with room names + remaining_images = processed_images[5:11] # Take next 6 images + remaining_names = image_names[5:11] if len(image_names) > 5 else [] + + if remaining_images: + # Process images in rows of 2 with room names + for i in range(0, len(remaining_images), 2): + row_images = remaining_images[i:i+2] + row_names = remaining_names[i:i+2] if len(remaining_names) > i else [] + + # Create table with images and names + row_data = [] + for j, img in enumerate(row_images): + room_name = row_names[j] if j < len(row_names) else f'Room {i+j+6}' + row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + # Pad row to 2 columns if needed + while len(row_data) < 2: + row_data.append(['', '']) + + row_table = Table(row_data, colWidths=[3*inch, 3*inch]) + row_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(row_table) + story.append(Spacer(1, 10)) + + story.append(Spacer(1, 20)) + + # Detailed Property Features + story.append(Paragraph('Detailed Property Features', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Property Specifications + specs_data = [ + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Location', property_data.get('location', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ['Price', f"AED {property_data.get('price', 'On Request')}"], + ] + + specs_table = Table(specs_data, colWidths=[3*inch, 3*inch]) + specs_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#f0f4ff')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#3b82f6')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 10), + ('BOTTOMPADDING', (0, 0), (-1, -1), 10), + ])) + story.append(specs_table) + story.append(Spacer(1, 25)) + + # PAGE BREAK TO START PAGE 4 + story.append(PageBreak()) + + # PAGE 4: Complete Amenities & Additional Information + story.append(Paragraph('Complete Amenities & Features', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # All Amenities in organized format + if property_data.get('amenities'): + story.append(Paragraph('Complete Amenities List', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create a comprehensive amenities table + amenities_list = property_data.get('amenities', []) + if amenities_list: + # Split amenities into two columns for better layout + mid_point = len(amenities_list) // 2 + col1_amenities = amenities_list[:mid_point] + col2_amenities = amenities_list[mid_point:] + + # Create table data + amenities_data = [] + max_rows = max(len(col1_amenities), len(col2_amenities)) + + for i in range(max_rows): + col1_item = f"โ€ข {col1_amenities[i]}" if i < len(col1_amenities) else "" + col2_item = f"โ€ข {col2_amenities[i]}" if i < len(col2_amenities) else "" + amenities_data.append([col1_item, col2_item]) + + amenities_table = Table(amenities_data, colWidths=[3*inch, 3*inch]) + amenities_table.setStyle(TableStyle([ + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 11), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LEFTPADDING', (0, 0), (-1, -1), 5), + ('RIGHTPADDING', (0, 0), (-1, -1), 5), + ('TOPPADDING', (0, 0), (-1, -1), 5), + ('BOTTOMPADDING', (0, 0), (-1, -1), 5), + ])) + story.append(amenities_table) + story.append(Spacer(1, 25)) + + # Additional Content + if property_data.get('additionalContent'): + story.append(Paragraph('Additional Information', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Layout information if custom template + if property_data.get('layout'): + story.append(Paragraph('Layout Configuration', self.styles['SectionHeader'])) + story.append(Paragraph(f"Selected Layout: {property_data.get('layout', 'Default').title()}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Customization details + customization_info = [] + if property_data.get('headerStyle'): + customization_info.append(f"Header Style: {property_data.get('headerStyle', 'Modern').title()}") + if property_data.get('colorScheme'): + customization_info.append(f"Color Scheme: {property_data.get('colorScheme', 'Blue').title()}") + if property_data.get('fontStyle'): + customization_info.append(f"Font Style: {property_data.get('fontStyle', 'Sans-Serif').title()}") + + if customization_info: + story.append(Paragraph('Document Customization', self.styles['SectionHeader'])) + for info in customization_info: + story.append(Paragraph(f"โ€ข {info}", self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # PAGE BREAK TO START PAGE 5 + story.append(PageBreak()) + + # PAGE 5: Final Images & Contact Information + story.append(Paragraph('Final Property Views & Contact', self.styles['UltraPremiumTitle'])) + story.append(Spacer(1, 30)) + + # Add remaining images if available (3x3 grid for page 5 with room names) + if processed_images and len(processed_images) > 11: + story.append(Paragraph('Final Property Gallery', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + # Create 3x3 grid for remaining images with room names + remaining_images = processed_images[11:20] # Take next 9 images + remaining_names = image_names[11:20] if len(image_names) > 11 else [] + + if remaining_images: + # Process images in rows of 3 with room names + for i in range(0, len(remaining_images), 3): + row_images = remaining_images[i:i+3] + row_names = remaining_names[i:i+3] if len(remaining_names) > i else [] + + # Create table with images and names + row_data = [] + for j, img in enumerate(row_images): + room_name = row_names[j] if j < len(row_names) else f'Room {i+j+12}' + row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])]) + + # Pad row to 3 columns if needed + while len(row_data) < 3: + row_data.append(['', '']) + + row_table = Table(row_data, colWidths=[2*inch, 2*inch, 2*inch]) + row_table.setStyle(TableStyle([ + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('LEFTPADDING', (0, 0), (-1, -1), 3), + ('RIGHTPADDING', (0, 0), (-1, -1), 3), + ])) + story.append(row_table) + story.append(Spacer(1, 10)) + + story.append(Spacer(1, 20)) + + # Investment Summary + story.append(Paragraph('Investment Summary', self.styles['SectionHeader'])) + story.append(Spacer(1, 15)) + + investment_summary = f"This {property_data.get('propertyType', 'property')} in {property_data.get('location', 'prime location')} represents an excellent investment opportunity. " + if property_data.get('roiPotential'): + investment_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, " + if property_data.get('rentalYield'): + investment_summary += f"and a rental yield of {property_data.get('rentalYield')}%, " + investment_summary += "this property offers strong potential for both capital appreciation and rental income." + + story.append(Paragraph(investment_summary, self.styles['PremiumContentText'])) + story.append(Spacer(1, 20)) + + # Contact Information / Footer + story.append(Paragraph('Contact Information', self.styles['SectionHeader'])) + story.append(Paragraph("For more information about this property or to schedule a viewing, please contact our property specialists.", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + story.append(Paragraph("๐Ÿ“ง Email: info@luxuryrealestate.com", self.styles['PremiumContentText'])) + story.append(Paragraph("๐Ÿ“ž Phone: +971 4 XXX XXXX", self.styles['PremiumContentText'])) + story.append(Paragraph("๐ŸŒ Website: www.luxuryrealestate.com", self.styles['PremiumContentText'])) + story.append(Spacer(1, 10)) + story.append(Paragraph("Thank you for considering this exceptional property investment opportunity.", self.styles['PremiumContentText'])) + + return story + + def _build_dubai_penthouse_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a Dubai penthouse brochure with detailed information""" + story = [] + + # Cover/Header Section + story.append(Paragraph(f"{property_data.get('propertyName', 'Dubai Penthouse')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Dubai Penthouse')} โ€ข {property_data.get('location', 'Prime Location')}", self.styles['DubaiCollectionTitle'])) + story.append(Spacer(1, 20)) + + # Price Section + price_color = "#c53030" + story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay'])) + story.append(Spacer(1, 15)) + + # Property Details Table + details_data = [ + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + # Add market data if available + if property_data.get('marketTrend'): + details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + + details_table = Table(details_data, colWidths=[3*inch, 3*inch]) + details_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 8), + ('BOTTOMPADDING', (0, 0), (-1, -1), 8), + ])) + story.append(details_table) + story.append(Spacer(1, 20)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Amenities + if property_data.get('amenities'): + story.append(Paragraph('Premium Amenities', self.styles['SectionHeader'])) + amenities_text = ' โ€ข '.join(property_data.get('amenities', [])) + story.append(Paragraph(amenities_text, self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Investment highlights + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + + return story + + def _build_modern_apartment_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a modern apartment brochure with detailed information""" + story = [] + + # Cover/Header Section + story.append(Paragraph(f"{property_data.get('propertyName', 'Modern Apartment')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Modern Apartment')} โ€ข {property_data.get('location', 'Prime Location')}", self.styles['ModernCollectionTitle'])) + story.append(Spacer(1, 20)) + + # Price Section + price_color = "#4a5568" + story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay'])) + story.append(Spacer(1, 15)) + + # Property Details Table + details_data = [ + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + # Add market data if available + if property_data.get('marketTrend'): + details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + + details_table = Table(details_data, colWidths=[3*inch, 3*inch]) + details_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 8), + ('BOTTOMPADDING', (0, 0), (-1, -1), 8), + ])) + story.append(details_table) + story.append(Spacer(1, 20)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Amenities + if property_data.get('amenities'): + story.append(Paragraph('Premium Amenities', self.styles['SectionHeader'])) + amenities_text = ' โ€ข '.join(property_data.get('amenities', [])) + story.append(Paragraph(amenities_text, self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Investment highlights + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + + return story + + def _build_custom_template_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List: + """Build a custom template brochure based on property data""" + story = [] + + # Cover/Header Section + story.append(Paragraph(f"{property_data.get('propertyName', 'Luxury Property')}", self.styles['UltraPremiumTitle'])) + story.append(Paragraph(f"{property_data.get('propertyType', 'Premium Property')} โ€ข {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle'])) + story.append(Spacer(1, 20)) + + # Price Section + price_color = "#2c5530" + story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay'])) + story.append(Spacer(1, 15)) + + # Property Details Table + details_data = [ + ['Property Type', property_data.get('propertyType', 'N/A')], + ['Bedrooms', property_data.get('bedrooms', 'N/A')], + ['Bathrooms', property_data.get('bathrooms', 'N/A')], + ['Area', f"{property_data.get('area', 'N/A')} sq ft"], + ] + + # Add market data if available + if property_data.get('marketTrend'): + details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()]) + if property_data.get('roiPotential'): + details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"]) + + details_table = Table(details_data, colWidths=[3*inch, 3*inch]) + details_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')), + ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), + ('FONTSIZE', (0, 0), (-1, -1), 12), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('LEFTPADDING', (0, 0), (-1, -1), 12), + ('RIGHTPADDING', (0, 0), (-1, -1), 12), + ('TOPPADDING', (0, 0), (-1, -1), 8), + ('BOTTOMPADDING', (0, 0), (-1, -1), 8), + ])) + story.append(details_table) + story.append(Spacer(1, 20)) + + # Description + if property_data.get('description'): + story.append(Paragraph('Property Description', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Amenities + if property_data.get('amenities'): + story.append(Paragraph('Premium Amenities', self.styles['SectionHeader'])) + amenities_text = ' โ€ข '.join(property_data.get('amenities', [])) + story.append(Paragraph(amenities_text, self.styles['PremiumContentText'])) + story.append(Spacer(1, 15)) + + # Investment highlights + if property_data.get('investmentHighlights'): + story.append(Paragraph('Investment Highlights', self.styles['SectionHeader'])) + story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText'])) + + return story + + +def main(): + """Test the PDF generator with sample data""" + generator = PropertyPDFGenerator() + + # Sample property data + sample_data = { + 'propertyName': 'Luxury Marina Villa', + 'propertyType': 'Villa', + 'location': 'Dubai Marina', + 'price': '5,500,000', + 'bedrooms': '5', + 'bathrooms': '6', + 'area': '4,200', + 'description': 'Stunning luxury villa with panoramic marina views, premium finishes, and exclusive amenities. This exceptional property offers the finest in luxury living.', + 'amenities': ['Private Pool', 'Gym', 'Security', 'Garden', 'Garage', 'Smart Home', 'Concierge', 'Spa'], + 'images': [], + 'imageNames': [] + } + + # Generate luxury villa PDF + output_file = 'luxury_property_brochure.pdf' + try: + result = generator.generate_property_pdf(sample_data, 'luxury-villa', output_file) + print(f"Luxury villa PDF generated: {result}") + except Exception as e: + print(f"Error: {str(e)}") + + # Generate modern apartment PDF + output_file = 'modern_property_brochure.pdf' + try: + result = generator.generate_property_pdf(sample_data, 'modern-apartment', output_file) + print(f"Modern apartment PDF generated: {result}") + except Exception as e: + print(f"Error: {str(e)}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/python-pdf-generator/requirements.txt b/python-pdf-generator/requirements.txt new file mode 100644 index 0000000..f5580f3 --- /dev/null +++ b/python-pdf-generator/requirements.txt @@ -0,0 +1,18 @@ +# Core PDF generation +reportlab>=4.0.0 +Pillow>=10.0.0 + +# HTTP and data handling +requests>=2.31.0 +python-multipart>=0.0.6 + +# Data validation and serialization +pydantic>=2.0.0 + +# Web framework +fastapi>=0.100.0 +uvicorn>=0.20.0 + +# Testing +pytest>=7.0.0 +pytest-cov>=4.0.0 \ No newline at end of file diff --git a/sfdx-project.json b/sfdx-project.json new file mode 100644 index 0000000..83ab7fb --- /dev/null +++ b/sfdx-project.json @@ -0,0 +1,11 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "namespace": "", + "sfdcLoginUrl": "https://test.salesforce.com", + "sourceApiVersion": "64.0" +} \ No newline at end of file diff --git a/test-environment/index.html b/test-environment/index.html new file mode 100644 index 0000000..0d50d22 --- /dev/null +++ b/test-environment/index.html @@ -0,0 +1,2876 @@ + + + + + + Professional Property Brochure Generator - Advanced Template System + + + +
+
+

Professional Property Brochure Generator

+

Advanced Template System with Multi-Page Layouts & Market Analytics

+
+ Market Analytics + Professional Templates + Multi-Format Support +
+
+ + +
+
+
1
+
Template & Layout
+
+
+
2
+
Property & Market Data
+
+
+
3
+
Content & Analytics
+
+
+
4
+
Preview & Customize
+
+
+
5
+
Generate & Export
+
+
+ + +
+
+
+ +
+
+
+
CUSTOM
+
Flexible Layout
+
โš™
+
+
+
+

Custom Template

+

Build your own template with custom layouts, image grids, and content structure.

+
+ Flexible Size + Custom Grid + Personal Design +
+
+
+ + +
+
+
+
1 PAGE
+
2x2 Grid Layout
+
๐Ÿ“„
+
+
+
+

Professional 1-Pager

+

Compact single-page brochure with 2x2 image grid. Perfect for quick property overviews and executive summaries.

+
+ ๐Ÿ“ A4 Portrait + 4 Images + Market Data +
+
+
+ + +
+
+
+
3 PAGES
+
4x4 Grid Layout
+
๐Ÿ“š
+
+
+
+

Professional 3-Pager

+

Comprehensive three-page brochure with detailed property analysis, market insights, and comprehensive property showcase.

+
+ ๐Ÿ“ A4 Portrait + 16 Images + ROI Analysis +
+
+
+ + +
+
+
+
5 PAGES
+
6x6 Grid Layout
+
๐Ÿ“–
+
+
+
+

Professional 5-Pager

+

Premium five-page brochure with comprehensive market analysis, investment strategies, and detailed property showcase.

+
+ ๐Ÿ“ A4 Portrait + 36 Images + Investment Guide +
+
+
+ + +
+
+
+
4 PAGES
+
Premium Layout
+
๐Ÿฐ
+
+
+
+

Luxury Villa Brochure

+

Exclusive villa template with premium styling, sophisticated layouts, and luxury branding elements.

+
+ ๐Ÿ“ A4 Portrait + 20 Images + Premium Design +
+
+
+ + +
+
+
+
6 PAGES
+
Dubai Style
+
๐ŸŒ†
+
+
+
+

Dubai Penthouse

+

Dubai-specific luxury penthouse template with iconic branding and local market insights.

+
+ ๐Ÿ“ A4 Portrait + 30 Images + Dubai Market +
+
+
+ + +
+
+
+
3 PAGES
+
Contemporary
+
๐Ÿข
+
+
+
+

Modern Apartment

+

Contemporary apartment template with clean lines, modern aesthetics, and urban lifestyle focus.

+
+ ๐Ÿ“ A4 Portrait + 15 Images + Urban Style +
+
+
+
+
+ + + + +
+ + +
+
+

Property Information

+ +
+

Property Selection

+
+ + + + + + +
+ + + +
+ +

Basic Information

+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+

Property Specifications

+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +
+

Description

+
+ + +
+
+ +
+

Amenities

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Property Images with Room Names

+

Upload images for different rooms and areas. Each image will be tagged with its room name for professional presentation.

+ +
+
+
+ + + +
+
+
+
+ + +

Recommended: Upload 8-16 high-quality images for best results. Supported formats: JPG, PNG, WebP

+
+ +
+

Market Data & Analytics

+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +
+

Investment Information

+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ + +
+
+

Content & Analytics Configuration

+ +
+

Content Modules

+
+
+ + +

Include comprehensive market analysis and trends

+
+
+ + +

Add investment strategies and ROI analysis

+
+
+ + +

Include Dubai-specific market information

+
+
+ + +

Add detailed location benefits and amenities

+
+
+
+ +
+

Investment Articles

+
+
+ + +

Comprehensive guide to Dubai real estate investment

+
+
+ + +

Current market trends and future predictions

+
+
+ + +

Tips for maximizing return on investment

+
+
+
+ +
+

Custom Content

+
+ + +
+
+ + +
+
+ + +
+
+
+

Preview & Customize

+

Review and customize your property brochure before final generation

+
+ +
+
+ +
+
+ +
+

Customization Options

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+

PDF Generation & Export

+

Your high-quality property brochure is being generated

+
+ +
+
+

Generating your PDF...

+

This may take a few moments. Please wait.

+
+ + + + +
+
+
+ + + + \ No newline at end of file diff --git a/test-environment/test-property-api.html b/test-environment/test-property-api.html new file mode 100644 index 0000000..68aab4c --- /dev/null +++ b/test-environment/test-property-api.html @@ -0,0 +1,626 @@ + + + + + + Property Brochure Generator - API Test + + + +
+
+

Property Brochure Generator

+

Test API Integration - Property Selection & Data Population

+
+ +
+

๐Ÿ”Œ API Information

+
+

Test API Endpoints

+
GET /api/properties - Get all properties list
+
GET /api/properties/{name} - Get property by name (e.g., PR-00036)
+
POST /api/generate-pdf - Generate PDF brochure
+ +
+ API Status: + Checking... +
+ +
+ Base URL: + http://localhost:8001 +
+
+
+ +
+

Property Selection Test

+
+
+ + +
+ +
+ + + +
+ + + + + +
+
+ + +
+ + + + \ No newline at end of file