Ajout d'un champ permettant de rédiger le rapport de vote #28
|
@ -7,7 +7,7 @@ export interface DecisioSupportFileLinkProps {
|
||||||
decisionSupportFileId: number
|
decisionSupportFileId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DecisioSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => {
|
export const DecisionSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => {
|
||||||
const { decisionSupportFiles } = useDecisionSupportFiles({
|
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||||
fetchPolicy: "cache-first",
|
fetchPolicy: "cache-first",
|
||||||
variables: {
|
variables: {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
|
||||||
|
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||||
|
|
||||||
|
export interface DecisionReportSectionProps extends DecisionSupportFileUpdaterProps {};
|
||||||
|
|
||||||
|
const DecisionReportSectionName = 'decision-report';
|
||||||
|
|
||||||
|
export const DecisionReportSection: FunctionComponent<DecisionReportSectionProps> = ({ dsf, updateDSF, readOnly }) => {
|
||||||
|
const [ state, setState ] = useState({
|
||||||
|
changed: false,
|
||||||
|
section: {
|
||||||
|
report: "",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!state.changed) return;
|
||||||
|
updateDSF({ ...dsf, sections: { ...dsf.sections, [DecisionReportSectionName]: { ...state.section }} })
|
||||||
|
setState(state => ({ ...state, changed: false }));
|
||||||
|
}, [state.changed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dsf.sections[DecisionReportSectionName]) return;
|
||||||
|
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[DecisionReportSectionName] }}));
|
||||||
|
}, [dsf.sections[DecisionReportSectionName]]);
|
||||||
|
|
||||||
|
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const target = evt.currentTarget;
|
||||||
|
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
|
||||||
|
setState(state => ({ ...state, changed: true, section: {...state.section, [attrName]: value }}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div className="box">
|
||||||
|
<div className="field">
|
||||||
|
<label className="label is-medium">Compte rendu du vote</label>
|
||||||
|
<div className="control">
|
||||||
|
<textarea className="textarea is-medium"
|
||||||
|
readOnly={readOnly}
|
||||||
|
value={state.section.report}
|
||||||
|
onChange={onSectionAttrChange.bind(null, 'report')}
|
||||||
|
rows={20}>
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
<p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer le résultat du vote et les éléments de contexte liés à la prise de décision.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
|
@ -10,13 +10,14 @@ import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutat
|
||||||
import { OptionsSection } from './OptionsSection';
|
import { OptionsSection } from './OptionsSection';
|
||||||
import { useIsAuthorized } from '../../gql/queries/authorization';
|
import { useIsAuthorized } from '../../gql/queries/authorization';
|
||||||
import { TimelinePanel } from './TimelinePanel';
|
import { TimelinePanel } from './TimelinePanel';
|
||||||
|
import { DecisionReportSection } from './DecisionReportSection';
|
||||||
|
|
||||||
export interface DecisionSupportFilePageProps {
|
export interface DecisionSupportFilePageProps {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
|
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams<any>();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { decisionSupportFiles } = useDecisionSupportFiles({
|
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||||
variables:{
|
variables:{
|
||||||
|
@ -164,10 +165,15 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
|
||||||
<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
|
<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
state.selectedTabIndex === 2 ?
|
||||||
|
<DecisionReportSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-4">
|
<div className="column is-4">
|
||||||
<MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />
|
<MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />
|
||||||
<AppendixPanel dsf={state.dsf} />
|
{/* <AppendixPanel dsf={state.dsf} /> */}
|
||||||
<TimelinePanel dsf={state.dsf} />
|
<TimelinePanel dsf={state.dsf} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { formatDate } from "../util/date";
|
||||||
import { Event } from "../types/event";
|
import { Event } from "../types/event";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { WorkgroupLink } from "./WorkgroupLink";
|
import { WorkgroupLink } from "./WorkgroupLink";
|
||||||
import { DecisioSupportFileLink } from "./DecisionSupportFileLink";
|
import { DecisionSupportFileLink } from "./DecisionSupportFileLink";
|
||||||
|
|
||||||
export interface TimelineProps {
|
export interface TimelineProps {
|
||||||
events?: Event[]
|
events?: Event[]
|
||||||
|
@ -97,6 +97,11 @@ const eventMarkerMap = {
|
||||||
<i className="fas fa-users-slash"></i>
|
<i className="fas fa-users-slash"></i>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
"voted": (evt:Event) => (
|
||||||
|
<div className="timeline-marker is-icon is-success">
|
||||||
|
<i className="fas fa-thumbs-up"></i>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEventMarker(evt: Event) {
|
function renderEventMarker(evt: Event) {
|
||||||
|
@ -119,7 +124,7 @@ const eventContentMap = {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a créé le dossier d'aide à la décision `}</span>
|
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a créé le dossier d'aide à la décision `}</span>
|
||||||
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />".
|
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -129,7 +134,7 @@ const eventContentMap = {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le titre du dossier d'aide à la décision `}</span>
|
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le titre du dossier d'aide à la décision `}</span>
|
||||||
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />".
|
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -139,7 +144,7 @@ const eventContentMap = {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le statut du dossier d'aide à la décision `}</span>
|
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le statut du dossier d'aide à la décision `}</span>
|
||||||
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />".
|
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -167,7 +172,7 @@ const eventContentMap = {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le dossier d'aide à la décision `}</span>
|
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le dossier d'aide à la décision `}</span>
|
||||||
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />".
|
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -183,6 +188,14 @@ const eventContentMap = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"closed": {
|
"closed": {
|
||||||
|
"dsf": (evt:Event) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a clos le dossier d'aide à la décision `}</span>
|
||||||
|
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
},
|
||||||
"workgroup": (evt:Event) => {
|
"workgroup": (evt:Event) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -192,6 +205,17 @@ const eventContentMap = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"voted": {
|
||||||
|
"dsf": (evt:Event) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<span>{`Le dossier d'aide à la décision `}</span>
|
||||||
|
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />"
|
||||||
|
<span> a été voté.</span>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderEventContent(evt: Event) {
|
function renderEventContent(evt: Event) {
|
||||||
|
|
|
@ -72,10 +72,21 @@ func handleUpdateDecisionSupportFile(ctx context.Context, id string, changes *mo
|
||||||
eventRepo := model.NewEventRepository(db)
|
eventRepo := model.NewEventRepository(db)
|
||||||
|
|
||||||
if changes != nil && changes.Status != nil && prevDsf.Status != *changes.Status {
|
if changes != nil && changes.Status != nil && prevDsf.Status != *changes.Status {
|
||||||
|
switch *changes.Status {
|
||||||
|
case model.StatusVoted:
|
||||||
|
if _, err := eventRepo.Add(ctx, user, model.EventTypeVoted, dsf); err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
case model.StatusClosed:
|
||||||
|
if _, err := eventRepo.Add(ctx, user, model.EventTypeClosed, dsf); err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
if _, err := eventRepo.Add(ctx, user, model.EventTypeStatusChanged, dsf); err != nil {
|
if _, err := eventRepo.Add(ctx, user, model.EventTypeStatusChanged, dsf); err != nil {
|
||||||
return nil, errs.WithStack(err)
|
return nil, errs.WithStack(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if changes != nil && changes.Title != nil && prevDsf.Title != *changes.Title {
|
if changes != nil && changes.Title != nil && prevDsf.Title != *changes.Title {
|
||||||
if _, err := eventRepo.Add(ctx, user, model.EventTypeTitleChanged, dsf); err != nil {
|
if _, err := eventRepo.Add(ctx, user, model.EventTypeTitleChanged, dsf); err != nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package model
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
errs "github.com/pkg/errors"
|
errs "github.com/pkg/errors"
|
||||||
|
@ -75,6 +76,14 @@ func (r *DSFRepository) updateFromChanges(dsf *DecisionSupportFile, changes *Dec
|
||||||
}
|
}
|
||||||
|
|
||||||
if changes.Status != nil {
|
if changes.Status != nil {
|
||||||
|
if dsf.Status != StatusVoted && *changes.Status == StatusVoted {
|
||||||
|
dsf.VotedAt = time.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if *changes.Status == StatusClosed {
|
||||||
|
dsf.VotedAt = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
dsf.Status = *changes.Status
|
dsf.Status = *changes.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,10 @@ func (v *DecisionSupportFileVoter) Vote(ctx context.Context, subject interface{}
|
||||||
case ActionRead:
|
case ActionRead:
|
||||||
return voter.Allow, nil
|
return voter.Allow, nil
|
||||||
case ActionUpdate:
|
case ActionUpdate:
|
||||||
|
if dsf.Status == StatusClosed || dsf.Status == StatusVoted {
|
||||||
|
return voter.Deny, nil
|
||||||
|
}
|
||||||
|
|
||||||
if inWorkgroup(user, dsf.Workgroup) {
|
if inWorkgroup(user, dsf.Workgroup) {
|
||||||
return voter.Allow, nil
|
return voter.Allow, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ const (
|
||||||
EventTypeClosed EventType = "closed"
|
EventTypeClosed EventType = "closed"
|
||||||
EventTypeStatusChanged EventType = "status-changed"
|
EventTypeStatusChanged EventType = "status-changed"
|
||||||
EventTypeTitleChanged EventType = "title-changed"
|
EventTypeTitleChanged EventType = "title-changed"
|
||||||
|
EventTypeVoted EventType = "voted"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EventObject interface {
|
type EventObject interface {
|
||||||
|
|
Loading…
Reference in New Issue