···
26
26
font-weight: 800;
27
27
}
28
28
29
29
+
/* Tab navigation - Updated */
30
30
+
.nav-tabs {
31
31
+
display: flex;
32
32
+
margin: 0 auto;
33
33
+
}
34
34
+
35
35
+
.nav-tab {
36
36
+
padding: 10px 20px;
37
37
+
background-color: var(--navbar-bg);
38
38
+
border: 1px solid var(--card-border);
39
39
+
border-bottom: 2px solid transparent;
40
40
+
cursor: pointer;
41
41
+
font-size: 16px;
42
42
+
margin: 0 5px;
43
43
+
color: var(--text);
44
44
+
transition: all 0.3s ease;
45
45
+
}
46
46
+
47
47
+
.nav-tab:hover {
48
48
+
background-color: var(--background);
49
49
+
border-color: var(--button-bg);
50
50
+
}
51
51
+
52
52
+
.nav-tab.active {
53
53
+
border-bottom: 2px solid var(--button-bg);
54
54
+
color: var(--button-bg);
55
55
+
font-weight: bold;
56
56
+
}
57
57
+
29
58
/* Container layout */
30
59
.admin-container {
31
60
display: grid;
···
451
480
transition: background-color 0.2s, opacity 0.2s;
452
481
}
453
482
483
483
+
button:disabled {
484
484
+
opacity: 0.5;
485
485
+
cursor: not-allowed;
486
486
+
}
487
487
+
454
488
.add-new-button {
455
489
background-color: var(--button-bg);
456
490
color: var(--button-text);
···
570
604
animation: spin 1s linear infinite;
571
605
margin-bottom: 15px;
572
606
}
573
573
-
574
574
-
/* Tab navigation */
575
575
-
.nav-tabs {
576
576
-
display: flex;
577
577
-
margin: 0 auto;
578
578
-
}
579
607
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 */
608
608
+
/* Reorder container - Updated */
595
609
.reorder-container {
596
610
max-width: 1200px;
597
611
margin: 0 auto;
598
612
padding: 20px;
613
613
+
background: var(--navbar-bg);
614
614
+
border: 5px solid var(--card-border);
615
615
+
border-radius: 12px;
616
616
+
color: var(--text);
599
617
}
600
618
601
619
.reorder-header {
···
618
636
619
637
.mode-button {
620
638
padding: 8px 16px;
621
621
-
background: #f5f5f5;
622
622
-
border: 1px solid #ddd;
639
639
+
background-color: var(--navbar-bg);
640
640
+
border: 1px solid var(--card-border);
623
641
border-radius: 4px;
624
642
cursor: pointer;
643
643
+
color: var(--text);
644
644
+
transition: all 0.3s ease;
645
645
+
}
646
646
+
647
647
+
.mode-button:hover {
648
648
+
background-color: var(--background);
649
649
+
border-color: var(--button-bg);
625
650
}
626
651
627
652
.mode-button.active {
628
628
-
background: #4a90e2;
629
629
-
color: white;
630
630
-
border-color: #4a90e2;
653
653
+
background-color: var(--button-bg);
654
654
+
color: var(--button-text);
655
655
+
border-color: var(--button-bg);
631
656
}
632
657
633
658
.category-select {
634
659
padding: 8px;
635
660
min-width: 200px;
636
636
-
border: 1px solid #ddd;
661
661
+
border: 1px solid var(--card-border);
662
662
+
border-radius: 4px;
663
663
+
background-color: var(--navbar-bg);
664
664
+
color: var(--text);
665
665
+
transition: all 0.3s ease;
666
666
+
}
667
667
+
668
668
+
.category-select:hover,
669
669
+
.category-select:focus {
670
670
+
border-color: var(--button-bg);
671
671
+
background-color: var(--background);
672
672
+
}
673
673
+
674
674
+
/* Position input - New */
675
675
+
.position-control {
676
676
+
display: flex;
677
677
+
align-items: center;
678
678
+
gap: 8px;
679
679
+
margin-left: 10px;
680
680
+
}
681
681
+
682
682
+
.position-input {
683
683
+
width: 60px;
684
684
+
padding: 3px 5px;
685
685
+
border: 1px solid var(--card-border);
637
686
border-radius: 4px;
687
687
+
background-color: var(--navbar-bg);
688
688
+
color: var(--text);
689
689
+
font-size: 13px;
690
690
+
transition: all 0.3s ease;
691
691
+
}
692
692
+
693
693
+
.position-input:hover,
694
694
+
.position-input:focus {
695
695
+
border-color: var(--button-bg);
696
696
+
background-color: var(--background);
638
697
}
639
698
640
699
/* Sortable resources list */
···
649
708
justify-content: space-between;
650
709
align-items: center;
651
710
padding: 15px;
652
652
-
background: white;
653
653
-
border: 1px solid #eee;
711
711
+
background-color: var(--background);
712
712
+
border: 1px solid var(--card-border);
654
713
border-radius: 4px;
655
714
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
715
715
+
transition: all 0.3s ease;
656
716
}
657
717
658
718
.resource-info {
···
662
722
.resource-name {
663
723
font-weight: bold;
664
724
margin-bottom: 5px;
725
725
+
color: var(--text);
665
726
}
666
727
667
728
.resource-meta {
668
729
display: flex;
669
730
gap: 10px;
670
731
font-size: 0.9rem;
671
671
-
}
672
672
-
673
673
-
.position-indicator {
674
674
-
color: #666;
732
732
+
align-items: center;
733
733
+
flex-wrap: wrap;
675
734
}
676
735
677
736
.reorder-actions {
···
682
741
.move-button {
683
742
width: 32px;
684
743
height: 32px;
685
685
-
background: #f5f5f5;
686
686
-
border: 1px solid #ddd;
744
744
+
background-color: var(--navbar-bg);
745
745
+
border: 1px solid var(--card-border);
687
746
border-radius: 4px;
688
747
cursor: pointer;
689
748
display: flex;
690
749
align-items: center;
691
750
justify-content: center;
692
751
font-size: 18px;
752
752
+
color: var(--text);
753
753
+
transition: all 0.3s ease;
693
754
}
694
755
695
695
-
.move-button:hover {
696
696
-
background: #e5e5e5;
756
756
+
.move-button:hover:not(:disabled) {
757
757
+
background-color: var(--background);
758
758
+
border-color: var(--button-bg);
697
759
}
698
760
699
761
.no-resources-message,
700
762
.select-category-message {
701
763
padding: 20px;
702
764
text-align: center;
703
703
-
color: #666;
765
765
+
color: var(--text);
766
766
+
background-color: var(--navbar-bg);
767
767
+
border: 1px solid var(--card-border);
768
768
+
border-radius: 4px;
769
769
+
margin-top: 20px;
770
770
+
}
771
771
+
772
772
+
.reorder-list h3 {
773
773
+
color: var(--text);
774
774
+
margin-top: 20px;
775
775
+
margin-bottom: 15px;
776
776
+
font-size: 18px;
704
777
}
705
778
706
779
@keyframes spin {
···
745
818
margin-top: 10px;
746
819
font-weight: 700;
747
820
}
748
748
-
821
821
+
749
822
.admin-container form {
750
823
border: 0px;
751
824
padding: 0px;
···
764
837
margin-bottom: 15px;
765
838
font-size: 14px;
766
839
}
767
767
-
840
840
+
768
841
.completeness-badge {
769
842
background-color: #f0f0f0;
770
843
color: #333;
···
773
846
border-radius: 3px;
774
847
margin-left: 5px;
775
848
}
776
776
-
849
849
+
777
850
.filter-group {
778
851
margin-bottom: 10px;
779
852
}
···
793
866
font-size: 16px;
794
867
color: #666;
795
868
}
796
796
-
869
869
+
797
870
.featured-filter {
798
871
width: 100%;
799
872
padding: 8px;
800
800
-
border: 1px solid #ddd;
873
873
+
border: 1px solid var(--card-border);
801
874
border-radius: 4px;
802
875
margin-bottom: 5px;
876
876
+
background-color: var(--navbar-bg);
877
877
+
color: var(--text);
878
878
+
transition: all 0.3s ease;
879
879
+
}
880
880
+
881
881
+
.featured-filter:hover,
882
882
+
.featured-filter:focus {
883
883
+
border-color: var(--button-bg);
884
884
+
background-color: var(--background);
803
885
}
804
886
805
887
.reset-filters-button:hover {
···
809
891
.resources-summary {
810
892
margin: 5px 0;
811
893
font-size: 0.9rem;
812
812
-
color: #666;
894
894
+
color: var(--text);
895
895
+
opacity: 0.8;
813
896
padding: 0 10px;
814
897
}
815
898
···
817
900
.tag-filter {
818
901
width: 100%;
819
902
padding: 8px;
820
820
-
border: 1px solid #ddd;
903
903
+
border: 1px solid var(--card-border);
821
904
border-radius: 4px;
822
905
margin-bottom: 5px;
906
906
+
background-color: var(--navbar-bg);
907
907
+
color: var(--text);
908
908
+
transition: all 0.3s ease;
909
909
+
}
910
910
+
911
911
+
.category-filter:hover,
912
912
+
.category-filter:focus,
913
913
+
.tag-filter:hover,
914
914
+
.tag-filter:focus {
915
915
+
border-color: var(--button-bg);
916
916
+
background-color: var(--background);
823
917
}
824
918
825
919
/* Accessibility */
···
840
934
.admin-container {
841
935
grid-template-columns: 1fr;
842
936
}
843
843
-
937
937
+
844
938
.floating-actions {
845
845
-
align-items: center;
846
846
-
display: flex;
847
847
-
gap: 15px;
848
848
-
justify-content: space-between;
849
849
-
flex-direction: column;
850
850
-
}
851
851
-
852
852
-
.admin-container form {
853
853
-
border: 0px;
854
854
-
padding: 0px;
855
855
-
align-content: center;
856
856
-
display: block;
857
857
-
}
858
858
-
859
859
-
.form-group textarea {
860
860
-
min-height: 100px;
861
861
-
resize: vertical;
862
862
-
font-family: articulat-cf;
863
863
-
width: unset;
864
864
-
}
865
865
-
866
866
-
.admin-container {
867
867
-
display: grid;
868
868
-
gap: 0px;
869
869
-
}
870
870
-
871
871
-
.resource-editor {
872
872
-
height: 235%;
873
873
-
}
874
874
-
875
875
-
.resources-sidebar {
876
876
-
max-height: 80%;
877
877
-
overflow-y: scroll;
878
878
-
}
879
879
-
880
880
-
.admin-panel {
881
881
-
height: 2121.5px;
882
882
-
}
939
939
+
align-items: center;
940
940
+
display: flex;
941
941
+
gap: 15px;
942
942
+
justify-content: space-between;
943
943
+
flex-direction: column;
944
944
+
}
945
945
+
946
946
+
.admin-container form {
947
947
+
border: 0px;
948
948
+
padding: 0px;
949
949
+
align-content: center;
950
950
+
display: block;
951
951
+
}
952
952
+
953
953
+
.form-group textarea {
954
954
+
min-height: 100px;
955
955
+
resize: vertical;
956
956
+
font-family: articulat-cf;
957
957
+
width: unset;
958
958
+
}
959
959
+
960
960
+
.admin-container {
961
961
+
display: grid;
962
962
+
gap: 0px;
963
963
+
}
964
964
+
965
965
+
.resource-editor {
966
966
+
height: 235%;
967
967
+
}
968
968
+
969
969
+
.resources-sidebar {
970
970
+
max-height: 80%;
971
971
+
overflow-y: scroll;
972
972
+
}
973
973
+
974
974
+
.admin-panel {
975
975
+
height: 2121.5px;
976
976
+
}
883
977
884
978
.form-row {
885
979
grid-template-columns: 1fr;
980
980
+
}
981
981
+
982
982
+
.reorder-header {
983
983
+
flex-direction: column;
984
984
+
align-items: start;
985
985
+
gap: 15px;
986
986
+
}
987
987
+
988
988
+
.reorder-controls {
989
989
+
flex-direction: column;
990
990
+
align-items: start;
991
991
+
width: 100%;
992
992
+
}
993
993
+
994
994
+
.reorder-mode-selector {
995
995
+
width: 100%;
996
996
+
}
997
997
+
998
998
+
.mode-button {
999
999
+
flex: 1;
1000
1000
+
text-align: center;
1001
1001
+
}
1002
1002
+
1003
1003
+
.category-selector {
1004
1004
+
width: 100%;
1005
1005
+
}
1006
1006
+
1007
1007
+
.category-select {
1008
1008
+
width: 100%;
1009
1009
+
}
1010
1010
+
1011
1011
+
.sortable-resource-item {
1012
1012
+
flex-direction: column;
1013
1013
+
align-items: start;
1014
1014
+
gap: 10px;
1015
1015
+
}
1016
1016
+
1017
1017
+
.resource-meta {
1018
1018
+
flex-direction: column;
1019
1019
+
align-items: start;
1020
1020
+
gap: 5px;
1021
1021
+
}
1022
1022
+
1023
1023
+
.reorder-actions {
1024
1024
+
align-self: flex-end;
886
1025
}
887
1026
}
···
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);
26
26
+
const [updatingPositions, setUpdatingPositions] = useState(false);
26
27
27
28
// Login state
28
29
const [email, setEmail] = useState('');
···
261
262
});
262
263
};
263
264
265
265
+
// Update a single resource position without full page refresh
266
266
+
const updateResourcePosition = async (resourceId, newPosition) => {
267
267
+
if (updatingPositions) return;
268
268
+
setUpdatingPositions(true);
269
269
+
270
270
+
try {
271
271
+
// Update the resource position in the database
272
272
+
const { error } = await supabase
273
273
+
.from('resources')
274
274
+
.update({ position: newPosition })
275
275
+
.eq('id', resourceId);
276
276
+
277
277
+
if (error) throw error;
278
278
+
279
279
+
// Update local state without fetching all data again
280
280
+
setResources(prevResources => {
281
281
+
return prevResources.map(resource => {
282
282
+
if (resource.id === resourceId) {
283
283
+
return { ...resource, position: newPosition };
284
284
+
}
285
285
+
return resource;
286
286
+
});
287
287
+
});
288
288
+
289
289
+
showAlert(`Position updated successfully!`);
290
290
+
} catch (error) {
291
291
+
console.error('Error updating position:', error);
292
292
+
showAlert(`Error: ${error.message}`, 'error');
293
293
+
} finally {
294
294
+
setUpdatingPositions(false);
295
295
+
}
296
296
+
};
297
297
+
298
298
+
// Handle position input change and update
299
299
+
const handlePositionChange = (resourceId, e) => {
300
300
+
const newPosition = parseInt(e.target.value);
301
301
+
if (isNaN(newPosition) || newPosition < 0) return;
302
302
+
303
303
+
updateResourcePosition(resourceId, newPosition);
304
304
+
};
305
305
+
264
306
// Reorder resources (move up or down in list)
265
307
const handleReorderResource = async (resourceId, direction) => {
266
308
const resourceIndex = resources.findIndex(r => r.id === resourceId);
267
267
-
268
309
if (resourceIndex === -1) return;
269
310
270
311
let filteredResources = resources;
···
279
320
}
280
321
281
322
const resourceToMoveIndex = filteredResources.findIndex(r => r.id === resourceId);
282
282
-
283
323
if (resourceToMoveIndex === -1) return;
284
324
285
325
const adjacentIndex = direction === 'up'
···
291
331
const resourceToMove = filteredResources[resourceToMoveIndex];
292
332
const adjacentResource = filteredResources[adjacentIndex];
293
333
294
294
-
setIsLoading(true);
334
334
+
if (updatingPositions) return;
335
335
+
setUpdatingPositions(true);
295
336
296
337
try {
297
297
-
// Swap positions
298
298
-
const tempPosition = resourceToMove.position;
299
299
-
338
338
+
// Swap positions in database
300
339
await supabase
301
340
.from('resources')
302
341
.update({ position: adjacentResource.position })
···
304
343
305
344
await supabase
306
345
.from('resources')
307
307
-
.update({ position: tempPosition })
346
346
+
.update({ position: resourceToMove.position })
308
347
.eq('id', adjacentResource.id);
309
348
310
310
-
// Refresh data
311
311
-
await fetchAllData();
349
349
+
// Update local state without fetching all data again
350
350
+
setResources(prevResources => {
351
351
+
return prevResources.map(resource => {
352
352
+
if (resource.id === resourceToMove.id) {
353
353
+
return { ...resource, position: adjacentResource.position };
354
354
+
}
355
355
+
if (resource.id === adjacentResource.id) {
356
356
+
return { ...resource, position: resourceToMove.position };
357
357
+
}
358
358
+
return resource;
359
359
+
});
360
360
+
});
312
361
313
362
showAlert(`Resources reordered successfully!`);
314
363
} catch (error) {
315
364
console.error('Error reordering resources:', error);
316
365
showAlert(`Error: ${error.message}`, 'error');
317
366
} finally {
318
318
-
setIsLoading(false);
367
367
+
setUpdatingPositions(false);
319
368
}
320
369
};
321
370
···
1123
1172
<span className={`status-badge status-${resource.status}`}>
1124
1173
{resource.status}
1125
1174
</span>
1126
1126
-
<span className="position-indicator">
1127
1127
-
Position: {resource.position}
1128
1128
-
</span>
1175
1175
+
<div className="position-control">
1176
1176
+
<span>Position:</span>
1177
1177
+
<input
1178
1178
+
type="number"
1179
1179
+
value={resource.position}
1180
1180
+
onChange={(e) => handlePositionChange(resource.id, e)}
1181
1181
+
className="position-input"
1182
1182
+
min="1"
1183
1183
+
/>
1184
1184
+
</div>
1129
1185
</div>
1130
1186
</div>
1131
1187
<div className="reorder-actions">
···
1133
1189
onClick={() => handleReorderResource(resource.id, 'up')}
1134
1190
className="move-button move-up"
1135
1191
title="Move up"
1192
1192
+
disabled={updatingPositions}
1136
1193
>
1137
1194
↑
1138
1195
</button>
···
1140
1197
onClick={() => handleReorderResource(resource.id, 'down')}
1141
1198
className="move-button move-down"
1142
1199
title="Move down"
1200
1200
+
disabled={updatingPositions}
1143
1201
>
1144
1202
↓
1145
1203
</button>
···
1178
1236
{resource.status}
1179
1237
</span>
1180
1238
{resource.featured && <span className="featured-badge">Featured</span>}
1181
1181
-
<span className="position-indicator">
1182
1182
-
Position: {resource.position}
1183
1183
-
</span>
1239
1239
+
<div className="position-control">
1240
1240
+
<span>Position:</span>
1241
1241
+
<input
1242
1242
+
type="number"
1243
1243
+
value={resource.position}
1244
1244
+
onChange={(e) => handlePositionChange(resource.id, e)}
1245
1245
+
className="position-input"
1246
1246
+
min="1"
1247
1247
+
/>
1248
1248
+
</div>
1184
1249
</div>
1185
1250
</div>
1186
1251
<div className="reorder-actions">
···
1188
1253
onClick={() => handleReorderResource(resource.id, 'up')}
1189
1254
className="move-button move-up"
1190
1255
title="Move up"
1256
1256
+
disabled={updatingPositions}
1191
1257
>
1192
1258
↑
1193
1259
</button>
···
1195
1261
onClick={() => handleReorderResource(resource.id, 'down')}
1196
1262
className="move-button move-down"
1197
1263
title="Move down"
1264
1264
+
disabled={updatingPositions}
1198
1265
>
1199
1266
↓
1200
1267
</button>