605 lines
20 KiB
JavaScript
605 lines
20 KiB
JavaScript
// Quote Management System
|
|
class QuoteManager {
|
|
constructor() {
|
|
this.storageKey = "khy_quote_items";
|
|
this.quoteItems = this.loadQuoteItems();
|
|
this.init();
|
|
}
|
|
|
|
// Load quote items from localStorage
|
|
loadQuoteItems() {
|
|
try {
|
|
const stored = localStorage.getItem(this.storageKey);
|
|
return stored ? JSON.parse(stored) : [];
|
|
} catch (error) {
|
|
console.error("Error loading quote items:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Save quote items to localStorage
|
|
saveQuoteItems() {
|
|
try {
|
|
localStorage.setItem(this.storageKey, JSON.stringify(this.quoteItems));
|
|
this.updateQuoteBadge();
|
|
} catch (error) {
|
|
console.error("Error saving quote items:", error);
|
|
}
|
|
}
|
|
|
|
// Add item to quote
|
|
addToQuote(productData) {
|
|
const {
|
|
id,
|
|
name,
|
|
image,
|
|
color = "Default",
|
|
size = "Standard",
|
|
quantity = 1,
|
|
} = productData;
|
|
|
|
// Check if item already exists with same specifications
|
|
const existingItemIndex = this.quoteItems.findIndex(
|
|
(item) => item.id === id && item.color === color && item.size === size
|
|
);
|
|
|
|
if (existingItemIndex !== -1) {
|
|
// Update quantity of existing item
|
|
this.quoteItems[existingItemIndex].quantity += quantity;
|
|
} else {
|
|
// Add new item
|
|
const newItem = {
|
|
id,
|
|
name,
|
|
image,
|
|
color,
|
|
size,
|
|
quantity,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
this.quoteItems.push(newItem);
|
|
}
|
|
|
|
this.saveQuoteItems();
|
|
this.renderQuoteItems();
|
|
this.showAddToQuoteSuccess();
|
|
}
|
|
|
|
// Remove item from quote
|
|
removeFromQuote(itemIndex) {
|
|
this.quoteItems.splice(itemIndex, 1);
|
|
this.saveQuoteItems();
|
|
this.renderQuoteItems();
|
|
}
|
|
|
|
// Update item quantity
|
|
updateQuantity(itemIndex, newQuantity) {
|
|
if (newQuantity > 0) {
|
|
this.quoteItems[itemIndex].quantity = newQuantity;
|
|
this.saveQuoteItems();
|
|
this.renderQuoteItems();
|
|
} else {
|
|
this.removeFromQuote(itemIndex);
|
|
}
|
|
}
|
|
|
|
// Edit a quote item
|
|
editQuoteItem(itemIndex) {
|
|
const item = this.quoteItems[itemIndex];
|
|
if (!item) return;
|
|
|
|
// Create edit modal
|
|
const modal = document.createElement("div");
|
|
modal.id = "edit-quote-modal";
|
|
modal.className =
|
|
"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50";
|
|
modal.innerHTML = `
|
|
<div class="bg-white rounded-lg p-8 max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="font-playfair font-semibold text-xl text-black">Edit Quote Item</h3>
|
|
<button onclick="quoteManager.closeEditModal()" class="text-gray-500 hover:text-gray-700">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-4 mb-6">
|
|
<img src="${item.image}" alt="${
|
|
item.name
|
|
}" class="w-16 h-16 object-cover rounded-lg">
|
|
<div>
|
|
<h4 class="font-playfair font-semibold text-lg text-black">${
|
|
item.name
|
|
}</h4>
|
|
<p class="text-sm text-gray-600">Product ID: ${item.id}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="edit-quote-form" class="space-y-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Quantity</label>
|
|
<div class="flex items-center space-x-3">
|
|
<button type="button" onclick="quoteManager.decrementEditQuantity()" class="w-8 h-8 rounded-full border border-gray-300 flex items-center justify-center hover:bg-gray-100 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
|
|
</svg>
|
|
</button>
|
|
<input type="number" id="edit-quantity" value="${
|
|
item.quantity
|
|
}" min="1" class="w-20 text-center border border-gray-300 rounded-lg px-3 py-2 font-playfair">
|
|
<button type="button" onclick="quoteManager.incrementEditQuantity()" class="w-8 h-8 rounded-full border border-gray-300 flex items-center justify-center hover:bg-gray-100 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Color</label>
|
|
<select id="edit-color" class="w-full border border-gray-300 rounded-lg px-3 py-2 font-playfair">
|
|
<option value="Black" ${
|
|
item.color === "Black" ? "selected" : ""
|
|
}>Black</option>
|
|
<option value="White" ${
|
|
item.color === "White" ? "selected" : ""
|
|
}>White</option>
|
|
<option value="Brown" ${
|
|
item.color === "Brown" ? "selected" : ""
|
|
}>Brown</option>
|
|
<option value="Gray" ${
|
|
item.color === "Gray" ? "selected" : ""
|
|
}>Gray</option>
|
|
<option value="Beige" ${
|
|
item.color === "Beige" ? "selected" : ""
|
|
}>Beige</option>
|
|
<option value="Navy" ${
|
|
item.color === "Navy" ? "selected" : ""
|
|
}>Navy</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Size</label>
|
|
<select id="edit-size" class="w-full border border-gray-300 rounded-lg px-3 py-2 font-playfair">
|
|
<option value="S" ${
|
|
item.size === "S" ? "selected" : ""
|
|
}>Small (S)</option>
|
|
<option value="M" ${
|
|
item.size === "M" ? "selected" : ""
|
|
}>Medium (M)</option>
|
|
<option value="L" ${
|
|
item.size === "L" ? "selected" : ""
|
|
}>Large (L)</option>
|
|
<option value="XL" ${
|
|
item.size === "XL" ? "selected" : ""
|
|
}>Extra Large (XL)</option>
|
|
<option value="Standard" ${
|
|
item.size === "Standard" ? "selected" : ""
|
|
}>Standard</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex space-x-3 pt-4">
|
|
<button type="button" onclick="quoteManager.closeEditModal()" class="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-lg font-playfair hover:bg-gray-400 transition-colors">
|
|
Cancel
|
|
</button>
|
|
<button type="submit" class="flex-1 bg-uc-gold text-white px-4 py-2 rounded-lg font-playfair hover:bg-amber-600 transition-colors">
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
// Add form submission handler
|
|
const form = modal.querySelector("#edit-quote-form");
|
|
form.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
this.saveEditChanges(itemIndex);
|
|
});
|
|
|
|
// Close modal when clicking outside
|
|
modal.addEventListener("click", (e) => {
|
|
if (e.target === modal) {
|
|
this.closeEditModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Close edit modal
|
|
closeEditModal() {
|
|
const modal = document.getElementById("edit-quote-modal");
|
|
if (modal) {
|
|
modal.remove();
|
|
}
|
|
}
|
|
|
|
// Increment quantity in edit modal
|
|
incrementEditQuantity() {
|
|
const input = document.getElementById("edit-quantity");
|
|
if (input) {
|
|
input.value = parseInt(input.value) + 1;
|
|
}
|
|
}
|
|
|
|
// Decrement quantity in edit modal
|
|
decrementEditQuantity() {
|
|
const input = document.getElementById("edit-quantity");
|
|
if (input && parseInt(input.value) > 1) {
|
|
input.value = parseInt(input.value) - 1;
|
|
}
|
|
}
|
|
|
|
// Save changes from edit modal
|
|
saveEditChanges(itemIndex) {
|
|
const quantity = parseInt(document.getElementById("edit-quantity").value);
|
|
const color = document.getElementById("edit-color").value;
|
|
const size = document.getElementById("edit-size").value;
|
|
|
|
if (quantity < 1) {
|
|
alert("Quantity must be at least 1");
|
|
return;
|
|
}
|
|
|
|
// Update the item
|
|
this.quoteItems[itemIndex] = {
|
|
...this.quoteItems[itemIndex],
|
|
quantity: quantity,
|
|
color: color,
|
|
size: size,
|
|
};
|
|
|
|
this.saveQuoteItems();
|
|
this.renderQuoteItems();
|
|
this.closeEditModal();
|
|
|
|
// Show success message
|
|
this.showEditSuccess();
|
|
}
|
|
|
|
// Show success message for edit
|
|
showEditSuccess() {
|
|
const notification = document.createElement("div");
|
|
notification.className =
|
|
"fixed top-24 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 translate-x-full";
|
|
notification.innerHTML = `
|
|
<div class="flex items-center space-x-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
<span class="font-playfair font-semibold">Quote item updated!</span>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
notification.classList.remove("translate-x-full");
|
|
}, 100);
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => {
|
|
notification.classList.add("translate-x-full");
|
|
setTimeout(() => {
|
|
document.body.removeChild(notification);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// Clear all quote items
|
|
clearQuote() {
|
|
this.quoteItems = [];
|
|
this.saveQuoteItems();
|
|
this.renderQuoteItems();
|
|
}
|
|
|
|
// Get quote items count
|
|
getQuoteCount() {
|
|
return this.quoteItems.reduce((total, item) => total + item.quantity, 0);
|
|
}
|
|
|
|
// Update quote badge in navigation
|
|
updateQuoteBadge() {
|
|
const count = this.getQuoteCount();
|
|
const quoteLinks = document.querySelectorAll('a[href="quote.html"]');
|
|
|
|
quoteLinks.forEach((quoteLink) => {
|
|
// Remove existing badge
|
|
const existingBadge = quoteLink.querySelector(".quote-badge");
|
|
if (existingBadge) {
|
|
existingBadge.remove();
|
|
}
|
|
|
|
// Add new badge if there are items
|
|
if (count > 0) {
|
|
const badge = document.createElement("span");
|
|
badge.className =
|
|
"quote-badge absolute -top-2 -right-2 bg-uc-gold text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-semibold";
|
|
badge.textContent = count > 99 ? "99+" : count;
|
|
quoteLink.style.position = "relative";
|
|
quoteLink.appendChild(badge);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Show success message when item is added
|
|
showAddToQuoteSuccess() {
|
|
// Create success notification
|
|
const notification = document.createElement("div");
|
|
notification.className =
|
|
"fixed top-24 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 translate-x-full";
|
|
notification.innerHTML = `
|
|
<div class="flex items-center space-x-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
<span class="font-playfair font-semibold">Added to quote!</span>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
notification.classList.remove("translate-x-full");
|
|
}, 100);
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => {
|
|
notification.classList.add("translate-x-full");
|
|
setTimeout(() => {
|
|
document.body.removeChild(notification);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// Render quote items on the quote page
|
|
renderQuoteItems() {
|
|
const container = document.getElementById("quote-items-container");
|
|
const emptyMessage = document.getElementById("empty-quote-message");
|
|
const quoteActions = document.getElementById("quote-actions");
|
|
|
|
if (!container) return;
|
|
|
|
if (this.quoteItems.length === 0) {
|
|
// Show empty state
|
|
if (emptyMessage) emptyMessage.style.display = "block";
|
|
if (quoteActions) quoteActions.classList.add("hidden");
|
|
// Clear any existing items but keep the empty message
|
|
const itemsToRemove = container.querySelectorAll(".bg-gray-50");
|
|
itemsToRemove.forEach((item) => item.remove());
|
|
return;
|
|
}
|
|
|
|
// Hide empty message and show actions
|
|
if (emptyMessage) emptyMessage.style.display = "none";
|
|
if (quoteActions) quoteActions.classList.remove("hidden");
|
|
|
|
// Remove any existing items first
|
|
const itemsToRemove = container.querySelectorAll(".bg-gray-50");
|
|
itemsToRemove.forEach((item) => item.remove());
|
|
|
|
// Render new items
|
|
this.quoteItems.forEach((item, index) => {
|
|
const itemElement = document.createElement("div");
|
|
itemElement.className =
|
|
"bg-gray-50 rounded-lg p-6 border border-gray-200";
|
|
itemElement.innerHTML = `
|
|
<div class="flex items-center space-x-6">
|
|
<!-- Product Image -->
|
|
<div class="w-24 h-24 flex-shrink-0">
|
|
<img
|
|
src="${item.image}"
|
|
alt="${item.name}"
|
|
class="w-full h-full object-cover rounded-lg"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Product Details -->
|
|
<div class="flex-1">
|
|
<h3 class="font-playfair font-semibold text-xl text-black mb-2">
|
|
${item.name}
|
|
</h3>
|
|
<div class="flex items-center space-x-6 text-sm text-gray-600">
|
|
<span><strong>Color:</strong> ${item.color}</span>
|
|
<span><strong>Size:</strong> ${item.size}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quantity Controls -->
|
|
<div class="flex items-center space-x-3">
|
|
<button
|
|
onclick="quoteManager.updateQuantity(${index}, ${
|
|
item.quantity - 1
|
|
})"
|
|
class="w-8 h-8 rounded-full border border-gray-300 flex items-center justify-center hover:bg-gray-100 transition-colors"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
|
|
</svg>
|
|
</button>
|
|
<span class="font-playfair font-semibold text-lg text-black min-w-[2rem] text-center">
|
|
${item.quantity}
|
|
</span>
|
|
<button
|
|
onclick="quoteManager.updateQuantity(${index}, ${
|
|
item.quantity + 1
|
|
})"
|
|
class="w-8 h-8 rounded-full border border-gray-300 flex items-center justify-center hover:bg-gray-100 transition-colors"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Edit Button -->
|
|
<button
|
|
onclick="quoteManager.editQuoteItem(${index})"
|
|
class="text-blue-500 hover:text-blue-700 transition-colors mr-2"
|
|
title="Edit item"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Remove Button -->
|
|
<button
|
|
onclick="quoteManager.removeFromQuote(${index})"
|
|
class="text-red-500 hover:text-red-700 transition-colors"
|
|
title="Remove item"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
`;
|
|
container.appendChild(itemElement);
|
|
});
|
|
}
|
|
|
|
// Render quote summary in modal
|
|
renderQuoteSummary() {
|
|
const summaryContainer = document.getElementById("quote-summary");
|
|
if (!summaryContainer) return;
|
|
|
|
summaryContainer.innerHTML = this.quoteItems
|
|
.map(
|
|
(item) => `
|
|
<div class="flex justify-between items-center py-2 border-b border-gray-100">
|
|
<div class="flex-1">
|
|
<h5 class="font-playfair font-semibold text-base text-black">${item.name}</h5>
|
|
<p class="text-sm text-gray-600">${item.color} • ${item.size} • Qty: ${item.quantity}</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Initialize quote manager
|
|
init() {
|
|
this.updateQuoteBadge();
|
|
this.renderQuoteItems();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
// Setup event listeners
|
|
setupEventListeners() {
|
|
// Clear quote button
|
|
const clearBtn = document.getElementById("clear-quote-btn");
|
|
if (clearBtn) {
|
|
clearBtn.addEventListener("click", () => {
|
|
if (confirm("Are you sure you want to clear your quote?")) {
|
|
this.clearQuote();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Request quote button
|
|
const requestBtn = document.getElementById("request-quote-btn");
|
|
if (requestBtn) {
|
|
requestBtn.addEventListener("click", () => {
|
|
this.openQuoteModal();
|
|
});
|
|
}
|
|
|
|
// Modal close button
|
|
const closeBtn = document.getElementById("close-modal-btn");
|
|
if (closeBtn) {
|
|
closeBtn.addEventListener("click", () => {
|
|
this.closeQuoteModal();
|
|
});
|
|
}
|
|
|
|
// Modal backdrop click
|
|
const modal = document.getElementById("quote-modal");
|
|
if (modal) {
|
|
modal.addEventListener("click", (e) => {
|
|
if (e.target === modal) {
|
|
this.closeQuoteModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Quote form submission
|
|
const quoteForm = document.getElementById("quote-form");
|
|
if (quoteForm) {
|
|
quoteForm.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
this.submitQuoteRequest();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Open quote modal
|
|
openQuoteModal() {
|
|
const modal = document.getElementById("quote-modal");
|
|
if (modal) {
|
|
this.renderQuoteSummary();
|
|
modal.classList.remove("hidden");
|
|
document.body.style.overflow = "hidden";
|
|
}
|
|
}
|
|
|
|
// Close quote modal
|
|
closeQuoteModal() {
|
|
const modal = document.getElementById("quote-modal");
|
|
if (modal) {
|
|
modal.classList.add("hidden");
|
|
document.body.style.overflow = "auto";
|
|
}
|
|
}
|
|
|
|
// Submit quote request
|
|
submitQuoteRequest() {
|
|
const form = document.getElementById("quote-form");
|
|
const formData = new FormData(form);
|
|
|
|
// Get form data
|
|
const quoteData = {
|
|
name: formData.get("name"),
|
|
email: formData.get("email"),
|
|
phone: formData.get("phone"),
|
|
company: formData.get("company"),
|
|
project: formData.get("project"),
|
|
items: this.quoteItems,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
// Here you would typically send this data to your server
|
|
console.log("Quote request data:", quoteData);
|
|
|
|
// For now, just show success message
|
|
alert("Thank you for your quote request! We will get back to you soon.");
|
|
|
|
// Clear the quote and close modal
|
|
this.clearQuote();
|
|
this.closeQuoteModal();
|
|
|
|
// Reset form
|
|
form.reset();
|
|
}
|
|
}
|
|
|
|
// Global quote manager instance
|
|
const quoteManager = new QuoteManager();
|
|
|
|
// Global function to add items to quote (called from other pages)
|
|
function addToQuote(productData) {
|
|
quoteManager.addToQuote(productData);
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
// Quote manager is already initialized in constructor
|
|
});
|
|
|
|
// Version: 3.5 - Added edit functionality for quote items
|