PageSourceSearch

https://planigrupo.com.mx/pages/portafolio/js/components/PropertyContactFormComponent.js

js planigrupo.com.mx collected 2026-09-26 01:50:00 UTC 14,229 bytes, 400 lines download raw bytes

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.