Permettre de gérer les options proposées dans un DAD #19
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "dadd-",
|
"name": "daddy",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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", "", "");
|
||||||
|
|||||||
|
setState(state => ({ ...state, changed: true, section: { ...state.section, options: [ ...state.section.options, option ] }}));
|
||||||
wpetit
commented
Si il n'y aura pas d'assignation de nouvelle valeur à la variable, préférer 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 }}));
|
||||||
wpetit
commented
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">
|
||||||
wpetit
commented
Idem, 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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
wpetit
commented
Je pense que ce fichier devrait plutot être dans le répertoire 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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
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.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.