updateweb

This commit is contained in:
Local Server
2026-01-01 22:24:30 -06:00
parent 017c6376fc
commit 1919f6f8bb
185 changed files with 19860 additions and 17603 deletions

View File

@@ -525,11 +525,15 @@ function renderImageVariants() {
// Edit product
async function editProduct(id) {
try {
console.log("Loading product for edit, ID:", id);
const response = await fetch(`/api/admin/products/${id}`, {
credentials: "include",
});
const data = await response.json();
console.log("Product data loaded:", data);
if (data.success) {
const product = data.product;
@@ -561,6 +565,7 @@ async function editProduct(id) {
// Load image variants and extract unique product images
imageVariants = product.images || [];
console.log("Loaded image variants:", imageVariants);
// Build productImages array from unique image URLs in variants
const uniqueImages = {};
@@ -579,33 +584,77 @@ async function editProduct(id) {
renderImageVariants();
productModal.show();
} else {
showError(data.message || "Failed to load product");
}
} catch (error) {
console.error("Failed to load product:", error);
showError("Failed to load product details");
showError("Failed to load product details: " + error.message);
}
}
// Save product
async function saveProduct() {
const id = document.getElementById("productId").value;
const saveButton = document.getElementById("btnSaveProduct");
// Disable button and show loading state
saveButton.disabled = true;
const originalButtonHTML = saveButton.innerHTML;
saveButton.innerHTML = '<i class="bi bi-hourglass-split"></i> Saving...';
saveButton.style.opacity = "0.7";
// Get description from Quill editor
const description = quillEditor.root.innerHTML;
// Prepare images array for backend with all new fields
const images = imageVariants.map((variant, index) => ({
image_url: variant.image_url,
color_variant: variant.color_variant || null,
color_code: variant.color_code || null,
alt_text: variant.alt_text || document.getElementById("productName").value,
display_order: index,
is_primary: variant.is_primary || false,
variant_price: variant.variant_price
? parseFloat(variant.variant_price)
: null,
variant_stock: parseInt(variant.variant_stock) || 0,
}));
console.log("=== SAVE PRODUCT DEBUG ===");
console.log("Product ID:", id);
console.log("Image Variants:", imageVariants);
console.log("Product Images:", productImages);
// Prepare images array - include BOTH product images AND color variants
const images = [];
// First, add all color variants (they have color_variant/color_code)
imageVariants.forEach((variant, index) => {
images.push({
image_url: variant.image_url,
color_variant: variant.color_variant || null,
color_code: variant.color_code || null,
alt_text:
variant.alt_text || document.getElementById("productName").value,
display_order: index,
is_primary:
variant.is_primary || (index === 0 && imageVariants.length > 0),
variant_price: variant.variant_price
? parseFloat(variant.variant_price)
: null,
variant_stock: parseInt(variant.variant_stock) || 0,
});
});
// Then add regular product images (without color variants)
// Skip images that are already in imageVariants
const variantUrls = imageVariants.map((v) => v.image_url);
productImages.forEach((img, index) => {
if (!variantUrls.includes(img.url)) {
images.push({
image_url: img.url,
color_variant: null,
color_code: null,
alt_text:
img.alt_text ||
img.filename ||
document.getElementById("productName").value,
display_order: imageVariants.length + index,
is_primary: images.length === 0, // First image overall is primary
variant_price: null,
variant_stock: 0,
});
}
});
console.log("Prepared images array (variants + regular):", images);
const formData = {
name: document.getElementById("productName").value,
@@ -624,17 +673,41 @@ async function saveProduct() {
images: images,
};
console.log("Form Data:", formData);
// Validation
if (!formData.name || !formData.price) {
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
showError("Please fill in all required fields (Name and Price)");
return;
}
if (
imageVariants.length > 0 &&
imageVariants.some((v) => !v.image_url || !v.color_variant)
) {
showError("All color variants must have an image and color name selected");
// Validate that we have at least one image (either variant or regular)
if (images.length === 0) {
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
showError("Please add at least one product image");
return;
}
// Only validate image URLs exist
if (images.some((img) => !img.image_url)) {
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
showError("All images must have a valid image URL");
return;
}
// If a color_variant name is provided, require color_code
if (images.some((img) => img.color_variant && !img.color_code)) {
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
showError("All color variants must have both a color name and color code");
return;
}
@@ -642,6 +715,8 @@ async function saveProduct() {
const url = id ? `/api/admin/products/${id}` : "/api/admin/products";
const method = id ? "PUT" : "POST";
console.log("Saving product:", method, url, formData);
const response = await fetch(url, {
method: method,
headers: {
@@ -651,21 +726,58 @@ async function saveProduct() {
body: JSON.stringify(formData),
});
console.log("Response status:", response.status);
if (!response.ok) {
const errorText = await response.text();
console.error("Response error:", errorText);
throw new Error(`Server error: ${response.status} - ${errorText}`);
}
const data = await response.json();
console.log("Response data:", data);
if (data.success) {
// Change button to success state briefly
saveButton.innerHTML = '<i class="bi bi-check-circle-fill"></i> Saved!';
saveButton.style.background = "#10b981";
// Show success toast
showSuccess(
id
? "Product updated successfully! 🎉"
: "Product created successfully! 🎉"
? "Product Updated Successfully! Changes are now live on your website."
: "Product Created Successfully! Now visible on your shop page."
);
productModal.hide();
loadProducts();
// Wait a moment then close modal
setTimeout(async () => {
productModal.hide();
// Reload products to show updated data
await loadProducts();
// Clear form for next use
imageVariants = [];
productImages = [];
// Reset button
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
saveButton.style.background = "";
}, 1000);
} else {
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
showError(data.message || "Failed to save product");
}
} catch (error) {
console.error("Failed to save product:", error);
showError("Failed to save product");
saveButton.disabled = false;
saveButton.innerHTML = originalButtonHTML;
saveButton.style.opacity = "1";
showError(`Failed to save product: ${error.message}`);
}
}
@@ -841,14 +953,18 @@ function formatDate(dateString) {
}
function showSuccess(message) {
console.log("✅ SUCCESS:", message);
showToast(message, "success");
}
function showError(message) {
console.error("❌ ERROR:", message);
showToast(message, "error");
}
function showToast(message, type = "info") {
console.log("📢 Showing toast:", type, message);
// Create toast container if it doesn't exist
let container = document.getElementById("toastContainer");
if (!container) {
@@ -856,6 +972,7 @@ function showToast(message, type = "info") {
container.id = "toastContainer";
container.className = "toast-container";
document.body.appendChild(container);
console.log("Created toast container");
}
// Create toast element
@@ -883,6 +1000,7 @@ function showToast(message, type = "info") {
`;
container.appendChild(toast);
console.log("Toast appended to container");
// Auto remove after 4 seconds
setTimeout(() => {

View File

@@ -84,19 +84,19 @@ function renderUsers(users) {
${u.isactive ? "Active" : "Disabled"}</span></td>
<td>${formatDate(u.createdat)}</td>
<td>
<button class="btn btn-sm btn-info" onclick="editUser(${
<button class="btn btn-sm btn-info" onclick="editUser('${escapeHtml(
u.id
})" title="Edit User">
)}')" title="Edit User">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-warning" onclick="showChangePassword(${
<button class="btn btn-sm btn-warning" onclick="showChangePassword('${escapeHtml(
u.id
}, '${escapeHtml(u.name)}')" title="Change Password">
)}', '${escapeHtml(u.name)}')" title="Change Password">
<i class="bi bi-key"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="deleteUser(${
<button class="btn btn-sm btn-danger" onclick="deleteUser('${escapeHtml(
u.id
}, '${escapeHtml(u.name)}')" title="Delete User">
)}', '${escapeHtml(u.name)}')" title="Delete User">
<i class="bi bi-trash"></i>
</button>
</td>
@@ -195,6 +195,8 @@ async function saveUser() {
return;
}
showLoading(id ? "Updating user..." : "Creating user...");
try {
const url = id ? `/api/admin/users/${id}` : "/api/admin/users";
const method = id ? "PUT" : "POST";
@@ -206,6 +208,8 @@ async function saveUser() {
});
const data = await response.json();
hideLoading();
if (data.success) {
showSuccess(
id ? "User updated successfully" : "User created successfully"
@@ -217,6 +221,7 @@ async function saveUser() {
}
} catch (error) {
console.error("Failed to save user:", error);
hideLoading();
showError("Failed to save user");
}
}
@@ -249,6 +254,8 @@ async function changePassword() {
return;
}
showLoading("Changing password...");
try {
const response = await fetch(`/api/admin/users/${id}/password`, {
method: "PUT",
@@ -258,6 +265,8 @@ async function changePassword() {
});
const data = await response.json();
hideLoading();
if (data.success) {
showSuccess("Password changed successfully");
passwordModal.hide();
@@ -266,6 +275,7 @@ async function changePassword() {
}
} catch (error) {
console.error("Failed to change password:", error);
hideLoading();
showError("Failed to change password");
}
}
@@ -278,12 +288,16 @@ async function deleteUser(id, name) {
)
return;
showLoading("Deleting user...");
try {
const response = await fetch(`/api/admin/users/${id}`, {
method: "DELETE",
credentials: "include",
});
const data = await response.json();
hideLoading();
if (data.success) {
showSuccess("User deleted successfully");
loadUsers();
@@ -292,6 +306,7 @@ async function deleteUser(id, name) {
}
} catch (error) {
console.error("Failed to delete user:", error);
hideLoading();
showError("Failed to delete user");
}
}
@@ -332,9 +347,76 @@ function formatDate(dateString) {
});
}
// Custom notification system
function showNotification(type, title, message) {
const container = document.getElementById("notificationContainer");
const notification = document.createElement("div");
notification.className = `notification notification-${type}`;
const icon =
type === "success"
? '<i class="bi bi-check-circle-fill"></i>'
: '<i class="bi bi-exclamation-circle-fill"></i>';
notification.innerHTML = `
<div class="notification-icon">${icon}</div>
<div class="notification-content">
<div class="notification-title">${title}</div>
<div class="notification-message">${message}</div>
</div>
<button class="notification-close" onclick="closeNotification(this)">
<i class="bi bi-x"></i>
</button>
`;
container.appendChild(notification);
// Auto remove after 3 seconds
setTimeout(() => {
if (notification.parentElement) {
notification.classList.add("removing");
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 300);
}
}, 3000);
}
function closeNotification(button) {
const notification = button.closest(".notification");
notification.classList.add("removing");
setTimeout(() => {
notification.remove();
}, 300);
}
function showSuccess(message) {
alert(message);
showNotification("success", "Success!", message);
}
function showError(message) {
alert("Error: " + message);
showNotification("error", "Error", message);
}
// Loading overlay functions
function showLoading(message = "Saving...") {
const overlay = document.createElement("div");
overlay.className = "loading-overlay";
overlay.id = "loadingOverlay";
overlay.innerHTML = `
<div class="loading-spinner-container">
<div class="loading-spinner-icon"></div>
<div class="loading-spinner-text">${message}</div>
</div>
`;
document.body.appendChild(overlay);
}
function hideLoading() {
const overlay = document.getElementById("loadingOverlay");
if (overlay) {
overlay.remove();
}
}