diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 32a387a61..1f5b8ffa6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,9 +34,9 @@ jobs: } ] extra_plugins: | - "@semantic-release/commit-analyzer" + "@semantic-release/commit-analyzer@9.0.2" "@semantic-release/release-notes-generator@10.0.3" - "@semantic-release/github" + "@semantic-release/github@8.1.0" env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} RUNNER_DEBUG: 1 diff --git a/public/gflogo.png b/public/gflogo.png index 8514310c3..d2b31d88c 100644 Binary files a/public/gflogo.png and b/public/gflogo.png differ diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx index b02fa0248..7b191771a 100644 --- a/src/app/Providers.tsx +++ b/src/app/Providers.tsx @@ -30,7 +30,7 @@ function Providers(props: ProviderProps) { maxWidth="lg" css={` padding: 0 24px; - min-height: calc(100vh - 48px); + min-height: calc(100vh - 45px); @media (max-width: 767px) { padding: 0 16px; diff --git a/src/app/Routes.tsx b/src/app/Routes.tsx index c477ebcce..2fd3e8d90 100755 --- a/src/app/Routes.tsx +++ b/src/app/Routes.tsx @@ -71,7 +71,9 @@ function GrantPeriodRedirect(props: RouteComponentProps) { export function MainRoutes() { useClearDataPathStepsOnDatasetChange(); - useFilterOptions({}); + useFilterOptions({ + loadFilterOptions: true, + }); useScrollToTop(); useUrlFilters(); useGA(); diff --git a/src/app/assets/icons/HIV.tsx b/src/app/assets/icons/HIV.tsx new file mode 100644 index 000000000..3a4e78922 --- /dev/null +++ b/src/app/assets/icons/HIV.tsx @@ -0,0 +1,37 @@ +import React from "react"; + +export const HIV = () => ( + + + + + + + + + +); diff --git a/src/app/assets/icons/Malaria.tsx b/src/app/assets/icons/Malaria.tsx new file mode 100644 index 000000000..b14d1f818 --- /dev/null +++ b/src/app/assets/icons/Malaria.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +export const Malaria = () => ( + + + + + + + + + +); diff --git a/src/app/assets/icons/TB.tsx b/src/app/assets/icons/TB.tsx new file mode 100644 index 000000000..f6c0d5a32 --- /dev/null +++ b/src/app/assets/icons/TB.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +export const Tuberculosis = () => ( + + + + + + + + + +); diff --git a/src/app/components/AppBar/index.tsx b/src/app/components/AppBar/index.tsx index 8fdec2aba..d02c1f8b7 100644 --- a/src/app/components/AppBar/index.tsx +++ b/src/app/components/AppBar/index.tsx @@ -175,8 +175,9 @@ export function AppBar() { css={` gap: 32px; width: 100%; - height: 48px; + height: 45px; display: flex; + min-height: 45px; flex-direction: row; align-items: center; justify-content: space-between; @@ -299,6 +300,8 @@ export function AppBar() { setOpenSearch(!openSearch)} css={` + width: 45px; + height: 45px; margin-right: -12px; `} > @@ -333,6 +336,8 @@ export function AppBar() { href="https://github.com/globalfund/data-explorer-client" css={` right: 0; + width: 45px; + height: 45px; position: absolute; path { diff --git a/src/app/components/Charts/Budgets/Flow/index.tsx b/src/app/components/Charts/Budgets/Flow/index.tsx index fb7ebc3c6..a7265b9d7 100644 --- a/src/app/components/Charts/Budgets/Flow/index.tsx +++ b/src/app/components/Charts/Budgets/Flow/index.tsx @@ -1,8 +1,6 @@ import React from "react"; import get from "lodash/get"; -import sumBy from "lodash/sumBy"; import uniqBy from "lodash/uniqBy"; -import filter from "lodash/filter"; import { appColors } from "app/theme"; import Grid from "@material-ui/core/Grid"; import { css } from "styled-components/macro"; diff --git a/src/app/components/Charts/Eligibility/Scatterplot/data.ts b/src/app/components/Charts/Eligibility/Scatterplot/data.ts index 402a17dc5..076c8dc13 100644 --- a/src/app/components/Charts/Eligibility/Scatterplot/data.ts +++ b/src/app/components/Charts/Eligibility/Scatterplot/data.ts @@ -21,6 +21,7 @@ export interface EligibilityScatterplotDataItemModel { } export interface EligibilityScatterplotDataModel extends Serie { + id: string; data: EligibilityScatterplotDataItemModel[]; } diff --git a/src/app/components/Charts/Grants/index.tsx b/src/app/components/Charts/Grants/index.tsx index bf973d23c..3ecce3574 100644 --- a/src/app/components/Charts/Grants/index.tsx +++ b/src/app/components/Charts/Grants/index.tsx @@ -15,6 +15,7 @@ import Button from "@material-ui/core/Button"; import CloseIcon from "@material-ui/icons/Close"; import { useCMSData } from "app/hooks/useCMSData"; import IconButton from "@material-ui/core/IconButton"; +import { useStoreActions } from "app/state/store/hooks"; import { isTouchDevice } from "app/utils/isTouchDevice"; import useMousePosition from "app/hooks/useMousePosition"; import useMediaQuery from "@material-ui/core/useMediaQuery"; @@ -63,6 +64,9 @@ export function GrantsViz(props: GrantsVizProps) { component: string; rating: string | null; } | null>(null); + const clearDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.clear + ); const components = uniq(data.map((item: any) => item.component)); const yearItemWidth = (width - 120) / 2 / datayears.length; const allValues: number[] = []; @@ -136,11 +140,15 @@ export function GrantsViz(props: GrantsVizProps) { {(isMobile || isTouchDevice()) && ( + + ); + })} + + + + {props.rows.map((row: SimpleTableRow, index: number) => ( + + ))} + + + +
+
); } diff --git a/src/app/components/Table/funding/index.tsx b/src/app/components/Table/funding/index.tsx new file mode 100644 index 000000000..1a68086c1 --- /dev/null +++ b/src/app/components/Table/funding/index.tsx @@ -0,0 +1,562 @@ +import React from "react"; +import { v4 } from "uuid"; +import get from "lodash/get"; +import find from "lodash/find"; +import filter from "lodash/filter"; +import { appColors } from "app/theme"; +import { Link } from "react-router-dom"; +import Menu from "@material-ui/core/Menu"; +import Table from "@material-ui/core/Table"; +import Button from "@material-ui/core/Button"; +import MenuItem from "@material-ui/core/MenuItem"; +import Collapse from "@material-ui/core/Collapse"; +import TableRow from "@material-ui/core/TableRow"; +import TableHead from "@material-ui/core/TableHead"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import IconButton from "@material-ui/core/IconButton"; +import { makeStyles } from "@material-ui/core/styles"; +import ArrowUpward from "@material-ui/icons/ArrowUpward"; +import ArrowDownward from "@material-ui/icons/ArrowDownward"; +import TableContainer from "@material-ui/core/TableContainer"; +import { tablecell } from "app/components/Table/Simple/styles"; +import IconChevronRight from "app/assets/icons/IconChevronRight"; +import { CloudDownloadIcon } from "app/assets/icons/CloudDownload"; +import { TableToolbar } from "app/components/Table/Expandable/Toolbar"; +import { TableToolbarCols } from "app/components/Table/Expandable/data"; +import { + cellData, + cellData2, +} from "app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/data"; + +export interface FundingTableRow { + [key: string]: any; + children?: FundingTableRow[]; +} +export interface FundingTableColumn { + key: string; + name: string; + col?: FundingTableColumn[]; +} +export interface FundingTableProps { + title: string; + light?: boolean; + search: string; + sortBy: string; + paddingLeft: number; + forceExpand?: boolean; + rows: FundingTableRow[]; + columns: FundingTableColumn[]; + onSearchChange: (value: string) => void; + onSortByChange: (value: string) => void; +} + +const FRIconDownload = (props: { + documents: { + url: string; + lang: string; + }[]; +}) => { + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + event.stopPropagation(); + setAnchorEl(event.currentTarget); + }; + + const handleClose = (event: React.MouseEvent) => { + event.stopPropagation(); + setAnchorEl(null); + }; + + const handleScroll = () => { + setAnchorEl(null); + }; + + React.useEffect(() => { + document.addEventListener("scroll", handleScroll); + return () => document.removeEventListener("scroll", handleScroll); + }, []); + + if (props.documents.length === 0) { + return ; + } + + return ( + + + + + + {props.documents.map((item) => ( + + {item.lang} + + ))} + + + ); +}; + +const useRowStyles = makeStyles({ + root: { + "& > *": { + userSelect: "none", + borderBottom: "1px solid #DFE3E6", + }, + }, +}); + +function Row(props: { + row: FundingTableRow; + index: number; + columns: FundingTableColumn[]; + paddingLeft: number; + visibleColumnsIndexes: number[]; + forceExpand?: boolean; +}) { + const classes = useRowStyles(); + const [open, setOpen] = React.useState(props.forceExpand); + + const [rowSelected, setRowSelected] = React.useState( + props.forceExpand ? "child" : "parent" + ); + + const secondLevelRow = !find(props.columns, { key: "name" }); + + const thirdLevelRow = secondLevelRow && !find(props.columns, { key: "date" }); + + return ( + + {thirdLevelRow && props.index === 0 && ( + + {cellData2.map((name, index) => ( + +
+
* { + @supports (-webkit-touch-callout: none) and + (not (translate: none)) { + &:not(:last-child) { + margin-right: 12px; + } + } + } + + > svg { + transition: transform 0.1s ease-in-out; + transform: rotate(${open ? "0deg" : "-180deg"}); + } + `} + > + {name} +
+
+
+ ))} +
+ )} + { + // padVal = padVal + 4; + if (props.row.children) { + setOpen(!open); + } + if ( + props.row.children && + props.row.children.findIndex((children) => children.children) > -1 + ) { + setRowSelected("child"); + } + }} + css={` + transition: background 0.2s ease-in-out; + background: ${props.row.children ? "#fff" : "#f5f5f7"}; + ${thirdLevelRow ? "background: #fafafa;" : ""} + + ${props.row.children + ? ` + :hover { + cursor: pointer; + background: #262C34; + + > td { + color: #fff; + } + + path { + fill: #fff; + } + } + ` + : ""}; + `} + > + {props.columns.map((column: FundingTableColumn, index: number) => ( + +
+
* { + @supports (-webkit-touch-callout: none) and + (not (translate: none)) { + &:not(:last-child) { + margin-right: 12px; + } + } + } + + > svg { + transition: transform 0.1s ease-in-out; + transform: rotate(${open ? "-90deg" : "90deg"}); + + > path { + fill: ${appColors.TABLE.ROW_TEXT_COLOR}; + } + } + `} + > + {index === 0 && + props.row.children && + props.row.children.length > 0 && } + {column.key !== "grant" && + column.key !== "documents" && + get(props.row, column.key, "")} + {column.key === "grant" && ( + + {get(props.row, column.key, "")} + + )} + {column.key === "documents" && ( + + )} +
+
+
+ ))} +
+ + + + + + {rowSelected === "child" && !secondLevelRow && ( + + {cellData.map((name, index) => ( + +
+
* { + @supports (-webkit-touch-callout: none) and + (not (translate: none)) { + &:not(:last-child) { + margin-right: 12px; + } + } + } + + > svg { + transition: transform 0.1s ease-in-out; + transform: rotate(${open ? "0deg" : "-180deg"}); + } + `} + > + {name} +
+
+
+ ))} +
+ )} + {props.row.children && + props.row.children.map( + (child: FundingTableRow, index: number) => + ( + filter( + props.columns, + (c) => c.col + ) as FundingTableColumn[] + ).map((childCol) => { + return ( + + ); + }) + )} +
+
+
+
+
+
+ ); +} + +export function FundingRequestTable(props: FundingTableProps) { + const sortBySplits = props.sortBy.split(" "); + + const [toolbarCols, setToolbarCols] = React.useState([]); + + function onColumnViewSelectionChange(e: React.ChangeEvent) { + const updatedToolbarCols = [...toolbarCols]; + if (updatedToolbarCols[parseInt(e.target.value, 10)]) { + updatedToolbarCols[parseInt(e.target.value, 10)].checked = + e.target.checked; + setToolbarCols(updatedToolbarCols); + } + } + + function onSortByChange(value: string) { + if (sortBySplits.length > 1) { + if (sortBySplits[0] === value) { + if (sortBySplits[1] === "ASC") { + props.onSortByChange(`${value} DESC`); + } else { + props.onSortByChange(`${value} ASC`); + } + } else { + props.onSortByChange(`${value} ASC`); + } + } else { + props.onSortByChange(`${value} ASC`); + } + } + + React.useEffect(() => { + setToolbarCols( + props.columns.map((c, index) => ({ name: c.name, checked: true, index })) + ); + }, [props.columns]); + + const visibleColumnsIndexes = filter(toolbarCols, { checked: true }).map( + (c) => c.index + ); + + return ( +
div:first-of-type { + font-size: 14px; + } + } + `} + > + + + + + + {props.columns.map( + (column: FundingTableColumn, index: number) => { + let icon = undefined; + if ( + sortBySplits.length > 1 && + sortBySplits[0] === column.key + ) { + if (sortBySplits[1] === "DESC") { + icon = ; + } else { + icon = ; + } + } + return ( + button { + ${tablecell} + padding-left: 0; + text-transform: none; + + > span { + font-size: 12px; + font-weight: bold; + justify-content: flex-start; + font-family: "GothamNarrow-Bold", "Helvetica Neue", + sans-serif; + } + } + `} + > + + + ); + } + )} + + + + {props.rows.map((row: FundingTableRow, index: number) => ( + + ))} + +
+
+
+
+ ); +} diff --git a/src/app/components/Table/funding/styles.ts b/src/app/components/Table/funding/styles.ts new file mode 100644 index 000000000..f09cd09d1 --- /dev/null +++ b/src/app/components/Table/funding/styles.ts @@ -0,0 +1,8 @@ +import { css } from "styled-components/macro"; + +export const tablecell = css` + color: #262c34; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.5px; +`; diff --git a/src/app/components/ToolBoxPanel/components/allocationsperiods/index.tsx b/src/app/components/ToolBoxPanel/components/allocationsperiods/index.tsx index 10d9ccb6d..2940692e0 100644 --- a/src/app/components/ToolBoxPanel/components/allocationsperiods/index.tsx +++ b/src/app/components/ToolBoxPanel/components/allocationsperiods/index.tsx @@ -11,6 +11,24 @@ export function AllocationsPeriods() { (state) => get(state.AllocationsPeriods.data, "data", []) as string[] ); + const [periodList, setPeriodList] = React.useState([ + { label: "All", value: "All" }, + ...dataPeriodOptions.map((period: string) => ({ + label: period, + value: period, + })), + ]); + + React.useEffect(() => { + setPeriodList([ + { label: "All", value: "All" }, + ...dataPeriodOptions.map((period: string) => ({ + label: period, + value: period, + })), + ]); + }, []); + const selectedPeriod = useStoreState( (state) => state.ToolBoxPanelAllocationsPeriodState.value ); @@ -28,10 +46,7 @@ export function AllocationsPeriods() { title={get(cmsData, "componentsSidebar.aggregateByPeriod", "")} selected={selectedPeriod} setSelected={setSelectedPeriod} - options={dataPeriodOptions.map((period: string) => ({ - label: period, - value: period, - }))} + options={periodList} /> ); } diff --git a/src/app/components/ToolBoxPanel/components/datapath/index.tsx b/src/app/components/ToolBoxPanel/components/datapath/index.tsx index d90045bfd..80e756ffc 100644 --- a/src/app/components/ToolBoxPanel/components/datapath/index.tsx +++ b/src/app/components/ToolBoxPanel/components/datapath/index.tsx @@ -1,17 +1,15 @@ import React from "react"; +import { appColors } from "app/theme"; import findIndex from "lodash/findIndex"; import { useHistory } from "react-router-dom"; import Timeline from "@material-ui/lab/Timeline"; -import IconButton from "@material-ui/core/IconButton"; import { DrilldownModelUpdated } from "app/interfaces"; import TimelineDot from "@material-ui/lab/TimelineDot"; import TimelineItem from "@material-ui/lab/TimelineItem"; import TimelineContent from "@material-ui/lab/TimelineContent"; -import CloseOutlinedIcon from "@material-ui/icons/CloseOutlined"; import TimelineSeparator from "@material-ui/lab/TimelineSeparator"; import TimelineConnector from "@material-ui/lab/TimelineConnector"; import { useStoreState, useStoreActions } from "app/state/store/hooks"; -import { appColors } from "app/theme"; export function DataPathPanel() { const history = useHistory(); diff --git a/src/app/components/ToolBoxPanel/components/filters/common/expandedgroup/index.tsx b/src/app/components/ToolBoxPanel/components/filters/common/expandedgroup/index.tsx index 9ce58f4ed..8602564c1 100644 --- a/src/app/components/ToolBoxPanel/components/filters/common/expandedgroup/index.tsx +++ b/src/app/components/ToolBoxPanel/components/filters/common/expandedgroup/index.tsx @@ -26,6 +26,9 @@ import { interface ExpandedFilterGroupProps extends FilterGroupModel, FilterGroupProps { goBack: () => void; + appliedFilters?: string[]; + expandedGroup?: FilterGroupProps | null; + setAppliedFilters?: (filters: string[]) => void; } export function ExpandedFilterGroup(props: ExpandedFilterGroupProps) { @@ -45,7 +48,7 @@ export function ExpandedFilterGroup(props: ExpandedFilterGroupProps) { type: props.name, }); const [tmpAppliedFilters, setTmpAppliedFilters] = React.useState([ - ...appliedFilters, + ...(props.appliedFilters || appliedFilters), ]); const [tmpAppliedFiltersChildren, setTmpAppliedFiltersChildren] = React.useState([...(appliedFiltersChildren || [])]); @@ -199,8 +202,12 @@ export function ExpandedFilterGroup(props: ExpandedFilterGroupProps) { } function handleApply() { - if (!isEqual(appliedFilters, tmpAppliedFilters)) { - setAppliedFilters(tmpAppliedFilters); + if (!isEqual(props.appliedFilters || appliedFilters, tmpAppliedFilters)) { + if (props.setAppliedFilters) { + props.setAppliedFilters(tmpAppliedFilters); + } else { + setAppliedFilters(tmpAppliedFilters); + } } if ( setAppliedFiltersChildren && @@ -280,8 +287,12 @@ export function ExpandedFilterGroup(props: ExpandedFilterGroupProps) { } function resetFilters() { - if (appliedFilters.length > 0) { - setAppliedFilters([]); + if ((props.appliedFilters || appliedFilters).length > 0) { + if (props.setAppliedFilters) { + props.setAppliedFilters([]); + } else { + setAppliedFilters([]); + } setTmpAppliedFilters([]); } if ( @@ -302,9 +313,15 @@ export function ExpandedFilterGroup(props: ExpandedFilterGroupProps) { } } + let expandedGroupValue = + props.expandedGroup !== undefined ? props.expandedGroup : null; + if (expandedGroupValue === null && expandedGroup) { + expandedGroupValue = expandedGroup; + } + return ( <> - {expandedGroup && ( + {expandedGroupValue && ( <>
-
void; + appliedFilters?: string[]; + setAppliedFilters?: (filters: string[]) => void; } export function FilterGroup(props: FilterGroupCompProps) { @@ -68,7 +70,7 @@ export function FilterGroup(props: FilterGroupCompProps) { }); } let newAppliedFilters = filter( - appliedFilters, + props.appliedFilters || appliedFilters, (af: string) => af !== option ); if (allOptionSubOptions.length > 0) { @@ -77,7 +79,11 @@ export function FilterGroup(props: FilterGroupCompProps) { (af: string) => !find(allOptionSubOptions, (so) => so.value === af) ); } - setAppliedFilters(newAppliedFilters); + if (props.setAppliedFilters) { + props.setAppliedFilters(newAppliedFilters); + } else { + setAppliedFilters(newAppliedFilters); + } if (setAppliedFiltersChildren && appliedFiltersChildren) { let newAppliedFiltersChildren = [...appliedFiltersChildren]; newAppliedFiltersChildren = filter( @@ -172,7 +178,7 @@ export function FilterGroup(props: FilterGroupCompProps) {
{[ - ...appliedFilters, + ...(props.appliedFilters || appliedFilters), ...(appliedFiltersChildren || []), ...(appliedFiltersGrandChildren || []), ].length > 0 && ( @@ -213,7 +219,7 @@ export function FilterGroup(props: FilterGroupCompProps) { `} > {[ - ...appliedFilters, + ...(props.appliedFilters || appliedFilters), ...(appliedFiltersChildren || []), ...(appliedFiltersGrandChildren || []), ].map((option: string) => { diff --git a/src/app/components/ToolBoxPanel/components/filters/data.ts b/src/app/components/ToolBoxPanel/components/filters/data.ts index c7356fb22..becdd0070 100644 --- a/src/app/components/ToolBoxPanel/components/filters/data.ts +++ b/src/app/components/ToolBoxPanel/components/filters/data.ts @@ -157,6 +157,56 @@ export const filtergroups: FilterGroupProps[] = [ name: "Document Types", addSubOptionFilters: false, }, + { + name: "Portfolio Categorization", + addSubOptionFilters: false, + }, + { + name: "TRP Window", + addSubOptionFilters: true, + }, +]; + +export const fundingRequestFilterGroups: FilterGroupProps[] = [ + { + name: "Components", + addSubOptionFilters: false, + }, + { + name: "Locations", + addSubOptionFilters: true, + }, + { + name: "Portfolio Categorization", + addSubOptionFilters: false, + }, + { + name: "TRP Window", + addSubOptionFilters: true, + }, +]; + +export const accessToFundingEligibilityFilterGroups: FilterGroupProps[] = [ + { + name: "Year", + addSubOptionFilters: true, + }, + { + name: "Components", + addSubOptionFilters: false, + }, + { + name: "Eligibility Status", + addSubOptionFilters: false, + }, + { + name: "Portfolio Categorization", + addSubOptionFilters: false, + }, + { + name: "TRP Window", + addSubOptionFilters: true, + }, ]; export const pathnameToFilterGroups = { @@ -166,7 +216,9 @@ export const pathnameToFilterGroups = { (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), documents: filter( filtergroups, @@ -186,105 +238,135 @@ export const pathnameToFilterGroups = { (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/disbursements/time-cycle": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/disbursements/map": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/disbursements/table": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/signed/treemap": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/signed/time-cycle": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/signed/map": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/signed/table": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/commitment/treemap": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/commitment/time-cycle": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/commitment/map": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/commitment/table": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/budgets/flow": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/budgets/time-cycle": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/budgets/map": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/viz/allocations": filter( filtergroups, @@ -331,6 +413,15 @@ export const pathnameToFilterGroups = { (fg: FilterGroupProps) => fg.name === "Donors" || fg.name === "Replenishment Periods" ), + "/viz/funding-requests/table": filter( + filtergroups, + (fg: FilterGroupProps) => + fg.name !== "Donors" && + fg.name !== "Replenishment Periods" && + fg.name !== "Document Types" && + fg.name !== "Grant Status" && + fg.name !== "Partner Types" + ), // location detail page "/location//overview": filter( filtergroups, @@ -338,7 +429,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), // "/location//overview": [], "/location//disbursements/treemap": filter( @@ -347,7 +440,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//disbursements/table": filter( filtergroups, @@ -355,7 +450,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//disbursements/time-cycle": filter( filtergroups, @@ -363,7 +460,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//disbursements/map": filter( filtergroups, @@ -371,7 +470,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//signed/treemap": filter( filtergroups, @@ -379,7 +480,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//signed/table": filter( filtergroups, @@ -387,7 +490,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//signed/time-cycle": filter( filtergroups, @@ -395,7 +500,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//signed/map": filter( filtergroups, @@ -403,7 +510,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//commitment/treemap": filter( filtergroups, @@ -411,7 +520,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//commitment/table": filter( filtergroups, @@ -419,7 +530,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//commitment/time-cycle": filter( filtergroups, @@ -427,7 +540,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//commitment/map": filter( filtergroups, @@ -435,7 +550,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//budgets/flow": filter( filtergroups, @@ -443,7 +560,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//budgets/time-cycle": filter( filtergroups, @@ -451,7 +570,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//budgets/map": filter( filtergroups, @@ -459,8 +580,11 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), + "/location//allocations": filter( filtergroups, (fg: FilterGroupProps) => fg.name === "Components" @@ -488,7 +612,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//grants/list": filter( filtergroups, @@ -496,7 +622,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/location//results": filter( filtergroups, @@ -509,7 +637,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//disbursements/table": filter( filtergroups, @@ -517,7 +647,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//disbursements/time-cycle": filter( filtergroups, @@ -525,7 +657,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//disbursements/map": filter( filtergroups, @@ -533,7 +667,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//signed/treemap": filter( filtergroups, @@ -541,7 +677,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//signed/table": filter( filtergroups, @@ -549,7 +687,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//signed/time-cycle": filter( filtergroups, @@ -557,7 +697,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//signed/map": filter( filtergroups, @@ -565,7 +707,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//commitment/treemap": filter( filtergroups, @@ -573,7 +717,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//commitment/table": filter( filtergroups, @@ -581,7 +727,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//commitment/time-cycle": filter( filtergroups, @@ -589,7 +737,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//commitment/map": filter( filtergroups, @@ -597,14 +747,18 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//budgets/flow": filter( filtergroups, (fg: FilterGroupProps) => fg.name !== "Locations" && fg.name !== "Donors" && - fg.name !== "Replenishment Periods" + fg.name !== "Replenishment Periods" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//budgets/time-cycle": filter( filtergroups, @@ -612,7 +766,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//budgets/map": filter( filtergroups, @@ -620,7 +776,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//grants": filter( filtergroups, @@ -628,7 +786,9 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), "/partner//grants/list": filter( filtergroups, @@ -636,6 +796,8 @@ export const pathnameToFilterGroups = { fg.name !== "Locations" && fg.name !== "Donors" && fg.name !== "Replenishment Periods" && - fg.name !== "Document Types" + fg.name !== "Document Types" && + fg.name !== "Portfolio Categorization" && + fg.name !== "TRP Window" ), }; diff --git a/src/app/components/ToolBoxPanel/components/filters/index.tsx b/src/app/components/ToolBoxPanel/components/filters/index.tsx index 1fe38e71e..504e7c861 100644 --- a/src/app/components/ToolBoxPanel/components/filters/index.tsx +++ b/src/app/components/ToolBoxPanel/components/filters/index.tsx @@ -16,6 +16,12 @@ import { ExpandedFilterGroup } from "app/components/ToolBoxPanel/components/filt interface ToolBoxPanelFiltersProps { groups: FilterGroupProps[]; + expandedGroup?: FilterGroupProps | null; + appliedFilters?: { [key: string]: string[] }; + defaultAppliedFilters?: { [key: string]: string[] }; + groupAppliedFiltersPathKey?: { [key: string]: string }; + setExpandedGroup?: (group: FilterGroupProps | null) => void; + setAppliedFilters?: (filters: { [key: string]: string[] }) => void; } export function ToolBoxPanelFilters(props: ToolBoxPanelFiltersProps) { @@ -26,30 +32,71 @@ export function ToolBoxPanelFilters(props: ToolBoxPanelFiltersProps) { const data = useStoreState((state) => state.AppliedFiltersState); function resetAllFilters() { - if (!isEqual(data, defaultAppliedFilters)) { - actions.setAll(defaultAppliedFilters); + if ( + props.setAppliedFilters && + props.defaultAppliedFilters && + props.appliedFilters + ) { + if (!isEqual(data, props.defaultAppliedFilters)) { + props.setAppliedFilters(props.defaultAppliedFilters); + } + } else { + if (!isEqual(data, defaultAppliedFilters)) { + actions.setAll(defaultAppliedFilters); + } + } + } + + function setGroupAppliedFilters(group: string, filters: string[]) { + const appliedFilterPathKey = get( + props.groupAppliedFiltersPathKey, + group, + null + ); + if ( + appliedFilterPathKey && + props.setAppliedFilters && + props.appliedFilters + ) { + props.setAppliedFilters({ + ...props.appliedFilters, + [appliedFilterPathKey]: filters, + }); } } const options = React.useMemo(() => { - if (expandedGroup) { + if (props.setExpandedGroup && props.expandedGroup) { + return get(filterOptions, props.expandedGroup.name, []); + } else if (!props.setExpandedGroup && expandedGroup) { return get(filterOptions, expandedGroup.name, []); } return []; - }, [filterOptions, expandedGroup]); + }, [ + filterOptions, + expandedGroup, + props.expandedGroup, + props.setExpandedGroup, + ]); if (props.groups.length === 0) { return ; } + let expandedGroupValue = + props.expandedGroup !== undefined ? props.expandedGroup : null; + if (expandedGroupValue === null && expandedGroup) { + expandedGroupValue = expandedGroup; + } + return (
- {!expandedGroup && ( + {!expandedGroupValue && (
setExpandedGroup(group)} + expandGroup={() => { + if (props.setExpandedGroup) { + props.setExpandedGroup(group); + } else { + setExpandedGroup(group); + } + }} + appliedFilters={ + props.appliedFilters + ? get( + props.appliedFilters, + get( + props.groupAppliedFiltersPathKey, + `["${group.name}"]`, + group.name + ), + [] + ) + : undefined + } + setAppliedFilters={ + props.setAppliedFilters + ? (filters: string[]) => + setGroupAppliedFilters(group.name, filters) + : undefined + } /> ))}
@@ -120,14 +192,43 @@ export function ToolBoxPanelFilters(props: ToolBoxPanelFiltersProps) {
- {expandedGroup ? ( + {expandedGroupValue ? ( setExpandedGroup(null)} + {...expandedGroupValue} + expandedGroup={expandedGroupValue} + goBack={() => { + if (props.setExpandedGroup) { + props.setExpandedGroup(null); + } else { + setExpandedGroup(null); + } + }} options={options} + appliedFilters={ + props.appliedFilters + ? get( + props.appliedFilters, + get( + props.groupAppliedFiltersPathKey, + `["${expandedGroupValue.name}"]`, + expandedGroupValue.name + ), + [] + ) + : undefined + } + setAppliedFilters={ + props.setAppliedFilters + ? (filters: string[]) => { + if (expandedGroupValue) { + setGroupAppliedFilters(expandedGroupValue.name, filters); + } + } + : undefined + } /> ) : ( <> diff --git a/src/app/components/ToolBoxPanel/components/subtoolboxpanel/index.tsx b/src/app/components/ToolBoxPanel/components/subtoolboxpanel/index.tsx index eec3c1ff7..2e7c18c17 100644 --- a/src/app/components/ToolBoxPanel/components/subtoolboxpanel/index.tsx +++ b/src/app/components/ToolBoxPanel/components/subtoolboxpanel/index.tsx @@ -11,7 +11,6 @@ import { useStoreActions, useStoreState } from "app/state/store/hooks"; import { ResultsYear } from "app/components/ToolBoxPanel/components/resultsyear"; import { ToolBoxPanelFilters } from "app/components/ToolBoxPanel/components/filters"; import { FilterGroupProps } from "app/components/ToolBoxPanel/components/filters/data"; -import { EligibilityYear } from "app/components/ToolBoxPanel/components/eligibilityyear"; import { ToolBoxPanelControlRow } from "app/components/ToolBoxPanel/components/controlrow"; import { ToolBoxPanelAggregateBy } from "app/components/ToolBoxPanel/components/aggregateby"; import { ToolBoxPanelDonorViews } from "app/components/ToolBoxPanel/components/donormapviews"; @@ -188,7 +187,6 @@ export function SubToolBoxPanel(props: SubToolBoxPanelProps) { const isGrantDetail = history.location.pathname.indexOf("/grant/") > -1; const isResultsPage = history.location.pathname.indexOf("/results") > -1; - const isLocationDetail = history.location.pathname.indexOf("/location/") > -1; return ( <> @@ -227,9 +225,6 @@ export function SubToolBoxPanel(props: SubToolBoxPanelProps) { {(params.vizType === "allocations" || params.vizType === "allocation") && params.subType !== "table" && } - {params.vizType === "eligibility" && !isLocationDetail && ( - - )} {isResultsPage && } {(((params.vizType === "commitment" || params.vizType === "disbursements" || diff --git a/src/app/components/ToolBoxPanel/utils/getControlItems.ts b/src/app/components/ToolBoxPanel/utils/getControlItems.ts index 628aebe32..325a157ce 100644 --- a/src/app/components/ToolBoxPanel/utils/getControlItems.ts +++ b/src/app/components/ToolBoxPanel/utils/getControlItems.ts @@ -169,34 +169,6 @@ const views = { }; const aggregates = { - // investments: [ - // { - // label: "Components", - // value: "Components", - // }, - // { - // label: "Partners", - // value: "Partners", - // }, - // { - // label: "Locations", - // value: "Locations", - // }, - // { - // label: "Grants", - // value: "Grants", - // }, - // ], - // budgets: [ - // { - // label: "Components", - // value: "Components", - // }, - // { - // label: "Locations", - // value: "Locations", - // }, - // ], "allocations-table": [ { label: "Locations", @@ -207,6 +179,16 @@ const aggregates = { value: "component.componentName", }, ], + "eligibility-table": [ + { + label: "Locations", + value: "geographicAreaName", + }, + { + label: "Components", + value: "componentName", + }, + ], "pledges-contributions-table": [ { label: "Donor", diff --git a/src/app/hooks/useAppliedFilters.tsx b/src/app/hooks/useAppliedFilters.tsx index 17f1373b4..a152f7fd5 100644 --- a/src/app/hooks/useAppliedFilters.tsx +++ b/src/app/hooks/useAppliedFilters.tsx @@ -72,6 +72,16 @@ export function useAppliedFilters(props: UseAppliedFiltersProps): { setAppliedFilters: actions.setDonors, appliedFilters: data.donors, }; + case "TRP Window": + return { + setAppliedFilters: actions.setTrpWindows, + appliedFilters: data.trpWindows, + }; + case "Portfolio Categorization": + return { + setAppliedFilters: actions.setPortfolioCategories, + appliedFilters: data.portfolioCategories, + }; case "All": return { setAppliedFilters: actions.setLocations, @@ -84,6 +94,8 @@ export function useAppliedFilters(props: UseAppliedFiltersProps): { ...data.status, ...data.donors, ...data.replenishmentPeriods, + ...data.trpWindows, + ...data.portfolioCategories, ], }; default: diff --git a/src/app/hooks/useCMSData.tsx b/src/app/hooks/useCMSData.tsx index 3a93539fe..2c698f554 100644 --- a/src/app/hooks/useCMSData.tsx +++ b/src/app/hooks/useCMSData.tsx @@ -184,6 +184,12 @@ export function useCMSData(props: UseCMSDataProps) { const modulesLandingCMSData = useStoreState( (state) => state.cms.modulesLanding.data ); + const modulesFundingRequestsCMSAction = useStoreActions( + (actions) => actions.cms.modulesFundingRequests.fetch + ); + const modulesFundingRequestsCMSData = useStoreState( + (state) => state.cms.modulesFundingRequests.data + ); React.useEffect(() => { if (props.loadData) { @@ -217,6 +223,7 @@ export function useCMSData(props: UseCMSDataProps) { modulesGrantDetailCMSAction({ isCMSfetch: true }); modulesGrantsCMSAction({ isCMSfetch: true }); modulesLandingCMSAction({ isCMSfetch: true }); + modulesFundingRequestsCMSAction({ isCMSfetch: true }); } }, []); @@ -338,6 +345,10 @@ export function useCMSData(props: UseCMSDataProps) { key: "modulesLanding", data: modulesLandingCMSData || {}, }, + { + key: "modulesFundingRequests", + data: modulesFundingRequestsCMSData || {}, + }, ]; items.forEach((item) => { let filteredData = {}; diff --git a/src/app/hooks/useDatasetMenuItems.tsx b/src/app/hooks/useDatasetMenuItems.tsx index ca0fa1a56..be2b8cb74 100644 --- a/src/app/hooks/useDatasetMenuItems.tsx +++ b/src/app/hooks/useDatasetMenuItems.tsx @@ -101,6 +101,9 @@ export function useDatasetMenuItems(): React.ReactChild[] { Eligibility Allocations + + Funding Requests + , store.LocationAccessToFunding.GrantCycles.fetch + ); + + const getEligibilityStatusCodelist = useStoreActions( + (store) => store.EligibilityStatusCodelist.fetch + ); + const eligibilityStatusCodelist = useStoreState((state) => + get(state.EligibilityStatusCodelist, "data.data", []) + ); + + const getEligibilityDiseaseBurdenCodelist = useStoreActions( + (store) => store.EligibilityDiseaseBurdenCodelist.fetch + ); + const EligibilityDiseaseBurdenCodelist = useStoreState((state) => + get(state.EligibilityDiseaseBurdenCodelist, "data.data", []) + ); + + const getEligibilityYearOptions = useStoreActions( + (store) => store.EligibilityYears.fetch + ); + const EligibilityYearOptions = useStoreState((state) => + get(state.EligibilityYears, "data.data", []) + ); + + const getFundingRequestsTRPWindowCodelist = useStoreActions( + (store) => store.FundingRequestsTRPWindowCodelist.fetch + ); + const FundingRequestsTRPWindowCodelist = useStoreState((state) => + get(state.FundingRequestsTRPWindowCodelist, "data.data", []) + ); + + const getFundingRequestsPortfolioCategoryCodelist = useStoreActions( + (store) => store.FundingRequestsPortfolioCategoryCodelist.fetch + ); + const FundingRequestsPortfolioCategoryCodelist = useStoreState((state) => + get(state.FundingRequestsPortfolioCategoryCodelist, "data.data", []) + ); + React.useEffect(() => { - if (locations.length === 0) { - getLocations({}); - } - if (components.length === 0) { - getComponents({}); - } - if (partnerTypes.length === 0) { - getPartnerTypes({}); - } - if (status.length === 0) { - getStatus({}); - } - if (replenishmentPeriods.length === 0) { - getReplenishmentPeriods({}); - } - if (donors.length === 0) { - getDonors({}); + if (props.loadFilterOptions) { + if (locations.length === 0) { + getLocations({}); + } + if (components.length === 0) { + getComponents({}); + } + if (partnerTypes.length === 0) { + getPartnerTypes({}); + } + if (status.length === 0) { + getStatus({}); + } + if (replenishmentPeriods.length === 0) { + getReplenishmentPeriods({}); + } + if (donors.length === 0) { + getDonors({}); + } + getGrantCycles({}); + getEligibilityStatusCodelist({}); + getEligibilityYearOptions({}); + getEligibilityDiseaseBurdenCodelist({}); + getFundingRequestsTRPWindowCodelist({}); + getFundingRequestsPortfolioCategoryCodelist({}); } }, []); @@ -109,6 +162,14 @@ export function useFilterOptions( location.pathname === "/viz/pledges-contributions/map" ? get(find(donors, { label: donorsMapView }), "subOptions", donors) : donors, + "Eligibility Status": eligibilityStatusCodelist, + "Disease Burden": EligibilityDiseaseBurdenCodelist, + "Eligibility Years": EligibilityYearOptions.map((item: string) => ({ + label: item, + value: item, + })), + "TRP Window": FundingRequestsTRPWindowCodelist, + "Portfolio Categorization": FundingRequestsPortfolioCategoryCodelist, }; } diff --git a/src/app/hooks/useGetAllAvailableGrants.tsx b/src/app/hooks/useGetAllAvailableGrants.tsx index 772870734..35fa37f85 100644 --- a/src/app/hooks/useGetAllAvailableGrants.tsx +++ b/src/app/hooks/useGetAllAvailableGrants.tsx @@ -28,7 +28,7 @@ export function useGetAllAvailableGrants( search: search.length > 0 ? search : undefined, } ); - return await axios + return axios .get( `${process.env.REACT_APP_API}/grants?${ `${filterString}&pageSize=0` ?? `pageSize=0` diff --git a/src/app/hooks/useGetAllVizData.tsx b/src/app/hooks/useGetAllVizData.tsx index 2aa3d7b25..1f6323832 100644 --- a/src/app/hooks/useGetAllVizData.tsx +++ b/src/app/hooks/useGetAllVizData.tsx @@ -212,6 +212,22 @@ export function useGetAllVizData() { const locationDetailDocuments = useStoreState( (state) => state.LocationDetailDocuments.data ); + const locationDetailEligibility = useStoreState( + (state) => + get( + state.LocationAccessToFunding.EligibilityTable.data, + "data", + [] + ) as SimpleTableRow[] + ); + const locationDetailFundingRequests = useStoreState( + (state) => + get( + state.LocationAccessToFunding.FundingRequestsTable.data, + "data", + [] + ) as SimpleTableRow[] + ); const pledgesContributionsGeomap = useStoreState((state) => ({ layers: { type: "FeatureCollection", @@ -326,7 +342,7 @@ export function useGetAllVizData() { "/viz/commitment/table": disbursementsTreemap, // Eligibility "/viz/eligibility": eligibility, - "/viz/eligibility/table": eligibility, + "/viz/eligibility/table": locationDetailEligibility, // Pledges & Contributions "/viz/pledges-contributions/map": pledgesContributionsGeomap, "/viz/pledges-contributions/table": pledgesContributionsTable, @@ -406,6 +422,12 @@ export function useGetAllVizData() { "/location//grants/list": grantsList, // Location Results "/location//results": resultsList, + // Location Access To Funding + "/location//access-to-funding": { + eligibility: locationDetailEligibility, + allocation: allocations, + fundingRequest: locationDetailFundingRequests, + }, // Grants "/grants": grantsList, "/grants/table": grantsList, diff --git a/src/app/hooks/useUrlFilters.tsx b/src/app/hooks/useUrlFilters.tsx index 810f43d56..7c0b6d0a8 100644 --- a/src/app/hooks/useUrlFilters.tsx +++ b/src/app/hooks/useUrlFilters.tsx @@ -29,6 +29,8 @@ export function useUrlFilters(): null { const donorSubCategories = currentUrlParams.get("donorSubCategories"); const donorCategories = currentUrlParams.get("donorCategories"); const replenishmentPeriods = currentUrlParams.get("replenishmentPeriods"); + const trpWindows = currentUrlParams.get("trpWindows"); + const portfolioCategories = currentUrlParams.get("portfolioCategories"); if (locations) { updatedAppliedFilters.locations = locations.split(","); @@ -65,6 +67,13 @@ export function useUrlFilters(): null { updatedAppliedFilters.replenishmentPeriods = replenishmentPeriods.split(","); } + if (trpWindows) { + updatedAppliedFilters.trpWindows = trpWindows.split(","); + } + if (portfolioCategories) { + updatedAppliedFilters.portfolioCategories = + portfolioCategories.split(","); + } actions.setAll(updatedAppliedFilters); }, @@ -138,6 +147,19 @@ export function useUrlFilters(): null { } else { currentUrlParams.delete("replenishmentPeriods"); } + if (data.trpWindows.length > 0) { + currentUrlParams.set("trpWindows", data.trpWindows.join(",")); + } else { + currentUrlParams.delete("trpWindows"); + } + if (data.portfolioCategories.length > 0) { + currentUrlParams.set( + "portfolioCategories", + data.portfolioCategories.join(",") + ); + } else { + currentUrlParams.delete("portfolioCategories"); + } const queryString = decodeURIComponent(currentUrlParams.toString()); history.push({ @@ -161,6 +183,8 @@ export function useUrlFilters(): null { const donorSubCategories = currentUrlParams.get("donorSubCategories"); const donorCategories = currentUrlParams.get("donorCategories"); const replenishmentPeriods = currentUrlParams.get("replenishmentPeriods"); + const trpWindows = currentUrlParams.get("trpWindows"); + const portfolioCategories = currentUrlParams.get("portfolioCategories"); if (locations) { updatedAppliedFilters.locations = locations.split(","); @@ -218,6 +242,17 @@ export function useUrlFilters(): null { } else if (updatedAppliedFilters.replenishmentPeriods.length > 0) { updatedAppliedFilters.replenishmentPeriods = []; } + if (trpWindows) { + updatedAppliedFilters.trpWindows = trpWindows.split(","); + } else if (updatedAppliedFilters.trpWindows.length > 0) { + updatedAppliedFilters.trpWindows = []; + } + if (portfolioCategories) { + updatedAppliedFilters.portfolioCategories = + portfolioCategories.split(","); + } else if (updatedAppliedFilters.portfolioCategories.length > 0) { + updatedAppliedFilters.portfolioCategories = []; + } if (!isEqual(data, updatedAppliedFilters)) { actions.setAll(updatedAppliedFilters); diff --git a/src/app/modules/common/page-loader/index.tsx b/src/app/modules/common/page-loader/index.tsx index 3a3bbb41b..2fba2a250 100644 --- a/src/app/modules/common/page-loader/index.tsx +++ b/src/app/modules/common/page-loader/index.tsx @@ -21,7 +21,6 @@ const Container = styled.div` align-items: center; flex-direction: column; justify-content: center; - height: calc(100vh - 45px); `; export const LoadingComp = styled.div` @@ -33,8 +32,13 @@ export const LoadingComp = styled.div` props.inLoader ? "100%" : "100vh"}; z-index: 100000; position: ${(props: { inLoader?: boolean }) => - props.inLoader ? "static" : "fixed"}; + props.inLoader ? "absolute" : "fixed"}; background: rgba(0, 0, 0, 0.2); + + > div { + height: ${(props: { inLoader?: boolean }) => + props.inLoader ? "100%" : "calc(100vh - 45px)"}; + } `; export const PageLoader = (props: { inLoader?: boolean }) => { diff --git a/src/app/modules/common/page-top-spacer/index.tsx b/src/app/modules/common/page-top-spacer/index.tsx index dbae0e17a..0289ace4b 100644 --- a/src/app/modules/common/page-top-spacer/index.tsx +++ b/src/app/modules/common/page-top-spacer/index.tsx @@ -4,7 +4,7 @@ export const PageTopSpacer = () => (
{ - if (!isMobile && !openToolboxPanel && params.vizType !== "overview") { + if ( + !isMobile && + !openToolboxPanel && + params.vizType !== "overview" && + location.pathname.indexOf("access-to-funding") <= -1 + ) { setOpenToolboxPanel(true); + } else { + setOpenToolboxPanel(false); } }, [params.vizType]); @@ -280,13 +284,6 @@ export default function CountryDetail() { - {/* - - */} - {/* Allocations */} - - - - - + - - - - {/* Eligibility */} - - - - - - {/* Grants */} - "), - filtergroups - )} - onCloseBtnClick={(value?: boolean) => - setOpenToolboxPanel(value !== undefined ? value : !openToolboxPanel) - } - getAllAvailableGrants={ - params.vizType === "grants" && params.subType === "list" - ? getAllAvailableGrants - : undefined - } - /> + {location.pathname.indexOf("access-to-funding") <= -1 && ( + "), + filtergroups + )} + onCloseBtnClick={(value?: boolean) => + setOpenToolboxPanel(value !== undefined ? value : !openToolboxPanel) + } + getAllAvailableGrants={ + params.vizType === "grants" && params.subType === "list" + ? getAllAvailableGrants + : undefined + } + /> + )}
- {c.name} + {c.url ? ( + + {c.name} + + ) : ( + {c.name} + )}
)} +
- {props.detailFilterType !== "partners" && } {(isLoading || loading) && } {!props.code && ( <> + + + + , + link: "/viz/funding-requests/table", + }, + ]} + /> + {/* Eligibility */} - + + {/* Funding Request */} + + +
void; + setSortBy: (value: string) => void; + title: string; + forceExpand?: boolean; + isLocation?: boolean; +} + +export function EligibilityTable(props: EligibilityTableProps) { + return ( + + ); +} diff --git a/src/app/modules/viz-module/sub-modules/accessToFunding/eligibility/tableWrapper.tsx b/src/app/modules/viz-module/sub-modules/accessToFunding/eligibility/tableWrapper.tsx new file mode 100644 index 000000000..ae07725ae --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/accessToFunding/eligibility/tableWrapper.tsx @@ -0,0 +1,393 @@ +/* third-party */ +import React from "react"; +import get from "lodash/get"; +import orderBy from "lodash/orderBy"; + +import { useDebounce } from "react-use"; +import { useRecoilValue } from "recoil"; +import TablePagination from "@material-ui/core/TablePagination"; +import { useStoreActions, useStoreState } from "app/state/store/hooks"; +/* project */ +import { PageLoader } from "app/modules/common/page-loader"; +import { TriangleXSIcon } from "app/assets/icons/TriangleXS"; +import { SimpleTableRow } from "app/components/Table/Simple/data"; +import { locationAccessToFundingCycleAtom } from "app/state/recoil/atoms"; +import { ToolBoxPanelFilters } from "app/components/ToolBoxPanel/components/filters"; +import { FilterGroupProps } from "app/components/ToolBoxPanel/components/filters/data"; +import { ToolBoxPanelAggregateBy } from "app/components/ToolBoxPanel/components/aggregateby"; +import { EligibilityTable } from "app/modules/viz-module/sub-modules/accessToFunding/eligibility/eligibilityTable"; + +interface Props { + code?: string; + forceExpand?: boolean; +} + +export function AccessToFundingEligibilityTableWrapper(props: Props) { + const data = useStoreState( + (state) => + get( + state.LocationAccessToFunding.EligibilityTable.data, + "data", + [] + ) as SimpleTableRow[] + ); + + const [search, setSearch] = React.useState(""); + const [sortBy, setSortBy] = React.useState( + `level1 ${props.code ? "DESC" : "ASC"}` + ); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + const [openToolboxPanel, setOpenToolboxPanel] = React.useState(false); + const [selectedAggregate, setSelectedAggregate] = + React.useState("geographicAreaName"); + const [expandedGroup, setExpandedGroup] = + React.useState(null); + + const controlItems = { + aggregates: [ + { label: "Locations", value: "geographicAreaName" }, + { label: "Components", value: "componentName" }, + ], + }; + + const fetchData = useStoreActions( + (store) => store.LocationAccessToFunding.EligibilityTable.fetch + ); + + const isLoading = useStoreState( + (state) => state.LocationAccessToFunding.EligibilityTable.loading + ); + const selectedAggregation = useStoreState( + (state) => state.ToolBoxPanelAggregateByState.value + ); + + const cycle = useRecoilValue(locationAccessToFundingCycleAtom); + + const [appliedFilters, setAppliedFilters] = React.useState<{ + [key: string]: string[]; + }>({ + year: [], + components: [], + status: [], + diseaseBurden: [], + }); + + const storedAppliedFilters = useStoreState( + (state) => state.AppliedFiltersState + ); + + const filterGroups = [ + { + name: "Eligibility Years", + }, + { + name: "Components", + }, + { + name: "Eligibility Status", + }, + { + name: "Disease Burden", + }, + ]; + + const groupAppliedFiltersPathKey = { + "Eligibility Years": "year", + Components: "components", + "Eligibility Status": "status", + "Disease Burden": "diseaseBurden", + }; + + function reloadData() { + const filterStr: string[] = []; + const appliedFiltersToUse = props.code + ? appliedFilters + : storedAppliedFilters; + if (props.code) { + filterStr.push(`locations=${props.code}`); + } else { + filterStr.push(`locations=${appliedFiltersToUse.locations.join(",")}`); + } + if (search.length > 0) { + filterStr.push(`q=${search}`); + } + if (sortBy.length > 0) { + filterStr.push(`sortBy=${sortBy}`); + } + if (appliedFilters.year.length > 0) { + filterStr.push(`periods=${appliedFilters.year.join(",")}`); + } + if (appliedFiltersToUse.components.length > 0) { + filterStr.push(`components=${appliedFiltersToUse.components.join(",")}`); + } + if (appliedFiltersToUse.status.length > 0) { + filterStr.push(`status=${appliedFiltersToUse.status.join(",")}`); + } + if (appliedFilters.diseaseBurden.length > 0) { + filterStr.push(`diseaseBurden=${appliedFilters.diseaseBurden.join(",")}`); + } + if (cycle !== "All" && props.code) { + filterStr.push( + `cycles=${(cycle || "").replace("-20", "-")}${ + cycle === "2002-2013" ? ",null" : "" + }` + ); + } + const aggregateBy = props.code ? selectedAggregate : selectedAggregation; + fetchData({ + filterString: `aggregateBy=${aggregateBy}&${filterStr.join("&")}`, + }); + } + + React.useEffect( + () => reloadData(), + [ + props.code, + storedAppliedFilters, + selectedAggregation, + selectedAggregate, + appliedFilters, + sortBy, + cycle, + ] + ); + + const [,] = useDebounce( + () => { + if (search.length > 0 || search === "") { + reloadData(); + } + }, + 500, + [search] + ); + + const handleChangePage = ( + _event: React.MouseEvent | null, + newPage: number + ) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + React.useEffect(() => { + if (!props.code) { + document.title = "The Data Explorer - Eligibility"; + } + }, []); + + let columns = [ + { name: "Location", key: "level1" }, + { name: "Component", key: "level2" }, + { + name: "Eligibility Status", + key: "eligibilityStatus", + valueToColorMap: { + Eligible: "#11AD6B", + "Not Eligible": "#FA7355", + "Transition Funding": "#F7E248", + }, + }, + { name: "Disease Burden", key: "diseaseBurden" }, + { name: "Income Level", key: "incomeLevel" }, + ]; + + if ( + (props.code ? selectedAggregate : selectedAggregation) === "componentName" + ) { + columns = [ + { name: "Component", key: "level1" }, + { name: "Location", key: "level2" }, + { + name: "Eligibility Status", + key: "eligibilityStatus", + valueToColorMap: { + Eligible: "#11AD6B", + "Not Eligible": "#FA7355", + "Transition Funding": "#F7E248", + }, + }, + { name: "Disease Burden", key: "diseaseBurden" }, + { name: "Income Level", key: "incomeLevel" }, + ]; + } + + let formattedData: SimpleTableRow[] = []; + if (props.code && props.forceExpand) { + if (selectedAggregate === "componentName") { + data.forEach((row) => { + formattedData.push({ + ...row, + children: row.children?.map((child) => ({ + level1: child.level1, + ...child.children?.[0], + })), + }); + }); + } else { + columns[0] = { name: "Year", key: "level1" }; + data.forEach((row) => { + row.children?.forEach((child) => { + formattedData.push(child); + }); + }); + } + } else { + formattedData = data; + } + const sortedData = formattedData.map((fdata) => { + const sort = orderBy(fdata.children, ["level1"], ["desc"]); + return { ...fdata, children: sort }; + }); + + return ( +
+ {isLoading && } + {props.code && ( +
svg { + transform: rotate(${!openToolboxPanel ? "-" : ""}90deg); + > path { + fill: #fff; + } + } + `} + onClick={() => setOpenToolboxPanel(!openToolboxPanel)} + > + +
+ )} + {props.code && ( +
+
+ + +
+
+ )} + + +
+ ); +} diff --git a/src/app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/table.tsx b/src/app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/table.tsx new file mode 100644 index 000000000..fbe6f1054 --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/table.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { + FundingTableRow, + FundingTableColumn, + FundingRequestTable, +} from "app/components/Table/funding"; + +interface FundingRequestTableProps { + search: string; + sortBy: string; + data: FundingTableRow[]; + columns: FundingTableColumn[]; + setSearch: (value: string) => void; + setSortBy: (value: string) => void; + title: string; + forceExpand?: boolean; +} + +export function Table(props: FundingRequestTableProps) { + return ( +
+ +
+ ); +} diff --git a/src/app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/tableWrapper.tsx b/src/app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/tableWrapper.tsx new file mode 100644 index 000000000..927df4dd4 --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/tableWrapper.tsx @@ -0,0 +1,286 @@ +/* third-party */ +import React from "react"; +import get from "lodash/get"; +import orderBy from "lodash/orderBy"; +import { useRecoilValue } from "recoil"; +import { useDebounce, useUpdateEffect } from "react-use"; +import { useStoreActions, useStoreState } from "app/state/store/hooks"; +/* project */ +import { PageLoader } from "app/modules/common/page-loader"; +import { TriangleXSIcon } from "app/assets/icons/TriangleXS"; +import TablePagination from "@material-ui/core/TablePagination"; +import { SimpleTableRow } from "app/components/Table/Simple/data"; +import { locationAccessToFundingCycleAtom } from "app/state/recoil/atoms"; +import { ToolBoxPanelFilters } from "app/components/ToolBoxPanel/components/filters"; +import { FilterGroupProps } from "app/components/ToolBoxPanel/components/filters/data"; +import { Table } from "app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/table"; +import { fundingRequestColumns } from "app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/data"; + +interface Props { + code: string; + codeParam: string; + filterGroups: FilterGroupProps[]; +} + +export function AccessToFundingRequestTableWrapper(props: Props) { + const [page, setPage] = React.useState(0); + const [search, setSearch] = React.useState(""); + const [sortBy, setSortBy] = React.useState(""); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + const [openToolboxPanel, setOpenToolboxPanel] = React.useState(false); + const [expandedGroup, setExpandedGroup] = + React.useState(null); + const [appliedFilters, setAppliedFilters] = React.useState<{ + [key: string]: string[]; + }>({ + components: [], + trpWindows: [], + portfolioCategories: [], + }); + + const filterGroups = [ + { + name: "Components", + }, + { + name: "TRP Window", + }, + { + name: "Portfolio Categorization", + }, + ]; + + const groupAppliedFiltersPathKey = { + Components: "components", + "TRP Window": "trpWindows", + "Portfolio Categorization": "portfolioCategories", + }; + + const cycle = useRecoilValue(locationAccessToFundingCycleAtom); + + const data = useStoreState( + (state) => + get( + state.LocationAccessToFunding.FundingRequestsTable.data, + "data", + [] + ) as SimpleTableRow[] + ); + + const isLoading = useStoreState( + (state) => state.LocationAccessToFunding.FundingRequestsTable.loading + ); + + const fetchData = useStoreActions( + (store) => store.LocationAccessToFunding.FundingRequestsTable.fetch + ); + + function handleChangePage( + _event: React.MouseEvent | null, + newPage: number + ) { + setPage(newPage); + } + + function handleChangeRowsPerPage( + event: React.ChangeEvent + ) { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + } + + function reloadData() { + const filterStr: string[] = [`locations=${props.code}`]; + if (search.length > 0) { + filterStr.push(`q=${search}`); + } + if (sortBy.length > 0) { + filterStr.push(`sortBy=${sortBy}`); + } + if (appliedFilters.components.length > 0) { + filterStr.push(`components=${appliedFilters.components.join(",")}`); + } + if (appliedFilters.trpWindows.length > 0) { + filterStr.push(`trpWindows=${appliedFilters.trpWindows.join(",")}`); + } + if (appliedFilters.portfolioCategories.length > 0) { + filterStr.push( + `portfolioCategories=${appliedFilters.portfolioCategories.join(",")}` + ); + } + if (cycle !== "All") { + filterStr.push(`cycles=${cycle}`); + } + fetchData({ + filterString: filterStr.join("&"), + }); + } + + const tableData = React.useMemo(() => { + let result; + if (props.code) { + result = get(data, "[0].children", []).slice( + page * rowsPerPage, + (page + 1) * rowsPerPage + ); + if (sortBy.length > 0) { + return result; + } + return orderBy(result, "children", "desc"); + } + result = data.map((item) => ({ + ...item, + children: orderBy(item.children, "children", "desc"), + })); + return result.slice(page * rowsPerPage, (page + 1) * rowsPerPage); + }, [data, page, rowsPerPage, sortBy]); + + React.useEffect(() => { + if ((props.code && cycle) || !props.code) { + reloadData(); + } + }, [props.code, appliedFilters, sortBy, cycle]); + + useUpdateEffect(() => { + if (search.length === 0) { + reloadData(); + } + }, [search]); + + const [,] = useDebounce( + () => { + if (search.length > 0) { + reloadData(); + } + }, + 500, + [search] + ); + + return ( +
+ {isLoading && } +
svg { + transform: rotate(${!openToolboxPanel ? "-" : ""}90deg); + > path { + fill: #fff; + } + } + `} + onClick={() => setOpenToolboxPanel(!openToolboxPanel)} + > + +
+
+
+ +
+
+ + + + ); +} diff --git a/src/app/modules/viz-module/sub-modules/accessToFunding/location.tsx b/src/app/modules/viz-module/sub-modules/accessToFunding/location.tsx new file mode 100644 index 000000000..b20552be4 --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/accessToFunding/location.tsx @@ -0,0 +1,312 @@ +import React from "react"; +import get from "lodash/get"; +import { Link } from "react-router-dom"; +import Box from "@material-ui/core/Box"; +import { useRecoilState } from "recoil"; +import { HIV } from "app/assets/icons/HIV"; +import { useCMSData } from "app/hooks/useCMSData"; +import { Malaria } from "app/assets/icons/Malaria"; +import { Tuberculosis } from "app/assets/icons/TB"; +import { formatFinancialValue } from "app/utils/formatFinancialValue"; +import { useStoreActions, useStoreState } from "app/state/store/hooks"; +import { locationAccessToFundingCycleAtom } from "app/state/recoil/atoms"; +import { FilterGroupProps } from "app/components/ToolBoxPanel/components/filters/data"; +import { formatLargeAmountsWithPrefix } from "app/utils/getFinancialValueWithMetricPrefix"; +import { AccessToFundingRequestTableWrapper } from "app/modules/viz-module/sub-modules/accessToFunding/fundingRequest/tableWrapper"; +import { AccessToFundingEligibilityTableWrapper } from "app/modules/viz-module/sub-modules/accessToFunding/eligibility/tableWrapper"; +import { + vizcss, + chipcss, + descriptioncss, +} from "app/modules/viz-module/sub-modules/accessToFunding/style"; + +interface Props { + code: string; + codeParam: string; + filterGroups: FilterGroupProps[]; +} + +export default function LocationAccessToFundingWrapper(props: Props) { + const cmsData = useCMSData({ returnData: true }); + + const [cycle, setCycle] = useRecoilState(locationAccessToFundingCycleAtom); + + const grantCycles = useStoreState( + (state) => + get( + state.LocationAccessToFunding.GrantCycles, + "data.data", + [] + ) as string[] + ); + const locationInfoData = useStoreState((state) => + get(state.LocationDetailInfo.data, "data[0]", { + id: "", + locationName: "", + disbursed: 0, + committed: 0, + signed: 0, + countries: [], + multicountries: [], + portfolioManager: "", + portfolioManagerEmail: "", + }) + ); + + // Allocation data + const total = useStoreState( + (state) => get(state.Allocations.data, "total", []) as number + ); + const keys = useStoreState( + (state) => get(state.Allocations.data, "keys", []) as string[] + ); + const values = useStoreState( + (state) => get(state.Allocations.data, "values", []) as number[] + ); + const fetchData = useStoreActions((store) => store.Allocations.fetch); + const isLoading = useStoreState((state) => state.Allocations.loading); + + function getAllocationIcon(key: string) { + if (key === "HIV") { + return ; + } + if (key === "Malaria") { + return ; + } + if (key === "Tuberculosis") { + return ; + } + return null; + } + + React.useEffect(() => { + return () => { + setCycle(null); + }; + }, []); + + React.useEffect(() => { + if (cycle) { + fetchData({ + filterString: `locations=${props.code}&${ + cycle !== "All" ? `periods=${cycle}` : "" + }`, + }); + } + }, [props.code, cycle]); + + React.useEffect(() => { + if (grantCycles.length > 0) { + setCycle(grantCycles[grantCycles.length - 1]); + } + }, [grantCycles]); + + return ( + <> +
+

+ Access to Funding +

+

+ Allocation, Funding Requests & Eligibility +

+

+ The Global Fund allocates funding to eligible countries every three + years. Countries access these funds by developing Funding Requests + which are informed by{" "} + + country dialogues + + . Information on the current status of Funding Requests is found + below. For those interested in participating in country dialogue, the + “Overview” tab above includes contact information for the Country + Coordinating Mechanism. +

+
+ +
+

+ Allocation Period +

+
+
setCycle("All")}> + All +
+ {grantCycles.map((c) => ( +
setCycle(c)}> + {c} +
+ ))} +
+
+
+ +
+

+ Allocation +

+
+
+ {!isLoading && ( +
+
+

+ {formatFinancialValue(total)} +

+

+ Allocated to {locationInfoData.locationName} for{" "} + {cycle} +

+
+
+ {values.map((val, index) => ( +
+ {getAllocationIcon(keys[index])} +
+ + {formatLargeAmountsWithPrefix(val) + .replace("$", "") + .replace("bln", "billion") + .replace("mln", "million")} + +
+ {keys[index]} +
+ ))} +
+
+ )} +
+
+
+

+ Funding Requests +

+
+
+ {get(cmsData, "modulesFundingRequests.tableDisclaimer", "")} +
+
+ +
+ {props.code.length === 3 && ( +
+ +

+ Eligibility +

+
+
+

+ Eligibility for funding from the Global Fund is determined by + country income classification and disease burden for HIV, + tuberculosis and malaria. Below are the components which are + eligible for an allocation for the selected allocation period, + according to the Global Fund Eligibility Policy. Eligibility for + the 2023-2025 Allocation Period was determined in 2022 and + documented in the 2023 Eligibility List. Eligibility does not + guarantee a funding allocation. Learn more about Eligibility{" "} + + here + {" "} + or{" "} + + see the full history of eligibility for this country. + +

+
+ + +
+ )} +
+ + + ); +} diff --git a/src/app/modules/viz-module/sub-modules/accessToFunding/style.ts b/src/app/modules/viz-module/sub-modules/accessToFunding/style.ts new file mode 100644 index 000000000..ad1bd18b7 --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/accessToFunding/style.ts @@ -0,0 +1,69 @@ +import { css } from "styled-components/macro"; + +export const chipcss = (active: boolean) => css` + padding: 7px 24px; + width: max-content; + height: 32px; + background: ${active ? "#252c34" : "#c7cdd1"}; + border-radius: 100px; + display: flex; + justify-content: center; + align-items: center; + color: #ffffff; + font-weight: 700; + font-family: "GothamNarrow-Bold", "Helvetica Neue", sans-serif; + cursor: pointer; + &:hover { + background-color: #252c34; + } +`; + +export const descriptioncss = css` + text-align: center; + font-family: "Gotham Narrow"; + + p { + color: #000; + width: 100%; + margin: auto; + font-size: 14px; + font-weight: 400; + line-height: 17px; + + a { + color: #252c34; + font-weight: 700; + } + } + + h1 { + color: #252c34; + font-size: 24px; + font-weight: 700; + margin-bottom: 0px; + } + + h3 { + color: #252c34; + margin-top: 3px; + font-size: 18px; + font-weight: 400; + } +`; + +export const vizcss = css` + a { + color: #252c34; + } + h4 { + font-size: 18px; + color: #252c34; + margin-bottom: 0; + font-family: "Gotham Narrow"; + } + hr { + border: 0.5px solid #000000; + width: 100%; + height: 0px; + } +`; diff --git a/src/app/modules/viz-module/sub-modules/allocations/components/nodata/index.tsx b/src/app/modules/viz-module/sub-modules/allocations/components/nodata/index.tsx index 02e4856dc..e2c861382 100644 --- a/src/app/modules/viz-module/sub-modules/allocations/components/nodata/index.tsx +++ b/src/app/modules/viz-module/sub-modules/allocations/components/nodata/index.tsx @@ -4,8 +4,8 @@ import { appColors } from "app/theme"; export function NoDataAllocations() { return ( { - const filterString = getAPIFormattedFilters( + let filterString = getAPIFormattedFilters( props.code ? { ...appliedFilters, @@ -76,11 +76,17 @@ export function AllocationsGeoMap(props: Props) { } : appliedFilters ); + if (filterString.length > 0) { + filterString = `&${filterString}`; + } else { + filterString = ""; + } if (geomapView === "countries") { fetchData({ - filterString: `periods=${selectedPeriod}${ - filterString.length > 0 ? `&${filterString}` : "" - }`, + filterString: + selectedPeriod !== "All" + ? `periods=${selectedPeriod}${filterString}` + : "", }); } else if (geomapView === "multicountries") { fetchMCData({ filterString }); diff --git a/src/app/modules/viz-module/sub-modules/allocations/index.tsx b/src/app/modules/viz-module/sub-modules/allocations/index.tsx index 31a95090c..6a2d74794 100644 --- a/src/app/modules/viz-module/sub-modules/allocations/index.tsx +++ b/src/app/modules/viz-module/sub-modules/allocations/index.tsx @@ -46,6 +46,7 @@ export function AllocationsModule(props: AllocationsModuleProps) { const selectedPeriod = useStoreState( (state) => state.ToolBoxPanelAllocationsPeriodState.value ); + const dataPathSteps = useStoreState((state) => state.DataPathSteps.steps); const addDataPathSteps = useStoreActions( (actions) => actions.DataPathSteps.addSteps @@ -96,6 +97,9 @@ export function AllocationsModule(props: AllocationsModuleProps) { const fetchPeriodOptionsData = useStoreActions( (store) => store.AllocationsPeriods.fetch ); + const clearDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.clear + ); const appliedFilters = useStoreState((state) => state.AppliedFiltersState); @@ -259,7 +263,7 @@ export function AllocationsModule(props: AllocationsModuleProps) { } React.useEffect(() => { - const filterString = getAPIFormattedFilters( + let filterString = getAPIFormattedFilters( props.code ? { ...appliedFilters, @@ -267,10 +271,17 @@ export function AllocationsModule(props: AllocationsModuleProps) { } : appliedFilters ); + if (filterString.length > 0) { + filterString = `&${filterString}`; + } else { + filterString = ""; + } + fetchData({ - filterString: `periods=${selectedPeriod}${ - filterString.length > 0 ? `&${filterString}` : "" - }`, + filterString: + selectedPeriod !== "All" + ? `periods=${selectedPeriod}${filterString}` + : "", }); }, [props.code, appliedFilters, selectedPeriod]); @@ -292,7 +303,7 @@ export function AllocationsModule(props: AllocationsModuleProps) { } } }); - const filterString = getAPIFormattedFilters( + let filterString = getAPIFormattedFilters( props.code ? { ...appliedFilters, @@ -300,6 +311,11 @@ export function AllocationsModule(props: AllocationsModuleProps) { } : appliedFilters ); + if (filterString.length > 0) { + filterString = `&${filterString}`; + } else { + filterString = ""; + } fetchDrilldownLevelData({ filterString: `levelParam=component/componentName in (${(vizSelected === "Total" @@ -308,9 +324,11 @@ export function AllocationsModule(props: AllocationsModuleProps) { ) .split(",") .map((s: string) => `'${s}'`) - .join(",")})&periods=${selectedPeriod}${ - filterString.length > 0 ? `&${filterString}` : "" - }`, + .join(",")})${ + selectedPeriod !== "All" + ? `&periods=${selectedPeriod}${filterString}` + : "" + }${filterString}`, }); } else { [...items].forEach((item: Element) => { @@ -456,11 +474,9 @@ export function AllocationsModule(props: AllocationsModuleProps) { selected={vizSelected || ""} onChange={(value: string) => { const prevValue = vizSelected || ""; - console.log("prevValue", prevValue); const fItemIndex = findIndex(dataPathSteps, { vizSelected: { id: prevValue, filterStr: prevValue }, }); - console.log("fItemIndex", fItemIndex); setVizSelected(value); let newDataPathSteps = [...dataPathSteps]; if (fItemIndex > -1) { @@ -489,10 +505,12 @@ export function AllocationsModule(props: AllocationsModuleProps) { onNodeClick={(node: string) => { const name = node.split("-")[0]; const code = getIso3FromName(name); - setReRouteDialog({ - display: true, - code, - }); + // setReRouteDialog({ + // display: true, + // code, + // }); + clearDataPathSteps(); + history.push(`/location/${code}/overview`); }} /> @@ -548,7 +566,6 @@ export function AllocationsModule(props: AllocationsModuleProps) { {selectedPeriod}
{formatFinancialValue(total)}
- {vizComponent} ); diff --git a/src/app/modules/viz-module/sub-modules/allocations/table/index.tsx b/src/app/modules/viz-module/sub-modules/allocations/table/index.tsx index 913da6dce..9828e152b 100644 --- a/src/app/modules/viz-module/sub-modules/allocations/table/index.tsx +++ b/src/app/modules/viz-module/sub-modules/allocations/table/index.tsx @@ -7,7 +7,10 @@ import useDebounce from "react-use/lib/useDebounce"; import { SimpleTable } from "app/components/Table/Simple"; import { PageLoader } from "app/modules/common/page-loader"; import useUpdateEffect from "react-use/lib/useUpdateEffect"; -import { SimpleTableRow } from "app/components/Table/Simple/data"; +import { + SimpleTableColumn, + SimpleTableRow, +} from "app/components/Table/Simple/data"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; import { getAPIFormattedFilters } from "app/utils/getAPIFormattedFilters"; @@ -75,12 +78,13 @@ export function AllocationsTableModule(props: AllocationsTableProps) { return ; } - const columns = + const columns: SimpleTableColumn[] = data.length > 0 ? filter(Object.keys(data[0]), (key) => key !== "children").map( (key) => ({ - name: key === "name" ? "Component/Location" : `${key} (USD)`, + name: key === "name" ? "Component/Location" : key, key, + isMonetary: key === "name" ? undefined : { currency: "USD" }, }) ) : []; diff --git a/src/app/modules/viz-module/sub-modules/budgets/flow/index.tsx b/src/app/modules/viz-module/sub-modules/budgets/flow/index.tsx index ae9111dd2..f59491b2e 100644 --- a/src/app/modules/viz-module/sub-modules/budgets/flow/index.tsx +++ b/src/app/modules/viz-module/sub-modules/budgets/flow/index.tsx @@ -72,6 +72,9 @@ export function BudgetsFlowModule(props: BudgetsFlowModuleProps) { const addDataPathSteps = useStoreActions( (actions) => actions.DataPathSteps.addSteps ); + const clearDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.clear + ); const totalBudget: number = React.useMemo(() => { return sumBy(filter(props.links, { source: "Budgets" }), "value"); @@ -201,10 +204,12 @@ export function BudgetsFlowModule(props: BudgetsFlowModuleProps) { .replace(idSplits[0], "") .replace(`-${idSplits[1]}`, ""); code = code.slice(0, code.length - 1); - setReRouteDialog({ - display: true, - code, - }); + // setReRouteDialog({ + // display: true, + // code, + // }); + clearDataPathSteps(); + history.push(`/grant/${code}/period/budgets/flow`); } }} /> diff --git a/src/app/modules/viz-module/sub-modules/budgets/time-cycle/index.tsx b/src/app/modules/viz-module/sub-modules/budgets/time-cycle/index.tsx index e4bd7644e..a88c369f6 100644 --- a/src/app/modules/viz-module/sub-modules/budgets/time-cycle/index.tsx +++ b/src/app/modules/viz-module/sub-modules/budgets/time-cycle/index.tsx @@ -11,7 +11,6 @@ import { useCMSData } from "app/hooks/useCMSData"; import useMediaQuery from "@material-ui/core/useMediaQuery"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; /* project */ -import { DrilldownModelUpdated } from "app/interfaces"; import { PageLoader } from "app/modules/common/page-loader"; import { getNameFromIso3 } from "app/utils/getIso3FromName"; import { formatFinancialValue } from "app/utils/formatFinancialValue"; @@ -56,6 +55,9 @@ export function BudgetsTimeCycleModule(props: BudgetsTimeCycleModuleProps) { const addDataPathSteps = useStoreActions( (actions) => actions.DataPathSteps.addSteps ); + const clearDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.clear + ); const [reRouteDialog, setReRouteDialog] = React.useState({ display: false, @@ -187,10 +189,12 @@ export function BudgetsTimeCycleModule(props: BudgetsTimeCycleModuleProps) { .replace(idSplits[0], "") .replace(`-${idSplits[1]}`, ""); code = code.slice(0, code.length - 1); - setReRouteDialog({ - display: true, - code, - }); + // setReRouteDialog({ + // display: true, + // code, + // }); + clearDataPathSteps(); + history.push(`/grant/${code}/period/budgets/time-cycle`); } }} /> diff --git a/src/app/modules/viz-module/sub-modules/eligibility/table/data-wrappers/location.tsx b/src/app/modules/viz-module/sub-modules/eligibility/table/data-wrappers/location.tsx deleted file mode 100644 index 715d7e9f9..000000000 --- a/src/app/modules/viz-module/sub-modules/eligibility/table/data-wrappers/location.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/* third-party */ -import React from "react"; -import get from "lodash/get"; -import TablePagination from "@material-ui/core/TablePagination"; -import { useDebounce, useTitle, useUpdateEffect } from "react-use"; -import { useStoreActions, useStoreState } from "app/state/store/hooks"; -/* project */ -import { PageLoader } from "app/modules/common/page-loader"; -import { SimpleTableRow } from "app/components/Table/Simple/data"; -import { getAPIFormattedFilters } from "app/utils/getAPIFormattedFilters"; -import { EligibilityTable } from "app/modules/viz-module/sub-modules/eligibility/table"; -import { - diseaseBurdens, - EligibilityScatterplotDataItemModel, - incomeLevels, -} from "app/components/Charts/Eligibility/Scatterplot/data"; - -function getTableData( - data: EligibilityScatterplotDataItemModel[] -): SimpleTableRow[] { - return data.map((item: EligibilityScatterplotDataItemModel) => ({ - year: item.x, - component: item.y, - incomeLevel: get(incomeLevels, `[${item.incomeLevel}]`, item.incomeLevel), - diseaseBurden: get(diseaseBurdens, `[${item.diseaseBurden}]`, ""), - status: item.eligibility, - })); -} - -interface Props { - code: string; -} - -export function LocationEligibilityTableWrapper(props: Props) { - useTitle("The Data Explorer - Location Eligibility"); - - const [search, setSearch] = React.useState(""); - const [sortBy, setSortBy] = React.useState("year ASC"); - - const data = useStoreState( - (state) => - get( - state.EligibilityCountry.data, - "data", - [] - ) as EligibilityScatterplotDataItemModel[] - ); - - const [page, setPage] = React.useState(0); - const [rowsPerPage, setRowsPerPage] = React.useState(10); - const [tableData, setTableData] = React.useState( - getTableData(data) - ); - - const fetchData = useStoreActions((store) => store.EligibilityCountry.fetch); - - const isLoading = useStoreState((state) => state.EligibilityCountry.loading); - - const appliedFilters = useStoreState((state) => state.AppliedFiltersState); - - function reloadData() { - const filterString = getAPIFormattedFilters( - props.code - ? { - ...appliedFilters, - locations: [...appliedFilters.locations, props.code], - } - : appliedFilters, - { search, sortBy } - ); - fetchData({ - filterString: `${filterString}${ - filterString.length > 0 ? "&" : "" - }view=table`, - }); - } - - React.useEffect(() => reloadData(), [props.code, appliedFilters, sortBy]); - - useUpdateEffect(() => setTableData(getTableData(data)), [data]); - - useUpdateEffect(() => { - if (search.length === 0) { - reloadData(); - } - }, [search]); - - const [,] = useDebounce( - () => { - if (search.length > 0) { - reloadData(); - } - }, - 500, - [search] - ); - - const handleChangePage = ( - _event: React.MouseEvent | null, - newPage: number - ) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = ( - event: React.ChangeEvent - ) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - - if (isLoading) { - return ; - } - - return ( - <> - - - - - ); -} diff --git a/src/app/modules/viz-module/sub-modules/eligibility/table/index.tsx b/src/app/modules/viz-module/sub-modules/eligibility/table/index.tsx index bcbf3cb05..7c2f013b5 100644 --- a/src/app/modules/viz-module/sub-modules/eligibility/table/index.tsx +++ b/src/app/modules/viz-module/sub-modules/eligibility/table/index.tsx @@ -1,40 +1,94 @@ -/* third-party */ import React from "react"; -import get from "lodash/get"; -/* project */ -import { useCMSData } from "app/hooks/useCMSData"; -import { SimpleTable } from "app/components/Table/Simple"; -import { PageLoader } from "app/modules/common/page-loader"; -import { - SimpleTableRow, - SimpleTableColumn, -} from "app/components/Table/Simple/data"; +import find from "lodash/find"; +import { appColors } from "app/theme"; +import uniqueId from "lodash/uniqueId"; +import { useHistory } from "react-router-dom"; +import { useStoreActions, useStoreState } from "app/state/store/hooks"; +import { AccessToFundingEligibilityTableWrapper } from "app/modules/viz-module/sub-modules/accessToFunding/eligibility/tableWrapper"; -interface EligibilityTableProps { - search: string; - sortBy: string; - isLoading: boolean; - data: SimpleTableRow[]; - columns: SimpleTableColumn[]; - setSearch: (value: string) => void; - setSortBy: (value: string) => void; -} +export function EligibilityTableModuleWrapper() { + const history = useHistory(); + + const addDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.addSteps + ); + const dataPathSteps = useStoreState((state) => state.DataPathSteps.steps); -export function EligibilityTable(props: EligibilityTableProps) { - const cmsData = useCMSData({ returnData: true }); - if (props.isLoading) { - return ; - } + React.useEffect(() => { + if ( + dataPathSteps.length === 0 || + !find(dataPathSteps, { name: "Access to Funding: Eligibility" }) + ) { + addDataPathSteps([ + { + id: uniqueId(), + name: "Access to Funding: Eligibility", + path: `${history.location.pathname}${history.location.search}`, + }, + ]); + } + }, []); return ( - + +
+

+ Access to Funding +

+

+ Eligibility +

+
+

+ Below are the components which are eligible for an allocation for + the selected allocation period, according to the Global Fund + Eligibility Policy. Eligibility does not guarantee a funding + allocation. Learn more about Eligibility{" "} + + here + +

+
+
+ +
); } diff --git a/src/app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/data.ts b/src/app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/data.ts new file mode 100644 index 000000000..61c268a00 --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/data.ts @@ -0,0 +1,84 @@ +export const fundingRequestColumns = [ + { + key: "name", + name: "Location", + col: [ + { + key: "component", + name: "Component", + col: [ + { + key: "gac", + name: "", + }, + // { + // key: "board", + // name: "", + // }, + { + key: "grant", + name: "", + }, + { + key: "start", + name: "", + }, + { + key: "end", + name: "", + }, + // { + // key: "recipient", + // name: "", + // }, + { + key: "component", + name: "", + }, + ], + }, + { + key: "date", + name: "Submission Date", + }, + { + key: "approach", + name: "Approach", + }, + { + key: "window", + name: "TRP Window", + }, + { + key: "outcome", + name: "TRP Outcome", + }, + { + key: "portfolioCategory", + name: "Portfolio Categorization", + }, + { + key: "documents", + name: "", + }, + ], + }, +]; + +export const cellData = [ + "Component", + "Submission date", + "Approach", + "TRP Window", + "TRP Outcome", + "Portfolio Categorization", +]; + +export const cellData2 = [ + "GAC Meeting", + // "Board Approval", + "Grant", + "Starting Date", + "End Date", + "Component", +]; diff --git a/src/app/modules/viz-module/sub-modules/eligibility/table/data-wrappers/generic.tsx b/src/app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/generic.tsx similarity index 53% rename from src/app/modules/viz-module/sub-modules/eligibility/table/data-wrappers/generic.tsx rename to src/app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/generic.tsx index 1e1cc5b66..9e6fd79bb 100644 --- a/src/app/modules/viz-module/sub-modules/eligibility/table/data-wrappers/generic.tsx +++ b/src/app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/generic.tsx @@ -2,84 +2,63 @@ import React from "react"; import get from "lodash/get"; import find from "lodash/find"; -import filter from "lodash/filter"; +import orderBy from "lodash/orderBy"; import uniqueId from "lodash/uniqueId"; import { useHistory } from "react-router-dom"; +import { useDebounce, useTitle } from "react-use"; import TablePagination from "@material-ui/core/TablePagination"; -import { useDebounce, useTitle, useUpdateEffect } from "react-use"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; /* project */ +import { InfoIcon } from "app/assets/icons/Info"; import { useCMSData } from "app/hooks/useCMSData"; import { PageLoader } from "app/modules/common/page-loader"; import { SimpleTableRow } from "app/components/Table/Simple/data"; import { getAPIFormattedFilters } from "app/utils/getAPIFormattedFilters"; -import { DotChartModel } from "app/components/Charts/Eligibility/DotChart/data"; -import { EligibilityTable } from "app/modules/viz-module/sub-modules/eligibility/table"; - -function getTableData(data: DotChartModel[]): SimpleTableRow[] { - const updatedTableData: SimpleTableRow[] = []; - data.forEach((item: DotChartModel) => { - let instance = { - name: item.name, - }; - item.items.forEach((subItem: any) => { - instance = { - ...instance, - [subItem.name]: subItem.status, - }; - }); - updatedTableData.push(instance); - }); - return updatedTableData; -} +import { Table } from "app/modules/viz-module/sub-modules/fundingRequests/table"; +import { fundingRequestColumns } from "app/modules/viz-module/sub-modules/fundingRequests/table/data-wrappers/data"; + +export function GenericFundingRequestWrapper() { + useTitle("The Data Explorer - Funding Requests"); -export function GenericEligibilityWrapper() { - useTitle("The Data Explorer - Eligibility"); - const cmsData = useCMSData({ returnData: true }); const history = useHistory(); + const cmsData = useCMSData({ returnData: true }); + const [search, setSearch] = React.useState(""); const [sortBy, setSortBy] = React.useState("name ASC"); - const selectedYear = useStoreState( - (state) => state.ToolBoxPanelEligibilityYearState.value - ); - - const fetchYearOptionsData = useStoreActions( - (store) => store.EligibilityYears.fetch - ); - - const dataPathSteps = useStoreState((state) => state.DataPathSteps.steps); const data = useStoreState( - (state) => get(state.EligibilityTable.data, "data", []) as DotChartModel[] + (state) => + get(state.FundingRequestsTable.data, "data", []) as SimpleTableRow[] ); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); - const [tableData, setTableData] = React.useState( - getTableData(data) - ); - const fetchData = useStoreActions((store) => store.EligibilityTable.fetch); + const fetchData = useStoreActions( + (store) => store.FundingRequestsTable.fetch + ); - const isLoading = useStoreState((state) => state.EligibilityTable.loading); + const isLoading = useStoreState( + (state) => state.FundingRequestsTable.loading + ); const appliedFilters = useStoreState((state) => state.AppliedFiltersState); const addDataPathSteps = useStoreActions( (actions) => actions.DataPathSteps.addSteps ); + const dataPathSteps = useStoreState((state) => state.DataPathSteps.steps); React.useEffect(() => { - fetchYearOptionsData({}); if ( dataPathSteps.length === 0 || - !find(dataPathSteps, { name: "Access to Funding: Eligibility" }) + !find(dataPathSteps, { name: "Access to Funding: Funding Requests" }) ) { addDataPathSteps([ { id: uniqueId(), - name: "Access to Funding: Eligibility", + name: "Access to Funding: Funding Requests", path: `${history.location.pathname}${history.location.search}`, }, ]); @@ -87,36 +66,23 @@ export function GenericEligibilityWrapper() { }, []); function reloadData() { - const filterString = getAPIFormattedFilters(appliedFilters, { + let filterString = getAPIFormattedFilters(appliedFilters, { search, sortBy, }); + if (filterString.length > 0) { + filterString = `&${filterString}`; + } else { + filterString = ""; + } fetchData({ - filterString: `aggregateBy=geographicAreaName&periods=${selectedYear}${ - filterString.length > 0 ? `&${filterString}` : "" - }`, + filterString, }); } - React.useEffect(() => reloadData(), [appliedFilters, selectedYear, sortBy]); - - useUpdateEffect(() => setTableData(getTableData(data)), [data]); + React.useEffect(() => reloadData(), [appliedFilters, sortBy]); - useUpdateEffect(() => { - if (search.length === 0) { - reloadData(); - } - }, [search]); - - const [,] = useDebounce( - () => { - if (search.length > 0) { - reloadData(); - } - }, - 500, - [search] - ); + const [,] = useDebounce(() => reloadData(), 500, [search]); const handleChangePage = ( _event: React.MouseEvent | null, @@ -132,21 +98,18 @@ export function GenericEligibilityWrapper() { setPage(0); }; + const tableData = React.useMemo(() => { + const result = data.map((item) => ({ + ...item, + children: orderBy(item.children, "children", "desc"), + })); + return result.slice(page * rowsPerPage, (page + 1) * rowsPerPage); + }, [data, page, rowsPerPage, sortBy]); + if (isLoading) { return ; } - const columns = - data.length > 0 - ? filter( - Object.keys(tableData.length > 0 ? tableData[0] : {}), - (key) => key !== "children" - ).map((key) => ({ - name: key === "name" ? "Location" : key, - key, - })) - : []; - return (
- {get(cmsData, "componentsChartsEligibility.year", "")} {selectedYear} +
-
- +
+
+ {get(cmsData, "modulesFundingRequests.tableDisclaimer", "")} +
+
); } diff --git a/src/app/modules/viz-module/sub-modules/fundingRequests/table/index.tsx b/src/app/modules/viz-module/sub-modules/fundingRequests/table/index.tsx new file mode 100644 index 000000000..3695e7836 --- /dev/null +++ b/src/app/modules/viz-module/sub-modules/fundingRequests/table/index.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { PageLoader } from "app/modules/common/page-loader"; +import { SimpleTableRow } from "app/components/Table/Simple/data"; +import { + FundingTableColumn, + FundingRequestTable, +} from "app/components/Table/funding"; + +interface FundingRequestTableProps { + search: string; + sortBy: string; + isLoading: boolean; + data: SimpleTableRow[]; + columns: FundingTableColumn[]; + setSearch: (value: string) => void; + setSortBy: (value: string) => void; +} + +export function Table(props: FundingRequestTableProps) { + if (props.isLoading) { + return ; + } + + return ( + + ); +} diff --git a/src/app/modules/viz-module/sub-modules/investments/disbursed/data-wrappers/partnerDetail.tsx b/src/app/modules/viz-module/sub-modules/investments/disbursed/data-wrappers/partnerDetail.tsx index af9419fe4..ce6329772 100644 --- a/src/app/modules/viz-module/sub-modules/investments/disbursed/data-wrappers/partnerDetail.tsx +++ b/src/app/modules/viz-module/sub-modules/investments/disbursed/data-wrappers/partnerDetail.tsx @@ -2,7 +2,6 @@ import React from "react"; import get from "lodash/get"; import isEqual from "lodash/isEqual"; -import { useHistory } from "react-router-dom"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; /* project */ import { getAPIFormattedFilters } from "app/utils/getAPIFormattedFilters"; diff --git a/src/app/modules/viz-module/sub-modules/investments/disbursed/index.tsx b/src/app/modules/viz-module/sub-modules/investments/disbursed/index.tsx index 927309a5f..f0e01312e 100644 --- a/src/app/modules/viz-module/sub-modules/investments/disbursed/index.tsx +++ b/src/app/modules/viz-module/sub-modules/investments/disbursed/index.tsx @@ -89,6 +89,9 @@ export function InvestmentsDisbursedModule( const addDataPathSteps = useStoreActions( (actions) => actions.DataPathSteps.addSteps ); + const clearDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.clear + ); const totalValue = React.useMemo( () => sumBy(props.data, "value"), @@ -235,7 +238,7 @@ export function InvestmentsDisbursedModule( _x: number, _y: number, code?: string, - name?: string + _name?: string ) => { if (props.allowDrilldown) { props.setVizLevel(1); @@ -256,10 +259,12 @@ export function InvestmentsDisbursedModule( .split("-") .slice(0, node.split("-").length - 1) .join("-"); - setReRouteDialog({ - display: true, - code, - }); + // setReRouteDialog({ + // display: true, + // code, + // }); + clearDataPathSteps(); + history.push(`/grant/${code}/period/${clickthroughPath}`); }} /> ); diff --git a/src/app/modules/viz-module/sub-modules/investments/time-cycle/data-wrappers/generic.tsx b/src/app/modules/viz-module/sub-modules/investments/time-cycle/data-wrappers/generic.tsx index 7a6cbd0fe..6a27b94ec 100644 --- a/src/app/modules/viz-module/sub-modules/investments/time-cycle/data-wrappers/generic.tsx +++ b/src/app/modules/viz-module/sub-modules/investments/time-cycle/data-wrappers/generic.tsx @@ -64,7 +64,7 @@ export function GenericInvestmentsTimeCycleWrapper(props: Props) { } }); const data = useStoreState((state) => { - let compData = state.DisbursementsTimeCycle.data; + let compData; switch (props.type) { case "Signed": compData = state.SignedTimeCycle.data; diff --git a/src/app/modules/viz-module/sub-modules/investments/time-cycle/index.tsx b/src/app/modules/viz-module/sub-modules/investments/time-cycle/index.tsx index 6f5c89748..efa4790a8 100644 --- a/src/app/modules/viz-module/sub-modules/investments/time-cycle/index.tsx +++ b/src/app/modules/viz-module/sub-modules/investments/time-cycle/index.tsx @@ -24,7 +24,7 @@ interface InvestmentsTimeCycleModuleProps { setVizLevel: (vizLevel: number) => void; vizSelected: string | undefined; setDrilldownVizSelected?: (vizSelected: string | undefined) => void; - drilldownVizSelected?: string | undefined; + drilldownVizSelected?: string; setVizSelected: (vizSelected: string | undefined) => void; type?: string; toolboxOpen?: boolean; @@ -54,6 +54,9 @@ export function InvestmentsTimeCycleModule( const addDataPathSteps = useStoreActions( (actions) => actions.DataPathSteps.addSteps ); + const clearDataPathSteps = useStoreActions( + (actions) => actions.DataPathSteps.clear + ); React.useEffect(() => { if (props.vizLevel === 0) { @@ -196,10 +199,12 @@ export function InvestmentsTimeCycleModule( onNodeClick={(node: string, _x: number, _y: number) => { const idSplits = node.split("-"); const code = getIso3FromName(idSplits[0]); - setReRouteDialog({ - display: true, - code, - }); + // setReRouteDialog({ + // display: true, + // code, + // }); + clearDataPathSteps(); + history.push(`/grant/${code}/period/${clickthroughPath}`); }} /> ); diff --git a/src/app/state/api/action-reducers/cms/modulesFundingRequests.ts b/src/app/state/api/action-reducers/cms/modulesFundingRequests.ts new file mode 100644 index 000000000..5b15cf9c8 --- /dev/null +++ b/src/app/state/api/action-reducers/cms/modulesFundingRequests.ts @@ -0,0 +1,10 @@ +import { APIModel } from "app/state/api"; +import { CMSApiCallModel } from "app/state/api/interfaces"; + +const modulesFundingRequests: CMSApiCallModel = { + ...APIModel( + `${process.env.REACT_APP_CMS_API}/singletons/get/modulesFundingRequests?token=${process.env.REACT_APP_CMS_TOKEN}` + ), +}; + +export default modulesFundingRequests; diff --git a/src/app/state/api/action-reducers/locationDetail/accessToFunding.ts b/src/app/state/api/action-reducers/locationDetail/accessToFunding.ts new file mode 100644 index 000000000..bcef13f03 --- /dev/null +++ b/src/app/state/api/action-reducers/locationDetail/accessToFunding.ts @@ -0,0 +1,26 @@ +import { APIModel } from "app/state/api"; +import { ApiCallModel } from "app/state/api/interfaces"; + +export const EligibilityLocation: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/eligibility/table`), +}; + +export const FundingRequestsTable: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/funding-requests/table`), +}; + +export const GrantCycles: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/grant-cycles`), +}; + +export const TRPWindowCodelist: ApiCallModel = { + ...APIModel( + `${process.env.REACT_APP_API}/funding-requests/trp-window/codelist` + ), +}; + +export const PortfolioCategoryCodelist: ApiCallModel = { + ...APIModel( + `${process.env.REACT_APP_API}/funding-requests/portfolio-categories/codelist` + ), +}; diff --git a/src/app/state/api/action-reducers/locationDetail/grantCycles.ts b/src/app/state/api/action-reducers/locationDetail/grantCycles.ts new file mode 100644 index 000000000..f868d9063 --- /dev/null +++ b/src/app/state/api/action-reducers/locationDetail/grantCycles.ts @@ -0,0 +1,8 @@ +import { APIModel } from "app/state/api"; +import { ApiCallModel } from "app/state/api/interfaces"; + +const GrantCycles: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/grant-cycles`), +}; + +export default GrantCycles; diff --git a/src/app/state/api/action-reducers/sync/filters/index.ts b/src/app/state/api/action-reducers/sync/filters/index.ts index dafecead3..3f24c11ab 100644 --- a/src/app/state/api/action-reducers/sync/filters/index.ts +++ b/src/app/state/api/action-reducers/sync/filters/index.ts @@ -13,6 +13,8 @@ export const defaultAppliedFilters: AppliedFiltersModel = { donors: [] as string[], donorCategories: [] as string[], donorSubCategories: [] as string[], + trpWindows: [] as string[], + portfolioCategories: [] as string[], }; export interface AppliedFiltersModel { @@ -27,6 +29,8 @@ export interface AppliedFiltersModel { donors: string[]; donorCategories: string[]; donorSubCategories: string[]; + trpWindows: string[]; + portfolioCategories: string[]; } export interface AppliedFiltersStateModel { @@ -52,6 +56,10 @@ export interface AppliedFiltersStateModel { setDonorCategories: Action; donorSubCategories: string[]; setDonorSubCategories: Action; + trpWindows: string[]; + setTrpWindows: Action; + setPortfolioCategories: Action; + portfolioCategories: string[]; setAll: Action; actionDefaultNone: Action; appliedFiltersCount: number; @@ -113,6 +121,16 @@ export const AppliedFiltersState: AppliedFiltersStateModel = { state.donorSubCategories = payload; state.appliedFiltersCount += payload.length; }), + trpWindows: [], + setTrpWindows: action((state, payload: string[]) => { + state.trpWindows = payload; + state.appliedFiltersCount += payload.length; + }), + portfolioCategories: [], + setPortfolioCategories: action((state, payload: string[]) => { + state.portfolioCategories = payload; + state.appliedFiltersCount += payload.length; + }), setAll: action((state, payload: AppliedFiltersModel) => { state.locations = payload.locations; state.components = payload.components; @@ -124,6 +142,8 @@ export const AppliedFiltersState: AppliedFiltersStateModel = { state.donors = payload.donors; state.donorCategories = payload.donorCategories; state.donorSubCategories = payload.donorSubCategories; + state.trpWindows = payload.trpWindows; + state.portfolioCategories = payload.portfolioCategories; state.appliedFiltersCount = payload.locations.length + payload.components.length + @@ -133,7 +153,10 @@ export const AppliedFiltersState: AppliedFiltersStateModel = { payload.status.length + payload.replenishmentPeriods.length + payload.donors.length + - payload.donorCategories.length; + payload.donorCategories.length + + payload.donorSubCategories.length + + payload.trpWindows.length + + payload.portfolioCategories.length; }), actionDefaultNone: action((state, payload: string[]) => { console.log("Incorrect filter type"); diff --git a/src/app/state/api/action-reducers/viz/eligibility.ts b/src/app/state/api/action-reducers/viz/eligibility.ts index 2b71d78c9..a8a13aabe 100644 --- a/src/app/state/api/action-reducers/viz/eligibility.ts +++ b/src/app/state/api/action-reducers/viz/eligibility.ts @@ -14,3 +14,13 @@ export const EligibilityYears: ApiCallModel = { export const EligibilityTable: ApiCallModel = { ...APIModel(`${process.env.REACT_APP_API}/eligibility`), }; + +export const EligibilityStatusCodelist: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/eligibility/status/codelist`), +}; + +export const EligibilityDiseaseBurdenCodelist: ApiCallModel = { + ...APIModel( + `${process.env.REACT_APP_API}/eligibility/disease-burden/codelist` + ), +}; diff --git a/src/app/state/api/action-reducers/viz/fundingRequests.ts b/src/app/state/api/action-reducers/viz/fundingRequests.ts new file mode 100644 index 000000000..ddcf8a118 --- /dev/null +++ b/src/app/state/api/action-reducers/viz/fundingRequests.ts @@ -0,0 +1,6 @@ +import { APIModel } from "app/state/api"; +import { ApiCallModel } from "app/state/api/interfaces"; + +export const FundingRequestsTableGeneric: ApiCallModel = { + ...APIModel(`${process.env.REACT_APP_API}/funding-requests/table`), +}; diff --git a/src/app/state/api/interfaces/cms.ts b/src/app/state/api/interfaces/cms.ts index 9f11b04c4..37ea963dc 100644 --- a/src/app/state/api/interfaces/cms.ts +++ b/src/app/state/api/interfaces/cms.ts @@ -333,6 +333,12 @@ export interface CMSApiModulesGrants { }; } +export interface CMSApiModulesFundingRequests { + data: { + tableDisclaimer: string; + }; +} + // COUNTRY SUMMARY export interface CMSApiCountrySummary { entries: { diff --git a/src/app/state/api/interfaces/index.ts b/src/app/state/api/interfaces/index.ts index fce3ff012..54a4aa144 100644 --- a/src/app/state/api/interfaces/index.ts +++ b/src/app/state/api/interfaces/index.ts @@ -43,6 +43,7 @@ import { CMSApiModulesGrants, CMSApiCountrySummary, CMSApiNotesAndDisclaimers, + CMSApiModulesFundingRequests, } from "app/state/api/interfaces/cms"; import { DataPathActiveStepStateModel, @@ -131,6 +132,7 @@ export type CMSApiCallModel = ApiModel< | CMSApiModulesGrants | CMSApiCountrySummary | CMSApiNotesAndDisclaimers + | CMSApiModulesFundingRequests >; export interface CMSApiCallParams {} @@ -148,6 +150,8 @@ export interface StoreModel { Eligibility: ApiCallModel; EligibilityTable: ApiCallModel; EligibilityYears: ApiCallModel; + EligibilityStatusCodelist: ApiCallModel; + EligibilityDiseaseBurdenCodelist: ApiCallModel; BudgetsFlow: ApiCallModel; BudgetsFlowDrilldownLevel1: ApiCallModel; BudgetsFlowDrilldownLevel2: ApiCallModel; @@ -181,6 +185,7 @@ export interface StoreModel { ResultsList: ApiCallModel; ResultsStats: ApiCallModel; ResultsYears: ApiCallModel; + FundingRequestsTable: ApiCallModel; // global search GlobalSearch: ApiCallModel; // grant detail api @@ -217,6 +222,13 @@ export interface StoreModel { LocationDetailBudgetsTimeCycleDrilldownLevel1: ApiCallModel; LocationDetailBudgetsTimeCycleDrilldownLevel2: ApiCallModel; LocationGrants: ApiCallModel; + LocationAccessToFunding: { + EligibilityTable: ApiCallModel; + FundingRequestsTable: ApiCallModel; + GrantCycles: ApiCallModel; + }; + FundingRequestsTRPWindowCodelist: ApiCallModel; + FundingRequestsPortfolioCategoryCodelist: ApiCallModel; // partner detail api PartnerDetailInfo: ApiCallModel; PartnerDetailDisbursementsTreemap: ApiCallModel; @@ -288,6 +300,7 @@ export interface StoreModel { modulesDatasets: CMSApiCallModel; modulesGrantDetail: CMSApiCallModel; modulesGrants: CMSApiCallModel; + modulesFundingRequests: CMSApiCallModel; countrySummary: CMSApiCallModel; notesAndDisclaimers: CMSApiCallModel; }; diff --git a/src/app/state/recoil/atoms/index.ts b/src/app/state/recoil/atoms/index.ts index 7147d0fba..d18e32295 100644 --- a/src/app/state/recoil/atoms/index.ts +++ b/src/app/state/recoil/atoms/index.ts @@ -64,3 +64,9 @@ export const selectedViewAtom = atom({ default: "", effects_UNSTABLE: [persistAtom], }); + +export const locationAccessToFundingCycleAtom = atom({ + key: "locationAccessToFundingCycleAtom", + default: null, + effects_UNSTABLE: [persistAtom], +}); diff --git a/src/app/state/store/index.ts b/src/app/state/store/index.ts index 780de4ece..9ab2ff5fc 100644 --- a/src/app/state/store/index.ts +++ b/src/app/state/store/index.ts @@ -31,6 +31,8 @@ import BudgetsFlow, { BudgetsFlowDrilldownLevel2, } from "app/state/api/action-reducers/viz/budgetsFlow"; import Eligibility, { + EligibilityDiseaseBurdenCodelist, + EligibilityStatusCodelist, EligibilityTable, EligibilityYears, } from "app/state/api/action-reducers/viz/eligibility"; @@ -170,6 +172,15 @@ import { DataPathStepsState, } from "../api/action-reducers/sync/dataPath"; import PledgesContributionsTable from "../api/action-reducers/viz/pledgesContributionsTable"; +import { + EligibilityLocation, + FundingRequestsTable, + GrantCycles, + PortfolioCategoryCodelist, + TRPWindowCodelist, +} from "../api/action-reducers/locationDetail/accessToFunding"; +import { FundingRequestsTableGeneric } from "../api/action-reducers/viz/fundingRequests"; +import modulesFundingRequests from "../api/action-reducers/cms/modulesFundingRequests"; import componentsSidebar from "../api/action-reducers/cms/componentsSidebar"; import componentsTable from "../api/action-reducers/cms/componentsTable"; import componentsDialogBox from "../api/action-reducers/cms/componentsDialogBox"; @@ -190,6 +201,8 @@ const storeContent: StoreModel = { Eligibility: persist(Eligibility), EligibilityTable: persist(EligibilityTable), EligibilityYears: persist(EligibilityYears), + EligibilityStatusCodelist: persist(EligibilityStatusCodelist), + EligibilityDiseaseBurdenCodelist: persist(EligibilityDiseaseBurdenCodelist), BudgetsGeomap: persist(BudgetsGeomap), BudgetsMCGeomap: persist(BudgetsMCGeomap), BudgetsTimeCycle: persist(BudgetsTimeCycle), @@ -222,6 +235,7 @@ const storeContent: StoreModel = { ResultsList: persist(ResultsList), ResultsStats: persist(ResultsStats), ResultsYears: persist(ResultsYears), + FundingRequestsTable: persist(FundingRequestsTableGeneric), // global search GlobalSearch: persist(GlobalSearch), // grant detail api @@ -278,6 +292,13 @@ const storeContent: StoreModel = { LocationDetailBudgetsTimeCycleDrilldownLevel1 ), LocationGrants: persist(LocationGrants), + LocationAccessToFunding: { + EligibilityTable: persist(EligibilityLocation), + FundingRequestsTable: persist(FundingRequestsTable), + GrantCycles: persist(GrantCycles), + }, + FundingRequestsTRPWindowCodelist: persist(TRPWindowCodelist), + FundingRequestsPortfolioCategoryCodelist: persist(PortfolioCategoryCodelist), // partner detail api PartnerDetailInfo: persist(PartnerDetailInfo), PartnerDetailDisbursementsTreemap: persist(PartnerDetailDisbursementsTreemap), @@ -372,6 +393,7 @@ const storeContent: StoreModel = { modulesDatasets: persist(modulesDatasets), modulesGrantDetail: persist(modulesGrantDetail), modulesGrants: persist(modulesGrants), + modulesFundingRequests: persist(modulesFundingRequests), countrySummary: persist(countrySummary), notesAndDisclaimers: persist(notesAndDisclaimers), }, diff --git a/src/app/theme/index.ts b/src/app/theme/index.ts index b3b8242ba..225e4d06d 100644 --- a/src/app/theme/index.ts +++ b/src/app/theme/index.ts @@ -591,8 +591,8 @@ export const appColors = { MENU_ITEM_COLOR: PRIMARY_COLOR_1, BUTTON_BACKGROUND_COLOR: SECONDARY_COLOR_7, BUTTON_BACKGROUND_HOVER_COLOR: SECONDARY_COLOR_7, - LINK_BACKGROUND_COLOR: PRIMARY_COLOR_1, - LINK_BACKGROUND_SELECTED_COLOR: SECONDARY_COLOR_7, + LINK_BACKGROUND_COLOR: SECONDARY_COLOR_7, + LINK_BACKGROUND_SELECTED_COLOR: PRIMARY_COLOR_1, LINK_ICON_COLOR: "#868A9D", LINK_ICON_SELECTED_COLOR: WHITE, BUTTON_COLOR: SECONDARY_COLOR_7, @@ -663,7 +663,7 @@ export const appColors = { ROW_TEXT_HOVER_COLOR: WHITE, ROW_TEXT_COLOR: PRIMARY_COLOR_1, DOWNLOAD_ICON_COLOR: PRIMARY_COLOR_2, - TOOLBAR_BACKGROUND_COLOR: "#dfe3e5", + TOOLBAR_BACKGROUND_COLOR: SECONDARY_COLOR_10, TOOLBAR_ICON_BUTTON_HOVER_BACKGROUND_COLOR: PRIMARY_COLOR_1, TOOLBAR_ICON_BUTTON_HOVER_COLOR: WHITE, TOOLBAR_SEARCH_TEXT_COLOR: PRIMARY_COLOR_1, diff --git a/src/app/utils/exportCSV.ts b/src/app/utils/exportCSV.ts index 70664ab1f..a791d4d01 100644 --- a/src/app/utils/exportCSV.ts +++ b/src/app/utils/exportCSV.ts @@ -21,7 +21,8 @@ export function exportCSV( donorMapView: string; isDetail: boolean; resultsSelectedYear: string; - } + }, + multiVizPageDataKey?: string ): CommonPropTypes { const csvData: any[] = []; const isComponent = options.selectedAggregation === "componentName"; @@ -703,51 +704,164 @@ export function exportCSV( ], }; case "/viz/eligibility/table": - if (options.isDetail) { + data.forEach((item: any) => { + item.children.forEach((subItem: any) => { + subItem.children.forEach((subSubItem: any) => { + csvData.push( + isComponent + ? { + component: item.level1, + year: subItem.level1, + location: subSubItem.level2, + status: subSubItem.eligibilityStatus, + diseaseBurden: subSubItem.diseaseBurden, + incomeLevel: subSubItem.incomeLevel, + } + : { + location: item.level1, + year: subItem.level1, + component: subSubItem.level2, + status: subSubItem.eligibilityStatus, + diseaseBurden: subSubItem.diseaseBurden, + incomeLevel: subItem.incomeLevel, + } + ); + }); + }); + }); + return { + data: csvData, + filename: `eligibility-by-${ + isComponent ? "component" : "location" + }.csv`, + headers: isComponent + ? [ + { label: "Component", key: "component" }, + { label: "Year", key: "year" }, + { label: "Location", key: "location" }, + { label: "Status", key: "status" }, + { label: "Disease Burden", key: "diseaseBurden" }, + { label: "Income Level", key: "incomeLevel" }, + ] + : [ + { label: "Location", key: "location" }, + { label: "Year", key: "year" }, + { label: "Component", key: "component" }, + { label: "Status", key: "status" }, + { label: "Disease Burden", key: "diseaseBurden" }, + { label: "Income Level", key: "incomeLevel" }, + ], + }; + case "/viz/access-to-funding": + if (multiVizPageDataKey === "eligibility") { data.forEach((item: any) => { - csvData.push({ - year: item.x, - component: item.y, - incomeLevel: get( - incomeLevels, - `[${item.incomeLevel}]`, - item.incomeLevel - ), - diseaseBurden: get(diseaseBurdens, `[${item.diseaseBurden}]`, ""), - eligibility: item.eligibility, + item.children.forEach((subItem: any) => { + subItem.children.forEach((subSubItem: any) => { + csvData.push( + isComponent + ? { + component: item.level1, + year: subItem.level1, + location: subSubItem.level2, + status: subSubItem.eligibilityStatus, + diseaseBurden: subSubItem.diseaseBurden, + incomeLevel: subSubItem.incomeLevel, + } + : { + location: item.level1, + year: subItem.level1, + component: subSubItem.level2, + status: subSubItem.eligibilityStatus, + diseaseBurden: subSubItem.diseaseBurden, + incomeLevel: subItem.incomeLevel, + } + ); + }); }); }); return { data: csvData, - filename: "location-eligibility.csv", + filename: `access-to-funding-eligibility-by-${ + isComponent ? "component" : "location" + }.csv`, + headers: isComponent + ? [ + { label: "Component", key: "component" }, + { label: "Year", key: "year" }, + { label: "Location", key: "location" }, + { label: "Status", key: "status" }, + { label: "Disease Burden", key: "diseaseBurden" }, + { label: "Income Level", key: "incomeLevel" }, + ] + : [ + { label: "Location", key: "location" }, + { label: "Year", key: "year" }, + { label: "Component", key: "component" }, + { label: "Status", key: "status" }, + { label: "Disease Burden", key: "diseaseBurden" }, + { label: "Income Level", key: "incomeLevel" }, + ], + }; + } + if (multiVizPageDataKey === "fundingRequest") { + data.forEach((item: any) => { + item.children.forEach((subItem: any) => { + if (subItem.children.length === 0) { + csvData.push({ + location: item.name, + component: subItem.component, + date: subItem.approach, + approach: subItem.approach, + window: subItem.window, + outcome: subItem.outcome, + gac: subItem.gac, + board: subItem.board, + portfolioCategory: subItem.portfolioCategory, + }); + } else { + subItem.children.forEach((subSubItem: any) => { + csvData.push({ + location: item.name, + component: subSubItem.component, + date: subItem.approach, + approach: subItem.approach, + window: subItem.window, + outcome: subItem.outcome, + board: subSubItem.board, + portfolioCategory: subItem.portfolioCategory, + grant: subSubItem.grant1, + gac: subSubItem.gac, + start: subSubItem.start, + end: subSubItem.end, + }); + }); + } + }); + }); + return { + data: csvData, + filename: "access-to-funding-funding-requests.csv", headers: [ - { label: "Year", key: "year" }, + { label: "Location", key: "location" }, + { label: "Submission date", key: "date" }, { label: "Component", key: "component" }, - { label: "Income Level", key: "incomeLevel" }, - { label: "Disease Burden", key: "diseaseBurden" }, - { label: "Status", key: "eligibility" }, + { label: "Approach", key: "approach" }, + { label: "TRP Window", key: "window" }, + { label: "TRP Outcome", key: "outcome" }, + { label: "GAC Meeting", key: "gac" }, + { label: "Portfolio Categorization", key: "portfolioCategory" }, + { label: "GAC Meeting", key: "gac" }, + // { label: "Board approval", key: "board" }, + { label: "Grant", key: "grant" }, + { label: "Starting date", key: "start" }, + { label: "End date", key: "end" }, ], }; } - data.forEach((item: any) => { - item.items.forEach((subitem: any) => { - csvData.push({ - [isComponent ? "component" : "location"]: item.name, - [!isComponent ? "component" : "location"]: subitem.name, - status: subitem.status, - }); - }); - }); return { - data: csvData, - filename: `eligibility-by-${ - isComponent ? "component" : "location" - }-${yearDropdownNode?.getAttribute("value")}.csv`, - headers: [ - { label: "Component", key: "component" }, - { label: "Location", key: "location" }, - { label: "Status", key: "status" }, - ], + data: [], + filename: "empty.csv", + headers: [], }; case "/viz/pledges-contributions/time-cycle": data.forEach((item: any) => { diff --git a/src/app/utils/exportView.ts b/src/app/utils/exportView.ts index a482d4aca..99c8dad6d 100644 --- a/src/app/utils/exportView.ts +++ b/src/app/utils/exportView.ts @@ -29,8 +29,6 @@ function getFileName( return "allocations"; case "/viz/allocations/map": return `allocations-${options.investmentsMapView}`; - case "/viz/allocations": - return "allocations"; case "/viz/eligibility": if (options.isDetail) { return "location-eligibility"; diff --git a/src/app/utils/getAPIFormattedFilters.ts b/src/app/utils/getAPIFormattedFilters.ts index 5bd805fe2..5d50dec97 100644 --- a/src/app/utils/getAPIFormattedFilters.ts +++ b/src/app/utils/getAPIFormattedFilters.ts @@ -79,6 +79,14 @@ export function getAPIFormattedFilters( `periods=${appliedFilters.replenishmentPeriods.join(",")}` ); } + if (appliedFilters.trpWindows.length > 0) { + filterArray.push(`trpWindows=${appliedFilters.trpWindows.join(",")}`); + } + if (appliedFilters.portfolioCategories.length > 0) { + filterArray.push( + `portfolioCategories=${appliedFilters.portfolioCategories.join(",")}` + ); + } return filterArray.join("&"); }