mirror of
https://github.com/status-im/ETHReport.git
synced 2025-02-10 13:26:44 +00:00
Merge pull request #14 from Nona-Creative/feat/data-restructure
Data Restructure & Search Functionality
This commit is contained in:
commit
7fabd4ef5b
@ -7,7 +7,7 @@ import TopicsList from '../topicsList';
|
||||
import ProjectsList from '../projectsList';
|
||||
import SearchBar from '../searchBar';
|
||||
import SearchResults from '../searchResults';
|
||||
import InterviewsData from '../../data/archives/interviews';
|
||||
import { InterviewsData, Questions } from '../../data/archives/interviews';
|
||||
import './style.scss';
|
||||
|
||||
class BrowseArchives extends React.Component {
|
||||
@ -16,11 +16,13 @@ class BrowseArchives extends React.Component {
|
||||
|
||||
this.state = {
|
||||
term: '',
|
||||
searchResults: [],
|
||||
debounceTerm: '',
|
||||
searchResults: [null],
|
||||
isSingleInterviewModalOpen: false,
|
||||
isInterviewsListModalOpen: false,
|
||||
activeSingleInterviewId: 1,
|
||||
isSearchActive: false,
|
||||
interviewData: this.transformInterviews(InterviewsData),
|
||||
};
|
||||
|
||||
this.onSearchInputChange = this.onSearchInputChange.bind(this);
|
||||
@ -28,15 +30,16 @@ class BrowseArchives extends React.Component {
|
||||
this.toggleInterviewsListModal = this.toggleInterviewsListModal.bind(this);
|
||||
this.setSearchTerm = this.setSearchTerm.bind(this);
|
||||
this.clearSearchInput = this.clearSearchInput.bind(this);
|
||||
this.transformInterviews = this.transformInterviews.bind(this);
|
||||
this.termIsInInterview = this.termIsInInterview.bind(this);
|
||||
this.getSearchResultsDebounce = this.getSearchResultsDebounce.bind(this);
|
||||
}
|
||||
|
||||
onSearchInputChange = (event) => {
|
||||
const { term } = this.state;
|
||||
const getSearchResults = _.debounce(() => { this.getSearchResults(term); }, 500);
|
||||
|
||||
this.setState({
|
||||
term: event.target.value,
|
||||
isSearchActive: true,
|
||||
searchResults: [null],
|
||||
});
|
||||
|
||||
if (event.target.value.length === 0) {
|
||||
@ -44,37 +47,50 @@ class BrowseArchives extends React.Component {
|
||||
}
|
||||
|
||||
// Throttle search result frequency with debounce while typing
|
||||
getSearchResults(term);
|
||||
this.getSearchResultsDebounce();
|
||||
}
|
||||
|
||||
getSearchResults = (term) => {
|
||||
// Edit function to do actual search based on term and update searchResults with dynamic data
|
||||
// eslint-disable-next-line
|
||||
console.log(`Get search results array based on searching for: ${term}`);
|
||||
const { interviewData } = this.state;
|
||||
|
||||
const searchResults = interviewData.reduce((filtered, interview) => {
|
||||
const findTerm = this.termIsInInterview(term, interview);
|
||||
const matchedIndex = findTerm.foundIndex;
|
||||
|
||||
if (findTerm.found) {
|
||||
filtered.push({ ...interview, matchedIndex });
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, []);
|
||||
|
||||
// Using static full interviews data array for now
|
||||
this.setState({
|
||||
searchResults: InterviewsData,
|
||||
searchResults,
|
||||
debounceTerm: term,
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedInterview = () => {
|
||||
const { activeSingleInterviewId } = this.state;
|
||||
const selectedInterview = InterviewsData.find(item => item.id === activeSingleInterviewId);
|
||||
const { activeSingleInterviewId, interviewData } = this.state;
|
||||
const selectedInterview = interviewData
|
||||
.find(item => item.id === activeSingleInterviewId);
|
||||
return selectedInterview;
|
||||
}
|
||||
|
||||
setSearchTerm = (event) => {
|
||||
const { term } = this.state;
|
||||
|
||||
this.setState({
|
||||
term: event.target.innerText,
|
||||
isSearchActive: true,
|
||||
});
|
||||
|
||||
this.getSearchResults(term);
|
||||
this.getSearchResults(event.target.innerText);
|
||||
}
|
||||
|
||||
getSearchResultsDebounce = _.debounce(() => {
|
||||
const { term } = this.state;
|
||||
this.getSearchResults(term);
|
||||
}, 700);
|
||||
|
||||
clearSearchInput = () => {
|
||||
this.setState({
|
||||
isSearchActive: false,
|
||||
@ -82,6 +98,37 @@ class BrowseArchives extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
transformInterviews = (interviews) => {
|
||||
// eslint-disable-next-line
|
||||
const length = Object.keys(interviews).length;
|
||||
const betterInterviews = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const interview = interviews[i];
|
||||
const qKeys = Object.keys(interview);
|
||||
const interviewFormatted = [];
|
||||
|
||||
qKeys.forEach((key, index) => {
|
||||
if (key !== 'Name' && interview[key] !== null) {
|
||||
interviewFormatted.push({
|
||||
question: index,
|
||||
answer: interview[key],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
betterInterviews.push({
|
||||
id: i + 1,
|
||||
tags: 'tag1, tag2, tag3',
|
||||
name: interview.Name,
|
||||
matchedIndex: -1,
|
||||
interview: interviewFormatted,
|
||||
});
|
||||
}
|
||||
|
||||
return betterInterviews;
|
||||
}
|
||||
|
||||
toggleInterviewsListModal = () => {
|
||||
const { isInterviewsListModalOpen } = this.state;
|
||||
|
||||
@ -107,6 +154,57 @@ class BrowseArchives extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
termIsInInterview = (term, interview) => {
|
||||
const lcTerm = term.toLowerCase();
|
||||
const matchesName = interview.name.toLowerCase().includes(lcTerm);
|
||||
const { interviewData } = this.state;
|
||||
let foundIndex = 0;
|
||||
|
||||
if (matchesName) {
|
||||
return {
|
||||
found: true,
|
||||
foundIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const matchesTag = interview.tags.toLowerCase().indexOf(lcTerm) !== -1;
|
||||
|
||||
if (matchesTag) {
|
||||
return {
|
||||
found: true,
|
||||
foundIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const matchingQuestions = interview.interview
|
||||
.filter((question, questionIndex) => {
|
||||
if (question.answer === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const index = question.answer.toLowerCase().indexOf(lcTerm);
|
||||
|
||||
if (index !== -1 && interview.activeIndex !== -1) {
|
||||
foundIndex = questionIndex;
|
||||
}
|
||||
|
||||
return index !== -1;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
interviewData,
|
||||
});
|
||||
|
||||
if (matchingQuestions.length > 0) {
|
||||
return {
|
||||
found: true,
|
||||
foundIndex,
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isSingleInterviewModalOpen,
|
||||
@ -115,6 +213,8 @@ class BrowseArchives extends React.Component {
|
||||
activeSingleInterviewId,
|
||||
term,
|
||||
searchResults,
|
||||
interviewData,
|
||||
debounceTerm,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
@ -129,7 +229,7 @@ class BrowseArchives extends React.Component {
|
||||
<div className="browse-content-left">
|
||||
{isSearchActive &&
|
||||
(<RelatedInterviewsList
|
||||
data={InterviewsData}
|
||||
data={searchResults}
|
||||
toggleSingleInterview={this.toggleSingleInterview}
|
||||
/>)
|
||||
}
|
||||
@ -138,12 +238,14 @@ class BrowseArchives extends React.Component {
|
||||
{isSearchActive ? (
|
||||
<SearchResults
|
||||
data={searchResults}
|
||||
questions={Questions}
|
||||
term={debounceTerm}
|
||||
toggleSingleInterview={this.toggleSingleInterview}
|
||||
/>) :
|
||||
(
|
||||
<React.Fragment>
|
||||
<InterviewsList
|
||||
data={InterviewsData}
|
||||
data={interviewData}
|
||||
isInterviewsListModalOpen={isInterviewsListModalOpen}
|
||||
toggleSingleInterview={this.toggleSingleInterview}
|
||||
toggleInterviewsListModal={this.toggleInterviewsListModal}
|
||||
@ -159,6 +261,7 @@ class BrowseArchives extends React.Component {
|
||||
activeSingleInterviewId={activeSingleInterviewId}
|
||||
selectedInterview={this.getSelectedInterview()}
|
||||
toggleSingleInterview={this.toggleSingleInterview}
|
||||
questions={Questions}
|
||||
/>)
|
||||
}
|
||||
</div>
|
||||
|
@ -2,22 +2,30 @@ import React from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import './style.scss';
|
||||
|
||||
const RelatedInterviewsList = props => (
|
||||
<div className="related-interviews-list">
|
||||
<h4>Related <br />Interviews</h4>
|
||||
<ul>
|
||||
{ props.data.map(interview => (
|
||||
<li
|
||||
id={interview.id}
|
||||
key={interview.id}
|
||||
>
|
||||
<button onClick={props.toggleSingleInterview}>{interview.name}</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
const RelatedInterviewsList = (props) => {
|
||||
if (!props.data || props.data[0] === null) {
|
||||
return <div>Loading...</div>;
|
||||
} else if (props.data.length < 1) {
|
||||
return <div>No results found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="related-interviews-list">
|
||||
<h4>Related <br />Interviews</h4>
|
||||
<ul>
|
||||
{ props.data.map(interview => (
|
||||
<li
|
||||
id={interview.id}
|
||||
key={interview.id}
|
||||
>
|
||||
<button onClick={props.toggleSingleInterview}>{ interview.name }</button>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RelatedInterviewsList.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import Parser from 'html-react-parser';
|
||||
import Modal from '../../modal';
|
||||
import './style.scss';
|
||||
|
||||
@ -11,7 +10,19 @@ const SingleInterview = props => (
|
||||
>
|
||||
<div className="single-interview">
|
||||
<span className="number">{ props.activeSingleInterviewId }</span>
|
||||
<div>{ Parser(props.selectedInterview.content) }</div>
|
||||
<span className="name"> { props.selectedInterview.name } </span>
|
||||
{ props.selectedInterview.interview.filter(interview => interview.answer !== null)
|
||||
.map((interview, index) => {
|
||||
const question = props.questions.find(q => q.id === interview.question);
|
||||
|
||||
return (
|
||||
<div key={`question-${question.id}`}>
|
||||
<p className="question">{index + 1}) { question.text }</p>
|
||||
<p className="answer">{ interview.answer }</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
@ -19,9 +30,14 @@ const SingleInterview = props => (
|
||||
SingleInterview.propTypes = {
|
||||
activeSingleInterviewId: PropTypes.number.isRequired,
|
||||
selectedInterview: PropTypes.shape({
|
||||
content: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
interview: PropTypes.array.isRequired,
|
||||
}).isRequired,
|
||||
toggleSingleInterview: PropTypes.func.isRequired,
|
||||
questions: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
export default SingleInterview;
|
||||
|
@ -6,6 +6,21 @@
|
||||
font-size: calculateRem(50);
|
||||
}
|
||||
|
||||
.name {
|
||||
color: white;
|
||||
font-size: calculateRem(36);
|
||||
}
|
||||
|
||||
.question {
|
||||
font-size: calculateRem(28);
|
||||
color: white;
|
||||
margin: calculateRem(16) 0;
|
||||
}
|
||||
|
||||
.answer {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* This is the css from `Fabio Berger + Remco Bloemen` interview
|
||||
* Note: it does not correctly format the other 2 interviews
|
||||
*/
|
||||
|
@ -1,15 +1,53 @@
|
||||
import React from 'react';
|
||||
import Parser from 'html-react-parser';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import './style.scss';
|
||||
|
||||
const SearchResults = (props) => {
|
||||
if (!props.data) {
|
||||
if (!props.data || props.data[0] === null) {
|
||||
return <div>Loading...</div>;
|
||||
} else if (props.data.length < 1) {
|
||||
return <div>No results found</div>;
|
||||
}
|
||||
|
||||
// sort array alphabetically
|
||||
const sortedInterviews = props.data.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const trimText = (text, length) => {
|
||||
if (text === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return text.length <= length ? text : `${text.substr(0, length)}...`;
|
||||
};
|
||||
|
||||
const highlightTerm = (text) => {
|
||||
const cleanTerm = props.term.replace(/[^a-zA-Z 0-9]+/g, '').toLowerCase();
|
||||
const regex = new RegExp(cleanTerm, 'ig');
|
||||
return text.replace(regex, `<span>${cleanTerm}</span>`);
|
||||
};
|
||||
|
||||
const processText = (text, length = 1500) => highlightTerm(trimText(text, length));
|
||||
|
||||
const findFirstQuestion = (interview) => {
|
||||
let { answer } = interview.interview[interview.matchedIndex];
|
||||
let id = interview.interview[interview.matchedIndex].question;
|
||||
|
||||
if (answer === null) {
|
||||
const firstNonNullAnswer = interview.interview.find(question => question.answer !== null);
|
||||
id = firstNonNullAnswer.question;
|
||||
// eslint-disable-next-line
|
||||
answer = firstNonNullAnswer.answer;
|
||||
}
|
||||
|
||||
const { text } = props.questions.find(question => question.id === id);
|
||||
|
||||
return {
|
||||
question: text,
|
||||
answer: processText(answer),
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="search-results">
|
||||
<ul>
|
||||
@ -21,11 +59,10 @@ const SearchResults = (props) => {
|
||||
onClick={props.toggleSingleInterview}
|
||||
>
|
||||
<h3>{ interview.name }</h3>
|
||||
<h5>1) Question goes here</h5>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce hendrerit dolor quis
|
||||
ante mollis fringilla. <span>Lorem</span> ipsum dolor sit amet, consectetur adipiscing
|
||||
elit.
|
||||
<h5>{interview.matchedIndex + 1})
|
||||
{ findFirstQuestion(interview).question }
|
||||
</h5>
|
||||
<p>{ Parser(findFirstQuestion(interview).answer) }
|
||||
</p>
|
||||
</li>
|
||||
))
|
||||
@ -38,6 +75,8 @@ const SearchResults = (props) => {
|
||||
SearchResults.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
toggleSingleInterview: PropTypes.func.isRequired,
|
||||
questions: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
term: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default SearchResults;
|
||||
|
@ -28,9 +28,9 @@
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
left: -15%;
|
||||
left: -2%;
|
||||
height: 120%;
|
||||
width: 130%;
|
||||
width: 104%;
|
||||
background-color: $red;
|
||||
color: $navy;
|
||||
z-index: -1;
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user