diff --git a/frontend/src/components/shared/ResearchLeadershipAnalysis/ResearchLeadershipAnalysis.module.css b/frontend/src/components/shared/ResearchLeadershipAnalysis/ResearchLeadershipAnalysis.module.css index 0e7f3e1..75b8ef3 100644 --- a/frontend/src/components/shared/ResearchLeadershipAnalysis/ResearchLeadershipAnalysis.module.css +++ b/frontend/src/components/shared/ResearchLeadershipAnalysis/ResearchLeadershipAnalysis.module.css @@ -1,7 +1,7 @@ .analysisContainer { - background: #ffffff; + background: #1a1a1a; border-radius: 12px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); padding: 24px; margin: 20px 0; position: relative; @@ -14,7 +14,7 @@ } .header h3 { - color: #2c3e50; + color: #ffffff; font-size: 24px; font-weight: 700; margin: 0 0 8px 0; @@ -25,7 +25,7 @@ } .header p { - color: #7f8c8d; + color: #b0b0b0; font-size: 16px; margin: 0; font-weight: 400; @@ -39,10 +39,10 @@ } .analysisCard { - background: #f8f9fa; + background: #2a2a2a; border-radius: 8px; padding: 20px; - border: 1px solid #e9ecef; + border: 1px solid #404040; transition: all 0.3s ease; } @@ -52,7 +52,7 @@ } .analysisCard h4 { - color: #2c3e50; + color: #ffffff; font-size: 18px; font-weight: 600; margin: 0 0 16px 0; @@ -72,14 +72,14 @@ align-items: center; gap: 12px; padding: 12px; - background: white; + background: #1a1a1a; border-radius: 6px; - border: 1px solid #e9ecef; + border: 1px solid #404040; transition: all 0.2s ease; } .statItem:hover { - background: #f8f9fa; + background: #2a2a2a; border-color: #667eea; } @@ -125,7 +125,7 @@ .name { font-weight: 600; - color: #2c3e50; + color: #ffffff; font-size: 14px; margin-bottom: 4px; white-space: nowrap; @@ -137,7 +137,7 @@ display: flex; gap: 12px; font-size: 12px; - color: #6c757d; + color: #b0b0b0; } .citations { @@ -155,14 +155,14 @@ justify-content: space-between; align-items: center; padding: 8px 12px; - background: white; + background: #1a1a1a; border-radius: 4px; - border: 1px solid #e9ecef; + border: 1px solid #404040; } .year { font-weight: 600; - color: #2c3e50; + color: #ffffff; font-size: 14px; } @@ -170,7 +170,7 @@ display: flex; gap: 12px; font-size: 12px; - color: #6c757d; + color: #b0b0b0; } .summaryStats { @@ -182,9 +182,9 @@ .summaryItem { text-align: center; padding: 16px; - background: white; + background: #1a1a1a; border-radius: 6px; - border: 1px solid #e9ecef; + border: 1px solid #404040; } .summaryValue { @@ -196,7 +196,7 @@ .summaryLabel { font-size: 12px; - color: #6c757d; + color: #b0b0b0; font-weight: 500; } @@ -225,7 +225,7 @@ } .loading p { - color: #7f8c8d; + color: #b0b0b0; font-size: 14px; margin: 0; } @@ -278,72 +278,4 @@ .summaryValue { font-size: 20px; } -} - -/* Dark mode support */ -@media (prefers-color-scheme: dark) { - .analysisContainer { - background: #1a1a1a; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - } - - .header h3 { - color: #ffffff; - } - - .header p { - color: #b0b0b0; - } - - .analysisCard { - background: #2a2a2a; - border-color: #404040; - } - - .analysisCard h4 { - color: #ffffff; - } - - .statItem { - background: #1a1a1a; - border-color: #404040; - } - - .statItem:hover { - background: #2a2a2a; - } - - .name { - color: #ffffff; - } - - .metrics { - color: #b0b0b0; - } - - .trendItem { - background: #1a1a1a; - border-color: #404040; - } - - .year { - color: #ffffff; - } - - .trendMetrics { - color: #b0b0b0; - } - - .summaryItem { - background: #1a1a1a; - border-color: #404040; - } - - .summaryLabel { - color: #b0b0b0; - } - - .loading p { - color: #b0b0b0; - } } \ No newline at end of file diff --git a/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.jsx b/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.jsx index e0072d8..0886d76 100644 --- a/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.jsx +++ b/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.jsx @@ -217,133 +217,76 @@ const getCountryCentroid = (countryCode) => countryCentroids[countryCode] || [0, const OPENALEX_API_BASE = 'https://api.openalex.org'; -const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate }) => { +const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerSearch = false, searchResults = null }) => { const [papers, setPapers] = useState([]); const [loading, setLoading] = useState(false); const [tooltipContent, setTooltipContent] = useState(''); const [mapError, setMapError] = useState(false); const [fetchError, setFetchError] = useState(null); - // Sample data structure for fallback - const samplePapers = [ - { - id: 1, - title: "Attention Is All You Need", - authors: ["Vaswani, A.", "Shazeer, N.", "Parmar, N."], - citations: 45000, - country: "US", - coordinates: [-95.7129, 37.0902], - year: 2017, - institution: "Google Research" - }, - { - id: 2, - title: "BERT: Pre-training of Deep Bidirectional Transformers", - authors: ["Devlin, J.", "Chang, M.W.", "Lee, K."], - citations: 38000, - country: "US", - coordinates: [-95.7129, 37.0902], - year: 2018, - institution: "Google AI" - }, - { - id: 3, - title: "Deep Learning", - authors: ["LeCun, Y.", "Bengio, Y.", "Hinton, G."], - citations: 35000, - country: "CA", - coordinates: [-106.3468, 56.1304], - year: 2015, - institution: "University of Toronto" - }, - { - id: 4, - title: "ImageNet Classification with Deep Convolutional Neural Networks", - authors: ["Krizhevsky, A.", "Sutskever, I.", "Hinton, G.E."], - citations: 32000, - country: "CA", - coordinates: [-106.3468, 56.1304], - year: 2012, - institution: "University of Toronto" - }, - { - id: 5, - title: "Generative Adversarial Networks", - authors: ["Goodfellow, I.", "Pouget-Abadie, J.", "Mirza, M."], - citations: 30000, - country: "US", - coordinates: [-95.7129, 37.0902], - year: 2014, - institution: "University of Montreal" - }, - { - id: 6, - title: "ResNet: Deep Residual Learning for Image Recognition", - authors: ["He, K.", "Zhang, X.", "Ren, S."], - citations: 28000, - country: "CN", - coordinates: [104.1954, 35.8617], - year: 2015, - institution: "Microsoft Research" - }, - { - id: 7, - title: "YOLO: Real-Time Object Detection", - authors: ["Redmon, J.", "Divvala, S.", "Girshick, R."], - citations: 25000, - country: "US", - coordinates: [-95.7129, 37.0902], - year: 2016, - institution: "University of Washington" - }, - { - id: 8, - title: "Transformer: A Novel Neural Network Architecture", - authors: ["Vaswani, A.", "Shazeer, N.", "Parmar, N."], - citations: 22000, - country: "GB", - coordinates: [-0.1278, 51.5074], - year: 2017, - institution: "Google DeepMind" - }, - { - id: 9, - title: "AlphaGo: Mastering the Game of Go", - authors: ["Silver, D.", "Huang, A.", "Maddison, C."], - citations: 20000, - country: "GB", - coordinates: [-0.1278, 51.5074], - year: 2016, - institution: "Google DeepMind" - }, - { - id: 10, - title: "GPT: Improving Language Understanding", - authors: ["Radford, A.", "Narasimhan, K.", "Salimans, T."], - citations: 18000, - country: "US", - coordinates: [-95.7129, 37.0902], - year: 2018, - institution: "OpenAI" - } - ]; - useEffect(() => { - const trimmedQuery = (searchQuery || '').trim(); - if (trimmedQuery.length > 0) { - fetchPapersByQuery(trimmedQuery); - } else { - setPapers(samplePapers); + // If search results are provided from parent, use those + if (searchResults && searchResults.length > 0) { + const mapped = searchResults.map((work, idx) => { + // Try to get first author institution country and coordinates + let country = null; + let coordinates = null; + let institution = null; + if (work.authorships && work.authorships.length > 0) { + const firstAuth = work.authorships[0]; + if (firstAuth.institutions && firstAuth.institutions.length > 0) { + const inst = firstAuth.institutions[0]; + country = inst.country_code || inst.country || null; + institution = inst.display_name || null; + // If OpenAlex provides lat/lon, use it (not always available) + if (inst.latitude && inst.longitude) { + coordinates = [inst.longitude, inst.latitude]; + } else if (country) { + coordinates = getCountryCentroid(country); + } + } + } + // Fallback: use country from work if available + if (!coordinates && work.country_code) { + coordinates = getCountryCentroid(work.country_code); + country = work.country_code; + } + // Fallback: skip if no coordinates + if (!coordinates) return null; + return { + id: work.id || idx, + title: work.title || work.display_name || 'Untitled', + authors: work.authorships ? work.authorships.map(a => a.author?.display_name || '').filter(Boolean) : [], + citations: work.citation_count || work.cited_by_count || 0, + country, + coordinates, + year: work.publication_year || null, + institution: institution || null + }; + }).filter(Boolean); + setPapers(mapped); + setLoading(false); + setFetchError(null); + } else if (triggerSearch && searchQuery && searchQuery.trim().length > 0) { + // Fallback to own API call if no results provided + fetchPapersByQuery(searchQuery.trim()); + } else if (!triggerSearch) { + // Reset to empty state when not searching + setPapers([]); setFetchError(null); setLoading(false); + // Update API calls for disclaimer + if (onApiCallsUpdate) { + onApiCallsUpdate([]); + } } // eslint-disable-next-line - }, [searchQuery]); + }, [triggerSearch, searchQuery, searchResults]); const fetchPapersByQuery = async (query) => { const trimmed = (query || '').trim(); if (!trimmed) { - setPapers(samplePapers); + setPapers([]); setFetchError(null); setLoading(false); // Update API calls for disclaimer @@ -522,6 +465,52 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate }) => {

{fetchError}

)} + {!loading && !fetchError && papers.length === 0 && searchQuery && ( +
+
🔍
+

No Results Found

+

+ No research papers found for your search query. Try different keywords or filters to discover relevant research. +

+
+
+
📝
+
Try different keywords
+
+
+
🔬
+
Use specific terms
+
+
+
🌍
+
Check filters
+
+
+
+ )} + {!loading && !fetchError && papers.length === 0 && !searchQuery && ( +
+
🌍
+

Discover Global Research Impact

+

+ Enter keywords in the search box above to find research papers and visualize their global impact on the world map. +

+
+
+
📊
+
Citation Analysis
+
+
+
🏆
+
Research Leadership
+
+
+
🎯
+
Global Impact
+
+
+
+ )} {mapError ? (

Map Loading Error

@@ -530,7 +519,7 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate }) => { Retry
- ) : ( + ) : papers.length > 0 && (
{
)} -
-

Citation Impact Legend

-
-
-
- 20,000+ citations -
-
-
- 15,000+ citations -
-
-
- 10,000+ citations -
-
-
- 5,000+ citations -
-
-
- < 5,000 citations + {papers.length > 0 && ( +
+

Citation Impact Legend

+
+
+
+ 20,000+ citations +
+
+
+ 15,000+ citations +
+
+
+ 10,000+ citations +
+
+
+ 5,000+ citations +
+
+
+ < 5,000 citations +
-
+ )} {/* Research Leadership Analysis */} {papers.length > 0 && !loading && ( diff --git a/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.module.css b/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.module.css index 6887802..4b6a3d8 100644 --- a/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.module.css +++ b/frontend/src/components/shared/WorldMapPapers/WorldMapPapers.module.css @@ -1,7 +1,7 @@ .worldMapContainer { - background: #ffffff; + background: #1a1a1a; border-radius: 12px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); padding: 24px; margin: 20px 0; position: relative; @@ -14,7 +14,7 @@ } .header h2 { - color: #2c3e50; + color: #ffffff; font-size: 28px; font-weight: 700; margin: 0 0 8px 0; @@ -25,7 +25,7 @@ } .header p { - color: #7f8c8d; + color: #b0b0b0; font-size: 16px; margin: 0; font-weight: 400; @@ -37,8 +37,8 @@ height: 600px; border-radius: 8px; overflow: hidden; - background: #f8f9fa; - border: 1px solid #e9ecef; + background: #2a2a2a; + border: 1px solid #404040; } .errorContainer { @@ -47,28 +47,133 @@ align-items: center; justify-content: center; height: 600px; - background: #f8f9fa; + background: #2a2a2a; border-radius: 8px; - border: 1px solid #e9ecef; + border: 1px solid #404040; text-align: center; padding: 24px; } .errorContainer h3 { - color: #e74c3c; + color: #ff6b6b; font-size: 24px; margin: 0 0 16px 0; font-weight: 600; } .errorContainer p { - color: #7f8c8d; + color: #b0b0b0; font-size: 16px; margin: 0 0 24px 0; max-width: 400px; line-height: 1.5; } +/* New attractive empty state styles */ +.emptyStateContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 600px; + background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%); + border-radius: 8px; + border: 1px solid #404040; + text-align: center; + padding: 40px; + position: relative; + overflow: hidden; +} + +.emptyStateContainer::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%); + animation: float 6s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0px) rotate(0deg); } + 50% { transform: translateY(-20px) rotate(180deg); } +} + +.emptyStateIcon { + font-size: 80px; + margin-bottom: 24px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + +.emptyStateTitle { + color: #ffffff; + font-size: 28px; + font-weight: 700; + margin: 0 0 16px 0; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.emptyStateSubtitle { + color: #b0b0b0; + font-size: 18px; + margin: 0 0 32px 0; + max-width: 500px; + line-height: 1.6; +} + +.emptyStateFeatures { + display: flex; + gap: 32px; + margin-top: 32px; + flex-wrap: wrap; + justify-content: center; +} + +.emptyStateFeature { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 20px; + background: rgba(102, 126, 234, 0.1); + border-radius: 12px; + border: 1px solid rgba(102, 126, 234, 0.2); + min-width: 140px; + transition: all 0.3s ease; +} + +.emptyStateFeature:hover { + transform: translateY(-4px); + background: rgba(102, 126, 234, 0.15); + border-color: rgba(102, 126, 234, 0.3); +} + +.featureIcon { + font-size: 32px; + color: #667eea; +} + +.featureText { + color: #b0b0b0; + font-size: 14px; + font-weight: 500; + text-align: center; +} + .retryButton { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; @@ -94,7 +199,7 @@ transform: translate(-50%, -50%); text-align: center; z-index: 10; - background: rgba(255, 255, 255, 0.95); + background: rgba(26, 26, 26, 0.95); padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); @@ -116,7 +221,7 @@ } .loading p { - color: #7f8c8d; + color: #b0b0b0; font-size: 14px; margin: 0; } @@ -170,14 +275,14 @@ .legend { margin-top: 24px; padding: 16px; - background: #f8f9fa; + background: #2a2a2a; border-radius: 8px; - border: 1px solid #e9ecef; + border: 1px solid #404040; } .legend h4 { margin: 0 0 12px 0; - color: #2c3e50; + color: #ffffff; font-size: 16px; font-weight: 600; } @@ -193,7 +298,7 @@ align-items: center; gap: 8px; font-size: 14px; - color: #5a6c7d; + color: #b0b0b0; } .legendDot { @@ -226,6 +331,28 @@ padding: 24px; } + .emptyStateContainer { + height: 500px; + padding: 24px; + } + + .emptyStateTitle { + font-size: 24px; + } + + .emptyStateSubtitle { + font-size: 16px; + } + + .emptyStateFeatures { + gap: 16px; + } + + .emptyStateFeature { + min-width: 120px; + padding: 16px; + } + .errorContainer h3 { font-size: 20px; } @@ -267,70 +394,38 @@ padding: 16px; } - .legend { - padding: 12px; - } - - .legendItems { - gap: 8px; - } - - .legendItem { - font-size: 12px; - } -} - -/* Dark mode support */ -@media (prefers-color-scheme: dark) { - .worldMapContainer { - background: #1a1a1a; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - } - - .header h2 { - color: #ffffff; - } - - .header p { - color: #b0b0b0; - } - - .mapContainer { - background: #2a2a2a; - border-color: #404040; - } - - .errorContainer { - background: #2a2a2a; - border-color: #404040; + .emptyStateContainer { + height: 350px; + padding: 16px; } - .errorContainer h3 { - color: #ff6b6b; + .emptyStateTitle { + font-size: 20px; } - .errorContainer p { - color: #b0b0b0; + .emptyStateSubtitle { + font-size: 14px; } - .loading { - background: rgba(26, 26, 26, 0.95); + .emptyStateFeatures { + flex-direction: column; + gap: 12px; } - .loading p { - color: #b0b0b0; + .emptyStateFeature { + min-width: auto; + width: 100%; } .legend { - background: #2a2a2a; - border-color: #404040; + padding: 12px; } - .legend h4 { - color: #ffffff; + .legendItems { + gap: 8px; } .legendItem { - color: #b0b0b0; + font-size: 12px; } } \ No newline at end of file diff --git a/frontend/src/pages/world_map.js b/frontend/src/pages/world_map.js index 29173b7..a0434c2 100644 --- a/frontend/src/pages/world_map.js +++ b/frontend/src/pages/world_map.js @@ -30,6 +30,10 @@ const WorldMapPapersPage = () => { const [showAdvanced, setShowAdvanced] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + // Search trigger state + const [triggerSearch, setTriggerSearch] = useState(false); + // Search results state + const [searchResults, setSearchResults] = useState([]); // Disclaimer state const [userInputs, setUserInputs] = useState([]); @@ -103,6 +107,7 @@ const WorldMapPapersPage = () => { const handleSearch = async () => { setLoading(true); setError(null); + setTriggerSearch(true); // Trigger the search // Track user inputs for disclaimer const inputs = []; @@ -162,13 +167,16 @@ const WorldMapPapersPage = () => { if (!response.ok) throw new Error('Failed to fetch search results'); const data = await response.json(); - // Pass the search results to WorldMapPapers component - // You might need to update the WorldMapPapers component to accept these results + // Store the search results for the world map + setSearchResults(data.results || []); } catch (e) { setError(e.message || 'Failed to fetch search results'); + setSearchResults([]); } finally { setLoading(false); + // Reset trigger after a short delay to allow the component to process + setTimeout(() => setTriggerSearch(false), 100); } }; @@ -270,6 +278,8 @@ const WorldMapPapersPage = () => { publicationYear={publicationYear} startYear={startYear} endYear={endYear} + triggerSearch={triggerSearch} + searchResults={searchResults} /> {/* Author Modal Dropdown */}