From 6aa7495ff3e4e82d2f5ae18a718b73160ea5d03a Mon Sep 17 00:00:00 2001 From: Tero Tikkanen Date: Sat, 12 Oct 2024 23:33:08 +0300 Subject: [PATCH] Handle sorting of dynamically loaded cards --- components/paths/CategoryCard.tsx | 65 +++++++-- .../contentblocks/CategoryTypeListBlock.tsx | 135 +++++++++++------- 2 files changed, 134 insertions(+), 66 deletions(-) diff --git a/components/paths/CategoryCard.tsx b/components/paths/CategoryCard.tsx index f73fd108..0c3e3f96 100644 --- a/components/paths/CategoryCard.tsx +++ b/components/paths/CategoryCard.tsx @@ -1,3 +1,5 @@ +import { useEffect } from 'react'; + import { beautifyValue } from 'common/data/format'; import { Link } from 'common/links'; import ActionParameters from 'components/paths/ActionParameters'; @@ -56,7 +58,7 @@ const Identifier = styled.span` `; const PathsBasicNodeContent = (props) => { - const { node, pathsInstance } = props; + const { categoryId, node, pathsInstance, onLoaded } = props; const yearRange = useReactiveVar(yearRangeVar); const activeGoal = useReactiveVar(activeGoalVar); const t = useTranslations(); @@ -70,6 +72,25 @@ const PathsBasicNodeContent = (props) => { }, }); + useEffect(() => { + if (data) { + const nodeMetric = new DimensionalMetric(data.node.metricDim!); + const defaultConfig = nodeMetric.getDefaultSliceConfig(activeGoal); + const thisYear = nodeMetric.getSingleYear( + yearRange[1], + defaultConfig.categories + ); + + const yearTotal = + thisYear.rows[0] && + thisYear.rows.reduce( + (partialSum, a) => (a ? partialSum + a[0] : partialSum), + 0 + ); + onLoaded(categoryId, yearTotal || 0); + } + }, [activeGoal, data, yearRange]); + if (loading) { return ; } @@ -105,6 +126,7 @@ const PathsBasicNodeContent = (props) => { )?.label; const unit = nodeMetric.getUnit(); + return (
{nodeMetric.getName()}
@@ -126,7 +148,7 @@ const PathsBasicNodeContent = (props) => { }; const PathsActionNodeContent = (props) => { - const { node, pathsInstance } = props; + const { categoryId, node, pathsInstance, onLoaded } = props; const yearRange = useReactiveVar(yearRangeVar); const activeGoal = useReactiveVar(activeGoalVar); const t = useTranslations(); @@ -140,6 +162,14 @@ const PathsActionNodeContent = (props) => { }, }); + useEffect(() => { + if (data) { + const pathsAction = new PathsActionNode(data.action); + const impact = pathsAction.getYearlyImpact(yearRange[1]) || 0; + onLoaded(categoryId, impact); + } + }, [activeGoal, data, yearRange]); + if (loading) { return ; } @@ -148,15 +178,12 @@ const PathsActionNodeContent = (props) => { } if (data) { const pathsAction = new PathsActionNode(data.action); + const impact = pathsAction.getYearlyImpact(yearRange[1]) || 0; return ( {t('impact')} {yearRange[1]}

- {yearRange ? ( - beautifyValue(pathsAction.getYearlyImpact(yearRange[1])) - ) : ( - --- - )} + {yearRange ? beautifyValue(impact) : ---} {pathsAction.getUnit()}

@@ -167,7 +194,7 @@ const PathsActionNodeContent = (props) => { }; const PathsNodeContent = (props) => { - const { node, paths } = props; + const { categoryId, node, paths, onLoaded } = props; const { data, loading, error } = useQuery(GET_NODE_INFO, { fetchPolicy: 'no-cache', @@ -186,16 +213,30 @@ const PathsNodeContent = (props) => { } if (data) { if (data.node.__typename === 'ActionNode') { - return ; + return ( + + ); } else if (data.node.__typename) { - return ; + return ( + + ); } return null; } }; const CategoryCard = (props) => { - const { category, group, pathsInstance } = props; + const { category, group, pathsInstance, onLoaded } = props; return ( {group && ( @@ -220,8 +261,10 @@ const CategoryCard = (props) => { )} {category.kausalPathsNodeUuid && ( )} diff --git a/components/paths/contentblocks/CategoryTypeListBlock.tsx b/components/paths/contentblocks/CategoryTypeListBlock.tsx index d1f545d2..55a83e29 100644 --- a/components/paths/contentblocks/CategoryTypeListBlock.tsx +++ b/components/paths/contentblocks/CategoryTypeListBlock.tsx @@ -1,28 +1,21 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { MultiUseImageFragmentFragment } from 'common/__generated__/graphql'; import { CommonContentBlockProps } from 'common/blocks.types'; +import { useTranslations } from 'next-intl'; import { readableColor } from 'polished'; -import { Col, Container, Row } from 'reactstrap'; +import { Col, Container, FormGroup, Input, Label, Row } from 'reactstrap'; import styled from 'styled-components'; import { getDeepParents } from '@/common/categories'; -import { - activeGoalVar, - activeScenarioVar, - yearRangeVar, -} from '@/context/paths/cache'; import { usePaths } from '@/context/paths/paths'; import { usePlan } from '@/context/plan'; import { CATEGORY_FRAGMENT, RECURSIVE_CATEGORY_FRAGMENT, } from '@/fragments/category.fragment'; -import { GET_PATHS_ACTION_LIST } from '@/queries/paths/get-paths-actions'; -import { getHttpHeaders } from '@/utils/paths/paths.utils'; -import PathsActionNode from '@/utils/paths/PathsActionNode'; -import { gql, useQuery, useReactiveVar } from '@apollo/client'; +import { gql, useQuery } from '@apollo/client'; import { Theme } from '@kausal/themes/types'; import CategoryCard from '../CategoryCard'; @@ -34,6 +27,18 @@ const getColor = (theme: Theme, darkFallback = theme.themeColors.black) => const getBackgroundColor = (theme: Theme) => theme.section.categoryList?.background || theme.neutralLight; +const getSortOptions = (t: TFunction): SortActionsConfig[] => [ + { + key: 'STANDARD', + label: t('actions-sort-default'), + }, + { + key: 'IMPACT', + label: t('actions-sort-impact'), + sortKey: 'impactOnTargetYear', + }, +]; + const GET_CATEGORIES_FOR_CATEGORY_TYPE_LIST = gql` query GetCategoriesForCategoryTypeList($plan: ID!, $categoryType: ID!) { planCategories(plan: $plan, categoryType: $categoryType) { @@ -124,55 +129,76 @@ export type CategoryListBlockCategory = { }; }; -const getPathsActionForCategory = (category, actions) => { - const pathsActionForCategory = actions.find( - (action) => action.id === category.kausalPathsNodeUuid - ); - if (!pathsActionForCategory) { - return null; - } - return new PathsActionNode(pathsActionForCategory); -}; - -const getCategoryColor = (category) => { - // We have many ways to define the color of a category. - // For now get the color from category paths group data - return category.pathsAction?.data.group.color || '#eeeeee'; -}; - const CategoryList = (props) => { const { categories, groups } = props; - console.log('category list props', props); - const plan = usePlan(); const paths = usePaths(); - const activeGoal = useReactiveVar(activeGoalVar); - const activeScenario = useReactiveVar(activeScenarioVar); - const yearRange = useReactiveVar(yearRangeVar); - - const pathsInstance = paths.instance.id; - const { data, loading } = useQuery(GET_PATHS_ACTION_LIST, { - fetchPolicy: 'no-cache', - variables: { goal: null }, - context: { - uri: '/api/graphql-paths', - headers: getHttpHeaders({ instanceIdentifier: pathsInstance }), - }, - }); + const t = useTranslations(); - if (loading) { - return
Loading...
; // Or any loading indicator you prefer - } + const pathsInstance = paths?.instance.id; + const sortOptions = getSortOptions(t); - const categoryData = categories?.map((cat) => { - const pathsAction = getPathsActionForCategory(cat, data.actions); - return { - ...cat, - pathsAction: pathsAction, - }; - }); + const [sortBy, setSortBy] = useState(sortOptions[0]); + const [categoriesData, setCategoriesData] = useState(categories); + const [categoriesPathsData, setCategoriesPathsData] = useState( + categories.map((cat) => { + return { id: cat.id, impact: null }; + }) + ); + + const handleCardLoaded = (id, impact) => { + //console.log('handleCardLoaded', id, impact); + setCategoriesPathsData((prevCategories) => { + const updatedCategories = [...prevCategories]; + const index = updatedCategories.findIndex((cat) => cat.id === id); + if (index !== -1) { + updatedCategories[index].impact = impact; + } + return updatedCategories; + }); + console.log('categoriesPathsData', categoriesPathsData); + //setLoadedCards(prev => ({ ...prev, [id]: impact })); + }; + + const handleChangeSort = (sortBy: SortActionsBy) => { + console.log('sorting', sortBy); + const selectedSorter = sortOptions.find((option) => option.key === sortBy); + setSortBy(selectedSorter ?? sortOptions[0]); + const sortedCategories = [...categoriesData].sort(sortCategories); + console.log('sorted', sortedCategories); + setCategoriesData(sortedCategories); + }; + + const sortCategories = (a, b) => { + if (sortBy.key === 'IMPACT') { + const aValue = categoriesPathsData.find((cat) => cat.id === a.id)?.impact; + const bValue = categoriesPathsData.find((cat) => cat.id === b.id)?.impact; + console.log('aValue', aValue, 'bValue', bValue); + return aValue - bValue; + } + return 0; + }; return ( <> + + + handleChangeSort(e.target.value as SortActionsBy)} + value={sortBy.key} + > + {sortOptions.map( + (sortOption) => + !sortOption.isHidden && ( + + ) + )} + + {groups?.map((group) => ( {group?.id !== 'all' && ( @@ -180,7 +206,7 @@ const CategoryList = (props) => { {group.name} )} - {categoryData + {categoriesData ?.filter( (cat) => (cat?.categoryPage?.live && hasParent(cat, group.id)) || @@ -201,6 +227,7 @@ const CategoryList = (props) => { category={cat} group={group} pathsInstance={pathsInstance} + onLoaded={handleCardLoaded} /> ) @@ -272,8 +299,6 @@ const CategoryTypeListBlock = (props: CategoryTypeListBlockProps) => { return ( -
[FILTERS HERE]
[SORTING HERE]
-
-----
{heading &&

{heading}

}