From 2f95741e4b4fbaec0d3dfbd201a0778045c0f5fb Mon Sep 17 00:00:00 2001 From: Mahmoud O <59492998+mxosman@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:49:18 -0500 Subject: [PATCH] Settings: Scaffolding (#59) * Restructure settings files, rename, extract styles * Refactor settings page and set up existing components in new structure * Styling adjustment * Change text to Your Account * 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 Co-authored-by: Mahmoud --- publisher/src/App.tsx | 6 +- publisher/src/components/Badge/Badge.tsx | 9 +- .../DataUpload/DataUpload.styles.tsx | 7 +- .../components/DataUpload/UploadedFiles.tsx | 54 +++-- publisher/src/components/Menu/Menu.tsx | 11 - .../MetricsView/MetricsView.styles.tsx | 55 +++-- .../components/MetricsView/MetricsView.tsx | 219 ++++++------------ .../components/Settings/AccountSettings.tsx | 69 ++++++ .../components/Settings/Settings.styles.tsx | 77 ++++++ .../src/components/Settings/SettingsMenu.tsx | 40 ++++ publisher/src/components/Settings/index.ts | 20 ++ publisher/src/pages/AccountSettings.tsx | 131 ----------- publisher/src/pages/Settings.tsx | 60 +++++ 13 files changed, 416 insertions(+), 342 deletions(-) create mode 100644 publisher/src/components/Settings/AccountSettings.tsx create mode 100644 publisher/src/components/Settings/Settings.styles.tsx create mode 100644 publisher/src/components/Settings/SettingsMenu.tsx create mode 100644 publisher/src/components/Settings/index.ts delete mode 100644 publisher/src/pages/AccountSettings.tsx create mode 100644 publisher/src/pages/Settings.tsx diff --git a/publisher/src/App.tsx b/publisher/src/App.tsx index e367f83a7..a46a5a721 100644 --- a/publisher/src/App.tsx +++ b/publisher/src/App.tsx @@ -23,12 +23,11 @@ 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"; -import AccountSettings from "./pages/AccountSettings"; import Reports from "./pages/Reports"; +import Settings from "./pages/Settings"; const App: React.FC = (): ReactElement => { const location = useLocation(); @@ -45,8 +44,7 @@ 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/DataUpload/DataUpload.styles.tsx b/publisher/src/components/DataUpload/DataUpload.styles.tsx index 9a5b44aec..dcdd0e4cc 100644 --- a/publisher/src/components/DataUpload/DataUpload.styles.tsx +++ b/publisher/src/components/DataUpload/DataUpload.styles.tsx @@ -513,19 +513,14 @@ export const ConfirmationPageContainer = styled.div` align-items: center; `; -export const UploadedFilesContainer = styled.div` - max-height: 50vh; - overflow-y: scroll; -`; +export const UploadedFilesContainer = styled.div``; export const UploadedFilesWrapper = styled.div` - margin-top: 50px; position: relative; `; export const UploadedFilesTable = styled(Table)` padding: unset; - max-height: 40vh; overflow-y: scroll; padding-bottom: 50px; `; diff --git a/publisher/src/components/DataUpload/UploadedFiles.tsx b/publisher/src/components/DataUpload/UploadedFiles.tsx index bdec22ee6..04ff7ebdf 100644 --- a/publisher/src/components/DataUpload/UploadedFiles.tsx +++ b/publisher/src/components/DataUpload/UploadedFiles.tsx @@ -24,6 +24,7 @@ import { useStore } from "../../stores"; import { removeSnakeCase } from "../../utils"; import downloadIcon from "../assets/download-icon.png"; import { Badge, BadgeColorMapping, BadgeColors } from "../Badge"; +import { Title, TitleWrapper } from "../Forms"; import { Loader } from "../Loading"; import { showToast } from "../Toast"; import { @@ -33,6 +34,7 @@ import { ExtendedCell, ExtendedLabelCell, ExtendedLabelRow, + ExtendedOpacityGradient, ExtendedRow, UploadedFile, UploadedFilesContainer, @@ -40,6 +42,7 @@ import { UploadedFilesLoading, UploadedFilesTable, UploadedFileStatus, + UploadedFilesWrapper, } from "."; export const UploadedFileRow: React.FC<{ @@ -315,26 +318,35 @@ export const UploadedFiles: React.FC = observer(() => { } return ( - - - {dataUploadColumnTitles.map((title) => ( - {title} - ))} - - - {uploadedFiles.map((fileDetails) => { - const fileRowDetails = getFileRowDetails(fileDetails); - - return ( - - ); - })} - - + + + Uploaded Files + + + + + {dataUploadColumnTitles.map((title) => ( + {title} + ))} + + + + {uploadedFiles.map((fileDetails) => { + const fileRowDetails = getFileRowDetails(fileDetails); + + return ( + + ); + })} + + + + + ); }); 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/components/Settings/AccountSettings.tsx b/publisher/src/components/Settings/AccountSettings.tsx new file mode 100644 index 000000000..ec1fe0667 --- /dev/null +++ b/publisher/src/components/Settings/AccountSettings.tsx @@ -0,0 +1,69 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { debounce as _debounce } from "lodash"; +import React, { useRef } from "react"; + +import { useStore } from "../../stores"; +import { TextInput, Title, TitleWrapper } from "../Forms"; +import { InputWrapper, SettingsFormPanel } from "."; + +export const AccountSettings = () => { + const { userStore } = useStore(); + const [email, setEmail] = React.useState(userStore?.email || ""); + const [name, setName] = React.useState(userStore?.name || ""); + + const saveNameEmailChange = (nameUpdate?: string, emailUpdate?: string) => { + if (nameUpdate) { + return userStore.updateUserNameAndEmail(nameUpdate, email); + } + if (emailUpdate) { + return userStore.updateUserNameAndEmail(name, emailUpdate); + } + }; + + const debouncedSave = useRef(_debounce(saveNameEmailChange, 1500)).current; + + return ( + + + Account + + + + { + setName(e.target.value); + debouncedSave(e.target.value, undefined); + }} + /> + { + setEmail(e.target.value); + debouncedSave(undefined, e.target.value); + }} + /> + + + ); +}; diff --git a/publisher/src/components/Settings/Settings.styles.tsx b/publisher/src/components/Settings/Settings.styles.tsx new file mode 100644 index 000000000..4e11eb802 --- /dev/null +++ b/publisher/src/components/Settings/Settings.styles.tsx @@ -0,0 +1,77 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import styled from "styled-components/macro"; + +import { palette, typography } from "../GlobalStyles"; + +export const SettingsContainer = styled.div` + width: 100%; + height: 100%; + display: flex; + align-items: flex-start; + padding: 39px 24px 0 24px; + position: fixed; + overflow-y: scroll; +`; + +export const ContentDisplay = styled.div` + max-height: 90%; + display: flex; + flex-direction: column; + justify-content: flex-start; + flex: 10 10 auto; + overflow-y: scroll; +`; + +export const SettingsMenuContainer = styled.div` + ${typography.sizeCSS.headline} + width: fit-content; + display: flex; + flex: 0 0 auto; + flex-direction: column; + gap: 16px; + padding: 16px 24px; + margin-right: 100px; +`; + +export const MenuItem = styled.div<{ selected?: boolean }>` + ${typography.sizeCSS.large} + width: fit-content; + padding-bottom: 4px; + color: ${({ selected }) => + selected ? palette.solid.darkgrey : palette.highlight.grey10}; + border-bottom: 2px solid + ${({ selected }) => (selected ? palette.solid.blue : `transparent`)}; + transition: color 0.2s ease; + + &:hover { + cursor: pointer; + color: ${({ selected }) => !selected && palette.solid.darkgrey}; + } +`; + +export const SettingsFormPanel = styled.div``; + +export const InputWrapper = styled.div` + display: flex; + gap: 10px; + + div { + width: 100%; + } +`; diff --git a/publisher/src/components/Settings/SettingsMenu.tsx b/publisher/src/components/Settings/SettingsMenu.tsx new file mode 100644 index 000000000..e67f354bd --- /dev/null +++ b/publisher/src/components/Settings/SettingsMenu.tsx @@ -0,0 +1,40 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; + +import { MenuOptions, menuOptions } from "../../pages/Settings"; +import { MenuItem, SettingsMenuContainer } from "./Settings.styles"; + +export const SettingsMenu: React.FC<{ + activeMenuItem: MenuOptions; + goToMenuItem: (destination: MenuOptions) => void; +}> = ({ activeMenuItem, goToMenuItem }) => { + return ( + + {menuOptions.map((option) => ( + goToMenuItem(option)} + > + {option} + + ))} + + ); +}; diff --git a/publisher/src/components/Settings/index.ts b/publisher/src/components/Settings/index.ts new file mode 100644 index 000000000..585b3cb16 --- /dev/null +++ b/publisher/src/components/Settings/index.ts @@ -0,0 +1,20 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./AccountSettings"; +export * from "./Settings.styles"; +export * from "./SettingsMenu"; diff --git a/publisher/src/pages/AccountSettings.tsx b/publisher/src/pages/AccountSettings.tsx deleted file mode 100644 index 202d58038..000000000 --- a/publisher/src/pages/AccountSettings.tsx +++ /dev/null @@ -1,131 +0,0 @@ -// Recidiviz - a data platform for criminal justice reform -// Copyright (C) 2022 Recidiviz, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// ============================================================================= - -import { debounce as _debounce } from "lodash"; -import React, { useRef } from "react"; -import styled from "styled-components/macro"; - -import { - ExtendedOpacityGradient, - UploadedFiles, - UploadedFilesWrapper, -} from "../components/DataUpload"; -import { TextInput, Title, TitleWrapper } from "../components/Forms"; -import { typography } from "../components/GlobalStyles"; -import { useStore } from "../stores"; - -const SettingsContainer = styled.div` - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: flex-start; - padding: 39px 24px 0 24px; - position: fixed; - overflow-y: scroll; - - @media only screen and (max-width: 1050px) { - width: unset; - flex-direction: column; - } -`; - -const SettingsFormPanel = styled.div``; - -const InputWrapper = styled.div` - display: flex; - gap: 10px; - - div { - width: 100%; - } -`; - -const SettingsFormUploadedFilesWrapper = styled.div` - display: flex; - flex-direction: column; - flex: 3 1 auto; -`; - -const SettingsTitle = styled.div` - ${typography.sizeCSS.headline} - display: flex; - flex: 1 1 auto; -`; - -const AccountSettings = () => { - const { userStore } = useStore(); - const [email, setEmail] = React.useState(userStore?.email || ""); - const [name, setName] = React.useState(userStore?.name || ""); - - const saveNameEmailChange = (nameUpdate?: string, emailUpdate?: string) => { - if (nameUpdate) { - return userStore.updateUserNameAndEmail(nameUpdate, email); - } - if (emailUpdate) { - return userStore.updateUserNameAndEmail(name, emailUpdate); - } - }; - - const debouncedSave = useRef(_debounce(saveNameEmailChange, 1500)).current; - - return ( - - Settings - - - - - Account - - - - { - setName(e.target.value); - debouncedSave(e.target.value, undefined); - }} - /> - { - setEmail(e.target.value); - debouncedSave(undefined, e.target.value); - }} - /> - - - - - - Uploaded Files - - - - - - - - ); -}; - -export default AccountSettings; diff --git a/publisher/src/pages/Settings.tsx b/publisher/src/pages/Settings.tsx new file mode 100644 index 000000000..8abea787f --- /dev/null +++ b/publisher/src/pages/Settings.tsx @@ -0,0 +1,60 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useState } from "react"; + +import { UploadedFiles } from "../components/DataUpload"; +import { MetricsView } from "../components/MetricsView"; +import { + AccountSettings, + ContentDisplay, + SettingsContainer, + SettingsMenu, +} from "../components/Settings"; + +export const menuOptions = [ + "Your Account", + "Uploaded Files", + "Metric Configuration", +] as const; +export type MenuOptions = typeof menuOptions[number]; + +const Settings = () => { + const [activeMenuItem, setActiveMenuItem] = useState( + menuOptions[0] + ); + + const goToMenuItem = (destination: MenuOptions) => + setActiveMenuItem(destination); + + return ( + + + + + {activeMenuItem === "Your Account" && } + {activeMenuItem === "Uploaded Files" && } + {activeMenuItem === "Metric Configuration" && } + + + ); +}; + +export default Settings;