From dfd13413d09c420921893ee7cfecdbf2a21413cf Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 28 Mar 2024 15:50:22 +0200 Subject: [PATCH] feat: table component visual implementation --- package.json | 2 + .../assets/vectors/TableToolbarColumns.svg | 11 + .../assets/vectors/TableToolbarDownload.svg | 11 + src/app/assets/vectors/TableToolbarFilter.svg | 4 + .../assets/vectors/TableToolbarFullscreen.svg | 4 + src/app/assets/vectors/TableToolbarSearch.svg | 9 + .../TableContainer.stories.tsx | 69 ++ src/app/components/table-container/data.ts | 17 + src/app/components/table-container/index.tsx | 134 +++ src/app/components/table/Table.stories.tsx | 100 ++ src/app/components/table/data.ts | 856 ++++++++++++++++++ src/app/components/table/index.tsx | 85 ++ .../views/access-to-funding/index.tsx | 38 +- .../views/grant-implementation/index.tsx | 21 +- yarn.lock | 10 + 15 files changed, 1340 insertions(+), 31 deletions(-) create mode 100644 src/app/assets/vectors/TableToolbarColumns.svg create mode 100644 src/app/assets/vectors/TableToolbarDownload.svg create mode 100644 src/app/assets/vectors/TableToolbarFilter.svg create mode 100644 src/app/assets/vectors/TableToolbarFullscreen.svg create mode 100644 src/app/assets/vectors/TableToolbarSearch.svg create mode 100644 src/app/components/table-container/TableContainer.stories.tsx create mode 100644 src/app/components/table-container/data.ts create mode 100644 src/app/components/table-container/index.tsx create mode 100644 src/app/components/table/Table.stories.tsx create mode 100644 src/app/components/table/data.ts create mode 100644 src/app/components/table/index.tsx diff --git a/package.json b/package.json index 5612449c8..c1da9a80a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@mui/styled-engine-sc": "^5.12.0", "@storybook/addon-themes": "^7.6.17", "@types/react-router-hash-link": "^2.4.9", + "@types/tabulator-tables": "^5.6.0", "axios": "^1.4.0", "dom-to-image": "^2.6.0", "easy-peasy": "^6.0.1", @@ -30,6 +31,7 @@ "react-scripts": "5.0.1", "react-use": "^17.4.0", "styled-components": "^6.0.3", + "tabulator-tables": "^6.1.0", "typescript": "^4.4.2", "web-vitals": "^2.1.0" }, diff --git a/src/app/assets/vectors/TableToolbarColumns.svg b/src/app/assets/vectors/TableToolbarColumns.svg new file mode 100644 index 000000000..f55a7a51d --- /dev/null +++ b/src/app/assets/vectors/TableToolbarColumns.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/app/assets/vectors/TableToolbarDownload.svg b/src/app/assets/vectors/TableToolbarDownload.svg new file mode 100644 index 000000000..e0761a1f8 --- /dev/null +++ b/src/app/assets/vectors/TableToolbarDownload.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/app/assets/vectors/TableToolbarFilter.svg b/src/app/assets/vectors/TableToolbarFilter.svg new file mode 100644 index 000000000..f70ce8c6d --- /dev/null +++ b/src/app/assets/vectors/TableToolbarFilter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/assets/vectors/TableToolbarFullscreen.svg b/src/app/assets/vectors/TableToolbarFullscreen.svg new file mode 100644 index 000000000..fc0a689cc --- /dev/null +++ b/src/app/assets/vectors/TableToolbarFullscreen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/app/assets/vectors/TableToolbarSearch.svg b/src/app/assets/vectors/TableToolbarSearch.svg new file mode 100644 index 000000000..1f2ae7979 --- /dev/null +++ b/src/app/assets/vectors/TableToolbarSearch.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/app/components/table-container/TableContainer.stories.tsx b/src/app/components/table-container/TableContainer.stories.tsx new file mode 100644 index 000000000..2c1c28223 --- /dev/null +++ b/src/app/components/table-container/TableContainer.stories.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import type { Meta, StoryObj } from "@storybook/react"; + +import { TableContainer } from "app/components/table-container"; +import { withRouter } from "storybook-addon-react-router-v6"; +import { + TABLE_VARIATION_1_DATA, + TABLE_VARIATION_1_COLUMNS, + TABLE_VARIATION_2_DATA, + TABLE_VARIATION_2_COLUMNS, + TABLE_VARIATION_3_DATA, + TABLE_VARIATION_3_COLUMNS, + TABLE_VARIATION_4_DATA, + TABLE_VARIATION_4_COLUMNS, + TABLE_VARIATION_5_DATA, + TABLE_VARIATION_5_COLUMNS, +} from "app/components/table/data"; + +const Variant2Wrapper = (args: any) => { + const [tab, setTab] = React.useState(args.tabsView?.selectedTab); + + return ( + + ); +}; + +const meta = { + title: "Components/Table Container", + component: Variant2Wrapper, + decorators: [withRouter], + parameters: { + layout: "fullscreen", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type StoryType = StoryObj; + +export const Variant1: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_1_DATA, + columns: TABLE_VARIATION_1_COLUMNS, + }, +}; + +export const Variant2: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_1_DATA, + columns: TABLE_VARIATION_1_COLUMNS, + tabsView: { + tabs: ["All", "Impact Indicators", "Outcome Indicators"], + selectedTab: "All", + onTabChange: (tab: string) => console.log(tab), + }, + }, +}; diff --git a/src/app/components/table-container/data.ts b/src/app/components/table-container/data.ts new file mode 100644 index 000000000..dff4b967c --- /dev/null +++ b/src/app/components/table-container/data.ts @@ -0,0 +1,17 @@ +import { TableProps } from "app/components/table/data"; + +export interface TableContainerProps extends TableProps { + tabsView?: { + tabs: string[]; + selectedTab: string; + onTabChange: (tab: string) => void; + }; +} + +export const TABLE_CONTAINER_TABS = [ + "All", + "Impact Indicators", + "Outcome Indicators", + "Modules & Coverage Indicators", + "Modules & Workplan Tracking Measures", +]; diff --git a/src/app/components/table-container/index.tsx b/src/app/components/table-container/index.tsx new file mode 100644 index 000000000..81ce1de92 --- /dev/null +++ b/src/app/components/table-container/index.tsx @@ -0,0 +1,134 @@ +import React from "react"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import { Table } from "app/components/table"; +import IconButton from "@mui/material/IconButton"; +import { TabulatorFull as Tabulator } from "tabulator-tables"; +import { TableContainerProps } from "app/components/table-container/data"; +import { ReactComponent as FilterIcon } from "app/assets/vectors/TableToolbarFilter.svg"; +import { ReactComponent as SearchIcon } from "app/assets/vectors/TableToolbarSearch.svg"; +import { ReactComponent as ColumnsIcon } from "app/assets/vectors/TableToolbarColumns.svg"; +import { ReactComponent as DownloadIcon } from "app/assets/vectors/TableToolbarDownload.svg"; +import { ReactComponent as FullscreenIcon } from "app/assets/vectors/TableToolbarFullscreen.svg"; + +export const TableContainer: React.FC = ( + props: TableContainerProps +) => { + const [table, setTable] = React.useState(null); + const fullscreenRef = React.useRef(null); + + React.useEffect(() => { + const element = document.getElementById("table"); + if (element) { + const tabulatorTables = Tabulator.findTable("#table"); + if (tabulatorTables.length > 0 && tabulatorTables[0]) { + setTable(tabulatorTables[0]); + } + } + }, []); + + const download = () => { + if (table) { + table.download("csv", "data.csv"); + } + }; + + const fullscreen = () => { + if (!fullscreenRef.current) { + return; + } + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + fullscreenRef.current.requestFullscreen(); + } + }; + + return ( + + {!props.tabsView && ( + button": { + padding: "0px", + }, + }} + > + + + + + + + + + + + + + + + + + )} + {props.tabsView && ( + + button": { + height: "32px", + fontSize: "14px", + fontWeight: "400", + padding: "0px 24px", + borderRadius: "8px", + textTransform: "none", + "&:hover": { + color: "#fff", + background: "#000", + }, + }, + }} + > + {props.tabsView.tabs.map((tab) => ( + + ))} + + + + + + )} + + + ); +}; diff --git a/src/app/components/table/Table.stories.tsx b/src/app/components/table/Table.stories.tsx new file mode 100644 index 000000000..333770186 --- /dev/null +++ b/src/app/components/table/Table.stories.tsx @@ -0,0 +1,100 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { Table } from "app/components/table"; +import { withRouter } from "storybook-addon-react-router-v6"; +import { + TABLE_VARIATION_1_DATA, + TABLE_VARIATION_1_COLUMNS, + TABLE_VARIATION_2_DATA, + TABLE_VARIATION_2_COLUMNS, + TABLE_VARIATION_3_DATA, + TABLE_VARIATION_3_COLUMNS, + TABLE_VARIATION_4_DATA, + TABLE_VARIATION_4_COLUMNS, + TABLE_VARIATION_5_DATA, + TABLE_VARIATION_5_COLUMNS, + TABLE_VARIATION_6_DATA, + TABLE_VARIATION_6_COLUMNS, + TABLE_VARIATION_7_DATA, + TABLE_VARIATION_7_COLUMNS, +} from "app/components/table/data"; + +const meta = { + title: "Components/Table", + component: Table, + decorators: [withRouter], + parameters: { + layout: "fullscreen", + }, + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; +type StoryType = StoryObj; + +export const Variant1: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_1_DATA, + columns: TABLE_VARIATION_1_COLUMNS, + }, +}; + +export const Variant2: StoryType = { + args: { + dataTree: true, + dataTreeBranchElement: false, + data: TABLE_VARIATION_2_DATA, + columns: TABLE_VARIATION_2_COLUMNS.slice(0, 7), + extraColumns: TABLE_VARIATION_2_COLUMNS.slice( + 7, + TABLE_VARIATION_2_COLUMNS.length - 1 + ), + }, +}; + +export const Variant3: StoryType = { + args: { + dataTree: true, + dataTreeBranchElement: false, + data: TABLE_VARIATION_3_DATA, + columns: TABLE_VARIATION_3_COLUMNS, + }, +}; + +export const Variant4: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_4_DATA, + columns: TABLE_VARIATION_4_COLUMNS.slice(0, 1), + extraColumns: TABLE_VARIATION_4_COLUMNS.slice( + 1, + TABLE_VARIATION_2_COLUMNS.length - 1 + ), + }, +}; + +export const Variant5: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_5_DATA, + columns: TABLE_VARIATION_5_COLUMNS, + }, +}; + +export const Variant6: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_6_DATA, + columns: TABLE_VARIATION_6_COLUMNS, + }, +}; + +export const Variant7: StoryType = { + args: { + dataTree: true, + data: TABLE_VARIATION_7_DATA, + columns: TABLE_VARIATION_7_COLUMNS, + }, +}; diff --git a/src/app/components/table/data.ts b/src/app/components/table/data.ts new file mode 100644 index 000000000..122e6dfb3 --- /dev/null +++ b/src/app/components/table/data.ts @@ -0,0 +1,856 @@ +import { formatLocale } from "app/utils/formatLocale"; +import { + CellComponent, + ColumnDefinition, + TabulatorFull as Tabulator, +} from "tabulator-tables"; + +export interface TableProps { + data: { + [key: string]: string | number | boolean | null | object | Array; + }[]; + columns: ColumnDefinition[]; + dataTree?: boolean; + dataTreeBranchElement?: boolean; + extraColumns?: ColumnDefinition[]; +} + +const cellBGColorFormatter = (cell: CellComponent) => { + if (!cell.getValue()) { + cell.getElement().style.backgroundColor = "#DFE3E5"; + } + return cell.getValue(); +}; + +const financialFormatter = (cell: CellComponent) => { + if (!cell.getValue()) { + return cellBGColorFormatter(cell); + } + return formatLocale(cell.getValue()); +}; + +export const TABLE_VARIATION_1_COLUMNS: ColumnDefinition[] = [ + { title: "Name", field: "name" }, + { + title: "Years", + columns: [ + { title: "2002", field: "2002" }, + { title: "2003", field: "2003" }, + { title: "2004", field: "2004" }, + { title: "2005", field: "2005" }, + ], + }, + { + title: "Components", + columns: [ + { title: "HIV", field: "HIV" }, + { title: "Tuberculosis", field: "Tuberculosis" }, + { title: "Malaria", field: "Malaria" }, + { title: "Others", field: "Others" }, + ], + }, +]; + +export const TABLE_VARIATION_1_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + name: "Africa", + 2002: 100, + 2003: 120, + 2004: 140, + 2005: 160, + HIV: 10, + Tuberculosis: 20, + Malaria: 30, + Others: 40, + _children: [ + { + name: "Eastern Africa", + 2002: 10, + 2003: 12, + 2004: 14, + 2005: 16, + HIV: 1, + Tuberculosis: 2, + Malaria: 3, + Others: 4, + _children: [ + { + name: "Kenya", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + { + name: "Uganda", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + ], + }, + { + name: "Western Africa", + 2002: 10, + 2003: 12, + 2004: 14, + 2005: 16, + HIV: 1, + Tuberculosis: 2, + Malaria: 3, + Others: 4, + _children: [ + { + name: "Nigeria", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + { + name: "Ghana", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + ], + }, + ], + }, + { + name: "Asia", + 2002: 100, + 2003: 120, + 2004: 140, + 2005: 160, + HIV: 10, + Tuberculosis: 20, + Malaria: 30, + Others: 40, + _children: [ + { + name: "Eastern Asia", + 2002: 10, + 2003: 12, + 2004: 14, + 2005: 16, + HIV: 1, + Tuberculosis: 2, + Malaria: 3, + Others: 4, + _children: [ + { + name: "China", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + { + name: "Japan", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + ], + }, + { + name: "Southern Asia", + 2002: 10, + 2003: 12, + 2004: 14, + 2005: 16, + HIV: 1, + Tuberculosis: 2, + Malaria: 3, + Others: 4, + _children: [ + { + name: "India", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + { + name: "Pakistan", + 2002: 1, + 2003: 1.2, + 2004: 1.4, + 2005: 1.6, + HIV: 0.1, + Tuberculosis: 0.2, + Malaria: 0.3, + Others: 0.4, + }, + ], + }, + ], + }, +]; + +export const TABLE_VARIATION_2_COLUMNS: ColumnDefinition[] = [ + { title: "Components", field: "components", formatter: cellBGColorFormatter }, + { + title: "Submission Date", + field: "submissionDate", + formatter: cellBGColorFormatter, + }, + { title: "Approach", field: "approach", formatter: cellBGColorFormatter }, + { title: "TRP Window", field: "trpWindow", formatter: cellBGColorFormatter }, + { + title: "TRP Outcome", + field: "trpOutcome", + formatter: cellBGColorFormatter, + }, + { + title: "Portfolio Categorization", + field: "portfolioCategorization", + formatter: cellBGColorFormatter, + }, + { + title: "Board Approval", + field: "boardApproval", + formatter: cellBGColorFormatter, + }, + { + title: "GAC Meeting", + field: "gacMeeting", + formatter: cellBGColorFormatter, + }, + { title: "Grant", field: "grant", formatter: cellBGColorFormatter }, + { + title: "Starting date", + field: "startingDate", + formatter: cellBGColorFormatter, + }, + { + title: "Ending date", + field: "endingDate", + formatter: cellBGColorFormatter, + }, + { + title: "Principal Recipient", + field: "principalRecipient", + formatter: cellBGColorFormatter, + }, + { title: "Component", field: "component", formatter: cellBGColorFormatter }, +]; + +export const TABLE_VARIATION_2_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + components: "HIV", + submissionDate: "23 March 2023", + approach: "Program continuation", + trpWindow: "B2", + trpOutcome: "Grant making", + portfolioCategorization: "--", + boardApproval: "November 2023", + _children: [ + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "HIV", + }, + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "HIV", + }, + ], + }, + { + components: "Malaria", + submissionDate: "23 March 2023", + approach: "Program continuation", + trpWindow: "B2", + trpOutcome: "Grant making", + portfolioCategorization: "--", + boardApproval: "November 2023", + _children: [ + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "Malaria", + }, + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH002", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "HIV", + }, + ], + }, + { + components: "Tuberculosis", + submissionDate: "23 March 2023", + approach: "Program continuation", + trpWindow: "B2", + trpOutcome: "Grant making", + portfolioCategorization: "--", + boardApproval: "November 2023", + _children: [ + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "Tuberculosis", + }, + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH002", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "Tuberculosis", + }, + ], + }, + { + components: "HIV", + submissionDate: "23 March 2023", + approach: "Program continuation", + trpWindow: "B2", + trpOutcome: "Grant making", + portfolioCategorization: "--", + boardApproval: "November 2023", + _children: [ + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "HIV", + }, + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "HIV", + }, + ], + }, + { + components: "Malaria", + submissionDate: "23 March 2023", + approach: "Program continuation", + trpWindow: "B2", + trpOutcome: "Grant making", + portfolioCategorization: "--", + boardApproval: "November 2023", + _children: [ + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "Malaria", + }, + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH002", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "HIV", + }, + ], + }, + { + components: "Tuberculosis", + submissionDate: "23 March 2023", + approach: "Program continuation", + trpWindow: "B2", + trpOutcome: "Grant making", + portfolioCategorization: "--", + boardApproval: "November 2023", + _children: [ + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH001", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "Tuberculosis", + }, + { + gacMeeting: "November 2022", + grant: "AFG-H-MOH002", + startingDate: "31-12-2023", + endingDate: "31-12-2025", + principalRecipient: "Private health ministry of Ghana", + component: "Tuberculosis", + }, + ], + }, +]; + +export const TABLE_VARIATION_3_COLUMNS: ColumnDefinition[] = [ + { title: "Location", field: "name", formatter: cellBGColorFormatter }, + { title: "2023", field: "2023", formatter: cellBGColorFormatter }, + { title: "2022", field: "2022", formatter: cellBGColorFormatter }, + { title: "2021", field: "2021", formatter: cellBGColorFormatter }, + { title: "2020", field: "2020", formatter: cellBGColorFormatter }, + { title: "2019", field: "2019", formatter: cellBGColorFormatter }, +]; + +export const TABLE_VARIATION_3_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + name: "Algeria", + _children: [ + { + name: "HIV", + 2023: "Eligible", + 2022: "Eligible", + 2021: "Eligible", + 2020: "Eligible", + 2019: "Eligible", + _children: [ + { + name: "Burden Disease", + 2023: "Low", + 2022: "Low", + 2021: "Low", + 2020: "Low", + 2019: "Low", + }, + ], + }, + ], + }, + { + name: "Kenya", + _children: [ + { + name: "HIV", + 2023: "Eligible", + 2022: "Eligible", + 2021: "Eligible", + 2020: "Eligible", + 2019: "Eligible", + _children: [ + { + name: "Burden Disease", + 2023: "Low", + 2022: "Low", + 2021: "Low", + 2020: "Low", + 2019: "Low", + }, + ], + }, + ], + }, +]; + +export const TABLE_VARIATION_4_COLUMNS: ColumnDefinition[] = [ + { + title: "Modules & Coverage Indicators", + field: "name", + formatter: cellBGColorFormatter, + }, + { + title: "Reversed", + columns: [ + { + title: "Value", + field: "reversed", + formatter: cellBGColorFormatter, + }, + ], + }, + { + title: "Geo. coverage", + columns: [ + { title: "Value", field: "geoCoverage", formatter: cellBGColorFormatter }, + ], + }, + { + title: "Cumulation", + columns: [ + { title: "Period", field: "cumulation", formatter: cellBGColorFormatter }, + ], + }, + { + title: "Baseline", + columns: [ + { + title: "Value", + field: "baselineValue", + formatter: cellBGColorFormatter, + }, + { + title: "Year", + field: "baselineYear", + formatter: cellBGColorFormatter, + }, + { + title: "Source", + field: "baselineSource", + formatter: cellBGColorFormatter, + }, + ], + }, + { + title: "2020", + field: "2020", + formatter: (cell: CellComponent) => { + var tableEl = document.createElement("div"); + cell.getElement().appendChild(tableEl); + const data = cell.getValue(); + + if (!cell.getValue()) { + return ""; + } + + new Tabulator(tableEl, { + data, + layout: "fitDataTable", + height: "fit-content", + columns: [ + { title: "Target", field: "target" }, + { title: "Result", field: "result" }, + { title: "Achievement", field: "achievement" }, + ], + }); + + cell.getElement().style.height = "max-content"; + + return tableEl; + }, + }, + { + title: "2021", + field: "2021", + formatter: (cell: CellComponent) => { + var tableEl = document.createElement("div"); + cell.getElement().appendChild(tableEl); + const data = cell.getValue(); + + if (!cell.getValue()) { + return ""; + } + + new Tabulator(tableEl, { + data, + layout: "fitDataTable", + height: "fit-content", + columns: [ + { title: "Target", field: "target" }, + { title: "Result", field: "result" }, + { title: "Achievement", field: "achievement" }, + ], + }); + + cell.getElement().style.height = "max-content"; + + return tableEl; + }, + }, +]; + +export const TABLE_VARIATION_4_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + name: "TB CARE AND PREVENTION", + _children: [ + { + name: "Number of notified cases of all forms of TB (i.e. bacteriologically confirmed + clinically diagnosed), new and relapse cases", + _children: [ + { + reversed: "No", + geoCoverage: "National", + cumulation: "Annually", + baselineValue: "T:0.5%", + baselineYear: "2018", + baselineSource: "--", + "2020": [ + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + ], + "2021": [ + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + ], + }, + ], + }, + ], + }, + { + name: "TB CARE AND PREVENTION", + _children: [ + { + name: "Number of notified cases of all forms of TB (i.e. bacteriologically confirmed + clinically diagnosed), new and relapse cases", + _children: [ + { + reversed: "No", + geoCoverage: "National", + cumulation: "Annually", + baselineValue: "T:0.5%", + baselineYear: "2018", + baselineSource: "--", + "2020": [ + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + { + target: "T:0.5%", + result: "T:0.5%", + achievement: "100%", + }, + ], + }, + ], + }, + ], + }, +]; + +export const TABLE_VARIATION_5_COLUMNS: ColumnDefinition[] = [ + { + title: "Grant ID", + field: "grantId", + formatter: (cell: CellComponent) => + `${cell.getValue()}`, + }, + { + title: "Start/End date", + field: "startEndDate", + formatter: cellBGColorFormatter, + }, + { + title: "Geography", + field: "geography", + formatter: cellBGColorFormatter, + }, + { + title: "Component", + field: "component", + formatter: cellBGColorFormatter, + }, + { + title: "Principal Recipient", + field: "principalRecipient", + formatter: cellBGColorFormatter, + }, + { + title: "Status", + field: "status", + formatter: cellBGColorFormatter, + }, + { + title: "Signed", + field: "signed", + formatter: financialFormatter, + }, + { + title: "Disbursed", + field: "disbursed", + formatter: financialFormatter, + }, +]; + +export const TABLE_VARIATION_5_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + grantId: "AFG-H-MOH001", + startEndDate: "Jul 2023 - Dec 2023", + geography: "Ghana", + component: "HIV", + principalRecipient: "Private health ministry of Ghana", + status: "Active", + signed: 1000000, + disbursed: 1000000, + }, + { + grantId: "AFG-H-MOH001", + startEndDate: "Jul 2023 - Dec 2023", + geography: "Ghana", + component: "HIV", + principalRecipient: "Private health ministry of Ghana", + status: "Active", + signed: 1000000, + disbursed: 1000000, + }, +]; + +export const TABLE_VARIATION_6_COLUMNS: ColumnDefinition[] = [ + { + title: "Location", + field: "name", + width: "80%", + formatter: cellBGColorFormatter, + }, + { + width: "20%", + title: "Documents", + field: "documents", + formatter: (cell: CellComponent) => { + if (typeof cell.getValue() === "string") { + cell.getElement().style.textAlign = "right"; + return ` + + + `; + } + return cell.getValue(); + }, + }, +]; + +export const TABLE_VARIATION_6_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + name: "Algeria", + documents: 19, + _children: [ + { + name: "Application", + documents: 4, + _children: [ + { + name: "Combined Grant To Support HIV/AIDS, Tuberculosis & Malaria Programs And Healthcare", + documents: + "https://gfdatastore.blob.core.windows.net/files/Applications/Fast-track%20Covid%20Funding%20Requests/QPA/2021/NA/en/QPA-X_FastCovidFundingRequest_1_en.zip", + }, + ], + }, + { + name: "Other documents", + documents: 15, + }, + ], + }, +]; + +export const TABLE_VARIATION_7_COLUMNS: ColumnDefinition[] = [ + { + title: "Description", + field: "description", + formatter: cellBGColorFormatter, + }, + { title: "Component", field: "component", formatter: cellBGColorFormatter }, + { title: "Result", field: "result", formatter: "money" }, +]; + +export const TABLE_VARIATION_7_DATA: { + [key: string]: string | number | boolean | null | object | Array; +}[] = [ + { + description: "People on antiretroviral therapy for HIV", + component: "HIV", + result: 1965401, + }, + { + description: "People with TB treated", + component: "Tuberculosis", + result: 1965401, + }, + { + description: "Mosquito nets distributed", + component: "Malaria", + result: 10000000, + }, + { + description: "Peopleusing pre-exposure prophylaxis", + component: "HIV", + result: 1000000, + }, + { + description: + "Other vulnerable populations reached with HIV prevention programs", + component: "HIV", + result: 100000, + }, +]; diff --git a/src/app/components/table/index.tsx b/src/app/components/table/index.tsx new file mode 100644 index 000000000..74778d421 --- /dev/null +++ b/src/app/components/table/index.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import Box from "@mui/material/Box"; +import { TableProps } from "app/components/table/data"; +import "tabulator-tables/dist/css/tabulator_simple.min.css"; +import { TabulatorFull as Tabulator } from "tabulator-tables"; + +export const Table: React.FC = (props: TableProps) => { + const ref = React.useRef(null); + const [expandedCount, setExpandedCount] = React.useState(0); + + React.useEffect(() => { + if (ref.current) { + const table = new Tabulator(ref.current, { + data: props.data, + reactiveData: true, + columns: props.columns, + layout: "fitDataStretch", + dataTree: props.dataTree, + dataTreeBranchElement: props.dataTreeBranchElement, + dataTreeExpandElement: ``, + dataTreeCollapseElement: ``, + }); + + table.on("dataTreeRowExpanded", (_, level) => { + if (level === 0) { + setExpandedCount((prev) => prev + 1); + } + }); + table.on("dataTreeRowCollapsed", (_, level) => { + if (level === 0) { + setExpandedCount((prev) => prev - 1); + } + }); + + return () => { + table.destroy(); + }; + } + }, []); + + React.useEffect(() => { + if (ref.current && props.extraColumns && props.extraColumns.length > 0) { + const tables = Tabulator.findTable("#table"); + if (tables.length > 0 && tables[0]) { + if (expandedCount > 0) { + tables[0].setColumns(props.columns.concat(props.extraColumns)); + } else { + setTimeout(() => { + tables[0].setColumns(props.columns); + }, 1); + } + } + } + }, [expandedCount, props.extraColumns]); + + return ( + + ); +}; diff --git a/src/app/pages/location/views/access-to-funding/index.tsx b/src/app/pages/location/views/access-to-funding/index.tsx index 18566824a..431a4a275 100644 --- a/src/app/pages/location/views/access-to-funding/index.tsx +++ b/src/app/pages/location/views/access-to-funding/index.tsx @@ -14,6 +14,13 @@ import { getEligibilityColor, STORY_DATA_VARIANT_2 as HEATMAP_DATA, } from "app/components/charts/heatmap/data"; +import { TableContainer } from "app/components/table-container"; +import { + TABLE_VARIATION_2_COLUMNS, + TABLE_VARIATION_2_DATA, + TABLE_VARIATION_6_COLUMNS, + TABLE_VARIATION_6_DATA, +} from "app/components/table/data"; export const AccessToFunding: React.FC = () => { const [chart1Cycle, setChart1Cycle] = React.useState(CYCLES[0]); @@ -76,16 +83,12 @@ export const AccessToFunding: React.FC = () => { handleCycleChange={(value) => handleChartCycleChange(value, 2)} text="The Funding Request explains how the applicant would use Global Fund allocated funds, if approved. Funding Requests are reviewed by the Global Fund’s Technical Review Panel (TRP). Once approved by the TRP, the Funding Request is turned into one or more grants through the grant-making negotiation. The Grant Approvals Committee (GAC) reviews the final version of each grant and recommends implementation-ready grants to the Global Fund Board for approval. Funding Requests are submitted for internal Global Fund review, but the final grant is the legally-binding agreement.

Documents for a specific funding request can be downloaded by clicking the cloud icon. Documents from the 2017-2019 Allocation Period and earlier can be found by clicking on the “Documents’ tab above. If a Funding Request is not visible for the 2023-2025 Allocation Period and the country received an Allocation, it likely means that the applicant has not yet registered for a TRP Window." > - - TABLE - + @@ -233,16 +236,11 @@ export const AccessToFunding: React.FC = () => { text="Description of Pledges & Contributions: We unite the world to find solutions that have the most impact, and we take them to scale worldwide. It’s working. We won’t stop until the job is finished." > - - TABLE - + ); diff --git a/src/app/pages/location/views/grant-implementation/index.tsx b/src/app/pages/location/views/grant-implementation/index.tsx index 4bd414fc4..e63a9f937 100644 --- a/src/app/pages/location/views/grant-implementation/index.tsx +++ b/src/app/pages/location/views/grant-implementation/index.tsx @@ -10,6 +10,8 @@ import { LineChart } from "app/components/charts/line"; import { ChartBlock } from "app/components/chart-block"; import { Heatmap } from "app/components/charts/heatmap"; import { SankeyChart } from "app/components/charts/sankey"; +import { TableContainer } from "app/components/table-container"; +import { ChartBlockCycles } from "app/components/chart-block/components/cycles"; import { STORY_DATA_VARIANT_1 as LINE_CHART_DATA } from "app/components/charts/line/data"; import { STORY_DATA_VARIANT_1 as SANKEY_CHART_DATA } from "app/components/charts/sankey/data"; import { @@ -25,7 +27,10 @@ import { STORY_DATA_VARIANT_2 as PIE_CHART_DATA_2, STORY_DATA_VARIANT_3 as PIE_CHART_DATA_3, } from "app/components/charts/pie/data"; -import { ChartBlockCycles } from "app/components/chart-block/components/cycles"; +import { + TABLE_VARIATION_5_DATA, + TABLE_VARIATION_5_COLUMNS, +} from "app/components/table/data"; export const GrantImplementation: React.FC = () => { const [chart1Cycle, setChart1Cycle] = React.useState(CYCLES[0]); @@ -269,16 +274,10 @@ export const GrantImplementation: React.FC = () => { selectedCycle={chart4Cycle} handleCycleChange={(value: string) => handleChartCycleChange(value, 4)} /> - - Grants table - + ); }; diff --git a/yarn.lock b/yarn.lock index 12a7909c1..18e378a9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4002,6 +4002,11 @@ resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.0.tgz#199a3f473f0c3a6f6e4e1b17cdbc967f274bdc6b" integrity sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw== +"@types/tabulator-tables@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@types/tabulator-tables/-/tabulator-tables-5.6.0.tgz#4eb77373688c435177527b076e44c84acb60ca8d" + integrity sha512-si5S49dCsVgUhLS2PIP93xXZtdrZEPldWDXzCTMtCgmoKP7rTM6eQOpeR+EW5YtyNDS2vAqQPlQ1ddBoFKIbAQ== + "@types/testing-library__jest-dom@^5.9.1": version "5.14.9" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz#0fb1e6a0278d87b6737db55af5967570b67cb466" @@ -12539,6 +12544,11 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032" integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g== +tabulator-tables@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tabulator-tables/-/tabulator-tables-6.1.0.tgz#f468c3a06abc716095c2f596cf10ba8c313673b1" + integrity sha512-J+FgWNTxrOoafOeNTUmFDFgKAqctFKtQmV6RwLuDcJJe2qsxKaeQ2OQ6OdaIDIPZ0mykpdKXYqjioOdUagvTZg== + tailwindcss@^3.0.2: version "3.4.1" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d"