diff --git a/client/package-lock.json b/client/package-lock.json index 8552a0f..721fda7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,5 +1,5 @@ { - "name": "dadd-", + "name": "daddy", "version": "0.0.0", "lockfileVersion": 1, "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": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -3127,6 +3135,14 @@ "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": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", @@ -8631,8 +8647,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", diff --git a/client/package.json b/client/package.json index ad76110..9a35688 100644 --- a/client/package.json +++ b/client/package.json @@ -53,6 +53,7 @@ "dependencies": { "@apollo/client": "^3.0.2", "@types/qs": "^6.9.3", + "bs58": "^4.0.1", "bulma": "^0.9.0", "graphql": "^15.3.0", "react": "^16.12.0", diff --git a/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx b/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx index 3af2a01..84da55f 100644 --- a/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx +++ b/client/src/components/DecisionSupportFilePage/DecisionSupportFilePage.tsx @@ -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 : null } + { + state.selectedTabIndex === 1 ? + : + null + }
diff --git a/client/src/components/DecisionSupportFilePage/OptionsSection.tsx b/client/src/components/DecisionSupportFilePage/OptionsSection.tsx index bd537cd..7c4e256 100644 --- a/client/src/components/DecisionSupportFilePage/OptionsSection.tsx +++ b/client/src/components/DecisionSupportFilePage/OptionsSection.tsx @@ -1,16 +1,152 @@ -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 = ({ 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({ + 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) => { + 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 = ({ dsf }) => { return (

Explorer les options

- +
+ + + + + + + + + + + { + state.section.options.map((o, index) => { + return ( + + + + + + + ) + }) + } + { + state.section.options.length === 0 ? + + + + : + null + } + + + + + + +
DécisionPoursContres
+ + + + + + + +
Aucune option pour l'instant.
+ + Ajouter + +
+
); diff --git a/client/src/gql/queries/dsf.tsx b/client/src/gql/queries/dsf.tsx index 2155aa6..e41b5fc 100644 --- a/client/src/gql/queries/dsf.tsx +++ b/client/src/gql/queries/dsf.tsx @@ -7,7 +7,7 @@ export const QUERY_DECISION_SUPPORT_FILES = gql` decisionSupportFiles(filter: $filter) { id, title, - sections + sections, createdAt, closedAt, votedAt, @@ -18,7 +18,7 @@ export const QUERY_DECISION_SUPPORT_FILES = gql` members { id } - } + }, } } `; diff --git a/client/src/util/uuid.ts b/client/src/util/uuid.ts new file mode 100644 index 0000000..9cf8c09 --- /dev/null +++ b/client/src/util/uuid.ts @@ -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)); +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 9846a19..8a23fe4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: command: hydra serve all --dangerous-force-http hydra-passwordless: - image: bornholm/hydra-passwordless:latest + image: bornholm/hydra-passwordless:latest@sha256:e6b335e3677dc937c62978890b42312a7486e4fe10208aa2670b1917489ec492 ports: - 3000:3000 environment: @@ -48,6 +48,7 @@ services: - SMTP_INSECURE_SKIP_VERIFY=true - HYDRA_BASE_URL=http://hydra:4445 - HYDRA_FAKE_SSL_TERMINATION=false + - NO_PROXY=hydra smtp: image: bornholm/fake-smtp