Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions src/js/pages/ElectionFinder/ElectionFinderForElection.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ContentCopy, FileDownloadOutlined, InfoOutlined, Launch, Search, Close, ExpandMore, UnfoldMore, UnfoldLess } from '@mui/icons-material';
import { IconButton, InputAdornment, Tooltip } from '@mui/material';
import { IconButton, InputAdornment } from '@mui/material';
import PropTypes from 'prop-types';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
Expand All @@ -11,14 +11,13 @@ import SupportActions from '../../actions/SupportActions';
import { renderLog } from '../../common/utils/logging';
import { convertStateCodeToStateText } from '../../common/utils/addressFunctions';
import AppObservableStore from '../../common/stores/AppObservableStore';
import { ElectionStateLabel } from '../../components/Style/BallotTitleHeaderStyles';
import { PageContentContainer } from '../../components/Style/pageLayoutStyles';
import BallotStore from '../../stores/BallotStore';
import CandidateStore from '../../stores/CandidateStore';
import ElectionStore from '../../stores/ElectionStore';
import ElectionFinderHeader from './ElectionFinderHeader';
import {
ActionChip, ActionDivider,
ActionChip, ActionDivider, DarkTooltip,
CandidateActions as CandidateActionsRow, CandidateInfo, CandidateList, CandidateName,
CandidateParty, CandidateRow, DetailTitle, ElectionTitleRow,
ExpandCollapseButton, ExpandCollapseRow, ExpandMoreIcon,
Expand Down Expand Up @@ -201,14 +200,14 @@ function ElectionFinderForElection () {
{ label: `${stateName} ${isUpcoming ? 'Upcoming' : 'Past'} Elections (${isUpcoming ? upcomingCount : pastCount})`, href: `/election-finder/${selectedStateCode.toLowerCase()}` },
{ label: electionName },
]}
stateLabel={stateName}
/>
<ElectionStateLabel style={{ marginBottom: 12 }}>{stateName}</ElectionStateLabel>

<ElectionTitleRow>
<DetailTitle>{electionName}</DetailTitle>
<Tooltip title="Download election data">
<DarkTooltip title="Download election data">
<IconButton size="small"><FileDownloadOutlined fontSize="small" /></IconButton>
</Tooltip>
</DarkTooltip>
{electionSearchOpen ? (
<InlineSearchField
variant="outlined"
Expand Down Expand Up @@ -263,9 +262,9 @@ function ElectionFinderForElection () {
{totalResults !== null && (
<SearchResultCount>
{`${totalResults} results for \u201C${electionSearchText}\u201D`}
<Tooltip title="Download search results">
<DarkTooltip title="Download search results">
<IconButton size="small" style={{ marginLeft: 8 }}><FileDownloadOutlined fontSize="small" /></IconButton>
</Tooltip>
</DarkTooltip>
</SearchResultCount>
)}

Expand Down Expand Up @@ -323,27 +322,27 @@ function OfficeSectionItemInner ({ // eslint-disable-line react/no-multi-comp
</OfficeName>
</OfficeHeaderLeft>
<OfficeHeaderActions className="u-show-desktop-tablet" onClick={(e) => e.stopPropagation()}>
<Tooltip title="Copy office name">
<DarkTooltip title="Copy office name">
<ActionChip onClick={() => copyToClipboard(officeName)}>
<ContentCopy sx={{ fontSize: 14, mr: 0.5 }} />
Copy office name
</ActionChip>
</Tooltip>
<Tooltip title="Copy link">
</DarkTooltip>
<DarkTooltip title="Copy link">
<ActionChip onClick={() => copyToClipboard(`${window.location.origin}/office/${officeWeVoteId}`)}>
<ContentCopy sx={{ fontSize: 14, mr: 0.5 }} />
Copy link
</ActionChip>
</Tooltip>
</DarkTooltip>
<ActionDivider />
<Tooltip title="Info">
<DarkTooltip title="Info">
<IconButton size="small"><InfoOutlined fontSize="small" /></IconButton>
</Tooltip>
<Tooltip title="Open in new tab">
</DarkTooltip>
<DarkTooltip title="Open in new tab">
<IconButton size="small" onClick={() => window.open(`/office/${officeWeVoteId}`, '_blank')}>
<Launch fontSize="small" />
</IconButton>
</Tooltip>
</DarkTooltip>
</OfficeHeaderActions>
</OfficeHeader>
{isExpanded && (
Expand All @@ -361,24 +360,24 @@ function OfficeSectionItemInner ({ // eslint-disable-line react/no-multi-comp
<CandidateParty>{candidateParty}</CandidateParty>
</CandidateInfo>
<CandidateActionsRow className="u-show-desktop-tablet">
<Tooltip title="Copy candidate name">
<DarkTooltip title="Copy candidate name">
<ActionChip onClick={() => copyToClipboard(candidateName)}>
<ContentCopy sx={{ fontSize: 14, mr: 0.5 }} />
Copy candidate name
</ActionChip>
</Tooltip>
<Tooltip title="Copy link">
</DarkTooltip>
<DarkTooltip title="Copy link">
<ActionChip onClick={() => copyToClipboard(`${window.location.origin}${getCandidatePath(candidate)}`)}>
<ContentCopy sx={{ fontSize: 14, mr: 0.5 }} />
Copy link
</ActionChip>
</Tooltip>
</DarkTooltip>
<ActionDivider />
<Tooltip title="Open in new tab">
<DarkTooltip title="Open in new tab">
<IconButton size="small" onClick={() => window.open(getCandidatePath(candidate), '_blank')}>
<Launch fontSize="small" />
</IconButton>
</Tooltip>
</DarkTooltip>
</CandidateActionsRow>
</CandidateRow>
);
Expand Down
60 changes: 44 additions & 16 deletions src/js/pages/ElectionFinder/ElectionFinderForState.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { ExpandMore, FileDownloadOutlined, Launch, Search, Close } from '@mui/icons-material';
import { IconButton, InputAdornment, Tooltip } from '@mui/material';
import { ContentCopy, ExpandMore, FileDownloadOutlined, Launch, Search, Close } from '@mui/icons-material';
import { IconButton, InputAdornment } from '@mui/material';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { useParams } from 'react-router-dom';
import ElectionActions from '../../actions/ElectionActions';
import { renderLog } from '../../common/utils/logging';
import { stateCodeMap, convertStateCodeToStateText } from '../../common/utils/addressFunctions';
import isMobileScreenSize from '../../common/utils/isMobileScreenSize';
import { PageContentContainer } from '../../components/Style/pageLayoutStyles';
import historyPush from '../../common/utils/historyPush';
import ElectionStore from '../../stores/ElectionStore';
import ElectionFinderHeader from './ElectionFinderHeader';
import {
ElectionLink, ElectionList, ElectionRow, ElectionRowActions,
ActionChip, ActionDivider, DarkTooltip,
ElectionDatePill, ElectionLink, ElectionList, ElectionRow, ElectionRowActions,
FilterTab, FilterTabsRow, InlineSearchField, NoResults,
SearchIconButton, SectionTitle, SectionTitleRow, ShowMoreButton,
StateSelectWrapper, StateSelectNative, StateSelectLabel, StateSelectCaret,
Expand All @@ -21,6 +23,16 @@ const SORTED_STATES = Object.entries(stateCodeMap)
.filter(([code]) => code !== 'NA')
.sort((a, b) => a[1].localeCompare(b[1]));

function formatDateUS (dateString) {
if (!dateString) return '';
const [y, m, d] = dateString.split('-');
return `${m}-${d}-${y}`;
}

function sortByDateAsc (a, b) {
return (a.election_day_text || '').localeCompare(b.election_day_text || '');
}

function getBreadcrumbTabLabel (filterTab) {
if (filterTab === 'all') return 'All';
if (filterTab === 'past') return 'Past';
Expand Down Expand Up @@ -105,7 +117,7 @@ function ElectionFinderForState () {
);
}

const upcomingElections = stateElections.filter((el) => el.election_is_upcoming);
const upcomingElections = stateElections.filter((el) => el.election_is_upcoming).sort(sortByDateAsc);
const pastElections = stateElections.filter((el) => !el.election_is_upcoming);
const upcomingCount = upcomingElections.length;
const pastCount = pastElections.length;
Expand All @@ -123,9 +135,14 @@ function ElectionFinderForState () {
sectionTitle = `${stateName} \u2013 Past Elections`;
}

const sectionDownloadLabel = filterTab === 'past' ?
'Download data for all past elections' :
'Download data for all upcoming elections';
let sectionDownloadLabel;
if (filterTab === 'all') {
sectionDownloadLabel = `Download data for all ${stateName} elections`;
} else if (filterTab === 'past') {
sectionDownloadLabel = 'Download data for all past elections';
} else {
sectionDownloadLabel = 'Download data for all upcoming elections';
}

return (
<>
Expand Down Expand Up @@ -162,7 +179,7 @@ function ElectionFinderForState () {
<InlineSearchField
variant="outlined"
size="small"
placeholder="Search elections..."
placeholder={isMobileScreenSize() ? 'Search...' : 'Search elections...'}
inputRef={searchInputRef}
defaultValue=""
onChange={(e) => {
Expand Down Expand Up @@ -199,32 +216,43 @@ function ElectionFinderForState () {

<SectionTitleRow>
<SectionTitle>{sectionTitle}</SectionTitle>
<Tooltip title={sectionDownloadLabel}>
<DarkTooltip title={sectionDownloadLabel}>
<IconButton size="small"><FileDownloadOutlined fontSize="small" /></IconButton>
</Tooltip>
</DarkTooltip>
</SectionTitleRow>

<ElectionList>
{(searchText ? displayElections : displayElections.slice(0, visibleCount)).map((election) => {
const googleCivicElectionId = election.google_civic_election_id;
const electionLabel = `${election.election_name || ''} \u2013 ${election.election_day_text || ''}`;
return (
<ElectionRow
key={googleCivicElectionId}
onClick={() => onElectionSelect(googleCivicElectionId)}
>
<ElectionLink>
{electionLabel}
{election.election_day_text && (
<ElectionDatePill>{formatDateUS(election.election_day_text)}</ElectionDatePill>
)}
{election.election_name || ''}
</ElectionLink>
<ElectionRowActions className="u-show-desktop-tablet" onClick={(e) => e.stopPropagation()}>
<Tooltip title="Download election data">
<DarkTooltip title="Copy link">
<ActionChip
onClick={() => navigator.clipboard.writeText(`${window.location.origin}/election-finder/${selectedStateCode.toLowerCase()}/${googleCivicElectionId}`)}
>
<ContentCopy sx={{ fontSize: 14, mr: 0.5 }} />
Copy link
</ActionChip>
</DarkTooltip>
<ActionDivider />
<DarkTooltip title="Download election data">
<IconButton size="small"><FileDownloadOutlined fontSize="small" /></IconButton>
</Tooltip>
<Tooltip title="Open in new tab">
</DarkTooltip>
<DarkTooltip title="Open in new tab">
<IconButton size="small" onClick={() => window.open(`/election-finder/${selectedStateCode.toLowerCase()}/${googleCivicElectionId}`, '_blank')}>
<Launch fontSize="small" />
</IconButton>
</Tooltip>
</DarkTooltip>
</ElectionRowActions>
</ElectionRow>
);
Expand Down
12 changes: 6 additions & 6 deletions src/js/pages/ElectionFinder/ElectionFinderHeader.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ElectionNameH1 } from '../../components/Style/BallotTitleHeaderStyles';
import { Breadcrumb, BreadcrumbAnchor, TitleWrapper } from './electionFinderStyles';
import { ElectionNameH1, ElectionStateLabel } from '../../components/Style/BallotTitleHeaderStyles';
import { Breadcrumb, BreadcrumbAnchor } from './electionFinderStyles';

function ElectionFinderHeader ({ breadcrumbs, subtitle }) {
function ElectionFinderHeader ({ breadcrumbs, stateLabel, subtitle }) {
return (
<>
<TitleWrapper>
<ElectionNameH1 style={{ paddingBottom: 4 }}>Election Finder</ElectionNameH1>
</TitleWrapper>
{stateLabel && <ElectionStateLabel>{stateLabel}</ElectionStateLabel>}
<ElectionNameH1>Election Finder</ElectionNameH1>
{breadcrumbs && breadcrumbs.length > 0 && (
<Breadcrumb>
{breadcrumbs.map((crumb, idx) => {
Expand Down Expand Up @@ -38,6 +37,7 @@ ElectionFinderHeader.propTypes = {
label: PropTypes.string.isRequired,
href: PropTypes.string,
})),
stateLabel: PropTypes.string,
subtitle: PropTypes.string,
};

Expand Down
53 changes: 40 additions & 13 deletions src/js/pages/ElectionFinder/ElectionFinderHome.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import { ExpandMore, FileDownloadOutlined, Launch, Search, Close } from '@mui/icons-material';
import { IconButton, InputAdornment, Tooltip } from '@mui/material';
import { ContentCopy, ExpandMore, FileDownloadOutlined, Launch, Search, Close } from '@mui/icons-material';
import { IconButton, InputAdornment } from '@mui/material';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import ElectionActions from '../../actions/ElectionActions';
import { renderLog } from '../../common/utils/logging';
import { stateCodeMap } from '../../common/utils/addressFunctions';
import isMobileScreenSize from '../../common/utils/isMobileScreenSize';
import { PageContentContainer } from '../../components/Style/pageLayoutStyles';
import historyPush from '../../common/utils/historyPush';
import ElectionStore from '../../stores/ElectionStore';
import ElectionFinderHeader from './ElectionFinderHeader';
import {
ElectionLink, ElectionList, ElectionRow, ElectionRowActions,
ActionChip, ActionDivider, DarkTooltip,
ElectionDatePill, ElectionLink, ElectionList, ElectionRow, ElectionRowActions,
FilterTab, FilterTabsRow, InlineSearchField, NoResults,
SearchIconButton, SectionTitle, SectionTitleRow, ShowMoreButton,
StateSelectWrapper, StateSelectNative, StateSelectLabel, StateSelectCaret,
} from './electionFinderStyles';

function formatDateUS (dateString) {
if (!dateString) return '';
const [y, m, d] = dateString.split('-');
return `${m}-${d}-${y}`;
}

function sortByDateAsc (a, b) {
return (a.election_day_text || '').localeCompare(b.election_day_text || '');
}

const SORTED_STATES = Object.entries(stateCodeMap)
.filter(([code]) => code !== 'NA')
.sort((a, b) => a[1].localeCompare(b[1]));
Expand Down Expand Up @@ -87,7 +99,7 @@ function ElectionFinderHome () {
);
}

const upcomingElections = filteredByState.filter((el) => el.election_is_upcoming);
const upcomingElections = filteredByState.filter((el) => el.election_is_upcoming).sort(sortByDateAsc);
const pastElections = filteredByState.filter((el) => !el.election_is_upcoming);
const upcomingCount = upcomingElections.length;
const pastCount = pastElections.length;
Expand Down Expand Up @@ -137,7 +149,7 @@ function ElectionFinderHome () {
<InlineSearchField
variant="outlined"
size="small"
placeholder="Search elections..."
placeholder={isMobileScreenSize() ? 'Search...' : 'Search elections...'}
inputRef={searchInputRef}
defaultValue=""
onChange={(e) => {
Expand Down Expand Up @@ -174,28 +186,43 @@ function ElectionFinderHome () {

<SectionTitleRow>
<SectionTitle>{sectionTitle}</SectionTitle>
<Tooltip title={sectionDownloadLabel}>
<DarkTooltip title={sectionDownloadLabel}>
<IconButton size="small"><FileDownloadOutlined fontSize="small" /></IconButton>
</Tooltip>
</DarkTooltip>
</SectionTitleRow>

<ElectionList>
{(searchText ? displayElections : displayElections.slice(0, visibleCount)).map((election) => {
const googleCivicElectionId = election.google_civic_election_id;
const electionLabel = `${election.election_name || ''} \u2013 ${election.election_day_text || ''}`;
return (
<ElectionRow
key={googleCivicElectionId}
onClick={() => onElectionSelect(election)}
>
<ElectionLink>
{electionLabel}
{election.election_day_text && (
<ElectionDatePill>{formatDateUS(election.election_day_text)}</ElectionDatePill>
)}
{election.election_name || ''}
</ElectionLink>
<ElectionRowActions className="u-show-desktop-tablet" onClick={(e) => e.stopPropagation()}>
<Tooltip title="Download election data">
<DarkTooltip title="Copy link">
<ActionChip onClick={() => {
const sc = (election.state_code_list && election.state_code_list.length > 0) ?
election.state_code_list[0].toLowerCase() :
'na';
navigator.clipboard.writeText(`${window.location.origin}/election-finder/${sc}/${googleCivicElectionId}`);
}}
>
<ContentCopy sx={{ fontSize: 14, mr: 0.5 }} />
Copy link
</ActionChip>
</DarkTooltip>
<ActionDivider />
<DarkTooltip title="Download election data">
<IconButton size="small"><FileDownloadOutlined fontSize="small" /></IconButton>
</Tooltip>
<Tooltip title="Open in new tab">
</DarkTooltip>
<DarkTooltip title="Open in new tab">
<IconButton
size="small"
onClick={() => {
Expand All @@ -207,7 +234,7 @@ function ElectionFinderHome () {
>
<Launch fontSize="small" />
</IconButton>
</Tooltip>
</DarkTooltip>
</ElectionRowActions>
</ElectionRow>
);
Expand Down
Loading
Loading