From 8150e95e2145965267adf944fd27431ae7e7aeb0 Mon Sep 17 00:00:00 2001 From: Jijeong Lee Date: Sat, 4 May 2024 17:24:19 -0700 Subject: [PATCH] delete dummy data and process survey data from server --- www/js/TimelineContext.ts | 5 ++ www/js/metrics/SurveyDoughnutCharts.tsx | 14 ++-- www/js/metrics/SurveyLeaderboardCard.tsx | 93 +++++++++++++++++---- www/js/metrics/SurveyTripCategoriesCard.tsx | 60 ++++++------- 4 files changed, 113 insertions(+), 59 deletions(-) diff --git a/www/js/TimelineContext.ts b/www/js/TimelineContext.ts index 25e015a4d..525eec90b 100644 --- a/www/js/TimelineContext.ts +++ b/www/js/TimelineContext.ts @@ -55,6 +55,8 @@ type ContextProps = { refreshTimeline: () => void; shouldUpdateTimeline: Boolean; setShouldUpdateTimeline: React.Dispatch>; + lastUpdateMetricDateTime: number; + setLastUpdateMetricDateTime: React.Dispatch>; }; export const useTimelineContext = (): ContextProps => { @@ -78,6 +80,7 @@ export const useTimelineContext = (): ContextProps => { // Leaflet map encounters an error when prerendered, so we need to render the TimelineScrollList component when the active tab is 'label' // 'shouldUpdateTimeline' gets updated based on the current tab index, and we can use it to determine whether to render the timeline or not const [shouldUpdateTimeline, setShouldUpdateTimeline] = useState(true); + const [lastUpdateMetricDateTime, setLastUpdateMetricDateTime] = useState(0); // initialization, once the appConfig is loaded useEffect(() => { @@ -372,6 +375,8 @@ export const useTimelineContext = (): ContextProps => { addUserInputToEntry, shouldUpdateTimeline, setShouldUpdateTimeline, + lastUpdateMetricDateTime, + setLastUpdateMetricDateTime, }; }; diff --git a/www/js/metrics/SurveyDoughnutCharts.tsx b/www/js/metrics/SurveyDoughnutCharts.tsx index fd67325e5..aa6311886 100644 --- a/www/js/metrics/SurveyDoughnutCharts.tsx +++ b/www/js/metrics/SurveyDoughnutCharts.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { useAppTheme } from '../appTheme'; import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; import { Doughnut } from 'react-chartjs-2'; +import { SurveyComparison } from './SurveyLeaderboardCard'; ChartJS.register(ArcElement, Tooltip, Legend); @@ -25,12 +26,13 @@ export const LabelPanel = ({ first, second }) => { ); }; -const SurveyDoughnutCharts = () => { +type Props = { + surveyComparison : SurveyComparison +} + +const SurveyDoughnutCharts = ({surveyComparison} : Props) => { const { colors } = useAppTheme(); const { t } = useTranslation(); - const myResonseRate = 68; - const othersResponseRate = 41; - const renderDoughnutChart = (rate, chartColor, myResponse) => { const data = { datasets: [ @@ -76,8 +78,8 @@ const SurveyDoughnutCharts = () => { {t('main-metrics.survey-response-rate')} - {renderDoughnutChart(myResonseRate, colors.navy, true)} - {renderDoughnutChart(othersResponseRate, colors.orange, false)} + {renderDoughnutChart(surveyComparison.me, colors.navy, true)} + {renderDoughnutChart(surveyComparison.others, colors.orange, false)} diff --git a/www/js/metrics/SurveyLeaderboardCard.tsx b/www/js/metrics/SurveyLeaderboardCard.tsx index 08430c56d..57b991df1 100644 --- a/www/js/metrics/SurveyLeaderboardCard.tsx +++ b/www/js/metrics/SurveyLeaderboardCard.tsx @@ -1,47 +1,104 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { View, Text } from 'react-native'; import { Card } from 'react-native-paper'; -import { cardStyles } from './MetricsTab'; +import { cardStyles, SurveyMetric, SurveyObject } from './MetricsTab'; import { useTranslation } from 'react-i18next'; import ToggleSwitch from '../components/ToggleSwitch'; import BarChart from '../components/BarChart'; import { useAppTheme } from '../appTheme'; import SurveyComparisonChart from './SurveyDoughnutCharts'; +import { Chart as ChartJS, registerables } from 'chart.js'; +import Annotation from 'chartjs-plugin-annotation'; -const SurveyLeaderboardCard = () => { +ChartJS.register(...registerables, Annotation); + +type Props = { + surveyMetric: SurveyMetric +} + +type LeaderboardRecord = { + label: string, + x: number, + y: string +} +export type SurveyComparison = { + 'me' : number, + 'others' : number, +} + +const SurveyLeaderboardCard = ( { surveyMetric }: Props) => { const { colors } = useAppTheme(); const { t } = useTranslation(); const [tab, setTab] = useState('leaderboard'); - const myLabel = '#3'; + + const myRank = surveyMetric.me.rank; + const mySurveyMetric = surveyMetric.me.overview; + const othersSurveyMetric = surveyMetric.others.overview; + const mySurveyRate = Math.round(mySurveyMetric.answered / (mySurveyMetric.answered + mySurveyMetric.unanswered) * 100); + + const surveyComparison: SurveyComparison = { + 'me' : mySurveyRate, + 'others' : Math.round(othersSurveyMetric.answered / (othersSurveyMetric.answered + othersSurveyMetric.unanswered) * 100) + } + + function getLabel(rank: number): string { + if(rank === 0) { + return '🏆 #1:'; + }else if(rank === 1) { + return '🥈 #2:'; + }else if(rank === 2) { + return '🥉 #3:'; + }else { + return `#${rank+1}:`; + } + } + + const leaderboardRecords: LeaderboardRecord[] = useMemo(() => { + const combinedLeaderboard:SurveyObject[] = [...surveyMetric.others.leaderboard]; + combinedLeaderboard.splice(myRank, 0, mySurveyMetric); + // This is to prevent the leaderboard from being too long for UX purposes. + // For a total of 20 members, we only show the top 5 members, myself, and the bottom 3 members. + const numberOfTopUsers = 5 + const numberOfBottomUsers = surveyMetric.others.leaderboard.length -3; + + return combinedLeaderboard.map((item, idx) => ( + { + 'isMe': idx === myRank, + 'rank': idx, + 'answered': item.answered, + 'unanswered': item.unanswered, + 'mismatched': item.mismatched, + } + )).filter((item) => ( item.isMe || item.rank < numberOfTopUsers || item.rank >= numberOfBottomUsers)) + .map((item) => ( + { + label: item.isMe ? `${item.rank}-me` : `${item.rank}-other`, + x: Math.round(item.answered / (item.answered + item.unanswered) * 100), + y: getLabel(item.rank) + } + )) + }, [surveyMetric]) + const renderBarChart = () => { - const records = [ - { label: '#1', x: 91, y: '🏆 #1:' }, - { label: '#2', x: 72, y: '🥈 #2:' }, - { label: '#3', x: 68, y: '🥉 #3:' }, - { label: '#4', x: 57, y: '#4:' }, - { label: '#5', x: 50, y: '#5:' }, - { label: '#6', x: 40, y: '#6:' }, - { label: '#7', x: 30, y: '#7:' }, - ]; return ( {t('main-metrics.survey-response-rate')} (l === myLabel ? colors.skyblue : colors.silver)} - getColorForChartEl={(l) => (l === myLabel ? colors.skyblue : colors.silver)} + getColorForLabel={(l) => (l === `${myRank}-me` ? colors.skyblue : colors.silver)} + getColorForChartEl={(l) => (l === `${myRank}-me` ? colors.skyblue : colors.silver)} showLegend={false} reverse={false} enableTooltip={false} /> {t('main-metrics.you-are-in')} - {myLabel} + #{myRank+1} {t('main-metrics.place')} @@ -74,7 +131,7 @@ const SurveyLeaderboardCard = () => { )} /> - {tab === 'leaderboard' ? renderBarChart() : } + {tab === 'leaderboard' ? renderBarChart() : } ); diff --git a/www/js/metrics/SurveyTripCategoriesCard.tsx b/www/js/metrics/SurveyTripCategoriesCard.tsx index aee33b057..4abac3cf4 100644 --- a/www/js/metrics/SurveyTripCategoriesCard.tsx +++ b/www/js/metrics/SurveyTripCategoriesCard.tsx @@ -1,47 +1,37 @@ import React from 'react'; -import { View, Text } from 'react-native'; import { Card } from 'react-native-paper'; -import { cardStyles } from './MetricsTab'; +import { cardStyles, SurveyObject } from './MetricsTab'; import { useTranslation } from 'react-i18next'; import BarChart from '../components/BarChart'; import { useAppTheme } from '../appTheme'; import { LabelPanel } from './SurveyDoughnutCharts'; -const SurveyTripCategoriesCard = () => { +type SurveyTripRecord = { + label: string, + x: string, + y: number +} + +type Props = { + surveyTripCategoryMetric : {[key: string]: SurveyObject} +} +const SurveyTripCategoriesCard = ( {surveyTripCategoryMetric}: Props ) => { const { colors } = useAppTheme(); const { t } = useTranslation(); - const records = [ - { - label: 'Response', - x: 'EV Roaming trip', - y: 20, - }, - { - label: 'No Response', - x: 'EV Roaming trip', - y: 20, - }, - { - label: 'Response', - x: 'EV Return trip', - y: 30, - }, - { - label: 'No Response', - x: 'EV Return trip', - y: 40, - }, - { - label: 'Response', - x: 'Gas Car trip', - y: 50, - }, - { - label: 'No Response', - x: 'Gas Car trip', - y: 10, - }, - ]; + const records: SurveyTripRecord[] = []; + + for(const category in surveyTripCategoryMetric) { + const metricByCategory = surveyTripCategoryMetric[category]; + for(const key in metricByCategory) { + // we don't consider "mismatched" survey result for now + if(key === "mismatched") continue; + records.push({ + label: key === 'answered' ? 'Response' : 'No Response', + x: category, + y: metricByCategory[key] + }) + } + } return (