From dd610ffa517afdaf98c9059d8a632c1f762d7bcb Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Mon, 2 Mar 2026 17:07:11 +0530 Subject: [PATCH 1/2] Search page --- app/[locale]/(user)/components/Content.tsx | 2 +- .../datasets/components/FIlter/Filter.tsx | 16 +- .../(user)/datasets/dataset.module.scss | 6 +- .../components/UnifiedListingComponent.tsx | 1101 ++++++++++------- .../dashboard/components/main-nav.tsx | 85 +- styles/tokens/variables.js | 2 +- 6 files changed, 691 insertions(+), 521 deletions(-) diff --git a/app/[locale]/(user)/components/Content.tsx b/app/[locale]/(user)/components/Content.tsx index 38c83ec3..b5572224 100644 --- a/app/[locale]/(user)/components/Content.tsx +++ b/app/[locale]/(user)/components/Content.tsx @@ -36,7 +36,7 @@ export const Content = () => { const handleSearch = (value: string) => { if (value) { - router.push(`/datasets?query=${encodeURIComponent(value)}`); + router.push(`/search?query=${encodeURIComponent(value)}`); } }; const Metrics = [ diff --git a/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx b/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx index 28ad1021..ba7a5d8d 100644 --- a/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx +++ b/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx @@ -37,6 +37,8 @@ const Filter: React.FC = ({ }); }; + console.log('options', options); + return (
@@ -89,7 +91,7 @@ const Filter: React.FC = ({ value={category} className=" border-surfaceDefault" > - + {toTitleCase(category)} = ({ name={category} options={data.map((option) => ({ ...option, - disabled: lockedFilters[category]?.includes(option.value) || false, + disabled: + lockedFilters[category]?.includes(option.value) || + false, }))} title={undefined} value={selectedOptions[category] || []} @@ -111,10 +115,12 @@ const Filter: React.FC = ({ // Prevent unselecting locked filters const locked = lockedFilters[category] || []; const newValues = values as string[]; - + // Ensure all locked values remain selected - const finalValues = Array.from(new Set([...locked, ...newValues])); - + const finalValues = Array.from( + new Set([...locked, ...newValues]) + ); + setSelectedOptions(category, finalValues); }} /> diff --git a/app/[locale]/(user)/datasets/dataset.module.scss b/app/[locale]/(user)/datasets/dataset.module.scss index 81b64ec8..0b723f2b 100644 --- a/app/[locale]/(user)/datasets/dataset.module.scss +++ b/app/[locale]/(user)/datasets/dataset.module.scss @@ -1,7 +1,7 @@ .Search { input { width: 80%; - height: 36px; + height: 46px; } /* In your global CSS/SCSS file */ div[class*='Input-module_Backdrop'] { @@ -18,3 +18,7 @@ max-width: 120px; } } + +// .Select { +// height: 100%; +// } diff --git a/app/[locale]/(user)/search/components/UnifiedListingComponent.tsx b/app/[locale]/(user)/search/components/UnifiedListingComponent.tsx index 4724467b..98222aaf 100644 --- a/app/[locale]/(user)/search/components/UnifiedListingComponent.tsx +++ b/app/[locale]/(user)/search/components/UnifiedListingComponent.tsx @@ -1,8 +1,9 @@ 'use client'; -import GraphqlPagination from '@/app/[locale]/dashboard/components/GraphqlPagination/graphqlPagination'; +import React, { useEffect, useReducer, useRef, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import GraphqlPagination from '@/app/[locale]/dashboard/components/GraphqlPagination/graphqlPagination'; import { Button, ButtonGroup, @@ -14,12 +15,11 @@ import { Text, Tray, } from 'opub-ui'; -import React, { useEffect, useReducer, useRef, useState } from 'react'; +import { cn, formatDate } from '@/lib/utils'; import BreadCrumbs from '@/components/BreadCrumbs'; import { Icons } from '@/components/icons'; import { Loading } from '@/components/loading'; -import { cn, formatDate } from '@/lib/utils'; import Filter from '../../datasets/components/FIlter/Filter'; import Styles from '../../datasets/dataset.module.scss'; @@ -108,6 +108,8 @@ interface QueryParams { types?: string; // New: comma-separated list of types to search } +const ALL_LISTING_TYPES = 'dataset,usecase,aimodel,publisher'; + type Action = | { type: 'SET_PAGE_SIZE'; payload: number } | { type: 'SET_CURRENT_PAGE'; payload: number } @@ -127,7 +129,7 @@ const initialState: QueryParams = { query: '', sort: 'recent', order: '', - types: 'dataset,usecase,aimodel,collaborative,publisher', // Default: search all types + types: ALL_LISTING_TYPES, // Default: search all listing types }; // Query Reducer @@ -194,7 +196,7 @@ const useUrlParams = ( currentPage: pageParam ? Number(pageParam) : 1, filters, query: urlParams.get('query') || '', - types: typesParam || 'dataset,usecase,aimodel,collaborative,publisher', + types: typesParam || ALL_LISTING_TYPES, }; setQueryParams({ type: 'INITIALIZE', payload: initialParams }); @@ -219,7 +221,13 @@ const useUrlParams = ( ? `&types=${encodeURIComponent(queryParams.types)}` : ''; - const variablesString = `?${filtersString}&size=${queryParams.pageSize}&page=${queryParams.currentPage}${searchParam}${sortParam}${orderParam}${typesParam}`; + // In landing mode we need enough mixed items to show all 4 sections. + const effectiveSize = + queryParams.types === ALL_LISTING_TYPES ? 120 : queryParams.pageSize; + const effectivePage = + queryParams.types === ALL_LISTING_TYPES ? 1 : queryParams.currentPage; + + const variablesString = `?${filtersString}&size=${effectiveSize}&page=${effectivePage}${searchParam}${sortParam}${orderParam}${typesParam}`; setVariables(variablesString); const currentUrl = new URL(window.location.href); @@ -307,6 +315,9 @@ const UnifiedListingComponent: React.FC = ({ const [open, setOpen] = useState(false); const [queryParams, setQueryParams] = useReducer(queryReducer, initialState); const [view, setView] = useState<'collapsed' | 'expanded'>('collapsed'); + const [persistedTypeCounts, setPersistedTypeCounts] = useState< + Record + >({}); const count = facets?.total ?? 0; const results = facets?.results ?? []; @@ -346,8 +357,6 @@ const UnifiedListingComponent: React.FC = ({ setHasMounted(true); }, []); - if (!hasMounted) return ; - const handlePageChange = (newPage: number) => { setQueryParams({ type: 'SET_CURRENT_PAGE', payload: newPage }); }; @@ -382,35 +391,94 @@ const UnifiedListingComponent: React.FC = ({ const aggregations: Aggregations = facets?.aggregations || {}; - const filterOptions = Object.entries(aggregations).reduce( - ( - acc: Record, - [key, _value] - ) => { - // Skip the 'types' aggregation from filters - if (key === 'types') return acc; - - // Check if _value exists and has buckets array (Elasticsearch format) - if (_value && _value.buckets && Array.isArray(_value.buckets)) { - acc[key] = _value.buckets.map((bucket) => ({ - label: bucket.key, - value: bucket.key, - })); - } - // Handle key-value object format (current backend format) - else if (_value && typeof _value === 'object' && !Array.isArray(_value)) { - acc[key] = Object.entries(_value).map(([label]) => ({ - label: label, - value: label, - })); + const getFilterPriority = (key: string) => { + console.log('key', key); + const normalized = key.toLowerCase().replace(/[\s_-]/g, ''); + + if (normalized === 'geographies') return 1; + if (normalized === 'sectors') return 2; + if (normalized === 'timeperiod') return 3; + if (normalized === 'contributortype' || normalized === 'publishertype') + return 4; + if (normalized === 'fileformat' || normalized === 'formats') return 5; + if (normalized === 'tags') return 6; + if (normalized === 'status') return 7; + + return 999; + }; + + const filterOptions = Object.entries(aggregations) + .sort(([a], [b]) => { + const priorityDiff = getFilterPriority(a) - getFilterPriority(b); + return priorityDiff !== 0 ? priorityDiff : a.localeCompare(b); + }) + .reduce( + ( + acc: Record, + [key, _value] + ) => { + // Skip the 'types' aggregation from filters + if (key === 'types') return acc; + + // Check if _value exists and has buckets array (Elasticsearch format) + if (_value && _value.buckets && Array.isArray(_value.buckets)) { + acc[key] = _value.buckets.map((bucket) => ({ + label: bucket.key, + value: bucket.key, + })); + } + // Handle key-value object format (current backend format) + else if ( + _value && + typeof _value === 'object' && + !Array.isArray(_value) + ) { + acc[key] = Object.entries(_value).map(([label]) => ({ + label: label, + value: label, + })); + } + return acc; + }, + {} + ); + + // Get type counts from aggregations + const typeCounts = aggregations.types || {}; + const liveTypeCounts = Object.entries(typeCounts).reduce( + (acc, [key, value]) => { + if (typeof value === 'number') { + acc[key] = value; } return acc; }, - {} + {} as Record ); + const displayTypeCounts = { ...persistedTypeCounts, ...liveTypeCounts }; - // Get type counts from aggregations - const typeCounts = aggregations.types || {}; + useEffect(() => { + if (Object.keys(liveTypeCounts).length === 0) return; + + setPersistedTypeCounts((prev) => { + const next = { ...prev }; + let changed = false; + + Object.entries(liveTypeCounts).forEach(([key, value]) => { + if (next[key] !== value) { + next[key] = value; + changed = true; + } + }); + + return changed ? next : prev; + }); + }, [typeCounts]); + + const getTypeButtonClass = (type: string) => { + return `font-normal rounded-full border-1 border-solid border-[#C9cccf] ${queryParams.types === type ? 'font-semibold bg-[#E5EFFD] text-primaryBlue border-[#E5EFFD]' : 'bg-[#F2F7FE]'} hover:bg-[#EDF4FE]`; + }; + + if (!hasMounted) return ; // Helper function to get redirect URL based on type const getRedirectUrl = (item: any) => { @@ -435,11 +503,425 @@ const UnifiedListingComponent: React.FC = ({ } }; + const isSectionedLanding = queryParams.types === ALL_LISTING_TYPES; + const selectedTypes = (queryParams.types || '') + .split(',') + .map((type) => type.trim()) + .filter(Boolean); + const singleSelectedType = + !isSectionedLanding && selectedTypes.length === 1 ? selectedTypes[0] : null; + const tabResults = singleSelectedType + ? results.filter((item: any) => item.type === singleSelectedType) + : results; + const tabTotalCount = + singleSelectedType && + typeof displayTypeCounts[singleSelectedType] === 'number' + ? displayTypeCounts[singleSelectedType] + : count; + + const renderResultCard = (item: any) => { + const isIndividual = + item.is_individual_dataset || + item.is_individual_usecase || + item.is_individual_model || + item.is_individual_collaborative || + (item.type === 'publisher' && item.publisher_type === 'user'); + + const image = + item.type === 'publisher' + ? item.logo + ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.logo}` + : item.publisher_type === 'user' + ? '/profile.png' + : '/org.png' + : isIndividual + ? item?.user?.profile_picture + ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.user.profile_picture}` + : '/profile.png' + : item?.organization?.logo + ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.organization.logo}` + : '/org.png'; + + const geographies = + item.geographies && item.geographies.length > 0 ? item.geographies : null; + const sdgs = item.sdgs && item.sdgs.length > 0 ? item.sdgs : null; + + const MetadataContent = []; + + if (item.type === 'publisher') { + MetadataContent.push({ + icon: Icons.calendar as any, + label: 'Joined', + value: formatDate(item.created), + tooltip: 'Date joined', + }); + MetadataContent.push({ + icon: Icons.dataset as any, + label: 'Datasets', + value: item.published_datasets_count?.toString() || '0', + tooltip: 'Published datasets', + }); + MetadataContent.push({ + icon: Icons.usecase as any, + label: 'Use Cases', + value: item.published_usecases_count?.toString() || '0', + tooltip: 'Published use cases', + }); + if (item.publisher_type === 'organization' && item.members_count > 0) { + MetadataContent.push({ + icon: Icons.users as any, + label: 'Members', + value: item.members_count?.toString() || '0', + tooltip: 'Organization members', + }); + } + } else if (item.type === 'collaborative') { + MetadataContent.push({ + icon: Icons.calendar as any, + label: 'Started', + value: formatDate(item.started_on || item.created), + }); + MetadataContent.push({ + icon: Icons.dataset as any, + label: 'Datasets', + value: item.dataset_count?.toString() || '0', + }); + if (geographies && geographies.length > 0) { + const geoDisplay = geographies.join(', '); + MetadataContent.push({ + icon: Icons.globe as any, + label: 'Geography', + value: geoDisplay, + }); + } else { + MetadataContent.push({ + icon: Icons.globe as any, + label: 'Geography', + value: 'N/A', + }); + } + } else { + MetadataContent.push({ + icon: Icons.calendar as any, + label: 'Date', + value: formatDate(item.modified || item.updated_at), + tooltip: 'Date', + }); + + if (geographies && geographies.length > 0) { + const geoDisplay = geographies.join(', '); + MetadataContent.push({ + icon: Icons.globe as any, + label: 'Geography', + value: geoDisplay, + tooltip: geoDisplay, + }); + } + } + + if (item.type === 'dataset' && item.download_count > 0) { + MetadataContent.push({ + icon: Icons.download as any, + label: 'Download', + value: item.download_count?.toString() || '0', + tooltip: 'Download', + }); + } + + if (item.type === 'dataset' && sdgs && sdgs.length > 0) { + const sdgDisplay = sdgs + .map((sdg: any) => `${sdg.code} - ${sdg.name}`) + .join(', '); + MetadataContent.push({ + icon: Icons.star as any, + label: 'SDG Goals', + value: sdgDisplay, + tooltip: sdgDisplay, + }); + } + + if (item.type === 'dataset' && item.has_charts && view === 'expanded') { + MetadataContent.push({ + icon: Icons.chart, + label: '', + value: 'With Charts', + tooltip: 'Charts', + }); + } + + const FooterContent = []; + + if (item.type === 'publisher') { + FooterContent.push({ + icon: + item.publisher_type === 'organization' ? '/org.png' : '/profile.png', + label: + item.publisher_type === 'organization' + ? 'Organization' + : 'Individual Publisher', + tooltip: + item.publisher_type === 'organization' + ? 'Organization Publisher' + : 'Individual Publisher', + }); + } else if (item.type === 'collaborative') { + if (item.sectors && item.sectors.length > 0) { + const sectorName = + typeof item.sectors[0] === 'string' + ? item.sectors[0] + : item.sectors[0]?.name; + FooterContent.push({ + icon: sectorName + ? `/Sectors/${sectorName}.svg` + : '/Sectors/default.svg', + label: 'Sectors', + }); + } else { + FooterContent.push({ + icon: '/Sectors/default.svg', + label: 'Sectors', + }); + } + } else if (item.sectors && item.sectors.length > 0) { + FooterContent.push({ + icon: `/Sectors/${item.sectors?.[0]}.svg` as any, + label: 'Sectors', + tooltip: `${item.sectors?.[0]}`, + }); + } + + if (item.type === 'dataset' && item.has_charts && view !== 'expanded') { + FooterContent.push({ + icon: `/chart-bar.svg` as any, + label: 'Charts', + tooltip: 'Charts', + }); + } + + if (item.type !== 'publisher') { + FooterContent.push({ + icon: image as any, + label: 'Published by', + tooltip: `${isIndividual ? item.user?.name : item.organization?.name}`, + }); + } + + const commonProps = { + title: item.title || item.name || '', + description: stripMarkdown(item.description || item.bio || ''), + // ...(item.type === 'usecase' && { + // description: stripMarkdown(item.description || item.bio || ''), + // }), + metadataContent: MetadataContent, + tag: item.tags || [], + formats: item.type === 'dataset' ? item.formats || [] : [], + footerContent: FooterContent, + imageUrl: '', + }; + + if (item.type === 'publisher') { + if (item.logo) { + commonProps.imageUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.logo}`; + } else if (item.profile_picture) { + commonProps.imageUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.profile_picture}`; + } + } else if (item.logo) { + commonProps.imageUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.logo}`; + } + + if (item.type === 'publisher') { + return ( + +
+ publisher logo +
+ + {item.name || item.title} + +
+ + {item.publisher_type === 'user' + ? 'Individual Publisher' + : 'Organization'} + +
+
+
+
+
+ + {item.published_usecases_count || 0} Use Cases + +
+
+ + {item.published_datasets_count || 0} Datasets + +
+
+ {(item.bio || item.description) && ( +
+ + {(item.bio || item.description)?.length > 220 + ? (item.bio || item.description).slice(0, 220) + '...' + : item.bio || item.description} + +
+ )} + + ); + } + + return ( + + ); + }; + return (
{breadcrumbData && } +
+
+ handleSearch(value)} + onClear={(value) => handleSearch(value)} + /> +
+
+ {/*
+ + + + +
*/} + {/*
+ +
*/} +
+ -
- - setOpen(true)} - > - Filter - - } - > - - -
-
- {Object.entries(queryParams.filters).some( ([key, value]) => key !== 'sort' && Array.isArray(value) && value.length > 0 @@ -674,316 +1046,93 @@ const UnifiedListingComponent: React.FC = ({ {facets === null ? ( ) : results.length > 0 ? ( - - {results.map((item: any) => { - // Determine if it's individual or organization - const isIndividual = - item.is_individual_dataset || - item.is_individual_usecase || - item.is_individual_model || - item.is_individual_collaborative || - (item.type === 'publisher' && item.publisher_type === 'user'); - - const image = item.type === 'publisher' - ? item.logo - ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.logo}` - : item.publisher_type === 'user' - ? '/profile.png' - : '/org.png' - : isIndividual - ? item?.user?.profile_picture - ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.user.profile_picture}` - : '/profile.png' - : item?.organization?.logo - ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.organization.logo}` - : '/org.png'; - - const geographies = - item.geographies && item.geographies.length > 0 - ? item.geographies - : null; - - const sdgs = - item.sdgs && item.sdgs.length > 0 ? item.sdgs : null; - - const MetadataContent = []; - - // Type-specific metadata - if (item.type === 'publisher') { - // Publisher-specific metadata - MetadataContent.push({ - icon: Icons.calendar as any, - label: 'Joined', - value: formatDate(item.created), - tooltip: 'Date joined', - }); - - MetadataContent.push({ - icon: Icons.dataset as any, - label: 'Datasets', - value: item.published_datasets_count?.toString() || '0', - tooltip: 'Published datasets', - }); - - MetadataContent.push({ - icon: Icons.usecase as any, - label: 'Use Cases', - value: item.published_usecases_count?.toString() || '0', - tooltip: 'Published use cases', - }); - - // Add members count for organizations - if (item.publisher_type === 'organization' && item.members_count > 0) { - MetadataContent.push({ - icon: Icons.users as any, - label: 'Members', - value: item.members_count?.toString() || '0', - tooltip: 'Organization members', - }); - } - } else if (item.type === 'collaborative') { - MetadataContent.push({ - icon: Icons.calendar as any, - label: 'Started', - value: formatDate(item.started_on || item.created), - }); - - MetadataContent.push({ - icon: Icons.dataset as any, - label: 'Datasets', - value: item.dataset_count?.toString() || '0', - }); - - // Add geography with proper fallback like listing page - if (geographies && geographies.length > 0) { - const geoDisplay = geographies.join(', '); - MetadataContent.push({ - icon: Icons.globe as any, - label: 'Geography', - value: geoDisplay, - }); - } else { - MetadataContent.push({ - icon: Icons.globe as any, - label: 'Geography', - value: 'N/A', - }); - } - } else { - // For other types, use the generic date - MetadataContent.push({ - icon: Icons.calendar as any, - label: 'Date', - value: formatDate(item.modified || item.updated_at), - tooltip: 'Date', - }); - - // Add geography for non-collaborative types - if (geographies && geographies.length > 0) { - const geoDisplay = geographies.join(', '); - MetadataContent.push({ - icon: Icons.globe as any, - label: 'Geography', - value: geoDisplay, - tooltip: geoDisplay, - }); - } - } - - // Type-specific metadata for datasets - if (item.type === 'dataset' && item.download_count > 0) { - MetadataContent.push({ - icon: Icons.download as any, - label: 'Download', - value: item.download_count?.toString() || '0', - tooltip: 'Download', - }); - } - - - // Add SDGs for datasets - if (item.type === 'dataset' && sdgs && sdgs.length > 0) { - const sdgDisplay = sdgs - .map((sdg: any) => `${sdg.code} - ${sdg.name}`) - .join(', '); - MetadataContent.push({ - icon: Icons.star as any, - label: 'SDG Goals', - value: sdgDisplay, - tooltip: sdgDisplay, - }); - } - - // Add charts indicator for datasets - if ( - item.type === 'dataset' && - item.has_charts && - view === 'expanded' - ) { - MetadataContent.push({ - icon: Icons.chart, - label: '', - value: 'With Charts', - tooltip: 'Charts', - }); - } - - const FooterContent = []; - - // Add sector icon - handle different types - if (item.type === 'publisher') { - // For publishers, show publisher type badge - FooterContent.push({ - icon: item.publisher_type === 'organization' ? '/org.png' : '/profile.png', - label: item.publisher_type === 'organization' ? 'Organization' : 'Individual Publisher', - tooltip: item.publisher_type === 'organization' ? 'Organization Publisher' : 'Individual Publisher', - }); - } else if (item.type === 'collaborative') { - // For collaboratives, match listing page format exactly - if (item.sectors && item.sectors.length > 0) { - const sectorName = typeof item.sectors[0] === 'string' ? item.sectors[0] : item.sectors[0]?.name; - FooterContent.push({ - icon: sectorName ? `/Sectors/${sectorName}.svg` : '/Sectors/default.svg', - label: 'Sectors', - }); - } else { - FooterContent.push({ - icon: '/Sectors/default.svg', - label: 'Sectors', - }); - } - } else { - // For other types, use existing format - if (item.sectors && item.sectors.length > 0) { - FooterContent.push({ - icon: `/Sectors/${item.sectors?.[0]}.svg` as any, - label: 'Sectors', - tooltip: `${item.sectors?.[0]}`, - }); - } - } - - // Add charts indicator for datasets - if (item.type === 'dataset' && item.has_charts && view !== 'expanded') { - FooterContent.push({ - icon: `/chart-bar.svg` as any, - label: 'Charts', - tooltip: 'Charts', - }); - } - - // Add published by info (skip for publishers since they are the publishers themselves) - if (item.type !== 'publisher') { - FooterContent.push({ - icon: image as any, - label: 'Published by', - tooltip: `${isIndividual ? item.user?.name : item.organization?.name}`, - }); - } - - const commonProps = { - title: item.title || item.name || '', - description: stripMarkdown(item.description || item.bio || ''), - metadataContent: MetadataContent, - tag: item.tags || [], - formats: item.type === 'dataset' ? item.formats || [] : [], - footerContent: FooterContent, - imageUrl: '', - }; - - // Handle different image sources for publishers vs other types - if (item.type === 'publisher') { - if (item.logo) { - commonProps.imageUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.logo}`; - } else if (item.profile_picture) { - commonProps.imageUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.profile_picture}`; - } - } else if (item.logo) { - commonProps.imageUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.logo}`; - } + isSectionedLanding ? ( +
+ {[ + { + key: 'aimodel', + title: 'AI Models', + subtitle: + 'The most popular AI models on CivicDataSpace', + }, + { + key: 'dataset', + title: 'Datasets', + subtitle: 'The most popular datasets on CivicDataSpace', + }, + { + key: 'usecase', + title: 'Use Cases', + subtitle: + 'The most popular use cases on CivicDataSpace', + }, + { + key: 'publisher', + title: 'Contributors', + subtitle: + 'The most active contributors on CivicDataSpace', + }, + ].map((section) => { + const sectionResults = Array.from( + new Map( + results + .filter((item: any) => item.type === section.key) + .map((item: any) => [ + `${item.type}-${item.publisher_type || ''}-${item.id}`, + item, + ]) + ).values() + ).slice(0, 3); + + if (!sectionResults.length) return null; - // Use different rendering for publishers vs other types - if (item.type === 'publisher') { return ( - -
- publisher logo -
- - {item.name || item.title} +
+
+
+ + {section.title} -
- - {item.publisher_type === 'user' - ? 'Individual Publisher' - : 'Organization'} - -
+ {section.subtitle}
+
-
-
- - {item.published_usecases_count || 0} Use Cases - -
-
- - {item.published_datasets_count || 0} Datasets - -
+
+ {sectionResults.map((item: any) => + renderResultCard(item) + )}
- {(item.bio || item.description) && ( -
- - {(item.bio || item.description)?.length > 220 - ? (item.bio || item.description).slice(0, 220) + '...' - : (item.bio || item.description)} - -
- )} - - ); - } else { - return ( - +
); - } - })} - + })} +
+ ) : ( + + {tabResults.map((item: any) => renderResultCard(item))} + + ) ) : (
No results found diff --git a/app/[locale]/dashboard/components/main-nav.tsx b/app/[locale]/dashboard/components/main-nav.tsx index cb384e18..ea1279ce 100644 --- a/app/[locale]/dashboard/components/main-nav.tsx +++ b/app/[locale]/dashboard/components/main-nav.tsx @@ -1,26 +1,26 @@ 'use client'; -import { Session } from 'next-auth'; -import { signIn, signOut, useSession } from 'next-auth/react'; +import React, { useEffect, useState } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; +import { Session } from 'next-auth'; +import { signIn, signOut, useSession } from 'next-auth/react'; import { - Avatar, - Button, - Dialog, - Divider, - IconButton, - Popover, - SearchInput, - Spinner, - Text, + Avatar, + Button, + Dialog, + Divider, + IconButton, + Popover, + SearchInput, + Spinner, + Text, } from 'opub-ui'; -import React, { useEffect, useState } from 'react'; -import { Icons } from '@/components/icons'; import { useDashboardStore } from '@/config/store'; import { GraphQL } from '@/lib/api'; +import { Icons } from '@/components/icons'; import { UserDetailsQryDoc } from '../[entityType]/[entitySlug]/schema'; import { allOrganizationsListingDoc } from '../[entityType]/schema'; import Sidebar from './sidebar'; @@ -60,25 +60,41 @@ export function MainNav({ hideSearch = false }) { const [hasFetched, setHasFetched] = useState(false); useEffect(() => { + if (status !== 'authenticated' || !session?.user) { + // Reset when user is not authenticated to allow a clean fetch after login. + if (hasFetched) setHasFetched(false); + return; + } + const fetchData = async () => { - if (session?.user && !hasFetched) { - try { - const [userDetailsRes, entityDetailsRes] = await Promise.all([ - GraphQL(UserDetailsQryDoc, {}, []), - GraphQL(allOrganizationsListingDoc, {}, []), - ]); + if (hasFetched) return; + + try { + const [userDetailsRes, entityDetailsRes] = await Promise.all([ + GraphQL(UserDetailsQryDoc, {}, []), + GraphQL(allOrganizationsListingDoc, {}, []), + ]); + + setUserDetails(userDetailsRes); + setAllEntityDetails(entityDetailsRes); + setHasFetched(true); + } catch (err: any) { + const errorMessage = String(err?.message || err || ''); + const isUnauthenticated = errorMessage.includes( + 'User is not authenticated' + ); - setUserDetails(userDetailsRes); - setAllEntityDetails(entityDetailsRes); - setHasFetched(true); - } catch (err) { + if (!isUnauthenticated) { console.error('Error fetching user/org data:', err); } + + // Stop repeated retries/log spam for unauthenticated sessions. + setHasFetched(true); } }; fetchData(); - }, [session, hasFetched, setUserDetails, setAllEntityDetails]); + }, [status, session?.user, hasFetched, setUserDetails, setAllEntityDetails]); const exploreLinks = [ { @@ -118,7 +134,7 @@ export function MainNav({ hideSearch = false }) { if (value) { setIsOpen(false); - router.push(`/datasets?query=${encodeURIComponent(value)}`); + router.push(`/search?query=${encodeURIComponent(value)}`); } }; return ( @@ -261,9 +277,7 @@ export function MainNav({ hideSearch = false }) { ) : (
{session?.user ? ( - + ) : (