Skip to content

Commit

Permalink
Add OR option to filters #8
Browse files Browse the repository at this point in the history
  • Loading branch information
jprodrigues70 committed May 27, 2021
1 parent b6d2f76 commit bfef328
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 76 deletions.
35 changes: 22 additions & 13 deletions src/components/AnswerPane/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,27 @@ export default class AnswerPane extends AnswerPrinter {

return () => (
<ul>
{Object.keys(grouped).map((i) => (
<li key={key(`categorie-${i}`)}>
{onClick ? (
<button className="v--link" onClick={() => onClick(i)}>
{Object.keys(grouped).map((i) => {
const classes = ["c-answer"];
const isFilterd = set.find((k) => k.answer === i && k.isFiltered);

if (isFilterd) {
classes.push("c-answer__ghost");
}

return (
<li className={classes.join(" ")} key={key(`categorie-${i}`)}>
{onClick ? (
<button className="v--link" onClick={() => onClick(i)}>
<b>{i}</b>
</button>
) : (
<b>{i}</b>
</button>
) : (
<b>{i}</b>
)}
: {grouped[i]} respondents
</li>
))}
)}
: {grouped[i]} respondents
</li>
);
})}
</ul>
);
});
Expand Down Expand Up @@ -131,10 +140,10 @@ export default class AnswerPane extends AnswerPrinter {
<div className="c-answer-pane">
<div className="c-answer-pane__counters">
<div className="c-answer-pane__counters-item">
Valids: {this.valids.length}
Valids: {this.valids.filter((i) => !i.isFiltered).length}
</div>
<div className="c-answer-pane__counters-item">
Invalids: {this.invalids.length}
Invalids: {this.invalids.filter((i) => !i.isFiltered).length}
</div>
</div>
<div className="c-answer-pane__body">{this.state.component}</div>
Expand Down
6 changes: 6 additions & 0 deletions src/components/AnswerPane/style.sass
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@
list-style: none
margin: 0
padding: 0

.c-answer
&__ghost
opacity: 0.2
&:hover
opacity: 0.5
6 changes: 4 additions & 2 deletions src/components/LoadingQuestions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ export default class LoadingQuestions extends Component {
return (
<div className="c-loading-questions c-suitable">
<div className="c-suitable__header">
<div className="c-loading-questions__button"></div>
<div className="c-loading-questions__button"></div>
<div className="c-suitable__header-controls">
<div className="c-loading-questions__button"></div>
<div className="c-loading-questions__button"></div>
</div>
</div>
<div className="c-question-mapper__questions">
{items.map((i) => (
Expand Down
36 changes: 32 additions & 4 deletions src/components/Question/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AnswerPane from "../AnswerPane";
import CsvExtractor from "../../plugins/CsvExtractor";
import { Context } from "../../store";
import "./style.sass";
import AnswerTreatment from "../../plugins/AnswerTreatment";
export default class Question extends Component {
static contextType = Context;

Expand Down Expand Up @@ -48,20 +49,47 @@ export default class Question extends Component {

render() {
let answers = this.rows;
let isInFilter = false;

this.props.filters.forEach((f) => {
answers = answers.filter((i) =>
f.filter(i, f.params.question, f.params.answer)
);
if (f.params.question !== this.props.position) {
answers = answers.filter((i) =>
f.filter(i, f.params.question, f.params.answers)
);
}
});

let answers_valids = [];
const filter_of_question = this.props.filters.find(
(i) => i.params.question === this.props.position
);

if (filter_of_question) {
answers_valids = filter_of_question.params.answers;
}

const value = (item) => {
if (AnswerTreatment[this.state.classification.key]) {
return AnswerTreatment[this.state.classification.key](item);
}
return item;
};

answers = answers.map((row, line) => ({
line,
answer: row[this.props.position],
isFiltered:
answers_valids.length &&
!answers_valids.includes(value(row[this.props.position])),
}));

let classes = ["c-question"];
if (isInFilter) {
classes.push("c-question--in-filter");
}

return (
<div className="c-question">
<div className={classes.join(" ")}>
<div className="c-question__header">
<div>
<h2 className="c-question__title">
Expand Down
3 changes: 2 additions & 1 deletion src/components/Question/style.sass
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
border-radius: 4px
max-width: 100%
overflow: hidden

&--in-filter
background-color: #F3F3F4
&__header, &__body
padding: 8px 16px
grid-gap: 16px
Expand Down
148 changes: 113 additions & 35 deletions src/components/QuestionMapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,69 +29,147 @@ export default class QuestionMapper extends Component {
};
}

removeFilter = (f) => {
const filters = this.state.filters.filter(
(i) =>
!(
i.params.question === f.params.question &&
i.params.answer === f.params.answer
)
removeFilter = (f, i) => {
let filters = [...this.state.filters];
const filterIndex = filters.findIndex(
(i) => i.params.question === f.params.question
);

if (filterIndex >= 0) {
filters[filterIndex].params.answers = filters[
filterIndex
].params.answers.filter((j) => j !== i);
if (!filters[filterIndex].params.answers.length) {
filters = filters.filter(
(i) => !(i.params.question === f.params.question)
);
}
}

this.setState({
filters,
phrase: this.getPhrase(filters),
});
};

getPhrase = (filters) => {
return filters.reduce((acc, f) => {
if (f.params.question === null) {
return acc;
}
return [
...acc,
<span
onClick={() => this.removeFilter(f)}
className="c-question-mapper__filter"
key={key(`${f.params.question}-${f.params.answer}`)}
>
{`Q.${f.params.question + 1} answer == "${f.params.answer}"`}
</span>,
];
}, []);
};

/**
* Here filters are mounted.
* When the question is stil in the filters list, we put the selected answer as an OR option grouped in the same question.
* When the question is new, we put the selected answer as an AND operation, and it adds a line on filters list.
* We don't have OR operation between 2 different questions
*
* @param {int} question
* @param {string} answer
* @param {object} classification
*/
onAnswerClick = (question, answer, classification) => {
const filters = [
...this.state.filters,
{
filter: (i, question, answer) => {
const questionIsInFilter = this.state.filters.findIndex(
(i) => i.params.question === question
);
let filters = [...this.state.filters];
if (questionIsInFilter < 0) {
// AND operation
filters.push({
filter: (i, question, answers) => {
if (AnswerTreatment[classification.key]) {
return AnswerTreatment[classification.key](i[question]) === answer;
return answers.includes(
AnswerTreatment[classification.key](i[question])
);
}
return i[question] === answer;
return answers.includes(i[question]);
},
params: {
question,
answer,
answers: [answer],
},
},
];
});
} else {
// OR operation
if (filters[questionIsInFilter].params.answers.includes(answer)) {
filters[questionIsInFilter].params.answers = filters[
questionIsInFilter
].params.answers.filter((i) => i !== answer);
} else {
filters[questionIsInFilter].params.answers.push(answer);
}

if (!filters[questionIsInFilter].params.answers.length) {
filters = filters.filter(
(i) =>
!(i.params.question === filters[questionIsInFilter].params.question)
);
}
}

this.setState({
filters,
phrase: this.getPhrase(filters),
});
};

/**
* Here we print the query string
*
* @param {array} filters
* @returns
*/
getPhrase = (filters) => {
return filters.reduce((acc, f) => {
if (f.params.question === null) {
return acc;
}

return [
...acc,
<>
{f.params.answers.length > 1 && (
<span key={key("leftb")} className="c-question-mapper__filter">
(
</span>
)}
{f.params.answers
.map((i) => (
<span
onClick={() => this.removeFilter(f, i)}
className="c-question-mapper__filter"
key={key(`${f.params.question}-${i.replace(/\s/g, "-")}`)}
>
{`Q.${f.params.question + 1} = "${i}"`}
</span>
))
.reduce((prev, curr) => [
prev,
<span key={key("or")} className="c-question-mapper__operator">
OR
</span>,
curr,
])}
{f.params.answers.length > 1 && (
<span key={key("rghtb")} className="c-question-mapper__filter">
)
</span>
)}
</>,
];
}, []);
};

render() {
const classifier = new QuestionClassifier();

return this.titles.length ? (
<div className="c-question-mapper">
{this.state.phrase.length ? (
<p className="c-question-mapper__query">
{this.state.phrase.map((i) => i)}
{this.state.phrase
.map((i) => i)
.reduce((prev, curr) => [
prev,
<span key={key("and")} className="c-question-mapper__operator">
AND
</span>,
curr,
])}
</p>
) : (
""
Expand Down
9 changes: 8 additions & 1 deletion src/components/QuestionMapper/style.sass
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
max-height: 80px
overflow: auto
display: flex
gap: 4px
gap: 2px
flex-wrap: wrap
max-width: 100%
&__filter
Expand All @@ -30,3 +30,10 @@
cursor: pointer
&:hover
background-color: #27447d
&__operator
border: 1px solid #252424
padding: 8px
background-color: #252424
border-radius: 8px
cursor: pointer
color: #067bc2
5 changes: 5 additions & 0 deletions src/components/SentimentItem/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from "react";
import Btn from "../Btn";
// import Field from "../Field";
import "./style.sass";

export default class SentimentItem extends Component {
Expand Down Expand Up @@ -38,6 +39,10 @@ export default class SentimentItem extends Component {
</ul>
)}
<div className="c-sentiment-item__control">
{/* <form>
<Field placeholder="Add a tag" />
<Btn>Ok</Btn>
</form> */}
{this.props.categories.map((k) => (
<Btn small color={k} key={k} onClick={() => this.change(k)}>
Change to {k}
Expand Down
Loading

0 comments on commit bfef328

Please sign in to comment.