Cấu hình 1
Cấu hình 2
Cấu hình 3
Cấu hình 4
Cấu hình 5
Xoá tất cả
Tải Xuống
Tải file ảnh
Tải file PDF
Tải file Excel
Xem và in
URL Share
So sánh
CPU - Bộ vi xử lý
Vui lòng chọn sản phẩm
Mainboard - Bo mạch chủ
Vui lòng chọn sản phẩm
RAM - Bộ nhớ trong
Vui lòng chọn sản phẩm
VGA - Card đồ họa
Vui lòng chọn sản phẩm
PSU - Nguồn máy tính
Vui lòng chọn sản phẩm
SSD - Ổ cứng
Vui lòng chọn sản phẩm
Case - Vỏ máy tính
Vui lòng chọn sản phẩm
Fan Case
Vui lòng chọn sản phẩm
Tải file ảnh
Tải file PDF
Tải file Excel
Xem và in
Thành tiền
0₫
Chọn sản phẩm
Bộ lọc
Sắp xếp
0 sản phẩm
Đang tải sản phẩm...
';
// Reset filters
document.getElementById('product-search').value = '';
document.getElementById('sort-options').value = 'name-asc';
selectedAttributeFilters = {};
// Clear any existing checkboxes
document.querySelectorAll('.attribute-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
// Get component data
const component = window.buildPcComponents.find(c => c.id == componentId);
if (!component || !component.woo_category_id) {
modalProducts.innerHTML = 'Không có danh mục sản phẩm được liên kết.
';
updateProductsCount(0);
return;
}
// AJAX request
const formData = new FormData();
formData.append('action', 'bpc_get_component_products');
formData.append('component_id', componentId);
formData.append('woo_category_id', component.woo_category_id);
formData.append('nonce', '7415bbe454');
fetch('https://giaphongpc.vn/wp-admin/admin-ajax.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success && data.data.products && data.data.products.length > 0) {
allProducts = data.data.products;
filteredProducts = [...allProducts];
// Load component attributes for filtering
componentAttributes = data.data.attributes || [];
buildAttributeFilters();
displayProductsList(filteredProducts);
updateProductsCount(filteredProducts.length);
// Add event listeners after products are loaded
setTimeout(addFilterEventListeners, 100);
} else {
modalProducts.innerHTML = 'Không tìm thấy sản phẩm nào.
';
updateProductsCount(0);
}
})
.catch(error => {
console.error('Error:', error);
modalProducts.innerHTML = 'Có lỗi xảy ra khi tải sản phẩm.
';
updateProductsCount(0);
});
}
// Build attribute filters dynamically with checkboxes
function buildAttributeFilters() {
const attributesContainer = document.getElementById('attributes-filters-container');
if (!componentAttributes || componentAttributes.length === 0) {
attributesContainer.innerHTML = 'Không có thuộc tính lọc nào.
';
return;
}
let html = '';
componentAttributes.forEach(attribute => {
if (attribute.options && attribute.options.length > 0) {
html += `
${attribute.name}
`;
attribute.options.forEach(option => {
html += `
${option.name}
`;
});
html += `
`;
}
});
attributesContainer.innerHTML = html;
}
// Handle attribute checkbox change
function onAttributeCheckboxChange() {
selectedAttributeFilters = {};
// Collect all checked values for each attribute
document.querySelectorAll('.attribute-checkbox:checked').forEach(checkbox => {
const attributeSlug = checkbox.dataset.attribute;
const value = checkbox.value;
if (!selectedAttributeFilters[attributeSlug]) {
selectedAttributeFilters[attributeSlug] = [];
}
selectedAttributeFilters[attributeSlug].push(value);
});
// Apply filters
filterProducts();
}
// Add event listeners for filter controls
function addFilterEventListeners() {
// Search input
const searchInput = document.getElementById('product-search');
if (searchInput && !searchInput.dataset.listenerAdded) {
searchInput.addEventListener('input', filterProducts);
searchInput.dataset.listenerAdded = 'true';
}
// Sort dropdown
const sortSelect = document.getElementById('sort-options');
if (sortSelect && !sortSelect.dataset.listenerAdded) {
sortSelect.addEventListener('change', sortProducts);
sortSelect.dataset.listenerAdded = 'true';
}
// Clear filters button
const clearBtn = document.querySelector('button[onclick="clearFilters()"]');
if (clearBtn && !clearBtn.dataset.listenerAdded) {
clearBtn.addEventListener('click', clearFilters);
clearBtn.dataset.listenerAdded = 'true';
}
// Attribute checkboxes (will be added dynamically)
document.querySelectorAll('.attribute-checkbox').forEach(checkbox => {
if (!checkbox.dataset.listenerAdded) {
checkbox.addEventListener('change', onAttributeCheckboxChange);
checkbox.dataset.listenerAdded = 'true';
}
});
}
// Initialize filter event listeners when modal opens
document.addEventListener('DOMContentLoaded', function() {
// Add CSS for spinner animation if not exists
if (!document.getElementById('spinner-style')) {
const style = document.createElement('style');
style.id = 'spinner-style';
style.textContent = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
document.head.appendChild(style);
}
// Add event listeners for modal elements
setTimeout(addFilterEventListeners, 100);
});
// Display products in modal as list
function displayProductsList(products) {
const modalProducts = document.getElementById('modal-products');
if (!products || products.length === 0) {
modalProducts.innerHTML = 'Không tìm thấy sản phẩm nào phù hợp.
';
return;
}
let html = '';
products.forEach(product => {
// Extract image URL
let imageUrl = '';
if (product.image) {
const imageMatch = product.image.match(/src="([^"]+)"/);
if (imageMatch) {
imageUrl = imageMatch[1];
}
}
// Format price for display and sorting
const priceText = product.price_html || 'Liên hệ';
const priceValue = parseFloat(product.price) || 0;
html += `
${imageUrl ? `` : 'No Image
'}
${product.name}
SKU: ${product.sku || 'N/A'}
${product.stock_status ? `
Tình trạng: ${product.stock_status === 'instock' ? 'Còn hàng' : 'Hết hàng'}
` : ''}
${Object.keys(product.attributes || {}).length > 0 ? `
${Object.entries(product.attributes).map(([attrSlug, attrValue]) => {
const attrInfo = componentAttributes.find(attr => attr.slug === attrSlug);
const attrName = attrInfo ? attrInfo.name : attrSlug;
return `${attrName}: ${attrValue}`;
}).join('')}
` : ''}
${product.short_description ? `${product.short_description}
` : ''}
${priceText}
`;
});
html += '
';
modalProducts.innerHTML = html;
}
// Update products count
function updateProductsCount(count) {
const countElement = document.getElementById('products-count');
if (countElement) {
countElement.textContent = count;
}
}
// Filter products based on search and attributes (removed price filter)
function filterProducts() {
const searchTerm = document.getElementById('product-search').value.toLowerCase();
filteredProducts = allProducts.filter(product => {
// Name search
const nameMatch = product.name.toLowerCase().includes(searchTerm);
// Attributes filter
let attributesMatch = true;
// Check each selected attribute filter
for (const [attributeSlug, selectedValues] of Object.entries(selectedAttributeFilters)) {
if (selectedValues.length > 0) {
// Check if product has any of the selected attribute values
const productAttributes = product.attributes || {};
const productValue = productAttributes[attributeSlug];
if (!productValue || !selectedValues.includes(productValue)) {
attributesMatch = false;
break;
}
}
}
return nameMatch && attributesMatch;
});
// Apply current sort
sortProducts();
}
// Sort products
function sortProducts() {
const sortOption = document.getElementById('sort-options').value;
filteredProducts.sort((a, b) => {
switch (sortOption) {
case 'name-asc':
return a.name.localeCompare(b.name);
case 'name-desc':
return b.name.localeCompare(a.name);
case 'price-asc':
return (parseFloat(a.price) || 0) - (parseFloat(b.price) || 0);
case 'price-desc':
return (parseFloat(b.price) || 0) - (parseFloat(a.price) || 0);
default:
return 0;
}
});
displayProductsList(filteredProducts);
updateProductsCount(filteredProducts.length);
}
// Clear all filters
function clearFilters() {
document.getElementById('product-search').value = '';
document.getElementById('sort-options').value = 'name-asc';
// Clear attribute checkboxes
selectedAttributeFilters = {};
document.querySelectorAll('.attribute-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
if (allProducts.length > 0) {
filteredProducts = [...allProducts];
sortProducts();
}
}
// Select product
function selectProduct(product) {
if (!currentComponentId) return;
// Store selected product
selectedProducts[currentComponentId] = product;
componentConfigs[currentConfig][currentComponentId] = product;
// Set default quantity if not exists
if (!productQuantities[currentComponentId]) {
productQuantities[currentComponentId] = 1;
quantityConfigs[currentConfig][currentComponentId] = 1;
}
// Update UI
updateComponentDisplay(currentComponentId, product);
updateSidebar();
// Close modal
closeProductModal();
}
// Update component display
function updateComponentDisplay(componentId, product) {
const item = document.querySelector(`[data-tax-id="${componentId}"]`);
if (!item) {
return;
}
const detailsSpan = item.querySelector('.isures-details--item');
const selectBtn = item.querySelector('.isures-select--item span');
const iconImg = item.querySelector('.isures-icon--feat img');
const quantityWrap = item.querySelector('.isures-wrap--quantity');
const qtyInput = item.querySelector('.isures-buildpc--qty');
if (detailsSpan && product) {
const productNameSpan = detailsSpan.querySelector('.product-name');
const productPriceSpan = detailsSpan.querySelector('.product-price');
const productPrice = parseFloat(product.price) || 0;
const currentQty = productQuantities[componentId] || 1;
const totalPrice = productPrice * currentQty;
// Update product name
if (productNameSpan) {
productNameSpan.textContent = product.name;
}
// Update product price
if (productPriceSpan) {
if (currentQty > 1) {
productPriceSpan.innerHTML = `
Giá: ${productPrice.toLocaleString('vi-VN')} đ × ${currentQty} = ${totalPrice.toLocaleString('vi-VN')} đ`;
} else {
productPriceSpan.innerHTML = `
Giá: ${productPrice.toLocaleString('vi-VN')} đ`;
}
productPriceSpan.style.display = 'inline';
}
if (selectBtn) {
selectBtn.textContent = 'Đổi';
}
item.classList.add('selected');
// Show quantity control
if (quantityWrap) {
quantityWrap.style.display = 'flex';
}
// Update quantity input
if (qtyInput) {
qtyInput.value = currentQty;
}
// Show minus button if quantity > 1
const minusBtn = item.querySelector('.isures-minus');
if (minusBtn) {
minusBtn.style.display = currentQty > 1 ? 'block' : 'none';
}
if (product.image && iconImg) {
const imageMatch = product.image.match(/src="([^"]+)"/);
if (imageMatch) {
iconImg.src = imageMatch[1];
}
}
}
}
// Reset component display
function resetComponentDisplay(componentId) {
const item = document.querySelector(`[data-tax-id="${componentId}"]`);
if (!item) return;
const detailsSpan = item.querySelector('.isures-details--item');
const selectBtn = item.querySelector('.isures-select--item span');
const quantityWrap = item.querySelector('.isures-wrap--quantity');
const qtyInput = item.querySelector('.isures-buildpc--qty');
const iconImg = item.querySelector('.isures-icon--feat img');
if (detailsSpan) {
const productNameSpan = detailsSpan.querySelector('.product-name');
const productPriceSpan = detailsSpan.querySelector('.product-price');
// Reset product name
if (productNameSpan) {
productNameSpan.textContent = 'Vui lòng chọn sản phẩm';
}
// Hide and clear price
if (productPriceSpan) {
productPriceSpan.style.display = 'none';
productPriceSpan.innerHTML = '';
}
// Reset button text and remove selected class
if (selectBtn) {
selectBtn.textContent = 'Chọn';
}
item.classList.remove('selected');
// Reset image to placeholder
if (iconImg) {
iconImg.src = window.buildPcPluginUrl + 'assets/images/placeholder.png';
}
// Hide quantity control
if (quantityWrap) {
quantityWrap.style.display = 'none';
}
// Reset quantity input
if (qtyInput) {
qtyInput.value = 1;
}
}
}
// Update sidebar
function updateSidebar() {
const chosenWrap = document.getElementById('chosen-wrap');
const emptyChosen = document.getElementById('empty-chosen');
const totalAmount = document.getElementById('total-amount');
const selectedCount = Object.keys(selectedProducts).length;
if (selectedCount > 0) {
chosenWrap.style.display = 'block';
emptyChosen.style.display = 'none';
// Calculate total only
let total = 0;
Object.entries(selectedProducts).forEach(([componentId, product]) => {
const quantity = productQuantities[componentId] || 1;
const productPrice = parseFloat(product.price) || 0;
const subtotal = productPrice * quantity;
total += subtotal;
});
totalAmount.textContent = total > 0 ? new Intl.NumberFormat('vi-VN').format(total) + '₫' : 'Liên hệ';
} else {
chosenWrap.style.display = 'none';
emptyChosen.style.display = 'block';
}
}
// Update product quantity
function updateQuantity(componentId, change) {
const currentQty = productQuantities[componentId] || 1;
const newQty = Math.max(1, currentQty + change);
productQuantities[componentId] = newQty;
quantityConfigs[currentConfig][componentId] = newQty;
// Update both input fields
const qtyInput = document.getElementById(`qty-input-${componentId}`);
const qtyInputNew = document.querySelector(`[data-tax-id="${componentId}"] .isures-buildpc--qty`);
if (qtyInput) {
qtyInput.value = newQty;
}
if (qtyInputNew) {
qtyInputNew.value = newQty;
}
// Show/hide minus button based on quantity
const item = document.querySelector(`[data-tax-id="${componentId}"]`);
if (item) {
const minusBtn = item.querySelector('.isures-minus');
if (minusBtn) {
minusBtn.style.display = newQty > 1 ? 'block' : 'none';
}
// Update price display
const product = selectedProducts[componentId];
if (product) {
updateComponentDisplay(componentId, product);
}
}
updateSidebar();
}
// Set specific quantity
function setQuantity(componentId, newQty) {
const qty = Math.max(1, parseInt(newQty) || 1);
productQuantities[componentId] = qty;
quantityConfigs[currentConfig][componentId] = qty;
// Update both input fields
const qtyInput = document.getElementById(`qty-input-${componentId}`);
const qtyInputNew = document.querySelector(`[data-tax-id="${componentId}"] .isures-buildpc--qty`);
if (qtyInput) {
qtyInput.value = qty;
}
if (qtyInputNew) {
qtyInputNew.value = qty;
}
// Show/hide minus button based on quantity
const item = document.querySelector(`[data-tax-id="${componentId}"]`);
if (item) {
const minusBtn = item.querySelector('.isures-minus');
if (minusBtn) {
minusBtn.style.display = qty > 1 ? 'block' : 'none';
}
// Update price display
const product = selectedProducts[componentId];
if (product) {
updateComponentDisplay(componentId, product);
}
}
updateSidebar();
}
// Remove product
function removeProduct(componentId) {
if (!confirm('Bạn có chắc muốn xóa sản phẩm này?')) return;
delete selectedProducts[componentId];
delete productQuantities[componentId];
delete componentConfigs[currentConfig][componentId];
delete quantityConfigs[currentConfig][componentId];
// Reset component display
resetComponentDisplay(componentId);
updateSidebar();
}
// Rebuild configuration
function rebuildConfiguration() {
if (Object.keys(selectedProducts).length === 0) return;
if (!confirm('Bạn có chắc muốn xóa tất cả cấu hình hiện tại?')) return;
selectedProducts = {};
productQuantities = {};
componentConfigs[currentConfig] = {};
quantityConfigs[currentConfig] = {};
// Reset all displays
updateAllComponentDisplays();
updateSidebar();
}
// Add to cart
function addToCart() {
if (Object.keys(selectedProducts).length === 0) {
alert('Vui lòng chọn ít nhất một sản phẩm.');
return;
}
// Prepare products with quantities
const cartData = [];
Object.entries(selectedProducts).forEach(([componentId, product]) => {
const quantity = productQuantities[componentId] || 1;
cartData.push({
id: product.id,
quantity: quantity
});
});
const formData = new FormData();
formData.append('action', 'bpc_add_to_cart_bulk');
formData.append('cart_data', JSON.stringify(cartData));
formData.append('nonce', '7415bbe454');
fetch('https://giaphongpc.vn/wp-admin/admin-ajax.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Đã thêm cấu hình vào giỏ hàng!');
if (data.data.cart_url && confirm('Chuyển đến giỏ hàng?')) {
window.location.href = data.data.cart_url;
}
} else {
alert('Có lỗi xảy ra: ' + (data.data || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Có lỗi xảy ra khi thêm vào giỏ hàng.');
});
}
// Export functions
function downloadImage() {
if (Object.keys(selectedProducts).length === 0) {
alert('Vui lòng chọn ít nhất một sản phẩm!');
return;
}
// Create quote container temporarily for image capture
const quoteContainer = document.createElement('div');
quoteContainer.id = 'quote-container-image';
quoteContainer.style.position = 'absolute';
quoteContainer.style.left = '-9999px';
quoteContainer.style.background = '#ffffff';
quoteContainer.style.padding = '20px';
quoteContainer.style.width = '800px';
quoteContainer.style.fontFamily = 'Arial, sans-serif';
document.body.appendChild(quoteContainer);
// Generate the same content as PDF
const settings = window.buildPcQuoteSettings || {};
const today = new Date();
const currentConfig = document.querySelector('.config-name')?.textContent || 'Cấu hình tùy chọn';
// Calculate total
let total = 0;
Object.entries(selectedProducts).forEach(([componentId, product]) => {
const quantity = productQuantities[componentId] || 1;
const price = parseFloat(product.price) || 0;
total += price * quantity;
});
// Generate HTML content - same as PDF
quoteContainer.innerHTML = `
${settings.company_logo ? `
` : ''}
${settings.company_name || 'Build PC Pro'}
${settings.company_tax_code ? `MST: ${settings.company_tax_code}
` : ''}
${settings.company_address ? `Địa chỉ: ${settings.company_address}
` : ''}
${settings.company_phone ? `Số điện thoại: ${settings.company_phone}
` : ''}
BÁO GIÁ LINH KIỆN MÁY TÍNH
STT
HÌNH ẢNH
TÊN SẢN PHẨM
SỐ LƯỢNG
ĐƠN GIÁ
THÀNH TIỀN
${Object.entries(selectedProducts).map(([componentId, product], index) => {
const quantity = productQuantities[componentId] || 1;
const price = parseFloat(product.price) || 0;
const subtotal = price * quantity;
// Extract image URL from product.image
let imageUrl = '';
if (product.image) {
const imageMatch = product.image.match(/src="([^"]+)"/);
if (imageMatch) {
imageUrl = imageMatch[1];
}
}
return `
${index + 1}
${imageUrl ? `` : 'No Image
'}
${product.name}
${quantity}
${price.toLocaleString('vi-VN')} đ
${subtotal.toLocaleString('vi-VN')} đ
`;
}).join('')}
TỔNG CỘNG:
${total.toLocaleString('vi-VN')} đ
${settings.quote_note ? `
LƯU Ý:
${settings.quote_note}
` : ''}
${settings.quote_show_warranty && settings.quote_warranty_info ? `
BẢO HÀNH:
${settings.quote_warranty_info.replace(/\n/g, '
')}
` : ''}
${settings.quote_footer_text ? `
${settings.quote_footer_text}
Ngày tạo: ${today.toLocaleDateString('vi-VN')} - Cấu hình: ${currentConfig}
` : `
Ngày tạo: ${today.toLocaleDateString('vi-VN')} - Cấu hình: ${currentConfig}
`}
`;
// Wait for images to load then capture
setTimeout(() => {
html2canvas(quoteContainer, {
scale: 2,
useCORS: true,
allowTaint: true,
backgroundColor: '#ffffff',
width: 800,
height: quoteContainer.scrollHeight
}).then(function(canvas) {
const link = document.createElement('a');
link.download = 'bao-gia-linh-kien.png';
link.href = canvas.toDataURL('image/png');
link.click();
// Remove temporary container
document.body.removeChild(quoteContainer);
}).catch(error => {
console.error('Error generating image:', error);
alert('Có lỗi xảy ra khi tạo file ảnh');
document.body.removeChild(quoteContainer);
});
}, 1000); // Wait 1 second for images to load
}
function downloadPDF() {
if (Object.keys(selectedProducts).length === 0) {
alert('Vui lòng chọn ít nhất một sản phẩm để tạo báo giá');
return;
}
// Generate professional quote table
generateQuoteTable();
}
function generateQuoteTable() {
const settings = window.buildPcQuoteSettings;
// Create a temporary container for the quote
const quoteContainer = document.createElement('div');
quoteContainer.id = 'quote-container';
quoteContainer.style.cssText = `
position: fixed;
top: -9999px;
left: -9999px;
width: 800px;
background: white;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 30px;
border: 1px solid #ddd;
`;
let total = 0;
const today = new Date();
// Calculate total
Object.entries(selectedProducts).forEach(([componentId, product]) => {
const quantity = productQuantities[componentId] || 1;
const price = parseFloat(product.price) || 0;
total += price * quantity;
});
// Generate HTML content
quoteContainer.innerHTML = `
${settings.company_logo ? `
` : ''}
${settings.company_name || 'Build PC Pro'}
${settings.company_tax_code ? `MST: ${settings.company_tax_code}
` : ''}
${settings.company_address ? `Địa chỉ: ${settings.company_address}
` : ''}
${settings.company_phone ? `Số điện thoại: ${settings.company_phone}
` : ''}
BÁO GIÁ LINH KIỆN MÁY TÍNH
STT
HÌNH ẢNH
TÊN SẢN PHẨM
SỐ LƯỢNG
ĐƠN GIÁ
THÀNH TIỀN
${Object.entries(selectedProducts).map(([componentId, product], index) => {
const quantity = productQuantities[componentId] || 1;
const price = parseFloat(product.price) || 0;
const subtotal = price * quantity;
// Extract image URL from product.image
let imageUrl = '';
if (product.image) {
const imageMatch = product.image.match(/src="([^"]+)"/);
if (imageMatch) {
imageUrl = imageMatch[1];
}
}
return `
${index + 1}
${imageUrl ? `` : 'No Image
'}
${product.name}
${quantity}
${price.toLocaleString('vi-VN')} đ
${subtotal.toLocaleString('vi-VN')} đ
`;
}).join('')}
TỔNG CỘNG:
${total.toLocaleString('vi-VN')} đ
${settings.quote_note ? `
LƯU Ý:
${settings.quote_note}
` : ''}
${settings.quote_show_warranty && settings.quote_warranty_info ? `
BẢO HÀNH:
${settings.quote_warranty_info.replace(/\n/g, '
')}
` : ''}
${settings.quote_footer_text ? `
${settings.quote_footer_text}
Ngày tạo: ${today.toLocaleDateString('vi-VN')} - Cấu hình: ${currentConfig}
` : `
Ngày tạo: ${today.toLocaleDateString('vi-VN')} - Cấu hình: ${currentConfig}
`}
`;
// Append to body temporarily
document.body.appendChild(quoteContainer);
// Capture as PDF using html2canvas
setTimeout(() => {
html2canvas(quoteContainer, {
backgroundColor: '#ffffff',
scale: 2,
useCORS: true,
allowTaint: true,
width: 800,
height: quoteContainer.scrollHeight
}).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210; // A4 width in mm
const imgHeight = (canvas.height * imgWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
// Save file
const fileName = `bao-gia-build-pc-${today.getFullYear()}${(today.getMonth() + 1).toString().padStart(2, '0')}${today.getDate().toString().padStart(2, '0')}-${currentConfig}.pdf`;
pdf.save(fileName);
// Remove temporary container
document.body.removeChild(quoteContainer);
}).catch(error => {
console.error('Error generating PDF:', error);
alert('Có lỗi xảy ra khi tạo file PDF');
document.body.removeChild(quoteContainer);
});
}, 100);
}
function downloadExcel() {
if (Object.keys(selectedProducts).length === 0) {
alert('Vui lòng chọn ít nhất một sản phẩm để xuất Excel!');
return;
}
const workbook = XLSX.utils.book_new();
const settings = window.buildPcQuoteSettings || {};
const today = new Date();
// Prepare data for Excel
const excelData = [];
let totalPrice = 0;
// Add company header
excelData.push(['BÁO GIÁ LINH KIỆN MÁY TÍNH']);
excelData.push(['']);
// Company information
if (settings.company_name) {
excelData.push(['Công ty:', settings.company_name]);
}
if (settings.company_address) {
excelData.push(['Địa chỉ:', settings.company_address]);
}
if (settings.company_tax_code) {
excelData.push(['MST:', settings.company_tax_code]);
}
if (settings.company_phone) {
excelData.push(['Số điện thoại:', settings.company_phone]);
}
excelData.push(['']);
excelData.push(['Ngày tạo:', today.toLocaleDateString('vi-VN')]);
excelData.push(['Cấu hình:', currentConfig]);
excelData.push(['']); // Empty row
// Table header
excelData.push(['STT', 'TÊN SẢN PHẨM', 'SỐ LƯỢNG', 'ĐƠN GIÁ', 'THÀNH TIỀN']);
// Add products data
let stt = 1;
Object.entries(selectedProducts).forEach(([componentId, product]) => {
const quantity = productQuantities[componentId] || 1;
const price = parseFloat(product.price) || 0;
const lineTotal = price * quantity;
totalPrice += lineTotal;
excelData.push([
stt++,
product.name || 'N/A',
quantity,
price,
lineTotal
]);
});
// Add empty row and total
excelData.push(['']);
excelData.push(['', '', '', 'TỔNG CỘNG:', totalPrice]);
// Add notes if available
if (settings.quote_note) {
excelData.push(['']);
excelData.push(['LƯU Ý:']);
excelData.push([settings.quote_note]);
}
// Add warranty info if available
if (settings.quote_show_warranty && settings.quote_warranty_info) {
excelData.push(['']);
excelData.push(['BẢO HÀNH:']);
const warrantyLines = settings.quote_warranty_info.split('\n');
warrantyLines.forEach(line => {
if (line.trim()) {
excelData.push([line.trim()]);
}
});
}
const worksheet = XLSX.utils.aoa_to_sheet(excelData);
// Set column widths
worksheet['!cols'] = [
{wch: 6}, // STT
{wch: 55}, // Tên sản phẩm
{wch: 12}, // Số lượng
{wch: 20}, // Đơn giá
{wch: 22} // Thành tiền
];
// Find key rows for styling
let titleRow = 0; // "BÁO GIÁ LINH KIỆN MÁY TÍNH"
let headerRow = -1; // Products table header
let totalRow = -1; // Grand total row
for (let i = 0; i < excelData.length; i++) {
if (excelData[i][0] === 'STT' && excelData[i][1] === 'TÊN SẢN PHẨM') {
headerRow = i;
}
if (excelData[i][0] === '' && excelData[i][3] === 'TỔNG CỘNG:') {
totalRow = i;
}
}
// Apply beautiful styling
if (!worksheet['!merges']) worksheet['!merges'] = [];
if (!worksheet['!rows']) worksheet['!rows'] = [];
// Style title row (merge and format)
const titleCell = 'A1';
worksheet['!merges'].push({s: {r: 0, c: 0}, e: {r: 0, c: 4}}); // Merge A1:E1
worksheet[titleCell].s = {
font: {
name: 'Arial',
sz: 18,
bold: true,
color: {rgb: 'FFFFFF'}
},
fill: {
type: 'pattern',
pattern: 'solid',
fgColor: {rgb: settings.quote_header_color ? settings.quote_header_color.replace('#', '') : '007BFF'}
},
alignment: {
horizontal: 'center',
vertical: 'center'
},
border: {
top: {style: 'medium', color: {rgb: '000000'}},
bottom: {style: 'medium', color: {rgb: '000000'}},
left: {style: 'medium', color: {rgb: '000000'}},
right: {style: 'medium', color: {rgb: '000000'}}
}
};
worksheet['!rows'][0] = {hpt: 30}; // Set row height
// Style company information rows (2-6)
for (let row = 2; row <= 6; row++) {
const cellA = XLSX.utils.encode_cell({r: row, c: 0});
const cellB = XLSX.utils.encode_cell({r: row, c: 1});
if (worksheet[cellA]) {
worksheet[cellA].s = {
font: {name: 'Arial', sz: 11, bold: true, color: {rgb: '2C3E50'}},
fill: {type: 'pattern', pattern: 'solid', fgColor: {rgb: 'F8F9FA'}},
alignment: {horizontal: 'left', vertical: 'center'}
};
}
if (worksheet[cellB]) {
worksheet[cellB].s = {
font: {name: 'Arial', sz: 11, color: {rgb: '34495E'}},
alignment: {horizontal: 'left', vertical: 'center'}
};
}
}
// Style table header row
if (headerRow >= 0) {
for (let col = 0; col <= 4; col++) {
const headerCell = XLSX.utils.encode_cell({r: headerRow, c: col});
if (worksheet[headerCell]) {
worksheet[headerCell].s = {
font: {
name: 'Arial',
sz: 12,
bold: true,
color: {rgb: 'FFFFFF'}
},
fill: {
type: 'pattern',
pattern: 'solid',
fgColor: {rgb: settings.quote_header_color ? settings.quote_header_color.replace('#', '') : '007BFF'}
},
alignment: {
horizontal: 'center',
vertical: 'center'
},
border: {
top: {style: 'medium', color: {rgb: 'FFFFFF'}},
bottom: {style: 'medium', color: {rgb: 'FFFFFF'}},
left: {style: 'thin', color: {rgb: 'FFFFFF'}},
right: {style: 'thin', color: {rgb: 'FFFFFF'}}
}
};
}
}
worksheet['!rows'][headerRow] = {hpt: 25}; // Set header row height
// Style product rows with alternating colors
const startDataRow = headerRow + 1;
const endDataRow = startDataRow + Object.keys(selectedProducts).length - 1;
for (let row = startDataRow; row <= endDataRow; row++) {
const isEvenRow = (row - startDataRow) % 2 === 0;
const rowColor = isEvenRow ? 'FFFFFF' : 'F8F9FA';
for (let col = 0; col <= 4; col++) {
const cell = XLSX.utils.encode_cell({r: row, c: col});
if (worksheet[cell]) {
worksheet[cell].s = {
font: {
name: 'Arial',
sz: 11,
color: {rgb: settings.quote_accent_color ? settings.quote_accent_color.replace('#', '') : '2C3E50'}
},
fill: {
type: 'pattern',
pattern: 'solid',
fgColor: {rgb: rowColor}
},
alignment: {
horizontal: col === 0 || col === 2 ? 'center' : col >= 3 ? 'right' : 'left',
vertical: 'center'
},
border: {
top: {style: 'thin', color: {rgb: 'E0E0E0'}},
bottom: {style: 'thin', color: {rgb: 'E0E0E0'}},
left: {style: 'thin', color: {rgb: 'E0E0E0'}},
right: {style: 'thin', color: {rgb: 'E0E0E0'}}
}
};
// Format currency columns
if ((col === 3 || col === 4) && typeof worksheet[cell].v === 'number') {
worksheet[cell].z = '#,##0" đ"';
worksheet[cell].s.font.bold = col === 4; // Bold for total column
}
}
}
worksheet['!rows'][row] = {hpt: 22}; // Set data row height
}
}
// Style total row
if (totalRow >= 0) {
for (let col = 0; col <= 4; col++) {
const totalCell = XLSX.utils.encode_cell({r: totalRow, c: col});
if (worksheet[totalCell]) {
worksheet[totalCell].s = {
font: {
name: 'Arial',
sz: 14,
bold: true,
color: {rgb: 'FFFFFF'}
},
fill: {
type: 'pattern',
pattern: 'solid',
fgColor: {rgb: settings.quote_header_color ? settings.quote_header_color.replace('#', '') : '007BFF'}
},
alignment: {
horizontal: col === 4 ? 'right' : 'center',
vertical: 'center'
},
border: {
top: {style: 'medium', color: {rgb: '000000'}},
bottom: {style: 'medium', color: {rgb: '000000'}},
left: {style: 'thin', color: {rgb: 'FFFFFF'}},
right: {style: 'thin', color: {rgb: 'FFFFFF'}}
}
};
// Format total amount
if (col === 4 && typeof worksheet[totalCell].v === 'number') {
worksheet[totalCell].z = '#,##0" đ"';
}
}
}
worksheet['!rows'][totalRow] = {hpt: 28}; // Set total row height
}
// Style notes sections
for (let i = 0; i < excelData.length; i++) {
if (excelData[i][0] === 'LƯU Ý:' || excelData[i][0] === 'BẢO HÀNH:') {
const noteHeaderCell = XLSX.utils.