diff --git a/app/components/CapitalProjectsList/CapitalProjectsPanel.test.tsx b/app/components/CapitalProjectsList/CapitalProjectsPanel.test.tsx new file mode 100644 index 0000000..b65240d --- /dev/null +++ b/app/components/CapitalProjectsList/CapitalProjectsPanel.test.tsx @@ -0,0 +1,17 @@ +import { render, screen } from "@testing-library/react"; +import { CapitalProjectsPanel } from "./CapitalProjectsPanel"; + +describe("CapitalProjectsPanel", () => { + it("should contain a project list", () => { + render( + + <> + , + ); + expect(screen.getByText(/Mapped Capital Projects/)).toBeVisible(); + }); +}); diff --git a/app/components/CapitalProjectsList/CapitalProjectsPanel.tsx b/app/components/CapitalProjectsList/CapitalProjectsPanel.tsx new file mode 100644 index 0000000..17558c2 --- /dev/null +++ b/app/components/CapitalProjectsList/CapitalProjectsPanel.tsx @@ -0,0 +1,38 @@ +import { Hide, Show } from "@nycplanning/streetscape"; +import { + CapitalProjectsAccordionPanel, + CapitalProjectsAccordionPanelProps, +} from "./CapitalProjectsAccordionPanel"; +import { + CapitalProjectsDrawer, + CapitalProjectsDrawerProps, +} from "./CapitalProjectsDrawer"; + +export interface CapitalProjectsPanelProps + extends CapitalProjectsAccordionPanelProps, + CapitalProjectsDrawerProps {} + +export function CapitalProjectsPanel(props: CapitalProjectsPanelProps) { + return ( + <> + + + {props.children} + + + + + {props.children} + + + + ); +} diff --git a/app/components/CapitalProjectsList/index.ts b/app/components/CapitalProjectsList/index.ts index fbd3eed..90349b3 100644 --- a/app/components/CapitalProjectsList/index.ts +++ b/app/components/CapitalProjectsList/index.ts @@ -1,3 +1,4 @@ export { CapitalProjectsAccordionPanel } from "./CapitalProjectsAccordionPanel"; export { CapitalProjectsList } from "./CapitalProjectsList"; export { CapitalProjectsListItem } from "./CapitalProjectsListItem"; +export { CapitalProjectsPanel } from "./CapitalProjectsPanel"; diff --git a/app/routes/boroughs.$boroughId.community-districts.$communityDistrictId.capital-projects.tsx b/app/routes/boroughs.$boroughId.community-districts.$communityDistrictId.capital-projects.tsx index a5b749a..2997735 100644 --- a/app/routes/boroughs.$boroughId.community-districts.$communityDistrictId.capital-projects.tsx +++ b/app/routes/boroughs.$boroughId.community-districts.$communityDistrictId.capital-projects.tsx @@ -1,3 +1,88 @@ +import { Flex } from "@nycplanning/streetscape"; +import { json, LoaderFunctionArgs } from "@remix-run/node"; +import { useLoaderData } from "@remix-run/react"; +import { CapitalProjectsPanel } from "~/components/CapitalProjectsList"; +import { Pagination } from "~/components/Pagination"; +import { + findAgencies, + findBoroughs, + findCapitalProjectsByBoroughIdCommunityDistrictId, +} from "~/gen"; + +export async function loader({ request, params }: LoaderFunctionArgs) { + const url = new URL(request.url); + const itemsPerPage = 7; + const pageParam = url.searchParams.get("page"); + const page = pageParam === null ? 1 : parseInt(pageParam); + + const { boroughId, communityDistrictId } = params; + if ( + boroughId === undefined || + communityDistrictId === undefined || + isNaN(page) + ) { + throw json("Bad request", { status: 400 }); + } + + const offset = (page - 1) * itemsPerPage; + const projectsByCommunityDistrictPromise = + findCapitalProjectsByBoroughIdCommunityDistrictId( + boroughId, + communityDistrictId, + { + limit: itemsPerPage, + offset: offset, + }, + { + baseURL: `${import.meta.env.VITE_ZONING_API_URL}/api`, + }, + ); + + const agenciesPromise = findAgencies({ + baseURL: `${import.meta.env.VITE_ZONING_API_URL}/api`, + }); + const boroughsPromise = findBoroughs({ + baseURL: `${import.meta.env.VITE_ZONING_API_URL}/api`, + }); + const [agenciesResponse, boroughsResponse, capitalProjectsResponse] = + await Promise.all([ + agenciesPromise, + boroughsPromise, + projectsByCommunityDistrictPromise, + ]); + const boroughAbbr = boroughsResponse.boroughs.find( + (borough) => borough.id === boroughId, + )?.abbr; + return { + capitalProjectsResponse, + agencies: agenciesResponse.agencies, + boroughAbbr, + communityDistrictId, + }; +} + export default function CapitalProjectsByBoroughIdCommunityDistrictId() { - return <>; + const { + capitalProjectsResponse: { total: capitalProjectsTotal, capitalProjects }, + agencies, + boroughAbbr, + communityDistrictId, + } = useLoaderData(); + + return ( + + + + + + ); } diff --git a/app/routes/city-council-districts.$cityCouncilDistrictId.capital-projects.tsx b/app/routes/city-council-districts.$cityCouncilDistrictId.capital-projects.tsx index f4ffa3e..2cffe44 100644 --- a/app/routes/city-council-districts.$cityCouncilDistrictId.capital-projects.tsx +++ b/app/routes/city-council-districts.$cityCouncilDistrictId.capital-projects.tsx @@ -1,29 +1,23 @@ import { LoaderFunctionArgs, json } from "@remix-run/node"; import { findCapitalProjectsByCityCouncilId, findAgencies } from "../gen"; import { useLoaderData } from "@remix-run/react"; -import { CapitalProjectsAccordionPanel } from "../components/CapitalProjectsList"; -import { Flex, Hide, Show } from "@nycplanning/streetscape"; -import { CapitalProjectsDrawer } from "~/components/CapitalProjectsList/CapitalProjectsDrawer"; +import { CapitalProjectsPanel } from "../components/CapitalProjectsList"; +import { Flex } from "@nycplanning/streetscape"; import { Pagination } from "~/components/Pagination"; export async function loader({ request, params }: LoaderFunctionArgs) { const url = new URL(request.url); - const agenciesResponse = await findAgencies({ - baseURL: `${import.meta.env.VITE_ZONING_API_URL}/api`, - }); - const itemsPerPage = 7; const pageParam = url.searchParams.get("page"); const page = pageParam === null ? 1 : parseInt(pageParam); - const offset = (page - 1) * itemsPerPage; - const { cityCouncilDistrictId } = params; - if (cityCouncilDistrictId === undefined) { + if (cityCouncilDistrictId === undefined || isNaN(page)) { throw json("Bad Request", { status: 400 }); } + const offset = (page - 1) * itemsPerPage; - const projectsByCityCouncilDistrictResponse = - await findCapitalProjectsByCityCouncilId( + const projectsByCityCouncilDistrictPromise = + findCapitalProjectsByCityCouncilId( cityCouncilDistrictId, { limit: itemsPerPage, @@ -34,48 +28,41 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }, ); + const agenciesPromise = findAgencies({ + baseURL: `${import.meta.env.VITE_ZONING_API_URL}/api`, + }); + + const [agenciesResponse, projectsByCityCouncilDistrictResponse] = + await Promise.all([agenciesPromise, projectsByCityCouncilDistrictPromise]); + return { - projects: projectsByCityCouncilDistrictResponse, + capitalProjectsResponse: projectsByCityCouncilDistrictResponse, agencies: agenciesResponse.agencies, - cityCouncilDistrictId: cityCouncilDistrictId, + cityCouncilDistrictId, }; } export default function CapitalProjectsByCityCouncilDistrict() { - const { projects, agencies, cityCouncilDistrictId } = - useLoaderData(); - - const pagination = ( - - - - ); + const { + capitalProjectsResponse: { total: capitalProjectsTotal, capitalProjects }, + agencies, + cityCouncilDistrictId, + } = useLoaderData(); return ( - <> - - - {pagination} - - - - - {pagination} - - - + + + + + ); }