// 🌟 ОПРЕДЕЛЯЕМ АКТИВНЫЙ ЗНАЧОК ВАЛЮТЫ ДЛЯ ДАШБОРДА
var cur = window.crmCurrentDisplayCurrency || 'rub';
var activeSign = { 'rub': '₽', 'usd': '$', 'eur': '€', 'kzt': '₸' }[cur] || '₽';
finSummary.innerHTML =
'<div style="display:flex; flex-direction:column; gap:6px; width:100%; font-size:14px; color:#5f6368; font-family:inherit;">' +
'<div style="display:flex; justify-content:space-between;"><span>Стоимость состава (с услугами):</span><b>' + totalItemsPrice.toFixed(2) + ' ' + activeSign + '</b></div>' +
'<div style="display:flex; justify-content:space-between; color:#1a73e8;"><span>Состав с учетом скидки:</span><b>' + totalWithDiscount.toFixed(2) + ' ' + activeSign + '</b></div>' +
'<div style="display:flex; justify-content:space-between; border-top:1px dashed #dadce0; padding-top:6px; margin-top:4px; font-weight:bold;">' +
'<span>Итого к оплате по чеку:</span><b style="color:#1a73e8;">' + finalTotalToPay.toFixed(2) + ' ' + activeSign + '</b>' +
'</div>' +
'<div style="display:flex; justify-content:space-between; font-size:14px; color:green;"><span>Внесено денег (Предоплата):</span><b>' + prepayPrice.toFixed(2) + ' ' + activeSign + '</b></div>' +
'<div style="display:flex; justify-content:space-between; font-size:16px; color:' + (leftPayDebt <= 0 ? 'green' : '#d93025') + ';">' +
'<span>' + (leftPayDebt <= 0 ? '✅ ЗАКАЗ ПОЛНОСТЬЮ ОПЛАЧЕН' : 'Остаток долга (Дебиторка):') + '</span><b>' + (leftPayDebt <= 0 ? '' : leftPayDebt.toFixed(2) + ' ' + activeSign) + '</b>' +
'</div>' +
'</div>';
}
};
// Функция нормализации номера по коду страны и региону
function normalizeCrmPhone(rawPhone) {
if (!rawPhone) return "";
var str = rawPhone.toString().trim();
var hasPlus = str.startsWith("+");
var clean = str.replace(/\D/g, "");
if (hasPlus && clean.length > 0) return "+" + clean;
if (clean.length === 11 && (clean.startsWith("7") || clean.startsWith("8"))) {
return "+7" + clean.substring(1);
}
}
async function handleFastClientSave(event) {
if (event) event.preventDefault();
// Находим синюю кнопку сохранения
var btnSubmit = document.getElementById("btnSubmitAddClient");
if (btnSubmit) {
btnSubmit.disabled = true;
btnSubmit.innerHTML = "⏳ Сохранение...";
}
// Собираем данные из полей вашей HTML-формы ввода
var rawPhone = document.getElementById("modalClientPhone") ? document.getElementById("modalClientPhone").value : "";
var cleanPhone = normalizeCrmPhone(rawPhone);
var payload = {
sheetId: window.st && window.st.sheetId ? window.st.sheetId : (localStorage.getItem('balloon_sheet_id') || localStorage.getItem('crm_sheet_id')),
clientName: document.getElementById("modalClientName") ? document.getElementById("modalClientName").value.trim() : "Без имени",
clientPhone: cleanPhone,
clientDate: document.getElementById("modalClientLastDate") ? document.getElementById("modalClientLastDate").value : "",
clientEvent: document.getElementById("modalClientEvent") ? document.getElementById("modalClientEvent").value.trim() : "",
comment: document.getElementById("modalClientComment") ? document.getElementById("modalClientComment").value.trim() : "",
manager: window.st && window.st.login ? window.st.login : "CRM"
};
localStorage.setItem('active_crm_tab', 'customers');
// 🚀 НАШ РОДНОЙ SaaS-СТАНДАРТ ПОДКЛЮЧЕНИЯ ИЗ ВАШЕГО ШАБЛОНА СДЕЛOК
var params = {
act: 'save_fast_client',
action: 'save_fast_client',
sheetId: payload.sheetId,
clientName: payload.clientName,
clientPhone: payload.clientPhone,
clientDate: payload.clientDate,
clientEvent: payload.clientEvent,
comment: payload.comment,
manager: payload.manager
};
var callbackName = "crmFastClientCallback_" + Math.floor(Math.random() * 100000);
params.callback = callbackName;
var savedId = localStorage.getItem('balloon_sheet_id') || localStorage.getItem('crm_sheet_id') || localStorage.getItem('sheetId');
if (savedId) {
params.id = "";
if (!params.sheetId) {
params.sheetId = savedId;
}
}
// Формируем тег скрипта по вашему эталону через динамическую константу GOOGLE_SCRIPT_URL
var script = document.createElement('script');
script.id = callbackName;
script.src = GOOGLE_SCRIPT_URL + '?' + Object.keys(params).map(function(k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]);
}).join('&');
// Наш системный ответчик для очистки памяти и перезагрузки интерфейса
window[callbackName] = function(response) {
if (document.getElementById(callbackName)) {
document.body.removeChild(script);
}
delete window[callbackName];
window.location.reload();
};
script.onerror = function() {
alert("Ошибка сети. Проверьте соединение с Ядром.");
if (btnSubmit) { btnSubmit.disabled = false; btnSubmit.innerHTML = "Сохранить контакт"; }
};
document.body.appendChild(script);
}
window.saveDealToServer = function(e) {
if (e && typeof e.preventDefault === 'function') e.preventDefault(); // 🛑 БЛОКИРУЕМ ПРЫЖОК СТРАНИЦЫ
var rowNum = document.getElementById('f-row').value;
var submitBtn = document.getElementById('save-deal-btn'); if (submitBtn) submitBtn.innerText = "Сохранение...";
var selectedProducts = []; var subtotal = 0; var cat = window.st.catalog || [];
// Получаем текущую активную валюту
var cur = window.crmCurrentDisplayCurrency || 'rub';
var symbols = { 'rub': 'руб.', 'usd': '$', 'eur': '€', 'kzt': '₸' };
var currentSymbol = symbols[cur] || 'руб.';
var simpleSymbol = { 'rub': '₽', 'usd': '$', 'eur': '€', 'kzt': '₸' }[cur] || '₽';
document.querySelectorAll('.p-chk').forEach(function(chk) {
if (chk.checked) {
var idx = parseInt(chk.getAttribute('data-idx'), 10);
var pName = chk.getAttribute('data-name');
var qty = parseInt(document.getElementById('crm-q-val-' + idx).value, 10) || 1;
// 🌟 УЛЬТРА-СТАБИЛЬНЫЙ ОДНОКОЛОНОЧНЫЙ ВЫБОР ЦЕНЫ ДЛЯ ОТПРАВКИ НА СЕРВЕР
var finalPrice = 0;
if (cat[idx]) {
finalPrice = parseFloat(cat[idx].price) || 0;
} else {
finalPrice = parseFloat(chk.getAttribute('data-baseprice')) || 0;
}
subtotal += (finalPrice * qty);
selectedProducts.push(pName + " (" + finalPrice + " " + currentSymbol + " x " + qty + " шт)");
}
});
document.querySelectorAll('.custom-service-row').forEach(function(row) {
var sName = row.querySelector('.c-srv-name').value.trim();
var sPrice = parseFloat(row.querySelector('.c-srv-price').value) || 0;
if (sName && sPrice > 0) {
// Строка 1049: Подставляем динамический символ валюты для ручных доп. услуг
selectedProducts.push(sName + " (" + sPrice + " " + simpleSymbol + ")");
subtotal += sPrice;
}
});
var payload = {
action: 'save_deal', act: 'save_deal', sheet_id: window.st.sheetId, row_num: rowNum, row: rowNum,
name: document.getElementById('f-name').value.trim(), phone: normalizeCrmPhone(document.getElementById('f-phone').value),
status: document.getElementById('f-status').value, event: document.getElementById('f-event').value.trim(),
date_event: document.getElementById('f-date-event').value, date_delivery: document.getElementById('f-date-deliv').value,
time_delivery: document.getElementById('f-time-deliv').value.trim(), address: document.getElementById('f-addr').value.trim(),
details: document.getElementById('f-details').value.trim(), products: selectedProducts.join(", "),
discount: document.getElementById('f-disc').value || '0', delivery: document.getElementById('f-deliv-sum').value || '0',
prepay: document.getElementById('f-prepay').value || '0', composition_sum: subtotal, login: window.st.login,
channel: document.getElementById('f-channel') ? document.getElementById('f-channel').value : "",
badge: document.getElementById('f-badge') ? document.getElementById('f-badge').value.trim() : "",
// ИСПРАВЛЕНО: Жестко объявляем поля для Сетевого Моста фото и Gmail
gmail: document.getElementById('user-target-gmail') ? document.getElementById('user-target-gmail').value.trim() : "",
file_base64: window.lastUploadedBase64 || "",
file_name: window.lastUploadedFileName || "photo.jpg"
};
var fileInp = document.getElementById('f-photos') || document.getElementById('f-file') || document.querySelector('input[type="file"]');
if (fileInp && fileInp.files && fileInp.files.length > 0) {
// СТРОГО ОБЯЗАТЕЛЕН ИНДЕКС [0], чтобы взять конкретный файл из массива!
var targetFile = fileInp.files[0];
var reader = new FileReader();
reader.onloadend = function() {
payload.file_base64 = reader.result;
payload.file_name = targetFile.name;
executePostRequest(payload, submitBtn);
};
reader.readAsDataURL(targetFile);
} else {
executePostRequest(payload, submitBtn);
}
function executePostRequest(payload, submitBtn) {
var xhr = new XMLHttpRequest();
xhr.open("POST", GOOGLE_SCRIPT_URL, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var qBody = Object.keys(payload).map(function(k) {
return encodeURIComponent(k) + '=' + encodeURIComponent(payload[k]);
}).join('&');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
alert("🎉 Успешно сохранено!");
location.reload();
}
};
xhr.send(qBody);
}
};
window.editDealInline = function(rowNum) {
var d = window.st.deals.find(function(x) { return x.row_num === rowNum; }); if (!d) return;
document.querySelectorAll('#nav-tabs .tab-btn, .tab-panel').forEach(function(el) { el.classList.remove('active'); });
var formTabTrigger = document.getElementById('tab-add-deal-btn'); if (formTabTrigger) formTabTrigger.classList.add('active');
document.getElementById('tab-form').classList.add('active');
document.getElementById('form-title').innerText = 'Редактирование заказа № ' + d.id;
var innerDateBox = document.getElementById('form-created-date-box');
if (!innerDateBox) {
innerDateBox = document.createElement('div');
innerDateBox.id = 'form-created-date-box';
innerDateBox.style.cssText = 'font-size:13px; color:#5f6368; margin-top:4px; font-weight:600; text-align:left;';
var fTitle = document.getElementById('form-title');
if (fTitle) fTitle.parentNode.insertBefore(innerDateBox, fTitle.nextSibling);
}
if (innerDateBox) {
innerDateBox.innerText = '📅 Дата создания заявки: ' + window.formatCrmDateToRu(d.date_created);
}
document.getElementById('f-row').value = d.row_num;
document.getElementById('f-name').value = d.client_name; document.getElementById('f-phone').value = d.phone;
document.getElementById('f-event').value = d.event_name; document.getElementById('f-date-event').value = d.date_event;
document.getElementById('f-date-deliv').value = d.date_delivery; document.getElementById('f-addr').value = d.address;
document.getElementById('f-details').value = d.details; document.getElementById('f-disc').value = String(d.discount).replace('%','');
document.getElementById('f-deliv-sum').value = d.delivery; document.getElementById('f-prepay').value = d.prepay;
if (document.getElementById('f-channel')) document.getElementById('f-channel').value = d.channel || "";
if (document.getElementById('f-badge')) document.getElementById('f-badge').value = d.badge || "";
setTimeout(function() {
var stF = document.getElementById('f-status');
if (stF) stF.value = String(d.status || "").trim();
}, 50);
var tMatch = String(d.time_delivery || '').match(/(\d{2}:\d{2})/); document.getElementById('f-time-deliv').value = tMatch ? tMatch[1] : '';
var sContainer = document.getElementById('custom-services-container'); if (sContainer) sContainer.innerHTML = '';
// ✅ ЗАРЯЖАЕМ СВЕРХНАДЕЖНЫЙ SaaS-ПАРСЕР СОСТАВОВ ПРОТИВ СЛЕТА В 0
window.frozenPricesMemory = {};
window.renderAllElements();
if (d.products && String(d.products).trim() !== "") {
var parts = String(d.products).split(", ");
parts.forEach(function(part) {
var matchProd = part.match(/(.+?)\s*\((\d+[\.,]?\d*)\s*(?:руб\.?|₽|\$|€|₸)\s*x\s*(\d+)\s*шт\)/);
if (matchProd) {
var pName = matchProd[1].trim().toLowerCase();
// Преобразуем запятую в точку для корректного parseFloat значений меньше 1 (например 0.50)
var pPrice = parseFloat(matchProd[2].replace(',', '.')) || 0;
var pQty = parseInt(matchProd[3], 10) || 1;
window.frozenPricesMemory[pName] = pPrice;
document.querySelectorAll('.p-chk').forEach(function(chk) {
if (String(chk.getAttribute('data-name')).toLowerCase().trim() === pName) {
chk.checked = true;
var targetQtyInput = document.getElementById('crm-q-val-' + chk.getAttribute('data-idx'));
if (targetQtyInput) targetQtyInput.value = pQty;
}
});
} else {
var matchSrv = part.match(/(.+?)\s*\((\d+[\.,]?\d*)\s*(?:руб\.?|₽|\$|€|₸)\)/);
if (matchSrv && sContainer) {
window.customServicesCount++;
var row = document.createElement('div'); row.className = 'custom-service-row';
row.style.display = 'flex'; row.style.gap = '10px'; row.style.marginBottom = '8px';
row.innerHTML = '<input type="text" class="c-srv-name" value="' + matchSrv[1].trim() + '" oninput="window.recalcDealTotal()" style="flex:3; padding:8px; border:1px solid #dadce0; border-radius:6px;"><input type="number" class="c-srv-price" value="' + parseFloat(matchSrv[2]) + '" oninput="window.recalcDealTotal()" style="flex:1; padding:8px; border:1px solid #dadce0; border-radius:6px;">';
sContainer.appendChild(row);
}
}
});
}
// Интерактивные заглушки вложений файлов из ячейки Excel
var pBox = document.getElementById('photo-preview-box');
if (pBox) {
pBox.innerHTML = ''; pBox.style.display = 'none';
if (d.photo && String(d.photo).trim() !== "" && d.photo !== "undefined") {
var photoUrlsArray = String(d.photo).split(', ');
var galleryHtml = '<div style="font-size:12px; color:#1a73e8; margin-bottom:8px; font-weight:700;">📎 Прикрепленные документы/фото (' + photoUrlsArray.length + ' шт):</div>' +
'<div style="display:flex; gap:10px; flex-wrap:wrap;">';
photoUrlsArray.forEach(function(url, idx) {
var cleanUrl = String(url).trim();
if (cleanUrl !== "") {
var targetUrl = (cleanUrl.indexOf('http') !== -1) ? cleanUrl : 'https://drive.google.com/file/d/' + cleanUrl + '/view';
galleryHtml += '<a href="' + targetUrl + '" target="_blank" style="display:flex; align-items:center; gap:6px; background:#f1f3f4; color:#202124; padding:8px 12px; border-radius:6px; border:1px solid #dadce0; text-decoration:none; font-size:12px; font-weight:600; cursor:pointer; transition: background 0.2s;" onmouseover="this.style.background=\'#e8eaed\'" onmouseout="this.style.background=\'#f1f3f4\'">' +
'<span>📄 Файл №' + (idx + 1) + '</span><span style="color:#1a73e8; font-size:11px;">(Открыть ↗)</span></a>';
}
});
galleryHtml += '</div>'; pBox.innerHTML = galleryHtml; pBox.style.display = 'block';
}
}
var delBtn = document.getElementById('delete-deal-btn'); if (delBtn) delBtn.style.display = (window.st.role === 'owner') ? 'inline-block' : 'none';
window.recalcDealTotal();
};
// ✅ SaaS-ОТРЕСОВЩИК ЛЕНТЫ С ЖИВЫМ ПОИСКОМ, ФИЛЬТРОМ ДАТ, ДЕБИТОРКОЙ И ПАГИНАЦИЕЙ v45.0
function renderDealsList() {
var listContainer = document.getElementById('deals-list'); if (!listContainer) return;
var dList = window.st.deals || [];
var sList = window.st.statuses || [];
var oldLoaderText = document.querySelector('.tab-panel div[style*="color:#999"]') || document.getElementById('status-tabs-box');
if (oldLoaderText && oldLoaderText.parentNode && oldLoaderText.innerHTML.indexOf('Загрузка групп') !== -1) {
oldLoaderText.innerHTML = '';
}
// Фильтрация по вкладкам воронки или долгов
if (window.currentStatusFilter === 'debts_filter') {
dList = dList.filter(function(d) {
var dStat = String(d.status || '').trim().toLowerCase();
var dLeft = parseFloat(d.leftPay || d.restDebt || 0) || 0;
var rule = sList.find(function(s) { return String(s.name || '').trim().toLowerCase() === dStat; });
var isDebtStatus = rule ? !!rule.isDebt : (['новое обращение','общение','согласован','внесена оплата','готов'].indexOf(dStat) !== -1);
return isDebtStatus && dLeft > 0;
});
} else {
var cleanFilter = String(window.currentStatusFilter || 'all').toLowerCase().trim();
if (cleanFilter !== 'all' && cleanFilter !== 'all_upper') {
dList = dList.filter(function(d) {
return String(d.status || '').trim().toLowerCase() === cleanFilter;
});
}
}
// 🎯 1. РАЗДЕЛЬНЫЙ ЗАХВАТ КАЛЕНДАРЕЙ И СТРОКИ ПОИСКА TILDA
var dateInputs = document.querySelectorAll('#tab-deals input[type="date"]');
var startDateVal = (dateInputs && dateInputs[0] && dateInputs[0].value) ? dateInputs[0].value : '';
var endDateVal = (dateInputs && dateInputs[1] && dateInputs[1].value) ? dateInputs[1].value : '';
var searchInp = document.querySelector('#tab-deals input[type="text"]') || document.getElementById('deal-search-input');
var searchQueryVal = (searchInp && searchInp.value) ? searchInp.value.trim() : '';
// // 🎯 . УМНАЯ АДАПТИВНАЯ ПАНЕЛЬ ФИЛЬТРОВ И КНОПКА СБРОСА (ФИКС ДЛЯ МОБИЛЬНЫХ)
var filterBar = document.getElementById('deals-date-filter-bar');
if (!filterBar) {
var tabsBox = document.getElementById('status-tabs-box') || document.getElementById('status-groups-tabs');
if (tabsBox) {
filterBar = document.createElement('div'); filterBar.id = 'deals-date-filter-bar';
// Flex-wrap заставит кнопку аккуратно упасть на 2-ю строку в смартфонах, не ломая экран!
filterBar.style.cssText = 'margin-bottom:15px; display:flex; flex-wrap:wrap; gap:10px; align-items:center; text-align:left; width:100%; clear:both;';
tabsBox.parentNode.insertBefore(filterBar, tabsBox.nextSibling);
}
}
if (filterBar) {
// Кнопка рендерится, если выбран хотя бы один календарь ИЛИ вбит текст в поиск заказов
if (startDateVal || endDateVal || searchQueryVal !== "") {
filterBar.innerHTML = '<button type="button" class="btn-reset-modern" onclick="window.resetAllCrmDealsFilters()" style="padding:8px 16px; background:#fff; border:1px solid #dadce0; border-radius:6px; color:#c5221f; font-weight:700; font-size:12px; cursor:pointer; transition:all 0.2s; box-shadow:0 1px 3px rgba(0,0,0,0.05); display:inline-flex; align-items:center; gap:6px; font-family:inherit; min-height:34px;" onmouseover="this.style.background=\'#fce8e6\'; this.style.borderColor=\'#fad2cf\'" onmouseout="this.style.background=\'#fff\'; this.style.borderColor=\'#dadce0\'">🧹 Сбросить все фильтры и поиск (За весь период)</button>';
} else {
filterBar.innerHTML = ''; // Автоматически скрываем, если фильтры не применены
}
}
// 🎯 4. ПРИНУДИТЕЛЬНАЯ ФИЛЬТРАЦИЯ МАССИВА ПО ДАТАМ СОЗДАНИЯ СДЕЛКИ
if (startDateVal) {
dList = dList.filter(function(d) { return d.date_created >= startDateVal; });
}
if (endDateVal) {
dList = dList.filter(function(d) { return d.date_created <= endDateVal; });
}
// 🎯 5. ПЕРЕРАСЧЕТ СЧЕТЧИКОВ В ТАБАХ ВОРОНКИ НА ОСНОВЕ ФИЛЬТРА ДАТ
window.st.filteredDealsCache = dList; // Сохраняем отфильтрованный пак в кэш для табов
if (typeof window.renderStatusTabs === 'function' && !window.st.isTabsRenderingBlock) {
window.st.isTabsRenderingBlock = true; // Защита от бесконечного цикла
window.renderStatusTabs();
window.st.isTabsRenderingBlock = false;
}
// 🎯 6. ЖИВОЙ ПОИСК ЗАКАЗОВ (Ищет по ID, Имени, Составу, Телефону)
var searchInp = document.querySelector('#tab-deals input[type="text"]') || document.getElementById('deal-search-input');
if (searchInp && searchInp.value.trim() !== "") {
var q = searchInp.value.trim().toLowerCase();
dList = dList.filter(function(d) {
var idM = String(d.id || '').toLowerCase().indexOf(q) !== -1;
var nameM = String(d.client_name || '').toLowerCase().indexOf(q) !== -1;
var prodM = String(d.products || '').toLowerCase().indexOf(q) !== -1;
var phoneM = String(d.phone || '').replace(/\D/g, '').indexOf(q) !== -1;
return idM || nameM || prodM || phoneM;
});
}
if (!dList.length) {
// Проверяем глобальный флаг загрузки
if (window.st && window.st.isLoading) {
listContainer.innerHTML = `
<div style="text-align:center; padding:30px 0;">
<div class="balloon-spinner" style="display:inline-block; width:24px; height:24px; border:3px solid #f3f3f3; border-top:3px solid #25d366; border-radius:50%; animation: crmSpin 0.8s linear infinite;"></div>
<div style="color:#666; font-size:13px; font-weight:500; margin-top:10px;">⏳ Данные подгружаются...</div>
</div>
<style>
@keyframes crmSpin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
`;
} else {
listContainer.innerHTML = '<div style="color:#999; padding:20px 0; text-align:center;">Заказы по выбранным фильтрам не найдены</div>';
}
return;
}
// Накопительная пагинация по 30 строк
var totalFoundCount = dList.length;
var visibleDeals = dList.slice(0, window.crmDealsLimit || 30);
var html = '';
visibleDeals.forEach(function(d) {
var cleanPhone = String(d.phone || '').replace(/\D/g, '');
var formattedPhone = cleanPhone.startsWith('8') ? '7' + cleanPhone.slice(1) : cleanPhone;
var isRepeatCustomer = window.st.clients.some(function(c) { return normalizeCrmPhone(c.phone) === normalizeCrmPhone(d.phone); });
var repeatBadgeHtml = isRepeatCustomer ? '<span style="background:#25d366; color:#fff; font-size:10px; font-weight:700; padding:2px 6px; border-radius:4px; margin-left:8px; text-transform:uppercase;">Повторный</span>' : '';
var customBadgeText = String(d.badge || '').trim();
var customBadgeHtml = customBadgeText ? '<span style="background:#ff9800; color:#fff; font-size:11px; font-weight:600; padding:2px 6px; border-radius:4px; margin-left:6px;">' + customBadgeText + '</span>' : '';
var phoneLinkHtml = formattedPhone ? '<a href="tel:+' + formattedPhone + '" onclick="event.stopPropagation();" style="color:#1a73e8; text-decoration:underline; font-weight:600;">📞 +' + formattedPhone + '</a>' : '📞 Не указан';
var dStat = String(d.status || '').trim().toLowerCase();
var debtSum = parseFloat(d.leftPay || d.restDebt || 0) || 0;
var totalSum = parseFloat(d.total) || 0;
var rule = sList.find(function(s) { return String(s.name || '').trim().toLowerCase() === dStat; });
var isDebtZone = rule ? !!rule.isDebt : (['новое обращение','общение','согласован','внесена оплата','готов'].indexOf(dStat) !== -1);
var financeBadgeHtml = '';
if (totalSum > 0 && debtSum <= 0) {
financeBadgeHtml = '<div style="color:#137333; background:#e6f4ea; font-weight:700; font-size:12px; padding:2px 8px; border-radius:4px; margin-top:4px; display:inline-block; border:1px solid #ceead6;">✅ Оплачен</div>';
} else if (isDebtZone && debtSum > 0) {
financeBadgeHtml = '<div style="color:#d93025; background:#fce8e6; font-weight:700; font-size:12px; padding:2px 8px; border-radius:4px; margin-top:4px; display:inline-block; border:1px solid #fad2cf;">🚨 Долг: ' + Math.round(debtSum).toLocaleString() + ' ₽</div>';
}
// Преобразуем системный формат в красивый русский вид через точки
var ruCreatedDate = window.formatCrmDateToRu(d.date_created);
var ruDeliveryDate = window.formatCrmDateToRu(d.date_delivery);
// Восстанавливаем оригинальные переменные оформления из вашего референса
var cleanPhone = String(d.phone || '').replace(/\D/g, '');
var formattedPhone = cleanPhone.startsWith('8') ? '7' + cleanPhone.slice(1) : cleanPhone;
// Проверяем, сколько раз этот телефон встречается в массиве ВСЕХ сделок CRM
var phoneMatchesCount = 0;
if (cleanPhone && window.st && window.st.deals) {
phoneMatchesCount = window.st.deals.filter(function(deal) {
// Приводим телефон каждой сделки из базы к чистому цифровому виду для сравнения
var dealPhone = String(deal.phone || '').replace(/\D/g, '');
return dealPhone === cleanPhone;
}).length;
}
// Проверяем канал привлечения текущего заказа (с защитой от регистра)
var currentChannel = String(d.channel || '').trim().toLowerCase();
// Условие: либо в заказе выбран канал "повторный", либо этот телефон встречается в сделках больше 1 раза
var isRealRepeat = (currentChannel === 'повторный' || currentChannel === 'повтор') || (phoneMatchesCount > 1);
// Выводим бейдж только при выполнении условий
var repeatBadgeHtml = isRealRepeat ? '<span style="background:#25d366; color:#fff; font-size:10px; font-weight:700; padding:3px 6px; border-radius:4px; margin-right:5px;">Повторный</span>' : '';
var customBadgeText = String(d.badge || '').trim();
var customBadgeHtml = customBadgeText ? '<span style="background:#ff9800; color:#fff; font-size:11px; font-weight:600; padding:2px 6px; border-radius:4px; margin-left:6px;">' + customBadgeText + '</span>' : '';
var dStat = String(d.status || '').trim().toLowerCase();
var debtSum = parseFloat(d.leftPay || d.restDebt || 0) || 0;
var totalSum = parseFloat(d.total) || 0;
var rule = sList.find(function(s) { return String(s.name || '').trim().toLowerCase() === dStat; });
var isDebtZone = rule ? !!rule.isDebt : (['новое обращение','общение','согласован','внесена оплата','готов','выполнен'].indexOf(dStat) !== -1);
var financeBadgeHtml = '';
if (totalSum > 0 && debtSum <= 0) {
financeBadgeHtml = '<div style="color:#137333; background:#e6f4ea; font-weight:700; font-size:12px; padding:2px 8px; border-radius:4px; margin-top:4px; display:inline-block; border:1px solid #ceead6;">✅ Оплачен</div>';
} else if (isDebtZone && debtSum > 0) {
financeBadgeHtml = '<div style="color:#d93025; background:#fce8e6; font-weight:700; font-size:12px; padding:2px 8px; border-radius:4px; margin-top:4px; display:inline-block; border:1px solid #fad2cf;">🚨 Долг: ' + Math.round(debtSum).toLocaleString() + ' ₽</div>';
}
// 🎯 ПОЛНЫЙ ПОВТОР ОРИГИНАЛЬНОЙ СТРУКТУРЫ И СТИЛЕЙ С ВАШЕГО РЕФЕРЕНСА:
html += '<div class="deal-card" onclick="window.editDealInline(' + d.row_num + ')" style="display:flex; gap:15px; align-items:center; border:1px solid #dadce0; padding:15px; border-radius:8px; margin-bottom:10px; background:#fff; cursor:pointer;">' +
'<div style="flex:1;">' +
'<div style="font-weight:700; font-size:15px; color:#1a73e8; display:flex; align-items:center; flex-wrap:wrap; gap:4px;">№ ' + d.id + ' | ' + d.client_name + repeatBadgeHtml + customBadgeHtml + '</div>' +
'<div style="font-size:12px; color:#5f6368; margin-top:4px;">📅 Создан: <b>' + ruCreatedDate + '</b> | 🚚 Доставка: <b>' + ruDeliveryDate + ' в ' + (d.time_delivery || 'не указано') + '</b></div>' +
'<div style="font-size:12px; color:#5f6368; margin-top:2px;">Статус: <span style="background:#e8f0fe; padding:2px 6px; border-radius:4px; font-weight:600;">' + d.status + '</span>' + (d.channel ? ' | Источник: <i>' + d.channel + '</i>' : '') + '</div>' +
'<div style="font-size:12px; color:#202124; margin-top:4px;">📞 <a href="tel:+' + formattedPhone + '" onclick="event.stopPropagation();" style="color:#1a73e8; text-decoration:underline;">+' + formattedPhone + '</a> | Состав: ' + d.products + '</div>' +
'<div>' + financeBadgeHtml + '</div>' +
'</div>' +
'<div style="text-align:right; display:flex; flex-direction:column; gap:6px; align-items:flex-end;">' +
'<div style="font-size:16px; font-weight:700; color:#202124;">' + d.total + ' ₽</div>' +
(cleanPhone ?
'<div style="display:flex; gap:4px;">' +
'<a href="https://wa.me' + formattedPhone + '" target="_blank" class="btn-whatsapp" onclick="event.stopPropagation();" style="background:#25d366; padding:5px 8px; border-radius:4px; font-size:11px; text-decoration:none; color:#fff; font-weight:bold;">💬 Чат</a>' +
'<button class="btn-action" onclick="event.stopPropagation(); window.sendOrderToWhatsApp(' + d.row_num + ')" style="background:#075e54; padding:5px 8px; border-radius:4px; font-size:11px; color:#fff; border:none; cursor:pointer; font-weight:bold;">📄 Состав</button>' +
'</div>' : '') +
'</div>' +
'</div>';
});
if (totalFoundCount > window.crmDealsLimit) {
html += '<div style="text-align:center; margin-top:15px; margin-bottom:10px;">' +
'<button type="button" class="btn-action" onclick="window.crmDealsLimit+=30; renderDealsList();" style="background:#5f6368; padding:6px 20px; width:auto; font-size:12px; font-weight:700; border-radius:6px; cursor:pointer;">Показать еще (осталось ' + (totalFoundCount - window.crmDealsLimit) + ' шт.)</button>' +
'</div>';
}
listContainer.innerHTML = html;}
window.crmLoadMoreDeals = function() {
window.crmDealsLimit += 30; // Наращиваем стек шага порции на +30 строк
renderDealsList(); // Мгновенный перерендер ленты без запроса к сети
};
// =========================================================================
// 🚀 ЧАСТЬ 4.2: ТАБ ДЕБИТОРКИ, СЕТКА КАЛЕНДАРЯ И ПАГИНАЦИЯ КЛИЕНТОВ v40.0
// =========================================================================
window.renderStatusTabs = function() {
var box = document.getElementById('status-tabs-box') || document.getElementById('status-groups-tabs');
if (!box) return;
var statuses = window.st.statuses || [];
// 🎯 ЖЕСТКИЙ ЯКОРЬ SaaS: Для расчета счетчиков ВСЕГДА берем ГЛОБАЛЬНЫЙ массив сделок из памяти, фильтруя ТОЛЬКО ПО ДАТАМ!
var globalDeals = window.st.deals || [];
// Принудительно прогоняем глобальный массив через выбранный календарь дат (с ... по ...), чтобы счетчики в табах реагировали на даты
var dateInputs = document.querySelectorAll('#tab-deals input[type="date"]');
var startDateVal = (dateInputs && dateInputs[0] && dateInputs[0].value) ? dateInputs[0].value : '';
var endDateVal = (dateInputs && dateInputs[1] && dateInputs[1].value) ? dateInputs[1].value : '';
if (startDateVal) {
globalDeals = globalDeals.filter(function(d) { return d.date_created >= startDateVal; });
}
if (endDateVal) {
globalDeals = globalDeals.filter(function(d) { return d.date_created <= endDateVal; });
}
var totalDealsCount = globalDeals.length;
if (!statuses || !statuses.length) {
statuses = [{name: 'Новое обращение'}, {name: 'Общение'}, {name: 'Согласован'}, {name: 'Внесена оплата'}, {name: 'Готов'}, {name: 'Выполнен'}, {name: 'Отказ'}, {name: 'Не целевой'}];
}
// Считаем должников в дебиторской зоне строго от глобального пака дат
var totalDebtsCount = globalDeals.filter(function(d) {
var dStat = String(d.status || '').trim().toLowerCase();
var dLeft = parseFloat(d.leftPay || d.restDebt || 0) || 0;
var rule = statuses.find(function(s) { return String(s.name || '').trim().toLowerCase() === dStat; });
var isDebtZone = rule ? !!rule.isDebt : (['новое обращение','общение','согласован','внесена оплата','готов','выполнен'].indexOf(dStat) !== -1);
return isDebtZone && dLeft > 0;
}).length;
// 🎯 ВСЕЯДНАЯ ПРОВЕРКА ДЛЯ ТАБА "ВСЕ ЗАКАЗЫ" (ИГНОРИРУЕМ СДВИГИ РЕГИСТРОВ БУКВ БРАУЗЕРА)
var currentFilterClean = String(window.currentStatusFilter || 'all').toLowerCase().trim();
var isAllActive = (currentFilterClean === 'all' || currentFilterClean === 'all_upper' || currentFilterClean === '');
// Кнопка 1: Все заказы (С синей подсветкой, которая теперь никогда не слетит при возврате!)
var html = '<button type="button" class="btn-out' + (isAllActive ? ' active' : '') + '" onclick="window.filterDealsByStatus(\'all\')" style="padding:5px 10px; font-weight:700; cursor:pointer; border-radius:6px; font-size:12px; border:1px solid #dadce0; transition:all 0.2s;' + (isAllActive ? 'background:#1a73e8; color:#fff; border-color:#1557b0;' : 'background:#fff; color:#202124;') + '">Все заказы (' + totalDealsCount + ')</button>';
// Кнопка 2: Задолженности
var isDebtActive = (currentFilterClean === 'debts_filter');
html += '<button type="button" class="btn-out' + (isDebtActive ? ' active' : '') + '" onclick="window.filterDealsByStatus(\'debts_filter\')" style="padding:5px 10px; font-weight:700; cursor:pointer; border-radius:6px; font-size:12px; color:' + (isDebtActive ? '#fff' : '#b06000') + '; background:' + (isDebtActive ? '#e67e22' : '#fef5e7') + '; border:1px solid ' + (isDebtActive ? '#d35400' : '#fdebd0') + '; transition:all 0.2s; margin-right:12px;">Задолженности (' + totalDebtsCount + ')</button>';
// Автономный перебор всех текущих и будущих кастомных групп воронки из Excel
statuses.forEach(function(st) {
var sName = String(st.name || st).trim();
if (sName) {
// 🎯 ТОТАЛЬНАЯ ИЗОЛЯЦИЯ СЧЕТЧИКА: Каждый статус считает свою цифру СТРОГО от globalDeals периода дат!
var countInStatus = globalDeals.filter(function(d) {
return String(d.status || '').trim().toLowerCase() === sName.toLowerCase();
}).length;
var isActive = (currentFilterClean === sName.toLowerCase());
html += '<button type="button" class="btn-out' + (isActive ? ' active' : '') + '" onclick="window.filterDealsByStatus(\'' + sName + '\')" style="padding:5px 10px; font-weight:700; cursor:pointer; border-radius:6px; font-size:12px; border:1px solid #dadce0; transition:all 0.2s;' + (isActive ? 'background:#1a73e8; color:#fff; border-color:#1557b0;' : 'background:#fff; color:#202124;') + '">' + sName + ' (' + countInStatus + ')</button>';
}
});
box.innerHTML = html;
// Полностью очищаем и скрываем весящие старые лоадеры
document.querySelectorAll('.tab-panel div, p, span').forEach(function(el) {
if (el.innerText && el.innerText.indexOf('Загрузка групп воронки') !== -1) {
el.style.display = 'none'; el.innerHTML = '';
}
});
};
// ✅ УМНЫЙ ПЕРЕКЛЮЧАТЕЛЬ ТАБОВ ВОРОНКИ С ФИЛЬТРОМ ЗАДОЛЖЕННОСТЕЙ v53.3
window.filterDealsByStatus = function(statusName) {
var cleanName = String(statusName || 'all').toLowerCase().trim();
// Жестко фиксируем системное имя фильтра
if (cleanName === 'debts_filter') {
window.currentStatusFilter = 'debts_filter';
} else {
window.currentStatusFilter = (cleanName === 'all') ? 'all' : statusName;
}
// Сбрасываем лимит накопительной пагинации до базовых 30 штук
window.crmDealsLimit = 30;
// Перерисовываем горизонтальную панель табов
if (typeof window.renderStatusTabs === 'function') {
window.renderStatusTabs();
}
// Запускаем живой перерендер ленты карточек
if (typeof renderDealsList === 'function') {
renderDealsList();
}
};
window.calendarMonthOffset = 0;
window.renderCrmCalendar = function() {
var grid = document.getElementById('crm-calendar-grid'); if (!grid) return;
var title = document.getElementById('calendar-month-title'); var targetDate = new Date();
targetDate.setMonth(targetDate.getMonth() + window.calendarMonthOffset);
title.innerHTML = '<button class="btn-out" onclick="window.changeCrmMonth(-1)" style="padding:4px 10px; cursor:pointer;">◀ Прошлый</button> <span><b>' + targetDate.toLocaleString('ru-RU', { month: 'long', year: 'numeric' }).toUpperCase() + '</b></span> <button class="btn-out" onclick="window.changeCrmMonth(1)" style="padding:4px 10px; cursor:pointer;">Следующий ▶</button>';
grid.innerHTML = ''; var dList = window.st.deals || [];
for (var day = 1; day <= 31; day++) {
var currentDayStr = targetDate.getFullYear() + '-' + String(targetDate.getMonth() + 1).padStart(2,'0') + '-' + String(day).padStart(2,'0');
var dayDeals = dList.filter(function(d) { return d.date_delivery === currentDayStr; });
dayDeals.sort(function(a,b){ return String(a.time_delivery).localeCompare(String(b.time_delivery)); });
var badgeHtml = ''; dayDeals.forEach(function(d) { badgeHtml += '<div class="calendar-deal-badge" onclick="window.editDealInline(' + d.row_num + ')">⏰ ' + d.time_delivery + ' - ' + d.client_name + '</div>'; });
var todayCheck = new Date(); var isToday = (todayCheck.getDate() === day && todayCheck.getMonth() === targetDate.getMonth() && todayCheck.getFullYear() === targetDate.getFullYear()) ? ' today' : '';
grid.innerHTML += '<div class="calendar-day-cell' + isToday + '"><div class="calendar-day-num">' + day + '</div>' + badgeHtml + '</div>';
}
};
window.changeCrmMonth = function(delta) { window.calendarMonthOffset += delta; window.renderCrmCalendar(); };
window.sendOrderToWhatsApp = function(rowNum) {
var d = window.st.deals.find(function(x) { return x.row_num === rowNum; }); if (!d) return;
var cleanPhone = String(d.phone || '').replace(/\D/g, ''); if (!cleanPhone) { alert("У клиента не указан номер телефона!"); return; }
var formattedPhone = cleanPhone.startsWith('8') ? '7' + cleanPhone.slice(1) : cleanPhone;
var cleanProducts = String(d.products || '').replace(/\s*\(\d+\s*руб\/шт\s*x\s*\d+\s*шт\)/g, '').trim();
var msg = "Здравствуйте, " + d.client_name + "! Ваш заказ сформирован №" + d.id + ".\n\n📦 Состав заказа: " + (cleanProducts || "Не указан") + "\n💰 Итого к оплате: " + Math.round(d.total).toLocaleString() + " ₽.\n\n🚚 Дата и время доставки: " + d.date_delivery + ", " + d.time_delivery + "\n🏠 Адрес доставки: " + (d.address || "Самовывоз");
window.open("https://wa.me" + formattedPhone + "?text=" + encodeURIComponent(msg), "_blank");
};