This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

add reorder tab

+688 -296
+133 -1
src/components/Admin/AdminPanel.css
··· 38 38 .resources-sidebar { 39 39 flex-direction: column; 40 40 height: 93.8%; 41 - overflow: hidden; 41 + overflow: scroll; 42 42 padding: 10px; 43 43 background: var(--navbar-bg); 44 44 border: 5px solid var(--card-border); ··· 569 569 height: 40px; 570 570 animation: spin 1s linear infinite; 571 571 margin-bottom: 15px; 572 + } 573 + 574 + /* Tab navigation */ 575 + .nav-tabs { 576 + display: flex; 577 + margin: 0 auto; 578 + } 579 + 580 + .nav-tab { 581 + padding: 10px 20px; 582 + background: #f5f5f5; 583 + border: none; 584 + border-bottom: 2px solid transparent; 585 + cursor: pointer; 586 + font-size: 16px; 587 + } 588 + 589 + .nav-tab.active { 590 + border-bottom: 2px solid #4a90e2; 591 + font-weight: bold; 592 + } 593 + 594 + /* Reorder container */ 595 + .reorder-container { 596 + max-width: 1200px; 597 + margin: 0 auto; 598 + padding: 20px; 599 + } 600 + 601 + .reorder-header { 602 + display: flex; 603 + justify-content: space-between; 604 + align-items: center; 605 + margin-bottom: 20px; 606 + } 607 + 608 + .reorder-controls { 609 + display: flex; 610 + align-items: center; 611 + gap: 15px; 612 + } 613 + 614 + .reorder-mode-selector { 615 + display: flex; 616 + gap: 10px; 617 + } 618 + 619 + .mode-button { 620 + padding: 8px 16px; 621 + background: #f5f5f5; 622 + border: 1px solid #ddd; 623 + border-radius: 4px; 624 + cursor: pointer; 625 + } 626 + 627 + .mode-button.active { 628 + background: #4a90e2; 629 + color: white; 630 + border-color: #4a90e2; 631 + } 632 + 633 + .category-select { 634 + padding: 8px; 635 + min-width: 200px; 636 + border: 1px solid #ddd; 637 + border-radius: 4px; 638 + } 639 + 640 + /* Sortable resources list */ 641 + .sortable-resources { 642 + display: flex; 643 + flex-direction: column; 644 + gap: 10px; 645 + } 646 + 647 + .sortable-resource-item { 648 + display: flex; 649 + justify-content: space-between; 650 + align-items: center; 651 + padding: 15px; 652 + background: white; 653 + border: 1px solid #eee; 654 + border-radius: 4px; 655 + box-shadow: 0 1px 3px rgba(0,0,0,0.1); 656 + } 657 + 658 + .resource-info { 659 + flex: 1; 660 + } 661 + 662 + .resource-name { 663 + font-weight: bold; 664 + margin-bottom: 5px; 665 + } 666 + 667 + .resource-meta { 668 + display: flex; 669 + gap: 10px; 670 + font-size: 0.9rem; 671 + } 672 + 673 + .position-indicator { 674 + color: #666; 675 + } 676 + 677 + .reorder-actions { 678 + display: flex; 679 + gap: 5px; 680 + } 681 + 682 + .move-button { 683 + width: 32px; 684 + height: 32px; 685 + background: #f5f5f5; 686 + border: 1px solid #ddd; 687 + border-radius: 4px; 688 + cursor: pointer; 689 + display: flex; 690 + align-items: center; 691 + justify-content: center; 692 + font-size: 18px; 693 + } 694 + 695 + .move-button:hover { 696 + background: #e5e5e5; 697 + } 698 + 699 + .no-resources-message, 700 + .select-category-message { 701 + padding: 20px; 702 + text-align: center; 703 + color: #666; 572 704 } 573 705 574 706 @keyframes spin {
+555 -295
src/components/Admin/AdminPanel.js
··· 18 18 const [categoryFilter, setCategoryFilter] = useState('all'); 19 19 const [tagFilter, setTagFilter] = useState('all'); 20 20 const [featuredFilter, setFeaturedFilter] = useState('all'); 21 + 22 + // View management 23 + const [activeView, setActiveView] = useState('resources'); // 'resources', 'reorder' 24 + const [reorderMode, setReorderMode] = useState('featured'); // 'featured', 'category' 25 + const [selectedCategoryForReorder, setSelectedCategoryForReorder] = useState(null); 21 26 22 27 // Login state 23 28 const [email, setEmail] = useState(''); ··· 254 259 ...formData, 255 260 status 256 261 }); 262 + }; 263 + 264 + // Reorder resources (move up or down in list) 265 + const handleReorderResource = async (resourceId, direction) => { 266 + const resourceIndex = resources.findIndex(r => r.id === resourceId); 267 + 268 + if (resourceIndex === -1) return; 269 + 270 + let filteredResources = resources; 271 + 272 + // Filter resources based on reorder mode 273 + if (reorderMode === 'featured') { 274 + filteredResources = resources.filter(r => r.featured); 275 + } else if (reorderMode === 'category' && selectedCategoryForReorder) { 276 + filteredResources = resources.filter(r => 277 + r.categoryIds && r.categoryIds.includes(selectedCategoryForReorder) 278 + ); 279 + } 280 + 281 + const resourceToMoveIndex = filteredResources.findIndex(r => r.id === resourceId); 282 + 283 + if (resourceToMoveIndex === -1) return; 284 + 285 + const adjacentIndex = direction === 'up' 286 + ? Math.max(0, resourceToMoveIndex - 1) 287 + : Math.min(filteredResources.length - 1, resourceToMoveIndex + 1); 288 + 289 + if (adjacentIndex === resourceToMoveIndex) return; 290 + 291 + const resourceToMove = filteredResources[resourceToMoveIndex]; 292 + const adjacentResource = filteredResources[adjacentIndex]; 293 + 294 + setIsLoading(true); 295 + 296 + try { 297 + // Swap positions 298 + const tempPosition = resourceToMove.position; 299 + 300 + await supabase 301 + .from('resources') 302 + .update({ position: adjacentResource.position }) 303 + .eq('id', resourceToMove.id); 304 + 305 + await supabase 306 + .from('resources') 307 + .update({ position: tempPosition }) 308 + .eq('id', adjacentResource.id); 309 + 310 + // Refresh data 311 + await fetchAllData(); 312 + 313 + showAlert(`Resources reordered successfully!`); 314 + } catch (error) { 315 + console.error('Error reordering resources:', error); 316 + showAlert(`Error: ${error.message}`, 'error'); 317 + } finally { 318 + setIsLoading(false); 319 + } 320 + }; 321 + 322 + // Save all positions at once 323 + const saveAllPositions = async (orderedResources) => { 324 + setIsLoading(true); 325 + 326 + try { 327 + // Create an array of update operations 328 + const updates = orderedResources.map((resource, index) => ({ 329 + id: resource.id, 330 + position: index + 1 331 + })); 332 + 333 + // Execute updates in parallel 334 + const promises = updates.map(update => 335 + supabase 336 + .from('resources') 337 + .update({ position: update.position }) 338 + .eq('id', update.id) 339 + ); 340 + 341 + await Promise.all(promises); 342 + 343 + // Refresh data 344 + await fetchAllData(); 345 + 346 + showAlert(`Resource positions updated successfully!`); 347 + } catch (error) { 348 + console.error('Error updating positions:', error); 349 + showAlert(`Error: ${error.message}`, 'error'); 350 + } finally { 351 + setIsLoading(false); 352 + } 257 353 }; 258 354 259 355 // Filter resources based on status, search query, completeness, category, tag, and featured status ··· 598 694 {/* Header */} 599 695 <header className="admin-header"> 600 696 <h1>Resources Admin Panel</h1> 697 + <div className="nav-tabs"> 698 + <button 699 + className={`nav-tab ${activeView === 'resources' ? 'active' : ''}`} 700 + onClick={() => setActiveView('resources')} 701 + > 702 + Resources 703 + </button> 704 + <button 705 + className={`nav-tab ${activeView === 'reorder' ? 'active' : ''}`} 706 + onClick={() => setActiveView('reorder')} 707 + > 708 + Reorder 709 + </button> 710 + </div> 601 711 <button onClick={handleLogout} className="logout-button">Logout</button> 602 712 </header> 603 713 ··· 608 718 </div> 609 719 )} 610 720 611 - <div className="admin-container"> 612 - {/* Resources list sidebar */} 613 - <div className="resources-sidebar"> 614 - <div className="sidebar-header"> 615 - <h2>Resources</h2> 616 - <button onClick={handleClearForm} className="add-new-button"> 617 - + New Resource 618 - </button> 619 - </div> 620 - <div className="sidebar-filters"> 621 - <div className="filter-group"> 721 + {activeView === 'resources' && ( 722 + <div className="admin-container"> 723 + {/* Resources list sidebar */} 724 + <div className="resources-sidebar"> 725 + <div className="sidebar-header"> 726 + <h2>Resources</h2> 727 + <button onClick={handleClearForm} className="add-new-button"> 728 + + New Resource 729 + </button> 730 + </div> 731 + <div className="sidebar-filters"> 622 732 <div className="search-container"> 623 733 <input 624 734 type="text" ··· 638 748 </button> 639 749 )} 640 750 </div> 751 + <div className="filter-group"> 752 + <select 753 + value={statusFilter} 754 + onChange={(e) => setStatusFilter(e.target.value)} 755 + className="status-filter" 756 + > 757 + <option value="all">All Statuses</option> 758 + <option value="draft">Draft</option> 759 + <option value="review">Review</option> 760 + <option value="published">Published</option> 761 + </select> 762 + <select 763 + value={featuredFilter} 764 + onChange={(e) => setFeaturedFilter(e.target.value)} 765 + className="featured-filter" 766 + > 767 + <option value="all">All Resources</option> 768 + <option value="featured">Featured Only</option> 769 + <option value="not-featured">Not Featured</option> 770 + </select> 771 + </div> 772 + <div className="filter-group"> 773 + <select 774 + value={completenessFilter} 775 + onChange={(e) => setCompletenessFilter(e.target.value)} 776 + className="completeness-filter" 777 + > 778 + <option value="all">All Completeness</option> 779 + <option value="incomplete">Incomplete Only</option> 780 + <option value="complete">100% Complete Only</option> 781 + <option value="min-25">At least 25%</option> 782 + <option value="min-50">At least 50%</option> 783 + <option value="min-75">At least 75%</option> 784 + <option value="max-25">Less than 25%</option> 785 + <option value="max-50">Less than 50%</option> 786 + <option value="max-75">Less than 75%</option> 787 + </select> 788 + </div> 789 + <div className="filter-group"> 790 + <select 791 + value={categoryFilter} 792 + onChange={(e) => setCategoryFilter(e.target.value)} 793 + className="category-filter" 794 + > 795 + <option value="all">All Categories</option> 796 + {categories.map(category => ( 797 + <option key={category.id} value={category.id}> 798 + {category.emoji} {category.name} 799 + </option> 800 + ))} 801 + </select> 802 + <select 803 + value={tagFilter} 804 + onChange={(e) => setTagFilter(e.target.value)} 805 + className="tag-filter" 806 + > 807 + <option value="all">All Tags</option> 808 + {tags.map(tag => ( 809 + <option key={tag.id} value={tag.id}> 810 + #{tag.name} 811 + </option> 812 + ))} 813 + </select> 814 + </div> 641 815 </div> 642 - <div className="filter-group"> 643 - <select 644 - value={statusFilter} 645 - onChange={(e) => setStatusFilter(e.target.value)} 646 - className="status-filter" 647 - > 648 - <option value="all">All Statuses</option> 649 - <option value="draft">Draft</option> 650 - <option value="review">Review</option> 651 - <option value="published">Published</option> 652 - </select> 653 - <select 654 - value={featuredFilter} 655 - onChange={(e) => setFeaturedFilter(e.target.value)} 656 - className="featured-filter" 657 - > 658 - <option value="all">All Resources</option> 659 - <option value="featured">Featured Only</option> 660 - <option value="not-featured">Not Featured</option> 661 - </select> 816 + <div className="resources-summary"> 817 + <span className="resources-count"> 818 + Showing {filteredResources.length} of {resources.length} resources 819 + </span> 662 820 </div> 663 - <div className="filter-group"> 664 - <select 665 - value={completenessFilter} 666 - onChange={(e) => setCompletenessFilter(e.target.value)} 667 - className="completeness-filter" 668 - > 669 - <option value="all">All Completeness</option> 670 - <option value="incomplete">Incomplete Only</option> 671 - <option value="complete">100% Complete Only</option> 672 - <option value="min-25">At least 25%</option> 673 - <option value="min-50">At least 50%</option> 674 - <option value="min-75">At least 75%</option> 675 - <option value="max-25">Less than 25%</option> 676 - <option value="max-50">Less than 50%</option> 677 - <option value="max-75">Less than 75%</option> 678 - </select> 679 - </div> 680 - <div className="filter-group"> 681 - <select 682 - value={categoryFilter} 683 - onChange={(e) => setCategoryFilter(e.target.value)} 684 - className="category-filter" 685 - > 686 - <option value="all">All Categories</option> 687 - {categories.map(category => ( 688 - <option key={category.id} value={category.id}> 689 - {category.emoji} {category.name} 690 - </option> 691 - ))} 692 - </select> 693 - <select 694 - value={tagFilter} 695 - onChange={(e) => setTagFilter(e.target.value)} 696 - className="tag-filter" 697 - > 698 - <option value="all">All Tags</option> 699 - {tags.map(tag => ( 700 - <option key={tag.id} value={tag.id}> 701 - #{tag.name} 702 - </option> 703 - ))} 704 - </select> 705 - </div> 706 - </div> 707 - <div className="resources-summary"> 708 - <span className="resources-count"> 709 - Showing {filteredResources.length} of {resources.length} resources 710 - </span> 711 - </div> 712 - <div className="resources-list"> 713 - {filteredResources.length > 0 ? ( 714 - filteredResources.map(resource => ( 715 - <div 716 - key={resource.id} 717 - className={`resource-item ${selectedResource && selectedResource.id === resource.id ? 'selected' : ''} status-${resource.status}`} 718 - onClick={() => handleSelectResource(resource)} 719 - > 720 - <div className="resource-completeness-indicator"> 721 - <div 722 - className="completeness-bar" 723 - style={{ width: `${resource.completeness}%` }} 724 - title={`${resource.completeness}% complete`} 725 - ></div> 726 - </div> 727 - <div className="resource-item-content"> 728 - <div className="resource-item-name">{resource.name}</div> 729 - <div className="resource-item-meta"> 730 - <span className={`status-badge status-${resource.status}`}> 731 - {resource.status} 732 - </span> 733 - {resource.featured && <span className="featured-badge">Featured</span>} 734 - <span className="completeness-badge" title="Completeness"> 735 - {resource.completeness}% 736 - </span> 821 + <div className="resources-list"> 822 + {filteredResources.length > 0 ? ( 823 + filteredResources.map(resource => ( 824 + <div 825 + key={resource.id} 826 + className={`resource-item ${selectedResource && selectedResource.id === resource.id ? 'selected' : ''} status-${resource.status}`} 827 + onClick={() => handleSelectResource(resource)} 828 + > 829 + <div className="resource-completeness-indicator"> 830 + <div 831 + className="completeness-bar" 832 + style={{ width: `${resource.completeness}%` }} 833 + title={`${resource.completeness}% complete`} 834 + ></div> 835 + </div> 836 + <div className="resource-item-content"> 837 + <div className="resource-item-name">{resource.name}</div> 838 + <div className="resource-item-meta"> 839 + <span className={`status-badge status-${resource.status}`}> 840 + {resource.status} 841 + </span> 842 + {resource.featured && <span className="featured-badge">Featured</span>} 843 + <span className="completeness-badge" title="Completeness"> 844 + {resource.completeness}% 845 + </span> 846 + </div> 847 + </div> 848 + <div className="resource-item-actions"> 849 + <button 850 + onClick={(e) => { 851 + e.stopPropagation(); 852 + handleDeleteResource(resource.id, resource.name); 853 + }} 854 + className="delete-button" 855 + title="Delete resource" 856 + > 857 + 🗑️ 858 + </button> 737 859 </div> 738 860 </div> 739 - <div className="resource-item-actions"> 740 - <button 741 - onClick={(e) => { 742 - e.stopPropagation(); 743 - handleDeleteResource(resource.id, resource.name); 744 - }} 745 - className="delete-button" 746 - title="Delete resource" 861 + )) 862 + ) : ( 863 + <div className="no-resources-message"> 864 + <p>No resources match your filters.</p> 865 + </div> 866 + )} 867 + </div> 868 + </div> 869 + 870 + {/* Resource edit form */} 871 + <div className="resource-editor"> 872 + <div className="editor-header"> 873 + <h2>{selectedResource ? 'Edit Resource' : 'Add New Resource'}</h2> 874 + <div className="floating-actions"> 875 + <div className="status-selector"> 876 + <span>Status:</span> 877 + <div className="status-buttons"> 878 + <button 879 + type="button" 880 + className={`status-button ${formData.status === 'draft' ? 'active' : ''}`} 881 + onClick={() => handleStatusChange('draft')} 747 882 > 748 - 🗑️ 883 + Draft 884 + </button> 885 + <button 886 + type="button" 887 + className={`status-button ${formData.status === 'review' ? 'active' : ''}`} 888 + onClick={() => handleStatusChange('review')} 889 + > 890 + Review 891 + </button> 892 + <button 893 + type="button" 894 + className={`status-button ${formData.status === 'published' ? 'active' : ''}`} 895 + onClick={() => handleStatusChange('published')} 896 + > 897 + Published 749 898 </button> 750 899 </div> 751 900 </div> 752 - )) 753 - ) : ( 754 - <div className="no-resources-message"> 755 - <p>No resources match your filters.</p> 901 + <button 902 + type="button" 903 + onClick={handleSaveResource} 904 + className="floating-save-button" 905 + > 906 + {selectedResource ? 'Update Resource' : 'Create Resource'} 907 + </button> 756 908 </div> 757 - )} 758 - </div> 759 - </div> 760 - 761 - {/* Resource edit form */} 762 - <div className="resource-editor"> 763 - <div className="editor-header"> 764 - <h2>{selectedResource ? 'Edit Resource' : 'Add New Resource'}</h2> 765 - <div className="floating-actions"> 766 - <div className="status-selector"> 767 - <span>Status:</span> 768 - <div className="status-buttons"> 769 - <button 770 - type="button" 771 - className={`status-button ${formData.status === 'draft' ? 'active' : ''}`} 772 - onClick={() => handleStatusChange('draft')} 773 - > 774 - Draft 775 - </button> 776 - <button 777 - type="button" 778 - className={`status-button ${formData.status === 'review' ? 'active' : ''}`} 779 - onClick={() => handleStatusChange('review')} 780 - > 781 - Review 782 - </button> 783 - <button 784 - type="button" 785 - className={`status-button ${formData.status === 'published' ? 'active' : ''}`} 786 - onClick={() => handleStatusChange('published')} 787 - > 788 - Published 789 - </button> 909 + </div> 910 + <form onSubmit={handleSaveResource}> 911 + <div className="form-row"> 912 + <div className="form-group"> 913 + <label htmlFor="name">Name *</label> 914 + <input 915 + type="text" 916 + id="name" 917 + name="name" 918 + value={formData.name} 919 + onChange={handleInputChange} 920 + required 921 + placeholder="Resource name" 922 + /> 923 + </div> 924 + <div className="form-group"> 925 + <label htmlFor="domain">Domain</label> 926 + <input 927 + type="text" 928 + id="domain" 929 + name="domain" 930 + value={formData.domain} 931 + onChange={handleInputChange} 932 + placeholder="e.g., design, development, marketing" 933 + /> 790 934 </div> 791 935 </div> 792 - <button 793 - type="button" 794 - onClick={handleSaveResource} 795 - className="floating-save-button" 796 - > 797 - {selectedResource ? 'Update Resource' : 'Create Resource'} 798 - </button> 799 - </div> 800 - </div> 801 - <form onSubmit={handleSaveResource}> 802 - <div className="form-row"> 936 + 803 937 <div className="form-group"> 804 - <label htmlFor="name">Name *</label> 938 + <label htmlFor="url">URL *</label> 805 939 <input 806 - type="text" 807 - id="name" 808 - name="name" 809 - value={formData.name} 940 + type="url" 941 + id="url" 942 + name="url" 943 + value={formData.url} 810 944 onChange={handleInputChange} 811 945 required 812 - placeholder="Resource name" 946 + placeholder="https://example.com" 813 947 /> 814 948 </div> 949 + 815 950 <div className="form-group"> 816 - <label htmlFor="domain">Domain</label> 817 - <input 818 - type="text" 819 - id="domain" 820 - name="domain" 821 - value={formData.domain} 951 + <label htmlFor="description">Description *</label> 952 + <textarea 953 + id="description" 954 + name="description" 955 + value={formData.description} 822 956 onChange={handleInputChange} 823 - placeholder="e.g., design, development, marketing" 824 - /> 957 + rows="4" 958 + required 959 + placeholder="Brief description of the resource..." 960 + ></textarea> 825 961 </div> 826 - </div> 827 962 828 - <div className="form-group"> 829 - <label htmlFor="url">URL *</label> 830 - <input 831 - type="url" 832 - id="url" 833 - name="url" 834 - value={formData.url} 835 - onChange={handleInputChange} 836 - required 837 - placeholder="https://example.com" 838 - /> 839 - </div> 963 + <div className="form-row"> 964 + <div className="form-group"> 965 + <label htmlFor="position">Position</label> 966 + <input 967 + type="number" 968 + id="position" 969 + name="position" 970 + value={formData.position} 971 + onChange={handleInputChange} 972 + min="0" 973 + /> 974 + </div> 975 + <div className="form-group checkbox-group"> 976 + <input 977 + type="checkbox" 978 + id="featured" 979 + name="featured" 980 + checked={formData.featured} 981 + onChange={handleInputChange} 982 + /> 983 + <label htmlFor="featured">Featured Resource</label> 984 + </div> 985 + </div> 840 986 841 - <div className="form-group"> 842 - <label htmlFor="description">Description *</label> 843 - <textarea 844 - id="description" 845 - name="description" 846 - value={formData.description} 847 - onChange={handleInputChange} 848 - rows="4" 849 - required 850 - placeholder="Brief description of the resource..." 851 - ></textarea> 852 - </div> 987 + <div className="form-row"> 988 + {/* Categories selection */} 989 + <div className="form-group categories-section"> 990 + <div className="section-header"> 991 + <label>Categories</label> 992 + <button 993 + type="button" 994 + onClick={handleCreateCategory} 995 + className="add-item-button" 996 + > 997 + + Add Category 998 + </button> 999 + </div> 1000 + <div className="checkbox-list"> 1001 + {categories.length > 0 ? ( 1002 + categories.map(category => ( 1003 + <div key={category.id} className="checkbox-item"> 1004 + <input 1005 + type="checkbox" 1006 + id={`category-${category.id}`} 1007 + checked={formData.selectedCategories.includes(category.id)} 1008 + onChange={() => handleCategoryChange(category.id)} 1009 + /> 1010 + <label htmlFor={`category-${category.id}`}> 1011 + {category.emoji} {category.name} 1012 + </label> 1013 + </div> 1014 + )) 1015 + ) : ( 1016 + <p className="no-items-message">No categories available. Create one!</p> 1017 + )} 1018 + </div> 1019 + </div> 853 1020 854 - <div className="form-row"> 855 - <div className="form-group"> 856 - <label htmlFor="position">Position</label> 857 - <input 858 - type="number" 859 - id="position" 860 - name="position" 861 - value={formData.position} 862 - onChange={handleInputChange} 863 - min="0" 864 - /> 1021 + {/* Tags selection */} 1022 + <div className="form-group tags-section"> 1023 + <div className="section-header"> 1024 + <label>Tags</label> 1025 + <button 1026 + type="button" 1027 + onClick={handleCreateTag} 1028 + className="add-item-button" 1029 + > 1030 + + Add Tag 1031 + </button> 1032 + </div> 1033 + <div className="checkbox-list"> 1034 + {tags.length > 0 ? ( 1035 + tags.map(tag => ( 1036 + <div key={tag.id} className="checkbox-item"> 1037 + <input 1038 + type="checkbox" 1039 + id={`tag-${tag.id}`} 1040 + checked={formData.selectedTags.includes(tag.id)} 1041 + onChange={() => handleTagChange(tag.id)} 1042 + /> 1043 + <label htmlFor={`tag-${tag.id}`}> 1044 + #{tag.name} 1045 + </label> 1046 + </div> 1047 + )) 1048 + ) : ( 1049 + <p className="no-items-message">No tags available. Create one!</p> 1050 + )} 1051 + </div> 1052 + </div> 865 1053 </div> 866 - <div className="form-group checkbox-group"> 867 - <input 868 - type="checkbox" 869 - id="featured" 870 - name="featured" 871 - checked={formData.featured} 872 - onChange={handleInputChange} 873 - /> 874 - <label htmlFor="featured">Featured Resource</label> 1054 + 1055 + <div className="form-actions"> 1056 + <button type="button" onClick={handleClearForm} className="cancel-button"> 1057 + Cancel 1058 + </button> 1059 + <button type="submit" className="save-button"> 1060 + {selectedResource ? 'Update Resource' : 'Create Resource'} 1061 + </button> 875 1062 </div> 876 - </div> 1063 + </form> 1064 + </div> 1065 + </div> 1066 + )} 877 1067 878 - <div className="form-row"> 879 - {/* Categories selection */} 880 - <div className="form-group categories-section"> 881 - <div className="section-header"> 882 - <label>Categories</label> 883 - <button 884 - type="button" 885 - onClick={handleCreateCategory} 886 - className="add-item-button" 887 - > 888 - + Add Category 889 - </button> 890 - </div> 891 - <div className="checkbox-list"> 892 - {categories.length > 0 ? ( 893 - categories.map(category => ( 894 - <div key={category.id} className="checkbox-item"> 895 - <input 896 - type="checkbox" 897 - id={`category-${category.id}`} 898 - checked={formData.selectedCategories.includes(category.id)} 899 - onChange={() => handleCategoryChange(category.id)} 900 - /> 901 - <label htmlFor={`category-${category.id}`}> 902 - {category.emoji} {category.name} 903 - </label> 904 - </div> 905 - )) 906 - ) : ( 907 - <p className="no-items-message">No categories available. Create one!</p> 908 - )} 909 - </div> 1068 + {activeView === 'reorder' && ( 1069 + <div className="reorder-container"> 1070 + <div className="reorder-header"> 1071 + <h2>Reorder Resources</h2> 1072 + <div className="reorder-controls"> 1073 + <div className="reorder-mode-selector"> 1074 + <button 1075 + className={`mode-button ${reorderMode === 'featured' ? 'active' : ''}`} 1076 + onClick={() => { 1077 + setReorderMode('featured'); 1078 + setSelectedCategoryForReorder(null); 1079 + }} 1080 + > 1081 + Featured Resources 1082 + </button> 1083 + <button 1084 + className={`mode-button ${reorderMode === 'category' ? 'active' : ''}`} 1085 + onClick={() => setReorderMode('category')} 1086 + > 1087 + By Category 1088 + </button> 910 1089 </div> 911 - 912 - {/* Tags selection */} 913 - <div className="form-group tags-section"> 914 - <div className="section-header"> 915 - <label>Tags</label> 916 - <button 917 - type="button" 918 - onClick={handleCreateTag} 919 - className="add-item-button" 1090 + 1091 + {reorderMode === 'category' && ( 1092 + <div className="category-selector"> 1093 + <select 1094 + value={selectedCategoryForReorder || ''} 1095 + onChange={(e) => setSelectedCategoryForReorder(parseInt(e.target.value))} 1096 + className="category-select" 920 1097 > 921 - + Add Tag 922 - </button> 1098 + <option value="">Select Category...</option> 1099 + {categories.map(category => ( 1100 + <option key={category.id} value={category.id}> 1101 + {category.emoji} {category.name} 1102 + </option> 1103 + ))} 1104 + </select> 923 1105 </div> 924 - <div className="checkbox-list"> 925 - {tags.length > 0 ? ( 926 - tags.map(tag => ( 927 - <div key={tag.id} className="checkbox-item"> 928 - <input 929 - type="checkbox" 930 - id={`tag-${tag.id}`} 931 - checked={formData.selectedTags.includes(tag.id)} 932 - onChange={() => handleTagChange(tag.id)} 933 - /> 934 - <label htmlFor={`tag-${tag.id}`}> 935 - #{tag.name} 936 - </label> 1106 + )} 1107 + </div> 1108 + </div> 1109 + 1110 + <div className="reorder-content"> 1111 + {reorderMode === 'featured' ? ( 1112 + <div className="reorder-list"> 1113 + <h3>Featured Resources</h3> 1114 + {resources.filter(r => r.featured).length === 0 ? ( 1115 + <p className="no-resources-message">No featured resources found.</p> 1116 + ) : ( 1117 + <div className="sortable-resources"> 1118 + {resources 1119 + .filter(r => r.featured) 1120 + .sort((a, b) => a.position - b.position) 1121 + .map(resource => ( 1122 + <div key={resource.id} className="sortable-resource-item"> 1123 + <div className="resource-info"> 1124 + <div className="resource-name">{resource.name}</div> 1125 + <div className="resource-meta"> 1126 + <span className={`status-badge status-${resource.status}`}> 1127 + {resource.status} 1128 + </span> 1129 + <span className="position-indicator"> 1130 + Position: {resource.position} 1131 + </span> 1132 + </div> 1133 + </div> 1134 + <div className="reorder-actions"> 1135 + <button 1136 + onClick={() => handleReorderResource(resource.id, 'up')} 1137 + className="move-button move-up" 1138 + title="Move up" 1139 + > 1140 + 1141 + </button> 1142 + <button 1143 + onClick={() => handleReorderResource(resource.id, 'down')} 1144 + className="move-button move-down" 1145 + title="Move down" 1146 + > 1147 + 1148 + </button> 1149 + </div> 1150 + </div> 1151 + )) 1152 + } 1153 + </div> 1154 + )} 1155 + </div> 1156 + ) : ( 1157 + <div className="reorder-list"> 1158 + {!selectedCategoryForReorder ? ( 1159 + <p className="select-category-message">Please select a category from the dropdown above.</p> 1160 + ) : ( 1161 + <> 1162 + <h3> 1163 + {categories.find(c => c.id === selectedCategoryForReorder)?.emoji} {' '} 1164 + {categories.find(c => c.id === selectedCategoryForReorder)?.name} Resources 1165 + </h3> 1166 + {resources.filter(r => 1167 + r.categoryIds && r.categoryIds.includes(selectedCategoryForReorder) 1168 + ).length === 0 ? ( 1169 + <p className="no-resources-message">No resources found in this category.</p> 1170 + ) : ( 1171 + <div className="sortable-resources"> 1172 + {resources 1173 + .filter(r => r.categoryIds && r.categoryIds.includes(selectedCategoryForReorder)) 1174 + .sort((a, b) => a.position - b.position) 1175 + .map(resource => ( 1176 + <div key={resource.id} className="sortable-resource-item"> 1177 + <div className="resource-info"> 1178 + <div className="resource-name">{resource.name}</div> 1179 + <div className="resource-meta"> 1180 + <span className={`status-badge status-${resource.status}`}> 1181 + {resource.status} 1182 + </span> 1183 + {resource.featured && <span className="featured-badge">Featured</span>} 1184 + <span className="position-indicator"> 1185 + Position: {resource.position} 1186 + </span> 1187 + </div> 1188 + </div> 1189 + <div className="reorder-actions"> 1190 + <button 1191 + onClick={() => handleReorderResource(resource.id, 'up')} 1192 + className="move-button move-up" 1193 + title="Move up" 1194 + > 1195 + 1196 + </button> 1197 + <button 1198 + onClick={() => handleReorderResource(resource.id, 'down')} 1199 + className="move-button move-down" 1200 + title="Move down" 1201 + > 1202 + 1203 + </button> 1204 + </div> 1205 + </div> 1206 + )) 1207 + } 937 1208 </div> 938 - )) 939 - ) : ( 940 - <p className="no-items-message">No tags available. Create one!</p> 941 - )} 942 - </div> 1209 + )} 1210 + </> 1211 + )} 943 1212 </div> 944 - </div> 945 - 946 - <div className="form-actions"> 947 - <button type="button" onClick={handleClearForm} className="cancel-button"> 948 - Cancel 949 - </button> 950 - <button type="submit" className="save-button"> 951 - {selectedResource ? 'Update Resource' : 'Create Resource'} 952 - </button> 953 - </div> 954 - </form> 1213 + )} 1214 + </div> 955 1215 </div> 956 - </div> 1216 + )} 957 1217 </div> 958 1218 ); 959 1219 };