Skip to content

Instantly share code, notes, and snippets.

@joshkadis
Created January 17, 2020 21:33
Show Gist options
  • Save joshkadis/3aa9dae1c1bde762a8284eaf8ee7975a to your computer and use it in GitHub Desktop.
Save joshkadis/3aa9dae1c1bde762a8284eaf8ee7975a to your computer and use it in GitHub Desktop.
Congressional Record search interface
/* 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