Création/mise à jour basique d'un DAD #15

Manually merged
wpetit merged 7 commits from feature/dad into develop 2020-08-26 14:52:54 +02:00
8 changed files with 150 additions and 42 deletions
Showing only changes of commit 680614148c - Show all commits

View File

@ -1,31 +1,70 @@
import React, { FunctionComponent, useState } from 'react'; import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
import { DecisionSupportFile } from '../../types/decision'; import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
export interface ClarificationSectionProps { export interface ClarificationSectionProps extends DecisionSupportFileUpdaterProps {};
dsf: DecisionSupportFile,
}; const ClarificationSectionName = 'clarification';
export const ClarificationSection: FunctionComponent<ClarificationSectionProps> = ({ dsf, updateDSF }) => {
const [ section, setSection ] = useState({
objectives: '',
motivations: '',
scope: '',
nature: '',
deadline: undefined,
hasDeadline: false,
});
useEffect(() => {
updateDSF({ ...dsf, sections: { ...dsf.sections, [ClarificationSectionName]: { ...section }} })
}, [section]);
const onTitleChange = (evt: ChangeEvent<HTMLInputElement>) => {
const title = (evt.currentTarget).value;
updateDSF({ ...dsf, title });
};
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
const target = evt.currentTarget;
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
setSection(section => ({ ...section, [attrName]: value }));
};
const onDeadlineChange = (evt: ChangeEvent<HTMLInputElement>) => {
const deadline = evt.currentTarget.valueAsDate;
setSection(section => ({ ...section, deadline }));
};
export const ClarificationSection: FunctionComponent<ClarificationSectionProps> = ({ dsf }) => {
return ( return (
<section> <section>
<div className="box"> <div className="box">
<div className="field"> <div className="field">
<label className="label">Intitulé du dossier</label> <label className="label">Intitulé du dossier</label>
<div className="control"> <div className="control">
<input className="input" type="text" /> <input className="input" type="text" value={dsf.title} onChange={onTitleChange} />
</div> </div>
</div> </div>
<div className="field"> <div className="field">
<label className="label">Quelle décision devons nous prendre ?</label> <label className="label">Quelle décision devons nous prendre ?</label>
<div className="control"> <div className="control">
<textarea className="textarea" placeholder="Décrire globalement les tenants et aboutissants de la décision à prendre." rows={10}></textarea> <textarea className="textarea"
value={section.objectives}
onChange={onSectionAttrChange.bind(null, 'objectives')}
placeholder="Décrire globalement les tenants et aboutissants de la décision à prendre."
rows={10}>
</textarea>
</div> </div>
<p className="help is-info"><i className="fa fa-info-circle"></i> Ne pas essayer de rentrer trop dans les détails ici. Préférer l'utilisation des annexes et y faire référence.</p> <p className="help is-info"><i className="fa fa-info-circle"></i> Ne pas essayer de rentrer trop dans les détails ici. Préférer l'utilisation des annexes et y faire référence.</p>
</div> </div>
<div className="field"> <div className="field">
<label className="label">Pourquoi devons nous prendre cette décision ?</label> <label className="label">Pourquoi devons nous prendre cette décision ?</label>
<div className="control"> <div className="control">
<textarea className="textarea" placeholder="Décrire pourquoi il est important de prendre cette décision." rows={10}></textarea> <textarea className="textarea"
value={section.motivations}
onChange={onSectionAttrChange.bind(null, 'motivations')}
placeholder="Décrire pourquoi il est important de prendre cette décision."
rows={10}>
</textarea>
</div> </div>
<p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer si des obligations légales pèsent sur cette prise de décision.</p> <p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer si des obligations légales pèsent sur cette prise de décision.</p>
</div> </div>
@ -33,11 +72,13 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
<label className="label">Portée de la décision</label> <label className="label">Portée de la décision</label>
<div className="control"> <div className="control">
<div className="select"> <div className="select">
<select> <select
onChange={onSectionAttrChange.bind(null, 'scope')}
value={section.scope}>
<option></option> <option></option>
<option>Individuelle</option> <option value="individual">Individuelle</option>
<option>Groupe identifié</option> <option value="identified-group">Groupe identifié</option>
<option>Collective</option> <option value="collective">Collective</option>
</select> </select>
</div> </div>
</div> </div>
@ -46,19 +87,33 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
<label className="label">Nature de la décision</label> <label className="label">Nature de la décision</label>
<div className="control"> <div className="control">
<div className="select"> <div className="select">
<select> <select onChange={onSectionAttrChange.bind(null, 'nature')}
value={section.nature}>
<option></option> <option></option>
<option>Opérationnelle</option> <option value="operational">Opérationnelle</option>
<option>Tactique</option> <option value="tactic">Tactique</option>
<option>Stratégique</option> <option value="strategic">Stratégique</option>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
<div className="field">
<label className="label">Existe t'il une échéance particulière pour cette décision ?</label> <div className="columns">
<div className="control"> <div className="column">
<input className="input" type="date" /> <label className="checkbox">
<input type="checkbox"
onChange={onSectionAttrChange.bind(null, 'hasDeadline')}
checked={section.hasDeadline} />
<span className="ml-1">Existe t'il une échéance particulière pour cette décision ?</span>
</label>
<div className="field">
<div className="control">
<input disabled={!section.hasDeadline}
value={section.deadline ? section.deadline.toISOString().substr(0, 10) : ''}
onChange={onDeadlineChange}
type="date" className="input" />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,15 +4,42 @@ import { ClarificationSection } from './ClarificationSection';
import { OptionsSection } from './OptionsSection'; import { OptionsSection } from './OptionsSection';
import { MetadataPanel } from './MetadataPanel'; import { MetadataPanel } from './MetadataPanel';
import { AppendixPanel } from './AppendixPanel'; import { AppendixPanel } from './AppendixPanel';
import { DecisionSupportFile, newDecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
import { useParams } from 'react-router';
import { useDecisions } from '../../gql/queries/decisions';
export interface DecisionSupportFilePageProps { export interface DecisionSupportFilePageProps {
}; };
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => { export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
const [ state, setState ] = useState({ dsf: null }); const { id } = useParams();
const isNew = true; const { decisions } = useDecisions({
const isClosed = false; variables:{
filter: {
ids: [id],
}
}
});
const [ state, setState ] = useState({
dsf: decisions.length > 0 ? decisions[0] : newDecisionSupportFile(),
selectedTabIndex: 0
});
const isNew = state.dsf.id === '';
const isClosed = state.dsf.status === DecisionSupportFileStatus.Closed;
const selectTab = (tabIndex: number) => {
setState(state => ({ ...state, selectedTabIndex: tabIndex }));
};
const updateDSF = (dsf: DecisionSupportFile) => {
setState(state => ({...state, dsf}));
};
console.log(state.dsf);
return ( return (
<Page title="Dossier d'Aide à la Décision"> <Page title="Dossier d'Aide à la Décision">
<div className="container is-fluid"> <div className="container is-fluid">
@ -39,21 +66,24 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
</section> </section>
<div className="columns mt-3"> <div className="columns mt-3">
<div className="column is-9"> <div className="column is-9">
<div className="tabs is-medium is-toggle has-background-white"> <div className="tabs is-medium is-toggle">
<ul> <ul>
<li className="is-active"> <li className={`has-background-white ${state.selectedTabIndex === 0 ? 'is-active': ''}`}
onClick={selectTab.bind(null, 0)}>
<a> <a>
<span className="icon is-small"><i className="fas fa-pen" aria-hidden="true"></i></span> <span className="icon is-small"><i className="fas fa-pen" aria-hidden="true"></i></span>
<span>Clarifier la décision</span> <span>Clarifier la décision</span>
</a> </a>
</li> </li>
<li> <li className={`has-background-white ${state.selectedTabIndex === 1 ? 'is-active': ''}`}
onClick={selectTab.bind(null, 1)}>
<a> <a>
<span className="icon is-small"><i className="fas fa-search" aria-hidden="true"></i></span> <span className="icon is-small"><i className="fas fa-search" aria-hidden="true"></i></span>
<span>Explorer les options</span> <span>Explorer les options</span>
</a> </a>
</li> </li>
<li> <li className={`has-background-white ${state.selectedTabIndex === 2 ? 'is-active': ''}`}
onClick={selectTab.bind(null, 2)}>
<a> <a>
<span className="icon is-small"><i className="fas fa-person-booth" aria-hidden="true"></i></span> <span className="icon is-small"><i className="fas fa-person-booth" aria-hidden="true"></i></span>
<span>Prendre la décision</span> <span>Prendre la décision</span>
@ -61,7 +91,11 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
</li> </li>
</ul> </ul>
</div> </div>
<ClarificationSection dsf={state.dsf} /> {
state.selectedTabIndex === 0 ?
<ClarificationSection dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
</div> </div>
<div className="column is-3"> <div className="column is-3">
<MetadataPanel dsf={state.dsf} /> <MetadataPanel dsf={state.dsf} />

View File

@ -0,0 +1,6 @@
import { DecisionSupportFile } from "../../types/decision";
export interface DecisionSupportFileUpdaterProps {
dsf: DecisionSupportFile
updateDSF: (dsf: DecisionSupportFile) => void
}

View File

@ -14,12 +14,12 @@ export function DecisionSupportFilePanel() {
label: 'Mes dossiers en cours', label: 'Mes dossiers en cours',
itemFilter: (item: Item) => { itemFilter: (item: Item) => {
const dsf = item as DecisionSupportFile; const dsf = item as DecisionSupportFile;
return dsf.status === DecisionSupportFileStatus.Opened && inWorkgroup(user, dsf.workgroup); return (dsf.status === DecisionSupportFileStatus.Draft || dsf.status === DecisionSupportFileStatus.Ready) && inWorkgroup(user, dsf.workgroup);
} }
}, },
{ {
label: 'Ouverts', label: 'Brouillons',
itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Opened itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Draft
}, },
{ {
label: 'Clos', label: 'Clos',

View File

@ -24,9 +24,9 @@ export function useDecisionsQuery(options = {}) {
return useQuery(QUERY_DECISIONS, options); return useQuery(QUERY_DECISIONS, options);
} }
export function useDecisions() { export function useDecisions(options = {}) {
const { data, loading, error } = useGraphQLData<DecisionSupportFile[]>( const { data, loading, error } = useGraphQLData<DecisionSupportFile[]>(
QUERY_DECISIONS, 'decicions', [] QUERY_DECISIONS, 'decisions', [], options
); );
return { decisions: data, loading, error }; return { decisions: data, loading, error };
} }

View File

@ -1,8 +1,8 @@
import { useQuery, DocumentNode } from "@apollo/client"; import { useQuery, DocumentNode } from "@apollo/client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
export function useGraphQLData<T>(q: DocumentNode, key: string, defaultValue: T) { export function useGraphQLData<T>(q: DocumentNode, key: string, defaultValue: T, options = {}) {
const query = useQuery(q); const query = useQuery(q, options);
const [ data, setData ] = useState<T>(defaultValue); const [ data, setData ] = useState<T>(defaultValue);
useEffect(() => { useEffect(() => {
setData(query.data ? query.data[key] as T : defaultValue); setData(query.data ? query.data[key] as T : defaultValue);

View File

@ -22,9 +22,10 @@ export function useWorkgroupsQuery(options = {}) {
return useQuery(QUERY_WORKGROUP, options); return useQuery(QUERY_WORKGROUP, options);
} }
export function useWorkgroups() { export function useWorkgroups(options = {}) {
const { data, loading, error } = useGraphQLData<Workgroup[]>( const { data, loading, error } = useGraphQLData<Workgroup[]>(
QUERY_WORKGROUP, 'workgroups', [] QUERY_WORKGROUP, 'workgroups', [],
options
); );
return { workgroups: data, loading, error }; return { workgroups: data, loading, error };
} }

View File

@ -1,23 +1,35 @@
import { Workgroup } from "./workgroup"; import { Workgroup } from "./workgroup";
export enum DecisionSupportFileStatus { export enum DecisionSupportFileStatus {
Opened = "opened", Draft = "draft",
Ready = "ready",
Voted = "voted", Voted = "voted",
Closed = "closed", Closed = "closed",
} }
export interface DecisionSupportFileSection { export interface DecisionSupportFileSection {
id: string name: string
} }
// aka Dossier d'aide à la décision // aka Dossier d'aide à la décision
export interface DecisionSupportFile { export interface DecisionSupportFile {
id: string id: string
title: string title: string
sections: DecisionSupportFileSection[] sections: {[name: string]: any}
status: DecisionSupportFileStatus status: DecisionSupportFileStatus
workgroup: Workgroup, workgroup?: Workgroup,
createdAt: Date createdAt: Date
votedAt?: Date votedAt?: Date
closedAt?: Date closedAt?: Date
} }
export function newDecisionSupportFile(): DecisionSupportFile {
return {
id: '',
title: '',
sections: {},
status: DecisionSupportFileStatus.Draft,
workgroup: null,
createdAt: new Date(),
};
}