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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![]()
-
-
-
-
-
-
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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0 products in this category
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ⏳
- Preparing sync...
-
-
- ⏳
- Cloning main repository...
-
-
- ⏳
- Syncing products...
-
-
- ⏳
- Syncing images...
-
-
- ⏳
- Committing changes...
-
-
- ⏳
- Pushing to main site...
-
-
-
-
-
-
-
-
-
-
-
-
-
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.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 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 = `
-
-
-
-
-
-
📤
-
Drag & Drop images here
-
or click to select files
-
-
-
-
-
-
-
-
-
-
- `;
-
- // 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}
- ${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,
'">
-
-
-
${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 = `
-
-
- `;
- 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