Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Publisher][Superagencies] Add dropdown for child agencies #1314

Merged
merged 14 commits into from
Apr 29, 2024
22 changes: 19 additions & 3 deletions common/components/Dropdown/Dropdown.styled.tsx
Expand Up @@ -194,18 +194,34 @@ export const CustomInputWrapper = styled.div`
padding: 20px 16px 8px 16px;
`;

export const CustomInput = styled.input`
export const CustomInput = styled.input<{
customClearButton?: boolean;
}>`
${typography.sizeCSS.normal}
width: 100%;
padding: 4px 8px 4px 8px;
padding: 4px 10px;
border: 1px solid ${palette.solid.lightgrey3};
color: ${palette.highlight.grey9};
color: ${palette.solid.darkgrey};
background: ${palette.solid.lightgrey2};
border-radius: 2px;

&::placeholder {
color: ${palette.highlight.grey5};
}

${({ customClearButton }) =>
customClearButton &&
`&::-webkit-search-cancel-button {
-webkit-appearance: none;
}`}
`;

export const CustomClearButton = styled.button`
border: 0;
padding: 0;
background: transparent;
color: ${palette.highlight.grey8};
cursor: pointer;
`;

export const NoResultsFoundWrapper = styled.div`
Expand Down
24 changes: 24 additions & 0 deletions common/components/Dropdown/Dropdown.tsx
Expand Up @@ -15,9 +15,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import { Icon, IconSVG } from "@recidiviz/design-system";
import React, { useEffect, useRef, useState } from "react";

import dropdownCaret from "../../assets/dropdown-caret.svg";
import { palette } from "../GlobalStyles";
import * as Styled from "./Dropdown.styled";
import {
DropdownMenuAlignment,
Expand All @@ -40,6 +42,7 @@ type DropdownProps = {
fullHeight?: boolean;
highlightIcon?: React.ReactNode;
typeaheadSearch?: { placeholder: string };
customClearSearchButton?: string;
};

/**
Expand Down Expand Up @@ -71,6 +74,7 @@ export function Dropdown({
fullHeight,
highlightIcon,
typeaheadSearch,
customClearSearchButton,
}: DropdownProps) {
const [filteredOptions, setFilteredOptions] = useState<DropdownOption[]>();
const [inputValue, setInputValue] = useState("");
Expand Down Expand Up @@ -133,8 +137,17 @@ export function Dropdown({
<>
{typeaheadSearch && (
<Styled.CustomInputWrapper>
{customClearSearchButton && (
<Icon
color={palette.highlight.grey8}
kind={IconSVG.Search}
width={20}
rotate={270}
/>
)}
<Styled.CustomInput
ref={inputRef}
customClearButton={Boolean(customClearSearchButton)}
id="dropdown-typeahead"
name="dropdown-typeahead"
type="search"
Expand All @@ -145,6 +158,17 @@ export function Dropdown({
updateFilteredOptions(e.target.value);
}}
/>
{customClearSearchButton && (
<Styled.CustomClearButton
type="button"
onClick={() => {
setInputValue("");
updateFilteredOptions("");
}}
>
{customClearSearchButton}
</Styled.CustomClearButton>
)}
</Styled.CustomInputWrapper>
)}

Expand Down
26 changes: 15 additions & 11 deletions publisher/src/components/DataViz/MetricsDataChart.styled.tsx
Expand Up @@ -56,6 +56,16 @@ export const MetricsViewPanel = styled.div<{
}
`;

export const DisclaimerContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-top: 15px;
padding-bottom: 37px;
width: ${INNER_PANEL_LEFT_CONTAINER_MAX_WIDTH}px;
min-height: 200px;
`;

export const PanelContainerLeft = styled.div`
width: 25%;
min-width: calc(314px + 24px + 95px);
Expand All @@ -64,7 +74,11 @@ export const PanelContainerLeft = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 46px 0 0 24px;
padding: 46px 24px 0;

& > ${DisclaimerContainer} {
margin-top: auto;
}

@media only screen and (max-width: ${MIN_DESKTOP_WIDTH}px) {
display: none;
Expand Down Expand Up @@ -135,16 +149,6 @@ export const MetricItem = styled.div<{ selected?: boolean }>`
}
`;

export const DisclaimerContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-top: 15px;
padding-bottom: 37px;
width: ${INNER_PANEL_LEFT_CONTAINER_MAX_WIDTH}px;
min-height: 200px;
`;

export const DisclaimerTitle = styled.div`
${typography.sizeCSS.small}
`;
Expand Down
2 changes: 2 additions & 0 deletions publisher/src/components/DataViz/MetricsDataChart.tsx
Expand Up @@ -54,6 +54,7 @@ import { ReactComponent as SwitchToDataTableIcon } from "../assets/switch-to-dat
import { AppGuideKeys, GuideKeys } from "../HelpCenter/types";
import { createURLToGuide } from "../HelpCenter/utils";
import { Loading } from "../Loading";
import { ChildAgenciesDropdown } from "../MetricsConfiguration/ChildAgenciesDropdown";
import { DisclaimerBanner } from "../primitives";
import { useSettingsSearchParams } from "../Settings";
import ConnectedDatapointsView from "./ConnectedDatapointsView";
Expand Down Expand Up @@ -250,6 +251,7 @@ export const MetricsDataChart: React.FC = observer(() => {
<Styled.MetricsViewPanel>
{/* List Of Metrics */}
<Styled.PanelContainerLeft>
<ChildAgenciesDropdown view="data" />
mxosman marked this conversation as resolved.
Show resolved Hide resolved
<Styled.SystemsContainer>
<div ref={topShadow.ref} />
<Styled.ScrollShadow
Expand Down
6 changes: 3 additions & 3 deletions publisher/src/components/Menu/Menu.tsx
Expand Up @@ -198,9 +198,9 @@ const Menu: React.FC = () => {
useEffect(() => {
const superagencyId = userStore.isAgencySuperagency(agencyId)
? agencyId
: undefined;
agencyStore.loadChildAgencies(superagencyId);
}, [agencyId, agencyStore, userStore]);
: currentAgency?.super_agency_id;
if (superagencyId) agencyStore.loadChildAgencies(String(superagencyId));
}, [agencyId, currentAgency, agencyStore, userStore]);
mxosman marked this conversation as resolved.
Show resolved Hide resolved

return (
<Styled.MenuContainer isMobileMenuOpen={isMobileMenuOpen}>
Expand Down
@@ -0,0 +1,48 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
// =============================================================================

import {
CustomDropdown,
CustomDropdownMenu,
CustomInput,
CustomInputWrapper,
} from "@justice-counts/common/components/Dropdown";
import { palette } from "@justice-counts/common/components/GlobalStyles";
import styled from "styled-components/macro";

export const DropdownWrapper = styled.div`
padding-bottom: 14px;
& ${CustomDropdown} {
width: 100%;
max-width: 360px;
border: 1px solid ${palette.highlight.grey2};
padding: 0 12px;
}
& ${CustomDropdownMenu} {
box-shadow: none;
border: 1px solid ${palette.highlight.grey2};
}
& ${CustomInputWrapper} {
padding: 12px 16px;
border-bottom: 1px solid ${palette.highlight.grey2};
}
& ${CustomInput} {
border: 0;
outline: 0;
background: transparent;
}
`;
@@ -0,0 +1,91 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
// =============================================================================

import {
Dropdown,
DropdownOption,
} from "@justice-counts/common/components/Dropdown";
import { observer } from "mobx-react-lite";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";

import { useStore } from "../../stores";
import * as Styled from "./ChildAgenciesDropdown.styled";

export const ChildAgenciesDropdown: React.FC<{
view: string;
}> = observer(({ view }) => {
const navigate = useNavigate();
const { agencyId } = useParams() as { agencyId: string };
const { userStore, agencyStore } = useStore();

const { superagencyChildAgencies } = agencyStore;
const currentAgency = userStore.getAgency(agencyId);
const isSuperagency = userStore.isAgencySuperagency(agencyId);
const superagencyId = currentAgency?.super_agency_id ?? agencyId;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry - suggested change here to make this a bit more accurate:

  const superagencyId = isSuperagency
    ? agencyId
    : currentAgency?.super_agency_id;


const isChildAgency = superagencyChildAgencies?.find(
(childAgency) => childAgency.id === currentAgency?.id
);
mxosman marked this conversation as resolved.
Show resolved Hide resolved

const currentSuperagency = userStore.userAgencies?.find(
(agency) => agency.id === superagencyId
);

const superagencyDropdownOptions: DropdownOption[] =
currentSuperagency && isChildAgency
? [
{
key: currentSuperagency.id,
label: `${currentSuperagency.name} (Superagency)`,
onClick: () => navigate(`/agency/${currentSuperagency.id}/${view}`),
},
]
: [];

const childAgenciesDropdownOptions: DropdownOption[] =
superagencyChildAgencies
? superagencyChildAgencies
.map((agency) => ({
key: agency.id,
label: agency.name,
onClick: () => navigate(`/agency/${agency.id}/${view}`),
highlight: agency.id === currentAgency?.id,
}))
.sort((a, b) => a.label.localeCompare(b.label))
: [];

return (
childAgenciesDropdownOptions.length > 0 &&
(isSuperagency || isChildAgency) && (
<Styled.DropdownWrapper>
<Dropdown
label={isSuperagency ? "Select Child Agency" : currentAgency?.name}
options={[
...superagencyDropdownOptions,
...childAgenciesDropdownOptions,
]}
size="small"
caretPosition="right"
fullWidth
typeaheadSearch={{ placeholder: "Search for Agency" }}
customClearSearchButton="Clear"
/>
</Styled.DropdownWrapper>
)
);
});
Expand Up @@ -56,7 +56,7 @@ export const OverviewHeader = styled.div`
export const OverviewDescription = styled.div`
${typography.body};
max-width: 470px;
margin-bottom: 32px;
margin-bottom: 24px;

a,
a:visited {
Expand Down
Expand Up @@ -33,6 +33,7 @@ import { AppGuideKeys, GuideKeys } from "../HelpCenter/types";
import { createURLToGuide } from "../HelpCenter/utils";
import { DisclaimerBanner } from "../primitives";
import { useSettingsSearchParams } from "../Settings";
import { ChildAgenciesDropdown } from "./ChildAgenciesDropdown";
import * as Styled from "./MetricsOverview.styled";

export const MetricsOverview = observer(() => {
Expand Down Expand Up @@ -129,6 +130,8 @@ export const MetricsOverview = observer(() => {
</a>
</Styled.OverviewDescription>

<ChildAgenciesDropdown view="metric-config" />

{/* System Selection */}
{showSystems && (
<Styled.TabbedBarWrapper>
Expand Down
8 changes: 2 additions & 6 deletions publisher/src/stores/AgencyStore.ts
Expand Up @@ -276,12 +276,8 @@ class AgencyStore {
return { settings: newSettings };
};

loadChildAgencies(superagencyId: string | undefined) {
if (superagencyId) {
this.getChildAgencies(superagencyId);
} else {
this.childAgencies = [];
}
loadChildAgencies(superagencyId: string) {
this.getChildAgencies(superagencyId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that it's just calling this one function - we can lose this extra function (loadChildAgencies) and just use getChildAgencies.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small request - in getChildAgencies, if the BE sends a non-200 status, can we add a toast message before throwing an error w/ the same message as the error?

}

updateAgencySystems = (
Expand Down