From 27b0dbf2481a6e7f910247ccb01e52aa2873b1c2 Mon Sep 17 00:00:00 2001 From: jekrch Date: Thu, 4 Jul 2024 16:06:06 -0500 Subject: [PATCH] add support for category comparison view --- src/components/modals/config/DisplayTab.tsx | 61 ++++++++++++++------- src/components/ranking/DetailsCard.tsx | 16 ++++-- src/redux/actions.ts | 7 ++- src/redux/reducers.ts | 7 ++- src/redux/types.ts | 6 ++ src/utilities/UrlUtil.ts | 42 ++++++++------ 6 files changed, 94 insertions(+), 45 deletions(-) diff --git a/src/components/modals/config/DisplayTab.tsx b/src/components/modals/config/DisplayTab.tsx index 69ed268..4f6a8f1 100644 --- a/src/components/modals/config/DisplayTab.tsx +++ b/src/components/modals/config/DisplayTab.tsx @@ -1,7 +1,7 @@ import React, { Dispatch, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from '../../../redux/types'; -import { setTheme, setVote, setContestants } from '../../../redux/actions'; +import { setTheme, setVote, setContestants, setShowComparison } from '../../../redux/actions'; import { assignVotesByCode, updateVoteTypeCode, voteCodeHasType } from '../../../utilities/VoteProcessor'; import { countries } from '../../../data/Countries'; import Dropdown from '../../Dropdown'; @@ -12,6 +12,7 @@ const DisplayTab: React.FC = () => { const dispatch: Dispatch = useDispatch(); const vote = useSelector((state: AppState) => state.vote); const theme = useSelector((state: AppState) => state.theme); + const showComparison = useSelector((state: AppState) => state.showComparison); const contestants = useSelector((state: AppState) => state.contestants); const year = useSelector((state: AppState) => state.year); @@ -34,7 +35,6 @@ const DisplayTab: React.FC = () => { return countries.filter((c) => c.key === sourceCountryKey)?.[0]?.name; }; - const [displayVoteSource, setDisplayVoteSource] = useState(() => getVoteSourceOption(vote)); const [voteDisplaySourceOptions, setVoteDisplaySourceOptions] = useState([ 'All', @@ -72,6 +72,17 @@ const DisplayTab: React.FC = () => { dispatch(setVote(newVote)); updateQueryParams({ v: newVote }); } + }; + + // Handle vote type input change + const onShowComparisonChange = (checked: boolean) => { + + updateQueryParams({cm: checked === true ? 't' : 'f'}) + dispatch( + setShowComparison(checked === true) + ); + + }; // Get vote source code from option @@ -119,11 +130,10 @@ const DisplayTab: React.FC = () => { return (
-
-

Show Votes

- -
+
+
+ Show Votes: {
- {'From'} + {'From:'} setDisplayVoteSource(s)} @@ -164,18 +174,29 @@ const DisplayTab: React.FC = () => {
-

Theme

- -
- onThemeInputChanged(v)} - options={['None', 'Auroral']} - showSearch={false} - /> + +
+
+
+ onShowComparisonChange(c)} + label="Show Category Comparisons" + /> +
+ Theme: + onThemeInputChanged(v)} + options={['None', 'Auroral']} + showSearch={false} + /> +
diff --git a/src/components/ranking/DetailsCard.tsx b/src/components/ranking/DetailsCard.tsx index a5ef972..f54dfaf 100644 --- a/src/components/ranking/DetailsCard.tsx +++ b/src/components/ranking/DetailsCard.tsx @@ -30,7 +30,9 @@ export interface DetailsCardProps { export const DetailsCard: FC = (props) => { const vote = useSelector((state: AppState) => state.vote); const categories = useSelector((state: AppState) => state.categories); + const activeCategory = useSelector((state: AppState) => state.activeCategory); const showTotalRank = useSelector((state: AppState) => state.showTotalRank); + const showComparison = useSelector((state: AppState) => state.showComparison); const contestant = props.countryContestant.contestant; const country = props.countryContestant.country; const categoryRankingsRef = useRef(null); @@ -42,7 +44,7 @@ export const DetailsCard: FC = (props) => { }, [props.categoryScrollPosition]); function getCategoryRankings() { - if (!showTotalRank) return undefined; + if (!showTotalRank && !showComparison) return undefined; return getCountryCategoryRankingsFromUrl(categories, country); } @@ -177,7 +179,7 @@ export const DetailsCard: FC = (props) => { }
- {showTotalRank && ( + {categories?.length && (showTotalRank || showComparison) && (
= (props) => {
{categories.map((category, index) => { - const categoryRankName = categoryRankings?.[category.name]; + if (!showTotalRank && index === activeCategory) { + return; + } + + const categoryRankIndex = categoryRankings?.[category.name]; var { arrowIcon, rankDifference } = getRankIconaAndDiff( - props.rank, categoryRankName + props.rank, categoryRankIndex ); return ( @@ -199,7 +205,7 @@ export const DetailsCard: FC = (props) => { title={`weight: ${category.weight}`} > {category.name}:{' '} - {categoryRankName || 'N/A'} + {categoryRankIndex || 'N/A'} {arrowIcon && ( { type: SET_NAME, payload: name } @@ -66,4 +67,8 @@ export const setActiveCategory = (index: number | undefined): SetActiveCategory export const setShowTotalRank = (show: boolean): SetShowTotalRank => ( { type: SET_SHOW_TOTAL_RANK, payload: show } +); + +export const setShowComparison = (show: boolean): SetShowComparison => ( + { type: SET_SHOW_COMPARISON, payload: show } ); \ No newline at end of file diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index f27e6c2..d1b8693 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -1,6 +1,6 @@ import { Category } from "../utilities/CategoryUtil"; import { CountryContestant } from '../data/CountryContestant'; -import { SET_NAME, SET_YEAR, SET_RANKED_ITEMS, SET_UNRANKED_ITEMS, SET_SHOW_UNRANKED, SET_CONTESTANTS, SET_IS_DELETE_MODE, SET_THEME, SET_VOTE, SET_HEADER_MENU_OPEN, SET_CATEGORIES, SET_ACTIVE_CATEGORY, SET_SHOW_TOTAL_RANK } from './actions'; +import { SET_NAME, SET_YEAR, SET_RANKED_ITEMS, SET_UNRANKED_ITEMS, SET_SHOW_UNRANKED, SET_CONTESTANTS, SET_IS_DELETE_MODE, SET_THEME, SET_VOTE, SET_HEADER_MENU_OPEN, SET_CATEGORIES, SET_ACTIVE_CATEGORY, SET_SHOW_TOTAL_RANK, SET_SHOW_COMPARISON } from './actions'; import { Action, AppState } from './types'; const initialState: AppState = { @@ -16,7 +16,8 @@ const initialState: AppState = { unrankedItems: [], categories: [], activeCategory: undefined, - showTotalRank: false + showTotalRank: false, + showComparison: false }; const rootReducer = (state = initialState, action: Action): AppState => { @@ -47,6 +48,8 @@ const rootReducer = (state = initialState, action: Action): AppState => { return { ...state, activeCategory: action.payload as number | undefined }; case SET_SHOW_TOTAL_RANK: return { ...state, showTotalRank: action.payload as boolean }; + case SET_SHOW_COMPARISON: + return { ...state, showComparison: action.payload as boolean }; default: return state; } diff --git a/src/redux/types.ts b/src/redux/types.ts index 1f081d7..0e4aad2 100644 --- a/src/redux/types.ts +++ b/src/redux/types.ts @@ -13,6 +13,7 @@ export interface AppState { categories: Category[]; activeCategory: number | undefined; showTotalRank: boolean; + showComparison: boolean; theme: string; vote: string; } @@ -85,4 +86,9 @@ export interface AppState { export interface SetShowTotalRank extends Action { type: 'SHOW_TOTAL_RANK'; payload: boolean; + } + + export interface SetShowComparison extends Action { + type: 'SHOW_COMPARISON'; + payload: boolean; } \ No newline at end of file diff --git a/src/utilities/UrlUtil.ts b/src/utilities/UrlUtil.ts index 4d60fd9..712aa99 100644 --- a/src/utilities/UrlUtil.ts +++ b/src/utilities/UrlUtil.ts @@ -1,25 +1,29 @@ import { Dispatch } from 'redux'; -import { setName, setYear, setRankedItems, setUnrankedItems, setContestants, setTheme, setVote } from '../redux/actions'; +import { setName, setYear, setRankedItems, setUnrankedItems, setContestants, setTheme, setVote, setShowComparison } from '../redux/actions'; import { fetchCountryContestantsByYear } from './ContestantRepository'; import { CountryContestant } from '../data/CountryContestant'; import { countries } from '../data/Countries'; import { defaultYear, sanitizeYear } from '../data/Contestants'; -import { getRankingComparison } from './RankAnalyzer'; + import { Category } from './CategoryUtil'; +export type UrlParams = { + rankingName: string | null; // n + contestYear: string | null; // y + rankings: string | null; // r + theme: string | null; // t: ab + voteCode: string | null; // v: {round}-{type}-{fromCountryKey} f-t-gb + comparisonMode: string | null; // cm: t/f +} + /** * Updates states based on extracted parameters using Redux. */ export const updateStates = ( - params: { - rankingName: string | null, - contestYear: string | null, - theme: string | null, - voteCode: string | null - }, + params: UrlParams, dispatch: Dispatch ) => { - let { rankingName, contestYear, theme, voteCode } = params; + let { rankingName, contestYear, theme, voteCode, comparisonMode } = params; if (rankingName) { dispatch( @@ -27,6 +31,10 @@ export const updateStates = ( ); } + dispatch( + setShowComparison(comparisonMode === 't') + ); + dispatch( setTheme(theme ?? "") ); @@ -149,7 +157,7 @@ export async function decodeRankingsFromURL( dispatch: Dispatch ): Promise { - const extractedParams = getUrlParams(activeCategory); + const extractedParams: UrlParams = getUrlParams(activeCategory); // console.log(activeCategory) // console.log(window.location.search) @@ -165,10 +173,9 @@ export async function decodeRankingsFromURL( ); }; -export function getUrlParams(activeCategory: number | undefined) { +export function getUrlParams(activeCategory: number | undefined): UrlParams { const params = new URLSearchParams(window.location.search); - const extractedParams = extractParams(params, activeCategory); - return extractedParams; + return extractParams(params, activeCategory); } export function getUrlParam(paramName: string): string | null { @@ -246,14 +253,15 @@ export function clearAllRankingParams(categories: Category[]) { window.history.replaceState(null, '', newUrl); } -export const extractParams = (params: URLSearchParams, activeCategory: number | undefined) => { +export const extractParams = (params: URLSearchParams, activeCategory: number | undefined): UrlParams => { return { rankingName: params.get('n'), contestYear: params.get('y'), rankings: params.get(`r${activeCategory !== undefined ? activeCategory + 1 : ''}`), - theme: params.get('t'), // e.g. ab - voteCode: params.get('v') // e.g. {round}-{type}-{fromCountryKey} f-t-gb - }; + theme: params.get('t'), // e.g. ab + voteCode: params.get('v'), // e.g. {round}-{type}-{fromCountryKey} f-t-gb + comparisonMode: params.get('cm') // e.g. t/f + } as UrlParams; }; export const updateUrlFromRankedItems = async (