Skip to content

Commit

Permalink
Filter project list by city council district
Browse files Browse the repository at this point in the history
Working logging of projects

Have project list and list item component

Add accordion component

Add pagination icons, almost add links

Add full agency name

Format the list and panel

Use box instead of button

Comment to answer later

Link filter menu submit to populated list

Add drawer component and break out reusable functions

Add filler text for empty last page edge case

Add test, incorporate some feed back, run lint and prettier

More tests but they're floppy

More feedback and get tests to pass

Add pagination test

clicking on capital project item in list will go to individual project panel

Almost have page search param

Get page to start at 1

Simplify set params function and isolate server side return

Clean up and add accordion test

Customize page number box

Simplify rendering mobile and desktop views and format

Preserve filter serach params when clicking on individual project

Update page box size

Get city council district from service so component doesn't change with query params

Use nested route path for project within district

Use streetscape over chakra and other feedback

Remove allowMultiple prop since only one panel in filter menu

Add link to mobile filter menu render

Remove overlay

Add formatting to list and differentiate mobile and desktop styling

Update height of full drawer

Use slide transition instead of Drawer

Use new go to district button only and fix logic in route

Remove old method to generate nav string

Incorporate feedback

Move link and param logic to project list item and update tests
  • Loading branch information
pratishta committed Aug 16, 2024
1 parent e877149 commit e907f3a
Show file tree
Hide file tree
Showing 19 changed files with 659 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { getYear, getMonth, compareAsc } from "date-fns";
import { formatFiscalYearRange } from "../../utils/utils";
import numbro from "numbro";
import { ChevronLeftIcon } from "@chakra-ui/icons";
import {
Expand All @@ -21,18 +21,6 @@ export interface CapitalProjectDetailPanelProps {
onClose: () => void;
}

const getFiscalYearForDate = (date: Date): number => {
const year = getYear(date);
const month = getMonth(date);
return month <= 6 ? year : year + 1;
};

const formatFiscalYearRange = (minDate: Date, maxDate: Date) => {
return compareAsc(minDate, maxDate) === 0
? `FY${getFiscalYearForDate(minDate)}`
: `FY${getFiscalYearForDate(minDate)} - FY${getFiscalYearForDate(maxDate)}`;
};

export const CapitalProjectDetailPanel = ({
capitalProject,
agencies,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { render, screen } from "@testing-library/react";
import { Agency, CapitalProject, createCapitalProjectPage } from "~/gen";
import { CapitalProjectsAccordionPanel } from "./CapitalProjectsAccordionPanel";
import { BrowserRouter } from "react-router-dom";

describe("CapitalProjectsAccordionPanel", () => {
let capitalProjects: CapitalProject[];
let agencies: Agency[];

beforeAll(() => {
agencies = [
{ initials: "DDC", name: "Department of Design and Construction" },
{ initials: "DEP", name: "Department of Environmental Protection" },
];
capitalProjects = createCapitalProjectPage().capitalProjects;
});

it("should render an Accordion with chevron to collapse project list panel", () => {
render(
// need to wrap in browser component to avoid https://github.com/remix-run/react-router/issues/9187
<BrowserRouter>
<CapitalProjectsAccordionPanel
capitalProjects={capitalProjects}
district={"City Council District 23"}
agencies={agencies}
>
<></>
</CapitalProjectsAccordionPanel>
</BrowserRouter>,
);
expect(
screen.getByLabelText("Toggle project list panel"),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Box,
Flex,
Heading,
} from "@nycplanning/streetscape";
import { Agency, CapitalProject } from "~/gen";
import { CapitalProjectsList } from "./CapitalProjectsList";

export interface CapitalProjectsAccordionPanelProps {
capitalProjects: Array<CapitalProject>;
district: string;
agencies: Agency[];
children: React.ReactNode;
}

export const CapitalProjectsAccordionPanel = ({
capitalProjects,
district,
agencies,
children,
}: CapitalProjectsAccordionPanelProps) => {
return (
<Flex
borderRadius={"base"}
padding={{ base: 3, lg: 4 }}
background={"white"}
direction={"column"}
width={{ base: "full", lg: "21.25rem" }}
maxW={{ base: "21.25rem", lg: "unset" }}
boxShadow={"0px 8px 4px 0px rgba(0, 0, 0, 0.08)"}
gap={4}
>
<Accordion defaultIndex={[0]} allowToggle>
<AccordionItem border="none">
<AccordionButton padding="0px" aria-label="Toggle project list panel">
<Box as="span" flex="1" textAlign="left">
<Heading
color="gray.600"
fontWeight={"bold"}
fontSize={"lg"}
paddingBottom={"8px"}
>
{district}
</Heading>
</Box>
<AccordionIcon size="lg" />
</AccordionButton>
<Box
borderTopWidth={"1px"}
borderTopColor={"gray.400"}
paddingBottom={4}
/>
<AccordionPanel padding={"0px"}>
<CapitalProjectsList
capitalProjects={capitalProjects}
agencies={agencies}
/>
{children}
</AccordionPanel>
</AccordionItem>
</Accordion>
</Flex>
);
};
35 changes: 35 additions & 0 deletions app/components/CapitalProjectsList/CapitalProjectsDrawer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { render, screen } from "@testing-library/react";
import { Agency, CapitalProject, createCapitalProjectPage } from "~/gen";
import { CapitalProjectsDrawer } from "./CapitalProjectsDrawer";
import { BrowserRouter } from "react-router-dom";

describe("CapitalProjectsDrawer", () => {
let capitalProjects: CapitalProject[];
let agencies: Agency[];

beforeAll(() => {
agencies = [
{ initials: "DDC", name: "Department of Design and Construction" },
{ initials: "DEP", name: "Department of Environmental Protection" },
];
capitalProjects = createCapitalProjectPage().capitalProjects;
});

it("should render a Drawer with bar to expand project list panel", () => {
render(
// need to wrap in browser component to avoid https://github.com/remix-run/react-router/issues/9187
<BrowserRouter>
<CapitalProjectsDrawer
capitalProjects={capitalProjects}
district={"City Council District 23"}
agencies={agencies}
>
<></>
</CapitalProjectsDrawer>
</BrowserRouter>,
);
expect(
screen.getByLabelText("Expand project list panel"),
).toBeInTheDocument();
});
});
66 changes: 66 additions & 0 deletions app/components/CapitalProjectsList/CapitalProjectsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Box, Flex, Heading } from "@nycplanning/streetscape";
import { Slide } from "@chakra-ui/react";
import { Agency, CapitalProject } from "~/gen";
import { CapitalProjectsList } from "./CapitalProjectsList";
import { useState } from "react";

export interface CapitalProjectsDrawerProps {
capitalProjects: Array<CapitalProject>;
district: string;
agencies: Agency[];
children: React.ReactNode;
}

export const CapitalProjectsDrawer = ({
capitalProjects,
district,
agencies,
children,
}: CapitalProjectsDrawerProps) => {
const [isExpanded, setIsExpanded] = useState(false);

return (
<Slide direction="bottom" in={true} style={{ zIndex: 10 }}>
<Flex
padding={{ base: 3, lg: 4 }}
background={"white"}
direction={"column"}
borderTopLeftRadius={"base"}
borderTopRightRadius={"base"}
gap={4}
>
<Box
height={"4px"}
width={20}
backgroundColor={"gray.300"}
alignSelf={"center"}
role="button"
borderRadius={"2px"}
aria-label={
isExpanded
? "Collapse project list panel"
: "Expand project list panel"
}
onClick={() => {
setIsExpanded(!isExpanded);
}}
/>
<Heading color="gray.600" fontWeight={"bold"} fontSize={"lg"}>
{district}
</Heading>

<Flex
height={{ base: isExpanded ? "85vh" : "40vh", lg: "auto" }}
direction={"column"}
transition={"height 0.5s ease-in-out"}
>
<CapitalProjectsList
capitalProjects={capitalProjects}
agencies={agencies}
/>
{children}
</Flex>
</Flex>
</Slide>
);
};
30 changes: 30 additions & 0 deletions app/components/CapitalProjectsList/CapitalProjectsList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createCapitalProjectPage } from "~/gen/mocks";
import { Agency, CapitalProject } from "~/gen/types";
import { CapitalProjectsList } from "./CapitalProjectsList";
import { render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";

describe("CapitalProjectsList", () => {
let capitalProjects: CapitalProject[];
let agencies: Agency[];

beforeAll(() => {
agencies = [
{ initials: "DDC", name: "Department of Design and Construction" },
{ initials: "DEP", name: "Department of Environmental Protection" },
];
capitalProjects = createCapitalProjectPage().capitalProjects;
});

it("should render each project in a CapitalProjectsListItem", () => {
render(
<BrowserRouter>
<CapitalProjectsList
capitalProjects={capitalProjects}
agencies={agencies}
/>
</BrowserRouter>,
);
expect(screen.getByRole("link")).toBeInTheDocument();
});
});
60 changes: 60 additions & 0 deletions app/components/CapitalProjectsList/CapitalProjectsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Box, Flex, Text, VStack } from "@nycplanning/streetscape";
import { useState } from "react";
import { Agency, CapitalProject, CapitalProjectBudgeted } from "~/gen";
import { CapitalProjectsListItem } from "./CapitalProjectsListItem";
import { formatFiscalYearRange, currentDate } from "../../utils/utils";

export interface CapitalProjectsListProps {
capitalProjects: CapitalProject[];
agencies: Agency[];
}

export const CapitalProjectsList = ({
capitalProjects,
agencies,
}: CapitalProjectsListProps) => {
const [isExpanded] = useState(false);

const listBody =
capitalProjects.length === 0 ? (
<Text>Reached end of projects</Text>
) : (
capitalProjects.map((capitalProject) => {
return (
<CapitalProjectsListItem
key={`${capitalProject.managingCode}${capitalProject.id}`}
capitalProject={capitalProject as CapitalProjectBudgeted}
agency={
agencies.find(
(agency) => agency.initials === capitalProject.managingAgency,
)?.name
}
yearRange={formatFiscalYearRange(
new Date(capitalProject.minDate),
new Date(capitalProject.maxDate),
)}
/>
);
})
);

return (
<>
<Box paddingBottom={4}>
<Text as={"span"}>
Mapped Capital Projects as of <Text as={"b"}>{currentDate()}</Text>
</Text>
</Box>
<Flex direction={"column"} overflow={"hidden"}>
<Box
height={{ base: isExpanded ? "70vh" : "70vh" }}
overflowY={{ base: "scroll" }}
>
<VStack align={"start"} gap={3}>
{listBody}
</VStack>
</Box>
</Flex>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { render, screen } from "@testing-library/react";
import { Agency, CapitalProjectBudgeted, createCapitalProject } from "~/gen";
import { CapitalProjectsListItem } from "./CapitalProjectsListItem";
import { BrowserRouter } from "react-router-dom";

describe("CapitalProjectsListItem", () => {
let capitalProject: CapitalProjectBudgeted;
let agencies: Agency[];

beforeAll(() => {
agencies = [
{ initials: "DDC", name: "Department of Design and Construction" },
{ initials: "DEP", name: "Department of Environmental Protection" },
];
capitalProject = createCapitalProject() as CapitalProjectBudgeted;
});

it("should render the capital project list item with description as title", () => {
render(
// need to wrap in browser component to avoid https://github.com/remix-run/react-router/issues/9187
<BrowserRouter>
<CapitalProjectsListItem
capitalProject={capitalProject}
agency={agencies[0].name}
yearRange="FY2024"
/>
</BrowserRouter>,
);
expect(screen.getByText(capitalProject.description)).toBeVisible();
});

it("should render the capital project list item with agency", () => {
render(
// need to wrap in browser component to avoid https://github.com/remix-run/react-router/issues/9187
<BrowserRouter>
<CapitalProjectsListItem
capitalProject={capitalProject}
agency={agencies[0].name}
yearRange="FY2024"
/>
</BrowserRouter>,
);
expect(screen.getByText(agencies[0].name)).toBeVisible();
});

it("should render the capital project list item with year", () => {
render(
// need to wrap in browser component to avoid https://github.com/remix-run/react-router/issues/9187
<BrowserRouter>
<CapitalProjectsListItem
capitalProject={capitalProject}
agency={agencies[0].name}
yearRange="FY2024"
/>
</BrowserRouter>,
);
expect(screen.getByText("FY2024")).toBeVisible();
});

it("hovering over list item should highlight project on map", () => {
render(
// need to wrap in browser component to avoid https://github.com/remix-run/react-router/issues/9187
<BrowserRouter>
<CapitalProjectsListItem
capitalProject={capitalProject}
agency={agencies[0].name}
yearRange="FY2024"
/>
</BrowserRouter>,
);
});
});
Loading

0 comments on commit e907f3a

Please sign in to comment.