Skip to content

Commit

Permalink
fix(admin): update admin display, add filters, tags on modification j…
Browse files Browse the repository at this point in the history
…ournal and error message
  • Loading branch information
Mihoub2 committed Jan 18, 2024
1 parent 9a3944c commit dd22698
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMemo } from 'react';
import useFetch from '../../../../hooks/useFetch';

const useModificationData = (days) => {
const queryDate = useMemo(() => new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(), [days]);
const { data, error } = useFetch(`/dashboard?filters[createdAt][$gte]=${queryDate}`);
const topUsers = data?.data?.[0]?.byUser.slice(0, 15) || [];
const userIDs = topUsers.map((item) => item._id);
return { topUsers, error, userIDs };
};

export default useModificationData;
93 changes: 93 additions & 0 deletions src/components/blocs/modification-journal/globalIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useState } from 'react';
import { Accordion, AccordionItem, Col, Pagination, Row, Tag, TagGroup, Text } from '@dataesr/react-dsfr';
import { useSearchParams } from 'react-router-dom';
import useFetch from '../../../hooks/useFetch';
import { Bloc, BlocContent, BlocTitle } from '../../bloc';
import ModificationDetails from './components/modification-details';
import ModificationTitle from './components/modification-title';
import useModificationData from './components/useModificationData';
import { PageSpinner } from '../../spinner';

const LAST_DAYS = 30;
const DATE = new Date(Date.now() - LAST_DAYS * 24 * 60 * 60 * 1000).toISOString();
const ITEMS_PER_PAGE = 50;

export default function GlobalModificationJournal() {
const { topUsers } = useModificationData(LAST_DAYS);
const [openAccordions, setOpenAccordions] = useState([]);
const [searchParams, setSearchParams] = useSearchParams();

const page = searchParams.get('page') || 1;
const id = searchParams.get('id');

const limit = ITEMS_PER_PAGE;
const skip = (page - 1) * limit;

const url = id
? `/journal?filters[userId]=${id}&filters[createdAt][$gte]=${DATE}&sort=-createdAt&limit=${limit}&skip=${skip}&filters[resourceType][$ne]=admin`
: `/journal?filters[createdAt][$gte]=${DATE}&sort=-createdAt&skip=${skip}&limit=${limit}&filters[resourceType][$ne]=admin`;

const { data, error, isLoading } = useFetch(url);

if (isLoading) return <PageSpinner />;
if (!data?.data?.length) return `L'utilisateur n'a pas effectué de modification depuis les ${LAST_DAYS} derniers jours`;

return (
<Bloc isLoading={isLoading} error={error} data={data}>
<BlocTitle as="h1" look="h3">
Journal des modifications
</BlocTitle>
<BlocContent>
<Row alignItems="middle" spacing="mb-1v">
<Text className="fr-m-0" size="sm" as="span"><i>Filtrer par utilisateurs ayant effectué une modification dans les 30 derniers jours :</i></Text>
</Row>
<TagGroup>
{topUsers.map((user) => (
<Tag
key={user._id}
onClick={() => {
if (id === user._id) {
setSearchParams({});
} else { setSearchParams({ id: user._id }); }
}}
selected={id === user._id}
className="no-span"
>
{user.displayName}
{' '}
(
{user.totalOperations}
)
</Tag>
))}
</TagGroup>
<Row gutters>
<Col n="12">
<Accordion keepOpen>
{data?.data.map((event, i) => (
<AccordionItem
key={event.createdAt}
onClick={() => setOpenAccordions((prev) => [...prev, i])}
title={<ModificationTitle data={event} />}
>
{openAccordions.includes(i) && <ModificationDetails data={event} />}
</AccordionItem>
))}
<Row className="flex--space-around fr-pt-3w">
<Pagination
onClick={(newPage) => {
searchParams.set('page', newPage);
setSearchParams(searchParams);
}}
surroundingPages={2}
currentPage={Number(page)}
pageCount={data?.totalCount ? Math.ceil(data.totalCount / ITEMS_PER_PAGE) : 0}
/>
</Row>
</Accordion>
</Col>
</Row>
</BlocContent>
</Bloc>
);
}
91 changes: 9 additions & 82 deletions src/components/blocs/modification-journal/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Accordion, AccordionItem, Col, Pagination, Row, Tag, TagGroup } from '@dataesr/react-dsfr';
import { useParams, useSearchParams } from 'react-router-dom';
import { Accordion, AccordionItem, Col, Row } from '@dataesr/react-dsfr';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import useFetch from '../../../hooks/useFetch';
import { Bloc, BlocContent, BlocTitle } from '../../bloc';
import ModificationDetails from './components/modification-details';
Expand All @@ -11,99 +11,26 @@ const DATE = new Date(Date.now() - LAST_DAYS * 24 * 60 * 60 * 1000).toISOString(

export default function ModificationJournal() {
const [openAccordions, setOpenAccordions] = useState([]);
const [searchParams, setSearchParams] = useSearchParams();
const [filterName, setFilterName] = useState('');
const [selectedUser, setSelectedUser] = useState('');

const page = searchParams.get('page') || 1;
const limit = 50;
const skip = (page - 1) * 50;
const { id: resourceId } = useParams();
const url = resourceId
? `/journal?filters[resourceId]=${resourceId}&filters[createdAt][$gte]=${DATE}&sort=-createdAt&skip=${skip}&limit=${limit}&filters[resourceType][$ne]=admin&filters[resourceType][$ne]=groups`
: `/journal?filters[createdAt][$gte]=${DATE}&sort=-createdAt&skip=${skip}&limit=${limit}&filters[resourceType][$ne]=admin`;

? `/journal?filters[resourceId]=${resourceId}&filters[createdAt][$gte]=${DATE}&sort=-createdAt&limit=100&filters[resourceType][$ne]=admin&filters[resourceType][$ne]=groups`
: `/journal?filters[createdAt][$gte]=${DATE}&sort=-createdAt&limit=100&filters[resourceType][$ne]=admin`;
const { data, error, isLoading } = useFetch(url);

if (!data?.totalCount) {
return null;
}
const uniqueUserNames = Array.from(
new Set(data?.data?.map((el) => `${el.user.firstName} ${el.user.lastName}`) || []),
);

const handleUserFilter = (userName) => {
setSearchParams({ page: 1 });

if (selectedUser === userName) {
setSelectedUser('');
setFilterName('');
} else {
setSelectedUser(userName);
setFilterName(userName);
}
};

const sortedUniqueUserNames = [...uniqueUserNames].sort((a, b) => {
const countA = data.data.filter(
(event) => `${event.user.firstName} ${event.user.lastName}` === a,
).length;
const countB = data.data.filter(
(event) => `${event.user.firstName} ${event.user.lastName}` === b,
).length;
return countB - countA;
});

return (
<Bloc isLoading={isLoading} error={error} data={data}>
<BlocTitle as="h1" look="h3">Journal des modifications</BlocTitle>
<BlocTitle as="h2" look="h6">Journal des modifications</BlocTitle>
<BlocContent>
<TagGroup>
{sortedUniqueUserNames.map((userName) => {
const modificationsCount = data?.data?.filter(
(el) => `${el.user.firstName} ${el.user.lastName}` === userName,
).length;
return (
<Tag
onClick={() => handleUserFilter(userName)}
selected={selectedUser === userName}
className="no-span"
>
{userName}
{' '}
(
{modificationsCount}
)
</Tag>
);
})}
</TagGroup>
<Row gutters>
<Col n="12">
<Accordion keepOpen>
{data?.data?.length > 0
&& data.data.map((event, i) => (
(!filterName || filterName === `${event.user.firstName} ${event.user.lastName}`) && (
<AccordionItem
onClick={() => setOpenAccordions((prev) => [...prev, i])}
key={event.createdAt}
title={<ModificationTitle data={event} />}
>
{data?.data?.length && data.data.map((event, i) => (
<AccordionItem onClick={() => setOpenAccordions((prev) => [...prev, i])} key={event.createdAt} title={<ModificationTitle data={event} />}>
{openAccordions.includes(i) && <ModificationDetails data={event} />}
</AccordionItem>
)
))}
))}
</Accordion>
</Col>
</Row>
<Row className="flex--space-around fr-pt-3w">
<Pagination
onClick={(newPage) => setSearchParams({ page: newPage })}
surrendingPages={2}
currentPage={Number(page)}
pageCount={data?.totalCount ? Math.ceil(data.totalCount / limit) : 0}
/>
</Row>
</BlocContent>
</Bloc>
);
Expand Down
11 changes: 10 additions & 1 deletion src/components/errors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ButtonGroup, Col, Container, Row, Text, Title } from '@dataesr/react-ds
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { ReactComponent as TechErrorSVG } from '../../assets/technical-error.svg';
import useAuth from '../../hooks/useAuth';

function Error500() {
return (
Expand All @@ -20,7 +21,7 @@ function Error404() {
<>
<Title as="h1" look="h1">Page non trouvée</Title>
<Text size="sm" className="fr-mb-3w">Erreur 404</Text>
<Text size="lead" className="fr-mb-3w">La page que vous cherchez est introuvable. Excusez-nous pour la gêne occasionnée.</Text>
<Text size="lead" className="fr-mb-3w">La page que vous cherchez est introuvable. Ou vous n'êtes peut être pas connecté avec vos identifiants. Excusez-nous pour la gêne occasionnée.</Text>
<Text size="sm" className="fr-mb-5w">
Si vous avez tapé l'adresse web dans le navigateur, vérifiez qu'elle est correcte. La page n'est peut-être plus disponible.
<br />
Expand All @@ -33,6 +34,7 @@ function Error404() {
}

export default function Error({ status }) {
const { viewer } = useAuth();
return (
<Container>
<Row gutters alignItems="middle" justifyContent="center" spacing="my-7w mt-md-12w mb-md-10w">
Expand All @@ -49,6 +51,13 @@ export default function Error({ status }) {
Contactez-nous
</Link>
</li>
{!viewer && (
<li>
<Link className="fr-btn fr-btn--secondary" to="/se-connecter">
Connectez-vous
</Link>
</li>
) }
</ButtonGroup>
</Col>
<Col spacing="px-6w px-md-0 py-0" offset="md-1" n="12 md-3">
Expand Down
62 changes: 46 additions & 16 deletions src/pages/admin/categories-juridiques.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { Badge, Breadcrumb, BreadcrumbItem, Col, Container, Modal, ModalContent, ModalTitle, Row, Tag, Text, Title } from '@dataesr/react-dsfr';
import { Badge, Breadcrumb, BreadcrumbItem, Col, Container, Link, Modal, ModalContent, ModalTitle, Row, Tag, Text, TextInput, Title } from '@dataesr/react-dsfr';
import { useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import Button from '../../components/button';
import CopyButton from '../../components/copy/copy-button';
import LegalCategoriesForm from '../../components/forms/legal-categories';
import useFetch from '../../hooks/useFetch';
import useNotice from '../../hooks/useNotice';
import api from '../../utils/api';
import { toString } from '../../utils/dates';
import { deleteError, deleteSuccess, saveError, saveSuccess } from '../../utils/notice-contents';
import { PageSpinner } from '../../components/spinner';

export default function LegalCategoriesPage() {
const route = '/legal-categories';
const { data, isLoading, error, reload } = useFetch(`${route}?limit=500`);
const [isOpen, setIsOpen] = useState();
const [isOpen, setIsOpen] = useState(false);
const [modalTitle, setModalTitle] = useState('');
const [modalContent, setModalContent] = useState(null);
const { notice } = useNotice();
const [query, setQuery] = useState('');

const filteredData = query
? (data?.data || []).filter((item) => item?.longNameFr?.toLowerCase().includes(query?.toLowerCase()))
: data?.data || [];

const handleSave = async (body, itemId) => {
const method = itemId ? 'patch' : 'post';
Expand Down Expand Up @@ -54,7 +61,8 @@ export default function LegalCategoriesPage() {
};

if (error) return <div>Erreur</div>;
if (isLoading) return <div>Chargement</div>;
if (isLoading) return <PageSpinner />;

return (
<Container fluid>
<Row>
Expand All @@ -66,23 +74,45 @@ export default function LegalCategoriesPage() {
</Breadcrumb>
</Col>
</Row>
<Row className="flex--space-between flex--baseline">
<Row alignItems="top">
<Title className="fr-pr-1v" as="h1" look="h3">Catégories juridiques</Title>
<Badge type="info" text={data?.totalCount} />
</Row>
<Row className="flex--space-between flex--baseline ">
<Title className="fr-pr-1v" as="h1" look="h3">
Catégories juridiques
<Badge type="info" text={filteredData?.length} />
</Title>
<Button color="success" size="sm" icon="ri-add-line" onClick={() => handleModalToggle()}>Ajouter</Button>
</Row>
<hr />
{data.data?.map((item) => (
<div key={item.id}>
<Row className="flex--space-between">
<div className="flex--grow fr-pl-2w">
<Text spacing="my-1v" bold size="lg">{item.longNameFr}</Text>
<Row alignItems="middle" spacing="mb-3v">
<Col n="12">
<Text className="fr-m-0" size="sm" as="span">
<i>Filtrer par catégories juridiques :</i>
</Text>
<TextInput placeholder="Filtrer" value={query} onChange={(e) => setQuery(e.target.value)} size="sm" />
</Col>
</Row>
{filteredData.map((item) => (
<div>
<Row key={item.id} className="flex--space-between">
<Col className="flex--grow fr-pl-2w">
<Link href={`/categories-juridiques/${item.id}`}>
<Text spacing="my-1v" bold size="lg">{item.longNameFr}</Text>
</Link>
<Text as="span" bold>
Autres noms :
</Text>
{item.otherNames.length ? item.otherNames.map((name) => <Tag as="span">{name}</Tag>) : <Text as="span">Aucun alias pour le moment</Text>}
{item.otherNames.length ? item.otherNames.map((name) => <Tag as="span">{name}</Tag>) : <Text as="span"> Aucun alias pour le moment</Text>}
{item?.id && (
<Row>
<Text as="span" bold className="fr-mb-2v">
ID :
{' '}
{item.id}
<CopyButton
copyText={item.id}
size="sm"
/>
</Text>
</Row>
)}
<Text spacing="mt-2w mb-0" size="xs">
Créé le
{' '}
Expand All @@ -99,7 +129,7 @@ export default function LegalCategoriesPage() {
{`${item.updatedBy?.firstName} ${item.updatedBy?.lastName}`}
</Text>
)}
</div>
</Col>
<div>
<Button size="sm" secondary icon="ri-edit-line" onClick={() => handleModalToggle(item)}>Editer</Button>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/admin/groupes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Bloc, BlocActionButton, BlocContent, BlocModal, BlocTitle } from '../..
import useNotice from '../../hooks/useNotice';
import { saveError, saveSuccess, deleteSuccess, deleteError } from '../../utils/notice-contents';
import useEditMode from '../../hooks/useEditMode';
import { PageSpinner } from '../../components/spinner';

export default function GroupsPage() {
const url = '/groups';
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function GroupsPage() {
));
};
if (error) return <div>Erreur</div>;
if (isLoading) return <div>Chargement</div>;
if (isLoading) return <PageSpinner />;
return (
<>
<Breadcrumb>
Expand Down
Loading

0 comments on commit dd22698

Please sign in to comment.