PDF_Generation_and_Automation/test-environment/index.html
2025-08-23 22:55:07 +05:30

2876 lines
117 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Professional Property Brochure Generator - Advanced Template System</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--custom-accent: #007bff;
--accent-color: #007bff;
--header-style: modern;
--font-family: Arial, sans-serif;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #f8f9fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
padding: 30px 0;
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 10px;
}
.header p {
font-size: 1.1rem;
color: #6c757d;
max-width: 600px;
margin: 0 auto 20px;
}
.header-features {
display: flex;
justify-content: center;
gap: 15px;
flex-wrap: wrap;
}
.feature-badge {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
}
/* Step Navigation */
.step-navigation {
display: flex;
justify-content: center;
margin-bottom: 40px;
background: white;
padding: 20px;
border-radius: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.step-item {
display: flex;
align-items: center;
margin: 0 20px;
position: relative;
}
.step-number {
width: 40px;
height: 40px;
border-radius: 50%;
background: #e9ecef;
color: #6c757d;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 15px;
transition: all 0.3s ease;
}
.step-item.active .step-number {
background: #007bff;
color: white;
}
.step-item.completed .step-number {
background: #28a745;
color: white;
}
.step-text {
font-weight: 500;
color: #6c757d;
}
.step-item.active .step-text {
color: #007bff;
font-weight: 600;
}
.step-item.completed .step-text {
color: #28a745;
}
/* Step Content */
.step-content {
display: none;
animation: fadeIn 0.5s ease-in;
}
.step-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Step 1: Template Selection */
.template-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 30px;
margin-top: 30px;
}
.template-card {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.08);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
border: 3px solid transparent;
position: relative;
}
.template-card:hover {
transform: translateY(-12px) scale(1.02);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 8px 24px rgba(0, 0, 0, 0.15);
}
.template-card.selected {
border-color: #28a745;
box-shadow: 0 20px 60px rgba(40, 167, 69, 0.3), 0 8px 24px rgba(40, 167, 69, 0.2);
}
.template-preview {
height: 280px;
overflow: hidden;
position: relative;
}
.template-preview img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.template-card:hover .template-preview img {
transform: scale(1.08);
}
.template-info {
padding: 25px;
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.template-info h3 {
font-size: 1.4rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 12px;
}
.template-info p {
color: #6c757d;
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 15px;
}
.template-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: auto;
}
.tag {
background: #e3f2fd;
color: #1976d2;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.template-selection-container {
margin-bottom: 30px;
}
.template-categories {
text-align: center;
margin-bottom: 40px;
}
.template-categories h3 {
font-size: 1.5rem;
color: #2c3e50;
margin-bottom: 20px;
}
/* Template Specifications */
.template-specs {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 15px;
}
.spec {
background: #f8f9fa;
color: #495057;
padding: 6px 12px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: 500;
}
/* Page Count and Layout Info */
.page-count {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 8px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.layout-info {
font-size: 0.9rem;
opacity: 0.9;
margin-bottom: 15px;
}
.template-icon {
font-size: 3rem;
margin-bottom: 15px;
}
/* Layout Options */
.layout-options {
background: white;
padding: 30px;
border-radius: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-top: 30px;
}
.layout-options h3 {
text-align: center;
margin-bottom: 25px;
color: #2c3e50;
}
.layout-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
}
.layout-option {
text-align: center;
padding: 20px;
border: 2px solid #e9ecef;
border-radius: 15px;
cursor: pointer;
transition: all 0.3s ease;
}
.layout-option:hover {
border-color: #007bff;
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 123, 255, 0.2);
}
.layout-preview {
font-size: 2rem;
font-weight: 700;
color: #007bff;
margin-bottom: 10px;
}
.layout-option span {
font-size: 0.9rem;
color: #6c757d;
}
/* Template-specific styles with better colors */
.professional-1pager .template-preview {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.professional-3pager .template-preview {
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
}
.professional-5pager .template-preview {
background: linear-gradient(135deg, #059669 0%, #10b981 100%);
}
.luxury-villa .template-preview {
background: linear-gradient(135deg, #2c1810 0%, #8b4513 100%);
}
.dubai-penthouse .template-preview {
background: linear-gradient(135deg, #dc2626 0%, #f87171 100%);
}
.modern-apartment .template-preview {
background: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%);
}
.custom-template .template-preview {
background: linear-gradient(135deg, #1f2937 0%, #4b5563 100%);
}
/* Customization Options */
.customization-options {
background: white;
padding: 30px;
border-radius: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-top: 30px;
}
.customization-options h3 {
text-align: center;
margin-bottom: 25px;
color: #2c3e50;
}
.customization-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.customization-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.customization-item label {
font-weight: 600;
color: #2c3e50;
}
.customization-item select {
padding: 10px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 0.9rem;
}
/* Content Modules */
.content-modules {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.module-item {
background: #f8f9fa;
padding: 20px;
border-radius: 15px;
border: 2px solid #e9ecef;
transition: all 0.3s ease;
}
.module-item:hover {
border-color: #007bff;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.1);
}
.module-item input[type="checkbox"] {
width: auto;
margin-right: 10px;
}
.module-item label {
font-weight: 600;
color: #2c3e50;
font-size: 1.1rem;
margin-bottom: 8px;
display: block;
}
.module-item p {
color: #6c757d;
font-size: 0.9rem;
margin-top: 8px;
}
/* Investment Articles */
.investment-articles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.article-item {
background: #f8f9fa;
padding: 20px;
border-radius: 15px;
border: 2px solid #e9ecef;
transition: all 0.3s ease;
}
.article-item:hover {
border-color: #28a745;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.1);
}
.article-item input[type="checkbox"] {
width: auto;
margin-right: 10px;
}
.article-item label {
font-weight: 600;
color: #2c3e50;
font-size: 1.1rem;
margin-bottom: 8px;
display: block;
}
.article-item p {
color: #6c757d;
font-size: 0.9rem;
margin-top: 8px;
}
.blank-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
opacity: 0.3;
}
.modern-template .template-preview {
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
position: relative;
overflow: hidden;
}
.modern-template .template-preview::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 20s linear infinite;
}
.luxury-template .template-preview {
background: linear-gradient(135deg, #2c1810 0%, #8b4513 100%);
position: relative;
overflow: hidden;
}
.luxury-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="luxury" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23luxury)"/></svg>');
opacity: 0.4;
}
.premium-template .template-preview {
background: linear-gradient(135deg, #1a1a1a 0%, #4a4a4a 100%);
position: relative;
overflow: hidden;
}
.premium-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="premium" width="15" height="15" patternUnits="userSpaceOnUse"><path d="M 0 7.5 L 7.5 0 L 15 7.5 L 7.5 15 Z" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23premium)"/></svg>');
opacity: 0.3;
}
.dubai-template .template-preview {
background: linear-gradient(135deg, #dc2626 0%, #f87171 100%);
position: relative;
overflow: hidden;
}
.dubai-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="dubai" width="25" height="25" patternUnits="userSpaceOnUse"><path d="M 0 12.5 L 12.5 0 L 25 12.5 L 12.5 25 Z" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="0.8"/></pattern></defs><rect width="100" height="100" fill="url(%23dubai)"/></svg>');
opacity: 0.4;
}
.villa-template .template-preview {
background: linear-gradient(135deg, #2c3e50 0%, #5a6c7d 100%);
position: relative;
overflow: hidden;
}
.villa-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="villa" width="30" height="30" patternUnits="userSpaceOnUse"><circle cx="15" cy="15" r="8" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23villa)"/></svg>');
opacity: 0.3;
}
.penthouse-template .template-preview {
background: linear-gradient(135deg, #fcb69f 0%, #ffecd2 100%);
position: relative;
overflow: hidden;
}
.penthouse-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="penthouse" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 0 10 L 10 0 L 20 10 L 10 20 Z" fill="none" stroke="rgba(139,69,19,0.2)" stroke-width="0.6"/></pattern></defs><rect width="100" height="100" fill="url(%23penthouse)"/></svg>');
opacity: 0.4;
}
.office-template .template-preview {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
position: relative;
overflow: hidden;
}
.office-template .template-preview::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="office" width="18" height="18" patternUnits="userSpaceOnUse"><rect x="1" y="1" width="16" height="16" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23office)"/></svg>');
opacity: 0.3;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Enhanced template content */
.template-preview .preview-content {
position: relative;
z-index: 2;
text-align: center;
padding: 40px 20px;
color: white;
}
.template-preview .preview-content h4 {
font-size: 1.8rem;
font-weight: 700;
margin-bottom: 15px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.template-preview .preview-content .icon {
width: 80px;
height: 80px;
background: rgba(255,255,255,0.2);
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
backdrop-filter: blur(10px);
border: 2px solid rgba(255,255,255,0.3);
}
/* Template info enhancements */
.template-info h3 {
font-size: 1.5rem;
font-weight: 700;
color: #1f2937;
margin-bottom: 15px;
position: relative;
}
.template-info h3::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 40px;
height: 3px;
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
border-radius: 2px;
}
.template-info p {
color: #6b7280;
font-size: 1rem;
line-height: 1.7;
margin-bottom: 20px;
}
/* Enhanced tags */
.template-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: auto;
}
.tag {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
color: white;
padding: 8px 16px;
border-radius: 25px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
transition: all 0.3s ease;
}
.tag:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
/* Selection feedback enhancement */
.template-card.selected {
border-color: #10b981;
box-shadow: 0 25px 80px rgba(16, 185, 129, 0.4), 0 10px 30px rgba(16, 185, 129, 0.3);
transform: translateY(-15px) scale(1.03);
}
.template-card.selected::before {
content: '✓ SELECTED';
position: absolute;
top: 20px;
right: 20px;
background: #10b981;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
z-index: 10;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* Step 2: Property Data Form */
.form-container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
}
.form-section {
margin-bottom: 30px;
}
.form-section h3 {
font-size: 1.3rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #e9ecef;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 600;
color: #2c3e50;
margin-bottom: 8px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid #e9ecef;
border-radius: 10px;
font-size: 1rem;
transition: all 0.3s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.amenities-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.amenity-item {
display: flex;
align-items: center;
padding: 10px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.amenity-item input[type="checkbox"] {
width: auto;
margin-right: 10px;
}
.image-upload {
border: 2px dashed #dee2e6;
border-radius: 10px;
padding: 30px;
text-align: center;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s ease;
}
.image-upload:hover {
border-color: #007bff;
background: #e3f2fd;
}
.image-upload input[type="file"] {
display: none;
}
/* Step 3: Preview */
.preview-container {
background: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
}
.preview-header {
text-align: center;
margin-bottom: 30px;
}
.preview-content {
border: 2px solid #e9ecef;
border-radius: 15px;
padding: 30px;
background: #f8f9fa;
min-height: 600px;
transition: all 0.3s ease;
}
.preview-content h1, .preview-content h2, .preview-content h3 {
transition: all 0.3s ease;
}
.color-accent {
color: var(--accent-color);
transition: color 0.3s ease;
}
.pdf-preview {
width: 100%;
height: 600px;
border: none;
border-radius: 10px;
background: white;
}
/* Navigation Buttons */
.navigation-buttons {
display: flex;
justify-content: space-between;
margin-top: 40px;
padding-top: 30px;
border-top: 1px solid #e9ecef;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 25px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108, 117, 125, 0.3);
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #1e7e34;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* Responsive Design */
@media (max-width: 768px) {
.step-navigation {
flex-direction: column;
gap: 20px;
}
.step-item:not(:last-child)::after {
display: none;
}
.form-row {
grid-template-columns: 1fr;
}
.template-grid {
grid-template-columns: 1fr;
}
.navigation-buttons {
flex-direction: column;
gap: 15px;
}
}
/* Loading Spinner */
.loading-spinner {
display: none;
text-align: center;
padding: 40px;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Image Upload Styles */
.image-upload-item {
background: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
transition: all 0.3s ease;
}
.image-upload-item:hover {
border-color: #007bff;
background: #f0f8ff;
}
.image-input-group {
display: flex;
gap: 15px;
align-items: center;
margin-bottom: 15px;
}
.room-name {
flex: 2;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
.image-file {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
.remove-image-btn {
padding: 8px 16px;
background: #dc3545;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s ease;
}
.remove-image-btn:hover {
background: #c82333;
}
.add-image-btn {
background: #28a745;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
margin: 20px 0;
transition: background 0.3s ease;
}
.add-image-btn:hover {
background: #218838;
}
.image-preview {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.image-preview img {
width: 120px;
height: 90px;
object-fit: cover;
border-radius: 8px;
border: 2px solid #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.section-description {
color: #6c757d;
font-size: 14px;
margin-bottom: 20px;
line-height: 1.5;
}
.help-text {
color: #6c757d;
font-size: 13px;
font-style: italic;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Professional Property Brochure Generator</h1>
<p>Advanced Template System with Multi-Page Layouts & Market Analytics</p>
<div class="header-features">
<span class="feature-badge">Market Analytics</span>
<span class="feature-badge">Professional Templates</span>
<span class="feature-badge">Multi-Format Support</span>
</div>
</div>
<!-- Step Navigation -->
<div class="step-navigation">
<div class="step-item active" id="step1-nav">
<div class="step-number">1</div>
<div class="step-text">Template & Layout</div>
</div>
<div class="step-item" id="step2-nav">
<div class="step-number">2</div>
<div class="step-text">Property & Market Data</div>
</div>
<div class="step-item" id="step3-nav">
<div class="step-number">3</div>
<div class="step-text">Content & Analytics</div>
</div>
<div class="step-item" id="step4-nav">
<div class="step-number">4</div>
<div class="step-text">Preview & Customize</div>
</div>
<div class="step-item" id="step5-nav">
<div class="step-number">5</div>
<div class="step-text">Generate & Export</div>
</div>
</div>
<!-- Step 1: Template & Layout Selection -->
<div id="step1" class="step-content active">
<div class="template-selection-container">
<div class="template-grid" id="all-templates">
<!-- Custom Template -->
<div class="template-card custom-template" onclick="selectTemplate('custom')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">CUSTOM</div>
<div class="layout-info">Flexible Layout</div>
<div class="template-icon"></div>
</div>
</div>
<div class="template-info">
<h3>Custom Template</h3>
<p>Build your own template with custom layouts, image grids, and content structure.</p>
<div class="template-specs">
<span class="spec">Flexible Size</span>
<span class="spec">Custom Grid</span>
<span class="spec">Personal Design</span>
</div>
</div>
</div>
<!-- Professional 1-Pager -->
<div class="template-card professional-1pager" onclick="selectTemplate('professional-1pager')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">1 PAGE</div>
<div class="layout-info">2x2 Grid Layout</div>
<div class="template-icon">📄</div>
</div>
</div>
<div class="template-info">
<h3>Professional 1-Pager</h3>
<p>Compact single-page brochure with 2x2 image grid. Perfect for quick property overviews and executive summaries.</p>
<div class="template-specs">
<span class="spec">📏 A4 Portrait</span>
<span class="spec">4 Images</span>
<span class="spec">Market Data</span>
</div>
</div>
</div>
<!-- Professional 3-Pager -->
<div class="template-card professional-3pager" onclick="selectTemplate('professional-3pager')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">3 PAGES</div>
<div class="layout-info">4x4 Grid Layout</div>
<div class="template-icon">📚</div>
</div>
</div>
<div class="template-info">
<h3>Professional 3-Pager</h3>
<p>Comprehensive three-page brochure with detailed property analysis, market insights, and comprehensive property showcase.</p>
<div class="template-specs">
<span class="spec">📏 A4 Portrait</span>
<span class="spec">16 Images</span>
<span class="spec">ROI Analysis</span>
</div>
</div>
</div>
<!-- Professional 5-Pager -->
<div class="template-card professional-5pager" onclick="selectTemplate('professional-5pager')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">5 PAGES</div>
<div class="layout-info">6x6 Grid Layout</div>
<div class="template-icon">📖</div>
</div>
</div>
<div class="template-info">
<h3>Professional 5-Pager</h3>
<p>Premium five-page brochure with comprehensive market analysis, investment strategies, and detailed property showcase.</p>
<div class="template-specs">
<span class="spec">📏 A4 Portrait</span>
<span class="spec">36 Images</span>
<span class="spec">Investment Guide</span>
</div>
</div>
</div>
<!-- Luxury Villa Brochure -->
<div class="template-card luxury-villa" onclick="selectTemplate('luxury-villa')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">4 PAGES</div>
<div class="layout-info">Premium Layout</div>
<div class="template-icon">🏰</div>
</div>
</div>
<div class="template-info">
<h3>Luxury Villa Brochure</h3>
<p>Exclusive villa template with premium styling, sophisticated layouts, and luxury branding elements.</p>
<div class="template-specs">
<span class="spec">📏 A4 Portrait</span>
<span class="spec">20 Images</span>
<span class="spec">Premium Design</span>
</div>
</div>
</div>
<!-- Dubai Penthouse -->
<div class="template-card dubai-penthouse" onclick="selectTemplate('dubai-penthouse')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">6 PAGES</div>
<div class="layout-info">Dubai Style</div>
<div class="template-icon">🌆</div>
</div>
</div>
<div class="template-info">
<h3>Dubai Penthouse</h3>
<p>Dubai-specific luxury penthouse template with iconic branding and local market insights.</p>
<div class="template-specs">
<span class="spec">📏 A4 Portrait</span>
<span class="spec">30 Images</span>
<span class="spec">Dubai Market</span>
</div>
</div>
</div>
<!-- Modern Apartment -->
<div class="template-card modern-apartment" onclick="selectTemplate('modern-apartment')">
<div class="template-preview">
<div class="preview-content">
<div class="page-count">3 PAGES</div>
<div class="layout-info">Contemporary</div>
<div class="template-icon">🏢</div>
</div>
</div>
<div class="template-info">
<h3>Modern Apartment</h3>
<p>Contemporary apartment template with clean lines, modern aesthetics, and urban lifestyle focus.</p>
<div class="template-specs">
<span class="spec">📏 A4 Portrait</span>
<span class="spec">15 Images</span>
<span class="spec">Urban Style</span>
</div>
</div>
</div>
</div>
</div>
<div class="layout-options" id="layoutOptions" style="display: none;">
<h3>Layout Configuration</h3>
<div class="layout-grid">
<div class="layout-option" onclick="selectLayout('2x2')">
<div class="layout-preview">2×2</div>
<span>2x2 Grid (4 Images)</span>
</div>
<div class="layout-option" onclick="selectLayout('3x3')">
<div class="layout-preview">3×3</div>
<span>3x3 Grid (9 Images)</span>
</div>
<div class="layout-option" onclick="selectLayout('4x4')">
<div class="layout-preview">4×4</div>
<span>4x4 Grid (16 Images)</span>
</div>
<div class="layout-option" onclick="selectLayout('6x6')">
<div class="layout-preview">6×6</div>
<span>6x6 Grid (36 Images)</span>
</div>
</div>
</div>
<div class="navigation-buttons">
<div></div>
<button class="btn btn-primary" onclick="nextStep()" id="step1-next" disabled>Next: Property & Market Data</button>
</div>
</div>
<!-- Step 2: Property Details Form -->
<div id="step2" class="step-content">
<div class="form-container">
<h2 style="text-align: center; margin-bottom: 30px; color: #2c3e50;">Property Information</h2>
<div class="form-section">
<h3>Property Selection</h3>
<div class="form-group" style="margin-bottom: 20px;">
<label>Select Property from Database</label>
<select id="propertySelect" onchange="handlePropertySelection()" style="background: #e3f2fd; border: 2px solid #007bff;">
<option value="">Choose a property from your Salesforce data...</option>
</select>
<div id="propertyLoading" style="display: none; text-align: center; padding: 10px; color: #007bff;">
<i>Loading properties...</i>
</div>
<div id="propertyError" style="display: none; color: #dc3545; padding: 10px; background: #f8d7da; border-radius: 5px; margin-top: 10px;"></div>
<!-- Property Statistics Display -->
<div id="propertyStats"></div>
<!-- Salesforce Credentials Form -->
<div id="salesforceCredentialsForm" style="display: none; background: #f8f9fa; padding: 20px; border-radius: 10px; margin: 20px 0; border: 2px solid #007bff;">
<h4 style="margin: 0 0 15px 0; color: #007bff;">Salesforce Credentials Required</h4>
<p style="margin: 0 0 15px 0; color: #666;">Enter your Salesforce sandbox credentials to fetch real-time property data:</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;">
<div>
<label style="display: block; margin-bottom: 5px; font-weight: 600;">Client ID:</label>
<input type="text" id="sfClientId" placeholder="Connected App Consumer Key" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: 600;">Client Secret:</label>
<input type="password" id="sfClientSecret" placeholder="Connected App Consumer Secret" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: 600;">Username:</label>
<input type="email" id="sfUsername" placeholder="Your sandbox email" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: 600;">Password:</label>
<input type="password" id="sfPassword" placeholder="Your sandbox password" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: 600;">Security Token:</label>
<input type="text" id="sfSecurityToken" placeholder="Your security token" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
</div>
</div>
<div style="margin-top: 20px;">
<button onclick="saveSalesforceCredentials()" style="background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-weight: 600;">
Save Credentials & Connect
</button>
<button onclick="hideCredentialForm()" style="background: #6c757d; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-weight: 600; margin-left: 10px;">
Cancel
</button>
</div>
<div style="margin-top: 15px; padding: 10px; background: #e3f2fd; border-radius: 5px; font-size: 0.9em; color: #1976d2;">
<strong>Need help?</strong> Check the <code>REAL-TIME-SETUP.md</code> file for detailed setup instructions.
</div>
</div>
</div>
<h3>Basic Information</h3>
<div class="form-row">
<div class="form-group">
<label>Property Name *</label>
<input type="text" id="propertyName" placeholder="Auto-filled when property selected" required readonly style="background: #f8f9fa;">
</div>
<div class="form-group">
<label>Property Type *</label>
<select id="propertyType" required>
<option value="">Select property type</option>
<option value="Apartment">Apartment</option>
<option value="Villa">Villa</option>
<option value="Penthouse">Penthouse</option>
<option value="Townhouse">Townhouse</option>
<option value="Office">Office</option>
<option value="Retail">Retail</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Location *</label>
<input type="text" id="location" placeholder="Auto-filled when property selected" required readonly style="background: #f8f9fa;">
</div>
<div class="form-group">
<label>Price (AED) *</label>
<input type="number" id="price" placeholder="Auto-filled when property selected" required readonly style="background: #f8f9fa;">
</div>
</div>
</div>
<div class="form-section">
<h3>Property Specifications</h3>
<div class="form-row">
<div class="form-group">
<label>Bedrooms *</label>
<input type="number" id="bedrooms" placeholder="Auto-filled when property selected" required readonly style="background: #f8f9fa;">
</div>
<div class="form-group">
<label>Bathrooms *</label>
<input type="number" id="bathrooms" placeholder="Auto-filled when property selected" required readonly style="background: #f8f9fa;">
</div>
</div>
<div class="form-group">
<label>Area (sq ft) *</label>
<input type="number" id="area" placeholder="Auto-filled when property selected" required readonly style="background: #f8f9fa;">
</div>
</div>
<div class="form-section">
<h3>Description</h3>
<div class="form-group">
<label>Property Description</label>
<textarea id="description" placeholder="Auto-filled when property selected" readonly style="background: #f8f9fa;"></textarea>
</div>
</div>
<div class="form-section">
<h3>Amenities</h3>
<div class="amenities-grid">
<div class="amenity-item">
<input type="checkbox" id="pool" value="Swimming Pool">
<label for="pool">Swimming Pool</label>
</div>
<div class="amenity-item">
<input type="checkbox" id="gym" value="Gym">
<label for="gym">Gym</label>
</div>
<div class="amenity-item">
<input type="checkbox" id="parking" value="Parking">
<label for="parking">Parking</label>
</div>
<div class="amenity-item">
<input type="checkbox" id="security" value="Security">
<label for="security">Security</label>
</div>
<div class="amenity-item">
<input type="checkbox" id="garden" value="Garden">
<label for="garden">Garden</label>
</div>
<div class="amenity-item">
<input type="checkbox" id="balcony" value="Balcony">
<label for="balcony">Balcony</label>
</div>
</div>
</div>
<div class="form-section">
<h3>Property Images with Room Names</h3>
<p class="section-description">Upload images for different rooms and areas. Each image will be tagged with its room name for professional presentation.</p>
<div id="imageUploads">
<div class="image-upload-item">
<div class="image-input-group">
<input type="text" class="room-name" placeholder="Room/Area Name (e.g., Master Bedroom, Kitchen, Living Room)" />
<input type="file" class="image-file" accept="image/*" />
<button type="button" class="remove-image-btn" onclick="removeImageUpload(this)">Remove</button>
</div>
<div class="image-preview"></div>
</div>
</div>
<button type="button" class="add-image-btn" onclick="addImageUpload()">+ Add Another Image</button>
<p class="help-text">Recommended: Upload 8-16 high-quality images for best results. Supported formats: JPG, PNG, WebP</p>
</div>
<div class="form-section">
<h3>Market Data & Analytics</h3>
<div class="form-row">
<div class="form-group">
<label>Market Trend</label>
<select id="marketTrend">
<option value="rising">Rising Market</option>
<option value="stable">Stable Market</option>
<option value="declining">Declining Market</option>
</select>
</div>
<div class="form-group">
<label>ROI Potential (%)</label>
<input type="number" id="roiPotential" placeholder="Expected ROI" min="0" max="100">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Average Price per sq ft</label>
<input type="number" id="avgPricePerSqft" placeholder="Market average">
</div>
<div class="form-group">
<label>Market Demand</label>
<select id="marketDemand">
<option value="high">High Demand</option>
<option value="medium">Medium Demand</option>
<option value="low">Low Demand</option>
</select>
</div>
</div>
<div class="form-group">
<label>Location Advantages</label>
<textarea id="locationAdvantages" placeholder="Describe location benefits (transport, schools, shopping, etc.)"></textarea>
</div>
</div>
<div class="form-section">
<h3>Investment Information</h3>
<div class="form-row">
<div class="form-group">
<label>Investment Type</label>
<select id="investmentType">
<option value="buy-to-live">Buy to Live</option>
<option value="buy-to-rent">Buy to Rent</option>
<option value="buy-to-sell">Buy to Sell</option>
</select>
</div>
<div class="form-group">
<label>Expected Rental Yield (%)</label>
<input type="number" id="rentalYield" placeholder="Annual rental yield" min="0" max="20">
</div>
</div>
<div class="form-group">
<label>Investment Highlights</label>
<textarea id="investmentHighlights" placeholder="Key investment benefits and features"></textarea>
</div>
</div>
<div class="navigation-buttons">
<button class="btn btn-secondary" onclick="previousStep()">Previous</button>
<button class="btn btn-primary" onclick="nextStep()" id="step2-next">Next: Content & Analytics</button>
</div>
</div>
</div>
<!-- Step 3: Content & Analytics -->
<div id="step3" class="step-content">
<div class="form-container">
<h2 style="text-align: center; margin-bottom: 30px; color: #2c3e50;">Content & Analytics Configuration</h2>
<div class="form-section">
<h3>Content Modules</h3>
<div class="content-modules">
<div class="module-item">
<input type="checkbox" id="marketResearch" value="market-research" checked>
<label for="marketResearch">Market Research Report</label>
<p>Include comprehensive market analysis and trends</p>
</div>
<div class="module-item">
<input type="checkbox" id="investmentGuide" value="investment-guide" checked>
<label for="investmentGuide">Investment Guide</label>
<p>Add investment strategies and ROI analysis</p>
</div>
<div class="module-item">
<input type="checkbox" id="dubaiInsights" value="dubai-insights" checked>
<label for="dubaiInsights">Dubai Market Insights</label>
<p>Include Dubai-specific market information</p>
</div>
<div class="module-item">
<input type="checkbox" id="locationAnalysis" value="location-analysis" checked>
<label for="locationAnalysis">Location Analysis</label>
<p>Add detailed location benefits and amenities</p>
</div>
</div>
</div>
<div class="form-section">
<h3>Investment Articles</h3>
<div class="investment-articles">
<div class="article-item">
<input type="checkbox" id="whyInvestDubai" value="why-invest-dubai" checked>
<label for="whyInvestDubai">"Why Invest in Dubai" Article</label>
<p>Comprehensive guide to Dubai real estate investment</p>
</div>
<div class="module-item">
<input type="checkbox" id="marketTrends" value="market-trends" checked>
<label for="marketTrends">Market Trends Analysis</label>
<p>Current market trends and future predictions</p>
</div>
<div class="module-item">
<input type="checkbox" id="roiStrategies" value="roi-strategies" checked>
<label for="roiStrategies">ROI Optimization Strategies</label>
<p>Tips for maximizing return on investment</p>
</div>
</div>
</div>
<div class="form-section">
<h3>Custom Content</h3>
<div class="form-group">
<label>Additional Content</label>
<textarea id="additionalContent" placeholder="Add any additional content, articles, or information you'd like to include in your brochure"></textarea>
</div>
</div>
<div class="navigation-buttons">
<button class="btn btn-secondary" onclick="previousStep()">Previous</button>
<button class="btn btn-primary" onclick="nextStep()" id="step3-next">Next: Preview & Customize</button>
</div>
</div>
</div>
<!-- Step 4: Preview & Customize -->
<div id="step4" class="step-content">
<div class="preview-container">
<div class="preview-header">
<h2 style="color: #2c3e50; margin-bottom: 10px;">Preview & Customize</h2>
<p style="color: #6c757d;">Review and customize your property brochure before final generation</p>
</div>
<div class="preview-content">
<div id="previewContent">
<!-- Preview content will be generated here -->
</div>
</div>
<div class="customization-options">
<h3>Customization Options</h3>
<div class="customization-grid">
<div class="customization-item">
<label>Header Style</label>
<select id="headerStyle">
<option value="modern">Modern</option>
<option value="classic">Classic</option>
<option value="luxury">Luxury</option>
</select>
</div>
<div class="customization-item">
<label>Color Scheme</label>
<select id="colorScheme">
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="purple">Purple</option>
<option value="gold">Gold</option>
</select>
</div>
<div class="customization-item">
<label>Font Style</label>
<select id="fontStyle">
<option value="sans-serif">Sans Serif</option>
<option value="serif">Serif</option>
<option value="modern">Modern</option>
</select>
</div>
</div>
</div>
<div class="navigation-buttons">
<button class="btn btn-secondary" onclick="previousStep()">Previous</button>
<button class="btn btn-success" onclick="generatePDF()" id="generate-pdf-btn">Next: Generate & Export</button>
</div>
</div>
</div>
<!-- Step 5: Generate & Export -->
<div id="step5" class="step-content">
<div class="preview-container">
<div class="preview-header">
<h2 style="color: #2c3e50; margin-bottom: 10px;">PDF Generation & Export</h2>
<p style="color: #6c757d;">Your high-quality property brochure is being generated</p>
</div>
<div id="loadingSpinner" class="loading-spinner">
<div class="spinner"></div>
<h3>Generating your PDF...</h3>
<p>This may take a few moments. Please wait.</p>
</div>
<div id="pdfResult" style="display: none;">
<div style="text-align: center; padding: 40px;">
<div style="font-size: 4rem; color: #28a745; margin-bottom: 20px;"></div>
<h3 style="color: #28a745; margin-bottom: 15px;">PDF Generated Successfully!</h3>
<p style="color: #6c757d; margin-bottom: 30px;">Your high-quality property brochure is ready for download.</p>
<button class="btn btn-success" onclick="downloadPDF()" style="font-size: 1.1rem; padding: 15px 40px;">📥 Download PDF</button>
</div>
</div>
<div class="navigation-buttons">
<button class="btn btn-secondary" onclick="previousStep()">Previous</button>
<button class="btn btn-primary" onclick="startOver()">Start Over</button>
</div>
</div>
</div>
</div>
<script>
let currentStep = 1;
let selectedTemplate = null;
let selectedLayout = null;
let propertyData = {};
let uploadedImages = [];
let apiBaseUrl = 'http://localhost:8000/api';
let salesforceApiUrl = 'https://test.salesforce.com';
let availableProperties = [];
let salesforceCredentials = {
clientId: '',
clientSecret: '',
username: '',
password: '',
securityToken: ''
};
// Auto-load credentials on page load
window.onload = function() {
loadAutoCredentials();
};
// Auto-credential loading function
function loadAutoCredentials() {
console.log('Loading auto-generated Salesforce credentials...');
// Try to load from auto-credentials.js first
const script = document.createElement('script');
script.src = 'auto-credentials.js';
script.onload = function() {
console.log('Auto-credentials script loaded');
// The script will automatically set localStorage
checkAndLoadCredentials();
};
script.onerror = function() {
console.log('Auto-credentials script not found, checking localStorage');
checkAndLoadCredentials();
};
document.head.appendChild(script);
}
function checkAndLoadCredentials() {
const savedCredentials = localStorage.getItem('salesforceCredentials');
if (savedCredentials) {
try {
const creds = JSON.parse(savedCredentials);
salesforceCredentials = creds;
console.log('Salesforce credentials loaded from localStorage');
// Auto-hide credential form if credentials exist
const form = document.getElementById('salesforceCredentialsForm');
if (form) {
form.style.display = 'none';
}
// Show success message
showSuccess('Salesforce credentials auto-loaded successfully!');
} catch (e) {
console.error('Error loading saved credentials:', e);
}
} else {
console.log('No saved credentials found');
}
}
// Real-time customization updates
function updateCustomizationPreview() {
const headerStyle = document.getElementById('headerStyle').value;
const colorScheme = document.getElementById('colorScheme').value;
const fontStyle = document.getElementById('fontStyle').value;
// Update preview content with new styles
const previewContent = document.getElementById('previewContent');
if (previewContent) {
// Apply color scheme
const colorMap = {
'blue': '#007bff',
'green': '#28a745',
'purple': '#6f42c1',
'gold': '#ffc107'
};
const selectedColor = colorMap[colorScheme] || '#007bff';
// Update preview styling
previewContent.style.setProperty('--accent-color', selectedColor);
previewContent.style.setProperty('--header-style', headerStyle);
previewContent.style.setProperty('--font-family', fontStyle === 'serif' ? 'Georgia, serif' : 'Arial, sans-serif');
// Add visual feedback
document.body.style.setProperty('--custom-accent', selectedColor);
// Update any color-coded elements in the preview
const colorElements = previewContent.querySelectorAll('.color-accent');
colorElements.forEach(el => {
el.style.color = selectedColor;
});
// Update header styling
const headers = previewContent.querySelectorAll('h1, h2, h3');
headers.forEach(header => {
if (headerStyle === 'luxury') {
header.style.fontWeight = '900';
header.style.textShadow = '2px 2px 4px rgba(0,0,0,0.3)';
} else if (headerStyle === 'classic') {
header.style.fontWeight = '700';
header.style.fontStyle = 'italic';
} else {
header.style.fontWeight = '600';
header.style.textShadow = 'none';
header.style.fontStyle = 'normal';
}
});
// Show visual feedback
showCustomizationFeedback(headerStyle, colorScheme, fontStyle);
}
}
function showCustomizationFeedback(headerStyle, colorScheme, fontStyle) {
// Create or update feedback element
let feedback = document.getElementById('customization-feedback');
if (!feedback) {
feedback = document.createElement('div');
feedback.id = 'customization-feedback';
feedback.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 15px 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 1000;
font-weight: 600;
transform: translateX(100%);
transition: transform 0.3s ease;
`;
document.body.appendChild(feedback);
}
feedback.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.2rem;">✨</span>
<span>Style Updated: ${headerStyle}${colorScheme}${fontStyle}</span>
</div>
`;
// Show feedback
setTimeout(() => {
feedback.style.transform = 'translateX(0)';
}, 100);
// Hide feedback after 3 seconds
setTimeout(() => {
feedback.style.transform = 'translateX(100%)';
}, 3000);
}
// Salesforce Direct Integration Functions
async function authenticateWithSalesforce() {
try {
// Check if credentials are set
if (!salesforceCredentials.clientId || !salesforceCredentials.clientSecret ||
!salesforceCredentials.username || !salesforceCredentials.password ||
!salesforceCredentials.securityToken) {
// Show credential input form
showCredentialInputForm();
return false;
}
const authUrl = `${salesforceApiUrl}/services/oauth2/token`;
const formData = new FormData();
formData.append('grant_type', 'password');
formData.append('client_id', salesforceCredentials.clientId);
formData.append('client_secret', salesforceCredentials.clientSecret);
formData.append('username', salesforceCredentials.username);
formData.append('password', salesforceCredentials.password + salesforceCredentials.securityToken);
const response = await fetch(authUrl, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Authentication failed: ${response.status}`);
}
const authData = await response.json();
window.salesforceAccessToken = authData.access_token;
window.salesforceInstanceUrl = authData.instance_url;
console.log('Authenticated with Salesforce:', authData.instance_url);
return true;
} catch (error) {
console.error('Salesforce authentication failed:', error);
showError(`Salesforce authentication failed: ${error.message}`);
return false;
}
}
async function loadPropertiesFromSalesforce() {
try {
document.getElementById('propertyLoading').style.display = 'block';
document.getElementById('propertyError').style.display = 'none';
// First authenticate
const isAuthenticated = await authenticateWithSalesforce();
if (!isAuthenticated) {
return;
}
// Query all properties from Salesforce
const query = `
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,
pcrm__Floor__c, pcrm__Total_Units__c, pcrm__Developer__c,
pcrm__Handover_Date__c, pcrm__Payment_Plan__c, pcrm__Maintenance_Company__c
FROM pcrm__Property__c
ORDER BY Name
`;
const queryUrl = `${window.salesforceInstanceUrl}/services/data/v59.0/query`;
const params = new URLSearchParams({ q: query });
const response = await fetch(`${queryUrl}?${params}`, {
headers: {
'Authorization': `Bearer ${window.salesforceAccessToken}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Salesforce API Error: ${response.status}`);
}
const result = await response.json();
if (result.records && result.records.length > 0) {
// Format properties for dropdown
availableProperties = result.records.map(prop => {
const propertyType = prop.pcrm__Property_Type__c || 'Unknown';
const location = prop.pcrm__Sub_Locality_Bayut_Dubizzle__c || 'Unknown Location';
const price = prop.pcrm__Sale_Price_max__c || prop.pcrm__Rent_Price_max__c;
const priceText = price ? `AED ${price.toLocaleString()}` : 'Price on request';
const label = `${prop.Name} - ${propertyType} - ${location} - ${priceText}`;
return {
label: label,
value: prop.Id,
property: prop,
summary: {
type: propertyType,
location: location,
price: price,
bedrooms: prop.pcrm__Bedrooms__c,
bathrooms: prop.pcrm__Bathrooms__c,
area: prop.pcrm__Size__c,
status: prop.pcrm__Completion_Status__c
}
};
});
populatePropertyDropdown();
console.log(`Loaded ${availableProperties.length} properties directly from Salesforce`);
// Show success message
showSuccess(`Successfully loaded ${availableProperties.length} properties from your Salesforce sandbox!`);
} else {
throw new Error('No properties found in Salesforce');
}
} catch (error) {
console.error('Error loading properties from Salesforce:', error);
document.getElementById('propertyError').innerHTML = `
<strong>Error loading properties from Salesforce:</strong> ${error.message}<br>
<small>Please check your credentials and ensure the pcrm__Property__c object exists</small>
`;
document.getElementById('propertyError').style.display = 'block';
} finally {
document.getElementById('propertyLoading').style.display = 'none';
}
}
// Legacy function for backward compatibility
async function loadPropertiesFromAPI() {
// Redirect to Salesforce function
await loadPropertiesFromSalesforce();
}
// Credential management functions
function showCredentialInputForm() {
document.getElementById('salesforceCredentialsForm').style.display = 'block';
// Load saved credentials if they exist
const savedCredentials = localStorage.getItem('salesforceCredentials');
if (savedCredentials) {
try {
const creds = JSON.parse(savedCredentials);
document.getElementById('sfClientId').value = creds.clientId || '';
document.getElementById('sfClientSecret').value = creds.clientSecret || '';
document.getElementById('sfUsername').value = creds.username || '';
document.getElementById('sfPassword').value = creds.password || '';
document.getElementById('sfSecurityToken').value = creds.securityToken || '';
} catch (e) {
console.error('Error loading saved credentials:', e);
}
}
}
function hideCredentialForm() {
document.getElementById('salesforceCredentialsForm').style.display = 'none';
}
function saveSalesforceCredentials() {
const clientId = document.getElementById('sfClientId').value.trim();
const clientSecret = document.getElementById('sfClientSecret').value.trim();
const username = document.getElementById('sfUsername').value.trim();
const password = document.getElementById('sfPassword').value.trim();
const securityToken = document.getElementById('sfSecurityToken').value.trim();
if (!clientId || !clientSecret || !username || !password || !securityToken) {
alert('Please fill in all credential fields');
return;
}
// Save credentials
salesforceCredentials = {
clientId,
clientSecret,
username,
password,
securityToken
};
// Save to localStorage for persistence
localStorage.setItem('salesforceCredentials', JSON.stringify(salesforceCredentials));
// Hide form
hideCredentialForm();
// Show success message
showSuccess('Salesforce credentials saved successfully! Now loading properties...');
// Load properties
setTimeout(() => {
loadPropertiesFromSalesforce();
}, 1000);
}
function showSuccess(message) {
let successDiv = document.getElementById('successMessage');
if (!successDiv) {
successDiv = document.createElement('div');
successDiv.id = 'successMessage';
successDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 15px 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 1000;
font-weight: 600;
transform: translateX(100%);
transition: transform 0.3s ease;
max-width: 400px;
`;
document.body.appendChild(successDiv);
}
successDiv.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.2rem;">✅</span>
<span>${message}</span>
</div>
`;
// Show feedback
setTimeout(() => {
successDiv.style.transform = 'translateX(0)';
}, 100);
// Hide feedback after 4 seconds
setTimeout(() => {
successDiv.style.transform = 'translateX(100%)';
}, 4000);
}
function showError(message) {
let errorDiv = document.getElementById('errorMessage');
if (!errorDiv) {
errorDiv = document.createElement('div');
errorDiv.id = 'errorMessage';
errorDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #dc3545;
color: white;
padding: 15px 20px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 1000;
font-weight: 600;
transform: translateX(100%);
transition: transform 0.3s ease;
max-width: 400px;
`;
document.body.appendChild(errorDiv);
}
errorDiv.innerHTML = `
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 1.2rem;">❌</span>
<span>${message}</span>
</div>
`;
// Show feedback
setTimeout(() => {
errorDiv.style.transform = 'translateX(0)';
}, 100);
// Hide feedback after 5 seconds
setTimeout(() => {
errorDiv.style.transform = 'translateX(100%)';
}, 5000);
}
function populatePropertyDropdown() {
const select = document.getElementById('propertySelect');
select.innerHTML = '<option value="">Choose a property from your Salesforce data...</option>';
availableProperties.forEach(prop => {
const option = document.createElement('option');
option.value = prop.value;
option.textContent = prop.label;
option.dataset.property = JSON.stringify(prop.property);
select.appendChild(option);
});
// Show property statistics
showPropertyStatistics();
}
function showPropertyStatistics() {
const stats = {
total: availableProperties.length,
types: {},
locations: {}
};
availableProperties.forEach(prop => {
const type = prop.property.pcrm__Property_Type__c || 'Unknown';
const location = prop.property.pcrm__Sub_Locality_Bayut_Dubizzle__c || 'Unknown';
stats.types[type] = (stats.types[type] || 0) + 1;
stats.locations[location] = (stats.locations[location] || 0) + 1;
});
console.log('Property Statistics:', stats);
// Show stats in the UI if there's a stats element
const statsElement = document.getElementById('propertyStats');
if (statsElement) {
statsElement.innerHTML = `
<div style="background: #e3f2fd; padding: 15px; border-radius: 10px; margin: 15px 0;">
<h4 style="margin: 0 0 10px 0; color: #1976d2;">Property Database Statistics</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;">
<div>
<strong>Total Properties:</strong> ${stats.total}
</div>
<div>
<strong>Property Types:</strong> ${Object.keys(stats.types).length}
</div>
<div>
<strong>Locations:</strong> ${Object.keys(stats.locations).length}
</div>
</div>
</div>
`;
}
}
function handlePropertySelection() {
const select = document.getElementById('propertySelect');
const selectedId = select.value;
if (!selectedId) {
clearPropertyForm();
return;
}
try {
const selectedOption = select.options[select.selectedIndex];
const property = JSON.parse(selectedOption.dataset.property);
// Enhanced property type mapping
const propertyTypeMap = {
'AP': 'Apartment',
'VI': 'Villa',
'PE': 'Penthouse',
'TH': 'Townhouse',
'BU': 'Office',
'BW': 'Retail'
};
// Fill form fields with enhanced data
document.getElementById('propertyName').value = property.Name || '';
const propertyType = propertyTypeMap[property.pcrm__Property_Type__c] || 'Apartment';
document.getElementById('propertyType').value = propertyType;
document.getElementById('location').value = property.pcrm__Sub_Locality_Bayut_Dubizzle__c || '';
document.getElementById('price').value = property.pcrm__Sale_Price_max__c || property.pcrm__Rent_Price_max__c || '';
document.getElementById('bedrooms').value = property.pcrm__Bedrooms__c || '';
document.getElementById('bathrooms').value = property.pcrm__Bathrooms__c || '';
document.getElementById('area').value = property.pcrm__Size__c || '';
document.getElementById('description').value = property.pcrm__Description_English__c || '';
// Auto-select amenities based on property data
if (property.pcrm__Private_Amenities__c) {
selectAmenitiesFromString(property.pcrm__Private_Amenities__c);
}
// Store complete property data for PDF generation with all new fields
propertyData = {
...propertyData,
propertyName: property.Name,
propertyType: propertyType,
location: property.pcrm__Sub_Locality_Bayut_Dubizzle__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,
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,
floor: property.pcrm__Floor__c,
totalUnits: property.pcrm__Total_Units__c,
developer: property.pcrm__Developer__c,
handoverDate: property.pcrm__Handover_Date__c,
paymentPlan: property.pcrm__Payment_Plan__c,
maintenanceCompany: property.pcrm__Maintenance_Company__c
};
console.log('Property selected and form populated with enhanced data:', propertyData);
// Enhanced visual feedback with property details
showPropertySelectedFeedback(property.Name, propertyType, property.pcrm__Sub_Locality_Bayut_Dubizzle__c);
} catch (error) {
console.error('Error handling property selection:', error);
alert('Error loading property data. Please try again.');
}
}
function clearPropertyForm() {
document.getElementById('propertyName').value = '';
document.getElementById('propertyType').value = '';
document.getElementById('location').value = '';
document.getElementById('price').value = '';
document.getElementById('bedrooms').value = '';
document.getElementById('bathrooms').value = '';
document.getElementById('area').value = '';
document.getElementById('description').value = '';
// Clear amenities
document.querySelectorAll('.amenity-item input[type="checkbox"]').forEach(checkbox => {
checkbox.checked = false;
});
}
function selectAmenitiesFromString(amenitiesString) {
if (!amenitiesString) return;
const amenities = amenitiesString.split(',').map(a => a.trim().toLowerCase());
document.querySelectorAll('.amenity-item input[type="checkbox"]').forEach(checkbox => {
const label = checkbox.nextElementSibling.textContent.toLowerCase();
checkbox.checked = amenities.some(amenity =>
amenity.includes(label) || label.includes(amenity)
);
});
}
function showPropertySelectedFeedback(propertyName, propertyType, location) {
let feedback = document.getElementById('property-selected-feedback');
if (!feedback) {
feedback = document.createElement('div');
feedback.id = 'property-selected-feedback';
feedback.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
background: #28a745;
color: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
z-index: 1000;
font-weight: 600;
transform: translateX(100%);
transition: transform 0.3s ease;
max-width: 350px;
`;
document.body.appendChild(feedback);
}
feedback.innerHTML = `
<div style="display: flex; flex-direction: column; gap: 8px;">
<div style="display: flex; align-items: center; gap: 10px; font-size: 1.1rem;">
<span style="font-size: 1.3rem;">🏠</span>
<span><strong>Property Loaded Successfully!</strong></span>
</div>
<div style="font-size: 0.9rem; opacity: 0.9;">
<div><strong>Name:</strong> ${propertyName}</div>
<div><strong>Type:</strong> ${propertyType}</div>
<div><strong>Location:</strong> ${location}</div>
</div>
<div style="font-size: 0.8rem; opacity: 0.8; margin-top: 5px;">
All fields have been auto-populated from your Salesforce data
</div>
</div>
`;
// Show feedback
setTimeout(() => {
feedback.style.transform = 'translateX(0)';
}, 100);
// Hide feedback after 5 seconds
setTimeout(() => {
feedback.style.transform = 'translateX(100%)';
}, 5000);
}
// Template category management
// Template selection
function selectTemplate(templateName) {
try {
// Remove previous selection
document.querySelectorAll('.template-card').forEach(card => {
card.classList.remove('selected');
});
// Select new template
if (event && event.currentTarget) {
event.currentTarget.classList.add('selected');
}
selectedTemplate = templateName;
// Show layout options for custom template
const layoutOptions = document.getElementById('layoutOptions');
if (templateName === 'custom' && layoutOptions) {
layoutOptions.style.display = 'block';
} else if (layoutOptions) {
layoutOptions.style.display = 'none';
}
// Enable next button
const step1Next = document.getElementById('step1-next');
if (step1Next) {
step1Next.disabled = false;
}
} catch (error) {
console.error('Error in selectTemplate:', error);
}
}
// Layout selection
function selectLayout(layout) {
try {
selectedLayout = layout;
// Update layout preview
document.querySelectorAll('.layout-option').forEach(option => {
option.style.borderColor = '#e9ecef';
});
if (event && event.currentTarget) {
event.currentTarget.style.borderColor = '#007bff';
}
} catch (error) {
console.error('Error in selectLayout:', error);
}
}
// Step navigation
function nextStep() {
if (currentStep < 5) {
if (validateCurrentStep()) {
currentStep++;
updateStepDisplay();
}
}
}
function previousStep() {
if (currentStep > 1) {
currentStep--;
updateStepDisplay();
}
}
function updateStepDisplay() {
// Hide all steps
document.querySelectorAll('.step-content').forEach(step => {
step.classList.remove('active');
});
// Show current step
document.getElementById(`step${currentStep}`).classList.add('active');
// Update navigation
updateStepNavigation();
// Handle step-specific actions
if (currentStep === 2) {
// Check if we have Salesforce credentials
const savedCredentials = localStorage.getItem('salesforceCredentials');
if (savedCredentials) {
try {
const creds = JSON.parse(savedCredentials);
salesforceCredentials = creds;
// Load properties from Salesforce
loadPropertiesFromSalesforce();
} catch (e) {
console.error('Error loading saved credentials:', e);
showCredentialInputForm();
}
} else {
// Show credential form if no credentials exist
showCredentialInputForm();
}
} else if (currentStep === 4) {
generatePreview();
} else if (currentStep === 5) {
// Show loading spinner when entering step 5
document.getElementById('loadingSpinner').style.display = 'block';
document.getElementById('pdfResult').style.display = 'none';
}
}
function updateStepNavigation() {
document.querySelectorAll('.step-item').forEach((item, index) => {
item.classList.remove('active', 'completed');
if (index + 1 === currentStep) {
item.classList.add('active');
} else if (index + 1 < currentStep) {
item.classList.add('completed');
}
});
}
function validateCurrentStep() {
if (currentStep === 1) {
if (!selectedTemplate) {
alert('Please select a template first.');
return false;
}
if (selectedTemplate === 'custom' && !selectedLayout) {
alert('Please select a layout for custom template.');
return false;
}
} else if (currentStep === 2) {
const requiredFields = ['propertyName', 'propertyType', 'location', 'price', 'bedrooms', 'bathrooms', 'area'];
for (let field of requiredFields) {
if (!document.getElementById(field).value) {
alert('Please fill in all required fields.');
return false;
}
}
} else if (currentStep === 3) {
const contentModules = Array.from(document.querySelectorAll('#step3 input[type="checkbox"]:checked'));
if (contentModules.length === 0) {
alert('Please select at least one content module.');
return false;
}
}
return true;
}
async function generatePreview() {
try {
// Collect images with room names
const imagesWithNames = [];
if (window.propertyImages) {
imagesWithNames.push(...window.propertyImages);
}
// Collect form data
propertyData = {
template: selectedTemplate,
layout: selectedLayout,
propertyName: document.getElementById('propertyName').value,
propertyType: document.getElementById('propertyType').value,
location: document.getElementById('location').value,
price: document.getElementById('price').value,
bedrooms: document.getElementById('bedrooms').value,
bathrooms: document.getElementById('bathrooms').value,
area: document.getElementById('area').value,
description: document.getElementById('description').value,
amenities: Array.from(document.querySelectorAll('#step2 input[type="checkbox"]:checked')).map(cb => cb.value),
images: imagesWithNames.map(img => img.data), // Extract base64 data
imageNames: imagesWithNames.map(img => img.name), // Extract room names
// Market Data
marketTrend: document.getElementById('marketTrend').value,
roiPotential: document.getElementById('roiPotential').value,
avgPricePerSqft: document.getElementById('avgPricePerSqft').value,
marketDemand: document.getElementById('marketDemand').value,
locationAdvantages: document.getElementById('locationAdvantages').value,
// Investment Data
investmentType: document.getElementById('investmentType').value,
rentalYield: document.getElementById('rentalYield').value,
investmentHighlights: document.getElementById('investmentHighlights').value,
// Content Modules
contentModules: Array.from(document.querySelectorAll('#step3 input[type="checkbox"]:checked')).map(cb => cb.value),
additionalContent: document.getElementById('additionalContent').value
};
// Call API to generate preview
const response = await fetch(`${apiBaseUrl}/preview`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(propertyData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const previewData = await response.json();
if (previewData.success) {
// Display preview content
const previewContent = document.getElementById('previewContent');
previewContent.innerHTML = previewData.preview.preview_html;
} else {
throw new Error(previewData.message || 'Failed to generate preview');
}
} catch (error) {
console.error('Error generating preview:', error);
alert('Error generating preview. Please try again.');
// Fallback to local preview
const previewContent = document.getElementById('previewContent');
previewContent.innerHTML = generateLocalPreview(propertyData);
}
}
function generateLocalPreview(data) {
// Fallback preview generation if API fails
let preview = `
<div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 4px 20px rgba(0,0,0,0.1);">
<div style="text-align: center; margin-bottom: 30px;">
<h1 style="color: #2c3e50; font-size: 2.5rem; margin-bottom: 10px;">${data.propertyName}</h1>
<p style="color: #6c757d; font-size: 1.2rem;">${data.propertyType} in ${data.location}</p>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px;">
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px;">
<h3 style="color: #2c3e50; margin-bottom: 15px;">Property Details</h3>
<p><strong>Type:</strong> ${data.propertyType}</p>
<p><strong>Location:</strong> ${data.location}</p>
<p><strong>Price:</strong> AED ${data.price}</p>
<p><strong>Bedrooms:</strong> ${data.bedrooms}</p>
<p><strong>Bathrooms:</strong> ${data.bathrooms}</p>
<p><strong>Area:</strong> ${data.area} sq ft</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px;">
<h3 style="color: #2c3e50; margin-bottom: 15px;">Amenities</h3>
${data.amenities.length > 0 ? data.amenities.map(amenity => `<p>• ${amenity}</p>`).join('') : '<p>No amenities selected</p>'}
</div>
</div>
${data.description ? `
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin-bottom: 30px;">
<h3 style="color: #2c3e50; margin-bottom: 15px;">Description</h3>
<p>${data.description}</p>
</div>
` : ''}
<div style="text-align: center; color: #6c757d; font-style: italic;">
<p>Template: ${data.template.charAt(0).toUpperCase() + data.template.slice(1)}</p>
<p>This is a preview. The final PDF will be generated with the selected template styling.</p>
</div>
</div>
`;
return preview;
}
async function generatePDF() {
try {
// Show loading spinner
document.getElementById('loadingSpinner').style.display = 'block';
document.getElementById('generate-pdf-btn').disabled = true;
// Prepare request data
const requestData = {
property_data: propertyData,
template_name: selectedTemplate
};
// Call API to generate PDF
const response = await fetch(`${apiBaseUrl}/generate-pdf`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
// Store PDF URL for download (remove the /api prefix since it's already in the URL)
const pdfUrl = result.pdf_url.replace('/api', '');
window.generatedPdfUrl = pdfUrl;
// Move to step 5 (PDF Export)
currentStep = 5;
updateStepDisplay();
// Show PDF result
document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('pdfResult').style.display = 'block';
} else {
throw new Error(result.error || 'Failed to generate PDF');
}
} catch (error) {
console.error('Error generating PDF:', error);
alert('Error generating PDF. Please try again.');
// Hide loading spinner and re-enable button
document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('generate-pdf-btn').disabled = false;
}
}
async function downloadPDF() {
try {
if (window.generatedPdfUrl) {
// Download the generated PDF
const response = await fetch(`${apiBaseUrl}${window.generatedPdfUrl}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `property_brochure_${selectedTemplate}_${Date.now()}.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} else {
// Fallback: generate a demo PDF
alert('Generating demo PDF...');
const response = await fetch(`${apiBaseUrl}/download-pdf/demo`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `demo_property_brochure.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
} catch (error) {
console.error('Error downloading PDF:', error);
alert('Error downloading PDF. Please try again.');
}
}
function startOver() {
currentStep = 1;
selectedTemplate = null;
selectedLayout = null;
propertyData = {};
uploadedImages = [];
// Reset form
document.querySelectorAll('input, select, textarea').forEach(el => {
if (el.type === 'checkbox') {
el.checked = false;
} else {
el.value = '';
}
});
// Reset template selection
document.querySelectorAll('.template-card').forEach(card => {
card.classList.remove('selected');
});
// Reset layout options
document.getElementById('layoutOptions').style.display = 'none';
document.querySelectorAll('.layout-option').forEach(option => {
option.style.borderColor = '#e9ecef';
});
// Reset buttons
document.getElementById('step1-next').disabled = true;
// Reset preview and result displays
document.getElementById('previewContent').innerHTML = '';
document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('pdfResult').style.display = 'none';
// Update display
updateStepDisplay();
}
// Image upload handling
document.getElementById('propertyImages').addEventListener('change', function(e) {
const files = e.target.files;
if (files.length > 0) {
document.getElementById('imagePreview').style.display = 'block';
const imageGrid = document.getElementById('imageGrid');
imageGrid.innerHTML = '';
uploadedImages = [];
Array.from(files).forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(e) {
uploadedImages.push(e.target.result);
const imgDiv = document.createElement('div');
imgDiv.innerHTML = `
<div style="position: relative; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<img src="${e.target.result}" style="width: 100%; height: 150px; object-fit: cover;">
<button onclick="removeImage(${index})" style="position: absolute; top: 5px; right: 5px; background: #dc3545; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer;">×</button>
</div>
`;
imageGrid.appendChild(imgDiv);
};
reader.readAsDataURL(file);
});
}
});
function removeImage(index) {
uploadedImages.splice(index, 1);
// Refresh image preview
document.getElementById('propertyImages').dispatchEvent(new Event('change'));
}
// Check API connection on page load
async function checkApiConnection() {
try {
const response = await fetch(`${apiBaseUrl}/health`);
if (response.ok) {
console.log('API server is running');
} else {
console.warn('API server responded with error');
}
} catch (error) {
console.warn('Cannot connect to API server. Make sure it\'s running on http://localhost:8000');
console.warn('The application will work with local preview generation only.');
}
}
// Initialize
updateStepNavigation();
checkApiConnection();
// Add event listeners for real-time customization updates
document.getElementById('headerStyle').addEventListener('change', updateCustomizationPreview);
document.getElementById('colorScheme').addEventListener('change', updateCustomizationPreview);
document.getElementById('fontStyle').addEventListener('change', updateCustomizationPreview);
// Debug logging
console.log('Property Brochure Generator initialized successfully');
console.log('Available templates:', document.querySelectorAll('.template-card').length);
console.log('Available steps:', document.querySelectorAll('.step-content').length);
// Image Upload Functions
function addImageUpload() {
const imageUploads = document.getElementById('imageUploads');
const newUpload = document.createElement('div');
newUpload.className = 'image-upload-item';
newUpload.innerHTML = `
<div class="image-input-group">
<input type="text" class="room-name" placeholder="Room/Area Name (e.g., Master Bedroom, Kitchen, Living Room)" />
<input type="file" class="image-file" accept="image/*" onchange="handleImageUpload(this)" />
<button type="button" class="remove-image-btn" onclick="removeImageUpload(this)">Remove</button>
</div>
<div class="image-preview"></div>
`;
imageUploads.appendChild(newUpload);
}
function removeImageUpload(button) {
const uploadItem = button.closest('.image-upload-item');
uploadItem.remove();
}
function handleImageUpload(input) {
const file = input.files[0];
const uploadItem = input.closest('.image-upload-item');
const preview = uploadItem.querySelector('.image-preview');
const roomName = uploadItem.querySelector('.room-name').value || 'Unnamed Room';
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.src = e.target.result;
img.alt = roomName;
img.title = roomName;
// Clear previous previews
preview.innerHTML = '';
preview.appendChild(img);
// Store image data with room name
if (!window.propertyImages) window.propertyImages = [];
window.propertyImages.push({
name: roomName,
data: e.target.result,
file: file
});
};
reader.readAsDataURL(file);
}
}
// Update room name when it changes
document.addEventListener('input', function(e) {
if (e.target.classList.contains('room-name')) {
const uploadItem = e.target.closest('.image-upload-item');
const preview = uploadItem.querySelector('.image-preview img');
if (preview) {
preview.alt = e.target.value || 'Unnamed Room';
preview.title = e.target.value || 'Unnamed Room';
// Update stored image data
if (window.propertyImages) {
const index = Array.from(document.querySelectorAll('.image-upload-item')).indexOf(uploadItem);
if (index >= 0 && window.propertyImages[index]) {
window.propertyImages[index].name = e.target.value || 'Unnamed Room';
}
}
}
}
});
</script>
</body>
</html>