| |
| |
| |
| |
|
|
|
|
| (global => {
|
| 'use strict';
|
|
|
| |
| |
|
|
| const ContactsState = {
|
| contacts: [],
|
| currentlyDisplayedContacts: [],
|
| selectedContactIds: new Set(),
|
| searchQuery: '',
|
| activeFilterTag: 'all',
|
| filterLifecycle: 'all',
|
| filterBlacklist: 'all',
|
| filterHasEmail: 'all',
|
| filterDateFrom: '',
|
| filterDateTo: '',
|
| sortColumn: 'createdAt',
|
| sortDirection: 'desc',
|
| viewMode: 'table',
|
| isLoaded: false
|
| };
|
|
|
| |
| |
| |
| |
|
|
| function getFollowupDateStyle(followupDateStr) {
|
| if (!followupDateStr) return { text: '-', color: 'var(--text-muted)', bg: 'var(--bg-subtle)', badge: 'غير مجدولة' };
|
|
|
| const followupDate = new Date(followupDateStr);
|
| if (isNaN(followupDate.getTime())) return { text: '-', color: 'var(--text-muted)', bg: 'var(--bg-subtle)', badge: 'غير مجدولة' };
|
|
|
| const now = new Date();
|
| const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
| const targetDay = new Date(followupDate.getFullYear(), followupDate.getMonth(), followupDate.getDate());
|
|
|
| const diffTime = targetDay.getTime() - today.getTime();
|
| const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
| const dateFormatted = targetDay.toLocaleDateString('en-US');
|
|
|
| if (diffDays <= 0) {
|
| return {
|
| text: dateFormatted,
|
| color: 'var(--danger)',
|
| bg: 'var(--danger-light)',
|
| badge: diffDays === 0 ? 'المتابعة اليوم!' : 'متأخرة'
|
| };
|
| } else if (diffDays <= 7) {
|
| return {
|
| text: dateFormatted,
|
| color: 'var(--warning-hover)',
|
| bg: 'var(--warning-light)',
|
| badge: `خلال ${diffDays} أيام`
|
| };
|
| } else {
|
| return {
|
| text: dateFormatted,
|
| color: 'var(--success-hover)',
|
| bg: 'var(--success-light)',
|
| badge: `بعد ${diffDays} يوم`
|
| };
|
| }
|
| }
|
|
|
| |
| |
| |
| |
|
|
| function getLifecycleBadgeHTML(stage) {
|
| const stages = {
|
| 'lead': { label: 'عميل محتمل', color: '#0284c7', bg: 'rgba(2, 132, 199, 0.12)', border: 'rgba(2, 132, 199, 0.3)' },
|
| 'qualified': { label: 'عميل مؤهل', color: '#d97706', bg: 'rgba(217, 119, 6, 0.12)', border: 'rgba(217, 119, 6, 0.3)' },
|
| 'active': { label: 'عميل حالي', color: '#16a34a', bg: 'rgba(22, 163, 74, 0.12)', border: 'rgba(22, 163, 74, 0.3)' },
|
| 'vip': { label: 'عميل متميز VIP', color: '#9333ea', bg: 'rgba(147, 51, 234, 0.12)', border: 'rgba(147, 51, 234, 0.3)' },
|
| 'won': { label: 'صفقة مكتملة', color: '#059669', bg: 'rgba(5, 150, 105, 0.12)', border: 'rgba(5, 150, 105, 0.3)' },
|
| 'lost': { label: 'غير مهتم', color: '#dc2626', bg: 'rgba(220, 38, 38, 0.12)', border: 'rgba(220, 38, 38, 0.3)' }
|
| };
|
| const conf = stages[stage] || stages['lead'];
|
| return `<span style="background:${conf.bg}; color:${conf.color}; border:1px solid ${conf.border}; font-size:9.5px; font-weight:800; padding:2px 8px; border-radius:var(--radius-pill); display:inline-flex; align-items:center; gap:4px; white-space:nowrap;">${conf.label}</span>`;
|
| }
|
|
|
| |
| |
|
|
| const ContactsRendererEngine = {
|
| |
| |
|
|
| updateSelectedCountBadge: function() {
|
| const badge = document.getElementById('qh64ktmllu');
|
| if (badge) {
|
| badge.textContent = `المحدد حالياً: ${ContactsState.selectedContactIds.size.toLocaleString('en-US')}`;
|
| }
|
| },
|
|
|
| |
| |
|
|
| updateSelectAllCheckboxState: function() {
|
| const selectAllChk = document.getElementById('ubt2zni00m');
|
| const rowCheckboxes = document.querySelectorAll('.i07h98mtfb');
|
| if (!selectAllChk || rowCheckboxes.length === 0) return;
|
| const allChecked = Array.from(rowCheckboxes).every(cb => cb.checked);
|
| selectAllChk.checked = allChecked;
|
| },
|
|
|
| |
| |
|
|
| renderTable: function() {
|
| const state = ContactsState;
|
| const displayContainer = document.getElementById('tu54lhiegr');
|
| if (!displayContainer) return;
|
|
|
| const q = (state.searchQuery || '').toLowerCase();
|
| const tagF = state.activeFilterTag;
|
| const lcF = state.filterLifecycle;
|
| const blF = state.filterBlacklist;
|
| const emailF = state.filterHasEmail;
|
| const dateFrom = state.filterDateFrom;
|
| const dateTo = state.filterDateTo;
|
|
|
|
|
| let filtered = state.contacts.filter(c => {
|
| const nameStr = c.name || '';
|
| const phoneStr = c.phone || '';
|
| const emailStr = c.email || '';
|
| const compStr = c.company || '';
|
| const jobStr = c.jobTitle || '';
|
| const notesStr = c.notes || '';
|
|
|
| const matchQuery = !q || nameStr.toLowerCase().includes(q) ||
|
| phoneStr.includes(q) ||
|
| (c.altPhones && c.altPhones.some(ap => (typeof ap === 'object' ? ap.phone : ap).includes(q))) ||
|
| emailStr.toLowerCase().includes(q) ||
|
| compStr.toLowerCase().includes(q) ||
|
| jobStr.toLowerCase().includes(q) ||
|
| notesStr.toLowerCase().includes(q);
|
|
|
| let matchTag = true;
|
| if (tagF !== 'all') matchTag = c.tags && c.tags.includes(tagF);
|
|
|
| let matchLC = true;
|
| if (lcF !== 'all') matchLC = (c.lifecycle || 'lead') === lcF;
|
|
|
| let matchBL = true;
|
| if (blF === 'clean') matchBL = !c.blacklisted;
|
| else if (blF === 'blacklisted') matchBL = !!c.blacklisted;
|
|
|
| let matchEmail = true;
|
| if (emailF === 'yes') matchEmail = !!c.email;
|
| else if (emailF === 'no') matchEmail = !c.email;
|
|
|
| let matchDate = true;
|
| if (dateFrom && c.createdAt) matchDate = matchDate && new Date(c.createdAt) >= new Date(dateFrom);
|
| if (dateTo && c.createdAt) matchDate = matchDate && new Date(c.createdAt) <= new Date(dateTo + 'T23:59:59');
|
|
|
| return matchQuery && matchTag && matchLC && matchBL && matchEmail && matchDate;
|
| });
|
|
|
|
|
| const col = state.sortColumn;
|
| const dir = state.sortDirection === 'asc' ? 1 : -1;
|
|
|
| filtered.sort((a, b) => {
|
| if (col === 'name') {
|
| return dir * (a.name || '').localeCompare(b.name || '', 'ar');
|
| } else if (col === 'phone') {
|
| return dir * (a.phone || '').localeCompare(b.phone || '');
|
| } else if (col === 'lifecycle') {
|
| return dir * (a.lifecycle || 'lead').localeCompare(b.lifecycle || 'lead');
|
| } else if (col === 'tags') {
|
| const tagsA = (a.tags || []).join(', ');
|
| const tagsB = (b.tags || []).join(', ');
|
| return dir * tagsA.localeCompare(tagsB, 'ar');
|
| } else if (col === 'followup') {
|
| const dateA = a.nextFollowupDate ? new Date(a.nextFollowupDate).getTime() : 0;
|
| const dateB = b.nextFollowupDate ? new Date(b.nextFollowupDate).getTime() : 0;
|
| return dir * (dateA - dateB);
|
| } else {
|
| const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
| const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
| return dir * (dateA - dateB);
|
| }
|
| });
|
|
|
| state.currentlyDisplayedContacts = filtered;
|
|
|
|
|
| const totalEl = document.getElementById('x7y3ihnuz6');
|
| const vipEl = document.getElementById('hl58n1vsdz');
|
| const tagsEl = document.getElementById('z9imk5h96y');
|
| const blacklistedEl = document.getElementById('xvhp7umte1');
|
|
|
| if (totalEl) totalEl.textContent = state.contacts.length.toLocaleString('en-US');
|
| if (vipEl) vipEl.textContent = state.contacts.filter(c => c.lifecycle === 'vip' || c.lifecycle === 'won').length.toLocaleString('en-US');
|
|
|
| const allTags = new Set();
|
| state.contacts.forEach(c => (c.tags || []).forEach(t => allTags.add(t)));
|
| if (tagsEl) tagsEl.textContent = allTags.size.toLocaleString('en-US');
|
| if (blacklistedEl) blacklistedEl.textContent = state.contacts.filter(c => c.blacklisted).length.toLocaleString('en-US');
|
|
|
| const rangeInfoText = document.getElementById('wsbcxjitau');
|
| if (rangeInfoText) {
|
| rangeInfoText.textContent = `المعروض حالياً: ${filtered.length.toLocaleString('en-US')} جهة اتصال (من 1 إلى ${filtered.length.toLocaleString('en-US')})`;
|
| }
|
|
|
| const selectTag = document.getElementById('rk1gpbb0ma');
|
| if (selectTag && selectTag.options.length <= 1) {
|
| selectTag.innerHTML = '<option value="all">كل التصنيفات والوسوم</option>';
|
| allTags.forEach(t => {
|
| const opt = document.createElement('option');
|
| opt.value = t;
|
| opt.textContent = `وسم: ${t}`;
|
| selectTag.appendChild(opt);
|
| });
|
| selectTag.value = tagF;
|
| }
|
|
|
| this.updateSelectedCountBadge();
|
|
|
| if (filtered.length === 0) {
|
| displayContainer.innerHTML = `
|
| <div style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius-lg); padding:35px 20px; text-align:center; color:var(--text-muted);">
|
| <i class="fa-solid fa-folder-open" style="font-size:28px; color:var(--primary); display:block; margin-bottom:8px;"></i>
|
| <span style="font-size:12px; font-weight:600;">لا توجد أي جهات اتصال مطابقة لشروط البحث والتصفية حالياً.</span>
|
| </div>
|
| `;
|
| return;
|
| }
|
|
|
|
|
| if (state.viewMode === 'cards') {
|
| displayContainer.innerHTML = '';
|
| const cardsGrid = document.createElement('div');
|
| cardsGrid.style.cssText = 'display:grid; grid-template-columns:repeat(auto-fill, minmax(clamp(250px, 24vw, 320px), 1fr)); gap:12px; max-height:550px; overflow-y:auto; padding:2px; box-sizing:border-box;';
|
|
|
| filtered.forEach((c, indexIdx) => {
|
| const itemNumber = indexIdx + 1;
|
| c._displayIndex = itemNumber;
|
| const isSelected = state.selectedContactIds.has(c.id);
|
| const fuStyle = getFollowupDateStyle(c.nextFollowupDate);
|
|
|
| const card = document.createElement('div');
|
| card.style.cssText = `background:var(--bg-card); border:1px solid ${isSelected ? 'var(--primary)' : (c.blacklisted ? 'var(--danger-border)' : 'var(--border)')}; border-radius:var(--radius-lg); padding:14px; display:flex; flex-direction:column; gap:8px; box-shadow:var(--shadow-xs); position:relative; ${isSelected ? 'background:var(--primary-light);' : (c.blacklisted ? 'background:var(--danger-light);' : '')}`;
|
|
|
| let phonesHTML = `<div style="font-size:11.5px; font-weight:700; font-family:var(--font-code); direction:ltr; text-align:right;"><i class="fa-solid fa-phone" style="font-size:10px; color:var(--primary); margin-right:4px;"></i> ${c.phone}</div>`;
|
| if (c.altPhones && Array.isArray(c.altPhones) && c.altPhones.length > 0) {
|
| c.altPhones.forEach(ap => {
|
| const pVal = typeof ap === 'object' ? ap.phone : ap;
|
| const pType = typeof ap === 'object' ? ap.type : 'عمل';
|
| phonesHTML += `<div style="font-size:10px; color:var(--text-muted); font-family:var(--font-code); direction:ltr; text-align:right;">${pVal} <span style="font-size:9px; font-family:var(--font-arabic);">(${pType})</span></div>`;
|
| });
|
| }
|
|
|
| card.innerHTML = `
|
| <span class="xjzxyzmfdl ${isSelected ? 'sq23y8364q' : ''}">#${itemNumber}</span>
|
| <div style="display:flex; justify-content:space-between; align-items:flex-start; margin-top:8px;">
|
| <div style="display:flex; align-items:center; gap:8px; min-width:0; flex:1;">
|
| <input type="checkbox" class="i07h98mtfb" value="${c.id}" ${isSelected ? 'checked' : ''} style="width:16px; height:16px; cursor:pointer; accent-color:var(--primary); flex-shrink:0;">
|
| <div style="min-width:0; overflow:hidden;">
|
| <strong style="font-size:12.5px; display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:var(--text-main);">${c.name}</strong>
|
| <span style="font-size:10px; color:var(--text-muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display:block;">${c.jobTitle || ''} ${c.company ? `@ ${c.company}` : ''}</span>
|
| </div>
|
| </div>
|
| ${getLifecycleBadgeHTML(c.lifecycle || 'lead')}
|
| </div>
|
| ${phonesHTML}
|
| ${c.email ? `<div style="font-size:10.5px; color:var(--text-secondary); overflow:hidden; text-overflow:ellipsis; direction:ltr; text-align:right;"><i class="fa-solid fa-envelope" style="margin-right:4px;"></i> ${c.email}</div>` : ''}
|
| <div style="display:flex; justify-content:space-between; align-items:center; border-top:1px dashed var(--border); padding-top:6px; margin-top:2px;">
|
| <span style="font-size:9.5px; font-weight:700; color:${fuStyle.color}; background:${fuStyle.bg}; padding:2px 6px; border-radius:var(--radius-pill);">
|
| متابعة: ${fuStyle.text} (${fuStyle.badge})
|
| </span>
|
| <div style="display:flex; gap:4px;">
|
| <button type="button" class="ipxi2jz4g0 uqi0ybfjg4" style="padding:4px 8px; font-size:10px; background:var(--wa-light); color:var(--wa-brand); border-color:var(--wa-border); min-height:28px;" title="إرسال واتساب مباشر"><i class="fa-brands fa-whatsapp"></i> </button>
|
| <button type="button" class="ipxi2jz4g0 ysx77m6bsw" style="padding:4px 8px; font-size:10px; min-height:28px;" title="تعديل"><i class="fa-solid fa-pen-to-square"></i> </button>
|
| <button type="button" class="btn-danger fp89nb580e" style="padding:4px 8px; font-size:10px; min-height:28px;" title="حذف"><i class="fa-solid fa-trash"></i> </button>
|
| </div>
|
| </div>
|
| `;
|
|
|
| const chk = card.querySelector('.i07h98mtfb');
|
| chk.addEventListener('change', (e) => {
|
| e.stopPropagation();
|
| if (chk.checked) state.selectedContactIds.add(c.id);
|
| else state.selectedContactIds.delete(c.id);
|
| this.updateSelectAllCheckboxState();
|
| this.updateSelectedCountBadge();
|
| this.renderTable();
|
| });
|
|
|
| card.querySelector('.ysx77m6bsw').onclick = () => {
|
| if (global.FritreeContactsEditor && typeof global.FritreeContactsEditor.openModal === 'function') {
|
| global.FritreeContactsEditor.openModal(c.id);
|
| }
|
| };
|
|
|
| card.querySelector('.fp89nb580e').onclick = async () => {
|
| if (confirm(`هل أنت متأكد من حذف العميل "${c.name}"؟`)) {
|
| state.contacts = state.contacts.filter(x => x.id !== c.id);
|
| state.selectedContactIds.delete(c.id);
|
| await global.FritreeContacts.save();
|
| this.renderTable();
|
| }
|
| };
|
|
|
| card.querySelector('.uqi0ybfjg4').onclick = () => {
|
| if (global.FritreeWhatsApp && typeof global.FritreeWhatsApp.setRecipients === 'function') {
|
| global.FritreeWhatsApp.setRecipients([{ phone: c.phone, name: c.name }]);
|
| window.location.hash = 'sf8pufcpyl';
|
| }
|
| };
|
|
|
| cardsGrid.appendChild(card);
|
| });
|
|
|
| displayContainer.appendChild(cardsGrid);
|
| } else {
|
|
|
| displayContainer.innerHTML = `
|
| <div class="diktqr0h64" style="max-height:550px;">
|
| <table style="width:100%; border-collapse:collapse; direction:rtl; text-align:right;">
|
| <thead>
|
| <tr>
|
| <th style="width:3%; text-align:center; padding:10px 6px;"><input type="checkbox" id="ubt2zni00m" style="width:16px; height:16px; cursor:pointer; accent-color:var(--primary);"></th>
|
| <th style="width:5%; text-align:center; padding:10px 6px; font-weight:800;"><i class="fa-solid fa-hashtag"></i> الرقم</th>
|
| <th class="fp6v22zaby" data-sort-key="name" style="width:22%; text-align:right; padding:10px 8px; cursor:pointer;" title="اضغط للترتيب">
|
| العميل والشركة <i class="fa-solid fa-sort zhldqwtmtp" style="margin-right:4px; font-size:10px; opacity:0.6;"></i>
|
| </th>
|
| <th class="fp6v22zaby" data-sort-key="phone" style="width:23%; text-align:right; padding:10px 8px; cursor:pointer;" title="اضغط للترتيب حسب الهاتف">
|
| أرقام الموبايل <i class="fa-solid fa-sort rzxfr4tqyp" style="margin-right:4px; font-size:10px; opacity:0.6;"></i>
|
| </th>
|
| <th class="fp6v22zaby" data-sort-key="lifecycle" style="width:15%; text-align:right; padding:10px 8px; cursor:pointer;" title="اضغط للترتيب حسب المرحلة">
|
| مرحلة المبيعات <i class="fa-solid fa-sort nytmhb3gqm" style="margin-right:4px; font-size:10px; opacity:0.6;"></i>
|
| </th>
|
| <th class="fp6v22zaby" data-sort-key="tags" style="width:14%; text-align:right; padding:10px 8px; cursor:pointer;" title="اضغط للترتيب حسب الوسوم">
|
| الوسوم <i class="fa-solid fa-sort sjvwcsuzye" style="margin-right:4px; font-size:10px; opacity:0.6;"></i>
|
| </th>
|
| <th class="fp6v22zaby" data-sort-key="followup" style="width:10%; text-align:right; padding:10px 8px; cursor:pointer;" title="اضغط للترتيب حسب المتابعة">
|
| المتابعة القادمة <i class="fa-solid fa-sort f074hadlgs" style="margin-right:4px; font-size:10px; opacity:0.6;"></i>
|
| </th>
|
| <th style="width:8%; text-align:center; padding:10px 8px;">الإجراءات</th>
|
| </tr>
|
| </thead>
|
| <tbody id="ojipewrl1o"></tbody>
|
| </table>
|
| </div>
|
| `;
|
|
|
| const tbody = displayContainer.querySelector('#ojipewrl1o');
|
| const selectAllChk = displayContainer.querySelector('#ubt2zni00m');
|
| const sortHeaders = displayContainer.querySelectorAll('.fp6v22zaby');
|
|
|
| sortHeaders.forEach(th => {
|
| const key = th.dataset.sortKey;
|
| const icon = th.querySelector('i');
|
| if (icon) {
|
| if (key === state.sortColumn) {
|
| icon.className = state.sortDirection === 'asc' ? 'fa-solid fa-sort-up' : 'fa-solid fa-sort-down';
|
| icon.style.opacity = '1';
|
| icon.style.color = 'var(--primary)';
|
| } else {
|
| icon.className = 'fa-solid fa-sort';
|
| icon.style.opacity = '0.4';
|
| icon.style.color = 'inherit';
|
| }
|
| }
|
|
|
| th.onclick = (e) => {
|
| e.stopPropagation();
|
| if (state.sortColumn === key) {
|
| state.sortDirection = state.sortDirection === 'asc' ? 'desc' : 'asc';
|
| } else {
|
| state.sortColumn = key;
|
| state.sortDirection = 'asc';
|
| }
|
| this.renderTable();
|
| };
|
| });
|
|
|
| if (selectAllChk) {
|
| const allRenderedSelected = filtered.length > 0 && filtered.every(c => state.selectedContactIds.has(c.id));
|
| selectAllChk.checked = allRenderedSelected;
|
| selectAllChk.onclick = (e) => {
|
| const isChecked = e.target.checked;
|
| filtered.forEach(c => {
|
| if (isChecked) state.selectedContactIds.add(c.id);
|
| else state.selectedContactIds.delete(c.id);
|
| });
|
| this.renderTable();
|
| };
|
| }
|
|
|
| filtered.forEach((c, indexIdx) => {
|
| const itemNumber = indexIdx + 1;
|
| c._displayIndex = itemNumber;
|
| const tr = document.createElement('tr');
|
| tr.dataset.id = c.id;
|
| tr.dataset.blacklisted = c.blacklisted ? 'true' : 'false';
|
|
|
| const isSelected = state.selectedContactIds.has(c.id);
|
| if (isSelected) {
|
| tr.style.backgroundColor = 'var(--primary-light)';
|
| } else if (c.blacklisted) {
|
| tr.style.backgroundColor = 'var(--danger-light)';
|
| }
|
|
|
| const tdChk = document.createElement('td');
|
| tdChk.style.textAlign = 'center';
|
| const chk = document.createElement('input');
|
| chk.type = 'checkbox';
|
| chk.className = 'i07h98mtfb';
|
| chk.value = c.id;
|
| chk.checked = isSelected;
|
| chk.style.cssText = 'width:16px; height:16px; cursor:pointer; accent-color:var(--primary);';
|
| chk.addEventListener('change', (e) => {
|
| e.stopPropagation();
|
| if (chk.checked) {
|
| state.selectedContactIds.add(c.id);
|
| tr.style.backgroundColor = 'var(--primary-light)';
|
| } else {
|
| state.selectedContactIds.delete(c.id);
|
| tr.style.backgroundColor = c.blacklisted ? 'var(--danger-light)' : '';
|
| }
|
| this.updateSelectAllCheckboxState();
|
| this.updateSelectedCountBadge();
|
| });
|
| tdChk.appendChild(chk);
|
|
|
| const tdIndex = document.createElement('td');
|
| tdIndex.style.textAlign = 'center';
|
| tdIndex.innerHTML = `<span class="v45vkzj9ap ${isSelected ? 'qv4oab8ah3' : ''}">#${itemNumber}</span>`;
|
|
|
| const tdName = document.createElement('td');
|
| tdName.style.fontWeight = '700';
|
| tdName.innerHTML = `
|
| <span style="color:var(--text-main);">${c.name}</span>
|
| <div style="font-size:10px; color:var(--text-muted); font-weight:normal; margin-top:2px;">
|
| ${c.jobTitle ? `<span>${c.jobTitle}</span>` : ''} ${c.company ? `<span>@ ${c.company}</span>` : ''}
|
| </div>
|
| `;
|
|
|
| const tdPhones = document.createElement('td');
|
| tdPhones.style.direction = 'ltr';
|
| tdPhones.style.textAlign = 'right';
|
| const phonesBox = document.createElement('div');
|
| phonesBox.style.cssText = 'display:flex; flex-direction:column; gap:2px; text-align:right; font-family:var(--font-code);';
|
| const mainPhoneDiv = document.createElement('div');
|
| mainPhoneDiv.style.cssText = 'font-weight:700; font-size:11.5px;';
|
| mainPhoneDiv.innerHTML = `${c.phone}`;
|
| phonesBox.appendChild(mainPhoneDiv);
|
|
|
| if (c.altPhones && Array.isArray(c.altPhones) && c.altPhones.length > 0) {
|
| c.altPhones.forEach(ap => {
|
| const pVal = typeof ap === 'object' ? ap.phone : ap;
|
| const pType = typeof ap === 'object' ? ap.type : 'عمل';
|
| if (pVal) {
|
| const altDiv = document.createElement('div');
|
| altDiv.style.cssText = 'font-size:10px; color:var(--text-muted); font-weight:normal;';
|
| altDiv.textContent = `${pVal} (${pType})`;
|
| phonesBox.appendChild(altDiv);
|
| }
|
| });
|
| }
|
| tdPhones.appendChild(phonesBox);
|
|
|
| const tdLifecycle = document.createElement('td');
|
| tdLifecycle.innerHTML = getLifecycleBadgeHTML(c.lifecycle || 'lead');
|
|
|
| const tdTags = document.createElement('td');
|
| if (c.tags && c.tags.length > 0) {
|
| c.tags.forEach(t => {
|
| const tagChip = document.createElement('span');
|
| tagChip.style.cssText = 'background:var(--bg-subtle); color:var(--primary); border:1px solid var(--border); font-size:9px; padding:2px 6px; border-radius:var(--radius-pill); font-weight:700; margin-left:4px; display:inline-block;';
|
| tagChip.textContent = t;
|
| tdTags.appendChild(tagChip);
|
| });
|
| } else {
|
| tdTags.textContent = '-';
|
| }
|
|
|
| const tdFollowup = document.createElement('td');
|
| const fuStyle = getFollowupDateStyle(c.nextFollowupDate);
|
| tdFollowup.style.cssText = 'font-size:11px; font-weight:700;';
|
| tdFollowup.innerHTML = `
|
| <span style="color:${fuStyle.color}; background:${fuStyle.bg}; padding:2px 6px; border-radius:var(--radius-pill); display:inline-block;">
|
| ${fuStyle.text}
|
| </span>
|
| <div style="font-size:9px; color:${fuStyle.color}; margin-top:2px;">${fuStyle.badge}</div>
|
| `;
|
|
|
| const tdActions = document.createElement('td');
|
| tdActions.style.textAlign = 'center';
|
| const editBtn = document.createElement('button');
|
| editBtn.className = 'f3ko84hxzm';
|
| editBtn.title = 'تعديل ملف العميل';
|
| editBtn.innerHTML = '<i class="fa-solid fa-pen-to-square"></i> ';
|
| editBtn.onclick = () => {
|
| if (global.FritreeContactsEditor && typeof global.FritreeContactsEditor.openModal === 'function') {
|
| global.FritreeContactsEditor.openModal(c.id);
|
| }
|
| };
|
|
|
| const delBtn = document.createElement('button');
|
| delBtn.className = 'f3ko84hxzm';
|
| delBtn.style.color = 'var(--danger)';
|
| delBtn.title = 'حذف';
|
| delBtn.innerHTML = '<i class="fa-solid fa-trash"></i> ';
|
| delBtn.onclick = async () => {
|
| if (confirm(`هل أنت متأكد من حذف العميل "${c.name}"؟`)) {
|
| state.contacts = state.contacts.filter(x => x.id !== c.id);
|
| state.selectedContactIds.delete(c.id);
|
| await global.FritreeContacts.save();
|
| this.renderTable();
|
| }
|
| };
|
|
|
| tdActions.appendChild(editBtn);
|
| tdActions.appendChild(delBtn);
|
|
|
| tr.appendChild(tdChk);
|
| tr.appendChild(tdIndex);
|
| tr.appendChild(tdName);
|
| tr.appendChild(tdPhones);
|
| tr.appendChild(tdLifecycle);
|
| tr.appendChild(tdTags);
|
| tr.appendChild(tdFollowup);
|
| tr.appendChild(tdActions);
|
| tbody.appendChild(tr);
|
| });
|
| }
|
| },
|
|
|
| |
| |
|
|
| bindFilterEvents: function() {
|
| const state = ContactsState;
|
|
|
| const searchInput = document.getElementById('n1ysqfcdai');
|
| const tagSelect = document.getElementById('rk1gpbb0ma');
|
| const lifecycleSelect = document.getElementById('hqh1kdfr3z');
|
| const blacklistSelect = document.getElementById('u8qlaic1mh');
|
| const sortSelect = document.getElementById('ranpfg2wg4');
|
| const dateFromInput = document.getElementById('z3wip3m4zk');
|
| const dateToInput = document.getElementById('m8dxissd3m');
|
| const btnViewTable = document.getElementById('gewqxqhw1a');
|
| const btnViewCards = document.getElementById('eqric71940');
|
|
|
| if (btnViewTable && btnViewCards) {
|
| btnViewTable.onclick = () => {
|
| state.viewMode = 'table';
|
| btnViewTable.classList.add('vn5qn7dmpk');
|
| btnViewCards.classList.remove('vn5qn7dmpk');
|
| this.renderTable();
|
| };
|
| btnViewCards.onclick = () => {
|
| state.viewMode = 'cards';
|
| btnViewCards.classList.add('vn5qn7dmpk');
|
| btnViewTable.classList.remove('vn5qn7dmpk');
|
| this.renderTable();
|
| };
|
| }
|
|
|
| if (searchInput) searchInput.oninput = (e) => { state.searchQuery = e.target.value; this.renderTable(); };
|
| if (tagSelect) tagSelect.onchange = (e) => { state.activeFilterTag = e.target.value; this.renderTable(); };
|
| if (lifecycleSelect) lifecycleSelect.onchange = (e) => { state.filterLifecycle = e.target.value; this.renderTable(); };
|
| if (blacklistSelect) blacklistSelect.onchange = (e) => { state.filterBlacklist = e.target.value; this.renderTable(); };
|
|
|
| if (sortSelect) {
|
| sortSelect.onchange = (e) => {
|
| const val = e.target.value;
|
| if (val === 'date_desc') { state.sortColumn = 'createdAt'; state.sortDirection = 'desc'; }
|
| else if (val === 'date_asc') { state.sortColumn = 'createdAt'; state.sortDirection = 'asc'; }
|
| else if (val === 'name_asc') { state.sortColumn = 'name'; state.sortDirection = 'asc'; }
|
| else if (val === 'name_desc') { state.sortColumn = 'name'; state.sortDirection = 'desc'; }
|
| this.renderTable();
|
| };
|
| }
|
|
|
| if (dateFromInput) dateFromInput.onchange = (e) => { state.filterDateFrom = e.target.value; this.renderTable(); };
|
| if (dateToInput) dateToInput.onchange = (e) => { state.filterDateTo = e.target.value; this.renderTable(); };
|
| }
|
| };
|
|
|
| |
| |
|
|
| global.FritreeContacts = {
|
| state: ContactsState,
|
| getContacts: () => ContactsState.contacts,
|
| lookup: (phone) => {
|
| if (global.FritreeContactsResolver && typeof global.FritreeContactsResolver.lookupByPhone === 'function') {
|
| return global.FritreeContactsResolver.lookupByPhone(phone, ContactsState.contacts);
|
| }
|
| return null;
|
| },
|
| load: async function() {
|
| if (global.FritreeContactsVault && typeof global.FritreeContactsVault.load === 'function') {
|
| ContactsState.contacts = await global.FritreeContactsVault.load();
|
| } else if (global.FritreeStorage) {
|
| ContactsState.contacts = await global.FritreeStorage.get('local_contacts_database', []);
|
| }
|
| ContactsState.isLoaded = true;
|
| ContactsRendererEngine.renderTable();
|
| return ContactsState.contacts;
|
| },
|
| save: async function() {
|
| if (global.FritreeContactsVault && typeof global.FritreeContactsVault.save === 'function') {
|
| await global.FritreeContactsVault.save(ContactsState.contacts);
|
| } else if (global.FritreeStorage) {
|
| await global.FritreeStorage.set('local_contacts_database', ContactsState.contacts);
|
| }
|
| },
|
| refreshUI: function() {
|
| ContactsRendererEngine.renderTable();
|
| },
|
| init: async function() {
|
| if (global.FritreeContactsLayout) {
|
| global.FritreeContactsLayout.buildLayout();
|
| }
|
| await this.load();
|
| ContactsRendererEngine.bindFilterEvents();
|
| if (global.FritreeContactsEditor && typeof global.FritreeContactsEditor.bindEvents === 'function') {
|
| global.FritreeContactsEditor.bindEvents();
|
| }
|
| if (global.FritreeContactsBulkActions && typeof global.FritreeContactsBulkActions.bindEvents === 'function') {
|
| global.FritreeContactsBulkActions.bindEvents();
|
| }
|
| if (global.FritreeContactsResolver && typeof global.FritreeContactsResolver.bindGlobalAutoResolution === 'function') {
|
| global.FritreeContactsResolver.bindGlobalAutoResolution();
|
| }
|
| return true;
|
| }
|
| };
|
|
|
| global.FritreeContactsRenderer = ContactsRendererEngine;
|
|
|
| if (typeof document !== 'undefined') {
|
| if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
| global.FritreeContacts.init();
|
| } else {
|
| document.addEventListener('DOMContentLoaded', () => global.FritreeContacts.init());
|
| }
|
| }
|
|
|
| })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this); |