From a17b66c078529874177dbf5ec8606c3d04de97bd Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Thu, 22 May 2025 12:48:15 +0200 Subject: [PATCH] Enhances data handling and geographic mapping Refactors dashboard to use actual metrics for country data, removing dummy data for improved accuracy. Integrates the country-code-lookup package for geographic mapping, adding comprehensive country coordinates. Increases performance and data validation across API endpoints and adjusts WordCloud component size for better visualization. Enhances session handling with improved validation logic, and updates configuration for allowed origins. --- app/dashboard/page.tsx | 34 ++-- app/dashboard/sessions/page.tsx | 1 + components/DonutChart.tsx | 9 +- components/GeographicMap.tsx | 82 +++++--- lib/metrics.ts | 2 +- lib/types.ts | 190 +++++++++--------- next.config.js | 7 +- package-lock.json | 8 + package.json | 2 + pages/api/dashboard/session-filter-options.ts | 114 ++++++----- pages/api/dashboard/sessions.ts | 118 +++++------ 11 files changed, 316 insertions(+), 251 deletions(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index bff9601..1142657 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -142,24 +142,22 @@ function DashboardContent() { return metrics.wordCloudData; }; - // Function to prepare country data for the map - using simulated/dummy data + // Function to prepare country data for the map using actual metrics const getCountryData = () => { - return { - US: 42, - GB: 25, - DE: 18, - FR: 15, - CA: 12, - AU: 10, - JP: 8, - BR: 6, - IN: 5, - ZA: 3, - ES: 7, - NL: 9, - IT: 6, - SE: 4, - }; + if (!metrics || !metrics.countries) return {}; + + // Convert the countries object from metrics to the format expected by GeographicMap + const result = Object.entries(metrics.countries).reduce( + (acc, [code, count]) => { + if (code && count) { + acc[code] = count; + } + return acc; + }, + {} as Record + ); + + return result; }; // Function to prepare response time distribution data @@ -378,7 +376,7 @@ function DashboardContent() {

Transcript Word Cloud

- +

diff --git a/app/dashboard/sessions/page.tsx b/app/dashboard/sessions/page.tsx index c0db957..90a3945 100644 --- a/app/dashboard/sessions/page.tsx +++ b/app/dashboard/sessions/page.tsx @@ -40,6 +40,7 @@ export default function SessionsPage() { // Pagination states const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(0); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [pageSize, setPageSize] = useState(10); // Or make this configurable useEffect(() => { diff --git a/components/DonutChart.tsx b/components/DonutChart.tsx index 54f1710..25d0501 100644 --- a/components/DonutChart.tsx +++ b/components/DonutChart.tsx @@ -92,12 +92,17 @@ export default function DonutChart({ data, centerText }: DonutChartProps) { { id: "centerText", beforeDraw: function (chart: any) { - const width = chart.width; const height = chart.height; const ctx = chart.ctx; ctx.restore(); - const centerX = width / 2; + // Calculate the actual chart area width (excluding legend) + // Legend is positioned on the right, so we adjust the center X coordinate + const chartArea = chart.chartArea; + const chartWidth = chartArea.right - chartArea.left; + + // Get the center of just the chart area (not including the legend) + const centerX = chartArea.left + chartWidth / 2; const centerY = height / 2; // Title text diff --git a/components/GeographicMap.tsx b/components/GeographicMap.tsx index a0eb99e..3cbef2d 100644 --- a/components/GeographicMap.tsx +++ b/components/GeographicMap.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import "leaflet/dist/leaflet.css"; +import countryLookup from "country-code-lookup"; // Define types for country data interface CountryData { @@ -17,25 +18,41 @@ interface GeographicMapProps { height?: number; // Optional height for the container } -// Default coordinates for commonly used countries (latitude, longitude) -const DEFAULT_COORDINATES: Record = { - US: [37.0902, -95.7129], - GB: [55.3781, -3.436], - DE: [51.1657, 10.4515], - FR: [46.2276, 2.2137], - CA: [56.1304, -106.3468], - AU: [-25.2744, 133.7751], - JP: [36.2048, 138.2529], - BR: [-14.235, -51.9253], - IN: [20.5937, 78.9629], - ZA: [-30.5595, 22.9375], - ES: [40.4637, -3.7492], - NL: [52.1326, 5.2913], - IT: [41.8719, 12.5674], - SE: [60.1282, 18.6435], - // Add more country coordinates as needed +// Get country coordinates from the country-code-lookup package +const getCountryCoordinates = (): Record => { + // Initialize with some fallback coordinates for common countries that might be missing + const coordinates: Record = { + // These are just in case the lookup fails for common countries + US: [37.0902, -95.7129], + GB: [55.3781, -3.436], + BA: [43.9159, 17.6791], + }; + + try { + // Get all countries from the package + const allCountries = countryLookup.countries; + + // Map through all countries and extract coordinates + allCountries.forEach((country) => { + if (country.iso2 && country.latitude && country.longitude) { + coordinates[country.iso2] = [ + parseFloat(country.latitude), + parseFloat(country.longitude), + ]; + } + }); + + return coordinates; + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error loading country coordinates:", error); + return coordinates; + } }; +// Load coordinates once when module is imported +const DEFAULT_COORDINATES = getCountryCoordinates(); + // Dynamically import the Map component to avoid SSR issues // This ensures the component only loads on the client side const Map = dynamic(() => import("./Map"), { @@ -68,9 +85,15 @@ export default function GeographicMap({ // Generate CountryData array for the Map component const data: CountryData[] = Object.entries(countries) // Only include countries with known coordinates - .filter( - ([code]) => countryCoordinates[code] || DEFAULT_COORDINATES[code] - ) + .filter(([code]) => { + // If no coordinates found, log to help with debugging + if (!countryCoordinates[code] && !DEFAULT_COORDINATES[code]) { + // eslint-disable-next-line no-console + console.warn(`Missing coordinates for country code: ${code}`); + return false; + } + return true; + }) .map(([code, count]) => ({ code, count, @@ -78,6 +101,12 @@ export default function GeographicMap({ DEFAULT_COORDINATES[code] || [0, 0], })); + // Log for debugging + // eslint-disable-next-line no-console + console.log( + `Found ${data.length} countries with coordinates out of ${Object.keys(countries).length} total countries` + ); + setCountryData(data); } catch (error) { // eslint-disable-next-line no-console @@ -86,8 +115,9 @@ export default function GeographicMap({ } }, [countries, countryCoordinates, isClient]); - // Find the max count for scaling circles - const maxCount = Math.max(...Object.values(countries), 1); + // Find the max count for scaling circles - handle empty countries object + const countryValues = Object.values(countries); + const maxCount = countryValues.length > 0 ? Math.max(...countryValues, 1) : 1; // Show loading state during SSR or until client-side rendering takes over if (!isClient) { @@ -100,7 +130,13 @@ export default function GeographicMap({ return (
- + {countryData.length > 0 ? ( + + ) : ( +
+ No geographic data available +
+ )}