Skip to content

Commit

Permalink
feat: historique pour les actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud AMBROSELLI committed Sep 8, 2023
1 parent 6a9599f commit 723cb1e
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 169 deletions.
13 changes: 13 additions & 0 deletions app/src/recoil/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ const encryptedFields = [
'history',
];

export const allowedActionFieldsInHistory = [
{ name: 'categories', label: 'Catégorie(s)' },
{ name: 'person', label: 'Personne suivie' },
{ name: 'group', label: 'Action familiale' },
{ name: 'name', label: "Nom de l'action" },
{ name: 'description', label: 'Description' },
{ name: 'teams', label: 'Équipe(s) en charge' },
{ name: 'urgent', label: 'Action urgente' },
{ name: 'completedAt', label: 'Faite le' },
{ name: 'dueAt', label: 'À faire le' },
{ name: 'status', label: 'Status' },
];

export const prepareActionForEncryption = (action) => {
try {
if (!looseUuidRegex.test(action.person)) {
Expand Down
25 changes: 13 additions & 12 deletions app/src/scenes/Actions/Action.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import ActionCategoriesModalSelect from '../../components/ActionCategoriesModalS
import Label from '../../components/Label';
import Tags from '../../components/Tags';
import { MyText } from '../../components/MyText';
import { actionsState, DONE, CANCEL, TODO, prepareActionForEncryption, mappedIdsToLabels } from '../../recoil/actions';
import { actionsState, DONE, CANCEL, TODO, prepareActionForEncryption, mappedIdsToLabels, allowedActionFieldsInHistory } from '../../recoil/actions';
import { useRecoilState, useRecoilValue } from 'recoil';
import { commentsState, prepareCommentForEncryption } from '../../recoil/comments';
import API from '../../services/api';
Expand Down Expand Up @@ -205,6 +205,18 @@ const Action = ({ navigation, route }) => {
}
}
delete action.team;

const historyEntry = {
date: new Date(),
user: user._id,
data: {},
};
for (const key in action) {
if (!allowedActionFieldsInHistory.map((field) => field.name).includes(key)) continue;
if (action[key] !== oldAction[key]) historyEntry.data[key] = { oldValue: oldAction[key], newValue: action[key] };
}
if (!!Object.keys(historyEntry.data).length) action.history = [...(action.history || []), historyEntry];

const response = await API.put({
path: `/action/${oldAction._id}`,
body: prepareActionForEncryption(action),
Expand All @@ -218,17 +230,6 @@ const Action = ({ navigation, route }) => {
})
);
if (!!newAction.completedAt) await createReportAtDateIfNotExist(newAction.completedAt);
if (!statusChanged) return response;
const comment = {
comment: `${user.name} a changé le status de l'action: ${mappedIdsToLabels.find((status) => status._id === newAction.status)?.name}`,
action: actionDB?._id,
team: currentTeam._id,
user: user._id,
organisation: organisation._id,
date: new Date().toISOString(),
};
const commentResponse = await API.post({ path: '/comment', body: prepareCommentForEncryption(comment) });
if (commentResponse.ok) setComments((comments) => [commentResponse.decryptedData, ...comments]);
return response;
} catch (error) {
capture(error, { extra: { message: 'error in updating action', action } });
Expand Down
136 changes: 77 additions & 59 deletions dashboard/src/components/ActionModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState, useRef } from 'react';
import { toast } from 'react-toastify';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useHistory, useLocation } from 'react-router-dom';
import { actionsState, allowedActionFieldsInHistory, CANCEL, DONE, mappedIdsToLabels, prepareActionForEncryption, TODO } from '../recoil/actions';
import { actionsState, allowedActionFieldsInHistory, CANCEL, DONE, prepareActionForEncryption, TODO } from '../recoil/actions';
import { currentTeamState, organisationState, teamsState, userState } from '../recoil/auth';
import { dayjsInstance, now, outOfBoundariesDate } from '../services/date';
import API from '../services/api';
Expand Down Expand Up @@ -289,7 +289,7 @@ function ActionContent({ onClose, action, personId = null, personIds = null, isM
data: {},
};
for (const key in body) {
if (!allowedActionFieldsInHistory.includes(key)) continue;
if (!allowedActionFieldsInHistory.map((field) => field.name).includes(key)) continue;
if (body[key] !== action[key]) historyEntry.data[key] = { oldValue: action[key], newValue: body[key] };
}
if (!!Object.keys(historyEntry.data).length) body.history = [...(action.history || []), historyEntry];
Expand All @@ -312,17 +312,6 @@ function ActionContent({ onClose, action, personId = null, personIds = null, isM
await createReportAtDateIfNotExist(newAction.completedAt);
}
}
if (statusChanged) {
const comment = {
comment: `${user.name} a changé le status de l'action: ${mappedIdsToLabels.find((status) => status._id === newAction.status)?.name}`,
action: action._id,
team: currentTeam._id,
user: user._id,
organisation: organisation._id,
};
const commentResponse = await API.post({ path: '/comment', body: prepareCommentForEncryption(comment) });
if (commentResponse.ok) setComments((comments) => [commentResponse.decryptedData, ...comments]);
}
toast.success('Mise à jour !');
if (location.pathname !== '/stats') refresh(); // if we refresh when we're on stats page, it will remove the view we're on
const actionCancelled = action.status !== CANCEL && body.status === CANCEL;
Expand Down Expand Up @@ -658,6 +647,12 @@ function ActionContent({ onClose, action, personId = null, personIds = null, isM
/>
</div>
)}
<div
className={['tw-flex tw-w-full tw-flex-col tw-gap-4 tw-overflow-y-auto sm:tw-h-[50vh]', activeTab !== 'Historique' && 'tw-hidden']
.filter(Boolean)
.join(' ')}>
<ActionHistory action={action} />
</div>
</form>
</ModalBody>
<ModalFooter>
Expand Down Expand Up @@ -692,58 +687,81 @@ function ActionContent({ onClose, action, personId = null, personIds = null, isM
}

function ActionHistory({ action }) {
const history = useMemo(() => (action.history || []).reverse(), [action.history]);
const history = useMemo(() => [...(action?.history || [])].reverse(), [action?.history]);
const teams = useRecoilValue(teamsState);

return (
<div>
<div>
<h3>Historique</h3>
</div>
<table className="table table-striped table-bordered">
<thead>
<tr>
<th>Date</th>
<th>Utilisateur</th>
<th>Donnée</th>
</tr>
</thead>
<tbody className="small">
{history.map((h) => {
return (
<tr key={h.date} className="tw-cursor-default">
<td>{dayjsInstance(h.date).format('DD/MM/YYYY HH:mm')}</td>
<td>
<UserName id={h.user} />
</td>
<td className="tw-max-w-prose">
{Object.entries(h.data).map(([key, value]) => {
const personField = personFieldsIncludingCustomFields.find((f) => f.name === key);
if (key === 'teams') {
{!history?.length ? (
<div className="tw-py-10 tw-text-center tw-text-gray-300">
<p className="tw-text-lg tw-font-bold">Cette action n'a pas encore d'historique.</p>
<p className="tw-mt-2 tw-text-sm">
Lorsqu'une action est modifiée, les changements sont enregistrés dans un historique,
<br />
que vous pourrez ainsi retrouver sur cette page.
</p>
</div>
) : (
<table className="table table-striped table-bordered">
<thead>
<tr className="tw-cursor-default">
<th>Date</th>
<th>Utilisateur</th>
<th>Donnée</th>
</tr>
</thead>
<tbody className="small">
{history.map((h) => {
return (
<tr key={h.date} className="tw-cursor-default">
<td>{dayjsInstance(h.date).format('DD/MM/YYYY HH:mm')}</td>
<td>
<UserName id={h.user} />
</td>
<td className="tw-max-w-prose">
{Object.entries(h.data).map(([key, value]) => {
const actionField = allowedActionFieldsInHistory.find((f) => f.name === key);
if (key === 'teams') {
return (
<p className="tw-flex tw-flex-col" key={key}>
<span>{actionField?.label} : </span>
<code>"{(value.oldValue || []).map((teamId) => teams.find((t) => t._id === teamId)?.name).join(', ')}"</code>
<span></span>
<code>"{(value.newValue || []).map((teamId) => teams.find((t) => t._id === teamId)?.name).join(', ')}"</code>
</p>
);
}
if (key === 'person') {
return (
<p key={key}>
{actionField?.label} : <br />
<code>
<PersonName item={{ person: value.oldValue }} />
</code>{' '}
{' '}
<code>
<PersonName item={{ person: value.newValue }} />
</code>
</p>
);
}

return (
<p className="tw-flex tw-flex-col" key={key}>
<span>{personField?.label} : </span>
<code>"{(value.oldValue || []).map((teamId) => teams.find((t) => t._id === teamId)?.name).join(', ')}"</code>
<span></span>
<code>"{(value.newValue || []).map((teamId) => teams.find((t) => t._id === teamId)?.name).join(', ')}"</code>
<p
key={key}
data-test-id={`${actionField?.label}: ${JSON.stringify(value.oldValue || '')}${JSON.stringify(value.newValue)}`}>
{actionField?.label} : <br />
<code>{JSON.stringify(value.oldValue || '')}</code><code>{JSON.stringify(value.newValue)}</code>
</p>
);
}

return (
<p
key={key}
data-test-id={`${personField?.label}: ${JSON.stringify(value.oldValue || '')}${JSON.stringify(value.newValue)}`}>
{personField?.label} : <br />
<code>{JSON.stringify(value.oldValue || '')}</code><code>{JSON.stringify(value.newValue)}</code>
</p>
);
})}
</td>
</tr>
);
})}
</tbody>
</table>
})}
</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
);
}
13 changes: 3 additions & 10 deletions dashboard/src/components/PersonName.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import styled from 'styled-components';
import { useHistory } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { personsObjectSelector } from '../recoil/selectors';
Expand All @@ -9,20 +8,14 @@ export default function PersonName({ item, onClick = null, redirectToTab = 'Rés
const persons = useRecoilValue(personsObjectSelector);
const personName = item?.personPopulated?.name || persons[item.person]?.name;
return (
<BoldOnHover
<span
className="hover:tw-cursor-zoom-in hover:tw-bg-yellow-400"
onClick={(e) => {
e.stopPropagation();
if (onClick) return onClick();
if (item.person) history.push(`/person/${item.person}?tab=${redirectToTab}`);
}}>
{personName}
</BoldOnHover>
</span>
);
}

const BoldOnHover = styled.span`
&:hover {
background-color: yellow;
cursor: zoom-in;
}
`;
13 changes: 12 additions & 1 deletion dashboard/src/recoil/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,18 @@ const encryptedFields = [
'history',
];

export const allowedActionFieldsInHistory = encryptedFields.filter((field) => !['category', 'team', 'history'].includes(field));
export const allowedActionFieldsInHistory = [
{ name: 'categories', label: 'Catégorie(s)' },
{ name: 'person', label: 'Personne suivie' },
{ name: 'group', label: 'Action familiale' },
{ name: 'name', label: "Nom de l'action" },
{ name: 'description', label: 'Description' },
{ name: 'teams', label: 'Équipe(s) en charge' },
{ name: 'urgent', label: 'Action urgente' },
{ name: 'completedAt', label: 'Faite le' },
{ name: 'dueAt', label: 'À faire le' },
{ name: 'status', label: 'Status' },
];

export const prepareActionForEncryption = (action, { checkRequiredFields = true } = {}) => {
if (!!checkRequiredFields) {
Expand Down
Loading

0 comments on commit 723cb1e

Please sign in to comment.