From d03a60344ad7cc1fd88a28a6896c9aa1c375e10f Mon Sep 17 00:00:00 2001 From: Mahmoud O <59492998+mxosman@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:46:52 -0500 Subject: [PATCH] Settings: Metric Configuration (1/n) (#64) * Initial work on metric config refactor in settings * Continue iterating on refactor up to the breakdown page * Remove routing to old metrics page - remove from menu * Styling adjustment * Fix key warning Co-authored-by: Mahmoud --- publisher/src/App.tsx | 2 - publisher/src/components/Badge/Badge.tsx | 9 +- publisher/src/components/Menu/Menu.tsx | 11 - .../MetricsView/MetricsView.styles.tsx | 55 +++-- .../components/MetricsView/MetricsView.tsx | 219 ++++++------------ publisher/src/pages/Settings.tsx | 8 +- 6 files changed, 121 insertions(+), 183 deletions(-) diff --git a/publisher/src/App.tsx b/publisher/src/App.tsx index f13d69f5e..a46a5a721 100644 --- a/publisher/src/App.tsx +++ b/publisher/src/App.tsx @@ -23,7 +23,6 @@ import { trackNavigation } from "./analytics"; import { DataUpload } from "./components/DataUpload"; import { PageWrapper } from "./components/Forms"; import Header from "./components/Header"; -import { MetricsView } from "./components/MetricsView"; import CreateReports from "./components/Reports/CreateReport"; import ReportDataEntry from "./components/Reports/ReportDataEntry"; import ReviewMetrics from "./components/ReviewMetrics/ReviewMetrics"; @@ -46,7 +45,6 @@ const App: React.FC = (): ReactElement => { } /> } /> } /> - } /> } /> } /> diff --git a/publisher/src/components/Badge/Badge.tsx b/publisher/src/components/Badge/Badge.tsx index 0ecc41b1c..7bdd62691 100644 --- a/publisher/src/components/Badge/Badge.tsx +++ b/publisher/src/components/Badge/Badge.tsx @@ -28,11 +28,13 @@ export type BadgeProps = { color: BadgeColors; disabled?: boolean; loading?: boolean; + noMargin?: boolean; }; export const BadgeElement = styled.div<{ color?: BadgeColors; disabled?: boolean; + noMargin?: boolean; }>` height: 24px; display: flex; @@ -40,7 +42,7 @@ export const BadgeElement = styled.div<{ align-items: center; background: ${({ color, disabled }) => { if (color === "GREY" || disabled) { - return palette.highlight.grey9; + return palette.highlight.grey8; } if (color === "RED") { return palette.solid.red; @@ -55,21 +57,22 @@ export const BadgeElement = styled.div<{ }}; color: ${palette.solid.white}; padding: 4px 8px; - margin-left: 10px; font-size: 0.65rem; font-weight: 600; white-space: nowrap; text-transform: capitalize; + ${({ noMargin }) => !noMargin && `margin-left: 10px;`}; `; export const Badge: React.FC = ({ color, disabled, loading, + noMargin, children, }) => { return ( - + {children} {loading && } diff --git a/publisher/src/components/Menu/Menu.tsx b/publisher/src/components/Menu/Menu.tsx index a4ea0ee74..d31816fc1 100644 --- a/publisher/src/components/Menu/Menu.tsx +++ b/publisher/src/components/Menu/Menu.tsx @@ -37,7 +37,6 @@ enum MenuItems { LearnMore = "LEARN MORE", Settings = "SETTINGS", Agencies = "AGENCIES", - Metrics = "METRICS", } const Menu = () => { @@ -77,8 +76,6 @@ const Menu = () => { setActiveMenuItem(MenuItems.CreateReport); } else if (location.pathname === "/settings") { setActiveMenuItem(MenuItems.Settings); - } else if (location.pathname === "/metrics") { - setActiveMenuItem(MenuItems.Metrics); } else { setActiveMenuItem(undefined); } @@ -92,14 +89,6 @@ const Menu = () => { `Welcome, ${userStore.nameOrEmail} at ${userStore.currentAgency.name}`} - {/* Metrics View */} - navigate("/metrics")} - active={activeMenuItem === MenuItems.Metrics} - > - Metrics - - {/* Reports */} navigate("/")} diff --git a/publisher/src/components/MetricsView/MetricsView.styles.tsx b/publisher/src/components/MetricsView/MetricsView.styles.tsx index dc4144466..57f2ca73a 100644 --- a/publisher/src/components/MetricsView/MetricsView.styles.tsx +++ b/publisher/src/components/MetricsView/MetricsView.styles.tsx @@ -31,6 +31,7 @@ export const MetricsViewControlPanel = styled.div` height: calc(100% - 170px); width: 100%; display: flex; + flex-wrap: wrap; justify-content: space-between; `; @@ -55,33 +56,29 @@ export const PanelContainerRight = styled.div` type MetricBoxContainerProps = { enabled?: boolean; - selected?: boolean; }; export const MetricBoxContainer = styled.div` + height: 197px; + max-width: 50%; display: flex; + flex: 1 1 50%; flex-direction: column; - border: 1px solid - ${({ selected }) => - selected ? palette.solid.blue : palette.highlight.grey2}; - border-radius: 12px; - padding: 15px; - margin-bottom: 11px; + justify-content: space-between; + border: 1px solid ${palette.highlight.grey2}; + padding: 27px 24px; transition: 0.2s ease; color: ${({ enabled }) => - enabled ? palette.solid.darkgrey : palette.highlight.grey7}; - ${({ selected }) => - selected && `box-shadow: 0px 4px 10px ${palette.highlight.blue};`} + enabled ? palette.solid.darkgrey : palette.highlight.grey10}; &:hover { cursor: pointer; - ${({ selected }) => - !selected && `border: 1px solid ${palette.highlight.lightblue2}`}; + border: 1px solid ${palette.solid.blue}; } `; export const MetricBoxWrapper = styled.div` - display: block; + display: flex; `; export const ActiveMetricSettingHeader = styled.div` @@ -106,12 +103,13 @@ type MetricNameProps = { isTitle?: boolean }; export const MetricName = styled.div` ${({ isTitle }) => - isTitle ? typography.sizeCSS.title : typography.sizeCSS.medium} + isTitle ? typography.sizeCSS.title : typography.sizeCSS.large} `; export const MetricDescription = styled.div` ${typography.sizeCSS.normal} - color: ${palette.highlight.grey9}; + height: 100%; + margin: 11px 0; @media only screen and (max-width: 1000px) { ${typography.sizeCSS.small} @@ -121,7 +119,7 @@ export const MetricDescription = styled.div` export const MetricDetailsDisplay = styled.div` width: 100%; overflow-y: scroll; - padding: 24px 15px 0 15px; + padding: 24px 0; `; export const MetricOnOffWrapper = styled.div` @@ -328,3 +326,28 @@ export const MetricSettingsDisplayError = styled.div` justify-content: center; margin-top: 50px; `; + +export const StickyHeader = styled.div` + width: 100%; + position: sticky; + top: 0; + background: ${palette.solid.white}; + margin-bottom: 29px; +`; + +export const BackToMetrics = styled.div` + color: ${palette.solid.blue}; + transition: 0.2s ease; + margin-bottom: 24px; + + &:hover { + cursor: pointer; + opacity: 0.85; + } +`; + +export const MetricConfigurationDisplay = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; diff --git a/publisher/src/components/MetricsView/MetricsView.tsx b/publisher/src/components/MetricsView/MetricsView.tsx index 0faad657a..e5ce2c07c 100644 --- a/publisher/src/components/MetricsView/MetricsView.tsx +++ b/publisher/src/components/MetricsView/MetricsView.tsx @@ -27,21 +27,19 @@ import { removeCommaSpaceAndTrim, removeSnakeCase, } from "../../utils"; -import { Badge, BadgeColorMapping } from "../Badge"; -import DatapointsView from "../DataViz/DatapointsView"; +import { Badge } from "../Badge"; import { BinaryRadioButton, BinaryRadioGroupClearButton, BinaryRadioGroupContainer, BinaryRadioGroupQuestion, - NotReportedIcon, TextInput, } from "../Forms"; import { Loading } from "../Loading"; -import { PageTitle, TabbedBar, TabbedItem, TabbedOptions } from "../Reports"; +import { TabbedBar, TabbedItem, TabbedOptions } from "../Reports"; import { showToast } from "../Toast"; import { - ActiveMetricSettingHeader, + BackToMetrics, Dimension, DimensionTitle, DimensionTitleWrapper, @@ -51,24 +49,22 @@ import { Header, Label, MetricBoxContainer, - MetricBoxWrapper, MetricConfigurationContainer, + MetricConfigurationDisplay, MetricContextContainer, MetricContextItem, MetricDescription, MetricDetailsDisplay, MetricDisaggregations, MetricName, - MetricNameBadgeToggleWrapper, MetricNameBadgeWrapper, MetricOnOffWrapper, MetricsViewContainer, MetricsViewControlPanel, MultipleChoiceWrapper, - PanelContainerLeft, - PanelContainerRight, RadioButtonGroupWrapper, Slider, + StickyHeader, Subheader, ToggleSwitch, ToggleSwitchInput, @@ -115,11 +111,6 @@ type MetricBoxProps = { setActiveMetricKey: React.Dispatch>; }; -const reportFrequencyBadgeColors: BadgeColorMapping = { - ANNUAL: "ORANGE", - MONTHLY: "GREEN", -}; - const MetricBox: React.FC = ({ metricKey, displayName, @@ -133,23 +124,14 @@ const MetricBox: React.FC = ({ setActiveMetricKey(metricKey)} enabled={enabled} - selected={metricKey === activeMetricKey} > - - - {displayName} - - {frequency} - - - - {!enabled && } - - + {displayName} {description} + + + {!enabled ? "Inactive" : frequency.toLowerCase()} + + ); }; @@ -375,11 +357,13 @@ const MetricContextConfiguration: React.FC = ({ }; useEffect(() => { - contexts.forEach((context) => { - if (context.type === "NUMBER") { - contextNumberValidation(context.key, context.value || ""); - } - }); + if (contexts) { + contexts.forEach((context) => { + if (context.type === "NUMBER") { + contextNumberValidation(context.key, context.value || ""); + } + }); + } }, [contexts]); return ( @@ -531,29 +515,16 @@ export type MetricSettings = { export const MetricsView: React.FC = observer(() => { const { reportStore, userStore, datapointsStore } = useStore(); - const configPanelRef = useRef(null); - - // TODO(#13805) Temporarily hiding the data tab until it is implemented. Currently it's only visible to Recidiviz admins. - const configSections = ["Data", "Configuration", "Context"]; - type ConfigSections = typeof configSections[number]; const [activeMetricFilter, setActiveMetricFilter] = useState(); - - const [activeConfigSection, setActiveConfigSection] = - useState("Data"); - const [isLoading, setIsLoading] = useState(true); - const [loadingError, setLoadingError] = useState( undefined ); - const [activeMetricKey, setActiveMetricKey] = useState(""); - const [metricSettings, setMetricSettings] = useState<{ [key: string]: MetricsViewMetric; }>({}); - const [filteredMetricSettings, setFilteredMetricSettings] = useState<{ [key: string]: MetricsViewMetric; }>({}); @@ -765,7 +736,6 @@ export const MetricsView: React.FC = observer(() => { }); setMetricSettings(metricKeyToMetricMap); - setActiveMetricKey(Object.keys(metricKeyToMetricMap)[0]); }; useEffect( @@ -827,129 +797,78 @@ export const MetricsView: React.FC = observer(() => { return ; } - if (!metricSettings[activeMetricKey]) { + if (loadingError) { return
Error: {loadingError}
; } return ( <> - Metrics - - - - {userStore.currentAgency?.systems.map((filterOption) => ( - - setActiveMetricFilter(removeSnakeCase(filterOption)) - } - capitalize - > - {removeSnakeCase(filterOption.toLowerCase())} - - ))} - - + {!activeMetricKey && ( + + + + {userStore.currentAgency?.systems.map((filterOption) => ( + + setActiveMetricFilter(removeSnakeCase(filterOption)) + } + capitalize + > + {removeSnakeCase(filterOption.toLowerCase())} + + ))} + + + + )} {/* List Of Metrics */} - - {filteredMetricSettings && - Object.values(filteredMetricSettings).map((metric) => ( - { - if (configPanelRef.current) { - configPanelRef.current.scrollTo({ - top: 0, - behavior: "smooth", - }); - } - }} - > - - - ))} - - - {/* Data | Configuration | Context */} - - - - - {metricSettings[activeMetricKey]?.display_name} - - - {metricSettings[activeMetricKey]?.frequency} - - - - - - {configSections.map((section) => ( - { - setActiveConfigSection(section); - if (configPanelRef.current) { - configPanelRef.current.scrollTo({ - top: 0, - behavior: "smooth", - }); - } - }} - > - {section} - - ))} - - - - - {/* Data */} - {activeConfigSection === "Data" && ( - - )} - - {/* Configuration */} - {activeConfigSection === "Configuration" && ( + {filteredMetricSettings && + !activeMetricKey && + Object.values(filteredMetricSettings).map((metric) => ( + + ))} + + {/* Metric Configuration */} + {activeMetricKey && ( + + setActiveMetricKey("")}> + ← Back to Metrics + + + + {metricSettings[activeMetricKey]?.display_name} + + - - )} - - {/* Context */} - {activeConfigSection === "Context" && ( - - )} - + + )} diff --git a/publisher/src/pages/Settings.tsx b/publisher/src/pages/Settings.tsx index 2d5f43489..8abea787f 100644 --- a/publisher/src/pages/Settings.tsx +++ b/publisher/src/pages/Settings.tsx @@ -18,6 +18,7 @@ import React, { useState } from "react"; import { UploadedFiles } from "../components/DataUpload"; +import { MetricsView } from "../components/MetricsView"; import { AccountSettings, ContentDisplay, @@ -25,7 +26,11 @@ import { SettingsMenu, } from "../components/Settings"; -export const menuOptions = ["Your Account", "Uploaded Files"] as const; +export const menuOptions = [ + "Your Account", + "Uploaded Files", + "Metric Configuration", +] as const; export type MenuOptions = typeof menuOptions[number]; const Settings = () => { @@ -46,6 +51,7 @@ const Settings = () => { {activeMenuItem === "Your Account" && } {activeMenuItem === "Uploaded Files" && } + {activeMenuItem === "Metric Configuration" && } );