diff --git a/src/app/App.tsx b/src/app/App.tsx index f421c466..d95e4b3c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -16,6 +16,7 @@ import { EventPage } from "../pages/EventPage"; import { EventsPage } from "../pages/EventsPage"; import { PersonPage } from "../pages/PersonPage"; import { PeoplePage } from "../pages/PeoplePage"; +import { MatterPage } from "../pages/MatterPage"; import { ErrorPage } from "../pages/ErrorPage"; import { SEARCH_TYPE } from "../pages/SearchPage/types"; @@ -102,6 +103,9 @@ function App() { + + + diff --git a/src/assets/LocalizedStrings.tsx b/src/assets/LocalizedStrings.tsx index 608cd46c..c0a955f4 100644 --- a/src/assets/LocalizedStrings.tsx +++ b/src/assets/LocalizedStrings.tsx @@ -101,6 +101,7 @@ export interface MasterStringsList extends LocalizedStringsMethods { latest_vote: string; history: string; minutes: string; + go_to_matter_details: string; } // Note: Add languages to video.js in /src/pages/EventPage/utils when adding new languages diff --git a/src/assets/strings/de.ts b/src/assets/strings/de.ts index cbb34d09..0907ba13 100644 --- a/src/assets/strings/de.ts +++ b/src/assets/strings/de.ts @@ -90,6 +90,7 @@ const de = { latest_vote: "Letzte Abstimmung", history: "Geschichte", minutes: "Protokoll", + go_to_matter_details: "Gehen Sie zu den vollständigen Rechtsvorschriften", }; export default de; diff --git a/src/assets/strings/en.ts b/src/assets/strings/en.ts index 712e7f40..75e227d6 100644 --- a/src/assets/strings/en.ts +++ b/src/assets/strings/en.ts @@ -89,6 +89,7 @@ const en = { latest_vote: "Latest Vote", history: "History", minutes: "Minutes", + go_to_matter_details: "Go to Full Legislation Details", }; export default en; diff --git a/src/assets/strings/es.ts b/src/assets/strings/es.ts index 4ce1aee2..44aa0a54 100644 --- a/src/assets/strings/es.ts +++ b/src/assets/strings/es.ts @@ -89,6 +89,7 @@ const es = { latest_vote: "Último Voto", history: "Historia", minutes: "Protocolo", + go_to_matter_details: "Ir a los detalles completos de la legislación", }; export default es; diff --git a/src/components/Details/Legislation/LegislationIntroduction.tsx b/src/components/Details/Legislation/LegislationIntroduction.tsx index 3bc0e841..886e14d6 100644 --- a/src/components/Details/Legislation/LegislationIntroduction.tsx +++ b/src/components/Details/Legislation/LegislationIntroduction.tsx @@ -38,12 +38,13 @@ const LegislationIntroduction: FC = ({ flex: 1, }} > - {indexedMatterGrams.map((indexedMatterGram) => ( -
-

-

{indexedMatterGram.unstemmed_gram}

-
- ))} + {indexedMatterGrams && + indexedMatterGrams.map((indexedMatterGram) => ( +
+

+

{indexedMatterGram.unstemmed_gram}

+
+ ))} diff --git a/src/components/Details/Legislation/LegislationOverview.tsx b/src/components/Details/Legislation/LegislationOverview.tsx index fae9038a..21105c0f 100644 --- a/src/components/Details/Legislation/LegislationOverview.tsx +++ b/src/components/Details/Legislation/LegislationOverview.tsx @@ -63,11 +63,11 @@ export interface LegislationOverviewProps { /** The latest matter status of the matter */ matterStatus: MatterStatus; /** The latest event where the matter was a minutes item */ - event: Event; + event?: Event; /** The persons who sponsored the matter */ sponsors: Person[]; /** The latest document of the matter */ - document: { name: string; url: string }; + document?: { name: string; url: string }; } const LegislationOverview: FC = ({ @@ -103,27 +103,31 @@ const LegislationOverview: FC = ({ -
-
Latest Meeting:
-
- - {event.event_datetime.toLocaleDateString(language, { - timeZone: municipality.timeZone, - month: "short", - day: "numeric", - year: "numeric", - })} - -
-
-
-
Latest Document:
-
- - {document.name} - -
-
+ {event && ( +
+
Latest Meeting:
+
+ + {event.event_datetime.toLocaleDateString(language, { + timeZone: municipality.timeZone, + month: "short", + day: "numeric", + year: "numeric", + })} + +
+
+ )} + {document && ( +
+
Latest Document:
+
+ + {document.name} + +
+
+ )}
Sponsored by:
diff --git a/src/components/Details/MinutesItemsList/MinutesItemsList.tsx b/src/components/Details/MinutesItemsList/MinutesItemsList.tsx index 4656cff8..0b35333c 100644 --- a/src/components/Details/MinutesItemsList/MinutesItemsList.tsx +++ b/src/components/Details/MinutesItemsList/MinutesItemsList.tsx @@ -1,11 +1,12 @@ import React, { FC } from "react"; import styled from "@emotion/styled"; -/* import { Link } from "react-router-dom"; */ +import { Link } from "react-router-dom"; import DocumentsList from "./DocumentsList"; -/* import ChevronDownIcon from "../../Shared/ChevronDownIcon"; */ +import ChevronDownIcon from "../../Shared/ChevronDownIcon"; import { Item } from "./types"; +import { strings } from "../../../assets/LocalizedStrings"; const ListItem = styled.li({ "& > div:first-of-type": { @@ -37,11 +38,11 @@ const MinutesItemsList: FC = ({ minutesItems }: MinutesIt return (
{elem.name}
- {/* {elem.matter_ref && ( + {elem.matter_ref && ( - {"Go to Full Legislation Details"} + {strings.go_to_matter_details} - )} */} + )} {elem.description &&
{elem.description}
}
diff --git a/src/components/Tables/MeetingVotesTableRow/MeetingVotesTableRow.tsx b/src/components/Tables/MeetingVotesTableRow/MeetingVotesTableRow.tsx index 6d958ed4..df43c0b0 100644 --- a/src/components/Tables/MeetingVotesTableRow/MeetingVotesTableRow.tsx +++ b/src/components/Tables/MeetingVotesTableRow/MeetingVotesTableRow.tsx @@ -98,7 +98,7 @@ function VoteCell(isExpanded: boolean, votes: IndividualMeetingVote[], isMobile: const MeetingVotesTableRow = ({ index, - /* legislationLink, */ + legislationLink, legislationName, legislationDescription, councilDecision, @@ -120,10 +120,11 @@ const MeetingVotesTableRow = ({ }} >
- {/* {legislationName} */} -

- {legislationName} -

+ +

+ {legislationName} +

+ {!isMobile &&

{legislationDescription}

}
diff --git a/src/components/Tables/VotingTableRow/VotingTableRow.tsx b/src/components/Tables/VotingTableRow/VotingTableRow.tsx index 03e65da8..960041de 100644 --- a/src/components/Tables/VotingTableRow/VotingTableRow.tsx +++ b/src/components/Tables/VotingTableRow/VotingTableRow.tsx @@ -35,7 +35,7 @@ export type VotingTableRowProps = { const VotingTableRow = ({ index, - /* legislationLink, */ + legislationLink, legislationName, legislationTags, voteDecision, @@ -67,10 +67,11 @@ const VotingTableRow = ({ columnDistribution={columnDistribution} > - {/* {legislationName} */} -

- {legislationName} -

+ +

+ {legislationName} +

+ {!isMobile &&

{legislationTagsString}

}
diff --git a/src/containers/MatterContainer/MatterContainer.tsx b/src/containers/MatterContainer/MatterContainer.tsx new file mode 100644 index 00000000..98d2805b --- /dev/null +++ b/src/containers/MatterContainer/MatterContainer.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import styled from "@emotion/styled"; + +import MatterStatus from "../../models/MatterStatus"; +import Event from "../../models/Event"; +import Person from "../../models/Person"; +import Vote from "../../models/Vote"; +import IndexedMatterGram from "../../models/IndexedMatterGram"; +import EventMinutesItem from "../../models/EventMinutesItem"; + +import LegislationIntroduction from "../../components/Details/Legislation/LegislationIntroduction"; +import LegislationOverview from "../../components/Details/Legislation/LegislationOverview"; +import { LegislationLatestVote } from "../../components/Details/Legislation/LegislationLatestVote"; +import { LegislationHistory } from "../../components/Details/Legislation/LegislationHistory"; + +const Matter = styled.div({ + display: "grid", + gridTemplateColumns: "1fr", + rowGap: 64, +}); +interface MatterContainerProps { + matterStatus: MatterStatus; + indexedMatterGrams: IndexedMatterGram[]; + event?: Event; + sponsors: Person[]; + votes?: Vote[]; + legislationHistory?: EventMinutesItem[]; +} + +const MatterContainer = ({ + matterStatus, + indexedMatterGrams, + event, + sponsors, + votes, + legislationHistory, +}: MatterContainerProps) => { + return ( + + + + {votes && } + {legislationHistory && } + + ); +}; + +export default MatterContainer; diff --git a/src/containers/MatterContainer/index.ts b/src/containers/MatterContainer/index.ts new file mode 100644 index 00000000..caafa980 --- /dev/null +++ b/src/containers/MatterContainer/index.ts @@ -0,0 +1 @@ +export { default as MatterContainer } from "./MatterContainer"; diff --git a/src/containers/PersonContainer/MattersSponsored.tsx b/src/containers/PersonContainer/MattersSponsored.tsx index fed08260..8562598a 100644 --- a/src/containers/PersonContainer/MattersSponsored.tsx +++ b/src/containers/PersonContainer/MattersSponsored.tsx @@ -1,5 +1,6 @@ import React, { FC, useMemo, useCallback } from "react"; import { Loader } from "semantic-ui-react"; +import { Link } from "react-router-dom"; import { useAppConfigContext } from "../../app"; import useFetchModels, { @@ -74,7 +75,12 @@ const MattersSponsored: FC = ({ personId }: MattersSponso
  • - {matterSponsored.matter?.name} + + {matterSponsored.matter?.name} +
    {matterSponsored.matter?.title}
    diff --git a/src/networking/MatterSponsorService.ts b/src/networking/MatterSponsorService.ts index a9383eca..9803fd03 100644 --- a/src/networking/MatterSponsorService.ts +++ b/src/networking/MatterSponsorService.ts @@ -27,6 +27,29 @@ export default class MatterSponsorService extends ModelService { super(COLLECTION_NAME.MatterSponsor, firebaseConfig); } + async getMatterSponsorByMatterId(matterId: string): Promise { + const populatePerson = new Populate( + COLLECTION_NAME.MatterSponsor, + REF_PROPERTY_NAME.MatterSponsorPersonRef + ); + const networkQueryResponse = this.networkService.getDocuments( + COLLECTION_NAME.MatterSponsor, + [ + where( + REF_PROPERTY_NAME.MatterSponsorMatterRef, + WHERE_OPERATOR.eq, + doc(NetworkService.getDb(), COLLECTION_NAME.Matter, matterId) + ), + ], + new PopulationOptions([populatePerson]) + ); + return this.createModels( + networkQueryResponse, + MatterSponsor, + `getMatterSponsorByMatterId(${matterId})` + ) as Promise; + } + /** * * @param personId The person's id diff --git a/src/networking/MatterStatusService.ts b/src/networking/MatterStatusService.ts new file mode 100644 index 00000000..264b8ee7 --- /dev/null +++ b/src/networking/MatterStatusService.ts @@ -0,0 +1,44 @@ +import ModelService from "./ModelService"; +import { where, orderBy, doc } from "@firebase/firestore"; +import { + COLLECTION_NAME, + Populate, + PopulationOptions, + REF_PROPERTY_NAME, +} from "./PopulationOptions"; +import { ORDER_DIRECTION, WHERE_OPERATOR } from "./constants"; +import { NetworkService } from "./NetworkService"; + +import MatterStatus from "../models/MatterStatus"; +import { FirebaseConfig } from "../app/AppConfigContext"; + +export default class MatterStatusService extends ModelService { + constructor(firebaseConfig: FirebaseConfig) { + super(COLLECTION_NAME.MatterStatus, firebaseConfig); + } + + async getMatterStatusesByMatterId(matterId: string): Promise { + const populateEventMinutesItems = new Populate( + COLLECTION_NAME.MatterStatus, + REF_PROPERTY_NAME.MatterStatusEventMinutesItemRef + ); + + const networkQueryResponse = this.networkService.getDocuments( + COLLECTION_NAME.MatterStatus, + [ + where( + REF_PROPERTY_NAME.MatterStatusMatterRef, + WHERE_OPERATOR.eq, + doc(NetworkService.getDb(), COLLECTION_NAME.Matter, matterId) + ), + orderBy("update_datetime", ORDER_DIRECTION.desc), + ], + new PopulationOptions([populateEventMinutesItems]) + ); + return this.createModels( + networkQueryResponse, + MatterStatus, + `getMatterStatusByMatterId(${matterId})` + ) as Promise; + } +} diff --git a/src/pages/MatterPage/MatterPage.tsx b/src/pages/MatterPage/MatterPage.tsx new file mode 100644 index 00000000..eabb669c --- /dev/null +++ b/src/pages/MatterPage/MatterPage.tsx @@ -0,0 +1,111 @@ +import React, { FC, useCallback } from "react"; +import { useParams } from "react-router-dom"; +import { useAppConfigContext } from "../../app"; +import useFetchData from "../../containers/FetchDataContainer/useFetchData"; +import FetchDataContainer from "../../containers/FetchDataContainer/FetchDataContainer"; +import { MatterContainer } from "../../containers/MatterContainer"; + +import MatterStatus from "../../models/MatterStatus"; +import IndexedMatterGram from "../../models/IndexedMatterGram"; +import EventMinutesItem from "../../models/EventMinutesItem"; +import Event from "../../models/Event"; +import Person from "../../models/Person"; +import Vote from "../../models/Vote"; + +import MatterStatusService from "../../networking/MatterStatusService"; +import EventService from "../../networking/EventService"; +import MatterSponsorService from "../../networking/MatterSponsorService"; +import VoteService from "../../networking/VoteService"; + +interface MatterContainerType { + matterStatus: MatterStatus; + indexedMatterGrams: IndexedMatterGram[]; + event?: Event; + sponsors: Person[]; + votes?: Vote[]; + legislationHistory: EventMinutesItem[]; +} + +const MatterPage: FC = () => { + // Get the app config context + const { firebaseConfig } = useAppConfigContext(); + // Get the id the matter, provided the route is `matter/:id` + const { id } = useParams<{ id: string }>(); + + const fetchMatterDetails = useCallback(async () => { + const matterStatusService = new MatterStatusService(firebaseConfig); + const matterSponsorService = new MatterSponsorService(firebaseConfig); + const eventService = new EventService(firebaseConfig); + + const matterSponsorPromises = matterSponsorService.getMatterSponsorByMatterId(id); + const matterStatusPromises = matterStatusService.getMatterStatusesByMatterId(id); + const responses = await Promise.all([matterSponsorPromises, matterStatusPromises]); + const [matterSponsors, matterStatuses] = responses; // javascript array destructuring shorthand + const sponsors = matterSponsors + .filter((matterSponsor) => { + return matterSponsor.person; + }) + .map((matterSponsor) => { + return matterSponsor.person as Person; + }); + const legislationHistory = matterStatuses.reduce((filtered, optional) => { + if (optional.event_minutes_item) { + filtered.push(optional.event_minutes_item); + } + return filtered; + }, [] as EventMinutesItem[]); + + const latestStatus = matterStatuses[0]; + const matterContainerData: MatterContainerType = { + matterStatus: latestStatus, + indexedMatterGrams: [], + sponsors, + legislationHistory, + }; + + // loop through matterStatuses until we find one where the event_minutes_item is not null + let latestEventMinutesItem: EventMinutesItem | undefined; + for (const matterStatus of matterStatuses) { + if (matterStatus.event_minutes_item) { + latestEventMinutesItem = matterStatus.event_minutes_item; + break; + } + } + if (latestEventMinutesItem) { + const votesService = new VoteService(firebaseConfig); + const eventResponses = await Promise.all([ + votesService.getVotesByEventMinutesItemId(latestEventMinutesItem.id), + eventService.getEventById(latestEventMinutesItem.event_ref), + ]); + matterContainerData.votes = eventResponses[0]; + matterContainerData.event = eventResponses[1]; + } + return matterContainerData; + }, [firebaseConfig, id]); + + const { state: matterDetailsState } = useFetchData( + { + isLoading: false, + error: null, + hasFetchRequest: true, + }, + fetchMatterDetails + ); + + return ( + + {matterDetailsState.data && ( + + )} + + ); +}; + +export default MatterPage; diff --git a/src/pages/MatterPage/index.ts b/src/pages/MatterPage/index.ts new file mode 100644 index 00000000..8467f3f8 --- /dev/null +++ b/src/pages/MatterPage/index.ts @@ -0,0 +1 @@ +export { default as MatterPage } from "./MatterPage";