Download contacts-editor-controller.js from Althnayi/fc: direct link, hf CLI and curl.
- Browser
- Download file 20.1 kB
-
https://huggingface.co/spaces/Althnayi/fc/resolve/main/contacts-editor-controller.js
- Command line
-
hf download hf://spaces/Althnayi/fc/contacts-editor-controller.js
-
curl -L -o contacts-editor-controller.js https://huggingface.co/spaces/Althnayi/fc/resolve/main/contacts-editor-controller.js
20.1 kB
| /** | |
| * @fileoverview Contact Record Editor & Modal Controller | |
| * @module controllers/contacts-editor-controller | |
| * @description وحدة التحكم في نافذة إنشاء وتعديل العملاء، إدارة الأرقام المتعددة، والحقول المخصصة. | |
| */ | |
| (global => { | |
| 'use strict'; | |
| /** | |
| * تنظيف وتنسيق رقم الهاتف عبر المحرك المعتمد | |
| * @param {string} phone | |
| * @returns {string} | |
| */ | |
| function normalizePhone(phone) { | |
| if (global.FritreeContactsResolver && typeof global.FritreeContactsResolver.normalize === 'function') { | |
| return global.FritreeContactsResolver.normalize(phone); | |
| } | |
| return phone ? String(phone).replace(/[^0-9+]/g, '') : ''; | |
| } | |
| /** | |
| * وحدة التحكم في محرر جهات الاتصال | |
| */ | |
| const ContactEditorController = { | |
| /** | |
| * إضافة سطر إدخال رقم هاتف ديناميكي مع تحديد نوع الرقم | |
| * @param {string} phoneVal | |
| * @param {string} phoneType ('mobile' | 'whatsapp' | 'work' | 'home' | 'other') | |
| */ | |
| addPhoneInputRow: function(phoneVal = '', phoneType = 'mobile') { | |
| const container = document.getElementById('pe2oiv9nvs'); | |
| if (!container) return; | |
| const row = document.createElement('div'); | |
| row.className = 'qp7emstoll'; | |
| row.style.cssText = 'display:flex; gap:6px; align-items:center; margin-bottom:6px;'; | |
| const typeSelect = document.createElement('select'); | |
| typeSelect.className = 'xy26ymifmf f0jy0nh7wu'; | |
| typeSelect.style.cssText = 'width:130px; padding:8px; font-size:11px; border-radius:6px; background:#fff; font-weight:bold;'; | |
| typeSelect.innerHTML = ` | |
| <option value="mobile" ${phoneType === 'mobile' ? 'selected' : ''}>جوال رئيسي</option> | |
| <option value="whatsapp" ${phoneType === 'whatsapp' ? 'selected' : ''}>واتساب مباشر</option> | |
| <option value="work" ${phoneType === 'work' ? 'selected' : ''}>موبايل عمل</option> | |
| <option value="home" ${phoneType === 'home' ? 'selected' : ''}>تليفون منزل</option> | |
| <option value="other" ${phoneType === 'other' ? 'selected' : ''}>رقم تاني</option> | |
| `; | |
| const input = document.createElement('input'); | |
| input.type = 'text'; | |
| input.className = 'xy26ymifmf kljqnbvx11'; | |
| input.placeholder = 'مثال: +201069794840'; | |
| input.value = phoneVal; | |
| input.style.cssText = 'flex:1; padding:8px 12px; direction:ltr; text-align:left;'; | |
| const removeBtn = document.createElement('button'); | |
| removeBtn.type = 'button'; | |
| removeBtn.className = 'btn-danger'; | |
| removeBtn.style.cssText = 'padding:6px 10px; font-size:11px; height:36px; border-radius:6px; background:#fee2e2; color:#ef4444; border:1px solid #fca5a5;'; | |
| removeBtn.innerHTML = '<i class="fa-solid fa-xmark" style="margin:0;"></i> '; | |
| removeBtn.title = 'حذف الرقم ده'; | |
| removeBtn.onclick = () => { | |
| if (container.querySelectorAll('.qp7emstoll').length > 1) { | |
| row.remove(); | |
| } else { | |
| input.value = ''; | |
| } | |
| }; | |
| row.appendChild(typeSelect); | |
| row.appendChild(input); | |
| row.appendChild(removeBtn); | |
| container.appendChild(row); | |
| // إضافة سطر جديد تلقائياً عند البدء في كتابة رقم بالسطر الأخير | |
| input.addEventListener('input', () => { | |
| const rows = container.querySelectorAll('.kljqnbvx11'); | |
| const lastInput = rows[rows.length - 1]; | |
| if (input === lastInput && input.value.trim().length > 0) { | |
| this.addPhoneInputRow('', 'mobile'); | |
| } | |
| const val = input.value.trim(); | |
| if (val.length >= 8 && global.FritreeContacts) { | |
| const match = global.FritreeContacts.lookup(val); | |
| if (match) { | |
| this.populateFormFields(match); | |
| } | |
| } | |
| }); | |
| }, | |
| /** | |
| * إضافة سطر حقل مخصص جديد | |
| * @param {string} key | |
| * @param {string} val | |
| */ | |
| addCustomFieldRow: function(key = '', val = '') { | |
| const container = document.getElementById('du5h7y5kct'); | |
| if (!container) return; | |
| const row = document.createElement('div'); | |
| row.className = 'tupbvypcqw'; | |
| row.style.cssText = 'display:flex; gap:6px; align-items:center; margin-bottom:6px;'; | |
| const keyInput = document.createElement('input'); | |
| keyInput.type = 'text'; | |
| keyInput.className = 'xy26ymifmf h83d317lfm'; | |
| keyInput.placeholder = 'اسم الحقل (زي: الميزانية)'; | |
| keyInput.value = key; | |
| keyInput.style.cssText = 'flex:1; padding:6px 10px; font-size:11px;'; | |
| const valInput = document.createElement('input'); | |
| valInput.type = 'text'; | |
| valInput.className = 'xy26ymifmf cv3mq1mhux'; | |
| valInput.placeholder = 'القيمة (زي: 50,000 ج.م)'; | |
| valInput.value = val; | |
| valInput.style.cssText = 'flex:1.5; padding:6px 10px; font-size:11px;'; | |
| const removeBtn = document.createElement('button'); | |
| removeBtn.type = 'button'; | |
| removeBtn.className = 'btn-danger'; | |
| removeBtn.style.cssText = 'padding:4px 8px; font-size:10px; height:32px; border:2px solid var(--border);'; | |
| removeBtn.innerHTML = '<i class="fa-solid fa-xmark"></i> '; | |
| removeBtn.onclick = () => row.remove(); | |
| row.appendChild(keyInput); | |
| row.appendChild(valInput); | |
| row.appendChild(removeBtn); | |
| container.appendChild(row); | |
| }, | |
| /** | |
| * ملء حقول النموذج ببيانات العميل المطابق | |
| * @param {Object} match | |
| */ | |
| populateFormFields: function(match) { | |
| document.getElementById('qfv7jp577g').value = match.id || ''; | |
| document.getElementById('sdd0e5bwjh').value = match.name || ''; | |
| document.getElementById('n8v43cmfzp').value = match.email || ''; | |
| document.getElementById('bluyqoyzyu').value = match.company || ''; | |
| document.getElementById('hns86xlu9u').value = match.jobTitle || ''; | |
| document.getElementById('tai1zux829').value = match.department || ''; | |
| document.getElementById('foockjmk6s').value = match.lifecycle || 'lead'; | |
| document.getElementById('g1oufrwen8').value = match.priority || 'medium'; | |
| document.getElementById('x1cb3ek74e').value = match.source || 'direct'; | |
| document.getElementById('s2admrtim1').value = match.nextFollowupDate ? match.nextFollowupDate.slice(0, 10) : ''; | |
| document.getElementById('ftiovdh9zk').value = match.preferredChannel || 'whatsapp'; | |
| document.getElementById('m6m8pmqk2g').value = match.preferredTime || 'any'; | |
| document.getElementById('kgb8k4l7x8').value = match.address || ''; | |
| document.getElementById('c4aemm27ll').value = match.website || ''; | |
| document.getElementById('abqk9m5uxg').value = match.tags ? match.tags.join(', ') : ''; | |
| document.getElementById('ab9w3psogf').value = match.notes || ''; | |
| document.getElementById('baysuwhq61').checked = !!match.blacklisted; | |
| if (match.customFields && typeof match.customFields === 'object') { | |
| const customContainer = document.getElementById('du5h7y5kct'); | |
| if (customContainer) { | |
| customContainer.innerHTML = ''; | |
| Object.entries(match.customFields).forEach(([k, v]) => { | |
| this.addCustomFieldRow(k, v); | |
| }); | |
| } | |
| } | |
| const title = document.getElementById('kvcvcii0b6'); | |
| if (title) title.textContent = `تعديل بيانات العميل: ${match.name}`; | |
| const badge = document.getElementById('o497sju1mo'); | |
| if (badge) { | |
| badge.style.display = 'inline-flex'; | |
| badge.innerHTML = `تم جلب بيانات العميل: ${match.name}`; | |
| } | |
| ['sdd0e5bwjh', 'n8v43cmfzp', 'bluyqoyzyu', 'hns86xlu9u', 'abqk9m5uxg', 'ab9w3psogf'].forEach(id => { | |
| const el = document.getElementById(id); | |
| if (el) { | |
| el.style.backgroundColor = '#dcfce7'; | |
| setTimeout(() => { el.style.backgroundColor = ''; }, 1800); | |
| } | |
| }); | |
| }, | |
| /** | |
| * فتح نافذة تحرير العميل (جديد أو تعديل) | |
| * @param {string|null} contactId | |
| */ | |
| openModal: function(contactId = null) { | |
| const modal = document.getElementById('g2vl6bip8z'); | |
| const title = document.getElementById('kvcvcii0b6'); | |
| const phoneContainer = document.getElementById('pe2oiv9nvs'); | |
| const customContainer = document.getElementById('du5h7y5kct'); | |
| const badge = document.getElementById('o497sju1mo'); | |
| if (!modal || !phoneContainer) return; | |
| phoneContainer.innerHTML = ''; | |
| if (customContainer) customContainer.innerHTML = ''; | |
| if (badge) badge.style.display = 'none'; | |
| const contacts = global.FritreeContacts ? global.FritreeContacts.getContacts() : []; | |
| if (contactId) { | |
| const c = contacts.find(x => x.id === contactId); | |
| if (!c) return; | |
| this.populateFormFields(c); | |
| this.addPhoneInputRow(c.phone, 'mobile'); | |
| if (c.altPhones && Array.isArray(c.altPhones)) { | |
| c.altPhones.forEach(ph => { | |
| const pVal = typeof ph === 'object' ? ph.phone : ph; | |
| const pType = typeof ph === 'object' ? ph.type : 'work'; | |
| this.addPhoneInputRow(pVal, pType); | |
| }); | |
| } | |
| this.addPhoneInputRow('', 'mobile'); | |
| } else { | |
| document.getElementById('qfv7jp577g').value = ''; | |
| document.getElementById('sdd0e5bwjh').value = ''; | |
| document.getElementById('n8v43cmfzp').value = ''; | |
| document.getElementById('bluyqoyzyu').value = ''; | |
| document.getElementById('hns86xlu9u').value = ''; | |
| document.getElementById('tai1zux829').value = ''; | |
| document.getElementById('foockjmk6s').value = 'lead'; | |
| document.getElementById('x1cb3ek74e').value = 'direct'; | |
| document.getElementById('g1oufrwen8').value = 'medium'; | |
| document.getElementById('s2admrtim1').value = ''; | |
| document.getElementById('ftiovdh9zk').value = 'whatsapp'; | |
| document.getElementById('m6m8pmqk2g').value = 'any'; | |
| document.getElementById('kgb8k4l7x8').value = ''; | |
| document.getElementById('c4aemm27ll').value = ''; | |
| document.getElementById('abqk9m5uxg').value = ''; | |
| document.getElementById('ab9w3psogf').value = ''; | |
| document.getElementById('baysuwhq61').checked = false; | |
| if (title) title.textContent = 'إضافة عميل جديد بكامل البيانات'; | |
| this.addPhoneInputRow('', 'mobile'); | |
| } | |
| modal.style.display = 'flex'; | |
| }, | |
| /** | |
| * حفظ بيانات العميل في الخزنة المشفرة وتحديث التقويم | |
| */ | |
| saveContactSubmit: async function() { | |
| const core = global.FritreeContacts; | |
| if (!core || !core.state) return; | |
| const state = core.state; | |
| const id = document.getElementById('qfv7jp577g').value; | |
| const name = document.getElementById('sdd0e5bwjh').value.trim(); | |
| const email = document.getElementById('n8v43cmfzp').value.trim(); | |
| const company = document.getElementById('bluyqoyzyu').value.trim(); | |
| const jobTitle = document.getElementById('hns86xlu9u').value.trim(); | |
| const department = document.getElementById('tai1zux829').value.trim(); | |
| const lifecycle = document.getElementById('foockjmk6s').value; | |
| const source = document.getElementById('x1cb3ek74e').value; | |
| const priority = document.getElementById('g1oufrwen8').value; | |
| const nextFollowupDate = document.getElementById('s2admrtim1').value; | |
| const preferredChannel = document.getElementById('ftiovdh9zk').value; | |
| const preferredTime = document.getElementById('m6m8pmqk2g').value; | |
| const address = document.getElementById('kgb8k4l7x8').value.trim(); | |
| const website = document.getElementById('c4aemm27ll').value.trim(); | |
| const tagsStr = document.getElementById('abqk9m5uxg').value; | |
| const notes = document.getElementById('ab9w3psogf').value.trim(); | |
| const blacklisted = document.getElementById('baysuwhq61').checked; | |
| const phoneRows = document.querySelectorAll('.qp7emstoll'); | |
| const phoneList = []; | |
| phoneRows.forEach(row => { | |
| const f = row.querySelector('.kljqnbvx11'); | |
| const t = row.querySelector('.f0jy0nh7wu'); | |
| if (f && f.value) { | |
| const norm = normalizePhone(f.value); | |
| if (norm && norm.length >= 7 && !phoneList.some(p => p.phone === norm)) { | |
| phoneList.push({ phone: norm, type: t ? t.value : 'mobile' }); | |
| } | |
| } | |
| }); | |
| if (phoneList.length === 0) { | |
| alert('اكتب رقم موبايل صحيح واحد على الأقل بالمفتاح الدولي!'); | |
| return; | |
| } | |
| const customFields = {}; | |
| const customRows = document.querySelectorAll('.tupbvypcqw'); | |
| customRows.forEach(cr => { | |
| const kEl = cr.querySelector('.h83d317lfm'); | |
| const vEl = cr.querySelector('.cv3mq1mhux'); | |
| if (kEl && kEl.value.trim()) { | |
| customFields[kEl.value.trim()] = vEl ? vEl.value.trim() : ''; | |
| } | |
| }); | |
| const mainPhone = phoneList[0].phone; | |
| const altPhones = phoneList.slice(1); | |
| const tags = tagsStr.split(',').map(t => t.trim()).filter(t => t); | |
| if (id) { | |
| const idx = state.contacts.findIndex(c => c.id === id); | |
| if (idx !== -1) { | |
| state.contacts[idx] = { | |
| ...state.contacts[idx], | |
| name: name || 'المستلم', | |
| phone: mainPhone, | |
| altPhones: altPhones, | |
| email: email, | |
| company: company, | |
| jobTitle: jobTitle, | |
| department: department, | |
| lifecycle: lifecycle, | |
| source: source, | |
| priority: priority, | |
| nextFollowupDate: nextFollowupDate, | |
| preferredChannel: preferredChannel, | |
| preferredTime: preferredTime, | |
| address: address, | |
| website: website, | |
| customFields: customFields, | |
| tags: tags, | |
| notes: notes, | |
| blacklisted: blacklisted, | |
| updatedAt: new Date().toISOString() | |
| }; | |
| } | |
| } else { | |
| const newContact = { | |
| id: 'cnt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4), | |
| name: name || 'المستلم', | |
| phone: mainPhone, | |
| altPhones: altPhones, | |
| email: email, | |
| company: company, | |
| jobTitle: jobTitle, | |
| department: department, | |
| lifecycle: lifecycle, | |
| source: source, | |
| priority: priority, | |
| nextFollowupDate: nextFollowupDate, | |
| preferredChannel: preferredChannel, | |
| preferredTime: preferredTime, | |
| address: address, | |
| website: website, | |
| customFields: customFields, | |
| tags: tags, | |
| notes: notes, | |
| blacklisted: blacklisted, | |
| createdAt: new Date().toISOString() | |
| }; | |
| state.contacts.unshift(newContact); | |
| } | |
| await core.save(); | |
| core.refreshUI(); | |
| document.getElementById('g2vl6bip8z').style.display = 'none'; | |
| // تسجيل المتابعة تلقائياً في التقويم الذكي | |
| if (global.FritreeCalendar && typeof global.FritreeCalendar.logActivity === 'function') { | |
| global.FritreeCalendar.logActivity({ | |
| title: id ? `[تحديث عميل] ${name || 'عميل'}` : `[إضافة عميل جديد] ${name || 'عميل'} (${mainPhone})`, | |
| type: 'followup', | |
| category: 'followup', | |
| notes: `الهاتف: ${mainPhone} | الشركة: ${company || '-'} | المسمى: ${jobTitle || '-'} | تاريخ المتابعة: ${nextFollowupDate || 'غير محدد'}`, | |
| contactId: id || '', | |
| contactName: name, | |
| priority: priority === 'urgent' ? 'urgent' : (priority === 'high' ? 'high' : 'medium'), | |
| timestamp: nextFollowupDate ? new Date(nextFollowupDate).toISOString() : new Date().toISOString() | |
| }); | |
| } | |
| if (typeof window.addLog === 'function') { | |
| window.addLog(`تم بنجاح حفظ بيانات العميل: ${name || 'جديد'} (${mainPhone})`, 'success'); | |
| } | |
| }, | |
| /** | |
| * ربط أحداث أزرار نافذة التحرير | |
| */ | |
| bindEvents: function() { | |
| const btnAddModal = document.getElementById('lndjiz92pl'); | |
| const btnCloseEditor = document.getElementById('z9jjbpxjm0'); | |
| const btnSaveContact = document.getElementById('du3bb5vayt'); | |
| const btnAddCustomField = document.getElementById('duv92vxgty'); | |
| if (btnAddModal) btnAddModal.onclick = () => this.openModal(null); | |
| if (btnCloseEditor) btnCloseEditor.onclick = () => document.getElementById('g2vl6bip8z').style.display = 'none'; | |
| if (btnSaveContact) btnSaveContact.onclick = () => this.saveContactSubmit(); | |
| if (btnAddCustomField) btnAddCustomField.onclick = () => this.addCustomFieldRow(); | |
| } | |
| }; | |
| /** | |
| * تصدير وحدة تحكم محرر جهات الاتصال | |
| */ | |
| global.FritreeContactsEditor = ContactEditorController; | |
| })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this); |