Skip to content

Commit

Permalink
feat: pouvoir filtrer par actions créées le
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud AMBROSELLI committed Feb 19, 2024
1 parent 0ab933f commit 3883879
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 59 deletions.
19 changes: 8 additions & 11 deletions dashboard/src/components/ActionModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useHistory, useLocation } from 'react-router-dom';
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 { dayjsInstance, formatDateWithNameOfDay, now, outOfBoundariesDate } from '../services/date';
import API from '../services/api';
import SelectPerson from './SelectPerson';
import SelectStatus from './SelectStatus';
Expand Down Expand Up @@ -274,15 +274,12 @@ function ActionContent({ onClose, action, personId = null, personIds = null, isM
const body = { ...data };
body.teams = Array.isArray(data.teams) ? data.teams : [data.team];
if (!data.teams?.length) return toast.error('Une action doit être associée à au moins une équipe.');
const statusChanged = data.status && action.status !== data.status;
if (statusChanged) {
if ([DONE, CANCEL].includes(data.status)) {
// When status changed to finished (done, cancel) completedAt we set it to now if not set.
body.completedAt = data.completedAt || now();
} else {
// When status just changed to "todo" we set completedAt to null (since it's not done yet).
body.completedAt = null;
}
if ([DONE, CANCEL].includes(data.status)) {
// When status changed to finished (done, cancel) completedAt we set it to now if not set.
body.completedAt = data.completedAt || now();
} else {
// When status just changed to "todo" we set completedAt to null (since it's not done yet).
body.completedAt = null;
}
if (data.completedAt && outOfBoundariesDate(data.completedAt)) return toast.error('La date de complétion est hors limites (entre 1900 et 2100)');
if (data.dueAt && outOfBoundariesDate(data.dueAt)) return toast.error("La date d'échéance est hors limites (entre 1900 et 2100)");
Expand Down Expand Up @@ -350,7 +347,7 @@ function ActionContent({ onClose, action, personId = null, personIds = null, isM
<UserName
className="tw-block tw-text-right tw-text-base tw-font-normal tw-italic"
id={action.user}
wrapper={(name) => ` (créée par ${name})`}
wrapper={(name) => ` (créée par ${name} le ${formatDateWithNameOfDay(action.createdAt)})`}
/>
)}
</>
Expand Down
15 changes: 13 additions & 2 deletions dashboard/src/components/ActionsSortableList.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import useSearchParamState from '../services/useSearchParamState';
import DescriptionIcon from './DescriptionIcon';
import { AgendaMutedIcon } from '../assets/icons/AgendaMutedIcon';

const ActionsSortableList = ({ data, limit }) => {
const ActionsSortableList = ({ data, limit, showCreatedAt }) => {
useTitle('Agenda');
const history = useHistory();
const user = useRecoilValue(userState);
Expand Down Expand Up @@ -154,7 +154,18 @@ const ActionsSortableList = ({ data, limit }) => {
);
},
},
]}
{
title: 'Créée le',
dataKey: 'createdAt',
onSortOrder: setSortOrder,
onSortBy: setSortBy,
sortBy,
sortOrder,
render: (action) => {
return <DateBloc date={action.createdAt} />;
},
},
].filter((e) => showCreatedAt || e.dataKey !== 'createdAt')}
/>
{limit > 0 && <Page page={page} limit={limit} total={total} onChange={({ page }) => setPage(page, true)} />}
</>
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/components/ConsultationModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
prepareConsultationForEncryption,
} from '../recoil/consultations';
import API from '../services/api';
import { dayjsInstance } from '../services/date';
import { dayjsInstance, formatDateWithNameOfDay } from '../services/date';
import useCreateReportAtDateIfNotExist from '../services/useCreateReportAtDateIfNotExist';
import CustomFieldInput from './CustomFieldInput';
import { modalConfirmState } from './ModalConfirm';
Expand Down Expand Up @@ -240,7 +240,7 @@ function ConsultationContent({ personId, consultation, date, onClose }) {
<UserName
className="tw-block tw-text-right tw-text-base tw-font-normal tw-italic"
id={consultation.user}
wrapper={(name) => ` (créée par ${name})`}
wrapper={(name) => ` (créée par ${name} le ${formatDateWithNameOfDay(consultation.createdAt)})`}
/>
)}
</>
Expand Down
127 changes: 103 additions & 24 deletions dashboard/src/scenes/report/components/ActionsOrConsultationsReport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { mappedIdsToLabels } from '../../../recoil/actions';
import { CANCEL, DONE, TODO, mappedIdsToLabels } from '../../../recoil/actions';
import { useHistory } from 'react-router-dom';
import SelectCustom from '../../../components/SelectCustom';
import { ModalHeader, ModalBody, ModalContainer, ModalFooter } from '../../../components/tailwind/Modal';
Expand All @@ -11,13 +11,33 @@ import { useRecoilValue } from 'recoil';
import { userState } from '../../../recoil/auth';
import { dayjsInstance } from '../../../services/date';

export const ActionsOrConsultationsReport = ({ actions, consultations, period }) => {
export const ActionsOrConsultationsReport = ({
actionsDueOrCompletedAt,
actionsCreatedAt,
consultationsDueOrCompletedAt,
consultationsCreatedAt,
period,
}) => {
const [activeTab, setActiveTab] = useLocalStorage('reports-actions-consultation-toggle', 'Actions');
const [fullScreen, setFullScreen] = useState(false);
const [filterStatus, setFilterStatus] = useState([]);
const filteredActions = actions.filter((item) => !filterStatus.length || filterStatus.includes(item.status));
const filteredConsultations = consultations.filter((item) => !filterStatus.length || filterStatus.includes(item.status));
const data = activeTab.includes('Actions') ? actions : consultations;
const [filters, setFilters] = useLocalStorage('reports-actions-consultation-filter', []); // TODO, DONE CANCEL createdOnPeriod

const filterStatus = filters.filter((f) => f !== 'createdOnPeriod');

const showCreatedAt = filters.includes('createdOnPeriod');

const actions = showCreatedAt ? actionsCreatedAt : actionsDueOrCompletedAt;
const consultations = showCreatedAt ? consultationsCreatedAt : consultationsDueOrCompletedAt;

const filteredActionsDueOrCompletedAt = actionsDueOrCompletedAt.filter((item) => !filterStatus.length || filterStatus.includes(item.status));
const filteredActionsCreatedAt = actionsCreatedAt.filter((item) => !filterStatus.length || filterStatus.includes(item.status));
const filteredActions = showCreatedAt ? filteredActionsCreatedAt : filteredActionsDueOrCompletedAt;
const filteredConsultationsDueOrCompletedAt = consultationsDueOrCompletedAt.filter(
(item) => !filterStatus.length || filterStatus.includes(item.status)
);
const filteredConsultationsCreatedAt = consultationsCreatedAt.filter((item) => !filterStatus.length || filterStatus.includes(item.status));
const filteredConsultations = showCreatedAt ? filteredConsultationsCreatedAt : filteredConsultationsDueOrCompletedAt;
const unFilteredData = activeTab.includes('Actions') ? actions : consultations;
const filteredData = activeTab.includes('Actions') ? filteredActions : filteredConsultations;
const history = useHistory();
const user = useRecoilValue(userState);
Expand All @@ -28,6 +48,8 @@ export const ActionsOrConsultationsReport = ({ actions, consultations, period })
? [`Actions (${filteredActions.length})`, `Consultations (${filteredConsultations.length})`]
: [`Actions (${filteredActions.length})`];

const periodIsOneDay = dayjsInstance(period.startDate).isSame(period.endDate, 'day');

return (
<>
<section title={activeTab} className="noprint tw-relative tw-flex tw-h-full tw-flex-col tw-overflow-hidden">
Expand Down Expand Up @@ -61,27 +83,33 @@ export const ActionsOrConsultationsReport = ({ actions, consultations, period })
'tw-h-6 tw-w-6 tw-rounded-full tw-transition hover:tw-scale-125 disabled:tw-cursor-not-allowed disabled:tw-opacity-30',
activeTab.includes('Actions') ? 'tw-text-main' : 'tw-text-blue-900',
].join(' ')}
disabled={!data.length}
disabled={!unFilteredData.length}
onClick={() => setFullScreen(true)}>
<FullScreenIcon />
</button>
</div>
</div>
<div className="w-full tw-max-w-lg tw-bg-white tw-px-7 tw-pb-1">
<ActionsOrConsultationsFilters setFilterStatus={setFilterStatus} filterStatus={filterStatus} disabled={!data.length} />
<ActionsOrConsultationsFilters
periodIsOneDay={periodIsOneDay}
setFilters={setFilters}
filters={filters}
disabled={!unFilteredData.length}
/>
</div>
<div className="tw-grow tw-overflow-y-auto tw-border-t tw-border-main tw-border-opacity-20">
<ActionsSortableList data={filteredData} />
</div>
</section>
{/* PRINTONLY */}
<section
aria-hidden="true"
className="printonly tw-flex tw-h-full tw-flex-col tw-overflow-hidden tw-rounded-lg tw-border tw-border-zinc-200 tw-shadow">
className="printonly tw-mt-12 tw-flex tw-h-full tw-flex-col tw-overflow-hidden tw-rounded-lg tw-border tw-border-zinc-200 tw-shadow">
<div className="tw-flex tw-flex-col tw-items-stretch tw-bg-white tw-px-3 tw-py-3">
<h3 className="tw-m-0 tw-text-base tw-font-medium">Actions ({filteredActions.length})</h3>
<h3 className="tw-m-0 tw-text-base tw-font-medium">Actions échues pendant la période ({filteredActionsDueOrCompletedAt.length})</h3>
{filterStatus.length > 0 && (
<h4 className="tw-m-0 tw-text-base tw-font-medium">
Filtrées par status:{' '}
Filtrées par statut:{' '}
{mappedIdsToLabels
.filter((s) => filterStatus.includes(s._id))
.map((status) => status.name)
Expand All @@ -90,17 +118,17 @@ export const ActionsOrConsultationsReport = ({ actions, consultations, period })
)}
</div>
<div className="tw-grow tw-overflow-y-auto tw-border-t tw-border-main tw-border-opacity-20">
<ActionsSortableList data={filteredActions} />
<ActionsSortableList data={filteredActionsDueOrCompletedAt} showCreatedAt />
</div>
</section>
<section
aria-hidden="true"
className="printonly tw-mt-12 tw-flex tw-h-full tw-flex-col tw-overflow-hidden tw-rounded-lg tw-border tw-border-zinc-200 tw-shadow">
<div className="tw-flex tw-flex-col tw-items-stretch tw-bg-white tw-px-3 tw-py-3">
<h3 className="tw-m-0 tw-text-base tw-font-medium">Consultations ({filteredConsultations.length})</h3>
<h3 className="tw-m-0 tw-text-base tw-font-medium">Actions créées pendant la période ({filteredActionsCreatedAt.length})</h3>
{filterStatus.length > 0 && (
<h4 className="tw-m-0 tw-text-base tw-font-medium">
Filtrées par status:{' '}
Filtrées par statut:{' '}
{mappedIdsToLabels
.filter((s) => filterStatus.includes(s._id))
.map((status) => status.name)
Expand All @@ -109,17 +137,63 @@ export const ActionsOrConsultationsReport = ({ actions, consultations, period })
)}
</div>
<div className="tw-grow tw-overflow-y-auto tw-border-t tw-border-main tw-border-opacity-20">
<ActionsSortableList data={filteredConsultations} />
<ActionsSortableList data={filteredActionsCreatedAt} showCreatedAt />
</div>
</section>
<section
aria-hidden="true"
className="printonly tw-mt-12 tw-flex tw-h-full tw-flex-col tw-overflow-hidden tw-rounded-lg tw-border tw-border-zinc-200 tw-shadow">
<div className="tw-flex tw-flex-col tw-items-stretch tw-bg-white tw-px-3 tw-py-3">
<h3 className="tw-m-0 tw-text-base tw-font-medium">
Consultations échues pendant la période ({filteredConsultationsDueOrCompletedAt.length})
</h3>
{filterStatus.length > 0 && (
<h4 className="tw-m-0 tw-text-base tw-font-medium">
Filtrées par statut:{' '}
{mappedIdsToLabels
.filter((s) => filterStatus.includes(s._id))
.map((status) => status.name)
.join(', ')}
</h4>
)}
</div>
<div className="tw-grow tw-overflow-y-auto tw-border-t tw-border-main tw-border-opacity-20">
<ActionsSortableList data={filteredConsultationsDueOrCompletedAt} showCreatedAt />
</div>
</section>
<section
aria-hidden="true"
className="printonly tw-mt-12 tw-flex tw-h-full tw-flex-col tw-overflow-hidden tw-rounded-lg tw-border tw-border-zinc-200 tw-shadow">
<div className="tw-flex tw-flex-col tw-items-stretch tw-bg-white tw-px-3 tw-py-3">
<h3 className="tw-m-0 tw-text-base tw-font-medium">Consultations créées pendant la période ({filteredConsultationsCreatedAt.length})</h3>
{filterStatus.length > 0 && (
<h4 className="tw-m-0 tw-text-base tw-font-medium">
Filtrées par statut:{' '}
{mappedIdsToLabels
.filter((s) => filterStatus.includes(s._id))
.map((status) => status.name)
.join(', ')}
</h4>
)}
</div>
<div className="tw-grow tw-overflow-y-auto tw-border-t tw-border-main tw-border-opacity-20">
<ActionsSortableList data={filteredConsultationsCreatedAt} showCreatedAt />
</div>
</section>
{/* END PRINTONLY */}
<ModalContainer open={!!fullScreen} className="" size="full" onClose={() => setFullScreen(false)}>
<ModalHeader title={`${activeTab} (${filteredData.length})`} onClose={() => setFullScreen(false)}>
<div className="tw-mx-auto tw-mt-2 tw-w-full tw-max-w-lg">
<ActionsOrConsultationsFilters setFilterStatus={setFilterStatus} filterStatus={filterStatus} disabled={!data.length} />
<ActionsOrConsultationsFilters
periodIsOneDay={periodIsOneDay}
setFilters={setFilters}
filters={filters}
disabled={!unFilteredData.length}
/>
</div>
</ModalHeader>
<ModalBody>
<ActionsSortableList data={filteredData} />
<ActionsSortableList data={filteredData} showCreatedAt />
</ModalBody>
<ModalFooter>
<button type="button" name="cancel" className="button-cancel" onClick={() => setFullScreen(false)}>
Expand All @@ -141,26 +215,31 @@ export const ActionsOrConsultationsReport = ({ actions, consultations, period })
);
};

const ActionsOrConsultationsFilters = ({ setFilterStatus, filterStatus, disabled }) => {
const ActionsOrConsultationsFilters = ({ setFilters, filters, disabled, periodIsOneDay }) => {
const filtersOptions = [
{ value: TODO, label: 'À FAIRE' },
{ value: DONE, label: 'FAITE' },
{ value: CANCEL, label: 'ANNULÉE' },
{ value: 'createdOnPeriod', label: periodIsOneDay ? 'Créées ce jour' : 'Créées pendant la période' },
];

return (
<>
<div className="tw-flex tw-justify-between">
<div className="tw-flex tw-w-full tw-shrink-0 tw-grow tw-items-center tw-pl-1 tw-pr-2">
<label htmlFor="action-select-status-filter" className="tw-text-xs">
Filtrer par statut
Filtrer par
</label>
<div className="tw-w-full">
<SelectCustom
inputId="action-select-status-filter"
options={mappedIdsToLabels}
getOptionValue={(s) => s._id}
getOptionLabel={(s) => s.name}
options={filtersOptions}
name="status"
onChange={(s) => setFilterStatus(s.map((s) => s._id))}
onChange={(s) => setFilters(s.map((s) => s.value))}
isClearable
isDisabled={disabled}
isMulti
value={mappedIdsToLabels.filter((s) => filterStatus.includes(s._id))}
value={filtersOptions.filter((s) => filters.includes(s.value))}
/>
</div>
</div>
Expand Down
Loading

0 comments on commit 3883879

Please sign in to comment.