Skip to content

Commit

Permalink
Add mold and moisture info to NYCHA LOC (#2468)
Browse files Browse the repository at this point in the history
* add mold/moisture modal to issues flow, add occ info to confirmation page

* add updated nycha BBL csv

* add mold moisture specific letter content, update yml to build this branch on demosite

* lint

* handle initial state of work order form, the 'I dont have a ticket number' checkbox should not be checked

* fix failing test

* remove mold moisture branch from building
  • Loading branch information
kiwansim authored Jan 14, 2025
1 parent 4dbf7eb commit c75f685
Show file tree
Hide file tree
Showing 15 changed files with 274 additions and 61 deletions.
25 changes: 12 additions & 13 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ jobs:
# We need to save the Git LFS cache directory to save bandwidth, because GitHub only
# allows for 1 GB download of anything stored in Git LFS per month.
- .git/lfs
- restore_cache: &restore_pipenv_cache
# https://circleci.com/docs/2.0/caching/#pip-python
- restore_cache:
&restore_pipenv_cache # https://circleci.com/docs/2.0/caching/#pip-python
name: Restore Pipenv cache
keys:
# when lock file changes, use increasingly general patterns to restore cache
Expand All @@ -41,15 +41,15 @@ jobs:
- run: &install_python_deps
name: Install Python dependencies
command: |
pipenv sync --dev
pipenv run pip install -r requirements.production.txt
pipenv sync --dev
pipenv run pip install -r requirements.production.txt
- save_cache:
name: Save Pipenv cache
key: pip-packages-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}-{{ checksum "requirements.production.txt" }}
paths:
- ".venv"
- restore_cache: &restore_yarn_cache
# https://circleci.com/docs/2.0/caching/#yarn-node
- restore_cache:
&restore_yarn_cache # https://circleci.com/docs/2.0/caching/#yarn-node
name: Restore Yarn cache
keys:
# when lock file changes, use increasingly general patterns to restore cache
Expand All @@ -59,14 +59,14 @@ jobs:
- run: &install_node_deps
name: Install Node dependencies
command: |
# Print out yarn's cache dir for debugging purposes.
yarn cache dir
# Print out yarn's cache dir for debugging purposes.
yarn cache dir
# This will ensure that 'npm prepare' scripts on dependencies are run.
# For more details, see: https://stackoverflow.com/a/52767310
yarn config set unsafe-perm true
# This will ensure that 'npm prepare' scripts on dependencies are run.
# For more details, see: https://stackoverflow.com/a/52767310
yarn config set unsafe-perm true
yarn install --frozen-lockfile
yarn install --frozen-lockfile
- save_cache:
name: Save Yarn cache
paths:
Expand Down Expand Up @@ -225,7 +225,6 @@ workflows:
filters:
branches:
only:
- nycha-work-order
- master
only_deploy:
jobs:
Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/issues/route-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function createIssuesRouteInfo(prefix: string): IssuesRouteInfo {
return {
[ROUTE_PREFIX]: prefix,
home: prefix,
modal: `${prefix}/covid-risk-modal`,
modal: `${prefix}/mold-moisture-modal`,
area: {
parameterizedRoute: `${prefix}/:area`,
create: (area: string) => `${prefix}/${area}`,
Expand Down
66 changes: 65 additions & 1 deletion frontend/lib/issues/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import { FormContext } from "../forms/form-context";
import { Formset } from "../forms/formset";
import { FormsetItem, formsetItemProps } from "../forms/formset-item";
import { TextualFieldWithCharsRemaining } from "../forms/chars-remaining";
import { Modal } from "../ui/modal";
import { OutboundLink } from "../ui/outbound-link";

const checkSvg = require("../svg/check-solid.svg") as JSX.Element;

Expand Down Expand Up @@ -310,6 +312,35 @@ export function groupByTwo<T>(arr: T[]): [T, T | null][] {

type IssuesHomeProps = IssuesRoutesProps;

const MoldMoistureMessage = () => (
<>
<p>
NYCHA is under a court order to remediate problems with mold and moisture
in their buildings. This includes peeling paint.
</p>
<ol type="1">
<li>
How to report mold issues through NYCHA’s{" "}
<OutboundLink
href={"https://www.nyc.gov/site/nycha/residents/mold-busters.page"}
target="_blank"
>
Mold Busters program.
</OutboundLink>
</li>
<li>
For additional support with mold, moisture, and leak repair issues,
contact the independent court-ordered Ombudsperson’s Call Center at{" "}
<OutboundLink href={"tel:+18883417152"}>1-888-341-7152</OutboundLink> or{" "}
<OutboundLink href={"https://ombnyc.com/"} target="_blank">
ombnyc.com
</OutboundLink>
</li>
</ol>
<p>We’ll provide this information again after you complete your letter.</p>
</>
);

class IssuesHome extends React.Component<IssuesHomeProps> {
constructor(props: IssuesHomeProps) {
super(props);
Expand All @@ -331,6 +362,14 @@ class IssuesHome extends React.Component<IssuesHomeProps> {
This <strong>issue checklist</strong> will be sent to your landlord.
</>
);

let housingTypeFromSession = "";
if (typeof window !== "undefined") {
housingTypeFromSession =
window.sessionStorage.getItem("housingType") || "";
}
const isUserNycha = housingTypeFromSession === "NYCHA";

return (
<Page title="Home self-inspection" withHeading>
<div>
Expand All @@ -352,9 +391,31 @@ class IssuesHome extends React.Component<IssuesHomeProps> {
<Link to={this.props.toBack} className="button is-light is-medium">
Back
</Link>
<IssuesLinkToNextStep toNext={this.props.toNext} />
<IssuesLinkToNextStep
toNext={isUserNycha ? this.props.routes.modal : this.props.toNext}
/>
</ProgressButtons>
</div>
{this.props.withModal && isUserNycha && (
<Modal
title="Mold and Moisture"
withHeading
onCloseGoTo={this.props.toNext}
render={(ctx) => (
<>
<MoldMoistureMessage />
<div className="has-text-centered">
<Link
className={`button is-primary is-medium`}
{...ctx.getLinkCloseProps()}
>
Got it
</Link>
</div>
</>
)}
/>
)}
</Page>
);
}
Expand All @@ -370,18 +431,21 @@ type IssuesRoutesProps = {

export function IssuesRoutes(props: IssuesRoutesProps): JSX.Element {
const { routes } = props;

return (
<Switch>
<Route
path={routes.home}
exact
render={() => <IssuesHome {...props} />}
/>

<Route
path={routes.modal}
exact
render={() => <IssuesHome {...props} withModal={true} />}
/>

<Route
path={routes.area.parameterizedRoute}
render={(ctx) => <IssuesArea {...ctx} toHome={routes.home} />}
Expand Down
88 changes: 88 additions & 0 deletions frontend/lib/loc/letter-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ const HEAT_ISSUE_CHOICES = new Set<IssueChoice>([
"PUBLIC_AREAS__NO_HOT_WATER",
]);

const MOLD_MOISTURE_ISSUE_CHOICES = new Set<IssueChoice>([
"HOME__MOLD",
"BEDROOMS__PAINT",
"BEDROOMS__MOLD_ON_WALLS",
"BEDROOMS__WATER_DAMAGE",
"BEDROOMS__CEILING_LEAKING",
"KITCHEN__MOLD",
"KITCHEN__WATER",
"KITCHEN__PAINT",
"KITCHEN__CEILING_LEAKING",
"KITCHEN__FAUCET_LEAKING",
"KITCHEN__PIPES",
"LIVING_ROOM__MOLD",
"LIVING_ROOM__WATER",
"LIVING_ROOM__PAINT",
"LIVING_ROOM__CEILING_LEAKING",
"BATHROOMS__MOLD",
"BATHROOMS__WATER",
"BATHROOMS__PAINT",
"BATHROOMS__CEILING_LEAKING",
"BATHROOMS__TOILET_LEAKING",
"BATHROOMS__SINK_FAUCET_LEAKING",
"BATHROOMS__SINK_PIPES",
"BATHROOMS__TUB_FAUCET_LEAKING",
"BATHROOMS__TUB_PIPES",
"BATHROOMS__SHOWER_MOLD",
"BATHROOMS__SHOWER_NO_FAUCET",
"PUBLIC_AREAS__PAINT",
]);

type Issue =
| { kind: "choice"; choice: IssueChoice }
| { kind: "custom"; value: string };
Expand All @@ -43,6 +73,7 @@ type LocContentProps = BaseLetterContentProps & {
accessDates: GraphQLDate[];
hasCalled311: boolean | null;
workOrderTickets?: string[] | null;
isUserNycha?: boolean;
};

const LetterTitle: React.FC<LocContentProps> = (props) => (
Expand Down Expand Up @@ -116,6 +147,50 @@ const WorkOrderTickets: React.FC<LocContentProps> = (props) => (
</div>
);

const MoldMoistureMandate: React.FC<LocContentProps> = (props) => {
const areaLabels = getIssueAreaChoiceLabels();
const issueLabels = getIssueChoiceLabels();

return (
<>
<p>
I have identified the following issues related to mold, leaks or
associated repairs:
</p>
{props.issues.map((areaIssues) => (
<React.Fragment key={areaIssues.area}>
{areaIssues.issues.some(
(issue) =>
issue.kind === "choice" &&
MOLD_MOISTURE_ISSUE_CHOICES.has(issue.choice)
) && (
<div>
<h3>{areaLabels[areaIssues.area]}</h3>
<ul>
{areaIssues.issues
.filter(
(issue) =>
issue.kind === "choice" &&
MOLD_MOISTURE_ISSUE_CHOICES.has(issue.choice)
)
.map((issue, i) => (
<li key={i}>
{issue.kind === "choice" && issueLabels[issue.choice]}
</li>
))}
</ul>
</div>
)}
</React.Fragment>
))}
<p>
NYCHA or RAD-PACT management is under a court mandate to remediate these
issues in a timely manner.
</p>
</>
);
};

function hasHeatIssues(issues: AreaIssues[]): boolean {
return issues.some((areaIssues) =>
areaIssues.issues.some(
Expand All @@ -124,6 +199,15 @@ function hasHeatIssues(issues: AreaIssues[]): boolean {
);
}

function meetsMoldMoistureMandate(issues: AreaIssues[]): boolean {
return issues.some((areaIssues) =>
areaIssues.issues.some(
(issue) =>
issue.kind == "choice" && MOLD_MOISTURE_ISSUE_CHOICES.has(issue.choice)
)
);
}

const Requirements: React.FC<LocContentProps> = (props) => (
<div className="jf-avoid-page-breaks-within">
<h2>Requirements</h2>
Expand All @@ -140,6 +224,9 @@ const Requirements: React.FC<LocContentProps> = (props) => (
through the NYC Housing Court system.
</p>
)}
{!!props.isUserNycha && meetsMoldMoistureMandate(props.issues) && (
<MoldMoistureMandate {...props} />
)}
</div>
);

Expand Down Expand Up @@ -267,6 +354,7 @@ export function getLocContentPropsFromSession(
return {
...sessionProps,
workOrderTickets: session.workOrderTickets,
isUserNycha: true,
};
}

Expand Down
Loading

0 comments on commit c75f685

Please sign in to comment.