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