Permettre de gérer les options proposées dans un DAD #19

Manually merged
tcornaut merged 6 commits from feature/options into develop 2020-08-31 15:32:40 +02:00
5 changed files with 225 additions and 8 deletions
Showing only changes of commit 406202ddc4 - Show all commits

View File

@ -1,5 +1,5 @@
{ {
"name": "dadd-", "name": "daddy",
"version": "0.0.0", "version": "0.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -8,6 +8,7 @@ import { useParams, useHistory } from 'react-router';
import { useDecisionSupportFiles } from '../../gql/queries/dsf'; import { useDecisionSupportFiles } from '../../gql/queries/dsf';
import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutation } from '../../gql/mutations/dsf'; import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutation } from '../../gql/mutations/dsf';
import { useDebounce } from '../../hooks/useDebounce'; import { useDebounce } from '../../hooks/useDebounce';
import { OptionsSection } from './OptionsSection';
export interface DecisionSupportFilePageProps { export interface DecisionSupportFilePageProps {
@ -143,6 +144,11 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
<ClarificationSection dsf={state.dsf} updateDSF={updateDSF} /> : <ClarificationSection dsf={state.dsf} updateDSF={updateDSF} /> :
null null
} }
{
state.selectedTabIndex === 1 ?
<OptionsSection dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
</div> </div>
<div className="column is-3"> <div className="column is-3">
<MetadataPanel dsf={state.dsf} updateDSF={updateDSF} /> <MetadataPanel dsf={state.dsf} updateDSF={updateDSF} />

View File

@ -1,16 +1,147 @@
import React, { FunctionComponent, useState } from 'react'; import React, { FunctionComponent, useState, useEffect, ChangeEvent, MouseEvent } from 'react';
import { DecisionSupportFile } from '../../types/decision'; import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
import { base58UUID } from "../../util/uuid";
export interface OptionsSectionProps { export interface OptionsSectionProps extends DecisionSupportFileUpdaterProps {};
dsf: DecisionSupportFile,
}; const OptionsSectionName = 'options';
export const OptionsSection: FunctionComponent<OptionsSectionProps> = ({ dsf, updateDSF }) => {
interface OptionsSectionState {
changed: boolean
section: OptionsSection
}
interface OptionsSection {
options: Option[]
}
interface Option {
id: string
label: string
pros: string
cons: string
}
const [ state, setState ] = useState<OptionsSectionState>({
changed: false,
section: {
options: [],
}
});
useEffect(() => {
if (!state.changed) return;
updateDSF({ ...dsf, sections: { ...dsf.sections, [OptionsSectionName]: { ...state.section }} })
setState(state => ({ ...state, changed: false }));
}, [state.changed]);
useEffect(() => {
if (!dsf.sections[OptionsSectionName]) return;
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[OptionsSectionName] }}));
}, [dsf.sections[OptionsSectionName]]);
function newOption(label: string, pros: string, cons: string): Option {
return {
id: base58UUID(),
label,
pros,
cons
};
}
const onAddOptionClick = (evt: MouseEvent) => {
const option = newOption("Décision", "", "");
Review

JSON.parse(JSON.stringify(state.section.options)) est utilisé ici pour faire une copie ?

Pourquoi ne pas plutôt faire const options = [ ...state.section.options ]; ? Cette méthode est plus performante qu'une sérialisation/dé-sérialisation JSON du tableau.

`JSON.parse(JSON.stringify(state.section.options))` est utilisé ici pour faire une copie ? Pourquoi ne pas plutôt faire `const options = [ ...state.section.options ];` ? Cette méthode est plus performante qu'une sérialisation/dé-sérialisation JSON du tableau.
Review

Malheureusement la méthode de copie que tu cites ne lève pas l'interdiction d'écrire dans options, je n'ai pas trouvé d'autre méthode que JSON.parse(JSON.stringify(state.section.options)) pour en faire une copie modifiable.

Malheureusement la méthode de copie que tu cites ne lève pas l'interdiction d'écrire dans options, je n'ai pas trouvé d'autre méthode que JSON.parse(JSON.stringify(state.section.options)) pour en faire une copie modifiable.
setState(state => ({ ...state, changed: true, section: { ...state.section, options: [ ...state.section.options, option ] }}));
Review

Si il n'y aura pas d'assignation de nouvelle valeur à la variable, préférer const à var pour identifier assez vite les erreurs liées à l'écrasement de valeurs "constantes".

Si il n'y aura pas d'assignation de nouvelle valeur à la variable, préférer `const` à `var` pour identifier assez vite les erreurs liées à l'écrasement de valeurs "constantes".
};
const onOptionChange = (id: string, attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
const target = evt.currentTarget;
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
var options = state.section.options;
options[id][attrName] = value;
setState(state => ({ ...state, changed: true, section: { ...state.section, options: options }}));
Review

Idem qu'un peu plus haut.

Idem qu'un peu plus haut.
};
export const OptionsSection: FunctionComponent<OptionsSectionProps> = ({ dsf }) => {
return ( return (
<section> <section>
<h4 id="options-section" className="is-size-4 title is-spaced"><a href="#options-section">Explorer les options</a></h4> <h4 id="options-section" className="is-size-4 title is-spaced"><a href="#options-section">Explorer les options</a></h4>
<div className="box"> <div className="box">
<div className="table-container">
Review

Idem, const semble préférable à var ici.

Idem, `const` semble préférable à `var` ici.
<table className={`table is-bordered is-striped is-hoverable is-fullwidth`}>
<thead>
<tr>
<th></th>
<th>Décision</th>
<th>Pours</th>
<th>Contres</th>
</tr>
</thead>
<tbody>
{
state.section.options.map((o, index) => {
return (
<tr key={`option-${o.id}`}>
<td>
<button
className="button is-danger is-small is-outlined">
🗑
</button>
</td>
<td>
<textarea className="textarea"
value={o.label}
onChange={onOptionChange.bind(null, index, 'label')}
placeholder="Décrire cette décision."
rows={10}>
</textarea>
</td>
<td>
<textarea className="textarea"
value={o.pros}
onChange={onOptionChange.bind(null, index, 'pros')}
placeholder="Décrire les avantages de cette décision."
rows={10}>
</textarea>
</td>
<td>
<textarea className="textarea"
value={o.cons}
onChange={onOptionChange.bind(null, index, 'cons')}
placeholder="Décrire les désavantages de cette décision."
rows={10}>
</textarea>
</td>
</tr>
)
})
}
{
state.section.options.length === 0 ?
<tr>
<td className={`noPrint`}></td>
<td colSpan={4}>Aucune option pour l'instant.</td>
</tr> :
null
}
</tbody>
<tfoot>
<tr>
<td className={`noPrint`} colSpan={3}></td>
<td colSpan={2}>
<div className="field has-addons noPrint">
<p className="control">
<a className="button is-primary" onClick={onAddOptionClick}>
Ajouter
</a>
</p>
</div>
</td>
</tr>
</tfoot>
</table>
</div>
</div> </div>
</section> </section>
); );

53
client/src/util/uuid.ts Normal file
View File

@ -0,0 +1,53 @@
import bs58 from 'bs58';
const hex: string[] = [];
for (var i = 0; i < 256; i++) {
hex[i] = (i < 16 ? '0' : '') + (i).toString(16);
}
export function uuidV4(): string {
const r = crypto.getRandomValues(new Uint8Array(16));
r[6] = r[6] & 0x0f | 0x40;
r[8] = r[8] & 0x3f | 0x80;
return (
hex[r[0]] +
hex[r[1]] +
hex[r[2]] +
hex[r[3]] +
"-" +
hex[r[4]] +
hex[r[5]] +
"-" +
hex[r[6]] +
hex[r[7]] +
"-" +
hex[r[8]] +
hex[r[9]] +
"-" +
hex[r[10]] +
hex[r[11]] +
hex[r[12]] +
hex[r[13]] +
hex[r[14]] +
hex[r[15]]
);
}
export function toUTF8Bytes(str: string): number[] {
var utf8 = unescape(encodeURIComponent(str));
var arr: number[] = [];
for (var i = 0; i < utf8.length; i++) {
arr.push(utf8.charCodeAt(i));
}
return arr
}
export function base58UUID(): string {
const uuid = uuidV4();
return bs58.encode(toUTF8Bytes(uuid));
}

27
package-lock.json generated Normal file
View File

@ -0,0 +1,27 @@
{
Review

Je pense que ce fichier devrait plutot être dans le répertoire client/, à côté du fichier package.json non ?

Je pense que ce fichier devrait plutot être dans le répertoire `client/`, à côté du fichier `package.json` non ?
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"base-x": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
"integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
"requires": {
"base-x": "^3.0.2"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
}