|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>WooCommerce Product Import CSV Generator</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@preline/[email protected]/dist/preline.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
.variation-item { |
|
transition: all 0.3s ease; |
|
} |
|
.variation-item:hover { |
|
background-color: #f3f4f6; |
|
} |
|
.progress-bar { |
|
height: 6px; |
|
background-color: #e5e7eb; |
|
border-radius: 3px; |
|
overflow: hidden; |
|
} |
|
.progress-bar-fill { |
|
height: 100%; |
|
background-color: #10b981; |
|
transition: width 0.3s ease; |
|
} |
|
.toast { |
|
position: fixed; |
|
top: 20px; |
|
right: 20px; |
|
padding: 12px 20px; |
|
background-color: #10b981; |
|
color: white; |
|
border-radius: 6px; |
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
transform: translateX(200%); |
|
transition: transform 0.3s ease; |
|
z-index: 1000; |
|
} |
|
.toast.show { |
|
transform: translateX(0); |
|
} |
|
.toast.error { |
|
background-color: #ef4444; |
|
} |
|
.back-to-top { |
|
position: fixed; |
|
bottom: 30px; |
|
right: 30px; |
|
width: 50px; |
|
height: 50px; |
|
border-radius: 50%; |
|
background-color: #3b82f6; |
|
color: white; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
cursor: pointer; |
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
opacity: 0; |
|
transition: opacity 0.3s ease; |
|
z-index: 999; |
|
} |
|
.back-to-top.visible { |
|
opacity: 1; |
|
} |
|
.back-to-top:hover { |
|
background-color: #2563eb; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100"> |
|
<div class="container mx-auto px-4 py-8"> |
|
<div class="flex items-center justify-between mb-6"> |
|
<div> |
|
<h1 class="text-3xl font-bold text-gray-800">WooCommerce Product Import CSV Generator</h1> |
|
<p class="text-gray-600 mt-1">Create CSV files for bulk importing products to WooCommerce</p> |
|
</div> |
|
<div class="flex items-center space-x-2"> |
|
<span class="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"> |
|
<i class="fas fa-box-open mr-1"></i> |
|
<span id="productCount">0</span> products |
|
</span> |
|
</div> |
|
</div> |
|
|
|
<div class="bg-white rounded-lg shadow-md p-6 mb-6 border border-gray-200"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-xl font-semibold text-gray-700">Product List</h2> |
|
<button id="addProductBtn" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 transition-colors"> |
|
<i class="fas fa-plus mr-2"></i>Add Product |
|
</button> |
|
</div> |
|
|
|
<div id="productList" class="space-y-4"> |
|
<div class="text-center py-12 text-gray-500"> |
|
<i class="fas fa-box-open text-4xl mb-2"></i> |
|
<p>No products added yet</p> |
|
<button id="addFirstProductBtn" class="mt-4 px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 transition-colors"> |
|
<i class="fas fa-plus mr-2"></i>Add Your First Product |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="flex justify-end space-x-3 pt-4"> |
|
<button id="resetBtn" class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors"> |
|
<i class="fas fa-trash-alt mr-2"></i>Reset All |
|
</button> |
|
<button id="generateBtn" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 transition-colors"> |
|
<i class="fas fa-file-csv mr-2"></i>Generate CSV |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="backToTop" class="back-to-top"> |
|
<i class="fas fa-arrow-up"></i> |
|
</div> |
|
|
|
|
|
<div id="productModal" class="fixed z-10 inset-0 overflow-y-auto hidden"> |
|
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> |
|
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> |
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div> |
|
</div> |
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full"> |
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> |
|
<h3 class="text-lg font-medium text-gray-900 mb-4" id="modalTitle">Add New Product</h3> |
|
<div class="mt-2"> |
|
<form id="productForm" class="space-y-4"> |
|
<input type="hidden" id="productId"> |
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
<div> |
|
<label for="productType" class="block text-sm font-medium text-gray-700">Product Type</label> |
|
<select id="productType" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
<option value="simple">Simple</option> |
|
<option value="variable">Variable</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label for="sku" class="block text-sm font-medium text-gray-700">SKU</label> |
|
<input type="text" id="sku" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<label for="productName" class="block text-sm font-medium text-gray-700">Product Name*</label> |
|
<input type="text" id="productName" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
|
|
<div> |
|
<label for="shortDescription" class="block text-sm font-medium text-gray-700">Short Description</label> |
|
<textarea id="shortDescription" rows="2" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea> |
|
</div> |
|
|
|
<div> |
|
<label for="description" class="block text-sm font-medium text-gray-700">Description</label> |
|
<textarea id="description" rows="4" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
|
<div> |
|
<label for="regularPrice" class="block text-sm font-medium text-gray-700">Regular Price</label> |
|
<input type="number" step="0.01" id="regularPrice" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
<div> |
|
<label for="salePrice" class="block text-sm font-medium text-gray-700">Sale Price</label> |
|
<input type="number" step="0.01" id="salePrice" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
<div> |
|
<label for="stock" class="block text-sm font-medium text-gray-700">Stock Quantity</label> |
|
<input type="number" id="stock" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
</div> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
<div> |
|
<label for="weight" class="block text-sm font-medium text-gray-700">Weight (kg)</label> |
|
<input type="number" step="0.01" id="weight" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
<div> |
|
<label for="category" class="block text-sm font-medium text-gray-700">Category Slug</label> |
|
<input type="text" id="category" placeholder="e.g. electronics" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
<p class="text-xs text-gray-500 mt-1">Enter the category slug (e.g. electronics, clothing)</p> |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<label for="tags" class="block text-sm font-medium text-gray-700">Tags (comma separated)</label> |
|
<input type="text" id="tags" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
|
|
<div> |
|
<label for="imageUrl" class="block text-sm font-medium text-gray-700">Image URL</label> |
|
<input type="text" id="imageUrl" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"> |
|
</div> |
|
|
|
<div id="variationsSection" class="hidden"> |
|
<div class="flex justify-between items-center mb-2"> |
|
<label class="block text-sm font-medium text-gray-700">Weight Variations*</label> |
|
<button type="button" id="addVariationBtn" class="px-2 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"> |
|
<i class="fas fa-plus mr-1"></i>Add Variation |
|
</button> |
|
</div> |
|
<div id="variationsContainer" class="space-y-2"> |
|
|
|
</div> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> |
|
<button type="button" id="saveProductBtn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm transition-colors"> |
|
Save Product |
|
</button> |
|
<button type="button" id="cancelProductBtn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm transition-colors"> |
|
Cancel |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="csvModal" class="fixed z-10 inset-0 overflow-y-auto hidden"> |
|
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> |
|
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> |
|
<div class="absolute inset-0 bg-gray-500 opacity-75"></div> |
|
</div> |
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"> |
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> |
|
<div class="sm:flex sm:items-start"> |
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10"> |
|
<i class="fas fa-file-csv text-green-600"></i> |
|
</div> |
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> |
|
<h3 class="text-lg leading-6 font-medium text-gray-900" id="csvModalTitle">CSV Generated Successfully</h3> |
|
<div class="mt-2"> |
|
<p class="text-sm text-gray-500">Your WooCommerce compatible CSV file is ready to download.</p> |
|
<div class="mt-4"> |
|
<div class="progress-bar"> |
|
<div class="progress-bar-fill" style="width: 100%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> |
|
<button type="button" id="downloadCsvBtn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm transition-colors"> |
|
<i class="fas fa-download mr-2"></i>Download CSV |
|
</button> |
|
<button type="button" id="closeCsvModalBtn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm transition-colors"> |
|
Close |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="toast" class="toast"> |
|
<div class="flex items-center"> |
|
<i class="fas fa-check-circle mr-2"></i> |
|
<span id="toastMessage">Operation completed successfully</span> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
let products = []; |
|
let currentProductId = 0; |
|
let editingProductId = null; |
|
let csvData = null; |
|
|
|
|
|
const addProductBtn = document.getElementById('addProductBtn'); |
|
const addFirstProductBtn = document.getElementById('addFirstProductBtn'); |
|
const productList = document.getElementById('productList'); |
|
const productModal = document.getElementById('productModal'); |
|
const productForm = document.getElementById('productForm'); |
|
const saveProductBtn = document.getElementById('saveProductBtn'); |
|
const cancelProductBtn = document.getElementById('cancelProductBtn'); |
|
const resetBtn = document.getElementById('resetBtn'); |
|
const generateBtn = document.getElementById('generateBtn'); |
|
const productTypeSelect = document.getElementById('productType'); |
|
const variationsSection = document.getElementById('variationsSection'); |
|
const variationsContainer = document.getElementById('variationsContainer'); |
|
const addVariationBtn = document.getElementById('addVariationBtn'); |
|
const csvModal = document.getElementById('csvModal'); |
|
const downloadCsvBtn = document.getElementById('downloadCsvBtn'); |
|
const closeCsvModalBtn = document.getElementById('closeCsvModalBtn'); |
|
const toast = document.getElementById('toast'); |
|
const productCount = document.getElementById('productCount'); |
|
const backToTop = document.getElementById('backToTop'); |
|
|
|
|
|
addProductBtn.addEventListener('click', openAddProductModal); |
|
addFirstProductBtn.addEventListener('click', openAddProductModal); |
|
saveProductBtn.addEventListener('click', saveProduct); |
|
cancelProductBtn.addEventListener('click', closeModal); |
|
resetBtn.addEventListener('click', resetAll); |
|
generateBtn.addEventListener('click', generateCSV); |
|
productTypeSelect.addEventListener('change', toggleVariationsSection); |
|
addVariationBtn.addEventListener('click', addVariation); |
|
downloadCsvBtn.addEventListener('click', downloadCSV); |
|
closeCsvModalBtn.addEventListener('click', closeCsvModal); |
|
|
|
|
|
window.addEventListener('scroll', function() { |
|
if (window.pageYOffset > 300) { |
|
backToTop.classList.add('visible'); |
|
} else { |
|
backToTop.classList.remove('visible'); |
|
} |
|
}); |
|
|
|
backToTop.addEventListener('click', function() { |
|
window.scrollTo({ |
|
top: 0, |
|
behavior: 'smooth' |
|
}); |
|
}); |
|
|
|
|
|
function openAddProductModal() { |
|
editingProductId = null; |
|
document.getElementById('modalTitle').textContent = 'Add New Product'; |
|
productForm.reset(); |
|
variationsContainer.innerHTML = ''; |
|
document.getElementById('productId').value = ''; |
|
productModal.classList.remove('hidden'); |
|
} |
|
|
|
function openEditProductModal(productId) { |
|
const product = products.find(p => p.id === productId); |
|
if (!product) return; |
|
|
|
editingProductId = productId; |
|
document.getElementById('modalTitle').textContent = 'Edit Product'; |
|
document.getElementById('productId').value = product.id; |
|
document.getElementById('productType').value = product.type; |
|
document.getElementById('sku').value = product.sku || ''; |
|
document.getElementById('productName').value = product.name; |
|
document.getElementById('shortDescription').value = product.shortDescription || ''; |
|
document.getElementById('description').value = product.description || ''; |
|
document.getElementById('regularPrice').value = product.regularPrice || ''; |
|
document.getElementById('salePrice').value = product.salePrice || ''; |
|
document.getElementById('stock').value = product.stock || ''; |
|
document.getElementById('weight').value = product.weight || ''; |
|
document.getElementById('category').value = product.category || ''; |
|
document.getElementById('tags').value = product.tags || ''; |
|
document.getElementById('imageUrl').value = product.imageUrl || ''; |
|
|
|
|
|
variationsContainer.innerHTML = ''; |
|
if (product.type === 'variable' && product.variations && product.variations.length > 0) { |
|
product.variations.forEach(variation => { |
|
addVariation(variation.weight, variation.price); |
|
}); |
|
} |
|
|
|
toggleVariationsSection(); |
|
productModal.classList.remove('hidden'); |
|
} |
|
|
|
function closeModal() { |
|
productModal.classList.add('hidden'); |
|
} |
|
|
|
function closeCsvModal() { |
|
csvModal.classList.add('hidden'); |
|
} |
|
|
|
function toggleVariationsSection() { |
|
if (productTypeSelect.value === 'variable') { |
|
variationsSection.classList.remove('hidden'); |
|
} else { |
|
variationsSection.classList.add('hidden'); |
|
} |
|
} |
|
|
|
function addVariation(weight = '', price = '') { |
|
const variationId = Date.now(); |
|
const variationItem = document.createElement('div'); |
|
variationItem.className = 'variation-item p-3 border rounded-md'; |
|
variationItem.dataset.id = variationId; |
|
variationItem.innerHTML = ` |
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3"> |
|
<div> |
|
<label class="block text-xs font-medium text-gray-500">Weight (kg)*</label> |
|
<input type="number" step="0.01" class="variation-weight w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" value="${weight}" required> |
|
</div> |
|
<div> |
|
<label class="block text-xs font-medium text-gray-500">Price*</label> |
|
<input type="number" step="0.01" class="variation-price w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" value="${price}" required> |
|
</div> |
|
</div> |
|
<div class="mt-2 flex justify-end"> |
|
<button type="button" class="remove-variation text-xs text-red-500 hover:text-red-700 transition-colors"> |
|
<i class="fas fa-trash mr-1"></i>Remove |
|
</button> |
|
</div> |
|
`; |
|
variationsContainer.appendChild(variationItem); |
|
|
|
|
|
variationItem.querySelector('.remove-variation').addEventListener('click', function() { |
|
variationsContainer.removeChild(variationItem); |
|
}); |
|
} |
|
|
|
function saveProduct() { |
|
const productId = document.getElementById('productId').value; |
|
const productType = document.getElementById('productType').value; |
|
const sku = document.getElementById('sku').value; |
|
const productName = document.getElementById('productName').value; |
|
const shortDescription = document.getElementById('shortDescription').value; |
|
const description = document.getElementById('description').value; |
|
const regularPrice = document.getElementById('regularPrice').value; |
|
const salePrice = document.getElementById('salePrice').value; |
|
const stock = document.getElementById('stock').value; |
|
const weight = document.getElementById('weight').value; |
|
const category = document.getElementById('category').value; |
|
const tags = document.getElementById('tags').value; |
|
const imageUrl = document.getElementById('imageUrl').value; |
|
|
|
|
|
let variations = []; |
|
if (productType === 'variable') { |
|
const variationItems = variationsContainer.querySelectorAll('.variation-item'); |
|
variationItems.forEach(item => { |
|
const weight = item.querySelector('.variation-weight').value; |
|
const price = item.querySelector('.variation-price').value; |
|
if (weight && price) { |
|
variations.push({ |
|
weight: weight, |
|
price: price |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
|
|
if (!productName) { |
|
showToast('Product name is required', true); |
|
return; |
|
} |
|
|
|
if (productType === 'variable' && variations.length === 0) { |
|
showToast('Please add at least one weight variation for variable products', true); |
|
return; |
|
} |
|
|
|
|
|
const productData = { |
|
id: productId || ++currentProductId, |
|
type: productType, |
|
sku: sku, |
|
name: productName, |
|
shortDescription: shortDescription, |
|
description: description, |
|
regularPrice: regularPrice, |
|
salePrice: salePrice, |
|
stock: stock, |
|
weight: weight, |
|
category: category, |
|
tags: tags, |
|
imageUrl: imageUrl, |
|
variations: variations |
|
}; |
|
|
|
if (editingProductId !== null) { |
|
|
|
const index = products.findIndex(p => p.id === editingProductId); |
|
if (index !== -1) { |
|
products[index] = productData; |
|
showToast('Product updated successfully'); |
|
} |
|
} else { |
|
|
|
products.push(productData); |
|
showToast('Product added successfully'); |
|
} |
|
|
|
renderProductList(); |
|
closeModal(); |
|
} |
|
|
|
function renderProductList() { |
|
productList.innerHTML = ''; |
|
|
|
if (products.length === 0) { |
|
productList.innerHTML = ` |
|
<div class="text-center py-12 text-gray-500"> |
|
<i class="fas fa-box-open text-4xl mb-2"></i> |
|
<p>No products added yet</p> |
|
<button id="addFirstProductBtn" class="mt-4 px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 transition-colors"> |
|
<i class="fas fa-plus mr-2"></i>Add Your First Product |
|
</button> |
|
</div> |
|
`; |
|
|
|
|
|
document.getElementById('addFirstProductBtn').addEventListener('click', openAddProductModal); |
|
|
|
productCount.textContent = '0'; |
|
return; |
|
} |
|
|
|
productCount.textContent = products.length; |
|
|
|
products.forEach(product => { |
|
const productCard = document.createElement('div'); |
|
productCard.className = 'bg-white border rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow border-gray-200'; |
|
productCard.innerHTML = ` |
|
<div class="p-4"> |
|
<div class="flex justify-between items-start"> |
|
<div> |
|
<h3 class="font-medium text-gray-900">${product.name}</h3> |
|
<p class="text-sm text-gray-500 mt-1"> |
|
${product.type === 'simple' ? 'Simple Product' : 'Variable Product'} |
|
${product.sku ? `• SKU: ${product.sku}` : ''} |
|
</p> |
|
</div> |
|
<div class="flex space-x-2"> |
|
<button class="edit-product p-1 text-blue-500 hover:text-blue-700 transition-colors" data-id="${product.id}" title="Edit"> |
|
<i class="fas fa-edit"></i> |
|
</button> |
|
<button class="duplicate-product p-1 text-purple-500 hover:text-purple-700 transition-colors" data-id="${product.id}" title="Duplicate"> |
|
<i class="fas fa-copy"></i> |
|
</button> |
|
<button class="delete-product p-1 text-red-500 hover:text-red-700 transition-colors" data-id="${product.id}" title="Delete"> |
|
<i class="fas fa-trash"></i> |
|
</button> |
|
</div> |
|
</div> |
|
${product.type === 'variable' ? ` |
|
<div class="mt-3"> |
|
<p class="text-xs font-medium text-gray-500">Weight Variations:</p> |
|
<div class="flex flex-wrap gap-1 mt-1"> |
|
${product.variations.map(v => ` |
|
<span class="text-xs bg-gray-100 px-2 py-1 rounded">${v.weight}kg (${v.price}৳)</span> |
|
`).join('')} |
|
</div> |
|
</div> |
|
` : ''} |
|
${product.regularPrice ? ` |
|
<div class="mt-2"> |
|
<span class="text-sm font-medium">Price: ${product.regularPrice}৳</span> |
|
${product.salePrice ? `<span class="text-sm text-gray-500 ml-2 line-through">${product.salePrice}৳</span>` : ''} |
|
</div> |
|
` : ''} |
|
${product.category ? ` |
|
<div class="mt-2"> |
|
<span class="text-xs bg-gray-100 px-2 py-1 rounded">Category: ${product.category}</span> |
|
</div> |
|
` : ''} |
|
</div> |
|
`; |
|
productList.appendChild(productCard); |
|
}); |
|
|
|
|
|
document.querySelectorAll('.edit-product').forEach(btn => { |
|
btn.addEventListener('click', function() { |
|
openEditProductModal(parseInt(this.dataset.id)); |
|
}); |
|
}); |
|
|
|
document.querySelectorAll('.duplicate-product').forEach(btn => { |
|
btn.addEventListener('click', function() { |
|
duplicateProduct(parseInt(this.dataset.id)); |
|
}); |
|
}); |
|
|
|
document.querySelectorAll('.delete-product').forEach(btn => { |
|
btn.addEventListener('click', function() { |
|
deleteProduct(parseInt(this.dataset.id)); |
|
}); |
|
}); |
|
} |
|
|
|
function duplicateProduct(productId) { |
|
const product = products.find(p => p.id === productId); |
|
if (!product) return; |
|
|
|
|
|
const productCopy = JSON.parse(JSON.stringify(product)); |
|
productCopy.id = ++currentProductId; |
|
productCopy.name = `${productCopy.name} (Copy)`; |
|
|
|
|
|
products.push(productCopy); |
|
renderProductList(); |
|
showToast('Product duplicated successfully'); |
|
} |
|
|
|
function deleteProduct(productId) { |
|
if (confirm('Are you sure you want to delete this product?')) { |
|
products = products.filter(p => p.id !== productId); |
|
renderProductList(); |
|
showToast('Product deleted successfully'); |
|
} |
|
} |
|
|
|
function resetAll() { |
|
if (products.length === 0 || confirm('Are you sure you want to reset all products? This cannot be undone.')) { |
|
products = []; |
|
currentProductId = 0; |
|
renderProductList(); |
|
showToast('All products have been reset'); |
|
} |
|
} |
|
|
|
function generateCSV() { |
|
if (products.length === 0) { |
|
showToast('No products to generate CSV', true); |
|
return; |
|
} |
|
|
|
|
|
const csvRows = []; |
|
|
|
|
|
const headers = [ |
|
'ID', 'Type', 'SKU', 'Name', 'Published', 'Is featured?', 'Visibility in catalog', |
|
'Short description', 'Description', 'Date sale price starts', 'Date sale price ends', |
|
'Tax status', 'Tax class', 'In stock?', 'Stock', 'Backorders', 'Sold individually?', |
|
'Weight (kg)', 'Length (cm)', 'Width (cm)', 'Height (cm)', 'Allow customer reviews?', |
|
'Purchase note', 'Sale price', 'Regular price', 'Categories', 'Tags', 'Shipping class', |
|
'Images', 'Download limit', 'Download expiry days', 'Parent', 'Grouped products', |
|
'Upsells', 'Cross-sells', 'External URL', 'Button text', 'Position', 'Attribute 1 name', |
|
'Attribute 1 value(s)', 'Attribute 1 visible', 'Attribute 1 global', 'Attribute 2 name', |
|
'Attribute 2 value(s)', 'Attribute 2 visible', 'Attribute 2 global', 'Meta: _wpcom_is_markdown', |
|
'Download 1 name', 'Download 1 URL', 'Download 2 name', 'Download 2 URL' |
|
]; |
|
|
|
csvRows.push(headers); |
|
|
|
|
|
products.forEach(product => { |
|
if (product.type === 'simple') { |
|
|
|
const row = [ |
|
'', |
|
'simple', |
|
product.sku || '', |
|
product.name, |
|
'1', |
|
'0', |
|
'visible', |
|
product.shortDescription || '', |
|
product.description || '', |
|
'', |
|
'', |
|
'taxable', |
|
'', |
|
product.stock ? '1' : '0', |
|
product.stock || '', |
|
'0', |
|
'0', |
|
product.weight || '', |
|
'', |
|
'', |
|
'', |
|
'1', |
|
'', |
|
product.salePrice || '', |
|
product.regularPrice || '', |
|
product.category ? `${product.category}:${product.category}` : '', |
|
product.tags || '', |
|
'', |
|
product.imageUrl || '', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'0', |
|
'', |
|
'', |
|
'0', |
|
'0', |
|
'', |
|
'', |
|
'0', |
|
'0', |
|
'0', |
|
'', |
|
'', |
|
'', |
|
'', |
|
]; |
|
|
|
csvRows.push(row); |
|
} else if (product.type === 'variable') { |
|
|
|
const parentRow = [ |
|
'', |
|
'variable', |
|
product.sku || '', |
|
product.name, |
|
'1', |
|
'0', |
|
'visible', |
|
product.shortDescription || '', |
|
product.description || '', |
|
'', |
|
'', |
|
'taxable', |
|
'', |
|
'1', |
|
'', |
|
'0', |
|
'0', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'1', |
|
'', |
|
'', |
|
'', |
|
product.category ? `${product.category}:${product.category}` : '', |
|
product.tags || '', |
|
'', |
|
product.imageUrl || '', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'0', |
|
'Weight', |
|
product.variations.map(v => v.weight).join('|'), |
|
'1', |
|
'1', |
|
'', |
|
'', |
|
'0', |
|
'0', |
|
'0', |
|
'', |
|
'', |
|
'', |
|
'', |
|
]; |
|
|
|
csvRows.push(parentRow); |
|
|
|
|
|
product.variations.forEach((variation, index) => { |
|
const variationRow = [ |
|
'', |
|
'variation', |
|
product.sku ? `${product.sku}-${index + 1}` : '', |
|
`${product.name} (${variation.weight}kg)`, |
|
'1', |
|
'0', |
|
'visible', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'taxable', |
|
'', |
|
'1', |
|
'', |
|
'0', |
|
'0', |
|
variation.weight, |
|
'', |
|
'', |
|
'', |
|
'1', |
|
'', |
|
'', |
|
variation.price, |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
product.name, |
|
'', |
|
'', |
|
'', |
|
'', |
|
'', |
|
'0', |
|
'Weight', |
|
variation.weight, |
|
'1', |
|
'1', |
|
'', |
|
'', |
|
'0', |
|
'0', |
|
'0', |
|
'', |
|
'', |
|
'', |
|
'', |
|
]; |
|
|
|
csvRows.push(variationRow); |
|
}); |
|
} |
|
}); |
|
|
|
|
|
csvData = Papa.unparse(csvRows); |
|
|
|
|
|
csvModal.classList.remove('hidden'); |
|
} |
|
|
|
function downloadCSV() { |
|
if (!csvData) return; |
|
|
|
const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' }); |
|
const url = URL.createObjectURL(blob); |
|
const link = document.createElement('a'); |
|
link.setAttribute('href', url); |
|
link.setAttribute('download', 'woocommerce_products_import.csv'); |
|
link.style.visibility = 'hidden'; |
|
document.body.appendChild(link); |
|
link.click(); |
|
document.body.removeChild(link); |
|
|
|
showToast('CSV downloaded successfully'); |
|
closeCsvModal(); |
|
} |
|
|
|
function showToast(message, isError = false) { |
|
const toastMessage = document.getElementById('toastMessage'); |
|
toastMessage.textContent = message; |
|
|
|
if (isError) { |
|
toast.classList.add('error'); |
|
} else { |
|
toast.classList.remove('error'); |
|
} |
|
|
|
toast.classList.add('show'); |
|
|
|
setTimeout(() => { |
|
toast.classList.remove('show'); |
|
}, 3000); |
|
} |
|
|
|
|
|
renderProductList(); |
|
}); |
|
</script> |
|
<footer class="text-center text-gray-500 text-sm mt-8"> |
|
<p>WC Product CSV Generator App | Created by <a href="https://github.com/almahmudbd/" target="_blank" class="text-blue-500 hover:text-blue-700">almahmud</a></p> |
|
</footer> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=almahmud/wc-product-cvs-generator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |