Created
January 17, 2020 21:33
-
-
Save joshkadis/3aa9dae1c1bde762a8284eaf8ee7975a to your computer and use it in GitHub Desktop.
Congressional Record search interface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable react/prop-types */ | |
import { Component } from 'react'; | |
import Select from 'react-select'; | |
/** | |
* Render a search result in the center well | |
* | |
* @param {Function} props.addToCollHandler | |
* @param {Array} props.collected | |
* @param {Object} props.result | |
*/ | |
const SearchResult = ( | |
{ | |
addToCollHandler, | |
collected, | |
result: { | |
candidate, | |
date, | |
permalink, | |
title, | |
section, | |
issueNumber, | |
issueVolume, | |
congress, | |
session, | |
page, | |
texts, | |
}, | |
}, | |
) => (<div> | |
<h3> | |
<a href={permalink}>{title}</a> | |
</h3> | |
<h4>{candidate}</h4> | |
<p>{`${section} | ${date} | Issue ${issueNumber} Volume ${issueVolume} | ${congress}th Congress, Session ${session} | Page ${page}`}</p> | |
<div> | |
{texts.map(({ value, topics }, idx) => { | |
// Render as paragraphs that can be added individually | |
// to collection | |
const key = `${page}-${idx}`; | |
const addText = () => addToCollHandler(key); | |
const disabled = collected.indexOf(key) !== -1; | |
return (<div key={key}> | |
<p>{value}</p> | |
<p>Topics: {topics.join(', ')} |{' '} | |
<button | |
onClick={addText} | |
disabled={disabled} | |
> | |
Add {key} to collection | |
</button> | |
</p> | |
</div>); | |
})} | |
</div> | |
</div>); | |
class CRSearchInterface extends Component { | |
state = { | |
search: { | |
input: '', | |
isActive: false, | |
}, | |
filters: { | |
selectedCandidates: '', | |
}, | |
results: [{ | |
candidate: 'Klobuchar', | |
date: '2019-12-17', | |
permalink: 'https://www.congress.gov/congressional-record/2019/12/17/senate-section/article/s7107-1', | |
title: 'HONORING SERGEANT KORT M. PLANTENBERG, CHIEF WARRANT OFFICER 2 JAMES A. ROGERS, JR., AND CHIEF WARRANT OFFICER 2 CHARLES P. NORD', | |
section: 'senate', | |
issueVolume: 165, | |
issueNumber: 204, | |
congress: 116, | |
session: 1, | |
page: 's7107', | |
texts: [ | |
{ | |
value: `Ms. KLOBUCHAR. Madam President, today I rise with a heavy | |
heart to honor and pay tribute to three exemplary National Guard | |
Members from my home State of Minnesota. On Thursday, December 5, SGT | |
Kort M. Plantenberg, CW2 James A. Rogers, Jr., and CW2 Charles P. Nord | |
lost their lives when their Black Hawk UH-60 helicopter went down | |
southwest of St. Cloud, MN, during a routine maintenance flight.`, | |
topics: [ | |
'military', | |
'minnesota', | |
], | |
}, | |
{ | |
value: `They had just returned home from the Middle East in May after a 9- | |
month deployment conducting medical evacuations. Once back in | |
Minnesota, they had continued serving our Nation by ensuring that our | |
Forces would be prepared to respond the moment they were needed. After | |
this tragic loss, Governor Walz, a Minnesota National Guardsman for | |
nearly 25 years, remarked that "we will forever be in the debt of | |
these warriors." I couldn't agree more.`, | |
topics: [ | |
'military', | |
'minnesota', | |
], | |
}, | |
{ | |
value: `Today, I would like to honor these brave men for giving what | |
President Lincoln called, ""the last full measure of devotion." We are | |
forever grateful for heroes like Sergeant Plantenberg, Chief Warrant | |
Officer 2 Rogers, and Chief Warrant Officer 2 Nord.`, | |
topics: [ | |
'military', | |
'minnesota', | |
], | |
}, | |
], | |
}], | |
collected: [], | |
} | |
/** | |
* Get search results for current query | |
*/ | |
fetchResults = async () => { | |
console.log('GET request to search service'); | |
setTimeout(() => { | |
this.setState({ | |
search: { | |
...this.state.search, | |
isActive: false, | |
}, | |
}); | |
}, 1000); | |
} | |
/** | |
* Update state to current value of search input | |
* | |
* @param {String} evt.target.value | |
*/ | |
handleSearchInputChange = ({ target: { value } }) => { | |
this.setState({ | |
search: { | |
...this.state.search, | |
input: value, | |
}, | |
}); | |
}; | |
/** | |
* Initiate search query | |
* | |
* @param {Event} evt Click event from search input | |
*/ | |
handleSearchSubmit = async (evt) => { | |
evt.preventDefault(); | |
if (!this.state.search.input.length) { | |
return; | |
} | |
this.setState({ | |
search: { | |
...this.state.search, | |
isActive: true, | |
}, | |
}); | |
await this.fetchResults(); | |
}; | |
/** | |
* Handle change in candidates filtering | |
* Using legacy v1 react-select here, long story... | |
* | |
* @param {Array} selectedCandidates Current value of react-select | |
*/ | |
handleCandidateChange = (selectedCandidates) => { | |
this.setState({ | |
filters: { | |
...this.state.filters, | |
selectedCandidates, | |
}, | |
}); | |
} | |
/** | |
* Add text block to current collection | |
* | |
* @param {String} textId | |
*/ | |
addTextToCollection = (textId) => { | |
if (this.state.collected.indexOf(textId) === -1) { | |
this.setState({ | |
collected: [...this.state.collected, textId], | |
}); | |
} | |
}; | |
/** | |
* Remove text block from current collection | |
* | |
* @param {String} textId | |
*/ | |
removeFromCollection = (textId) => { | |
this.setState({ | |
collected: this.state.collected.filter((id) => id !== textId), | |
}); | |
} | |
/** | |
* Send current state to permalink service | |
* and update component state | |
*/ | |
getPermalink = async () => { | |
console.log('POST request to permalink service'); | |
// this.setState({ | |
// permalink: responseFromPermalinkService, | |
// }); | |
} | |
render() { | |
const { | |
search: { | |
input, | |
isActive, | |
}, | |
filters: { | |
selectedCandidates, | |
}, | |
results, | |
collected, | |
} = this.state; | |
return ( | |
<div> | |
<h1>Search the Congressional Record</h1> | |
{/* Search Input */} | |
<header> | |
<form> | |
<label> | |
Search topics and keywords: | |
<input | |
type="text" | |
value={input} | |
onChange={this.handleSearchInputChange} | |
/> | |
</label> | |
<input | |
type="submit" | |
value="Submit" | |
onClick={this.handleSearchSubmit} | |
/> | |
{isActive && ( | |
<span>Loading...</span> | |
)} | |
</form> | |
</header> | |
{/* Results Filters */} | |
<section> | |
<h2>Filter search results</h2> | |
<form> | |
<label> | |
Filter by candidate: | |
<Select | |
multi | |
simplevalue | |
value={selectedCandidates} | |
onChange={this.handleCandidateChange} | |
options={[ | |
{ value: '0', label: 'Biden' }, | |
{ value: '1', label: 'Klobuchar' }, | |
{ value: '2', label: 'Warren' }, | |
{ value: '3', label: 'Sanders' }, | |
]} | |
/> | |
</label> | |
</form> | |
<p>Filter by other stuff...</p> | |
</section> | |
{/* View Results */} | |
<section> | |
<h2>View Results</h2> | |
<div> | |
{!!results.length | |
&& results.map((result, idx) => ( | |
<SearchResult | |
key={`result-${idx}`} | |
result={result} | |
collected={collected} | |
addToCollHandler={this.addTextToCollection} | |
/> | |
))} | |
</div> | |
</section> | |
{/* Collection */} | |
<section> | |
<h2>Current collection</h2> | |
<div> | |
<ul> | |
{!!collected.length | |
&& collected.map((item, idx) => ( | |
<li key={`collected-${idx}`}> | |
{item} | |
<button | |
onClick={() => this.removeFromCollection(item)} | |
>X</button> | |
</li> | |
)) | |
} | |
</ul> | |
<button | |
onClick={this.getPermalink} | |
>Generate permalink</button> | |
</div> | |
</section> | |
</div> | |
); | |
} | |
} | |
export default CRSearchInterface; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment