${imgContent}
${p.name}
${formatPrice(finalPrice)}
${item.qty}
ВСЕ ТОВАРЫ ОПТОМ ОТ ПРОИЗВОДИТЕЛЕЙ
Надежный партнер для регулярных оптовых закупок, прозрачных условий и стабильных поставок.
Чтобы оставить отзыв, авторизуйтесь
Корзина пуста
'; totalBlock.innerHTML = ''; return; } let total = 0; container.innerHTML = cart.map(item => { const p = getProductById(item.productId); if (!p) return ''; const finalPrice = getFinalPrice(p); const sub = finalPrice * item.qty; total += sub; const imgSrc = (p.images && p.images[0]) || p.imageUrl || ''; const imgContent = imgSrc ? `Товары не найдены.
'; return; } grid.innerHTML = products.map(p => { const finalPrice = getFinalPrice(p); const basePrice = p.price || 0; const discountPercent = basePrice > 0 ? Math.round((1 - finalPrice / basePrice) * 100) : 0; const showDiscount = finalPrice < basePrice; const rating = (p.rating || State.rating).toFixed(1); const isFav = favorites.includes(p.id); const heartIcon = isFav ? 'fas fa-heart' : 'far fa-heart'; const finalPriceStr = formatPrice(finalPrice); const basePriceStr = formatPrice(basePrice); const productImages = (p.images && Array.isArray(p.images) && p.images.length > 0) ? p.images : (p.imageUrl ? [p.imageUrl] : []); const firstImage = productImages[0]; const imgContent = firstImage ? `Корзина пуста
'; totalBlock.innerHTML = `Итого:${formatPrice(total)}`; openModal('orderModal'); logAnalyticsEvent('begin_checkout', { currency: "RUB", value: total, items: cart.map(item => { const p = getProductById(item.productId); if (!p) return null; return { item_id: p.id, item_name: p.name, item_category: p.category, price: getFinalPrice(p), quantity: item.qty }; }).filter(Boolean) }); } function generateOrderText() { let text = '🛒 *Заказ в FULL SELLER*\n\n'; let total = 0; cart.forEach(item => { const p = getProductById(item.productId); if (!p) return; const fp = getFinalPrice(p); const sub = fp * item.qty; total += sub; text += `• ${p.name} — ${item.qty} шт. × ${fp} ₽ = ${sub.toFixed(2)} ₽\n`; }); text += `\n💰 *Итого: ${total.toFixed(2)} ₽*`; if (currentUser) text += `\n👤 ${currentUser.name || currentUser.email || currentUser.phone}`; return text; } async function saveOrderToFirestore(orderData) { try { const docRef = await db.collection("orders").add({ ...orderData, createdAt: firebase.firestore.FieldValue.serverTimestamp(), userId: currentUser.uid, userEmail: currentUser.email, userName: currentUser.name || '' }); return docRef.id; } catch (err) { console.error("Ошибка сохранения заказа:", err); return null; } } async function sendOrderTo(messenger) { if (!currentUser) { openAuthModal(); return; } if (cart.length === 0) { showToast('Корзина пуста'); return; } const orderItems = []; let total = 0; cart.forEach(item => { const p = getProductById(item.productId); if (!p) return; const fp = getFinalPrice(p); const sub = fp * item.qty; total += sub; orderItems.push({ productId: item.productId, name: p.name, price: fp, qty: item.qty, subtotal: sub }); }); const orderData = { items: orderItems, total: total, status: 'new', date: new Date().toISOString() }; let orderId = null; try { orderId = await saveOrderToFirestore(orderData); } catch (err) { console.error('Не удалось сохранить заказ:', err); } logAnalyticsEvent('purchase', { currency: "RUB", transaction_id: orderId || 'unknown', value: total, items: orderItems.map(item => ({ item_id: item.productId, item_name: item.name, item_category: getProductById(item.productId)?.category, price: item.price, quantity: item.qty })) }); let orderText = generateOrderText(); if (orderId) { orderText += `\n\n📋 Заказ #${orderId}`; } const text = encodeURIComponent(orderText); let url = ''; if (messenger === 'whatsapp') { url = `https://wa.me/998958239490?text=${text}`; } else { url = `https://t.me/fullsellertjmsh?text=${text}`; } window.open(url, '_blank'); cart = []; saveCart(); closeModal('orderModal'); showToast('Заказ отправлен!'); } // ---------- ИСТОРИЯ ЗАКАЗОВ ---------- async function loadUserOrders() { const container = document.getElementById('ordersHistoryContainer'); if (!currentUser) { container.innerHTML = 'Необходимо авторизоваться
'; return; } container.innerHTML = 'У вас пока нет заказов
'; return; } const statusNames = { new: 'Новый', processing: 'В обработке', shipped: 'Отправлен', delivered: 'Доставлен', cancelled: 'Отменен' }; container.innerHTML = orders.map(order => { const date = order.createdAt ? new Date(order.createdAt.seconds * 1000).toLocaleDateString('ru-RU') : '—'; return `Ошибка загрузки заказов
'; } } window.openOrderDetail = async function(orderId) { const content = document.getElementById('orderDetailContent'); content.innerHTML = 'Заказ не найден
'; return; } const order = doc.data(); const statusNames = { new: 'Новый', processing: 'В обработке', shipped: 'Отправлен', delivered: 'Доставлен', cancelled: 'Отменен' }; const date = order.createdAt ? new Date(order.createdAt.seconds * 1000).toLocaleString('ru-RU') : '—'; content.innerHTML = `Заказ #${orderId}
Статус: ${statusNames[order.status] || 'Новый'}
Дата: ${date}
Ошибка загрузки
'; } }; // ---------- АДМИН-ПАНЕЛЬ ---------- function isAdmin() { return currentUser && currentUser.email === ADMIN_EMAIL; } function updateAdminUI() { const link = document.getElementById('adminNavLink'); const mobileLink = document.getElementById('mobileAdminLink'); if (isAdmin()) { if (link) { link.style.display = 'inline'; link.onclick = (e) => { e.preventDefault(); showPage('admin'); }; } if (mobileLink) { mobileLink.style.display = 'block'; mobileLink.onclick = (e) => { e.preventDefault(); showPage('admin'); closeMobileMenu(); }; } } else { if (link) link.style.display = 'none'; if (mobileLink) mobileLink.style.display = 'none'; } } function renderAdminProducts() { const container = document.getElementById('adminProductsList'); let allP = []; for (let cat in allProducts) { allP = allP.concat(allProducts[cat].map(p => ({ ...p, category: cat }))); } if (allP.length === 0) { container.innerHTML = 'Товары не найдены.
'; return; } container.innerHTML = `| Название | Категория | Цена | Рейтинг | Остаток | Бренд | Материал | Размер | Описание | Скрыт | Действия |
|---|---|---|---|---|---|---|---|---|---|---|
Заказов пока нет
'; return; } container.innerHTML = `| ID | Пользователь | Сумма | Статус | Дата | Действие |
|---|---|---|---|---|---|
| ${o.id} | ${o.userName || o.userEmail || o.userPhone} | ${o.total?.toFixed(2)} ₽ | ${o.status || 'new'} | ${o.createdAt ? new Date(o.createdAt.seconds * 1000).toLocaleDateString('ru-RU') : '—'} |
Ошибка загрузки
'; } } window.updateOrderStatus = async function(orderId, newStatus) { try { await db.collection("orders").doc(orderId).update({ status: newStatus }); showToast('Статус обновлён'); } catch (err) { showToast('Ошибка: ' + err.message); } }; document.getElementById('saveGlobalParamsBtn').addEventListener('click', async () => { const percent = parseFloat(document.getElementById('adminDiscountPercent').value) || 0; const rub = parseFloat(document.getElementById('adminDiscountRub').value) || 0; const rating = Math.min(5, Math.max(0, parseFloat(document.getElementById('adminRating').value) || 4.2)); State.percentageDiscount = percent; State.fixedDiscountRub = rub; State.rating = rating; try { await db.collection("settings").doc("global").set({ percentageDiscount: percent, fixedDiscountRub: rub, defaultRating: rating }); showToast('Настройки сохранены'); } catch (err) { showToast('Ошибка: ' + err.message); } renderCatalog(); }); document.querySelectorAll('.admin-tab').forEach(tab => { tab.addEventListener('click', function() { document.querySelectorAll('.admin-tab').forEach(t => t.classList.remove('active')); this.classList.add('active'); const tabName = this.dataset.tab; document.getElementById('adminGlobalTab').style.display = tabName === 'global' ? 'block' : 'none'; document.getElementById('adminProductsTab').style.display = tabName === 'products' ? 'block' : 'none'; document.getElementById('adminAddTab').style.display = tabName === 'add' ? 'block' : 'none'; document.getElementById('adminOrdersTab').style.display = tabName === 'orders' ? 'block' : 'none'; if (tabName === 'products') renderAdminProducts(); if (tabName === 'orders') renderAdminOrders(); }); }); function loadGlobalSettings() { db.collection("settings").doc("global").onSnapshot(doc => { if (doc.exists) { const data = doc.data(); State.percentageDiscount = data.percentageDiscount || 0; State.fixedDiscountRub = data.fixedDiscountRub || 0; State.rating = data.defaultRating || 4.2; } renderCatalog(); }); } // ---------- ОТЗЫВЫ ---------- async function loadPurchasedProductsForReview() { const select = document.getElementById('reviewProductSelect'); select.innerHTML = ''; if (!currentUser) { select.innerHTML = ''; return; } try { const ordersSnap = await db.collection("orders").where("userId", "==", currentUser.uid).get(); const purchasedIds = new Set(); if (!ordersSnap.empty) { ordersSnap.forEach(doc => { (doc.data().items || []).forEach(item => { if (item.productId) purchasedIds.add(item.productId); }); }); } const reviewsSnap = await db.collection("reviews").where("userId", "==", currentUser.uid).get(); const reviewedIds = new Set(); if (!reviewsSnap.empty) { reviewsSnap.forEach(doc => reviewedIds.add(doc.data().productId)); } const available = [...purchasedIds].filter(id => !reviewedIds.has(id)).map(id => getProductById(id)).filter(Boolean); if (available.length === 0) select.innerHTML = ''; else select.innerHTML = '' + available.map(p => ``).join(''); } catch (err) { select.innerHTML = ''; } } function compressImage(file, maxWidth = 400, quality = 0.6) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { const canvas = document.createElement('canvas'); const scale = maxWidth / img.width; canvas.width = maxWidth; canvas.height = img.height * scale; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL('image/jpeg', quality)); }; img.onerror = reject; img.src = e.target.result; }; reader.onerror = reject; reader.readAsDataURL(file); }); } function readFileAsBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = e => resolve(e.target.result); reader.onerror = reject; reader.readAsDataURL(file); }); } function updateReviewPreview() { const container = document.getElementById('reviewAttachmentsPreview'); if (!container) return; container.innerHTML = reviewPhotoFiles.map((item, idx) => `Пока нет отзывов.
'; toggleBtn.style.display = 'none'; return; } container.innerHTML = displayReviews.map(r => { let starsHtml = ''; for (let i = 0; i < Math.floor(r.rating); i++) starsHtml += ''; if (r.rating % 1 >= 0.5) starsHtml += ''; const likes = r.likes || []; const likeCount = likes.length; const userLiked = currentUser && likes.includes(currentUser.uid); let mediaHtml = ''; if (r.media && r.media.length > 0) { mediaHtml = `