From 81c8e2dcf64ed5e00d21228be5125bdddf05918d Mon Sep 17 00:00:00 2001 From: nasaownsky Date: Fri, 22 Nov 2024 19:04:39 +0400 Subject: [PATCH 1/8] Add agency definition setting UI and logic --- common/types.ts | 3 +- .../AgencySettings/AgencySettings.styles.tsx | 1 + .../AgencySettings/AgencySettings.tsx | 5 + .../AgencySettingsDefinition.tsx | 305 ++++++++++++++++++ .../AgencySettings/IncludesExcludes/types.ts | 2 +- .../src/components/AgencySettings/types.ts | 3 +- 6 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 publisher/src/components/AgencySettings/AgencySettingsDefinition.tsx diff --git a/common/types.ts b/common/types.ts index 25029a106..910a32bbd 100644 --- a/common/types.ts +++ b/common/types.ts @@ -87,7 +87,8 @@ export type AgencySettingType = | "HOMEPAGE_URL" | "ZIPCODE" | "DATA_SHARING_TYPE" - | "BIOLOGICAL_SEX_RACE_ETHNICITY_DATA_SOURCE"; + | "BIOLOGICAL_SEX_RACE_ETHNICITY_DATA_SOURCE" + | "SECTOR_INCLUDES_EXCLUDES"; export interface AgencySetting { setting_type: AgencySettingType; diff --git a/publisher/src/components/AgencySettings/AgencySettings.styles.tsx b/publisher/src/components/AgencySettings/AgencySettings.styles.tsx index f708d9df8..53c19ec66 100644 --- a/publisher/src/components/AgencySettings/AgencySettings.styles.tsx +++ b/publisher/src/components/AgencySettings/AgencySettings.styles.tsx @@ -453,6 +453,7 @@ export const DataSourceContainer = styled.div` padding: 16px 40px; `; export const DataSourceTitle = styled.div` + text-transform: capitalize; font-weight: 700; padding-bottom: 8px; `; diff --git a/publisher/src/components/AgencySettings/AgencySettings.tsx b/publisher/src/components/AgencySettings/AgencySettings.tsx index 7d283fbe3..1c6add13f 100644 --- a/publisher/src/components/AgencySettings/AgencySettings.tsx +++ b/publisher/src/components/AgencySettings/AgencySettings.tsx @@ -27,6 +27,7 @@ import { } from "./AgencySettings.styles"; import { AgencySettingsBasicInfo } from "./AgencySettingsBasicInfo"; import AgencySettingsDataSource from "./AgencySettingsDataSource"; +import AgencySettingsDefinition from "./AgencySettingsDefinition"; // TODO(#1537) Ungate zipcode and agency data sharing fields // import AgencySettingsDataSharingType from "./AgencySettingsDataSharingType"; import AgencySettingsDescription from "./AgencySettingsDescription"; @@ -44,6 +45,7 @@ export enum ActiveSetting { Supervisions = "SUPERVISIONS", Jurisdictions = "JURISDICTIONS", DataSource = "BIOLOGICAL_SEX_RACE_ETHNICITY_DATA_SOURCE", + Definition = "SECTOR_INCLUDES_EXCLUDES", } export type SettingProps = { @@ -97,6 +99,9 @@ export const AgencySettings: React.FC = observer(() => { + {/* TODO(#1537) Ungate zipcode and agency data sharing fields */} {/* */} diff --git a/publisher/src/components/AgencySettings/AgencySettingsDefinition.tsx b/publisher/src/components/AgencySettings/AgencySettingsDefinition.tsx new file mode 100644 index 000000000..d7fe96636 --- /dev/null +++ b/publisher/src/components/AgencySettings/AgencySettingsDefinition.tsx @@ -0,0 +1,305 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2024 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 { Button } from "@justice-counts/common/components/Button"; +import { CheckboxOptions } from "@justice-counts/common/components/CheckboxOptions"; +import { NewInput } from "@justice-counts/common/components/Input"; +import { Modal } from "@justice-counts/common/components/Modal"; +import { + AgencySystems, + SupervisionSubsystems, +} from "@justice-counts/common/types"; +import { removeSnakeCase } from "@justice-counts/common/utils"; +import { observer } from "mobx-react-lite"; +import React, { useEffect, useMemo, useState } from "react"; +import { useParams } from "react-router-dom"; + +import { useStore } from "../../stores"; +import { ChooseDefaultSettings } from "../MetricsConfiguration/ModalForm.styled"; +import { SettingProps } from "./AgencySettings"; +import { + AgencyInfoBlockDescription, + AgencySettingsBlock, + BasicInfoBlockTitle, + DataSourceContainer, + DataSourceQuestionWrapper, + DataSourceTitle, + EditButtonContainer, +} from "./AgencySettings.styles"; +import { AgencySettingsEditModeModal } from "./AgencySettingsEditModeModal"; +import { + AgencyIncludesExcludes, + boolToYesNoEnum, +} from "./IncludesExcludes/includesExcludes"; +import { + AgencySystemKeys, + IncludesExcludesEnum, +} from "./IncludesExcludes/types"; + +type IncludedValue = `${IncludesExcludesEnum}`; + +type AgencyDefinitionSetting = { + sector: AgencySystemKeys; + settings: { + key: string; + included: IncludedValue; + }[]; +}; + +const getDefaultSetting = ( + systems: AgencySystemKeys[], + defaultIncluded?: IncludedValue +): AgencyDefinitionSetting[] => { + if (!systems.length) return []; + + return systems.map((system) => { + return { + sector: system, + settings: Object.entries(AgencyIncludesExcludes[system]).map( + ([key, obj]) => { + return { + key, + included: defaultIncluded ?? obj.default, + }; + } + ), + }; + }); +}; + +const AgencySettingsDefinition: React.FC<{ + settingProps: SettingProps; +}> = ({ settingProps }) => { + const { isSettingInEditMode, openSetting, removeEditMode, allowEdit } = + settingProps; + + const { agencyId } = useParams() as { agencyId: string }; + const { agencyStore } = useStore(); + const { + currentAgencySystems, + currentAgencySettings, + updateAgencySettings, + saveAgencySettings, + } = agencyStore; + + const isSupervisionAgency = currentAgencySystems?.includes( + AgencySystems.SUPERVISION + ); + const isCourtAgency = currentAgencySystems?.includes( + AgencySystems.COURTS_AND_PRETRIAL + ); + + const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); + const [currentSystems, setCurrentSystems] = useState([]); + const [descriptionValue, setDescriptionValue] = useState(""); + + const agencyDefinitionSetting = useMemo(() => { + return ( + (currentAgencySettings?.find( + (setting) => setting.setting_type === "SECTOR_INCLUDES_EXCLUDES" + )?.value as AgencyDefinitionSetting[]) || [] + ); + }, [currentAgencySettings]); + + const isSettingConfigured = agencyDefinitionSetting.length > 0; + + useEffect(() => { + setCurrentSystems([]); + if (isSupervisionAgency) + setCurrentSystems( + (currentAgencySystems?.filter((system) => + SupervisionSubsystems.includes(system) + ) as AgencySystemKeys[]) || [] + ); + if (isCourtAgency) setCurrentSystems([AgencySystems.COURTS_AND_PRETRIAL]); + }, [currentAgencySystems, isCourtAgency, isSupervisionAgency]); + + const defaultSetting = isSettingConfigured + ? agencyDefinitionSetting + : getDefaultSetting(currentSystems, IncludesExcludesEnum.NO); + + const [updatedSetting, setUpdatedSetting] = useState(defaultSetting); + + useEffect(() => { + setUpdatedSetting(defaultSetting); + setDescriptionValue(""); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agencyDefinitionSetting, currentSystems]); + + const handleSaveClick = () => { + const updatedSettings = updateAgencySettings( + "SECTOR_INCLUDES_EXCLUDES", + updatedSetting, + parseInt(agencyId) + ); + saveAgencySettings(updatedSettings, agencyId); + removeEditMode(); + }; + + const handleCancelClick = () => { + if (JSON.stringify(updatedSetting) === JSON.stringify(defaultSetting)) { + removeEditMode(); + } else { + setIsConfirmModalOpen(true); + } + }; + + const handleCancelModalConfirm = () => { + setUpdatedSetting(defaultSetting); + setIsConfirmModalOpen(false); + removeEditMode(); + }; + + if (!defaultSetting.length) return null; + + return ( + <> + {isSettingInEditMode && ( + setIsConfirmModalOpen(false)} + handleCancelModalConfirm={handleCancelModalConfirm} + > + + + Indicate which of the following categories best defines your + agency. Or, choose the{" "} + + setUpdatedSetting(getDefaultSetting(currentSystems)) + } + > + Justice Counts definition. + + + + {currentSystems?.map((system) => { + return ( + + {isSupervisionAgency && ( + + {removeSnakeCase(system).toLocaleLowerCase()} + + )} + { + const included = updatedSetting + .find((sector) => sector.sector === system) + ?.settings.find( + (setting) => setting.key === key + )?.included; + + return { + key, + label: mapObj.label, + checked: included === IncludesExcludesEnum.YES, + }; + } + ), + ]} + onChange={({ key, checked }) => { + setUpdatedSetting((prevSettings) => { + const updates = prevSettings.map((sector) => { + if (sector.sector === system) { + return { + ...sector, + settings: sector.settings.map((setting) => + setting.key === key + ? { + ...setting, + included: boolToYesNoEnum(!checked), + } + : setting + ), + }; + } + return sector; + }); + + return updates; + }); + }} + /> + + ); + })} + setDescriptionValue(e.target.value)} + fullWidth + /> + + } + buttons={[ + { + label: "Save", + onClick: () => { + handleSaveClick(); + }, + }, + ]} + maxHeight={777} + modalBackground="opaque" + onClickClose={handleCancelClick} + agencySettingsConfigs + jurisdictionsSettingsConfigs + agencySettingsAndJurisdictionsTitleConfigs + customPadding="4px 40px 24px 40px" + /> + + )} + + + + {isSupervisionAgency ? "Supervision" : "Court"} Agency Definition + {allowEdit && ( + +