Unverified Commit 38b79a42 authored by Ivan Gabriele's avatar Ivan Gabriele Committed by GitHub
Browse files

feat(admin): add publication toggle in answers edition (#604)

parent 2325d0b2
......@@ -4,7 +4,32 @@ We would love for you to contribute to the backoffice of [Code du travail
numérique][link-cdtn] and help make it even better than it is today! As a
contributor, here are the guidelines we would like you to follow:
- [Commit Message Guidelines](#commit-message-guidelines)
- [Contributing](#contributing)
- [Code Naming Guidelines](#code-naming-guidelines)
- [API-related Actions](#api-related-actions)
- [React Component Actions](#react-component-actions)
- [Commit Message Guidelines](#commit-message-guidelines)
- [Revert](#revert)
- [Type](#type)
- [Scope](#scope)
- [Subject](#subject)
## Code Naming Guidelines
### API-related Actions
This includes react components methods as well as redux actions, action types
and sagas:
- Any `GET` call-related method should start with the verb **load**.
- Any `POST` call-related method should start with the verb **create**.
- Any `PATCH` call-related method should start with the verb **update**.
- Any `DELETE` call-related method should start with the verb **delete** (or
**\_delete**).
### React Component Actions
- Any method generating components should start with the verb **render**.
## Commit Message Guidelines
......
-------------------------------------- UP --------------------------------------
DROP VIEW api.public_answers;
CREATE VIEW api.public_answers AS
SELECT
id,
parent_id,
value,
generic_reference,
is_published,
created_at,
updated_at,
question_id,
agreement_id
FROM api.answers answers
WHERE answers.state = 'validated'
AND answers.is_published = TRUE;
GRANT SELECT ON api.public_answers TO anonymous;
------------------------------------- DOWN -------------------------------------
DROP VIEW api.public_answers;
CREATE VIEW api.public_answers AS
SELECT
id,
parent_id,
value,
generic_reference,
is_published,
created_at,
updated_at,
question_id,
agreement_id
FROM api.answers answers
WHERE answers.state = 'validated';
GRANT SELECT ON api.public_answers TO anonymous;
const getMigrationQuery = require("../../../scripts/db/getMigrationQuery");
exports.up = async knex => {
await knex.raw(getMigrationQuery("20191002115740_update_public_answers_view").up());
};
exports.down = async knex => {
await knex.raw(getMigrationQuery("20191002115740_update_public_answers_view").down());
};
......@@ -11,6 +11,7 @@ import LawReferences from "../../../src/components/LawReferences";
import Reference from "../../../src/components/Reference";
import AdminMain from "../../../src/layouts/AdminMain";
import Button from "../../../src/elements/Button";
import _Checkbox from "../../../src/elements/Checkbox";
import Hr from "../../../src/elements/Hr";
import Icon from "../../../src/elements/Icon";
import Idcc from "../../../src/elements/Idcc";
......@@ -18,7 +19,6 @@ import Input from "../../../src/elements/Input";
import Radio from "../../../src/elements/Radio";
import _Select from "../../../src/elements/Select";
import Subtitle from "../../../src/elements/Subtitle";
import Tag from "../../../src/elements/Tag";
import Textarea from "../../../src/elements/Textarea";
import Title from "../../../src/elements/Title";
import capitalize from "../../../src/helpers/capitalize";
......@@ -183,6 +183,9 @@ const FormHiddenSubmit = styled.button`
visibility: hidden;
width: 1px;
`;
const Checkbox = styled(_Checkbox)`
padding-left: 0;
`;
const Comments = styled(Flex)`
flex-grow: 1;
......@@ -249,7 +252,7 @@ export class AdminAnwsersEditPage extends React.Component {
try {
this.fetchAnswer();
await this.fetchReferences();
await this.loadReferences();
this.props.dispatch(actions.comments.load(this.props.id));
this.setState({ isLoading: false });
......@@ -285,7 +288,7 @@ export class AdminAnwsersEditPage extends React.Component {
dispatch(actions.answers.loadOne(id));
}
async fetchReferences() {
async loadReferences() {
try {
const referencesSelect = `select=*`;
const referencesWhere = `answer_id=eq.${this.props.id}`;
......@@ -302,15 +305,6 @@ export class AdminAnwsersEditPage extends React.Component {
}
}
updateAnswerState() {
const { id } = this.props;
const newState = this.$newStateSelect.value;
this.props.dispatch(
actions.answers.setState([id], newState, () => window.location.reload())
);
}
async _updateAnswerValue({ source }) {
this.setState({ isUpdating: true });
......@@ -329,7 +323,30 @@ export class AdminAnwsersEditPage extends React.Component {
this.setState({ isUpdating: false });
}
async insertTag(tagId) {
updateAnswerState() {
const { id } = this.props;
const newState = this.$newStateSelect.value;
this.props.dispatch(
actions.answers.updateState([id], newState, () =>
window.location.reload()
)
);
}
updateGenericReference(generic_reference) {
const { dispatch, id } = this.props;
dispatch(
actions.answers.updateGenericReference(
[id],
generic_reference,
this.fetchAnswer.bind(this)
)
);
}
async createTag(tagId) {
try {
const answersUri = `/answers?id=eq.${this.props.id}`;
// An answer can't have a custom tag and be generic at the same time:
......@@ -366,7 +383,7 @@ export class AdminAnwsersEditPage extends React.Component {
await this.fetchTags();
}
async insertReference(reference) {
async createReference(reference) {
this.setState({ isUpdating: true });
try {
......@@ -387,7 +404,7 @@ export class AdminAnwsersEditPage extends React.Component {
console.warn(err);
}
await this.fetchReferences();
await this.loadReferences();
}
async deleteReference(id) {
......@@ -401,7 +418,7 @@ export class AdminAnwsersEditPage extends React.Component {
console.warn(err);
}
await this.fetchReferences();
await this.loadReferences();
}
submitReference(event, category = null) {
......@@ -433,7 +450,7 @@ export class AdminAnwsersEditPage extends React.Component {
});
}
this.insertReference(reference);
this.createReference(reference);
}
showSavingSpinner() {
......@@ -462,12 +479,24 @@ export class AdminAnwsersEditPage extends React.Component {
this.setState({ isUpdating: true });
if (isAdded) {
this.insertTag(id);
this.createTag(id);
} else {
this.deleteTag(id);
}
}
toggleIsPublished() {
const { id, is_published } = this.props.answers.data;
this.props.dispatch(
actions.answers.updateIsPublished(
[id],
!is_published,
this.fetchAnswer.bind(this)
)
);
}
handleCommentField(event) {
if (event.charCode !== 13) return;
......@@ -496,22 +525,7 @@ export class AdminAnwsersEditPage extends React.Component {
this.props.dispatch(actions.comments.remove([id], this.props.id));
}
getTags(category) {
return this.tags
.filter(tag => tag.category === category)
.map(({ id, value }, index) => (
<Tag
key={index}
isDisabled={this.state.isUpdating}
onClick={() => this.toggleTag(id, !this.state.tags.includes(id))}
selected={this.state.tags.includes(id)}
>
{value}
</Tag>
));
}
getReferences(category = null) {
renderReferences(category, isDisabled) {
const references = this.state.references.filter(
({ category: _category }) => _category === category
);
......@@ -525,6 +539,7 @@ export class AdminAnwsersEditPage extends React.Component {
return references.map(({ id, url, value }, index) => (
<Reference
isDisabled={isDisabled}
key={index}
onRemove={() => this.deleteReference(id)}
url={url}
......@@ -533,7 +548,7 @@ export class AdminAnwsersEditPage extends React.Component {
));
}
getComments() {
renderComments() {
return this.props.comments.data.map(({ id, is_private, value }, index) => (
<Comment
isMe={true}
......@@ -545,18 +560,6 @@ export class AdminAnwsersEditPage extends React.Component {
));
}
updateGenericReferenceTo(generic_reference) {
const { dispatch, id } = this.props;
dispatch(
actions.answers.setGenericRefence(
[id],
generic_reference,
this.fetchAnswer.bind(this)
)
);
}
render() {
const { answers, comments } = this.props;
const { isLoading, prevalue, isSidebarHidden, value } = this.state;
......@@ -565,7 +568,13 @@ export class AdminAnwsersEditPage extends React.Component {
return <AdminMain isLoading />;
}
const { agreement, generic_reference, question, state } = answers.data;
const {
agreement,
generic_reference,
is_published,
question,
state
} = answers.data;
return (
<AdminMain isScrollable={false}>
......@@ -651,7 +660,7 @@ export class AdminAnwsersEditPage extends React.Component {
<Strong isFirst>Articles du Code du travail</Strong>
<LawReferences
isDisabled={this.state.isUpdating}
onAdd={this.insertReference.bind(this)}
onAdd={this.createReference.bind(this)}
onRemove={this.deleteReference.bind(this)}
references={this.state.references.filter(
({ category }) => category === "labor_code"
......@@ -668,7 +677,9 @@ export class AdminAnwsersEditPage extends React.Component {
placeholder="Ex: Article 7, Texte sur les salaires de 1984…"
ref={node => (this.$agreementReferenceValueInput = node)}
/>
<Flex flexWrap="wrap">{this.getReferences("agreement")}</Flex>
<Flex flexWrap="wrap">
{this.renderReferences("agreement")}
</Flex>
<FormHiddenSubmit type="submit" />
</Form>
<Form onSubmit={this.submitReference.bind(this)}>
......@@ -686,7 +697,7 @@ export class AdminAnwsersEditPage extends React.Component {
placeholder="URL (ex: https://www.legifrance.gouv.fr/…)"
ref={node => (this.$otherReferenceUrlInput = node)}
/>
<Flex flexWrap="wrap">{this.getReferences()}</Flex>
<Flex flexWrap="wrap">{this.renderReferences(null)}</Flex>
<FormHiddenSubmit type="submit" />
</Form>
<Hr />
......@@ -694,7 +705,7 @@ export class AdminAnwsersEditPage extends React.Component {
<Subtitle isFirst>Renvoi</Subtitle>
<Radio
disabled={state === ANSWER_STATE.VALIDATED}
onChange={this.updateGenericReferenceTo.bind(this)}
onChange={this.updateGenericReference.bind(this)}
options={[
{
label: "Aucun renvoi.",
......@@ -728,11 +739,15 @@ export class AdminAnwsersEditPage extends React.Component {
<Subtitle>Références juridiques</Subtitle>
<Strong>Convention collective</Strong>
<Flex flexWrap="wrap">{this.getReferences("agreement")}</Flex>
<Flex flexWrap="wrap">
{this.renderReferences("agreement", true)}
</Flex>
<Strong>Code du travail</Strong>
<Flex flexWrap="wrap">{this.getReferences("labor_code")}</Flex>
<Flex flexWrap="wrap">
{this.renderReferences("labor_code", true)}
</Flex>
<Strong>Autres</Strong>
<Flex flexWrap="wrap">{this.getReferences()}</Flex>
<Flex flexWrap="wrap">{this.renderReferences(null, true)}</Flex>
<Hr />
<Subtitle isFirst>Renvoi</Subtitle>
......@@ -743,6 +758,16 @@ export class AdminAnwsersEditPage extends React.Component {
null: "Aucun renvoi."
}[String(generic_reference)]
}
<Hr />
<Subtitle isFirst>Publication</Subtitle>
<Flex>
<Checkbox
isChecked={is_published}
onClick={this.toggleIsPublished.bind(this)}
/>
Publiée sur le site du code du travail numérique.
</Flex>
</Flex>
)}
......@@ -758,7 +783,7 @@ export class AdminAnwsersEditPage extends React.Component {
flexDirection="column"
ref={node => (this.$commentsContainer = node)}
>
{this.getComments()}
{this.renderComments()}
</Comments>
<CommentEditor
disabled={comments.currentIsLoading}
......
......@@ -106,7 +106,7 @@ export class AdminAnswersIndexPage extends React.Component {
const newState = this.$newStateSelect.value;
this.props.dispatch(
actions.answers.setState(checked, newState, () =>
actions.answers.updateState(checked, newState, () =>
this.loadAnswers(state, 0)
)
);
......
......@@ -48,7 +48,7 @@ export class AdminAnswersPrintPage extends React.Component {
}
componentDidMount() {
this.loadAnswers();
this.load();
}
componentDidUpdate() {
......@@ -57,7 +57,7 @@ export class AdminAnswersPrintPage extends React.Component {
if (!isLoading) window.print();
}
loadAnswers() {
load() {
const uriParams = getUriParams();
const meta = {
isGeneric: this.isGeneric,
......@@ -69,11 +69,11 @@ export class AdminAnswersPrintPage extends React.Component {
this.props.dispatch(actions.answers.load(meta));
}
getAnswerValue(value) {
renderValue(value) {
return { __html: markdown.toHtml(value) };
}
getAnswersList() {
renderAnswers() {
const { data } = this.props.answers;
return data.map(
......@@ -93,7 +93,7 @@ export class AdminAnswersPrintPage extends React.Component {
>{`${question_index}) ${question_value}`}</Subtitle>
</Header>
<ContentTitle isFirst>Réponse corrigée:</ContentTitle>
<Pre dangerouslySetInnerHTML={this.getAnswerValue(value)} />
<Pre dangerouslySetInnerHTML={this.renderValue(value)} />
{references.length !== 0 && (
<div>
<ContentTitle isFirst>Références:</ContentTitle>
......@@ -117,9 +117,7 @@ export class AdminAnswersPrintPage extends React.Component {
return <Container>Chargement</Container>;
}
return (
<Container flexDirection="column">{this.getAnswersList()}</Container>
);
return <Container flexDirection="column">{this.renderAnswers()}</Container>;
}
}
......
......@@ -54,14 +54,11 @@ class AnswersEditPage extends React.Component {
this.prevalue = null;
this.newPrevalue = null;
this.createReference = debounce(this._createReference.bind(this), 500);
this.deleteReference = debounce(this._deleteReference.bind(this), 500);
this.insertReference = debounce(this._insertReference.bind(this), 500);
this.createTag = debounce(this._createTag.bind(this), 500);
this.deleteTag = debounce(this._deleteTag.bind(this), 500);
this.insertTag = debounce(this._insertTag.bind(this), 500);
this.saveAnswerPrevalue = debounce(
this._saveAnswerPrevalue.bind(this),
500
);
this.updatePrevalue = debounce(this._updatePrevalue.bind(this), 500);
}
static getInitialProps({ query: { id } }) {
......@@ -73,7 +70,7 @@ class AnswersEditPage extends React.Component {
const me = getCurrentUser();
this.axios = customAxios();
this.fetchAnswer();
this.load();
try {
const { data: references } = await this.axios.get(
......@@ -109,7 +106,7 @@ class AnswersEditPage extends React.Component {
}
}
fetchAnswer() {
load() {
const { dispatch, id } = this.props;
dispatch(actions.answers.loadOne(id));
......@@ -117,13 +114,13 @@ class AnswersEditPage extends React.Component {
toggleTag(id, isAdded) {
if (isAdded) {
this.insertTag(id);
this.createTag(id);
} else {
this.deleteTag(id);
}
}
async cancelAnswer() {
async cancel() {
if (this.state.isSaving) return;
this.props.dispatch(
......@@ -144,7 +141,7 @@ class AnswersEditPage extends React.Component {
actions.modal.open(
`Êtes-vous sûr d'envoyer cette réponse en validation ?`,
() =>
actions.answers.setState(
actions.answers.updateState(
[this.props.id],
ANSWER_STATE.PENDING_REVIEW,
() => Router.push("/answers/draft/1")
......@@ -153,7 +150,7 @@ class AnswersEditPage extends React.Component {
);
}
async _saveAnswerPrevalue(value) {
async _updatePrevalue(value) {
this.setState({ isSaving: true });
this.showSavingSpinner();
......@@ -174,7 +171,7 @@ class AnswersEditPage extends React.Component {
this.setState({ isSaving: false });
}
async _insertTag(tagId) {
async _createTag(tagId) {
this.setState({ isSaving: true });
try {
......@@ -221,7 +218,7 @@ class AnswersEditPage extends React.Component {
});
}
async _insertReference(reference) {
async _createReference(reference) {
this.setState({ isSaving: true });
try {
......@@ -296,7 +293,7 @@ class AnswersEditPage extends React.Component {
this.setState({ currentTab: nextTab });
}
getTabContent() {
renderTab() {
const { prevalue } = this;
const { references, tags } = this.state;
......@@ -305,7 +302,7 @@ class AnswersEditPage extends React.Component {
return (
<AnswerEditionReferences
laborCodeReferences={this.laborCodeReferences}
onAdd={this.insertReference.bind(this)}
onAdd={this.createReference.bind(this)}
onRemove={this.deleteReference.bind(this)}
references={references}
/>
......@@ -325,7 +322,7 @@ class AnswersEditPage extends React.Component {
return (
<AnswerEditionContent
defaultValue={prevalue}
onChange={this.saveAnswerPrevalue}
onChange={this.updatePrevalue}
/>
);
}
......@@ -349,7 +346,7 @@ class AnswersEditPage extends React.Component {
currentTab={this.state.currentTab}
idcc={agreement.idcc}
index={agreement.index}
onCancel={() => this.cancelAnswer()}
onCancel={() => this.cancel()}
onSubmit={() => this.requestForAnswerValidation()}
onTabChange={this.switchTab.bind(this)}
question={question}
......@@ -364,7 +361,7 @@ class AnswersEditPage extends React.Component {
</ContentInfo>
)}
{this.getTabContent()}
{this.renderTab()}
</Content>
</Container>
);
......
......@@ -61,7 +61,7 @@ class AnswersIndexPage extends React.Component {
me: null
};
this.loadAnswers = debounce(this._loadAnswers.bind(this), 500);
this.load = debounce(this._load.bind(this), 500);
}
static getInitialProps({ query: { page: maybePage, state: maybeState } }) {
......@@ -76,7 +76,7 @@ class AnswersIndexPage extends React.Component {
}
componentDidMount() {
this.loadAnswers(this.props.page - 1);
this.load(this.props.page - 1);
}
componentDidUpdate() {
......@@ -106,7 +106,7 @@ class AnswersIndexPage extends React.Component {
}
}
_loadAnswers(pageIndex = 0) {
_load(pageIndex = 0) {