Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/localize-date-to-instance-time-zone #214

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27,573 changes: 27,494 additions & 79 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Expand Up @@ -105,7 +105,6 @@
"@mozilla-protocol/core": "^13.0.1",
"firebase": "^9.5.0",
"lodash": "^4.17.21",
"moment": "^2.24.0",
"n-gram": "^2.0.1",
"query-string": "^7.0.1",
"react-highlight-words": "^0.17.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app/AppConfigContext.tsx
Expand Up @@ -13,6 +13,7 @@ export interface AppConfig {
firebaseConfig: FirebaseConfig;
municipality: {
name: string;
timeZone: string;
footerLinksSections: {
footerLinksSectionName: string;
links: {
Expand Down Expand Up @@ -40,6 +41,7 @@ const AppConfigContext = createContext<AppConfig>({
},
municipality: {
name: "Seattle Staging",
timeZone: "America/Los_Angeles",
footerLinksSections: [],
},
});
Expand Down
3 changes: 2 additions & 1 deletion src/components/Cards/MeetingCard/MeetingCard.tsx
Expand Up @@ -58,7 +58,7 @@ const Images = styled.div({

const MeetingCard = ({ event, tags, excerpt, gram, query }: MeetingCardProps) => {
const { language } = useLanguageConfigContext();
const { firebaseConfig } = useAppConfigContext();
const { firebaseConfig, municipality } = useAppConfigContext();

const [staticThumbNailIsLoading, setStaticThumbNailIsLoading] = useState(true);

Expand Down Expand Up @@ -98,6 +98,7 @@ const MeetingCard = ({ event, tags, excerpt, gram, query }: MeetingCardProps) =>
);

const meetingDate = event.event_datetime?.toLocaleDateString(language, {
timeZone: municipality.timeZone,
month: "long",
day: "numeric",
year: "numeric",
Expand Down
7 changes: 5 additions & 2 deletions src/components/Details/Legislation/LegislationOverview.tsx
Expand Up @@ -6,7 +6,7 @@ import Event from "../../../models/Event";
import MatterStatus from "../../../models/MatterStatus";
import Person from "../../../models/Person";

import { useLanguageConfigContext } from "../../../app";
import { useAppConfigContext, useLanguageConfigContext } from "../../../app";

import { DecisionResult } from "../../Shared";
import H2 from "../../Shared/H2";
Expand Down Expand Up @@ -76,13 +76,15 @@ const LegislationOverview: FC<LegislationOverviewProps> = ({
sponsors,
document,
}: LegislationOverviewProps) => {
const { municipality } = useAppConfigContext();
const { language } = useLanguageConfigContext();

return (
<div>
<Title hasBorderBottom={true} className="mzp-u-title-xs">
<span>Legislation Overview</span>
<em>{`Last Updated ${matterStatus.update_datetime.toLocaleDateString(language, {
timeZone: municipality.timeZone,
month: "long",
day: "numeric",
year: "numeric",
Expand All @@ -105,7 +107,8 @@ const LegislationOverview: FC<LegislationOverviewProps> = ({
<dt>Latest Meeting:</dt>
<dd>
<Link to={`/events/${event.id}`}>
{event.event_datetime.toLocaleDateString("en-US", {
{event.event_datetime.toLocaleDateString(language, {
timeZone: municipality.timeZone,
month: "short",
day: "numeric",
year: "numeric",
Expand Down
3 changes: 3 additions & 0 deletions src/components/Details/Legislation/LegislativeHistoryNode.tsx
Expand Up @@ -39,6 +39,8 @@ const LegislativeHistoryNode: FC<LegislativeHistoryNodeProps> = ({
eventMinutesItem,
isLastIndex,
}: LegislativeHistoryNodeProps) => {
const { municipality } = useAppConfigContext();

const isMobile = useMediaQuery({ query: `(max-width: ${screenWidths.tablet})` });
const MARGIN = isMobile ? 4 : 12;

Expand Down Expand Up @@ -70,6 +72,7 @@ const LegislativeHistoryNode: FC<LegislativeHistoryNodeProps> = ({
const { firebaseConfig } = useAppConfigContext();
const { language } = useLanguageConfigContext();
const localizedDateString = eventMinutesItem.event?.event_datetime.toLocaleDateString(language, {
timeZone: municipality.timeZone,
month: "long",
day: "numeric",
year: "numeric",
Expand Down
5 changes: 3 additions & 2 deletions src/components/Filters/SelectDateRange/getDateText.test.ts
@@ -1,7 +1,8 @@
import getDateText from "./getDateText";
import getDateTextFunctionCreator from "./getDateText";

describe("getDateText", () => {
const defaultText = "Date";
const getDateText = getDateTextFunctionCreator("en-US", "America/Los_Angeles");

test("Returns defaultText", () => {
const textRep = getDateText({ start: "", end: "" }, defaultText);
Expand All @@ -20,7 +21,7 @@ describe("getDateText", () => {

test("Returns start and end date with same year and month", () => {
const textRep = getDateText({ start: "2020/01/01", end: "2020/01/02" }, defaultText);
expect(textRep).toEqual("Jan 1 - 2, 2020");
expect(textRep).toEqual("Jan 1 - Jan 2, 2020");
});

test("Returns start and end date with same year only", () => {
Expand Down
63 changes: 32 additions & 31 deletions src/components/Filters/SelectDateRange/getDateText.ts
@@ -1,37 +1,38 @@
import moment from "moment";

import { FilterState } from "../reducer";

/**
* Generate the text representation of a date range.
* @param {Object} dateRange The start and end dates object.
* @param {string} dateRange.start
* @param {string} dateRange.end
* @param {string} defaultText The default text representation, when no dates have been entered.
* @return {string} The text representation.
*/
const getDateText = (dateRange: FilterState<string>, defaultText: string): string => {
const start = moment.utc(dateRange.start, "YYYY-MM-DD");
const end = moment.utc(dateRange.end, "YYYY-MM-DD");
const startString = start.format("MMM D, YYYY");
const endString = end.format("MMM D, YYYY");
let textRep;
if (dateRange.start && dateRange.end) {
if (start.year() === end.year() && start.month() === end.month()) {
textRep = `${startString.split(",")[0]} - ${end.date()}, ${end.year()}`;
} else if (start.year() === end.year()) {
textRep = `${startString.split(",")[0]} - ${endString.split(",")[0]}, ${end.year()}`;
} else {
import getTimeZoneDate from "../../../utils/getTimeZoneName";

const getDateText =
(language: string, timeZone: string) =>
(dateRange: FilterState<string>, defaultText: string): string => {
const timeZoneStartDate = getTimeZoneDate(new Date(dateRange.start), timeZone);
const timeZoneEndDate = getTimeZoneDate(new Date(dateRange.end), timeZone);
const startString = timeZoneStartDate?.toLocaleDateString(language, {
timeZone,
month: "short",
day: "numeric",
year:
timeZoneStartDate.getUTCFullYear() === timeZoneEndDate?.getUTCFullYear()
? undefined
: "numeric",
});
const endString = timeZoneEndDate?.toLocaleDateString(language, {
timeZone,
month: "short",
day: "numeric",
year: "numeric",
});
let textRep;
if (timeZoneStartDate && timeZoneEndDate) {
textRep = `${startString} - ${endString}`;
} else if (timeZoneStartDate) {
textRep = `${startString} -`;
} else if (timeZoneEndDate) {
textRep = `- ${endString}`;
} else {
textRep = defaultText;
}
} else if (dateRange.start) {
textRep = `${startString} -`;
} else if (dateRange.end) {
textRep = `- ${endString}`;
} else {
textRep = defaultText;
}
return textRep;
};
return textRep;
};

export default getDateText;
4 changes: 3 additions & 1 deletion src/components/Tables/VotingTableRow/VotingTableRow.tsx
Expand Up @@ -6,7 +6,7 @@ import { useMediaQuery } from "react-responsive";
import { screenWidths } from "../../../styles/mediaBreakpoints";
import { ReactiveTableRow } from "../ReactiveTableRow";
import { Link } from "react-router-dom";
import { useLanguageConfigContext } from "../../../app";
import { useAppConfigContext, useLanguageConfigContext } from "../../../app";

export type VotingTableRowProps = {
/** the name of the matter that was voted on */
Expand Down Expand Up @@ -46,11 +46,13 @@ const VotingTableRow = ({
columnNames,
columnDistribution,
}: VotingTableRowProps) => {
const { municipality } = useAppConfigContext();
const { language } = useLanguageConfigContext();

const legislationTagsString =
legislationTags && legislationTags.length > 0 ? legislationTags.join(TAG_CONNECTOR) : "";
const dateText = meetingDate?.toLocaleDateString(language, {
timeZone: municipality.timeZone,
month: "long",
day: "numeric",
year: "numeric",
Expand Down
18 changes: 11 additions & 7 deletions src/containers/EventContainer/EventContainer.tsx
@@ -1,7 +1,7 @@
import React, { createRef, FC, useState, useRef, useEffect, useMemo, useCallback } from "react";
import styled from "@emotion/styled";

import { useLanguageConfigContext } from "../../app";
import { useAppConfigContext, useLanguageConfigContext } from "../../app";
import useDocumentTitle from "../../hooks/useDocumentTitle";

import { EventVideoRef } from "../../components/Details/EventVideo/EventVideo";
Expand All @@ -26,7 +26,7 @@ const Container = styled.div({
});

const EventName = styled.div({
"& > h2": {
"& > h1": {
// Remove mozilla protocol margin on h2
margin: 0,
},
Expand Down Expand Up @@ -71,15 +71,14 @@ const EventContainer: FC<EventContainerProps> = ({
initialSeconds,
searchQuery,
}: EventContainerProps) => {
const { municipality } = useAppConfigContext();
const { language } = useLanguageConfigContext();

useDocumentTitle(
`${event.body?.name}` +
`${event.body?.name && event.event_datetime && " -- "}` +
`${event.event_datetime?.toLocaleDateString(language, {
month: "numeric",
day: "numeric",
year: "numeric",
timeZone: municipality.timeZone,
})}`
);

Expand Down Expand Up @@ -135,12 +134,17 @@ const EventContainer: FC<EventContainerProps> = ({
return (
<Container>
<EventName>
<h2 className="mzp-u-title-xs">{event.body?.name}</h2>
<h1 className="mzp-u-title-sm">{event.body?.name}</h1>
<p className="mzp-c-card-desc">
{event.event_datetime?.toLocaleDateString(language, {
{event.event_datetime?.toLocaleString(language, {
timeZone: municipality.timeZone,
timeZoneName: "short",
month: "long",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
})}
</p>
</EventName>
Expand Down
20 changes: 14 additions & 6 deletions src/containers/EventsContainer/EventsContainer.tsx
Expand Up @@ -3,7 +3,7 @@ import { useMediaQuery } from "react-responsive";
import { useHistory } from "react-router-dom";
import { Loader } from "semantic-ui-react";

import { useAppConfigContext } from "../../app";
import { useAppConfigContext, useLanguageConfigContext } from "../../app";
import useFetchModels, {
FetchModelsActionType,
FetchModelsState,
Expand Down Expand Up @@ -33,6 +33,7 @@ import { SEARCH_TYPE } from "../../pages/SearchPage/types";
import { strings } from "../../assets/LocalizedStrings";
import { FETCH_CARDS_BATCH_SIZE } from "../../constants/ProjectConstants";
import { screenWidths } from "../../styles/mediaBreakpoints";
import getTimeZoneDate from "../../utils/getTimeZoneName";

interface EventsContainerProps extends EventsData {
initialSelectedBodies: Record<string, boolean>;
Expand All @@ -42,13 +43,14 @@ const EventsContainer: FC<EventsContainerProps> = ({
bodies,
initialSelectedBodies,
}: EventsContainerProps) => {
const { firebaseConfig } = useAppConfigContext();
const { firebaseConfig, municipality } = useAppConfigContext();
const { language } = useLanguageConfigContext();

const dateRangeFilter = useFilter<string>({
name: strings.date,
initialState: { start: "", end: "" },
defaultDataValue: "",
textRepFunction: getDateText,
textRepFunction: getDateText(language, municipality.timeZone),
});
const committeeFilter = useFilter<boolean>({
name: strings.committee,
Expand Down Expand Up @@ -82,8 +84,8 @@ const EventsContainer: FC<EventsContainerProps> = ({
state.batchSize,
getSelectedOptions(committeeFilter.state),
{
start: dateRangeFilter.state.start ? new Date(dateRangeFilter.state.start) : undefined,
end: dateRangeFilter.state.end ? new Date(dateRangeFilter.state.end) : undefined,
start: getTimeZoneDate(new Date(dateRangeFilter.state.start), municipality.timeZone),
end: getTimeZoneDate(new Date(dateRangeFilter.state.end), municipality.timeZone),
},
{
by: sortFilter.state.by,
Expand All @@ -98,7 +100,13 @@ const EventsContainer: FC<EventsContainerProps> = ({
);
return Promise.resolve(renderableEvents);
},
[firebaseConfig, committeeFilter.state, dateRangeFilter.state, sortFilter.state]
[
firebaseConfig,
committeeFilter.state,
dateRangeFilter.state,
sortFilter.state,
municipality.timeZone,
]
);

const isDesktop = useMediaQuery({ query: `(min-width: ${screenWidths.desktop})` });
Expand Down
13 changes: 10 additions & 3 deletions src/containers/PersonContainer/PersonRoles.tsx
Expand Up @@ -4,7 +4,7 @@ import { Link } from "react-router-dom";
import Role from "../../models/Role";
import { getUniqueTermRoles, partitionNonTermRoles } from "../../models/util/RoleUtilities";

import { useLanguageConfigContext } from "../../app";
import { useAppConfigContext, useLanguageConfigContext } from "../../app";

import Details from "../../components/Shared/Details";
import H2 from "../../components/Shared/H2";
Expand Down Expand Up @@ -62,6 +62,7 @@ interface PersonRolesProps {
}

const PersonRoles: FC<PersonRolesProps> = ({ councilMemberRoles, allRoles }: PersonRolesProps) => {
const { municipality } = useAppConfigContext();
const { language } = useLanguageConfigContext();

const [termRoles, partitionedNonTermRoles, nonTermRoles] = useMemo(() => {
Expand Down Expand Up @@ -106,8 +107,14 @@ const PersonRoles: FC<PersonRolesProps> = ({ councilMemberRoles, allRoles }: Per
<strong>{`${strings[role.title.toLowerCase().replace(" ", "_")]}: `}</strong>{" "}
{`${role.seat?.name} // ${
role.seat?.electoral_area
} (${role.start_datetime.toLocaleDateString(language)} - ${
role.end_datetime ? role.end_datetime.toLocaleDateString(language) : ""
} (${role.start_datetime.toLocaleDateString(language, {
timeZone: municipality.timeZone,
})} - ${
role.end_datetime
? role.end_datetime.toLocaleDateString(language, {
timeZone: municipality.timeZone,
})
: ""
})`}
</span>
}
Expand Down