Merge branch 'feature/options' of Cadoles/daddy into develop
This commit is contained in:
commit
c95fbf6915
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "dadd-",
|
"name": "daddy",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
@ -2839,6 +2839,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"base64-js": {
|
"base64-js": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||||
|
@ -3127,6 +3135,14 @@
|
||||||
"pkg-up": "^2.0.0"
|
"pkg-up": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"btoa": {
|
"btoa": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
||||||
|
@ -8631,8 +8647,7 @@
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"safe-regex": {
|
"safe-regex": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.0.2",
|
"@apollo/client": "^3.0.2",
|
||||||
"@types/qs": "^6.9.3",
|
"@types/qs": "^6.9.3",
|
||||||
|
"bs58": "^4.0.1",
|
||||||
"bulma": "^0.9.0",
|
"bulma": "^0.9.0",
|
||||||
"graphql": "^15.3.0",
|
"graphql": "^15.3.0",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
|
|
|
@ -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,152 @@
|
||||||
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 options = JSON.parse(JSON.stringify(state.section.options))
|
||||||
|
const option = newOption("Décision", "", "");
|
||||||
|
options.push(option);
|
||||||
|
setState(state => ({ ...state, changed: true, section: { ...state.section, options }}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOptionChange = (id: number, attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const target = evt.currentTarget;
|
||||||
|
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
|
||||||
|
const options = JSON.parse(JSON.stringify(state.section.options))
|
||||||
|
options[id][attrName] = value;
|
||||||
|
setState(state => ({ ...state, changed: true, section: { ...state.section, options }}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRemoveOptionClick = (id: number, evt: MouseEvent) => {
|
||||||
|
if(confirm('Voulez-vous supprimer cette option ?')){
|
||||||
|
const options = JSON.parse(JSON.stringify(state.section.options))
|
||||||
|
options.splice(id, 1);
|
||||||
|
setState(state => ({ ...state, changed: true, section: { ...state.section, options }}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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">
|
||||||
|
<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
|
||||||
|
onClick={onRemoveOptionClick.bind(null, index)}
|
||||||
|
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 is-success"
|
||||||
|
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 is-danger"
|
||||||
|
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></td>
|
||||||
|
<td colSpan={4}>Aucune option pour l'instant.</td>
|
||||||
|
</tr> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colSpan={5}>
|
||||||
|
<a className="button is-primary is-pulled-right" onClick={onAddOptionClick}>
|
||||||
|
Ajouter
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const QUERY_DECISION_SUPPORT_FILES = gql`
|
||||||
decisionSupportFiles(filter: $filter) {
|
decisionSupportFiles(filter: $filter) {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
sections
|
sections,
|
||||||
createdAt,
|
createdAt,
|
||||||
closedAt,
|
closedAt,
|
||||||
votedAt,
|
votedAt,
|
||||||
|
@ -18,7 +18,7 @@ export const QUERY_DECISION_SUPPORT_FILES = gql`
|
||||||
members {
|
members {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
Loading…
Reference in New Issue