SCGR commited on
Commit
97ee203
·
1 Parent(s): 3d8b8a4
frontend/src/pages/MapDetailsPage/MapDetailPage.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { PageContainer, Container, Button, Spinner, SegmentInput, TextInput, SelectInput, MultiSelectInput } from '@ifrc-go/ui';
2
  import { useParams, useNavigate } from 'react-router-dom';
3
  import { useState, useEffect, useMemo, useCallback } from 'react';
4
  import { ChevronLeftLineIcon, ChevronRightLineIcon, DeleteBinLineIcon } from '@ifrc-go/icons';
@@ -56,37 +56,7 @@ export default function MapDetailPage() {
56
  const navigate = useNavigate();
57
  const { isAuthenticated } = useAdmin();
58
 
59
- // Debug: Log the current URL and mapId for production debugging
60
- console.log('MapDetailsPage: Current URL:', window.location.href);
61
- console.log('MapDetailsPage: Hash:', window.location.hash);
62
- console.log('MapDetailsPage: mapId from useParams:', mapId);
63
- console.log('MapDetailsPage: mapId type:', typeof mapId);
64
- console.log('MapDetailsPage: mapId length:', mapId?.length);
65
- console.log('MapDetailsPage: mapId value:', JSON.stringify(mapId));
66
-
67
- // Early validation - if mapId is invalid, show error immediately
68
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
69
- if (!mapId || mapId === 'undefined' || mapId === 'null' || mapId.trim() === '' || !uuidRegex.test(mapId)) {
70
- return (
71
- <PageContainer>
72
- <div className="flex flex-col items-center gap-4 text-center py-12">
73
- <div className="text-4xl">⚠️</div>
74
- <div className="text-xl font-semibold">Invalid Map ID</div>
75
- <div>The map ID provided is not valid.</div>
76
- <div className="text-sm text-gray-500 mt-2">
77
- Debug Info: mapId = "{mapId}" (type: {typeof mapId})
78
- </div>
79
- <Button
80
- name="back-to-explore"
81
- variant="secondary"
82
- onClick={() => navigate('/explore')}
83
- >
84
- Return to Explore
85
- </Button>
86
- </div>
87
- </PageContainer>
88
- );
89
- }
90
  const [view, setView] = useState<'explore' | 'mapDetails'>('mapDetails');
91
  const [map, setMap] = useState<MapOut | null>(null);
92
  const [loading, setLoading] = useState(true);
@@ -142,6 +112,92 @@ export default function MapDetailPage() {
142
  { key: 'mapDetails' as const, label: 'Carousel' }
143
  ];
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  const fetchMapData = useCallback(async (id: string) => {
146
  console.log('fetchMapData called with id:', id);
147
  console.log('fetchMapData id type:', typeof id);
@@ -217,31 +273,6 @@ export default function MapDetailPage() {
217
  }
218
  }, [checkNavigationAvailability, fetchAllImages]);
219
 
220
- const fetchAllImages = useCallback(async (imageIds: string[]) => {
221
- console.log('fetchAllImages called with imageIds:', imageIds);
222
- setIsLoadingImages(true);
223
-
224
- try {
225
- const imagePromises = imageIds.map(async (imageId) => {
226
- const response = await fetch(`/api/images/${imageId}`);
227
- if (!response.ok) {
228
- throw new Error(`Failed to fetch image ${imageId}`);
229
- }
230
- return response.json();
231
- });
232
-
233
- const images = await Promise.all(imagePromises);
234
- setAllImages(images);
235
- setCurrentImageIndex(0);
236
- console.log('fetchAllImages: Loaded', images.length, 'images');
237
- } catch (err: unknown) {
238
- console.error('fetchAllImages error:', err);
239
- setError(err instanceof Error ? err.message : 'Failed to load all images');
240
- } finally {
241
- setIsLoadingImages(false);
242
- }
243
- }, []);
244
-
245
  // Carousel navigation functions
246
  const goToPrevious = useCallback(() => {
247
  if (allImages.length > 1) {
@@ -431,65 +462,6 @@ export default function MapDetailPage() {
431
  }
432
  }, [map, search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, showReferenceExamples, mapId, navigate, loading, isDeleting]);
433
 
434
- const checkNavigationAvailability = useCallback(async (currentId: string) => {
435
- // Validate the ID before making the request
436
- if (!currentId || currentId === 'undefined' || currentId === 'null' || currentId.trim() === '') {
437
- return;
438
- }
439
-
440
- try {
441
-
442
- const params = new URLSearchParams();
443
- if (search) params.append('search', search);
444
- if (srcFilter) params.append('source', srcFilter);
445
- if (catFilter) params.append('event_type', catFilter);
446
- if (regionFilter) params.append('region', regionFilter);
447
- if (countryFilter) params.append('country', countryFilter);
448
- if (imageTypeFilter) params.append('image_type', imageTypeFilter);
449
- if (uploadTypeFilter) params.append('upload_type', uploadTypeFilter);
450
- if (showReferenceExamples) params.append('starred_only', 'true');
451
-
452
- const response = await fetch(`/api/images/grouped?${params.toString()}`);
453
- if (response.ok) {
454
- const filteredImages = await response.json();
455
-
456
- console.log('Server response for upload_type=multiple:', {
457
- url: `/api/images/grouped?${params.toString()}`,
458
- count: filteredImages.length,
459
- images: filteredImages.map((img: any) => ({
460
- image_id: img.image_id,
461
- image_count: img.image_count,
462
- all_image_ids: img.all_image_ids,
463
- all_image_ids_length: img.all_image_ids?.length
464
- }))
465
- });
466
-
467
- const currentIndex = filteredImages.findIndex((img: { image_id: string }) => img.image_id === currentId);
468
-
469
- // Debug logging
470
- console.log('Navigation availability check (server-side):', {
471
- filteredImagesCount: filteredImages.length,
472
- currentIndex,
473
- currentId,
474
- uploadTypeFilter,
475
- hasPrevious: filteredImages.length > 1 && currentIndex > 0,
476
- hasNext: filteredImages.length > 1 && currentIndex < filteredImages.length - 1,
477
- filteredImages: filteredImages.map((img: any) => ({
478
- image_id: img.image_id,
479
- image_count: img.image_count,
480
- all_image_ids: img.all_image_ids,
481
- image_type: img.image_type
482
- }))
483
- });
484
-
485
- setHasPrevious(filteredImages.length > 1 && currentIndex > 0);
486
- setHasNext(filteredImages.length > 1 && currentIndex < filteredImages.length - 1);
487
- }
488
- } catch (error) {
489
- console.error('Failed to check navigation availability:', error);
490
- }
491
- }, [search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, uploadTypeFilter, showReferenceExamples]);
492
-
493
  const navigateToItem = async (direction: 'previous' | 'next') => {
494
  if (isNavigating) return;
495
 
@@ -735,6 +707,42 @@ export default function MapDetailPage() {
735
  setIsDeleting(false);
736
  }
737
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
 
739
  const filteredMap = useMemo(() => {
740
  if (!map) return null;
@@ -776,42 +784,6 @@ export default function MapDetailPage() {
776
  return matches ? map : null;
777
  }, [map, search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, uploadTypeFilter, showReferenceExamples, navigateToMatchingImage]);
778
 
779
- const navigateToMatchingImage = useCallback(async () => {
780
- setLoading(true);
781
- try {
782
- // Use server-side filtering like ExplorePage
783
- const params = new URLSearchParams();
784
- if (search) params.append('search', search);
785
- if (srcFilter) params.append('source', srcFilter);
786
- if (catFilter) params.append('event_type', catFilter);
787
- if (regionFilter) params.append('region', regionFilter);
788
- if (countryFilter) params.append('country', countryFilter);
789
- if (imageTypeFilter) params.append('image_type', imageTypeFilter);
790
- if (uploadTypeFilter) params.append('upload_type', uploadTypeFilter);
791
- if (showReferenceExamples) params.append('starred_only', 'true');
792
-
793
- const response = await fetch(`/api/images/grouped?${params.toString()}`);
794
- if (response.ok) {
795
- const filteredImages = await response.json();
796
-
797
- if (filteredImages.length > 0) {
798
- const firstMatchingImage = filteredImages[0];
799
- if (firstMatchingImage && firstMatchingImage.image_id) {
800
- navigate(`/map/${firstMatchingImage.image_id}`);
801
- }
802
- } else {
803
- // No matching images, go back to explore
804
- navigate('/explore');
805
- }
806
- }
807
- } catch (error) {
808
- console.error('Failed to navigate to matching image:', error);
809
- navigate('/explore');
810
- } finally {
811
- setLoading(false);
812
- }
813
- }, [search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, uploadTypeFilter, showReferenceExamples, navigate]);
814
-
815
  const handleContribute = () => {
816
  if (!map) return;
817
 
@@ -1122,6 +1094,30 @@ export default function MapDetailPage() {
1122
  }
1123
  };
1124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1125
  if (loading) {
1126
  return (
1127
  <PageContainer>
 
1
+ import { PageContainer, Container, Button, Spinner, SegmentInput } from '@ifrc-go/ui';
2
  import { useParams, useNavigate } from 'react-router-dom';
3
  import { useState, useEffect, useMemo, useCallback } from 'react';
4
  import { ChevronLeftLineIcon, ChevronRightLineIcon, DeleteBinLineIcon } from '@ifrc-go/icons';
 
56
  const navigate = useNavigate();
57
  const { isAuthenticated } = useAdmin();
58
 
59
+ // All React Hooks must be called before any early returns
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  const [view, setView] = useState<'explore' | 'mapDetails'>('mapDetails');
61
  const [map, setMap] = useState<MapOut | null>(null);
62
  const [loading, setLoading] = useState(true);
 
112
  { key: 'mapDetails' as const, label: 'Carousel' }
113
  ];
114
 
115
+ // Early validation will be moved after all hooks
116
+
117
+ const checkNavigationAvailability = useCallback(async (currentId: string) => {
118
+ // Validate the ID before making the request
119
+ if (!currentId || currentId === 'undefined' || currentId === 'null' || currentId.trim() === '') {
120
+ return;
121
+ }
122
+
123
+ try {
124
+
125
+ const params = new URLSearchParams();
126
+ if (search) params.append('search', search);
127
+ if (srcFilter) params.append('source', srcFilter);
128
+ if (catFilter) params.append('event_type', catFilter);
129
+ if (regionFilter) params.append('region', regionFilter);
130
+ if (countryFilter) params.append('country', countryFilter);
131
+ if (imageTypeFilter) params.append('image_type', imageTypeFilter);
132
+ if (uploadTypeFilter) params.append('upload_type', uploadTypeFilter);
133
+ if (showReferenceExamples) params.append('starred_only', 'true');
134
+
135
+ const response = await fetch(`/api/images/grouped?${params.toString()}`);
136
+ if (response.ok) {
137
+ const filteredImages = await response.json();
138
+
139
+ console.log('Server response for upload_type=multiple:', {
140
+ url: `/api/images/grouped?${params.toString()}`,
141
+ count: filteredImages.length,
142
+ images: filteredImages.map((img: any) => ({
143
+ image_id: img.image_id,
144
+ image_count: img.image_count,
145
+ all_image_ids: img.all_image_ids,
146
+ all_image_ids_length: img.all_image_ids?.length
147
+ }))
148
+ });
149
+
150
+ const currentIndex = filteredImages.findIndex((img: { image_id: string }) => img.image_id === currentId);
151
+
152
+ // Debug logging
153
+ console.log('Navigation availability check (server-side):', {
154
+ filteredImagesCount: filteredImages.length,
155
+ currentIndex,
156
+ currentId,
157
+ uploadTypeFilter,
158
+ hasPrevious: filteredImages.length > 1 && currentIndex > 0,
159
+ hasNext: filteredImages.length > 1 && currentIndex < filteredImages.length - 1,
160
+ filteredImages: filteredImages.map((img: any) => ({
161
+ image_id: img.image_id,
162
+ image_count: img.image_count,
163
+ all_image_ids: img.all_image_ids,
164
+ image_type: img.image_type
165
+ }))
166
+ });
167
+
168
+ setHasPrevious(filteredImages.length > 1 && currentIndex > 0);
169
+ setHasNext(filteredImages.length > 1 && currentIndex < filteredImages.length - 1);
170
+ }
171
+ } catch (error) {
172
+ console.error('Failed to check navigation availability:', error);
173
+ }
174
+ }, [search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, uploadTypeFilter, showReferenceExamples]);
175
+
176
+ const fetchAllImages = useCallback(async (imageIds: string[]) => {
177
+ console.log('fetchAllImages called with imageIds:', imageIds);
178
+ setIsLoadingImages(true);
179
+
180
+ try {
181
+ const imagePromises = imageIds.map(async (imageId) => {
182
+ const response = await fetch(`/api/images/${imageId}`);
183
+ if (!response.ok) {
184
+ throw new Error(`Failed to fetch image ${imageId}`);
185
+ }
186
+ return response.json();
187
+ });
188
+
189
+ const images = await Promise.all(imagePromises);
190
+ setAllImages(images);
191
+ setCurrentImageIndex(0);
192
+ console.log('fetchAllImages: Loaded', images.length, 'images');
193
+ } catch (err: unknown) {
194
+ console.error('fetchAllImages error:', err);
195
+ setError(err instanceof Error ? err.message : 'Failed to load all images');
196
+ } finally {
197
+ setIsLoadingImages(false);
198
+ }
199
+ }, []);
200
+
201
  const fetchMapData = useCallback(async (id: string) => {
202
  console.log('fetchMapData called with id:', id);
203
  console.log('fetchMapData id type:', typeof id);
 
273
  }
274
  }, [checkNavigationAvailability, fetchAllImages]);
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  // Carousel navigation functions
277
  const goToPrevious = useCallback(() => {
278
  if (allImages.length > 1) {
 
462
  }
463
  }, [map, search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, showReferenceExamples, mapId, navigate, loading, isDeleting]);
464
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  const navigateToItem = async (direction: 'previous' | 'next') => {
466
  if (isNavigating) return;
467
 
 
707
  setIsDeleting(false);
708
  }
709
  };
710
+
711
+ const navigateToMatchingImage = useCallback(async () => {
712
+ setLoading(true);
713
+ try {
714
+ // Use server-side filtering like ExplorePage
715
+ const params = new URLSearchParams();
716
+ if (search) params.append('search', search);
717
+ if (srcFilter) params.append('source', srcFilter);
718
+ if (catFilter) params.append('event_type', catFilter);
719
+ if (regionFilter) params.append('region', regionFilter);
720
+ if (countryFilter) params.append('country', countryFilter);
721
+ if (imageTypeFilter) params.append('image_type', imageTypeFilter);
722
+ if (uploadTypeFilter) params.append('upload_type', uploadTypeFilter);
723
+ if (showReferenceExamples) params.append('starred_only', 'true');
724
+
725
+ const response = await fetch(`/api/images/grouped?${params.toString()}`);
726
+ if (response.ok) {
727
+ const filteredImages = await response.json();
728
+
729
+ if (filteredImages.length > 0) {
730
+ const firstMatchingImage = filteredImages[0];
731
+ if (firstMatchingImage && firstMatchingImage.image_id) {
732
+ navigate(`/map/${firstMatchingImage.image_id}`);
733
+ }
734
+ } else {
735
+ // No matching images, go back to explore
736
+ navigate('/explore');
737
+ }
738
+ }
739
+ } catch (error) {
740
+ console.error('Failed to navigate to matching image:', error);
741
+ navigate('/explore');
742
+ } finally {
743
+ setLoading(false);
744
+ }
745
+ }, [search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, uploadTypeFilter, showReferenceExamples, navigate]);
746
 
747
  const filteredMap = useMemo(() => {
748
  if (!map) return null;
 
784
  return matches ? map : null;
785
  }, [map, search, srcFilter, catFilter, regionFilter, countryFilter, imageTypeFilter, uploadTypeFilter, showReferenceExamples, navigateToMatchingImage]);
786
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
  const handleContribute = () => {
788
  if (!map) return;
789
 
 
1094
  }
1095
  };
1096
 
1097
+ // Early validation - if mapId is invalid, show error immediately
1098
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1099
+ if (!mapId || mapId === 'undefined' || mapId === 'null' || mapId.trim() === '' || !uuidRegex.test(mapId)) {
1100
+ return (
1101
+ <PageContainer>
1102
+ <div className="flex flex-col items-center gap-4 text-center py-12">
1103
+ <div className="text-4xl">⚠️</div>
1104
+ <div className="text-xl font-semibold">Invalid Map ID</div>
1105
+ <div>The map ID provided is not valid.</div>
1106
+ <div className="text-sm text-gray-500 mt-2">
1107
+ Debug Info: mapId = "{mapId}" (type: {typeof mapId})
1108
+ </div>
1109
+ <Button
1110
+ name="back-to-explore"
1111
+ variant="secondary"
1112
+ onClick={() => navigate('/explore')}
1113
+ >
1114
+ Return to Explore
1115
+ </Button>
1116
+ </div>
1117
+ </PageContainer>
1118
+ );
1119
+ }
1120
+
1121
  if (loading) {
1122
  return (
1123
  <PageContainer>