Création/mise à jour basique d'un DAD #15
|
@ -3,11 +3,11 @@ import { DecisionSupportFile, DecisionSupportFileStatus } from '../../types/deci
|
||||||
import { ItemPanel, TabDefinition, Item } from './ItemPanel';
|
import { ItemPanel, TabDefinition, Item } from './ItemPanel';
|
||||||
import { useUserProfile } from '../../gql/queries/profile';
|
import { useUserProfile } from '../../gql/queries/profile';
|
||||||
import { inWorkgroup } from '../../types/workgroup';
|
import { inWorkgroup } from '../../types/workgroup';
|
||||||
import { useDecisions } from '../../gql/queries/decisions';
|
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||||
|
|
||||||
export function DecisionSupportFilePanel() {
|
export function DecisionSupportFilePanel() {
|
||||||
const { user } = useUserProfile();
|
const { user } = useUserProfile();
|
||||||
const { decisions } = useDecisions();
|
const { decisionSupportFiles } = useDecisionSupportFiles();
|
||||||
|
|
||||||
const tabs: TabDefinition[] = [
|
const tabs: TabDefinition[] = [
|
||||||
{
|
{
|
||||||
|
@ -31,9 +31,9 @@ export function DecisionSupportFilePanel() {
|
||||||
return (
|
return (
|
||||||
<ItemPanel
|
<ItemPanel
|
||||||
className='is-link'
|
className='is-link'
|
||||||
title="D.A.D."
|
title="Dossiers"
|
||||||
newItemUrl="/decisions/new"
|
newItemUrl="/decisions/new"
|
||||||
items={decisions}
|
items={decisionSupportFiles}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
itemIconClassName='fas fa-folder'
|
itemIconClassName='fas fa-folder'
|
||||||
itemKey={item => item.id}
|
itemKey={item => item.id}
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
|
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
|
||||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||||
|
import { useDebounce } from '../../hooks/useDebounce';
|
||||||
|
import { asDate } from '../../util/date';
|
||||||
|
|
||||||
export interface ClarificationSectionProps extends DecisionSupportFileUpdaterProps {};
|
export interface ClarificationSectionProps extends DecisionSupportFileUpdaterProps {};
|
||||||
|
|
||||||
const ClarificationSectionName = 'clarification';
|
const ClarificationSectionName = 'clarification';
|
||||||
|
|
||||||
export const ClarificationSection: FunctionComponent<ClarificationSectionProps> = ({ dsf, updateDSF }) => {
|
export const ClarificationSection: FunctionComponent<ClarificationSectionProps> = ({ dsf, updateDSF }) => {
|
||||||
const [ section, setSection ] = useState({
|
const [ state, setState ] = useState({
|
||||||
|
changed: false,
|
||||||
|
section: {
|
||||||
objectives: '',
|
objectives: '',
|
||||||
motivations: '',
|
motivations: '',
|
||||||
scope: '',
|
scope: '',
|
||||||
nature: '',
|
nature: '',
|
||||||
deadline: undefined,
|
deadline: undefined,
|
||||||
hasDeadline: false,
|
hasDeadline: false,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateDSF({ ...dsf, sections: { ...dsf.sections, [ClarificationSectionName]: { ...section }} })
|
if (!state.changed) return;
|
||||||
}, [section]);
|
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 onTitleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
const title = (evt.currentTarget).value;
|
const title = (evt.currentTarget).value;
|
||||||
|
@ -27,12 +39,12 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||||
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
const target = evt.currentTarget;
|
const target = evt.currentTarget;
|
||||||
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
|
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 onDeadlineChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
const deadline = evt.currentTarget.valueAsDate;
|
const deadline = evt.currentTarget.valueAsDate;
|
||||||
setSection(section => ({ ...section, deadline }));
|
setState(state => ({ ...state, changed: true, section: { ...state.section, deadline }}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,7 +60,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||||
<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"
|
<textarea className="textarea"
|
||||||
value={section.objectives}
|
value={state.section.objectives}
|
||||||
onChange={onSectionAttrChange.bind(null, 'objectives')}
|
onChange={onSectionAttrChange.bind(null, 'objectives')}
|
||||||
placeholder="Décrire globalement les tenants et aboutissants de la décision à prendre."
|
placeholder="Décrire globalement les tenants et aboutissants de la décision à prendre."
|
||||||
rows={10}>
|
rows={10}>
|
||||||
|
@ -60,7 +72,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||||
<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"
|
<textarea className="textarea"
|
||||||
value={section.motivations}
|
value={state.section.motivations}
|
||||||
onChange={onSectionAttrChange.bind(null, 'motivations')}
|
onChange={onSectionAttrChange.bind(null, 'motivations')}
|
||||||
placeholder="Décrire pourquoi il est important de prendre cette décision."
|
placeholder="Décrire pourquoi il est important de prendre cette décision."
|
||||||
rows={10}>
|
rows={10}>
|
||||||
|
@ -74,7 +86,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||||
<div className="select">
|
<div className="select">
|
||||||
<select
|
<select
|
||||||
onChange={onSectionAttrChange.bind(null, 'scope')}
|
onChange={onSectionAttrChange.bind(null, 'scope')}
|
||||||
value={section.scope}>
|
value={state.section.scope}>
|
||||||
<option></option>
|
<option></option>
|
||||||
<option value="individual">Individuelle</option>
|
<option value="individual">Individuelle</option>
|
||||||
<option value="identified-group">Groupe identifié</option>
|
<option value="identified-group">Groupe identifié</option>
|
||||||
|
@ -88,7 +100,7 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<div className="select">
|
<div className="select">
|
||||||
<select onChange={onSectionAttrChange.bind(null, 'nature')}
|
<select onChange={onSectionAttrChange.bind(null, 'nature')}
|
||||||
value={section.nature}>
|
value={state.section.nature}>
|
||||||
<option></option>
|
<option></option>
|
||||||
<option value="operational">Opérationnelle</option>
|
<option value="operational">Opérationnelle</option>
|
||||||
<option value="tactic">Tactique</option>
|
<option value="tactic">Tactique</option>
|
||||||
|
@ -103,13 +115,13 @@ export const ClarificationSection: FunctionComponent<ClarificationSectionProps>
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
onChange={onSectionAttrChange.bind(null, 'hasDeadline')}
|
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>
|
<span className="ml-1">Existe t'il une échéance particulière pour cette décision ?</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<input disabled={!section.hasDeadline}
|
<input disabled={!state.section.hasDeadline}
|
||||||
value={section.deadline ? section.deadline.toISOString().substr(0, 10) : ''}
|
value={state.section.deadline ? asDate(state.section.deadline).toISOString().substr(0, 10) : ''}
|
||||||
onChange={onDeadlineChange}
|
onChange={onDeadlineChange}
|
||||||
type="date" className="input" />
|
type="date" className="input" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import React, { FunctionComponent, useState } from 'react';
|
import React, { FunctionComponent, useState, useEffect } from 'react';
|
||||||
import { Page } from '../Page';
|
import { Page } from '../Page';
|
||||||
import { ClarificationSection } from './ClarificationSection';
|
import { ClarificationSection } from './ClarificationSection';
|
||||||
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 { DecisionSupportFile, newDecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
|
||||||
import { useParams } from 'react-router';
|
import { useParams, useHistory } from 'react-router';
|
||||||
import { useDecisions } from '../../gql/queries/decisions';
|
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||||
|
import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutation } from '../../gql/mutations/dsf';
|
||||||
|
import { useDebounce } from '../../hooks/useDebounce';
|
||||||
|
|
||||||
export interface DecisionSupportFilePageProps {
|
export interface DecisionSupportFilePageProps {
|
||||||
|
|
||||||
|
@ -14,31 +15,69 @@ export interface DecisionSupportFilePageProps {
|
||||||
|
|
||||||
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
|
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { decisions } = useDecisions({
|
const history = useHistory();
|
||||||
|
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||||
variables:{
|
variables:{
|
||||||
filter: {
|
filter: {
|
||||||
ids: [id],
|
ids: id !== 'new' ? [id] : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [ state, setState ] = useState({
|
const [ state, setState ] = useState({
|
||||||
dsf: decisions.length > 0 ? decisions[0] : newDecisionSupportFile(),
|
dsf: newDecisionSupportFile(),
|
||||||
selectedTabIndex: 0
|
saved: true,
|
||||||
|
selectedTabIndex: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isNew = state.dsf.id === '';
|
useEffect(() => {
|
||||||
const isClosed = state.dsf.status === DecisionSupportFileStatus.Closed;
|
const dsf = decisionSupportFiles.length > 0 && decisionSupportFiles[0].id === id ? decisionSupportFiles[0] : {};
|
||||||
|
setState(state => ({ ...state, dsf: { ...state.dsf, ...dsf }}))
|
||||||
|
}, [ decisionSupportFiles ]);
|
||||||
|
|
||||||
const selectTab = (tabIndex: number) => {
|
const selectTab = (tabIndex: number) => {
|
||||||
setState(state => ({ ...state, selectedTabIndex: tabIndex }));
|
setState(state => ({ ...state, selectedTabIndex: tabIndex }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDSF = (dsf: DecisionSupportFile) => {
|
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 (
|
return (
|
||||||
<Page title="Dossier d'Aide à la Décision">
|
<Page title="Dossier d'Aide à la Décision">
|
||||||
|
@ -62,6 +101,14 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div className="columns mt-3">
|
<div className="columns mt-3">
|
||||||
|
@ -98,7 +145,7 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-3">
|
<div className="column is-3">
|
||||||
<MetadataPanel dsf={state.dsf} />
|
<MetadataPanel dsf={state.dsf} updateDSF={updateDSF} />
|
||||||
<AppendixPanel dsf={state.dsf} />
|
<AppendixPanel dsf={state.dsf} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,35 @@
|
||||||
import React, { FunctionComponent, useState } from 'react';
|
import React, { FunctionComponent, useState, useEffect, ChangeEvent } from 'react';
|
||||||
import { DecisionSupportFile } from '../../types/decision';
|
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 {
|
export interface MetadataPanelProps extends DecisionSupportFileUpdaterProps {};
|
||||||
dsf: DecisionSupportFile,
|
|
||||||
|
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 (
|
return (
|
||||||
<nav className="panel">
|
<nav className="panel">
|
||||||
<p className="panel-heading">
|
<p className="panel-heading">
|
||||||
|
@ -17,8 +41,15 @@ export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) =>
|
||||||
<div className="label">Groupe de travail</div>
|
<div className="label">Groupe de travail</div>
|
||||||
<div className="control is-expanded">
|
<div className="control is-expanded">
|
||||||
<div className="select is-fullwidth">
|
<div className="select is-fullwidth">
|
||||||
<select>
|
<select onChange={onWorkgroupChanged} value={dsf.workgroup ? dsf.workgroup.id : ''}>
|
||||||
<option></option>
|
<option></option>
|
||||||
|
{
|
||||||
|
userOpenedWorkgroups.map(wg => {
|
||||||
|
return (
|
||||||
|
<option key={`wg-${wg.id}`} value={wg.id}>{wg.name}</option>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,10 +58,11 @@ export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) =>
|
||||||
<div className="label">Statut</div>
|
<div className="label">Statut</div>
|
||||||
<div className="control is-expanded">
|
<div className="control is-expanded">
|
||||||
<div className="select is-fullwidth">
|
<div className="select is-fullwidth">
|
||||||
<select>
|
<select onChange={onStatusChanged} value={dsf.status}>
|
||||||
<option>En préparation</option>
|
<option value="draft">Brouillon</option>
|
||||||
<option>Prêt à voter</option>
|
<option value="ready">Prêt à voter</option>
|
||||||
<option>Voté</option>
|
<option value="voted">Voté</option>
|
||||||
|
<option value="closed">Clôs</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,13 +70,13 @@ export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf }) =>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="label">Créé le</div>
|
<div className="label">Créé le</div>
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<p>--</p>
|
<p>{asDate(dsf.createdAt).toISOString()}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<div className="label">Dernière modification</div>
|
<div className="label">Voté le</div>
|
||||||
<div className="control">
|
<div className="control">
|
||||||
<p>--</p>
|
<p>{dsf.votedAt ? dsf.votedAt : '--'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,10 @@ import logo from '../resources/logo.svg';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Config } from '../config';
|
import { Config } from '../config';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useUserProfileQuery } from '../gql/queries/profile';
|
import { useLoggedIn } from '../hooks/useLoggedIn';
|
||||||
import { WithLoader } from './WithLoader';
|
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const userProfileQuery = useUserProfileQuery();
|
const loggedIn = useLoggedIn();
|
||||||
const [ isActive, setActive ] = useState(false);
|
const [ isActive, setActive ] = useState(false);
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
|
@ -35,30 +34,30 @@ export function Navbar() {
|
||||||
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
||||||
<div className="navbar-end">
|
<div className="navbar-end">
|
||||||
<div className="navbar-item">
|
<div className="navbar-item">
|
||||||
<WithLoader loading={userProfileQuery.loading}>
|
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
{
|
{
|
||||||
userProfileQuery.data && userProfileQuery.data.userProfile ?
|
loggedIn ?
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Link to="/profile" className="button">
|
<Link to="/profile" className="button">
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<i className="fas fa-user"></i>
|
<i className="fas fa-user"></i>
|
||||||
</span>
|
</span>
|
||||||
|
<span>Mon profil</span>
|
||||||
</Link>
|
</Link>
|
||||||
<a className="button" href={Config.logoutURL}>
|
<a className="button is-warning" href={Config.logoutURL}>
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<i className="fas fa-sign-out-alt"></i>
|
<i className="fas fa-sign-out-alt"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</Fragment> :
|
</Fragment> :
|
||||||
<a className="button" href={Config.loginURL}>
|
<a className="button is-primary" href={Config.loginURL}>
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<i className="fas fa-sign-in-alt"></i>
|
<i className="fas fa-sign-in-alt"></i>
|
||||||
</span>
|
</span>
|
||||||
|
<span>S'identifier</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</WithLoader>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Config } from '../config';
|
||||||
import { WebSocketLink } from "@apollo/client/link/ws";
|
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||||
import { RetryLink } from "@apollo/client/link/retry";
|
import { RetryLink } from "@apollo/client/link/retry";
|
||||||
import { SubscriptionClient } from "subscriptions-transport-ws";
|
import { SubscriptionClient } from "subscriptions-transport-ws";
|
||||||
|
import { User } from '../types/user';
|
||||||
|
|
||||||
const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, {
|
const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, {
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
|
@ -14,7 +15,42 @@ const link = new RetryLink({attempts: {max: 2}}).split(
|
||||||
new HttpLink({ uri: Config.graphQLEndpoint, credentials: 'include' })
|
new HttpLink({ uri: Config.graphQLEndpoint, credentials: 'include' })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cache = new InMemoryCache({
|
||||||
|
typePolicies: {
|
||||||
|
Workgroup: {
|
||||||
|
fields: {
|
||||||
|
members: {
|
||||||
|
merge: mergeArrayByField<User>("id"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const client = new ApolloClient<any>({
|
export const client = new ApolloClient<any>({
|
||||||
cache: new InMemoryCache(),
|
cache: cache,
|
||||||
link: link,
|
link: link,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mergeArrayByField<T>(fieldName: string) {
|
||||||
|
return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => {
|
||||||
|
const merged: any[] = existing ? existing.slice(0) : [];
|
||||||
|
const objectFieldToIndex: Record<string, number> = Object.create(null);
|
||||||
|
if (existing) {
|
||||||
|
existing.forEach((obj, index) => {
|
||||||
|
objectFieldToIndex[readField(fieldName, obj)] = index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
incoming.forEach(obj => {
|
||||||
|
const field = readField(fieldName, obj);
|
||||||
|
const index = objectFieldToIndex[field];
|
||||||
|
if (typeof index === "number") {
|
||||||
|
merged[index] = mergeObjects(merged[index], obj);
|
||||||
|
} else {
|
||||||
|
objectFieldToIndex[name] = merged.length;
|
||||||
|
merged.push(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { gql, useQuery, useMutation } from '@apollo/client';
|
||||||
|
import { QUERY_DECISION_SUPPORT_FILES } from '../queries/dsf';
|
||||||
|
|
||||||
|
export const MUTATION_CREATE_DECISION_SUPPORT_FILE = gql`
|
||||||
|
mutation createDecisionSupportFile($changes: DecisionSupportFileChanges!) {
|
||||||
|
createDecisionSupportFile(changes: $changes) {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
status,
|
||||||
|
sections,
|
||||||
|
createdAt,
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export function useCreateDecisionSupportFileMutation() {
|
||||||
|
return useMutation(MUTATION_CREATE_DECISION_SUPPORT_FILE, {
|
||||||
|
refetchQueries: [{query: QUERY_DECISION_SUPPORT_FILES}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MUTATION_UPDATE_DECISION_SUPPORT_FILE = gql`
|
||||||
|
mutation updateDecisionSupportFile($id: ID!, $changes: DecisionSupportFileChanges!) {
|
||||||
|
updateDecisionSupportFile(id: $id, changes: $changes) {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
status,
|
||||||
|
sections,
|
||||||
|
createdAt,
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export function useUpdateDecisionSupportFileMutation() {
|
||||||
|
return useMutation(MUTATION_UPDATE_DECISION_SUPPORT_FILE, {
|
||||||
|
refetchQueries: [{
|
||||||
|
query: QUERY_DECISION_SUPPORT_FILES,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
import { gql, useQuery } from '@apollo/client';
|
|
||||||
import { DecisionSupportFile } from '../../types/decision';
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useGraphQLData } from './helper';
|
|
||||||
|
|
||||||
export const QUERY_DECISIONS = gql`
|
|
||||||
query decisions($filter: DecisionFilter) {
|
|
||||||
decisions(filter: $filter) {
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
sections
|
|
||||||
createdAt,
|
|
||||||
closedAt,
|
|
||||||
votedAt,
|
|
||||||
workgroup {
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function useDecisionsQuery(options = {}) {
|
|
||||||
return useQuery(QUERY_DECISIONS, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDecisions(options = {}) {
|
|
||||||
const { data, loading, error } = useGraphQLData<DecisionSupportFile[]>(
|
|
||||||
QUERY_DECISIONS, 'decisions', [], options
|
|
||||||
);
|
|
||||||
return { decisions: data, loading, error };
|
|
||||||
}
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import { DecisionSupportFile } from '../../types/decision';
|
||||||
|
import { useGraphQLData } from './helper';
|
||||||
|
|
||||||
|
export const QUERY_DECISION_SUPPORT_FILES = gql`
|
||||||
|
query decisionSupportFiles($filter: DecisionSupportFileFilter) {
|
||||||
|
decisionSupportFiles(filter: $filter) {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
sections
|
||||||
|
createdAt,
|
||||||
|
closedAt,
|
||||||
|
votedAt,
|
||||||
|
status,
|
||||||
|
workgroup {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
members {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useDecisionSupportFilesQuery(options = {}) {
|
||||||
|
return useQuery(QUERY_DECISION_SUPPORT_FILES, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDecisionSupportFiles(options = {}) {
|
||||||
|
const { data, loading, error } = useGraphQLData<DecisionSupportFile[]>(
|
||||||
|
QUERY_DECISION_SUPPORT_FILES, 'decisionSupportFiles', [], options
|
||||||
|
);
|
||||||
|
return { decisionSupportFiles: data, loading, error };
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useDebounce(value, delay) {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[value, delay]
|
||||||
|
);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export function asDate(d: string|Date): Date {
|
||||||
|
if (typeof d === 'string') return new Date(d);
|
||||||
|
return d;
|
||||||
|
}
|
|
@ -80,6 +80,7 @@ func applyMigration(ctx context.Context, ctn *service.Container) error {
|
||||||
var initialModels = []interface{}{
|
var initialModels = []interface{}{
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Workgroup{},
|
&model.Workgroup{},
|
||||||
|
&model.DecisionSupportFile{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func m000initialSchema() orm.Migration {
|
func m000initialSchema() orm.Migration {
|
||||||
|
@ -95,8 +96,8 @@ func m000initialSchema() orm.Migration {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(ctx context.Context, tx *gorm.DB) error {
|
func(ctx context.Context, tx *gorm.DB) error {
|
||||||
for _, m := range initialModels {
|
for i := len(initialModels) - 1; i >= 0; i-- {
|
||||||
if err := tx.DropTableIfExists(m).Error; err != nil {
|
if err := tx.DropTableIfExists(initialModels[i]).Error; err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||||
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
|
errs "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleCreateDecisionSupportFile(ctx context.Context, changes *model.DecisionSupportFileChanges) (*model.DecisionSupportFile, error) {
|
||||||
|
ctn := container.Must(ctx)
|
||||||
|
db := orm.Must(ctn).DB()
|
||||||
|
|
||||||
|
repo := model.NewDSFRepository(db)
|
||||||
|
|
||||||
|
dsf, err := repo.Create(ctx, changes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dsf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpdateDecisionSupportFile(ctx context.Context, id string, changes *model.DecisionSupportFileChanges) (*model.DecisionSupportFile, error) {
|
||||||
|
ctn := container.Must(ctx)
|
||||||
|
db := orm.Must(ctn).DB()
|
||||||
|
|
||||||
|
repo := model.NewDSFRepository(db)
|
||||||
|
|
||||||
|
dsf, err := repo.Update(ctx, id, changes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dsf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDecisionSupportFiles(ctx context.Context, filter *model.DecisionSupportFileFilter) ([]*model.DecisionSupportFile, error) {
|
||||||
|
ctn := container.Must(ctx)
|
||||||
|
db := orm.Must(ctn).DB()
|
||||||
|
|
||||||
|
repo := model.NewDSFRepository(db)
|
||||||
|
|
||||||
|
return repo.Search(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSections(ctx context.Context, dsf *model.DecisionSupportFile) (map[string]interface{}, error) {
|
||||||
|
sections := make(map[string]interface{})
|
||||||
|
|
||||||
|
if err := json.Unmarshal(dsf.Sections.RawMessage, §ions); err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections, nil
|
||||||
|
}
|
|
@ -6,8 +6,21 @@ input WorkgroupChanges {
|
||||||
name: String
|
name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input DecisionSupportFileChanges {
|
||||||
|
title: String
|
||||||
|
sections: Map
|
||||||
|
status: String
|
||||||
|
workgroupId: ID
|
||||||
|
votedAt: Time
|
||||||
|
closedAt: Time
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
createDecisionSupportFile(changes: DecisionSupportFileChanges): DecisionSupportFile!
|
||||||
|
updateDecisionSupportFile(id: ID!, changes: DecisionSupportFileChanges): DecisionSupportFile!
|
||||||
|
|
||||||
updateProfile(changes: ProfileChanges!): User!
|
updateProfile(changes: ProfileChanges!): User!
|
||||||
|
|
||||||
joinWorkgroup(workgroupId: ID!): Workgroup!
|
joinWorkgroup(workgroupId: ID!): Workgroup!
|
||||||
leaveWorkgroup(workgroupId: ID!): Workgroup!
|
leaveWorkgroup(workgroupId: ID!): Workgroup!
|
||||||
createWorkgroup(changes: WorkgroupChanges!): Workgroup!
|
createWorkgroup(changes: WorkgroupChanges!): Workgroup!
|
||||||
|
|
|
@ -10,6 +10,14 @@ import (
|
||||||
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *mutationResolver) CreateDecisionSupportFile(ctx context.Context, changes *model.DecisionSupportFileChanges) (*model.DecisionSupportFile, error) {
|
||||||
|
return handleCreateDecisionSupportFile(ctx, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) UpdateDecisionSupportFile(ctx context.Context, id string, changes *model.DecisionSupportFileChanges) (*model.DecisionSupportFile, error) {
|
||||||
|
return handleUpdateDecisionSupportFile(ctx, id, changes)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) UpdateProfile(ctx context.Context, changes model.ProfileChanges) (*model.User, error) {
|
func (r *mutationResolver) UpdateProfile(ctx context.Context, changes model.ProfileChanges) (*model.User, error) {
|
||||||
return handleUpdateUserProfile(ctx, changes)
|
return handleUpdateUserProfile(ctx, changes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
scalar Time
|
scalar Time
|
||||||
|
scalar Map
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
@ -21,7 +22,24 @@ input WorkgroupsFilter {
|
||||||
ids: [ID]
|
ids: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DecisionSupportFile {
|
||||||
|
id: ID!
|
||||||
|
title: String
|
||||||
|
sections: Map
|
||||||
|
status: String
|
||||||
|
workgroup: Workgroup
|
||||||
|
createdAt: Time
|
||||||
|
updatedAt: Time
|
||||||
|
votedAt: Time
|
||||||
|
closedAt: Time
|
||||||
|
}
|
||||||
|
|
||||||
|
input DecisionSupportFileFilter {
|
||||||
|
ids: [ID]
|
||||||
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
userProfile: User
|
userProfile: User
|
||||||
workgroups(filter: WorkgroupsFilter): [Workgroup]!
|
workgroups(filter: WorkgroupsFilter): [Workgroup]!
|
||||||
|
decisionSupportFiles(filter: DecisionSupportFileFilter): [DecisionSupportFile]!
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,14 @@ import (
|
||||||
model1 "forge.cadoles.com/Cadoles/daddy/internal/model"
|
model1 "forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *decisionSupportFileResolver) ID(ctx context.Context, obj *model1.DecisionSupportFile) (string, error) {
|
||||||
|
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *decisionSupportFileResolver) Sections(ctx context.Context, obj *model1.DecisionSupportFile) (map[string]interface{}, error) {
|
||||||
|
return handleSections(ctx, obj)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) UserProfile(ctx context.Context) (*model1.User, error) {
|
func (r *queryResolver) UserProfile(ctx context.Context) (*model1.User, error) {
|
||||||
return handleUserProfile(ctx)
|
return handleUserProfile(ctx)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +27,10 @@ func (r *queryResolver) Workgroups(ctx context.Context, filter *model1.Workgroup
|
||||||
return handleWorkgroups(ctx, filter)
|
return handleWorkgroups(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) DecisionSupportFiles(ctx context.Context, filter *model1.DecisionSupportFileFilter) ([]*model1.DecisionSupportFile, error) {
|
||||||
|
return handleDecisionSupportFiles(ctx, filter)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *userResolver) ID(ctx context.Context, obj *model1.User) (string, error) {
|
func (r *userResolver) ID(ctx context.Context, obj *model1.User) (string, error) {
|
||||||
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
||||||
}
|
}
|
||||||
|
@ -27,6 +39,11 @@ func (r *workgroupResolver) ID(ctx context.Context, obj *model1.Workgroup) (stri
|
||||||
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
return strconv.FormatUint(uint64(obj.ID), 10), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecisionSupportFile returns generated.DecisionSupportFileResolver implementation.
|
||||||
|
func (r *Resolver) DecisionSupportFile() generated.DecisionSupportFileResolver {
|
||||||
|
return &decisionSupportFileResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
// Query returns generated.QueryResolver implementation.
|
// Query returns generated.QueryResolver implementation.
|
||||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
|
@ -36,6 +53,7 @@ func (r *Resolver) User() generated.UserResolver { return &userResolver{r} }
|
||||||
// Workgroup returns generated.WorkgroupResolver implementation.
|
// Workgroup returns generated.WorkgroupResolver implementation.
|
||||||
func (r *Resolver) Workgroup() generated.WorkgroupResolver { return &workgroupResolver{r} }
|
func (r *Resolver) Workgroup() generated.WorkgroupResolver { return &workgroupResolver{r} }
|
||||||
|
|
||||||
|
type decisionSupportFileResolver struct{ *Resolver }
|
||||||
type queryResolver struct{ *Resolver }
|
type queryResolver struct{ *Resolver }
|
||||||
type userResolver struct{ *Resolver }
|
type userResolver struct{ *Resolver }
|
||||||
type workgroupResolver struct{ *Resolver }
|
type workgroupResolver struct{ *Resolver }
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DecisionSupportFile struct {
|
||||||
|
gorm.Model
|
||||||
|
Title string `json:"title"`
|
||||||
|
Sections postgres.Jsonb `json:"sections"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
WorkgroupID uint `json:"-"`
|
||||||
|
Workgroup *Workgroup `json:"workgroup"`
|
||||||
|
VotedAt time.Time `json:"votedAt"`
|
||||||
|
ClosedAt time.Time `json:"closedAt"`
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
errs "github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrMissingWorkgroup = errs.New("missing workgroup")
|
||||||
|
ErrInvalidWorkgroup = errs.New("invalid workgroup")
|
||||||
|
ErrDecisionSupportFileDoesNotExist = errs.New("decision support file does not exist")
|
||||||
|
)
|
||||||
|
|
||||||
|
type DSFRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DSFRepository) Create(ctx context.Context, changes *DecisionSupportFileChanges) (*DecisionSupportFile, error) {
|
||||||
|
dsf := &DecisionSupportFile{}
|
||||||
|
|
||||||
|
if err := r.updateFromChanges(dsf, changes); err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.db.Save(&dsf).Error; err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dsf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DSFRepository) Update(ctx context.Context, id string, changes *DecisionSupportFileChanges) (*DecisionSupportFile, error) {
|
||||||
|
dsf := &DecisionSupportFile{}
|
||||||
|
|
||||||
|
if err := r.db.Find(dsf, "id = ?", id).Error; err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.updateFromChanges(dsf, changes); err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.db.Save(dsf).Error; err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dsf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DSFRepository) updateFromChanges(dsf *DecisionSupportFile, changes *DecisionSupportFileChanges) error {
|
||||||
|
if changes.WorkgroupID == nil {
|
||||||
|
return errs.WithStack(ErrMissingWorkgroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &Workgroup{}
|
||||||
|
if err := r.db.Model(wg).First(wg, "id = ?", changes.WorkgroupID).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errs.WithStack(ErrInvalidWorkgroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dsf.Workgroup = wg
|
||||||
|
|
||||||
|
if changes.Sections != nil {
|
||||||
|
rawSections, err := json.Marshal(changes.Sections)
|
||||||
|
if err != nil {
|
||||||
|
return errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dsf.Sections = postgres.Jsonb{RawMessage: rawSections}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changes.Title != nil {
|
||||||
|
dsf.Title = *changes.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
if changes.Status != nil {
|
||||||
|
dsf.Status = *changes.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DSFRepository) Search(ctx context.Context, filter *DecisionSupportFileFilter) ([]*DecisionSupportFile, error) {
|
||||||
|
query := r.db.Model(&DecisionSupportFile{}).Preload("Workgroup")
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
if filter.Ids != nil {
|
||||||
|
query = query.Where("id in (?)", filter.Ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsfs := make([]*DecisionSupportFile, 0)
|
||||||
|
|
||||||
|
if err := query.Find(&dsfs).Error; err != nil {
|
||||||
|
return nil, errs.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dsfs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDSFRepository(db *gorm.DB) *DSFRepository {
|
||||||
|
return &DSFRepository{db}
|
||||||
|
}
|
Loading…
Reference in New Issue