diff --git a/README.md b/README.md index 68f54d0..446e85f 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ A **preview/staging environment** for managing KHY Furniture's product catalog w ## Features -- **Add Products**: Create new products with full details -- **Edit Products**: Modify existing product information -- **Delete Products**: Remove products with confirmation -- **Preview Products**: See how products will look before saving -- **Export JSON**: Download updated products.json file -- **Live Preview**: See exactly how changes will look on the website -- **One-Click Deploy**: Deploy changes to production with a single command -- **Automatic Backups**: Safe deployment with backup system -- **No Technical Knowledge Required**: Simple form-based interface +- ✅ **Add Products**: Create new products with full details +- ✅ **Edit Products**: Modify existing product information +- ✅ **Delete Products**: Remove products with confirmation +- ✅ **Preview Products**: See how products will look before saving +- ✅ **Export JSON**: Download updated products.json file +- ✅ **Live Preview**: See exactly how changes will look on the website +- ✅ **One-Click Deploy**: Deploy changes to production with a single command +- ✅ **Automatic Backups**: Safe deployment with backup system +- ✅ **No Technical Knowledge Required**: Simple form-based interface ## Quick Start @@ -71,7 +71,7 @@ A **preview/staging environment** for managing KHY Furniture's product catalog w 3. **Confirm Changes** → Review everything thoroughly 4. **Deploy to Production** → Run `npm run deploy` to copy updated files to the main KHY website -> **Detailed Workflow**: See [WORKFLOW.md](WORKFLOW.md) for complete instructions +> 📖 **Detailed Workflow**: See [WORKFLOW.md](WORKFLOW.md) for complete instructions ## File Structure diff --git a/admin-dashboard/assets/icons/address.png b/admin-dashboard/assets/icons/address.png deleted file mode 100644 index 28e21f5..0000000 Binary files a/admin-dashboard/assets/icons/address.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/mail.png b/admin-dashboard/assets/icons/mail.png deleted file mode 100644 index 97d2e58..0000000 Binary files a/admin-dashboard/assets/icons/mail.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/phone.png b/admin-dashboard/assets/icons/phone.png deleted file mode 100644 index 1e83e1a..0000000 Binary files a/admin-dashboard/assets/icons/phone.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/phone2.png b/admin-dashboard/assets/icons/phone2.png deleted file mode 100644 index 5ba105b..0000000 Binary files a/admin-dashboard/assets/icons/phone2.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/shipping.png b/admin-dashboard/assets/icons/shipping.png deleted file mode 100644 index 9fb9a8f..0000000 Binary files a/admin-dashboard/assets/icons/shipping.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/star.png b/admin-dashboard/assets/icons/star.png deleted file mode 100644 index 4f7a231..0000000 Binary files a/admin-dashboard/assets/icons/star.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/support.png b/admin-dashboard/assets/icons/support.png deleted file mode 100644 index ebb5cd6..0000000 Binary files a/admin-dashboard/assets/icons/support.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/time.png b/admin-dashboard/assets/icons/time.png deleted file mode 100644 index d45e13b..0000000 Binary files a/admin-dashboard/assets/icons/time.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/trophy.png b/admin-dashboard/assets/icons/trophy.png deleted file mode 100644 index c1820f9..0000000 Binary files a/admin-dashboard/assets/icons/trophy.png and /dev/null differ diff --git a/admin-dashboard/assets/icons/warranty.png b/admin-dashboard/assets/icons/warranty.png deleted file mode 100644 index 6448c7c..0000000 Binary files a/admin-dashboard/assets/icons/warranty.png and /dev/null differ diff --git a/admin-dashboard/product-detail.html b/admin-dashboard/product-detail.html deleted file mode 100644 index d6c091a..0000000 --- a/admin-dashboard/product-detail.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - Product Detail - KHY - - - - - - -
- -
- -
- -
-
-
-

- Product Details -

-
-
-
- - -
-
-
- -
- -
- -
- - -
- -
-
- - -
- -

- -

- - -

- -

- - -
- - - Request a Quote - - -
- - -
- - -
-
- -
-
- - -
- -
-
-
-
-
- - -
-
-
- -
- -
- - -
- -
- - -
- - - - - -
-
-
- - -
- - - - - -
-
- - - - - - - diff --git a/admin-dashboard/src/css/admin.css b/admin-dashboard/src/css/admin.css deleted file mode 100644 index 5595da3..0000000 --- a/admin-dashboard/src/css/admin.css +++ /dev/null @@ -1,958 +0,0 @@ -/* Admin Dashboard Styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: "Montserrat", sans-serif; - background-color: #f8f9fa; - color: #333; - line-height: 1.6; -} - -.admin-container { - max-width: 1400px; - margin: 0 auto; - padding: 20px; -} - -/* Header Styles */ -.admin-header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 30px; - border-radius: 12px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - margin-bottom: 20px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-left h1 { - font-family: "Playfair Display", serif; - font-size: 2.5rem; - font-weight: 600; - margin-bottom: 5px; -} - -.header-left p { - opacity: 0.9; - font-size: 1.1rem; -} - -.header-actions { - display: flex; - gap: 15px; -} - -/* Navigation Styles */ -.admin-nav { - background: white; - padding: 20px; - border-radius: 12px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); - margin-bottom: 20px; - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; -} - -.nav-left { - display: flex; - gap: 10px; -} - -.nav-right { - display: flex; - gap: 10px; -} - -.nav-btn { - padding: 15px 25px; - border: none; - background: #f8f9fa; - border-radius: 8px; - cursor: pointer; - transition: all 0.3s ease; - display: flex; - align-items: center; - gap: 8px; - font-weight: 500; - color: #666; -} - -.nav-btn:hover { - background: #e9ecef; - transform: translateY(-2px); -} - -.nav-btn-undo { - background: #fff3cd !important; - color: #856404 !important; - border: 1px solid #ffeaa7 !important; -} - -.nav-btn-undo:hover { - background: #ffeaa7 !important; - color: #6c5b00 !important; -} - -.nav-btn.active { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); -} - -.nav-icon { - font-size: 1.2rem; -} - -/* Main Content */ -.admin-main { - background: white; - border-radius: 12px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); - padding: 30px; - min-height: 600px; -} - -.content-section { - display: none; -} - -.content-section.active { - display: block; -} - -.section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 30px; - padding-bottom: 20px; - border-bottom: 2px solid #f8f9fa; -} - -.section-header h2 { - font-family: "Playfair Display", serif; - font-size: 2rem; - color: #333; -} - -.section-actions { - display: flex; - gap: 15px; - align-items: center; -} - -.search-input { - padding: 10px 15px; - border: 2px solid #e9ecef; - border-radius: 8px; - font-size: 14px; - width: 250px; - transition: border-color 0.3s; -} - -.search-input:focus { - outline: none; - border-color: #667eea; -} - -/* Button Styles */ -.btn { - padding: 12px 24px; - border: none; - border-radius: 8px; - cursor: pointer; - text-decoration: none; - display: inline-flex; - align-items: center; - gap: 8px; - font-weight: 500; - font-size: 14px; - transition: all 0.3s ease; - text-align: center; - justify-content: center; -} - -.btn:hover { - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); -} - -.btn-primary { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; -} - -.btn-success { - background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%); - color: white; -} - -.btn-danger { - background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%); - color: white; -} - -.btn-secondary { - background: linear-gradient(135deg, #6c757d 0%, #adb5bd 100%); - color: white; -} - -.btn-outline { - background: transparent; - color: white; - border: 2px solid white; -} - -.btn-outline:hover { - background: white; - color: #667eea; -} - -.btn-sm { - padding: 8px 16px; - font-size: 12px; -} - -/* Products Grid */ -.products-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); - gap: 25px; -} - -.product-card { - border: 1px solid #e9ecef; - border-radius: 12px; - overflow: hidden; - transition: all 0.3s ease; - background: white; -} - -.product-card:hover { - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); - transform: translateY(-5px); -} - -.product-image { - height: 200px; - overflow: hidden; - background: #f8f9fa; - display: flex; - align-items: center; - justify-content: center; -} - -.product-image img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s; -} - -.product-card:hover .product-image img { - transform: scale(1.05); -} - -.product-info { - padding: 20px; -} - -.product-info h3 { - font-family: "Playfair Display", serif; - font-size: 1.3rem; - margin-bottom: 8px; - color: #333; -} - -.product-category { - color: #667eea; - font-weight: 500; - font-size: 0.9rem; - margin-bottom: 10px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.product-description { - color: #666; - font-size: 0.9rem; - line-height: 1.5; - margin-bottom: 15px; -} - -.product-actions { - padding: 15px 20px; - display: flex; - gap: 10px; - border-top: 1px solid #f8f9fa; - background: #f8f9fa; -} - -/* Categories List */ -.categories-list { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 20px; -} - -.category-card { - border: 1px solid #e9ecef; - border-radius: 12px; - padding: 20px; - background: white; - transition: all 0.3s ease; -} - -.category-card:hover { - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - transform: translateY(-2px); -} - -.category-card h3 { - font-family: "Playfair Display", serif; - color: #333; - margin-bottom: 10px; -} - -.category-id { - color: #667eea; - font-size: 0.9rem; - font-weight: 500; - margin-bottom: 10px; -} - -.category-description { - color: #666; - font-size: 0.9rem; - margin-bottom: 15px; -} - -.category-actions { - display: flex; - gap: 10px; -} - -/* Image Manager */ -.image-manager { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 20px; -} - -.image-item { - border: 1px solid #e9ecef; - border-radius: 8px; - overflow: hidden; - background: white; -} - -.image-item img { - width: 100%; - height: 150px; - object-fit: cover; -} - -.image-item-info { - padding: 15px; -} - -.image-item-name { - font-size: 0.9rem; - color: #333; - margin-bottom: 10px; - word-break: break-all; -} - -.image-item-actions { - display: flex; - gap: 5px; -} - -/* Preview Container */ -.preview-container { - border: 1px solid #e9ecef; - border-radius: 8px; - overflow: hidden; - height: 600px; -} - -.preview-container iframe { - width: 100%; - height: 100%; - border: none; -} - -.preview-controls { - display: flex; - gap: 10px; -} - -/* Modal Styles */ -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(5px); -} - -.modal-content { - background-color: white; - margin: 2% auto; - padding: 0; - border-radius: 12px; - width: 90%; - max-width: 800px; - max-height: 90vh; - overflow-y: auto; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); -} - -.modal-header { - padding: 25px 30px; - border-bottom: 1px solid #e9ecef; - display: flex; - justify-content: space-between; - align-items: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-radius: 12px 12px 0 0; -} - -.modal-header h3 { - font-family: "Playfair Display", serif; - font-size: 1.5rem; -} - -.modal-body { - padding: 30px; -} - -.close { - font-size: 28px; - font-weight: bold; - cursor: pointer; - color: white; - opacity: 0.8; - transition: opacity 0.3s; -} - -.close:hover { - opacity: 1; -} - -/* Form Styles */ -form { - display: grid; - gap: 20px; -} - -.form-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 20px; -} - -.form-group { - display: flex; - flex-direction: column; -} - -label { - margin-bottom: 8px; - font-weight: 600; - color: #333; -} - -input, -textarea, -select { - padding: 12px 15px; - border: 2px solid #e9ecef; - border-radius: 8px; - font-size: 14px; - transition: border-color 0.3s; - font-family: inherit; -} - -input:focus, -textarea:focus, -select:focus { - outline: none; - border-color: #667eea; -} - -textarea { - resize: vertical; - min-height: 100px; -} - -/* Additional Information Section */ -.additional-info-section { - margin-top: 25px; - padding: 20px; - background-color: #f8f9fa; - border-radius: 8px; - border: 1px solid #e9ecef; -} - -.additional-info-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 15px; -} - -.additional-info-section h4 { - margin: 0; - color: #495057; - font-size: 1.1rem; - font-weight: 600; -} - -#add-new-field-btn { - font-size: 0.85rem; - padding: 6px 12px; -} - -.additional-info-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 15px; -} - -.additional-info-item { - display: flex; - flex-direction: column; -} - -.additional-info-item label { - font-size: 0.85rem; - font-weight: 500; - color: #6c757d; - margin-bottom: 5px; -} - -.additional-info-item input, -.additional-info-item textarea { - padding: 8px 12px; - border: 1px solid #ced4da; - border-radius: 4px; - font-size: 0.9rem; - background-color: white; -} - -.additional-info-item input:focus, -.additional-info-item textarea:focus { - outline: none; - border-color: #6366f1; - box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1); -} - -.additional-info-item textarea { - resize: vertical; - min-height: 60px; -} - -/* New field creation form */ -.new-field-form { - grid-column: 1 / -1; - background-color: #e3f2fd; - border: 2px dashed #2196f3; - border-radius: 8px; - padding: 15px; - margin-bottom: 15px; -} - -.new-field-form h5 { - margin: 0 0 10px 0; - color: #1976d2; - font-size: 0.95rem; -} - -.new-field-inputs { - display: grid; - grid-template-columns: 1fr 2fr auto; - gap: 10px; - align-items: end; -} - -.new-field-inputs input { - padding: 8px 12px; - border: 1px solid #2196f3; - border-radius: 4px; - font-size: 0.9rem; -} - -.new-field-inputs button { - padding: 8px 12px; - font-size: 0.85rem; -} - -/* Remove field button */ -.remove-field-btn { - background: #dc3545; - color: white; - border: none; - border-radius: 50%; - width: 20px; - height: 20px; - font-size: 14px; - font-weight: bold; - cursor: pointer; - margin-left: 8px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -.remove-field-btn:hover { - background: #c82333; -} - -/* Image Delete Confirmation Dialog */ -.image-delete-confirmation { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 2000; -} - -.confirmation-content { - background: white; - padding: 30px; - border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); - max-width: 400px; - text-align: center; -} - -.confirmation-content h4 { - margin: 0 0 15px 0; - color: #dc3545; - font-size: 1.3rem; -} - -.confirmation-content p { - margin: 10px 0; - line-height: 1.5; -} - -.confirmation-content .warning { - color: #dc3545; - font-weight: 500; - background: #f8d7da; - padding: 10px; - border-radius: 6px; - border: 1px solid #f5c6cb; -} - -.confirmation-buttons { - display: flex; - gap: 15px; - justify-content: center; - margin-top: 20px; -} - -.confirmation-buttons .btn { - min-width: 80px; -} - -.form-actions { - display: flex; - gap: 15px; - justify-content: flex-end; - margin-top: 20px; - padding-top: 20px; - border-top: 1px solid #e9ecef; -} - -/* Image Preview */ -.image-preview { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); - gap: 10px; - margin-top: 10px; -} - -.image-preview-item { - position: relative; - border-radius: 8px; - overflow: hidden; -} - -.image-preview-item img { - width: 100%; - height: 100px; - object-fit: cover; -} - -.image-preview-item .remove-btn { - position: absolute; - top: 5px; - right: 5px; - background: rgba(220, 53, 69, 0.9); - color: white; - border: 2px solid white; - border-radius: 50%; - width: 24px; - height: 24px; - cursor: pointer; - font-size: 14px; - font-weight: bold; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: all 0.2s ease; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); -} - -.image-preview-item .remove-btn:hover { - background: rgba(220, 53, 69, 1); - transform: scale(1.1); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3); -} - -/* Sync Status */ -.sync-status { - display: flex; - flex-direction: column; - gap: 15px; -} - -.sync-step { - display: flex; - align-items: center; - gap: 15px; - padding: 15px; - border-radius: 8px; - background: #f8f9fa; - transition: all 0.3s; -} - -.sync-step.active { - background: #e3f2fd; - color: #1976d2; -} - -.sync-step.completed { - background: #e8f5e8; - color: #2e7d32; -} - -.sync-step.error { - background: #ffebee; - color: #c62828; -} - -.step-icon { - font-size: 1.2rem; -} - -.step-text { - font-weight: 500; -} - -/* Responsive Design */ -@media (max-width: 768px) { - .admin-container { - padding: 10px; - } - - .admin-header { - flex-direction: column; - gap: 20px; - text-align: center; - } - - .header-actions { - flex-wrap: wrap; - justify-content: center; - } - - .admin-nav { - flex-direction: column; - } - - .section-header { - flex-direction: column; - gap: 15px; - align-items: stretch; - } - - .section-actions { - flex-direction: column; - } - - .search-input { - width: 100%; - } - - .products-grid { - grid-template-columns: 1fr; - } - - .form-row { - grid-template-columns: 1fr; - } - - .modal-content { - width: 95%; - margin: 5% auto; - } - - .form-actions { - flex-direction: column; - } - - /* Additional info responsive */ - .additional-info-grid { - grid-template-columns: 1fr; - } - - .new-field-inputs { - grid-template-columns: 1fr; - gap: 8px; - } - - .additional-info-header { - flex-direction: column; - align-items: flex-start; - gap: 10px; - } -} - -/* Loading States */ -.loading { - opacity: 0.6; - pointer-events: none; -} - -.spinner { - display: inline-block; - width: 20px; - height: 20px; - border: 3px solid #f3f3f3; - border-top: 3px solid #667eea; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -/* Success/Error Messages */ -.message { - padding: 15px 20px; - border-radius: 8px; - margin-bottom: 20px; - font-weight: 500; -} - -.message.success { - background: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; -} - -.message.error { - background: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; -} - -.message.info { - background: #d1ecf1; - color: #0c5460; - border: 1px solid #bee5eb; -} - -/* Products Modal Styles */ -.products-modal-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; - padding-bottom: 15px; - border-bottom: 1px solid #e9ecef; -} - -.products-modal-header p { - margin: 0; - font-weight: 500; - color: #6c757d; -} - -.products-list { - max-height: 400px; - overflow-y: auto; -} - -.product-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 15px; - margin-bottom: 10px; - background: #f8f9fa; - border: 1px solid #e9ecef; - border-radius: 8px; - cursor: pointer; - transition: all 0.2s ease; -} - -.product-item:hover { - background: #e9ecef; - border-color: #6c757d; -} - -.product-info h4 { - margin: 0 0 5px 0; - color: #333; - font-size: 16px; -} - -.product-description { - margin: 0; - color: #6c757d; - font-size: 14px; - line-height: 1.4; -} - -.product-actions { - display: flex; - gap: 10px; -} - -.no-products { - text-align: center; - color: #6c757d; - font-style: italic; - padding: 40px 20px; -} diff --git a/admin-dashboard/src/index.html b/admin-dashboard/src/index.html deleted file mode 100644 index 0440fa5..0000000 --- a/admin-dashboard/src/index.html +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - KHY Admin Dashboard - - - - -
- -
-
-

KHY Admin Dashboard

-

Product Management System

-
-
- - - - -
-
- - - - - -
- -
-
-

Manage your products here

-
-
- -
-
- - -
-
-

Product Management

-
- - -
-
- -
- -
-
- - -
-
-

Site Preview

-
- -
-
-
- -
-
- - - -
-
- - - - - - - - - - - - - - - - - diff --git a/admin-dashboard/src/js/admin.js b/admin-dashboard/src/js/admin.js deleted file mode 100644 index 0ea018e..0000000 --- a/admin-dashboard/src/js/admin.js +++ /dev/null @@ -1,543 +0,0 @@ -// Main Admin Dashboard Controller -class AdminDashboard { - constructor() { - this.currentSection = "categories"; - this.productManager = null; - this.categoryManager = null; - this.init(); - } - - init() { - this.setupEventListeners(); - this.initializeManagers(); - this.loadInitialData(); - - // Debug: Check if modal exists - const modal = document.getElementById("product-modal"); - console.log("Product modal element exists:", !!modal); - if (modal) { - console.log("Modal current display:", modal.style.display); - } - } - - setupEventListeners() { - // Navigation - document.querySelectorAll(".nav-btn").forEach((btn) => { - btn.addEventListener("click", (e) => { - const button = e.currentTarget; - this.switchSection(button.dataset.section); - }); - }); - - // Header actions - document.getElementById("preview-btn").addEventListener("click", () => { - this.switchSection("preview"); - }); - - document.getElementById("sync-btn").addEventListener("click", () => { - this.syncToMainSite(); - }); - - document.getElementById("quick-sync-btn").addEventListener("click", () => { - this.quickSync(); - }); - - document.getElementById("logout-btn").addEventListener("click", () => { - this.logout(); - }); - - document - .getElementById("undo-changes-btn") - .addEventListener("click", () => { - this.undoChanges(); - }); - - // Preview control buttons - const catalogBtn = document.getElementById("preview-catalog"); - - if (catalogBtn) { - catalogBtn.addEventListener("click", () => { - this.loadPreview("catalog"); - }); - } - - // Modal close handlers - document.querySelectorAll(".close").forEach((closeBtn) => { - closeBtn.addEventListener("click", (e) => { - const modal = e.target.closest(".modal"); - modal.style.display = "none"; - }); - }); - - // Close modal when clicking outside - window.addEventListener("click", (e) => { - if ( - e.target.classList.contains("modal") && - e.target === e.currentTarget - ) { - e.target.style.display = "none"; - } - }); - - // Keyboard shortcuts - document.addEventListener("keydown", (e) => { - if (e.key === "Escape") { - this.closeAllModals(); - } - }); - } - - initializeManagers() { - this.productManager = new ProductManager(this); - this.categoryManager = new CategoryManager(this); - } - - async loadInitialData() { - try { - await this.productManager.loadData(); - await this.categoryManager.loadData(); - - // Render categories immediately since Categories is the default active section - this.categoryManager.renderCategories(); - - this.showMessage("Dashboard loaded successfully", "success"); - } catch (error) { - this.showMessage( - "Error loading dashboard data: " + error.message, - "error" - ); - } - } - - switchSection(sectionName) { - // Update navigation - document.querySelectorAll(".nav-btn").forEach((btn) => { - btn.classList.remove("active"); - }); - - // Only update nav button if it's a regular section - if (sectionName !== "category-products") { - const navBtn = document.querySelector(`[data-section="${sectionName}"]`); - if (navBtn) { - navBtn.classList.add("active"); - } - } - - // Update content - hide all sections first - document.querySelectorAll(".content-section").forEach((section) => { - section.classList.remove("active"); - section.style.display = "none"; - }); - - // Show the target section - const targetSection = document.getElementById(`${sectionName}-section`); - if (targetSection) { - targetSection.classList.add("active"); - targetSection.style.display = "block"; - } - - this.currentSection = sectionName; - - // Load section-specific data - this.loadSectionData(sectionName); - } - - loadSectionData(sectionName) { - switch (sectionName) { - case "categories": - this.categoryManager.renderCategories(); - break; - case "products": - this.productManager.renderProducts(); - break; - case "preview": - this.loadPreview(); - break; - } - } - - loadPreview(type = "catalog") { - const iframe = document.getElementById("preview-frame"); - const timestamp = new Date().getTime(); - - if (type === "catalog") { - iframe.src = `../preview/catalog.html?t=${timestamp}`; - } else if (type === "detail") { - iframe.src = `../preview/detail.html?t=${timestamp}`; - } - } - - async syncToMainSite() { - const syncModal = document.getElementById("sync-modal"); - syncModal.style.display = "block"; - - try { - // Step 1: Copy admin dashboard data to main website - if (this.productManager) { - await this.copyDataToMainWebsite(); - } - - // Step 2: Proceed with main site sync - await this.simulateSyncProcess(); - this.showMessage("Successfully synced to main site!", "success"); - } catch (error) { - this.showMessage("Sync failed: " + error.message, "error"); - } finally { - syncModal.style.display = "none"; - } - } - - async quickSync() { - try { - console.log("⚡ QUICK SYNC STARTED"); - - // Get the current data from localStorage backup - const backupData = localStorage.getItem("khy_products_backup"); - if (!backupData) { - this.showMessage( - "No changes to sync. Save a product first!", - "warning" - ); - return; - } - - const data = JSON.parse(backupData); - console.log("📦 Syncing data:", data.products.length, "products"); - - // Create a simple copy command for the user - const dataStr = JSON.stringify(data, null, 2); - const dataBlob = new Blob([dataStr], { type: "application/json" }); - const url = URL.createObjectURL(dataBlob); - - // Create download link - const downloadLink = document.createElement("a"); - downloadLink.href = url; - downloadLink.download = "products_sync.json"; - downloadLink.style.display = "none"; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - URL.revokeObjectURL(url); - - // Show instructions - this.showMessage( - "File downloaded! Copy it to data/products.json", - "info" - ); - console.log("📋 QUICK SYNC INSTRUCTIONS:"); - console.log("1. File 'products_sync.json' has been downloaded"); - console.log("2. Copy it to replace: data/products.json"); - console.log("3. Refresh the preview to see changes"); - } catch (error) { - console.error("❌ Quick sync failed:", error); - this.showMessage("Quick sync failed: " + error.message, "error"); - } - } - - async copyDataToMainWebsite() { - try { - console.log("🔄 COPYING DATA TO MAIN WEBSITE"); - - // Get the current data from localStorage backup - const backupData = localStorage.getItem("khy_products_backup"); - if (!backupData) { - console.log("⚠️ No backup data found in localStorage"); - return; - } - - const data = JSON.parse(backupData); - console.log("📦 Data to copy:", data.products.length, "products"); - - // Create a simple file copy using Node.js script - // This will be handled by the sync-to-main.js script - console.log("✅ Data ready for sync - will be processed by sync script"); - } catch (error) { - console.error("❌ Error preparing data for sync:", error); - throw error; - } - } - - async syncTemporaryChangesToLocal() { - try { - // Check if there are temporary changes to sync - const temporaryChanges = - await window.storageManager.getTemporaryChanges(); - - if ( - temporaryChanges.products && - Object.keys(temporaryChanges.products).length > 0 - ) { - console.log("🔄 Syncing temporary changes to local files..."); - - // Handle image changes first - if (temporaryChanges.imageChanges) { - console.log("📸 Processing image changes..."); - await this.processImageChanges(temporaryChanges.imageChanges); - } - - // Apply changes to actual products array - Object.values(temporaryChanges.products).forEach((tempProduct) => { - const existingIndex = this.productManager.products.findIndex( - (p) => p.id === tempProduct.id - ); - - if (existingIndex >= 0) { - // Update existing product - this.productManager.products[existingIndex] = { - ...this.productManager.products[existingIndex], - ...tempProduct, - }; - } else { - // Add new product - this.productManager.products.push(tempProduct); - } - }); - - // Save to actual JSON file - await this.productManager.saveData(); - - // Clear temporary storage - await window.storageManager.clearAllTemporaryData(); - - console.log("✅ Temporary changes synced to local files successfully"); - } - } catch (error) { - console.error("❌ Error syncing temporary changes:", error); - } - } - - async processImageChanges(imageChanges) { - console.log("📸 PROCESSING IMAGE CHANGES:"); - console.log("Image changes:", imageChanges); - - for (const [productId, changes] of Object.entries(imageChanges)) { - console.log(`📦 Processing images for product ${productId}:`, changes); - - // Handle new image uploads - if (changes.newImages && changes.newImages.length > 0) { - console.log("📁 Uploading new images:", changes.newImages); - for (const imageRef of changes.newImages) { - // Get the actual file from IndexedDB - const imageData = await window.storageManager.getImageFile( - imageRef.imageId - ); - if (imageData) { - await this.uploadImageFile(imageData); - } - } - } - - // Handle deleted images - if (changes.deletedImages && changes.deletedImages.length > 0) { - console.log("🗑️ Deleting images:", changes.deletedImages); - for (const deletedImage of changes.deletedImages) { - await this.deleteImageFile(deletedImage); - } - } - } - } - - async uploadImageFile(imageData) { - console.log("📤 UPLOADING IMAGE FILE:", imageData.name); - - // In a real implementation, you would: - // 1. Create FormData with the file - // 2. Upload to server/assets directory - // 3. Return the new file path - // 4. Handle file naming conflicts - - // For now, simulate the upload process - const newPath = `assets/images/products/uploaded/${Date.now()}_${ - imageData.name - }`; - console.log(`📁 File would be uploaded to: ${newPath}`); - console.log(`📁 File size: ${imageData.size} bytes`); - console.log(`📁 File type: ${imageData.type}`); - - // TODO: Implement actual file upload - // The actual file is in imageData.file - return newPath; - } - - async deleteImageFile(deletedImage) { - console.log("🗑️ DELETING IMAGE FILE:", deletedImage.path); - - // In a real implementation, you would: - // 1. Check if file exists - // 2. Delete the file from assets directory - // 3. Handle any errors gracefully - - // For now, simulate the deletion process - console.log(`🗑️ File would be deleted: ${deletedImage.path}`); - - // TODO: Implement actual file deletion - return true; - } - - async simulateSyncProcess() { - const steps = [ - "sync-step-1", - "sync-step-2", - "sync-step-3", - "sync-step-4", - "sync-step-5", - "sync-step-6", - ]; - - for (let i = 0; i < steps.length; i++) { - const step = document.getElementById(steps[i]); - step.classList.add("active"); - - // Simulate processing time - await new Promise((resolve) => setTimeout(resolve, 1000)); - - step.classList.remove("active"); - step.classList.add("completed"); - } - - // In a real implementation, this would call the actual sync script - console.log("Sync process completed"); - } - - logout() { - if (confirm("Are you sure you want to logout?")) { - // In a real implementation, this would clear authentication - window.location.href = "login.html"; - } - } - - undoChanges() { - if ( - confirm( - "Are you sure you want to undo all unsaved changes? This action cannot be undone." - ) - ) { - // Call the productManager's undo method - if (this.productManager) { - this.productManager.undoTemporaryChanges(); - } - } - } - - closeAllModals() { - document.querySelectorAll(".modal").forEach((modal) => { - modal.style.display = "none"; - }); - } - - showMessage(message, type = "info") { - // Remove existing messages - const existingMessages = document.querySelectorAll(".message"); - existingMessages.forEach((msg) => msg.remove()); - - // Create new message - const messageDiv = document.createElement("div"); - messageDiv.className = `message ${type}`; - messageDiv.textContent = message; - - // Insert at the top of the main content - const main = document.querySelector(".admin-main"); - main.insertBefore(messageDiv, main.firstChild); - - // Auto-remove after 5 seconds - setTimeout(() => { - messageDiv.remove(); - }, 5000); - } - - // Utility method for other managers to use - getData() { - return { - products: this.productManager.products, - categories: this.categoryManager.categories, - }; - } - - // Utility method to refresh current section - refreshCurrentSection() { - this.loadSectionData(this.currentSection); - } - - async undoChanges() { - const undoStack = JSON.parse( - localStorage.getItem("khy_undo_stack") || "[]" - ); - - if (undoStack.length === 0) { - this.showMessage("No changes to undo.", "info"); - return; - } - - const lastChange = undoStack[undoStack.length - 1]; - const confirmMessage = `Undo last change: ${lastChange.action.toUpperCase()} "${ - lastChange.newData.name - }"?\n\nThis action cannot be undone.`; - - if (confirm(confirmMessage)) { - try { - await this.performUndo(lastChange); - - // Remove the undone change from the stack - undoStack.pop(); - localStorage.setItem("khy_undo_stack", JSON.stringify(undoStack)); - - this.showMessage( - `Successfully undid: ${lastChange.action} "${lastChange.newData.name}"`, - "success" - ); - - // Refresh the current view - this.productManager.refreshCurrentView(); - } catch (error) { - console.error("❌ ERROR DURING UNDO:", error); - this.showMessage("Error during undo: " + error.message, "error"); - } - } - } - - async performUndo(changeEntry) { - console.log("⏪ PERFORMING UNDO:", changeEntry); - - if (changeEntry.action === "edit") { - // Restore the original product data - const index = this.productManager.products.findIndex( - (p) => p.id === changeEntry.productId - ); - if (index !== -1) { - this.productManager.products[index] = changeEntry.originalData; - console.log("✅ Restored original product data"); - } - } else if (changeEntry.action === "add") { - // Remove the added product - const index = this.productManager.products.findIndex( - (p) => p.id === changeEntry.productId - ); - if (index !== -1) { - this.productManager.products.splice(index, 1); - console.log("✅ Removed added product"); - } - } - - // Save the changes - await this.productManager.saveData(); - console.log("💾 Undo changes saved to file"); - } -} - -// Initialize dashboard when DOM is loaded -document.addEventListener("DOMContentLoaded", () => { - window.adminDashboard = new AdminDashboard(); -}); - -// Global utility functions -window.showMessage = (message, type) => { - if (window.adminDashboard) { - window.adminDashboard.showMessage(message, type); - } -}; - -window.refreshDashboard = () => { - if (window.adminDashboard) { - window.adminDashboard.refreshCurrentSection(); - } -}; diff --git a/admin-dashboard/src/js/categoryManager.js b/admin-dashboard/src/js/categoryManager.js deleted file mode 100644 index afa48ca..0000000 --- a/admin-dashboard/src/js/categoryManager.js +++ /dev/null @@ -1,459 +0,0 @@ -// Category Management Class -class CategoryManager { - constructor(dashboard) { - this.dashboard = dashboard; - this.categories = []; - this.currentCategory = null; - this.setupEventListeners(); - } - - setupEventListeners() { - // Add category button - removed from UI - // document - // .getElementById("add-category-btn") - // .addEventListener("click", () => { - // this.showCategoryModal(); - // }); - - // Category form submit - document.getElementById("category-form").addEventListener("submit", (e) => { - e.preventDefault(); - this.saveCategory(); - }); - - // Cancel button in category modal - document.addEventListener("click", (e) => { - if (e.target.id === "cancel-category-btn") { - e.preventDefault(); - this.hideCategoryModal(); - } - }); - - // View Products buttons - use event delegation - document.addEventListener("click", (e) => { - if (e.target.matches("[data-category-id]")) { - const categoryId = e.target.getAttribute("data-category-id"); - this.viewCategoryProducts(categoryId); - } - }); - - // Back to Categories button - document.addEventListener("click", (e) => { - if (e.target.id === "back-to-categories-btn") { - this.backToHome(); - } - }); - - // Add Product to Category button - document.addEventListener("click", (e) => { - if (e.target.id === "add-product-to-category-btn") { - const categoryId = e.target.getAttribute("data-category-id"); - this.addProductToCategory(categoryId); - } - }); - - // Edit Product buttons in category view - use event delegation - document.addEventListener("click", (e) => { - console.log("Click detected on:", e.target); - // Check if the clicked element or its parent has the edit-product action - const editButton = e.target.closest("[data-action='edit-product']"); - if (editButton) { - console.log("Edit product button clicked!"); - e.stopPropagation(); - const productId = editButton.getAttribute("data-product-id"); - console.log("Product ID:", productId); - this.editProduct(productId); - } - }); - } - - async loadData() { - try { - const response = await fetch("../data/products.json"); - const data = await response.json(); - this.categories = data.categories || []; - } catch (error) { - console.error("Error loading category data:", error); - throw error; - } - } - - renderCategories() { - const container = document.getElementById("categories-list"); - - if (this.categories.length === 0) { - container.innerHTML = ` -
-

No categories found

-

Add your first category to get started!

- -
- `; - return; - } - - container.innerHTML = ""; - this.categories.forEach((category) => { - const categoryCard = this.createCategoryCard(category); - container.appendChild(categoryCard); - }); - } - - createCategoryCard(category) { - const card = document.createElement("div"); - card.className = "category-card"; - - // Count products in this category - const productCount = this.dashboard.productManager.products.filter( - (p) => p.category === category.id - ).length; - - card.innerHTML = ` -
-

${category.name}

- ${category.id} -
-

${ - category.description || "No description" - }

-
- ${productCount} product${ - productCount !== 1 ? "s" : "" - } -
-
- -
- `; - return card; - } - - showCategoryModal(category = null) { - const modal = document.getElementById("category-modal"); - const title = document.getElementById("category-modal-title"); - const form = document.getElementById("category-form"); - - this.currentCategory = category; - - if (category) { - title.textContent = "Edit Category"; - this.populateForm(category); - } else { - title.textContent = "Add Category"; - form.reset(); - } - - modal.style.display = "block"; - } - - hideCategoryModal() { - document.getElementById("category-modal").style.display = "none"; - this.currentCategory = null; - } - - populateForm(category) { - document.getElementById("category-id").value = category.id; - document.getElementById("category-name").value = category.name; - document.getElementById("category-description").value = - category.description || ""; - } - - async saveCategory() { - const formData = new FormData(document.getElementById("category-form")); - const categoryData = { - id: formData.get("id"), - name: formData.get("name"), - description: formData.get("description"), - }; - - try { - // Validate required fields - if (!categoryData.id || !categoryData.name) { - throw new Error("Category ID and name are required"); - } - - // Check for duplicate ID (only for new categories) - if (!this.currentCategory) { - const existingCategory = this.categories.find( - (cat) => cat.id === categoryData.id - ); - if (existingCategory) { - throw new Error("A category with this ID already exists"); - } - } - - // Update or add category - const existingIndex = this.categories.findIndex( - (cat) => cat.id === categoryData.id - ); - if (existingIndex >= 0) { - this.categories[existingIndex] = { - ...this.categories[existingIndex], - ...categoryData, - }; - } else { - this.categories.push(categoryData); - } - - await this.saveData(); - this.renderCategories(); - this.hideCategoryModal(); - this.dashboard.showMessage("Category saved successfully!", "success"); - - // Update product manager's category select - if (this.dashboard.productManager) { - this.dashboard.productManager.populateCategorySelect(); - } - } catch (error) { - this.dashboard.showMessage( - "Error saving category: " + error.message, - "error" - ); - } - } - - editCategory(id) { - const category = this.categories.find((cat) => cat.id === id); - if (category) { - this.showCategoryModal(category); - } - } - - viewCategoryProducts(categoryId) { - const category = this.categories.find((cat) => cat.id === categoryId); - if (!category) return; - - // Get products in this category - const productsInCategory = this.dashboard.productManager.products.filter( - (p) => p.category === categoryId - ); - - // Switch to the dedicated category products section - this.dashboard.switchSection("category-products"); - - // Render the category products - this.showCategoryProductsScreen(category, productsInCategory); - } - - showCategoryProductsScreen(category, products) { - console.log("showCategoryProductsScreen called with category:", category); - console.log("Products in category:", products); - - // Update the section title - const title = document.getElementById("category-products-title"); - if (title) { - title.textContent = `${category.name} Products`; - } - - // Update the product count - const productCount = document.getElementById("category-product-count"); - if (productCount) { - productCount.textContent = `${products.length} product${ - products.length !== 1 ? "s" : "" - } in this category`; - } - - // Update the add product button - const addButton = document.getElementById("add-product-to-category-btn"); - if (addButton) { - addButton.setAttribute("data-category-id", category.id); - } - - // Update the products list - const productsList = document.getElementById("category-products-list"); - if (productsList) { - productsList.innerHTML = - products.length === 0 - ? '

No products in this category yet. Click "Add Product to this Category" to get started!

' - : products - .map((product) => { - console.log( - "Rendering product:", - product.name, - "with ID:", - product.id - ); - return ` -
-
-

${product.name}

-

${ - product.description || "No description" - }

-
-
- -
-
- `; - }) - .join(""); - } - } - - backToHome() { - // Switch back to home section - this.dashboard.switchSection("categories"); - } - - setupModalEventListeners() { - const modal = document.getElementById("products-modal"); - if (!modal) return; - - // Close modal - modal.addEventListener("click", (e) => { - if ( - e.target.matches("[data-action='close-modal']") || - e.target.classList.contains("modal") - ) { - modal.style.display = "none"; - } - }); - - // Add product to category - modal.addEventListener("click", (e) => { - if (e.target.matches("[data-action='add-product']")) { - const categoryId = e.target.getAttribute("data-category-id"); - this.addProductToCategory(categoryId); - } - }); - - // View product details - modal.addEventListener("click", (e) => { - if (e.target.matches("[data-action='view-product']")) { - const productId = e.target.getAttribute("data-product-id"); - this.viewProductDetails(productId); - } - }); - - // Edit product - modal.addEventListener("click", (e) => { - if (e.target.matches("[data-action='edit-product']")) { - e.stopPropagation(); - const productId = e.target.getAttribute("data-product-id"); - this.editProduct(productId); - } - }); - } - - addProductToCategory(categoryId) { - // Show the product modal directly without switching sections - this.dashboard.productManager.showProductModal(); - - // Pre-select the category in the form after the modal is shown - setTimeout(() => { - const categorySelect = document.getElementById("product-category"); - if (categorySelect) { - categorySelect.value = categoryId; - } - }, 200); - } - - viewProductDetails(productId) { - // Close the products modal immediately - const modal = document.getElementById("products-modal"); - if (modal) { - modal.remove(); - } - - // Switch to products section and show product details - this.dashboard.switchSection("products"); - // You could implement a product details view here - this.dashboard.showMessage( - `Viewing product details for ID: ${productId}`, - "info" - ); - } - - editProduct(productId) { - console.log("editProduct called with ID:", productId); - console.log("Dashboard productManager:", this.dashboard.productManager); - // Edit the product directly (we're already in the products section) - this.dashboard.productManager.editProduct(productId); - } - - async deleteCategory(id) { - const category = this.categories.find((cat) => cat.id === id); - if (!category) return; - - // Check if category has products - const productsInCategory = this.dashboard.productManager.products.filter( - (p) => p.category === id - ); - if (productsInCategory.length > 0) { - this.dashboard.showMessage( - `Cannot delete category "${category.name}" because it contains ${productsInCategory.length} product(s). Please move or delete the products first.`, - "error" - ); - return; - } - - if ( - confirm( - `Are you sure you want to delete the category "${category.name}"? This action cannot be undone.` - ) - ) { - try { - this.categories = this.categories.filter((cat) => cat.id !== id); - await this.saveData(); - this.renderCategories(); - this.dashboard.showMessage("Category deleted successfully!", "success"); - - // Update product manager's category select - if (this.dashboard.productManager) { - this.dashboard.productManager.populateCategorySelect(); - } - } catch (error) { - this.dashboard.showMessage( - "Error deleting category: " + error.message, - "error" - ); - } - } - } - - async saveData() { - const data = { - products: this.dashboard.productManager.products, - categories: this.categories, - }; - - // In a real implementation, this would save to a server - console.log("Saving category data:", data); - - // Simulate API call - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 500); - }); - } - - // Utility method to get category by ID - getCategoryById(id) { - return this.categories.find((cat) => cat.id === id); - } - - // Utility method to get category name by ID - getCategoryName(id) { - const category = this.getCategoryById(id); - return category ? category.name : id; - } - - // Utility method to validate category ID format - validateCategoryId(id) { - // Category IDs should be lowercase, hyphen-separated - return /^[a-z0-9-]+$/.test(id); - } -} - -// Make CategoryManager available globally -window.CategoryManager = CategoryManager; diff --git a/admin-dashboard/src/js/imageManager.js b/admin-dashboard/src/js/imageManager.js deleted file mode 100644 index 8944dc6..0000000 --- a/admin-dashboard/src/js/imageManager.js +++ /dev/null @@ -1,338 +0,0 @@ -// Image Management Class -class ImageManager { - constructor(dashboard) { - this.dashboard = dashboard; - this.images = []; - this.setupEventListeners(); - } - - setupEventListeners() { - // Upload images button - document - .getElementById("upload-images-btn") - .addEventListener("click", () => { - this.showUploadModal(); - }); - } - - async loadData() { - try { - // In a real implementation, this would fetch from a server - // For now, we'll collect images from products - this.collectImagesFromProducts(); - } catch (error) { - console.error("Error loading image data:", error); - throw error; - } - } - - collectImagesFromProducts() { - this.images = []; - const products = this.dashboard.productManager.products; - - products.forEach((product) => { - if (product.images && Array.isArray(product.images)) { - product.images.forEach((imagePath) => { - if (!this.images.find((img) => img.path === imagePath)) { - this.images.push({ - path: imagePath, - name: this.extractFileName(imagePath), - productId: product.id, - productName: product.name, - size: "Unknown", // Would be fetched from server - uploadDate: "Unknown", // Would be fetched from server - }); - } - }); - } - }); - } - - extractFileName(path) { - return path.split("/").pop(); - } - - renderImages() { - const container = document.getElementById("image-manager"); - - if (this.images.length === 0) { - container.innerHTML = ` -
-

No images found

-

Upload images to get started!

- -
- `; - return; - } - - container.innerHTML = ""; - this.images.forEach((image) => { - const imageItem = this.createImageItem(image); - container.appendChild(imageItem); - }); - } - - createImageItem(image) { - const item = document.createElement("div"); - item.className = "image-item"; - - item.innerHTML = ` -
- ${image.name}Image Not Found'"> -
-
-
${image.name}
-
- Product: ${image.productName} - Size: ${image.size} -
-
-
- - - -
- `; - return item; - } - - showUploadModal() { - // Create upload modal if it doesn't exist - let uploadModal = document.getElementById("upload-modal"); - if (!uploadModal) { - uploadModal = this.createUploadModal(); - document.body.appendChild(uploadModal); - } - - uploadModal.style.display = "block"; - } - - createUploadModal() { - const modal = document.createElement("div"); - modal.id = "upload-modal"; - modal.className = "modal"; - - modal.innerHTML = ` - - `; - - // Setup drag and drop - const uploadArea = modal.querySelector("#upload-area"); - const fileInput = modal.querySelector("#file-input"); - - uploadArea.addEventListener("click", () => fileInput.click()); - uploadArea.addEventListener("dragover", (e) => { - e.preventDefault(); - uploadArea.classList.add("drag-over"); - }); - uploadArea.addEventListener("dragleave", () => { - uploadArea.classList.remove("drag-over"); - }); - uploadArea.addEventListener("drop", (e) => { - e.preventDefault(); - uploadArea.classList.remove("drag-over"); - this.handleFileSelect(e.dataTransfer.files); - }); - - fileInput.addEventListener("change", (e) => { - this.handleFileSelect(e.target.files); - }); - - return modal; - } - - handleFileSelect(files) { - const preview = document.getElementById("upload-preview"); - preview.innerHTML = ""; - - Array.from(files).forEach((file) => { - if (file.type.startsWith("image/")) { - const reader = new FileReader(); - reader.onload = (e) => { - const imageDiv = document.createElement("div"); - imageDiv.className = "upload-preview-item"; - imageDiv.innerHTML = ` - ${file.name} -
- ${file.name} - ${this.formatFileSize( - file.size - )} -
- - `; - preview.appendChild(imageDiv); - }; - reader.readAsDataURL(file); - } - }); - } - - formatFileSize(bytes) { - if (bytes === 0) return "0 Bytes"; - const k = 1024; - const sizes = ["Bytes", "KB", "MB", "GB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; - } - - async processUpload() { - const fileInput = document.getElementById("file-input"); - const files = fileInput.files; - - if (files.length === 0) { - this.dashboard.showMessage("Please select files to upload", "error"); - return; - } - - try { - this.dashboard.showMessage("Uploading images...", "info"); - - const uploadedImages = await this.uploadImages(files); - - // Add to images list - uploadedImages.forEach((imagePath) => { - const fileName = this.extractFileName(imagePath); - this.images.push({ - path: imagePath, - name: fileName, - productId: null, - productName: "Unassigned", - size: "Unknown", - uploadDate: new Date().toISOString(), - }); - }); - - this.renderImages(); - document.getElementById("upload-modal").style.display = "none"; - this.dashboard.showMessage( - `Successfully uploaded ${uploadedImages.length} image(s)!`, - "success" - ); - } catch (error) { - this.dashboard.showMessage( - "Error uploading images: " + error.message, - "error" - ); - } - } - - async uploadImages(files) { - const uploadedImages = []; - - for (const file of files) { - if (file.type.startsWith("image/")) { - // In a real implementation, you'd upload to a server - const filename = `uploaded_${Date.now()}_${file.name}`; - const imagePath = `assets/images/products/${filename}`; - uploadedImages.push(imagePath); - - console.log(`Would upload: ${file.name} to ${imagePath}`); - } - } - - return uploadedImages; - } - - viewImage(imagePath) { - // Create image viewer modal - const viewer = document.createElement("div"); - viewer.className = "modal image-viewer"; - viewer.innerHTML = ` - - `; - - document.body.appendChild(viewer); - viewer.style.display = "block"; - } - - renameImage(imagePath) { - const currentName = this.extractFileName(imagePath); - const newName = prompt("Enter new name for the image:", currentName); - - if (newName && newName !== currentName) { - // In a real implementation, you'd rename the file on the server - console.log(`Would rename ${imagePath} to ${newName}`); - this.dashboard.showMessage( - "Image rename functionality would be implemented here", - "info" - ); - } - } - - async deleteImage(imagePath) { - if ( - confirm( - "Are you sure you want to delete this image? This action cannot be undone." - ) - ) { - try { - // Check if image is used by any products - const productsUsingImage = - this.dashboard.productManager.products.filter( - (product) => product.images && product.images.includes(imagePath) - ); - - if (productsUsingImage.length > 0) { - this.dashboard.showMessage( - `Cannot delete image because it's used by ${productsUsingImage.length} product(s). Please remove it from the products first.`, - "error" - ); - return; - } - - // Remove from images list - this.images = this.images.filter((img) => img.path !== imagePath); - - // In a real implementation, you'd delete from server - console.log(`Would delete image: ${imagePath}`); - - this.renderImages(); - this.dashboard.showMessage("Image deleted successfully!", "success"); - } catch (error) { - this.dashboard.showMessage( - "Error deleting image: " + error.message, - "error" - ); - } - } - } -} - -// Make ImageManager available globally -window.ImageManager = ImageManager; diff --git a/admin-dashboard/src/js/productManager.js b/admin-dashboard/src/js/productManager.js deleted file mode 100644 index a96605a..0000000 --- a/admin-dashboard/src/js/productManager.js +++ /dev/null @@ -1,1490 +0,0 @@ -// Product Management Class -class ProductManager { - constructor(dashboard) { - this.dashboard = dashboard; - this.products = []; - this.categories = []; - this.currentProduct = null; - this.currentBlobUrls = []; - this.setupEventListeners(); - console.log("✅ ProductManager initialized with direct save system"); - } - - setupEventListeners() { - // Add product button - document - .getElementById("add-product-btn") - .addEventListener("click", async () => { - await this.showProductModal(); - }); - - // Product form submit - document.getElementById("product-form").addEventListener("submit", (e) => { - e.preventDefault(); - this.saveProduct(); - }); - - // Product search - document.getElementById("product-search").addEventListener("input", (e) => { - this.filterProducts(e.target.value); - }); - - // Image upload preview - document - .getElementById("product-images") - .addEventListener("change", async (e) => { - await this.previewImages(e.target.files); - }); - - // Add new additional field and image removal - document.addEventListener("click", (e) => { - if (e.target.id === "add-new-field-btn") { - this.showNewFieldForm(); - } else if (e.target.id === "save-new-field-btn") { - this.addNewField(); - } else if (e.target.id === "cancel-new-field-btn") { - this.hideNewFieldForm(); - } else if (e.target.matches(".remove-field-btn")) { - this.removeField(e.target); - } else if (e.target.matches("[data-action='remove-image']")) { - e.preventDefault(); - e.stopPropagation(); - const imageIndex = parseInt(e.target.getAttribute("data-image-index")); - this.removeExistingImage(imageIndex); - } else if (e.target.id === "cancel-product-btn") { - // Handle cancel button in product modal - e.preventDefault(); - this.hideProductModal(); - } - }); - } - - async loadData() { - try { - // Try to load from admin dashboard's local copy first - let response; - try { - response = await fetch("../data/products.json"); - console.log("✅ Loaded from admin dashboard local copy"); - } catch (localError) { - // Fallback to main website if admin local copy doesn't exist - response = await fetch("../../data/products.json"); - console.log("✅ Loaded from main website (fallback)"); - } - - const data = await response.json(); - this.products = data.products || []; - this.categories = data.categories || []; - this.populateCategorySelect(); - console.log("✅ Loaded data:", this.products.length, "products"); - } catch (error) { - console.error("Error loading product data:", error); - throw error; - } - } - - populateCategorySelect() { - const categorySelect = document.getElementById("product-category"); - categorySelect.innerHTML = ''; - - this.categories.forEach((category) => { - const option = document.createElement("option"); - option.value = category.id; - option.textContent = category.name; - categorySelect.appendChild(option); - }); - } - - renderProducts(productsToRender = null) { - const grid = document.getElementById("products-grid"); - - // Don't display any products, just show empty state - grid.innerHTML = ` -
-

Product Management

-

Use the "Add Product" button above to create new products.

-
- `; - } - - createProductCard(product) { - const card = document.createElement("div"); - card.className = "product-card"; - - const category = this.categories.find((cat) => cat.id === product.category); - const categoryName = category ? category.name : product.category; - - card.innerHTML = ` -
- No Image' - }" - alt="${product.name}" - onerror="this.src='data:image/svg+xml,No Image'"> -
-
-

${product.name}

-

${categoryName}

-

${ - product.description - ? product.description.substring(0, 100) + "..." - : "No description" - }

-
- ID: ${product.id} - ${ - product.additionalInformation?.Warranty || - "No warranty info" - } -
-
-
- - - -
- `; - return card; - } - - async showProductModal(product = null) { - console.log("showProductModal called with product:", product); - const modal = document.getElementById("product-modal"); - console.log("Modal element:", modal); - const title = document.getElementById("modal-title"); - const form = document.getElementById("product-form"); - const additionalInfoSection = document.getElementById( - "additional-info-section" - ); - - this.currentProduct = product; - - if (product) { - title.textContent = "Edit Product"; - await this.populateForm(product); - this.populateAdditionalInfo(product); - additionalInfoSection.style.display = "block"; - } else { - title.textContent = "Add Product"; - form.reset(); - document.getElementById("product-warranty").value = "5 years"; - document.getElementById("image-preview").innerHTML = ""; - additionalInfoSection.style.display = "none"; - } - - console.log("Setting modal display to block"); - modal.style.display = "block"; - } - - hideProductModal() { - // Clean up blob URLs to prevent memory leaks - this.cleanupBlobUrls(); - - if (this.currentProduct && this.currentProduct.images) { - this.currentProduct.images.forEach((img) => { - if (img.startsWith("blob:")) { - URL.revokeObjectURL(img); - } - }); - } - - document.getElementById("product-modal").style.display = "none"; - this.currentProduct = null; - } - - async populateForm(product) { - document.getElementById("product-name").value = product.name || ""; - document.getElementById("product-category").value = product.category || ""; - document.getElementById("product-description").value = - product.description || ""; - - // Handle descriptionLong array - if (product.descriptionLong && Array.isArray(product.descriptionLong)) { - document.getElementById("product-description-long").value = - product.descriptionLong.join("\n"); - } else { - document.getElementById("product-description-long").value = - product.descriptionLong || ""; - } - - // Populate additional information - if (product.additionalInformation) { - document.getElementById("product-material").value = - product.additionalInformation.Material || ""; - document.getElementById("product-design").value = - product.additionalInformation.Design || ""; - document.getElementById("product-use-cases").value = - product.additionalInformation["Use Cases"] || ""; - } - - document.getElementById("product-warranty").value = - product.additionalInformation?.Warranty || "5 years"; - - // Clean up any expired blob URLs in the product data - // BUT preserve newly uploaded images that should be in IndexedDB - if (product.images) { - const originalCount = product.images.length; - const validImages = []; - - for (const img of product.images) { - if (img.startsWith("blob:")) { - // Check if this blob URL corresponds to a newly uploaded image in IndexedDB - try { - const imageData = await this.findImageDataInIndexedDB( - img, - product.id - ); - if (imageData && imageData.file) { - // This is a newly uploaded image - keep it and recreate blob URL - const newBlobUrl = URL.createObjectURL(imageData.file); - validImages.push(newBlobUrl); - - // Store for cleanup - if (!this.currentBlobUrls) this.currentBlobUrls = []; - this.currentBlobUrls.push(newBlobUrl); - - console.log( - `📁 Preserved newly uploaded image: ${imageData.name}` - ); - } else { - console.log(`🧹 Removed expired blob URL: ${img}`); - } - } catch (error) { - console.log(`🧹 Removed invalid blob URL: ${img}`); - } - } else { - // Regular image path - keep it - validImages.push(img); - } - } - - product.images = validImages; - const cleanedCount = product.images.length; - if (originalCount !== cleanedCount) { - console.log( - `🧹 Cleaned ${ - originalCount - cleanedCount - } expired blob URLs from product data` - ); - } - } - - // Show existing images - await this.showExistingImages(product); - } - - populateAdditionalInfo(product) { - const additionalInfoGrid = document.getElementById("additional-info-grid"); - - if (!product.additionalInformation) { - additionalInfoGrid.innerHTML = - "

No additional information available.

"; - return; - } - - // Clear existing content - additionalInfoGrid.innerHTML = ""; - - // Get all additional information fields - const additionalFields = product.additionalInformation; - - // Fields that are already shown in the main form (exclude these) - const excludedFields = ["Material", "Design", "Use Cases", "Warranty"]; - - // Create form fields for each additional information field - Object.keys(additionalFields).forEach((key) => { - if (!excludedFields.includes(key)) { - const value = additionalFields[key]; - const fieldId = `additional-${key - .toLowerCase() - .replace(/[^a-z0-9]/g, "-")}`; - - // Determine if it should be a textarea (for longer content) - const isLongContent = value && value.length > 50; - - const fieldElement = document.createElement("div"); - fieldElement.className = "additional-info-item"; - - if (isLongContent) { - fieldElement.innerHTML = ` - - - `; - } else { - fieldElement.innerHTML = ` - - - `; - } - - additionalInfoGrid.appendChild(fieldElement); - } - }); - - // If no additional fields to show, display a message - if (additionalInfoGrid.children.length === 0) { - additionalInfoGrid.innerHTML = - "

No additional information fields to display.

"; - } - } - - showNewFieldForm() { - const additionalInfoGrid = document.getElementById("additional-info-grid"); - - // Remove any existing new field form - this.hideNewFieldForm(); - - // Create new field form - const newFieldForm = document.createElement("div"); - newFieldForm.className = "new-field-form"; - newFieldForm.innerHTML = ` -
Add New Field
-
- - - - -
- `; - - // Insert at the beginning of the grid - additionalInfoGrid.insertBefore( - newFieldForm, - additionalInfoGrid.firstChild - ); - - // Focus on the title input - document.getElementById("new-field-title").focus(); - } - - hideNewFieldForm() { - const existingForm = document.querySelector(".new-field-form"); - if (existingForm) { - existingForm.remove(); - } - } - - addNewField() { - const titleInput = document.getElementById("new-field-title"); - const valueInput = document.getElementById("new-field-value"); - - const title = titleInput.value.trim(); - const value = valueInput.value.trim(); - - if (!title || !value) { - alert("Both title and value are required!"); - return; - } - - // Check if field already exists - const existingField = document.querySelector( - `[name="additionalInfo.${title}"]` - ); - if (existingField) { - alert("A field with this title already exists!"); - return; - } - - // Create new field element - const additionalInfoGrid = document.getElementById("additional-info-grid"); - const fieldId = `additional-${title - .toLowerCase() - .replace(/[^a-z0-9]/g, "-")}`; - const isLongContent = value.length > 50; - - const fieldElement = document.createElement("div"); - fieldElement.className = "additional-info-item"; - - if (isLongContent) { - fieldElement.innerHTML = ` - - - `; - } else { - fieldElement.innerHTML = ` - - - `; - } - - // Insert after the new field form - const newFieldForm = document.querySelector(".new-field-form"); - additionalInfoGrid.insertBefore(fieldElement, newFieldForm.nextSibling); - - // Hide the form and clear inputs - this.hideNewFieldForm(); - } - - removeField(button) { - const fieldName = button.getAttribute("data-field-name"); - if (confirm(`Are you sure you want to remove the "${fieldName}" field?`)) { - const fieldElement = button.closest(".additional-info-item"); - fieldElement.remove(); - } - } - - async showExistingImages(product) { - const preview = document.getElementById("image-preview"); - preview.innerHTML = ""; - - console.log( - `🖼️ showExistingImages called with ${ - product.images ? product.images.length : 0 - } images` - ); - - if (product.images && product.images.length > 0) { - // Clean up any existing blob URLs - this.cleanupBlobUrls(); - - console.log( - `🖼️ Processing ${product.images.length} images:`, - product.images - ); - - for (let index = 0; index < product.images.length; index++) { - const imagePath = product.images[index]; - const imageDiv = document.createElement("div"); - imageDiv.className = "image-preview-item"; - - // Handle different types of image paths - let imageSrc; - if (imagePath.startsWith("blob:")) { - // This is an expired blob URL - try to find the image in IndexedDB - console.log(`⚠️ Found expired blob URL: ${imagePath}`); - try { - const imageData = await this.findImageDataInIndexedDB( - imagePath, - product.id - ); - if (imageData && imageData.file) { - // Create new blob URL from stored file - const newBlobUrl = URL.createObjectURL(imageData.file); - imageSrc = newBlobUrl; - - // Store the new blob URL for cleanup later - if (!this.currentBlobUrls) this.currentBlobUrls = []; - this.currentBlobUrls.push(newBlobUrl); - - console.log( - `📁 Recreated blob URL from IndexedDB: ${imageData.name}` - ); - } else { - // Show error placeholder - imageSrc = - "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+SW1hZ2UgRXJyb3I8L3RleHQ+PC9zdmc+"; - console.log(`❌ No IndexedDB data found for expired blob URL`); - } - } catch (error) { - console.error("❌ Error loading IndexedDB image:", error); - imageSrc = - "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+SW1hZ2UgRXJyb3I8L3RleHQ+PC9zdmc+"; - } - } else if ( - imagePath.includes("product_") && - imagePath.includes("_cover.jpg") - ) { - // This is a newly uploaded image that should be in IndexedDB - try { - const imageData = await this.findImageDataInIndexedDB( - imagePath, - product.id - ); - if (imageData && imageData.file) { - // Create new blob URL from stored file - const newBlobUrl = URL.createObjectURL(imageData.file); - imageSrc = newBlobUrl; - - // Store the new blob URL for cleanup later - if (!this.currentBlobUrls) this.currentBlobUrls = []; - this.currentBlobUrls.push(newBlobUrl); - - console.log(`📁 Displaying IndexedDB image: ${imageData.name}`); - } else { - // Fallback to original path - imageSrc = this.fixImagePath(imagePath); - console.log(`📷 Fallback to original path: ${imagePath}`); - } - } catch (error) { - console.error("❌ Error loading IndexedDB image:", error); - imageSrc = this.fixImagePath(imagePath); - } - } else { - // This is an existing image path - fix it for admin dashboard - imageSrc = this.fixImagePath(imagePath); - console.log(`📷 Displaying original image: ${imagePath}`); - } - - console.log( - `🖼️ Creating image ${index + 1} with src: ${imageSrc.substring( - 0, - 50 - )}...` - ); - - imageDiv.innerHTML = ` - Product image ${
-          index + 1
-        } - - `; - preview.appendChild(imageDiv); - console.log(`✅ Image ${index + 1} added to DOM`); - } - } - } - - async findImageDataInIndexedDB(imagePath, productId) { - try { - // Get all image files from IndexedDB - const allImageFiles = await window.storageManager.getAllImageFiles(); - console.log( - `🔍 Searching IndexedDB for image. Total files: ${allImageFiles.length}` - ); - - // Extract filename from the image path - const filename = imagePath.split("/").pop(); - console.log( - `🔍 Looking for filename: ${filename}, Product ID: ${productId}` - ); - - // Find the matching image data with multiple strategies - let matchingImage = null; - - // Strategy 1: Direct filename match - matchingImage = allImageFiles.find((img) => img.name === filename); - if (matchingImage) { - console.log(`✅ Found by filename match: ${matchingImage.name}`); - return matchingImage; - } - - // Strategy 2: Match by product ID (for newly uploaded images) - matchingImage = allImageFiles.find( - (img) => - img.id.includes(productId.toString()) && - (img.name.includes("cover") || - img.name.includes("jpg") || - img.name.includes("jpeg") || - img.name.includes("png")) - ); - if (matchingImage) { - console.log(`✅ Found by product ID match: ${matchingImage.name}`); - return matchingImage; - } - - // Strategy 3: Match by partial filename (for generated paths) - if (filename.includes("product_") || filename.includes("cover")) { - matchingImage = allImageFiles.find((img) => - img.id.includes(productId.toString()) - ); - if (matchingImage) { - console.log(`✅ Found by partial match: ${matchingImage.name}`); - return matchingImage; - } - } - - // Strategy 4: If it's a blob URL, try to find any image for this product - if (imagePath.startsWith("blob:")) { - matchingImage = allImageFiles.find((img) => - img.id.includes(productId.toString()) - ); - if (matchingImage) { - console.log(`✅ Found blob URL replacement: ${matchingImage.name}`); - return matchingImage; - } - } - - console.log(`❌ No matching image found in IndexedDB for: ${filename}`); - console.log( - `Available images:`, - allImageFiles.map((img) => ({ id: img.id, name: img.name })) - ); - return null; - } catch (error) { - console.error("❌ Error searching IndexedDB for image:", error); - return null; - } - } - - cleanupBlobUrls() { - if (this.currentBlobUrls) { - this.currentBlobUrls.forEach((url) => { - URL.revokeObjectURL(url); - }); - this.currentBlobUrls = []; - } - } - - fixImagePath(imagePath) { - // Fix image path for admin dashboard - if (imagePath && !imagePath.startsWith("http")) { - // Remove any leading slashes and ensure we have the correct path - let cleanPath = imagePath.replace(/^\/+/, ""); - - // If it starts with 'assets/', use it as is - if (cleanPath.startsWith("assets/")) { - return "/" + cleanPath; - } - - // If it starts with 'src/assets/', remove the 'src/' part - if (cleanPath.startsWith("src/assets/")) { - return "/" + cleanPath.replace("src/", ""); - } - - // For other paths, assume they need to be prefixed with 'assets/' - return "/" + cleanPath; - } - return imagePath; - } - - async saveProduct() { - console.log("🚀 SAVE PRODUCT STARTED - DIRECT SAVE MODE"); - console.log( - "Current product (editing):", - this.currentProduct ? "YES" : "NO" - ); - - const formData = new FormData(document.getElementById("product-form")); - const productData = this.extractFormData(formData); - - try { - // Track changes for undo before making any modifications - await this.trackChangesForUndo(productData); - - // Handle images - separate existing paths from blob URLs - const existingImages = (productData.images || []).filter( - (img) => !img.startsWith("blob:") - ); - - console.log("🖼️ IMAGE PROCESSING:"); - console.log("Existing images (non-blob):", existingImages); - - // Handle new file uploads - const imageInput = document.getElementById("product-images"); - if (imageInput.files.length > 0) { - console.log("📁 New files to upload:", imageInput.files.length); - const newImages = await this.uploadImages(imageInput.files); - productData.images = [...existingImages, ...newImages]; - console.log("🆕 New image paths:", newImages); - } else { - // Only keep existing non-blob images - productData.images = existingImages; - console.log("📷 No new files, keeping existing images"); - } - - console.log("🖼️ Final images array:", productData.images); - - // Generate new ID if adding new product - if (!this.currentProduct) { - productData.id = this.getNextProductId(); - console.log("🆔 NEW PRODUCT - Generated ID:", productData.id); - } else { - productData.id = this.currentProduct.id; - console.log("✏️ EDITING PRODUCT - Using existing ID:", productData.id); - } - - console.log("📦 FINAL PRODUCT DATA BEFORE DIRECT SAVE:", productData); - - // Save directly to actual data - await this.saveDirectlyToData(productData); - - // Close modal and show success message - this.hideProductModal(); - this.dashboard.showMessage("Product saved successfully!", "success"); - - // Refresh the current view to show changes - this.refreshCurrentView(); - } catch (error) { - console.error("❌ ERROR SAVING PRODUCT:", error); - this.dashboard.showMessage( - "Error saving product: " + error.message, - "error" - ); - } - } - - // Track changes for undo functionality - async trackChangesForUndo(productData) { - console.log("📝 TRACKING CHANGES FOR UNDO"); - - // Get current undo stack from localStorage - let undoStack = JSON.parse(localStorage.getItem("khy_undo_stack") || "[]"); - - if (this.currentProduct) { - // Editing existing product - store the original state - const originalProduct = { ...this.currentProduct }; - const changeEntry = { - id: Date.now(), - timestamp: new Date().toISOString(), - action: "edit", - productId: productData.id, - originalData: originalProduct, - newData: { ...productData }, - }; - - undoStack.push(changeEntry); - console.log("📌 Tracked EDIT change:", changeEntry); - } else { - // Adding new product - just track the addition - const changeEntry = { - id: Date.now(), - timestamp: new Date().toISOString(), - action: "add", - productId: productData.id, - newData: { ...productData }, - }; - - undoStack.push(changeEntry); - console.log("📌 Tracked ADD change:", changeEntry); - } - - // Keep only last 10 changes to prevent excessive memory usage - if (undoStack.length > 10) { - undoStack = undoStack.slice(-10); - } - - localStorage.setItem("khy_undo_stack", JSON.stringify(undoStack)); - console.log("💾 Undo stack updated. Total changes:", undoStack.length); - } - - // Save directly to the actual products.json - async saveDirectlyToData(productData) { - console.log("💾 SAVING DIRECTLY TO DATA"); - - if (this.currentProduct) { - // Editing existing product - const index = this.products.findIndex((p) => p.id === productData.id); - if (index !== -1) { - this.products[index] = productData; - console.log("✏️ Updated existing product at index:", index); - } - } else { - // Adding new product - this.products.push(productData); - console.log( - "➕ Added new product. Total products:", - this.products.length - ); - } - - // Save to file - await this.saveData(); - console.log("💾 Data saved to products.json"); - } - - // Save data to the products.json file - async saveData() { - try { - const dataToSave = { - products: this.products, - categories: this.categories, - }; - - console.log("💾 SAVING DATA TO FILE:"); - console.log("📦 Products to save:", this.products.length); - console.log("📂 Categories to save:", this.categories.length); - - // Store in localStorage for backup - localStorage.setItem("khy_products_backup", JSON.stringify(dataToSave)); - - // Write to admin dashboard's local copy for sync - await this.writeToAdminLocal(dataToSave); - - // Create a downloadable file for manual sync - this.createDownloadableFile(dataToSave); - - // Show instructions for manual sync - console.log("📋 TO SYNC TO MAIN WEBSITE:"); - console.log("1. Two files have been downloaded:"); - console.log(" - 'products_updated.json' (for main website)"); - console.log(" - 'admin_local_products.json' (for admin dashboard)"); - console.log("2. QUICK SYNC - Copy this command and run it in terminal:"); - console.log( - " cp ~/Downloads/products_updated.json ../data/products.json" - ); - console.log(" (from the admin-dashboard directory)"); - console.log("3. Refresh the preview to see changes"); - console.log( - "💡 The admin dashboard will now show your changes immediately!" - ); - } catch (error) { - console.error("❌ Error saving data:", error); - throw error; - } - } - - // Write to admin dashboard's local copy using a simple approach - async writeToAdminLocal(data) { - try { - // Since we can't write files directly from browser, we'll use a different approach - // Store the data in a special localStorage key that can be accessed by a sync script - localStorage.setItem("khy_admin_local_copy", JSON.stringify(data)); - console.log("✅ Data stored for admin local copy"); - - // Also create a downloadable file specifically for admin local copy - const dataStr = JSON.stringify(data, null, 2); - const dataBlob = new Blob([dataStr], { type: "application/json" }); - const url = URL.createObjectURL(dataBlob); - - const downloadLink = document.createElement("a"); - downloadLink.href = url; - downloadLink.download = "admin_local_products.json"; - downloadLink.style.display = "none"; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - URL.revokeObjectURL(url); - - console.log( - "✅ Admin local copy downloaded as 'admin_local_products.json'" - ); - } catch (error) { - console.log("⚠️ Could not create admin local copy:", error); - } - } - - // Create a downloadable file for manual sync - createDownloadableFile(data) { - try { - const dataStr = JSON.stringify(data, null, 2); - const dataBlob = new Blob([dataStr], { type: "application/json" }); - const url = URL.createObjectURL(dataBlob); - - // Create a temporary download link - const downloadLink = document.createElement("a"); - downloadLink.href = url; - downloadLink.download = "products_updated.json"; - downloadLink.style.display = "none"; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - URL.revokeObjectURL(url); - - console.log("✅ Updated products.json downloaded for manual sync"); - } catch (error) { - console.log("⚠️ Could not create download file:", error); - } - } - - // Refresh current view to show changes immediately - refreshCurrentView() { - console.log("🔄 REFRESHING CURRENT VIEW"); - - // If we're in category view, refresh that - if (this.dashboard.currentSection === "category-products") { - // Get the current category from the URL or state - const categoryButton = document.querySelector("[data-category-id]"); - if (categoryButton) { - const categoryId = categoryButton.getAttribute("data-category-id"); - // Refresh the category products view - if (this.dashboard.categoryManager) { - this.dashboard.categoryManager.viewCategoryProducts(categoryId); - } - } - } - - // Always refresh the preview if it exists - this.refreshPreview(); - } - - // Refresh the preview iframe - refreshPreview() { - console.log("🖼️ REFRESHING PREVIEW"); - const iframe = document.getElementById("preview-iframe"); - if (iframe && iframe.src) { - // Add a timestamp to force refresh - const url = new URL(iframe.src); - url.searchParams.set("_refresh", Date.now()); - iframe.src = url.toString(); - console.log("🔄 Preview refreshed with timestamp"); - } - } - - extractFormData(formData) { - console.log("🔍 EXTRACTING FORM DATA:"); - console.log("FormData entries:", Array.from(formData.entries())); - - const productData = { - name: formData.get("name"), - category: formData.get("category"), - description: formData.get("description"), - warranty: formData.get("warranty"), - }; - - console.log("📝 Basic product data:", productData); - - // Handle descriptionLong - const descriptionLong = formData.get("descriptionLong"); - if (descriptionLong) { - productData.descriptionLong = descriptionLong - .split("\n") - .filter((line) => line.trim()); - console.log("📄 DescriptionLong array:", productData.descriptionLong); - } - - // Handle additional information - const material = formData.get("material"); - const design = formData.get("design"); - const useCases = formData.get("useCases"); - const warranty = formData.get("warranty"); - - console.log("🔧 Additional info fields:", { - material, - design, - useCases, - warranty, - }); - - // Start with existing additional information or create new object - productData.additionalInformation = - this.currentProduct?.additionalInformation || {}; - - // Update main fields - if (material) productData.additionalInformation.Material = material; - if (design) productData.additionalInformation.Design = design; - if (useCases) productData.additionalInformation["Use Cases"] = useCases; - if (warranty) productData.additionalInformation.Warranty = warranty; - - // Handle all additional information fields from the form - for (const [key, value] of formData.entries()) { - if (key.startsWith("additionalInfo.")) { - const fieldName = key.replace("additionalInfo.", ""); - if (value && value.trim()) { - productData.additionalInformation[fieldName] = value.trim(); - } - } - } - - console.log( - "📋 Additional Information object:", - productData.additionalInformation - ); - - // Preserve existing images - if (this.currentProduct && this.currentProduct.images) { - productData.images = [...this.currentProduct.images]; - console.log("🖼️ Existing images:", productData.images); - } - - console.log("✅ FINAL EXTRACTED PRODUCT DATA:", productData); - return productData; - } - - getNextProductId() { - const maxId = Math.max(...this.products.map((p) => p.id), 0); - return maxId + 1; - } - - async editProduct(id) { - console.log( - "ProductManager.editProduct called with ID:", - id, - "Type:", - typeof id - ); - console.log("Available products:", this.products.length); - // Convert string ID to number for comparison - const numericId = typeof id === "string" ? parseInt(id, 10) : id; - console.log("Looking for numeric ID:", numericId); - const product = this.products.find((p) => p.id === numericId); - console.log("Found product:", product); - if (product) { - console.log("Showing product modal for:", product.name); - await this.showProductModal(product); - } else { - console.log("Product not found with ID:", numericId); - // Debug: show first few product IDs for comparison - console.log( - "First 5 product IDs:", - this.products - .slice(0, 5) - .map((p) => ({ id: p.id, name: p.name, type: typeof p.id })) - ); - } - } - - async duplicateProduct(id) { - const numericId = typeof id === "string" ? parseInt(id, 10) : id; - const product = this.products.find((p) => p.id === numericId); - if (product) { - const duplicatedProduct = { - ...product, - id: this.getNextProductId(), - name: product.name + " (Copy)", - }; - await this.showProductModal(duplicatedProduct); - } - } - - async deleteProduct(id) { - const numericId = typeof id === "string" ? parseInt(id, 10) : id; - const product = this.products.find((p) => p.id === numericId); - if (!product) return; - - if ( - confirm( - `Are you sure you want to delete "${product.name}"? This action cannot be undone.` - ) - ) { - try { - // Remove product images if any - if (product.images) { - await this.deleteProductImages(product.images); - } - - // Remove from products array - this.products = this.products.filter((p) => p.id !== numericId); - await this.saveData(); - this.renderProducts(); - this.dashboard.showMessage("Product deleted successfully!", "success"); - } catch (error) { - this.dashboard.showMessage( - "Error deleting product: " + error.message, - "error" - ); - } - } - } - - removeExistingImage(index) { - console.log("removeExistingImage called with index:", index); - if (this.currentProduct && this.currentProduct.images) { - const imagePath = this.currentProduct.images[index]; - const imageName = imagePath ? imagePath.split("/").pop() : "this image"; - - console.log("Showing confirmation dialog for:", imageName); - this.showImageDeleteConfirmation(imageName, index, imagePath); - } - } - - showImageDeleteConfirmation(imageName, index, imagePath) { - // Create custom confirmation dialog - const confirmationDiv = document.createElement("div"); - confirmationDiv.className = "image-delete-confirmation"; - confirmationDiv.innerHTML = ` -
-

Delete Image

-

Do you really want to delete ${imageName}?

-

This action is irreversible and will permanently remove the image from this product.

-
- - -
-
- `; - - // Add to modal - const modal = document.getElementById("product-modal"); - modal.appendChild(confirmationDiv); - - // Add event listeners - document - .getElementById("cancel-image-delete") - .addEventListener("click", () => { - confirmationDiv.remove(); - }); - - document - .getElementById("confirm-image-delete") - .addEventListener("click", async () => { - const imageToRemove = this.currentProduct.images[index]; - - // Track deleted image for sync - this.trackDeletedImage(imagePath, imageName); - - // Clean up blob URL if it's a blob - if (imageToRemove && imageToRemove.startsWith("blob:")) { - URL.revokeObjectURL(imageToRemove); - } - - this.currentProduct.images.splice(index, 1); - await this.showExistingImages(this.currentProduct); - console.log(`Image removed: ${imageName}`); - confirmationDiv.remove(); - }); - } - - async trackDeletedImage(imagePath, imageName) { - console.log("🗑️ TRACKING DELETED IMAGE:"); - console.log("Image path:", imagePath); - console.log("Image name:", imageName); - - try { - // Get current temporary changes - const temporaryChanges = - await window.storageManager.getTemporaryChanges(); - - // Initialize image tracking if not exists - temporaryChanges.imageChanges = temporaryChanges.imageChanges || {}; - - // Find the product ID (could be current product or new product being created) - const productId = this.currentProduct ? this.currentProduct.id : "new"; - - temporaryChanges.imageChanges[productId] = temporaryChanges.imageChanges[ - productId - ] || { - newImages: [], - deletedImages: [], - blobImages: [], - finalImages: [], - }; - - // Add to deleted images if not already there - const existingDeleted = temporaryChanges.imageChanges[ - productId - ].deletedImages.find((img) => img.path === imagePath); - - if (!existingDeleted) { - temporaryChanges.imageChanges[productId].deletedImages.push({ - path: imagePath, - name: imageName, - timestamp: Date.now(), - }); - } - - // Save updated tracking - await window.storageManager.saveTemporaryChanges(temporaryChanges); - - console.log( - "📸 Updated image tracking:", - temporaryChanges.imageChanges[productId] - ); - } catch (error) { - console.error("❌ Error tracking deleted image:", error); - } - } - - filterProducts(searchTerm) { - if (!searchTerm) { - this.renderProducts(); - return; - } - - const filtered = this.products.filter( - (product) => - product.name.toLowerCase().includes(searchTerm.toLowerCase()) || - product.description.toLowerCase().includes(searchTerm.toLowerCase()) || - product.category.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - this.renderProducts(filtered); - } - - // Temporary Storage Methods - async addToTemporaryStorage(productData) { - console.log("💾 ADDING TO TEMPORARY STORAGE:"); - - try { - // Get existing temporary storage or create new - let temporaryChanges = await window.storageManager.getTemporaryChanges(); - - console.log("📦 Existing temporary changes:", temporaryChanges); - - // Initialize image tracking if not exists - temporaryChanges.imageChanges = temporaryChanges.imageChanges || {}; - temporaryChanges.imageChanges[productData.id] = temporaryChanges - .imageChanges[productData.id] || { - newImages: [], - deletedImages: [], - }; - - // Track image changes - await this.trackImageChanges(productData, temporaryChanges); - - // Store the product change - temporaryChanges.products = temporaryChanges.products || {}; - - const finalProductData = { - ...productData, - timestamp: Date.now(), - action: this.currentProduct ? "updated" : "created", - }; - - temporaryChanges.products[productData.id] = finalProductData; - - console.log("🏷️ Product metadata:", { - id: productData.id, - timestamp: finalProductData.timestamp, - action: finalProductData.action, - }); - - console.log( - "📸 Image changes tracked:", - temporaryChanges.imageChanges[productData.id] - ); - - console.log("📋 FINAL PRODUCT STRUCTURE IN TEMPORARY STORAGE:"); - console.log(JSON.stringify(finalProductData, null, 2)); - - // Save to IndexedDB - await window.storageManager.saveTemporaryChanges(temporaryChanges); - - console.log( - "✅ COMPLETE TEMPORARY STORAGE STRUCTURE SAVED TO INDEXEDDB:" - ); - console.log(JSON.stringify(temporaryChanges, null, 2)); - } catch (error) { - console.error("❌ Error saving to temporary storage:", error); - } - } - - async trackImageChanges(productData, temporaryChanges) { - const productId = productData.id; - const currentImages = this.currentProduct - ? this.currentProduct.images || [] - : []; - const newImages = productData.images || []; - - console.log("📸 TRACKING IMAGE CHANGES:"); - console.log("Current images:", currentImages); - console.log("New images:", newImages); - - // Find new images (blob URLs or new file uploads) - const newImageFiles = []; - const imageInput = document.getElementById("product-images"); - if (imageInput && imageInput.files.length > 0) { - for (const file of Array.from(imageInput.files)) { - // Save file to IndexedDB and get reference - const imageId = `${productId}_${Date.now()}_${file.name}`; - await window.storageManager.saveImageFile(imageId, file); - - newImageFiles.push({ - imageId: imageId, - name: file.name, - size: file.size, - type: file.type, - lastModified: file.lastModified, - }); - } - } - - // Find deleted images - const deletedImages = []; - currentImages.forEach((oldImage) => { - if (!newImages.includes(oldImage)) { - deletedImages.push({ - path: oldImage, - name: oldImage.split("/").pop(), - }); - } - }); - - // Find blob URLs (temporary preview images) - const blobImages = newImages.filter((img) => img.startsWith("blob:")); - - // Update image tracking - temporaryChanges.imageChanges[productId] = { - newImages: newImageFiles, - deletedImages: deletedImages, - blobImages: blobImages, - finalImages: newImages.filter((img) => !img.startsWith("blob:")), - }; - - console.log("📁 New image files:", newImageFiles); - console.log("🗑️ Deleted images:", deletedImages); - console.log("🖼️ Blob images (temporary):", blobImages); - } - - async updatePreviewWithTemporaryData() { - try { - // Get temporary changes - const temporaryChanges = - await window.storageManager.getTemporaryChanges(); - - if ( - temporaryChanges.products && - Object.keys(temporaryChanges.products).length > 0 - ) { - // Create a merged dataset for preview - const previewData = this.createPreviewData(temporaryChanges); - - // Update preview iframe with temporary data - this.updatePreviewIframe(previewData); - } - } catch (error) { - console.error("❌ Error updating preview with temporary data:", error); - } - } - - createPreviewData(temporaryChanges) { - // Start with original products - const previewProducts = [...this.products]; - - // Apply temporary changes - if (temporaryChanges.products) { - Object.values(temporaryChanges.products).forEach((tempProduct) => { - const existingIndex = previewProducts.findIndex( - (p) => p.id === tempProduct.id - ); - - if (existingIndex >= 0) { - // Update existing product - previewProducts[existingIndex] = { - ...previewProducts[existingIndex], - ...tempProduct, - }; - } else { - // Add new product - previewProducts.push(tempProduct); - } - }); - } - - return { - products: previewProducts, - categories: this.categories, - }; - } - - updatePreviewIframe(previewData) { - // Send data to preview iframe - const iframe = document.getElementById("preview-iframe"); - if (iframe && iframe.contentWindow) { - iframe.contentWindow.postMessage( - { - type: "UPDATE_PREVIEW_DATA", - data: previewData, - }, - "*" - ); - } - } - - async undoTemporaryChanges() { - try { - const temporaryChanges = - await window.storageManager.getTemporaryChanges(); - - if ( - !temporaryChanges.products || - Object.keys(temporaryChanges.products).length === 0 - ) { - this.dashboard.showMessage("No temporary changes to undo.", "info"); - return; - } - - // Clear temporary storage - await window.storageManager.clearAllTemporaryData(); - - // Update preview with original data - this.updatePreviewWithTemporaryData(); - - // Refresh product list - this.renderProducts(); - - this.dashboard.showMessage("Temporary changes undone!", "success"); - } catch (error) { - console.error("❌ Error undoing changes:", error); - this.dashboard.showMessage( - "Error undoing changes. Please try again.", - "error" - ); - } - } - - async previewImages(files) { - if (!files || files.length === 0) return; - - console.log(`📁 Previewing ${files.length} new image(s)`); - - // Add new images to current product's images array - for (const file of Array.from(files)) { - if (file.type.startsWith("image/")) { - console.log(`📷 Processing image: ${file.name}`); - - // Create a temporary URL for preview - const tempUrl = URL.createObjectURL(file); - - // Store blob URL for cleanup - if (!this.currentBlobUrls) this.currentBlobUrls = []; - this.currentBlobUrls.push(tempUrl); - - // Add to current product's images array - if (this.currentProduct) { - if (!this.currentProduct.images) { - this.currentProduct.images = []; - } - // Add temporary URL for immediate preview - this.currentProduct.images.push(tempUrl); - console.log( - `📁 Added image to existing product. Total images: ${this.currentProduct.images.length}` - ); - } else { - // If no current product (adding new product), create a temporary one for preview - this.currentProduct = { - images: [tempUrl], - }; - console.log(`📁 Created new product with image. Total images: 1`); - } - } - } - - // Refresh the display to show all images (existing + new) - if (this.currentProduct) { - console.log( - `🖼️ Refreshing display with ${this.currentProduct.images.length} total images` - ); - await this.showExistingImages(this.currentProduct); - } - } - - async uploadImages(files) { - const uploadedImages = []; - - for (const file of files) { - if (file.type.startsWith("image/")) { - // In a real implementation, you'd upload to a server - // For now, we'll create a local path - const filename = `product_${Date.now()}_${file.name}`; - const imagePath = `assets/images/products/${filename}`; - uploadedImages.push(imagePath); - - // In a real implementation, you'd save the file - console.log(`Would upload: ${file.name} to ${imagePath}`); - } - } - - return uploadedImages; - } - - async deleteProductImages(imagePaths) { - // In a real implementation, you'd delete from server - console.log("Would delete images:", imagePaths); - } - - async saveData() { - const data = { - products: this.products, - categories: this.categories, - }; - - // In a real implementation, this would save to a server - // For now, we'll just update the local data - console.log("Saving data:", data); - - // Simulate API call - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 500); - }); - } -} - -// Make ProductManager available globally -window.ProductManager = ProductManager; diff --git a/admin-dashboard/src/js/storageManager.js b/admin-dashboard/src/js/storageManager.js deleted file mode 100644 index f3974f4..0000000 --- a/admin-dashboard/src/js/storageManager.js +++ /dev/null @@ -1,245 +0,0 @@ -class StorageManager { - constructor() { - this.dbName = "KHYAdminStorage"; - this.dbVersion = 1; - this.db = null; - this.initDB(); - } - - async initDB() { - return new Promise((resolve, reject) => { - const request = indexedDB.open(this.dbName, this.dbVersion); - - request.onerror = () => { - console.error("Failed to open IndexedDB:", request.error); - reject(request.error); - }; - - request.onsuccess = () => { - this.db = request.result; - console.log("✅ IndexedDB initialized successfully"); - resolve(this.db); - }; - - request.onupgradeneeded = (event) => { - const db = event.target.result; - - // Create stores for different types of data - if (!db.objectStoreNames.contains("temporaryChanges")) { - db.createObjectStore("temporaryChanges", { keyPath: "id" }); - } - - if (!db.objectStoreNames.contains("imageFiles")) { - db.createObjectStore("imageFiles", { keyPath: "id" }); - } - - console.log("✅ IndexedDB stores created"); - }; - }); - } - - async saveTemporaryChanges(changes) { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction( - ["temporaryChanges"], - "readwrite" - ); - const store = transaction.objectStore("temporaryChanges"); - - const changeData = { - id: "current", - data: changes, - timestamp: Date.now(), - }; - - const request = store.put(changeData); - - request.onsuccess = () => { - console.log("💾 Temporary changes saved to IndexedDB"); - resolve(); - }; - - request.onerror = () => { - console.error("Failed to save temporary changes:", request.error); - reject(request.error); - }; - }); - } - - async getTemporaryChanges() { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction(["temporaryChanges"], "readonly"); - const store = transaction.objectStore("temporaryChanges"); - const request = store.get("current"); - - request.onsuccess = () => { - const result = request.result; - if (result && result.data) { - console.log("📦 Retrieved temporary changes from IndexedDB"); - resolve(result.data); - } else { - console.log("📦 No temporary changes found in IndexedDB"); - resolve({}); - } - }; - - request.onerror = () => { - console.error("Failed to get temporary changes:", request.error); - reject(request.error); - }; - }); - } - - async saveImageFile(imageId, file) { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction(["imageFiles"], "readwrite"); - const store = transaction.objectStore("imageFiles"); - - const imageData = { - id: imageId, - file: file, - name: file.name, - size: file.size, - type: file.type, - lastModified: file.lastModified, - timestamp: Date.now(), - }; - - const request = store.put(imageData); - - request.onsuccess = () => { - console.log(`📁 Image file saved to IndexedDB: ${file.name}`); - resolve(); - }; - - request.onerror = () => { - console.error("Failed to save image file:", request.error); - reject(request.error); - }; - }); - } - - async getImageFile(imageId) { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction(["imageFiles"], "readonly"); - const store = transaction.objectStore("imageFiles"); - const request = store.get(imageId); - - request.onsuccess = () => { - const result = request.result; - if (result) { - console.log(`📁 Retrieved image file from IndexedDB: ${result.name}`); - resolve(result); - } else { - resolve(null); - } - }; - - request.onerror = () => { - console.error("Failed to get image file:", request.error); - reject(request.error); - }; - }); - } - - async deleteImageFile(imageId) { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction(["imageFiles"], "readwrite"); - const store = transaction.objectStore("imageFiles"); - const request = store.delete(imageId); - - request.onsuccess = () => { - console.log(`🗑️ Image file deleted from IndexedDB: ${imageId}`); - resolve(); - }; - - request.onerror = () => { - console.error("Failed to delete image file:", request.error); - reject(request.error); - }; - }); - } - - async clearAllTemporaryData() { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction( - ["temporaryChanges", "imageFiles"], - "readwrite" - ); - - const changeStore = transaction.objectStore("temporaryChanges"); - const imageStore = transaction.objectStore("imageFiles"); - - const changeRequest = changeStore.clear(); - const imageRequest = imageStore.clear(); - - let completed = 0; - const total = 2; - - const checkComplete = () => { - completed++; - if (completed === total) { - console.log("🧹 All temporary data cleared from IndexedDB"); - resolve(); - } - }; - - changeRequest.onsuccess = checkComplete; - imageRequest.onsuccess = checkComplete; - - changeRequest.onerror = () => reject(changeRequest.error); - imageRequest.onerror = () => reject(imageRequest.error); - }); - } - - async getAllImageFiles() { - if (!this.db) { - await this.initDB(); - } - - return new Promise((resolve, reject) => { - const transaction = this.db.transaction(["imageFiles"], "readonly"); - const store = transaction.objectStore("imageFiles"); - const request = store.getAll(); - - request.onsuccess = () => { - console.log( - `📁 Retrieved ${request.result.length} image files from IndexedDB` - ); - resolve(request.result); - }; - - request.onerror = () => { - console.error("Failed to get all image files:", request.error); - reject(request.error); - }; - }); - } -} - -// Create global instance -window.storageManager = new StorageManager(); diff --git a/admin-dashboard/start.sh b/admin-dashboard/start.sh deleted file mode 100755 index 6334a08..0000000 --- a/admin-dashboard/start.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -echo "🚀 Starting KHY Admin Dashboard..." -echo "📱 Dashboard will be available at: http://localhost:3000" -echo "🔧 To stop the server, press Ctrl+C" -echo "" -python3 -m http.server 3000