Ajout d'un champ permettant de rédiger le rapport de vote #28

Manually merged
wpetit merged 2 commits from feature/vote-report into develop 2020-10-12 13:29:29 +02:00
10 changed files with 133 additions and 10 deletions

View File

@ -7,7 +7,7 @@ export interface DecisioSupportFileLinkProps {
decisionSupportFileId: number
}
export const DecisioSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => {
export const DecisionSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => {
const { decisionSupportFiles } = useDecisionSupportFiles({
fetchPolicy: "cache-first",
variables: {

View File

@ -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>
);
};

View File

@ -10,13 +10,14 @@ import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutat
import { OptionsSection } from './OptionsSection';
import { useIsAuthorized } from '../../gql/queries/authorization';
import { TimelinePanel } from './TimelinePanel';
import { DecisionReportSection } from './DecisionReportSection';
export interface DecisionSupportFilePageProps {
};
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
const { id } = useParams();
const { id } = useParams<any>();
const history = useHistory();
const { decisionSupportFiles } = useDecisionSupportFiles({
variables:{
@ -164,10 +165,15 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
{
state.selectedTabIndex === 2 ?
<DecisionReportSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
</div>
<div className="column is-4">
<MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />
<AppendixPanel dsf={state.dsf} />
{/* <AppendixPanel dsf={state.dsf} /> */}
<TimelinePanel dsf={state.dsf} />
</div>
</div>

View File

@ -3,7 +3,7 @@ import { formatDate } from "../util/date";
import { Event } from "../types/event";
import { Link } from "react-router-dom";
import { WorkgroupLink } from "./WorkgroupLink";
import { DecisioSupportFileLink } from "./DecisionSupportFileLink";
import { DecisionSupportFileLink } from "./DecisionSupportFileLink";
export interface TimelineProps {
events?: Event[]
@ -97,6 +97,11 @@ const eventMarkerMap = {
<i className="fas fa-users-slash"></i>
</div>
),
"voted": (evt:Event) => (
<div className="timeline-marker is-icon is-success">
<i className="fas fa-thumbs-up"></i>
</div>
),
}
function renderEventMarker(evt: Event) {
@ -119,7 +124,7 @@ const eventContentMap = {
return (
<React.Fragment>
<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>
);
},
@ -129,7 +134,7 @@ const eventContentMap = {
return (
<React.Fragment>
<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>
)
}
@ -139,7 +144,7 @@ const eventContentMap = {
return (
<React.Fragment>
<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>
)
}
@ -167,7 +172,7 @@ const eventContentMap = {
return (
<React.Fragment>
<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>
);
},
@ -183,6 +188,14 @@ const eventContentMap = {
},
},
"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) => {
return (
<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 é voté.</span>
</React.Fragment>
);
},
},
};
function renderEventContent(evt: Event) {

View File

@ -172,6 +172,16 @@ func NewDefault() *Config {
{{ end }}
{{- end}}
{{- with .Voted }}
Récemment votés
---------------
{{range . -}}
- "{{ .Title }}" - {{ $root.BaseURL }}/decisions/{{ .ID }} - voté le {{ .VotedAt.Format "02/01/2006" }}
{{ end }}
{{- end}}
{{- with .NewDecisionSupportFiles }}
Nouveaux dossiers d'aide à la décision

View File

@ -72,10 +72,21 @@ func handleUpdateDecisionSupportFile(ctx context.Context, id string, changes *mo
eventRepo := model.NewEventRepository(db)
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 {
return nil, errs.WithStack(err)
}
}
}
if changes != nil && changes.Title != nil && prevDsf.Title != *changes.Title {
if _, err := eventRepo.Add(ctx, user, model.EventTypeTitleChanged, dsf); err != nil {

View File

@ -3,6 +3,7 @@ package model
import (
"context"
"errors"
"time"
"github.com/jinzhu/gorm"
errs "github.com/pkg/errors"
@ -75,6 +76,14 @@ func (r *DSFRepository) updateFromChanges(dsf *DecisionSupportFile, changes *Dec
}
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
}

View File

@ -31,6 +31,10 @@ func (v *DecisionSupportFileVoter) Vote(ctx context.Context, subject interface{}
case ActionRead:
return voter.Allow, nil
case ActionUpdate:
if dsf.Status == StatusClosed || dsf.Status == StatusVoted {
return voter.Deny, nil
}
if inWorkgroup(user, dsf.Workgroup) {
return voter.Allow, nil
}

View File

@ -14,6 +14,7 @@ const (
EventTypeClosed EventType = "closed"
EventTypeStatusChanged EventType = "status-changed"
EventTypeTitleChanged EventType = "title-changed"
EventTypeVoted EventType = "voted"
)
type EventObject interface {

View File

@ -73,6 +73,7 @@ func (t *Newsletter) Run() {
newWorkgroups := make([]*model.Workgroup, 0)
newDecisionSupportFiles := make([]*model.DecisionSupportFile, 0)
readyToVote := make([]*model.DecisionSupportFile, 0)
voted := make([]*model.DecisionSupportFile, 0)
workgroupRepo := model.NewWorkgroupRepository(db)
dsfRepo := model.NewDSFRepository(db)
@ -122,6 +123,10 @@ func (t *Newsletter) Run() {
if dsf.Status == model.StatusReady {
readyToVote = append(readyToVote, dsf)
}
if dsf.Status == model.StatusVoted {
voted = append(voted, dsf)
}
}
}
@ -147,6 +152,7 @@ func (t *Newsletter) Run() {
NewWorkgroups []*model.Workgroup
NewDecisionSupportFiles []*model.DecisionSupportFile
ReadyToVote []*model.DecisionSupportFile
Voted []*model.DecisionSupportFile
BaseURL string
From time.Time
To time.Time
@ -157,6 +163,7 @@ func (t *Newsletter) Run() {
NewWorkgroups: newWorkgroups,
NewDecisionSupportFiles: newDecisionSupportFiles,
ReadyToVote: readyToVote,
Voted: voted,
From: from.Local(),
To: to.Local(),
HasEvents: hasEvents,