Création/mise à jour basique d'un DAD
This commit is contained in:
@ -3,11 +3,11 @@ import { DecisionSupportFile, DecisionSupportFileStatus } from '../../types/deci
|
||||
import { ItemPanel, TabDefinition, Item } from './ItemPanel';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { inWorkgroup } from '../../types/workgroup';
|
||||
import { useDecisions } from '../../gql/queries/decisions';
|
||||
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||
|
||||
export function DecisionSupportFilePanel() {
|
||||
const { user } = useUserProfile();
|
||||
const { decisions } = useDecisions();
|
||||
const { decisionSupportFiles } = useDecisionSupportFiles();
|
||||
|
||||
const tabs: TabDefinition[] = [
|
||||
{
|
||||
@ -31,9 +31,9 @@ export function DecisionSupportFilePanel() {
|
||||
return (
|
||||
<ItemPanel
|
||||
className='is-link'
|
||||
title="D.A.D."
|
||||
title="Dossiers"
|
||||
newItemUrl="/decisions/new"
|
||||
items={decisions}
|
||||
items={decisionSupportFiles}
|
||||
tabs={tabs}
|
||||
itemIconClassName='fas fa-folder'
|
||||
itemKey={item => item.id}
|
||||
|
@ -1,23 +1,35 @@
|
||||
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
|
||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { asDate } from '../../util/date';
|
||||
|
||||
export interface ClarificationSectionProps extends DecisionSupportFileUpdaterProps {};
|
||||
|
||||
const ClarificationSectionName = 'clarification';
|
||||
|
||||
export const ClarificationSection: FunctionComponent<ClarificationSectionProps> = ({ dsf, updateDSF }) => {
|
||||
const [ section, setSection ] = useState({
|
||||
objectives: '',
|
||||
motivations: '',
|
||||
scope: '',
|
||||
nature: '',
|
||||
deadline: undefined,
|
||||
hasDeadline: false,
|
||||
const [ state, setState ] = useState({
|
||||
changed: false,
|
||||
section: {
|
||||
objectives: '',
|
||||
motivations: '',
|
||||
scope: '',
|
||||
nature: '',
|
||||
deadline: undefined,
|
||||
hasDeadline: false,
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateDSF({ ...dsf, sections: { ...dsf.sections, [ClarificationSectionName]: { ...section }} })
|
||||
}, [section]);
|
||||
if (!state.changed) return;
|
||||
updateDSF({ ...dsf, sections: { ...dsf.sections, [ClarificationSectionName]: { ...state.section }} })
|
||||
setState(state => ({ ...state, changed: false }));
|
||||
}, [state.changed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dsf.sections[ClarificationSectionName]) return;
|
||||
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[ClarificationSectionName] }}));
|
||||
}, [dsf.sections[ClarificationSectionName]]);
|
||||
|
||||
const onTitleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const title = (evt.currentTarget).value;
|
||||
@ -27,12 +39,12 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||
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 }));
|
||||
setState(state => ({ ...state, changed: true, section: {...state.section, [attrName]: value }}));
|
||||
};
|
||||
|
||||
const onDeadlineChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const deadline = evt.currentTarget.valueAsDate;
|
||||
setSection(section => ({ ...section, deadline }));
|
||||
setState(state => ({ ...state, changed: true, section: { ...state.section, deadline }}));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -48,7 +60,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||
<label className="label">Quelle décision devons nous prendre ?</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea"
|
||||
value={section.objectives}
|
||||
value={state.section.objectives}
|
||||
onChange={onSectionAttrChange.bind(null, 'objectives')}
|
||||
placeholder="Décrire globalement les tenants et aboutissants de la décision à prendre."
|
||||
rows={10}>
|
||||
@ -60,7 +72,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||
<label className="label">Pourquoi devons nous prendre cette décision ?</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea"
|
||||
value={section.motivations}
|
||||
value={state.section.motivations}
|
||||
onChange={onSectionAttrChange.bind(null, 'motivations')}
|
||||
placeholder="Décrire pourquoi il est important de prendre cette décision."
|
||||
rows={10}>
|
||||
@ -74,7 +86,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||
<div className="select">
|
||||
<select
|
||||
onChange={onSectionAttrChange.bind(null, 'scope')}
|
||||
value={section.scope}>
|
||||
value={state.section.scope}>
|
||||
<option></option>
|
||||
<option value="individual">Individuelle</option>
|
||||
<option value="identified-group">Groupe identifié</option>
|
||||
@ -88,7 +100,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||
<div className="control">
|
||||
<div className="select">
|
||||
<select onChange={onSectionAttrChange.bind(null, 'nature')}
|
||||
value={section.nature}>
|
||||
value={state.section.nature}>
|
||||
<option></option>
|
||||
<option value="operational">Opérationnelle</option>
|
||||
<option value="tactic">Tactique</option>
|
||||
@ -103,13 +115,13 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||
<label className="checkbox">
|
||||
<input type="checkbox"
|
||||
onChange={onSectionAttrChange.bind(null, 'hasDeadline')}
|
||||
checked={section.hasDeadline} />
|
||||
checked={state.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) : ''}
|
||||
<input disabled={!state.section.hasDeadline}
|
||||
value={state.section.deadline ? asDate(state.section.deadline).toISOString().substr(0, 10) : ''}
|
||||
onChange={onDeadlineChange}
|
||||
type="date" className="input" />
|
||||
</div>
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import React, { FunctionComponent, useState, useEffect } from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { ClarificationSection } from './ClarificationSection';
|
||||
import { OptionsSection } from './OptionsSection';
|
||||
import { MetadataPanel } from './MetadataPanel';
|
||||
import { AppendixPanel } from './AppendixPanel';
|
||||
import { DecisionSupportFile, newDecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
|
||||
import { useParams } from 'react-router';
|
||||
import { useDecisions } from '../../gql/queries/decisions';
|
||||
import { useParams, useHistory } from 'react-router';
|
||||
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||
import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutation } from '../../gql/mutations/dsf';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
|
||||
export interface DecisionSupportFilePageProps {
|
||||
|
||||
@ -14,31 +15,69 @@ export interface DecisionSupportFilePageProps {
|
||||
|
||||
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
|
||||
const { id } = useParams();
|
||||
const { decisions } = useDecisions({
|
||||
const history = useHistory();
|
||||
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||
variables:{
|
||||
filter: {
|
||||
ids: [id],
|
||||
filter: {
|
||||
ids: id !== 'new' ? [id] : undefined,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [ state, setState ] = useState({
|
||||
dsf: decisions.length > 0 ? decisions[0] : newDecisionSupportFile(),
|
||||
selectedTabIndex: 0
|
||||
dsf: newDecisionSupportFile(),
|
||||
saved: true,
|
||||
selectedTabIndex: 0,
|
||||
});
|
||||
|
||||
const isNew = state.dsf.id === '';
|
||||
const isClosed = state.dsf.status === DecisionSupportFileStatus.Closed;
|
||||
|
||||
useEffect(() => {
|
||||
const dsf = decisionSupportFiles.length > 0 && decisionSupportFiles[0].id === id ? decisionSupportFiles[0] : {};
|
||||
setState(state => ({ ...state, dsf: { ...state.dsf, ...dsf }}))
|
||||
}, [ decisionSupportFiles ]);
|
||||
|
||||
const selectTab = (tabIndex: number) => {
|
||||
setState(state => ({ ...state, selectedTabIndex: tabIndex }));
|
||||
};
|
||||
|
||||
const updateDSF = (dsf: DecisionSupportFile) => {
|
||||
setState(state => ({...state, dsf}));
|
||||
setState(state => {
|
||||
return { ...state, saved: false, dsf: { ...state.dsf, ...dsf } };
|
||||
});
|
||||
};
|
||||
|
||||
console.log(state.dsf);
|
||||
const [ createDecisionSupportFile ] = useCreateDecisionSupportFileMutation();
|
||||
const [ updateDecisionSupportFile ] = useUpdateDecisionSupportFileMutation();
|
||||
|
||||
const saveDSF = () => {
|
||||
const changes = {
|
||||
title: state.dsf.title !== '' ? state.dsf.title : undefined,
|
||||
status: state.dsf.status,
|
||||
workgroupId: state.dsf.workgroup ? state.dsf.workgroup.id : undefined,
|
||||
sections: state.dsf.sections,
|
||||
};
|
||||
|
||||
if (!changes.workgroupId) return;
|
||||
|
||||
if (state.dsf.id === '') {
|
||||
createDecisionSupportFile({
|
||||
variables: { changes },
|
||||
}).then(({ data }) => {
|
||||
history.push(`/decisions/${data.createDecisionSupportFile.id}`);
|
||||
});
|
||||
} else {
|
||||
updateDecisionSupportFile({
|
||||
variables: { changes, id: state.dsf.id },
|
||||
}).then(({ data }) => {
|
||||
setState(state => {
|
||||
return { ...state, saved: true, dsf: { ...state.dsf, ...data.updateDecisionSupportFile } };
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const canSave = !!state.dsf.workgroup && !state.saved;
|
||||
const isNew = state.dsf.id === '';
|
||||
const isClosed = state.dsf.status === DecisionSupportFileStatus.Closed;
|
||||
|
||||
return (
|
||||
<Page title="Dossier d'Aide à la Décision">
|
||||
@ -62,6 +101,14 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<div className="level-item buttons">
|
||||
<button className="button is-medium is-success" disabled={!canSave} onClick={saveDSF}>
|
||||
<span className="icon"><i className="fa fa-save"></i></span>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="columns mt-3">
|
||||
@ -98,7 +145,7 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
|
||||
}
|
||||
</div>
|
||||
<div className="column is-3">
|
||||
<MetadataPanel dsf={state.dsf} />
|
||||
<MetadataPanel dsf={state.dsf} updateDSF={updateDSF} />
|
||||
<AppendixPanel dsf={state.dsf} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,35 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { DecisionSupportFile } from '../../types/decision';
|
||||
import React, { FunctionComponent, useState, useEffect, ChangeEvent } from 'react';
|
||||
import { DecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
|
||||
import { useWorkgroups } from '../../gql/queries/workgroups';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { inWorkgroup } from '../../types/workgroup';
|
||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||
import { asDate } from '../../util/date';
|
||||
|
||||
export interface MetadataPanelProps {
|
||||
dsf: DecisionSupportFile,
|
||||
};
|
||||
export interface MetadataPanelProps extends DecisionSupportFileUpdaterProps {};
|
||||
|
||||
export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf, updateDSF }) => {
|
||||
const { user } = useUserProfile();
|
||||
const { workgroups } = useWorkgroups();
|
||||
|
||||
const [ userOpenedWorkgroups, setUserOpenedWorkgroups ] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = workgroups.filter(wg => !wg.closedAt && inWorkgroup(user, wg));
|
||||
setUserOpenedWorkgroups(filtered);
|
||||
}, [workgroups, user])
|
||||
|
||||
const onStatusChanged = (evt: ChangeEvent<HTMLSelectElement>) => {
|
||||
const status = evt.currentTarget.value as DecisionSupportFileStatus;
|
||||
updateDSF({ ...dsf, status });
|
||||
};
|
||||
|
||||
const onWorkgroupChanged = (evt: ChangeEvent<HTMLSelectElement>) => {
|
||||
const workgroupId = evt.currentTarget.value;
|
||||
const workgroup = workgroups.find(wg => wg.id === workgroupId);
|
||||
updateDSF({ ...dsf, workgroup });
|
||||
};
|
||||
|
||||
export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) => {
|
||||
return (
|
||||
<nav className="panel">
|
||||
<p className="panel-heading">
|
||||
@ -17,8 +41,15 @@ export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) =>
|
||||
<div className="label">Groupe de travail</div>
|
||||
<div className="control is-expanded">
|
||||
<div className="select is-fullwidth">
|
||||
<select>
|
||||
<select onChange={onWorkgroupChanged} value={dsf.workgroup ? dsf.workgroup.id : ''}>
|
||||
<option></option>
|
||||
{
|
||||
userOpenedWorkgroups.map(wg => {
|
||||
return (
|
||||
<option key={`wg-${wg.id}`} value={wg.id}>{wg.name}</option>
|
||||
);
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -27,10 +58,11 @@ export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) =>
|
||||
<div className="label">Statut</div>
|
||||
<div className="control is-expanded">
|
||||
<div className="select is-fullwidth">
|
||||
<select>
|
||||
<option>En préparation</option>
|
||||
<option>Prêt à voter</option>
|
||||
<option>Voté</option>
|
||||
<select onChange={onStatusChanged} value={dsf.status}>
|
||||
<option value="draft">Brouillon</option>
|
||||
<option value="ready">Prêt à voter</option>
|
||||
<option value="voted">Voté</option>
|
||||
<option value="closed">Clôs</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,13 +70,13 @@ export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) =>
|
||||
<div className="field">
|
||||
<div className="label">Créé le</div>
|
||||
<div className="control">
|
||||
<p>--</p>
|
||||
<p>{asDate(dsf.createdAt).toISOString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="label">Dernière modification</div>
|
||||
<div className="label">Voté le</div>
|
||||
<div className="control">
|
||||
<p>--</p>
|
||||
<p>{dsf.votedAt ? dsf.votedAt : '--'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,11 +3,10 @@ import logo from '../resources/logo.svg';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Config } from '../config';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useUserProfileQuery } from '../gql/queries/profile';
|
||||
import { WithLoader } from './WithLoader';
|
||||
import { useLoggedIn } from '../hooks/useLoggedIn';
|
||||
|
||||
export function Navbar() {
|
||||
const userProfileQuery = useUserProfileQuery();
|
||||
const loggedIn = useLoggedIn();
|
||||
const [ isActive, setActive ] = useState(false);
|
||||
|
||||
const toggleMenu = () => {
|
||||
@ -35,30 +34,30 @@ export function Navbar() {
|
||||
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
||||
<div className="navbar-end">
|
||||
<div className="navbar-item">
|
||||
<WithLoader loading={userProfileQuery.loading}>
|
||||
<div className="buttons">
|
||||
<div className="buttons">
|
||||
{
|
||||
userProfileQuery.data && userProfileQuery.data.userProfile ?
|
||||
loggedIn ?
|
||||
<Fragment>
|
||||
<Link to="/profile" className="button">
|
||||
<span className="icon">
|
||||
<i className="fas fa-user"></i>
|
||||
</span>
|
||||
<span>Mon profil</span>
|
||||
</Link>
|
||||
<a className="button" href={Config.logoutURL}>
|
||||
<a className="button is-warning" href={Config.logoutURL}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-out-alt"></i>
|
||||
</span>
|
||||
</a>
|
||||
</Fragment> :
|
||||
<a className="button" href={Config.loginURL}>
|
||||
<a className="button is-primary" href={Config.loginURL}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-in-alt"></i>
|
||||
</span>
|
||||
<span>S'identifier</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</WithLoader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user