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

This commit is contained in:
Teddy Cornaut 2020-08-28 16:00:36 +02:00
parent 0fe6e1f07a
commit 406202ddc4
5 changed files with 225 additions and 8 deletions

View File

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

View File

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

View File

@ -1,16 +1,147 @@
import React, { FunctionComponent, useState } from 'react';
import { DecisionSupportFile } from '../../types/decision';
import React, { FunctionComponent, useState, useEffect, ChangeEvent, MouseEvent } from 'react';
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
import { base58UUID } from "../../util/uuid";
export interface OptionsSectionProps {
dsf: DecisionSupportFile,
};
export interface OptionsSectionProps extends DecisionSupportFileUpdaterProps {};
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 ] }}));
};
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 }}));
};
export const OptionsSection: FunctionComponent<OptionsSectionProps> = ({ dsf }) => {
return (
<section>
<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="table-container">
<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>
</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 @@
{
"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=="
}
}
}