From fabdb06948ad1fc06038fd85ec9f5a7122b89922 Mon Sep 17 00:00:00 2001 From: damedotblog Date: Mon, 3 Mar 2025 11:26:27 -0500 Subject: [PATCH] add reorder tab --- src/components/Admin/AdminPanel.css | 134 ++++- src/components/Admin/AdminPanel.js | 850 ++++++++++++++++++---------- 2 files changed, 688 insertions(+), 296 deletions(-) diff --git a/src/components/Admin/AdminPanel.css b/src/components/Admin/AdminPanel.css index 9276191..cfca4a5 100644 --- a/src/components/Admin/AdminPanel.css +++ b/src/components/Admin/AdminPanel.css @@ -38,7 +38,7 @@ .resources-sidebar { flex-direction: column; height: 93.8%; - overflow: hidden; + overflow: scroll; padding: 10px; background: var(--navbar-bg); border: 5px solid var(--card-border); @@ -570,6 +570,138 @@ animation: spin 1s linear infinite; margin-bottom: 15px; } + + /* Tab navigation */ +.nav-tabs { + display: flex; + margin: 0 auto; + } + + .nav-tab { + padding: 10px 20px; + background: #f5f5f5; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + font-size: 16px; + } + + .nav-tab.active { + border-bottom: 2px solid #4a90e2; + font-weight: bold; + } + + /* Reorder container */ + .reorder-container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + } + + .reorder-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + } + + .reorder-controls { + display: flex; + align-items: center; + gap: 15px; + } + + .reorder-mode-selector { + display: flex; + gap: 10px; + } + + .mode-button { + padding: 8px 16px; + background: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + } + + .mode-button.active { + background: #4a90e2; + color: white; + border-color: #4a90e2; + } + + .category-select { + padding: 8px; + min-width: 200px; + border: 1px solid #ddd; + border-radius: 4px; + } + + /* Sortable resources list */ + .sortable-resources { + display: flex; + flex-direction: column; + gap: 10px; + } + + .sortable-resource-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; + background: white; + border: 1px solid #eee; + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + + .resource-info { + flex: 1; + } + + .resource-name { + font-weight: bold; + margin-bottom: 5px; + } + + .resource-meta { + display: flex; + gap: 10px; + font-size: 0.9rem; + } + + .position-indicator { + color: #666; + } + + .reorder-actions { + display: flex; + gap: 5px; + } + + .move-button { + width: 32px; + height: 32px; + background: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + } + + .move-button:hover { + background: #e5e5e5; + } + + .no-resources-message, + .select-category-message { + padding: 20px; + text-align: center; + color: #666; + } @keyframes spin { 0% { transform: rotate(0deg); } diff --git a/src/components/Admin/AdminPanel.js b/src/components/Admin/AdminPanel.js index 87b11e4..b765f05 100644 --- a/src/components/Admin/AdminPanel.js +++ b/src/components/Admin/AdminPanel.js @@ -18,6 +18,11 @@ const AdminPanel = () => { const [categoryFilter, setCategoryFilter] = useState('all'); const [tagFilter, setTagFilter] = useState('all'); const [featuredFilter, setFeaturedFilter] = useState('all'); + + // View management + const [activeView, setActiveView] = useState('resources'); // 'resources', 'reorder' + const [reorderMode, setReorderMode] = useState('featured'); // 'featured', 'category' + const [selectedCategoryForReorder, setSelectedCategoryForReorder] = useState(null); // Login state const [email, setEmail] = useState(''); @@ -256,6 +261,97 @@ const AdminPanel = () => { }); }; + // Reorder resources (move up or down in list) + const handleReorderResource = async (resourceId, direction) => { + const resourceIndex = resources.findIndex(r => r.id === resourceId); + + if (resourceIndex === -1) return; + + let filteredResources = resources; + + // Filter resources based on reorder mode + if (reorderMode === 'featured') { + filteredResources = resources.filter(r => r.featured); + } else if (reorderMode === 'category' && selectedCategoryForReorder) { + filteredResources = resources.filter(r => + r.categoryIds && r.categoryIds.includes(selectedCategoryForReorder) + ); + } + + const resourceToMoveIndex = filteredResources.findIndex(r => r.id === resourceId); + + if (resourceToMoveIndex === -1) return; + + const adjacentIndex = direction === 'up' + ? Math.max(0, resourceToMoveIndex - 1) + : Math.min(filteredResources.length - 1, resourceToMoveIndex + 1); + + if (adjacentIndex === resourceToMoveIndex) return; + + const resourceToMove = filteredResources[resourceToMoveIndex]; + const adjacentResource = filteredResources[adjacentIndex]; + + setIsLoading(true); + + try { + // Swap positions + const tempPosition = resourceToMove.position; + + await supabase + .from('resources') + .update({ position: adjacentResource.position }) + .eq('id', resourceToMove.id); + + await supabase + .from('resources') + .update({ position: tempPosition }) + .eq('id', adjacentResource.id); + + // Refresh data + await fetchAllData(); + + showAlert(`Resources reordered successfully!`); + } catch (error) { + console.error('Error reordering resources:', error); + showAlert(`Error: ${error.message}`, 'error'); + } finally { + setIsLoading(false); + } + }; + + // Save all positions at once + const saveAllPositions = async (orderedResources) => { + setIsLoading(true); + + try { + // Create an array of update operations + const updates = orderedResources.map((resource, index) => ({ + id: resource.id, + position: index + 1 + })); + + // Execute updates in parallel + const promises = updates.map(update => + supabase + .from('resources') + .update({ position: update.position }) + .eq('id', update.id) + ); + + await Promise.all(promises); + + // Refresh data + await fetchAllData(); + + showAlert(`Resource positions updated successfully!`); + } catch (error) { + console.error('Error updating positions:', error); + showAlert(`Error: ${error.message}`, 'error'); + } finally { + setIsLoading(false); + } + }; + // Filter resources based on status, search query, completeness, category, tag, and featured status const filteredResources = resources.filter(resource => { // Status filter @@ -598,6 +694,20 @@ const AdminPanel = () => { {/* Header */}

Resources Admin Panel

+
+ + +
@@ -608,17 +718,17 @@ const AdminPanel = () => { )} -
- {/* Resources list sidebar */} -
-
-

Resources

- -
-
-
+ {activeView === 'resources' && ( +
+ {/* Resources list sidebar */} +
+
+

Resources

+ +
+
{ )}
+
+ + +
+
+ +
+
+ + +
-
- - -
-
- -
-
- - +
+ + Showing {filteredResources.length} of {resources.length} resources +
-
-
- - Showing {filteredResources.length} of {resources.length} resources - -
-
- {filteredResources.length > 0 ? ( - filteredResources.map(resource => ( -
handleSelectResource(resource)} - > -
-
-
-
-
{resource.name}
-
- - {resource.status} - - {resource.featured && Featured} - - {resource.completeness}% - +
+ {filteredResources.length > 0 ? ( + filteredResources.map(resource => ( +
handleSelectResource(resource)} + > +
+
+
+
+
{resource.name}
+
+ + {resource.status} + + {resource.featured && Featured} + + {resource.completeness}% + +
+
+
+
-
-
+
+ + {/* Resource edit form */} +
+
+

{selectedResource ? 'Edit Resource' : 'Add New Resource'}

+
+
+ Status: +
+ + +
- )) - ) : ( -
-

No resources match your filters.

+
- )} -
-
- - {/* Resource edit form */} -
-
-

{selectedResource ? 'Edit Resource' : 'Add New Resource'}

-
-
- Status: -
- - - +
+
+
+
+ + +
+
+ +
- -
-
- -
+
- + -
-
- -
-
- -
- - -
-
- - -
- -
- - Description * +
-
-
- {/* Categories selection */} -
-
- - +
+
+ +
-
- {categories.length > 0 ? ( - categories.map(category => ( -
- handleCategoryChange(category.id)} - /> - -
- )) - ) : ( -

No categories available. Create one!

- )} +
+ +
- {/* Tags selection */} -
-
- - +
+ {/* Categories selection */} +
+
+ + +
+
+ {categories.length > 0 ? ( + categories.map(category => ( +
+ handleCategoryChange(category.id)} + /> + +
+ )) + ) : ( +

No categories available. Create one!

+ )} +
-
- {tags.length > 0 ? ( - tags.map(tag => ( -
- handleTagChange(tag.id)} - /> - -
- )) - ) : ( -

No tags available. Create one!

- )} + + {/* Tags selection */} +
+
+ + +
+
+ {tags.length > 0 ? ( + tags.map(tag => ( +
+ handleTagChange(tag.id)} + /> + +
+ )) + ) : ( +

No tags available. Create one!

+ )} +
-
-
- - +
+ + +
+ +
+
+ )} + + {activeView === 'reorder' && ( +
+
+

Reorder Resources

+
+
+ + +
+ + {reorderMode === 'category' && ( +
+ +
+ )}
- +
+ +
+ {reorderMode === 'featured' ? ( +
+

Featured Resources

+ {resources.filter(r => r.featured).length === 0 ? ( +

No featured resources found.

+ ) : ( +
+ {resources + .filter(r => r.featured) + .sort((a, b) => a.position - b.position) + .map(resource => ( +
+
+
{resource.name}
+
+ + {resource.status} + + + Position: {resource.position} + +
+
+
+ + +
+
+ )) + } +
+ )} +
+ ) : ( +
+ {!selectedCategoryForReorder ? ( +

Please select a category from the dropdown above.

+ ) : ( + <> +

+ {categories.find(c => c.id === selectedCategoryForReorder)?.emoji} {' '} + {categories.find(c => c.id === selectedCategoryForReorder)?.name} Resources +

+ {resources.filter(r => + r.categoryIds && r.categoryIds.includes(selectedCategoryForReorder) + ).length === 0 ? ( +

No resources found in this category.

+ ) : ( +
+ {resources + .filter(r => r.categoryIds && r.categoryIds.includes(selectedCategoryForReorder)) + .sort((a, b) => a.position - b.position) + .map(resource => ( +
+
+
{resource.name}
+
+ + {resource.status} + + {resource.featured && Featured} + + Position: {resource.position} + +
+
+
+ + +
+
+ )) + } +
+ )} + + )} +
+ )} +
-
+ )}
); }; -- 2.51.2