1/** 2 * PropertyContactFormComponent - Formulario de contacto para propiedades 3 * 4 * Migrado desde oldsite/js/portafolio/form-sender.js con soporte multi-idioma 5 * 6 * CaracterÃsticas: 7 * - Formulario de contacto por propiedad 8 * - Integración con Salesforce Web-to-Lead 9 * - Validación de campos 10 * - Soporte multi-idioma 11 * - Dialog independiente de Syncfusion (900x600px) 12 * - Selector de propiedades inteligente (habilitado/deshabilitado según contexto) 13 * - Propiedades ordenadas por estado y nombre 14 * 15 * Uso: 16 * ```javascript 17 * // Desde propiedad especÃfica (selector deshabilitado) 18 * contactForm.show('P133'); 19 * 20 * // Desde contacto general (selector habilitado) 21 * contactForm.show(null); 22 * ``` 23 */ 24 25export class PropertyContactFormComponent { 26 constructor(facade, dataService) { 27 this.facade = facade; 28 this.dataService = dataService; 29 this.logger = facade.logger || console; 30 31 this.dialog = null; 32 this.currentProperty = null; 33 this.isInitialized = false; 34 35 // Configuración del dialog independiente 36 this.dialogConfig = { 37 containerId: 'contact_dialog', 38 targetId: 'contact_dialog_container', 39 width: '900px', 40 height: '600px' 41 }; 42 43 // Configuración de Salesforce 44 this.salesforceConfig = { 45 endpoint: 'https://webto.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8', 46 oid: '00Di0000000cSTi', 47 returnUrl: window.location.origin + '/portafolio.html' 48 }; 49 50 // Información de contacto (default) 51 this.contactInfo = { 52 address: { 53 es: 'Av. Eugenio Garza Sada #3431\\nUrban Village Garza Sada, P1, local SA012,\\nCol. Arroyo Seco CP. 64740\\nMonterrey, Nuevo León.', 54 en: 'Av. Eugenio Garza Sada #3431\\nUrban Village Garza Sada, P1, local SA012,\\nCol. Arroyo Seco CP. 64740\\nMonterrey, Nuevo León.' 55 }, 56 phone: '(+52 81) 8335-1631', 57 email: '[email protected]', 58 social: { 59 linkedin: 'https://www.linkedin.com/company/planigrupo/', 60 instagram: 'https://www.instagram.com/planigrupo/', 61 facebook: 'https://www.facebook.com/PlanigrupoMX/', 62 twitter: 'https://x.com/planigrupo', 63 youtube: 'https://www.youtube.com/@Planigrupo-c3v' 64 } 65 }; 66 } 67 68 69 _createDialog() { 70 const container = document.getElementById(this.dialogConfig.containerId); 71 const target = document.getElementById(this.dialogConfig.targetId); 72 73 if (!container) { 74 throw new Error(`Dialog container #${this.dialogConfig.containerId} not found`); 75 } 76 77 if (!target) { 78 throw new Error(`Dialog target #${this.dialogConfig.targetId} not found`); 79 } 80 81 const dialogOptions = { 82 isModal: true, 83 overlayClick: () => this.hide(), 84 content: '', 85 target: target, 86 width: this.dialogConfig.width, 87 height: this.dialogConfig.height, 88 visible: false 89 }; 90 91 this.dialog = new ej.popups.Dialog(dialogOptions); 92 this.dialog.appendTo(`#${this.dialogConfig.containerId}`); 93 94 this.logger.info('[PropertyContactFormComponent] â Dialog created'); 95 } 96 97 async init() { 98 try { 99 this.logger.info('[PropertyContactFormComponent] Initializing...'); 100 101 if (typeof ej === 'undefined' || !ej.popups) { 102 throw new Error('Syncfusion Popups library not loaded'); 103 } 104 105 ej.base.enableRipple(true); 106 this._createDialog(); 107 108 this.isInitialized = true; 109 this.logger.info('[PropertyContactFormComponent] â Initialized'); 110 111 } catch (error) { 112 this.logger.error('[PropertyContactFormComponent] Error initializing:', error); 113 throw error; 114 } 115 } 116 117 async show(propertyId) { 118 if (!this.dialog) { 119 this.logger.warn('[PropertyContactFormComponent] Cannot show: dialog not initialized'); 120 return; 121 } 122 123 try { 124 this.logger.info(`[PropertyContactFormComponent] Showing contact form for: ${propertyId}`); 125 126 const property = this.dataService.getPropertyById(propertyId); 127 128 if (!property) { 129 this.logger.error(`[PropertyContactFormComponent] Property not found: ${propertyId}`); 130 return; 131 } 132 133 this.currentProperty = property; 134 135 const content = this._renderContactForm(property); 136 137 const dialogContent = document.querySelector('#contact_dialog_dialog-content'); 138 if (dialogContent) { 139 dialogContent.innerHTML = content; 140 } 141 142 this.dialog.show(); 143 this._applyTranslations(); 144 this._setupFormEventListeners(); 145 146 } catch (error) { 147 this.logger.error('[PropertyContactFormComponent] Error showing form:', error); 148 } 149 } 150 151 _applyTranslations() { 152 const dialogContent = document.querySelector('#contact_dialog_dialog-content'); 153 if (!dialogContent) return; 154 155 const elements = dialogContent.querySelectorAll('[data-i18n]');
156 elements.forEach(element => { 157 const key = element.getAttribute('data-i18n'); 158 if (key) { 159 const translation = this.facade.t(key); 160 if (translation && translation !== key) { 161 element.textContent = translation; 162 } 163 } 164 }); 165 166 const elementsWithAttr = dialogContent.querySelectorAll('[data-i18n-attr]'); 167 elementsWithAttr.forEach(element => { 168 const attrName = element.getAttribute('data-i18n-attr'); 169 const key = element.getAttribute('data-i18n'); 170 if (attrName && key) { 171 const translation = this.facade.t(key); 172 if (translation && translation !== key) { 173 element.setAttribute(attrName, translation); 174 } 175 } 176 }); 177 } 178 179 _renderContactForm(property) { 180 return ` 181 <div class="contacto__form__card contacto__form__card--dialog"> 182 ${this._renderContactInfo()} 183 ${this._renderForm(property)} 184 </div> 185 `; 186 } 187 188 _renderContactInfo() { 189 const lang = this.facade.getCurrentLanguage(); 190 const address = this.contactInfo.address[lang] || this.contactInfo.address.es; 191 const email = this.currentProperty?.email || this.contactInfo.email; 192 193 return ` 194 <div class="contacto__form__card__text"> 195 <span>${this.facade.t('contact.title')}</span> 196 197 <p class="first"> 198 <b>${this.facade.t('contact.address')}</b> 199 <br>
199${address.replace(/\\n/g, '<br>')}</br> 200 </p> 201 202 <p> 203 <b>${this.facade.t('contact.phone')}</b> 204 <br>${this.contactInfo.phone}<br> 205 ${email}<br> 206 </p> 207 208 <div class="cfcSocial"> 209 <a href="${this.contactInfo.social.linkedin}" target="_blank"> 210 <i class="fa-brands fa-linkedin fa-lg" style="color: #0096a9;"></i> 211 </a> 212 <a href="${this.contactInfo.social.instagram}" target="_blank"> 213 <i class="fa-brands fa-instagram fa-lg" style="color: #0096a9;"></i> 214 </a> 215 <a href="${this.contactInfo.social.facebook}" target="_blank"> 216 <i class="fa-brands fa-facebook fa-lg" style="color: #0096a9;"></i> 217 </a> 218 <a href="${this.contactInfo.social.twitter}" target="_blank"> 219 <i class="fa-brands fa-x-twitter fa-lg" style="color: #0096a9;"></i> 220 </a> 221 <a href="${this.contactInfo.social.youtube}" target="_blank"> 222 <i class="fa-brands fa-youtube fa-lg" style="color: #0096a9;"></i> 223 </a> 224 </div> 225 </div> 226 `; 227 } 228 229 230 _renderForm(property) { 231 const allProperties = this._getSortedProperties(); 232 const isDisabled = property !== null; 233 const propertyOptions = this._renderPropertyOptions(allProperties, property); 234 235 return ` 236 <form action="${this.salesforceConfig.endpoint}" method="POST" class="portfolioContactForm"> 237 <input type="hidden" name="oid" value="${this.salesforceConfig.oid}"> 238 <input type="hidden" name="retURL" value="${this.salesforceConfig.returnUrl}"> 239 240 <div class="contacto__form__card__form"> 241 <div class="contacto__form__card__form__container contacto__form__card__form__container--nombre"> 242 <label for="first_name">*${this.facade.t('contact.form.firstName')}</label> 243 <input id="first_name" type="text" tabindex="0" name="first_name" required> 244 </div> 245 246 <div class="contacto__form__card__form__container contacto__form__card__form__container--compania"> 247 <label for="company">*${this.facade.t('contact.form.company')}</label> 248 <input id="company" type="text" tabindex="1" name="company" required> 249 </div> 250 </div> 251 252 <div class="contacto__form__card__form"> 253 <div class="contacto__form__card__form__container contacto__form__card__form__container--apellido"> 254 <label for="last_name">*${this.facade.t('contact.form.lastName')}</label> 255 <input id="last_name" type="text" tabindex="0" name="last_name" required> 256 </div> 257 258 <div class="contacto__form__card__form__container contacto__form__card__form__container--email"> 259 <label for="phone">*${this.facade.t('contact.form.phone')}</label> 260 <input id="phone" type="tel" 261 pattern="^(\\\\d{10}|\\\\(\\\\d{3}\\\\)\\\\s?\\\\d{3}-\\\\d{4}|(\\\\+\\\\d{2}\\\\s?)?(\\\\d{2}\\\\s?)?\\\\d{4}\\\\s?\\\\d{4})$" 262 tabindex="3" 263 name="phone" 264 title="${this.facade.t('contact.form.phonePattern')}" 265 placeholder="1234512345" 266 required> 267 </div> 268 </div> 269 270 <div class="contacto__form__card__form contacto__form__card__form--portfolio"> 271 <div class="contacto__form__card__form--portfolio--email"> 272 <label for="email">*${this.facade.t('contact.form.email')}</label> 273 <input id="email" type="email" tabindex="2" name="email" required> 274 </div> 275 276 <div class="contacto__form__card__form--portfolio--personalidad"> 277 <label for="00N5A00000HQFp4">*${this.facade.t('contact.form.legalEntity')}</label> 278 <select id="00N5A00000HQFp4" name="00N5A00000HQFp4" tabindex="4" required> 279 <option disabled selected value="">${this.facade.t('contact.form.selectLegalEntity')}</option> 280 <option value="Persona FÃsica">${this.facade.t('contact.form.naturalPerson')}</option> 281 <option value="Persona Moral">${this.facade.t('contact.form.legalPerson')}</option> 282 </select> 283 </div> 284
285 <div class="contacto__form__card__form--portfolio--comentarios"> 286 <label for="00Ni000000Dwzjf">*${this.facade.t('contact.form.comments')}</label> 287 <textarea id="00Ni000000Dwzjf" tabindex="6" name="00Ni000000Dwzjf" cols="21" placeholder=""></textarea> 288 </div> 289 </div> 290 291 <div class="contacto__form__card__form contacto__form__card__form--portfolio--propiedad"> 292 <div class="contacto__form__card__form--portfolio--personalidad"> 293 <label for="00Ni000000Ekl2M">*${this.facade.t('contact.form.property')}</label> 294 <select id="00Ni000000Ekl2M" name="00Ni000000Ekl2M" tabindex="5" required ${isDisabled ? 'disabled' : ''}> 295 ${propertyOptions} 296 </select> 297 </div> 298 299 <div class="contacto__form__card__form contacto__form__card__form--submit"> 300 <input type="hidden" id="external" name="external" value="1" /> 301 <label for=""> 302 <b style="font-size: 30px; line-height: 10px;">* </b>${this.facade.t('contact.form.requiredFields')} 303 </label> 304 <input type="submit" name="submit" value="${this.facade.t('contact.form.submit')}" id="submitButton"> 305 </div> 306 </div> 307 </form> 308 `; 309 } 310 311 _getSortedProperties() { 312 const properties = this.dataService.getProperties(); 313 314 return properties.sort((a, b) => { 315 const stateA = (a.state || '').toUpperCase(); 316 const stateB = (b.state || '').toUpperCase(); 317 318 if (stateA < stateB) return -1; 319 if (stateA > stateB) return 1; 320 321 const nameA = (a.name || '').toUpperCase(); 322 const nameB = (b.name || '').toUpperCase(); 323 324 if (nameA < nameB) return -1; 325 if (nameA > nameB) return 1; 326 327 return 0; 328 }); 329 } 330 331 _renderPropertyOptions(properties, selectedProperty) { 332 let options = ''; 333 334 if (!selectedProperty) { 335 options += `<option disabled selected value="">${this.facade.t('contact.form.property.placeholder')}</option>`; 336 } 337 338 properties.forEach(prop => { 339 const isSelected = selectedProperty && prop.id === selectedProperty.id; 340 const displayText = `${prop.state} - ${prop.name}`; 341 342 options += `<option value="${prop.id}" ${isSelected ? 'selected' : ''}>${displayText}</option>`; 343 }); 344 345 return options; 346 } 347 348 _setupFormEventListeners() { 349 const form = document.querySelector('.portfolioContactForm'); 350 351 if (form) { 352 form.addEventListener('submit', (e) => { 353 this._handleFormSubmit(e); 354 }); 355 } 356 } 357 358 _handleFormSubmit(event) { 359 try { 360 this.logger.info('[PropertyContactFormComponent] Form submitted'); 361 362 const form = event.target; 363 if (!form.checkValidity()) { 364 this.logger.warn('[PropertyContactFormComponent] Form validation failed'); 365 return; 366 } 367 368 this.logger.info('[PropertyContactFormComponent] â Form submitted to Salesforce'); 369 370 } catch (error) { 371 this.logger.error('[PropertyContactFormComponent] Error submitting form:', error); 372 } 373 } 374 375 hide() { 376 if (this.dialog) { 377 this.dialog.hide(); 378 this.currentProperty = null; 379 } 380 } 381 382 destroy() { 383 if (this.dialog) { 384 try { 385 this.dialog.destroy(); 386 this.dialog = null; 387 this.isInitialized = false; 388 this.logger.info('[PropertyContactFormComponent] Dialog destroyed'); 389 } catch (error) { 390 this.logger.error('[PropertyContactFormComponent] Error destroying dialog:', error); 391 } 392 } 393 } 394 395 isReady() { 396 return this.isInitialized && this.dialog !== null; 397 } 398} 399 400export default PropertyContactFormComponent;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.