Skip to content

Commit

Permalink
feat(last-mails-sent): add last mails sent route and ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Mihoub2 committed Oct 28, 2024
1 parent 43cfeb9 commit c380eed
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 3 deletions.
2 changes: 2 additions & 0 deletions client/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Layout from "./src/layout";
import React from "react";
import ContactAndContributionPage from "./src/pages/contact-contributionbyobject-page";
import LocalBSO from "./src/pages/bso/index";
import LastMailSent from "./src/pages/last-mails-sent";

export default function Router() {
return (
Expand Down Expand Up @@ -43,6 +44,7 @@ export default function Router() {
path="/datasupr-contact"
element={<ContactAndContributionPage fromApplication="datasupr" />}
/>
<Route path="/last-mails-sent" element={<LastMailSent />} />
<Route path="/scanr-apioperations" element={<ApiOperationPage />} />
<Route
path="/scanr-removeuser"
Expand Down
23 changes: 23 additions & 0 deletions client/src/api/contribution-api/getSentMails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from "@tanstack/react-query";
import { postHeaders } from "../../config/api";

const useSentEmails = () => {
const fetchSentEmails = async () => {
const response = await fetch("/api/get-sent-emails", {
headers: postHeaders,
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};

const { data, isLoading, isError, refetch } = useQuery(
["sentEmails"],
fetchSentEmails
);
console.log(data);
return { data, isLoading, isError, refetch };
};

export default useSentEmails;
2 changes: 1 addition & 1 deletion client/src/api/send-mail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function EmailSender({ contribution, refetch }: EmailSenderProps) {
<ProfileModal
isOpen={showProfileModal}
onClose={() => setShowProfileModal(false)}
onSelectProfile={handleProfileSelect} // Passer la fonction pour sélectionner le profil
onSelectProfile={handleProfileSelect}
selectedProfile={selectedProfile}
/>
<Modal isOpen={showPreviewModal} hide={() => setShowPreviewModal(false)}>
Expand Down
6 changes: 6 additions & 0 deletions client/src/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ const Header: React.FC = () => {
>
Works magnet
</Link>
<Link
current={pathname.startsWith("/last-mails-sent")}
href="/last-mails-sent"
>
Dernier mails envoyés
</Link>
</Nav>
</HeaderWrapper>
</>
Expand Down
94 changes: 94 additions & 0 deletions client/src/pages/last-mails-sent/components/item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useState } from "react";
import { Badge, Col, Text, Title } from "@dataesr/dsfr-plus";
import { FaCopy } from "react-icons/fa";
import "./styles.scss";
import { LastMailsSentProps } from "../../../types";
import collectionNameMapping, { generateLink } from "./utils";

const LastMailsSentItem: React.FC<LastMailsSentProps> = ({ data }) => {
const [copiedId, setCopiedId] = useState<string | null>(null);

const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text).then(() => {
setCopiedId(text);
setTimeout(() => setCopiedId(null), 2000);
});
};

return (
<>
{data.emails.map((email, index) => {
const link = generateLink(
email.collectionName,
email.fromApplication,
email.contributionId
);

const sentDate = new Date(email.sentAt);
const formattedDate = sentDate.toLocaleDateString("fr-FR");
const formattedTime = sentDate.toLocaleTimeString("fr-FR", {
hour: "2-digit",
minute: "2-digit",
});

return (
<Col key={index} className="email-item fr-mb-2w">
<div className="badges">
<Badge
size="sm"
color="green-menthe"
className="fr-mr-1w fr-mb-1w"
>
{collectionNameMapping[email.collectionName]}
</Badge>

{email.fromApplication && (
<Badge
size="sm"
color="blue-ecume"
className="fr-mr-1w fr-mb-1w"
>
{email.fromApplication}
</Badge>
)}
</div>

<div className="email-content">
<Title look="h6" as="h3" className="fr-mb-0">
Réponse de <i>{email.selectedProfile}</i> à{" "}
<i>
{email?.name} ({email?.to})
</i>
</Title>
<Text size="sm">
<a href={link} target="_blank" rel="noopener noreferrer">
<i>{email?.contributionId}</i>
</a>
<button
className={`copy-button ${
copiedId === email.contributionId ? "copied" : ""
}`}
onClick={() => copyToClipboard(email.contributionId)}
title="Copier l'ID"
>
{copiedId === email.contributionId && (
<span className="copied-text">Copié</span>
)}
<FaCopy size={14} color="#2196f3" className="copy-icon" />
</button>
</Text>
<Text size="sm">
<i>
Envoyé le {formattedDate} à {formattedTime}
</i>
</Text>
<Text>{email.userResponse}</Text>
</div>
</Col>
);
})}
</>
);
};

export default LastMailsSentItem;
24 changes: 24 additions & 0 deletions client/src/pages/last-mails-sent/components/selectors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Col } from "@dataesr/dsfr-plus";

const Selectors = ({ uniqueProfiles, setStatus }) => {
const handleProfileChange = (event) => {
setStatus(event.target.value);
};

return (
<Col>
<Col className="fr-mb-1w">
<select onChange={handleProfileChange} className="fr-select">
<option value="choose">Par</option>
{uniqueProfiles.map((profile, index) => (
<option key={index} value={profile}>
{profile}
</option>
))}
</select>
</Col>
</Col>
);
};

export default Selectors;
6 changes: 6 additions & 0 deletions client/src/pages/last-mails-sent/components/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.email-item {
background-color: #f1f4f8;
padding: 1.5rem;
border-radius: 8px;
margin-top: 1rem;
}
42 changes: 42 additions & 0 deletions client/src/pages/last-mails-sent/components/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const collectionNameMapping: { [key: string]: string } = {
contribute: "Contribution par objets",
contacts: "Formulaire de contact",
contribute_productions: "Lier des publications",
"remove-user": "Retirer de la base de données",
"update-user-data": "Changement de nom",
};

export default collectionNameMapping;
// utils/generateLink.ts
export function generateLink(
collectionName: string,
fromApplication?: string,
id?: string
): string {
const basePathMap: { [key: string]: { [key: string]: string } | string } = {
contacts: {
scanr: "/scanr-contact",
paysage: "/paysage-contact",
bso: "/bso-contact",
curiexplore: "/curiexplore-contact",
"works-magnet": "/works-magnet-contact",
datasupr: "/datasupr-contact",
},
contribute_production: "/scanr-apioperations",
"remove-user": "/scanr-removeuser",
"update-user-data": "/scanr-namechange",
contribute: "/scanr-contributionPage",
};

let basePath = "";

if (collectionName === "contacts" && fromApplication) {
basePath = basePathMap[collectionName][fromApplication] || "";
} else {
basePath = (basePathMap[collectionName] as string) || "";
}

return id
? `${basePath}?page=1&query=${id}&searchInMessage=false&sort=DESC&status=choose`
: basePath;
}
93 changes: 93 additions & 0 deletions client/src/pages/last-mails-sent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState, useEffect } from "react";
import { useLocation } from "react-router-dom";
import { Col, Container, Row, Text, Title } from "@dataesr/dsfr-plus";
import { Contribution } from "../../types";
import BottomPaginationButtons from "../../components/pagination/bottom-buttons";
import TopPaginationButtons from "../../components/pagination/top-buttons";
import useSentEmails from "../../api/contribution-api/getSentMails";
import LastMailsSentItem from "./components/item";
import Selectors from "./components/selectors";

const LastMailsSent: React.FC = () => {
const location = useLocation();
const [sort, setSort] = useState("DESC");
const [status, setStatus] = useState("choose");
const [query, setQuery] = useState<string[]>([]);
const [page, setPage] = useState(1);
const [searchInMessage, setSearchInMessage] = useState(false);

const { data, isLoading, isError } = useSentEmails();
const sentEmails: Contribution[] = data ? data.emails : [];
const maxPage = Math.ceil((data?.emails.length || 0) / 10);

const uniqueProfiles = Array.from(
new Set(sentEmails.map((email) => email.selectedProfile))
);

useEffect(() => {
const params = new URLSearchParams(location.search);
setPage(parseInt(params.get("page") || "1"));
setSearchInMessage(params.get("searchInMessage") === "true");
const queryParam = params.get("query") || "";
setQuery(queryParam ? queryParam.split(",") : []);
setSort(params.get("sort") || "DESC");
}, [location.search]);

useEffect(() => {
const newSearchParams = new URLSearchParams();
newSearchParams.set("page", page.toString());
newSearchParams.set("query", query.join(","));
newSearchParams.set("searchInMessage", searchInMessage.toString());
newSearchParams.set("sort", sort);
if (status !== "choose") {
newSearchParams.set("status", status);
}
const newURL = `${window.location.pathname}?${newSearchParams.toString()}`;
window.history.pushState({}, "", newURL);
}, [page, query, searchInMessage, sort, status]);

if (isLoading)
return (
<Container className="fr-my-5w">
<Text>Chargement...</Text>
</Container>
);

if (isError)
return (
<Container className="fr-my-5w">
<Text>Erreur lors du chargement des emails envoyés.</Text>
</Container>
);

return (
<Container className="fr-my-5w">
<Title as="h1">Derniers mails envoyés</Title>
<Row gutters className="fr-mb-3w">
<Col>
<TopPaginationButtons
meta={{ total: sentEmails.length }}
page={page}
maxPage={maxPage}
setPage={setPage}
/>
</Col>
<Col offsetLg="6">
<Selectors setStatus={setStatus} uniqueProfiles={uniqueProfiles} />
</Col>
</Row>
<Row>
<Col>
<LastMailsSentItem data={data} />
</Col>
</Row>
<BottomPaginationButtons
page={page}
maxPage={maxPage}
setPage={setPage}
/>
</Container>
);
};

export default LastMailsSent;
4 changes: 4 additions & 0 deletions client/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export interface Contribution {
threads?: Thread[];
fromApplication: string;
}

export interface LastMailsSentProps {
data: Contribution;
}
export interface ContributorSummaryProps {
contributions: Contribution[];
onSelectContribution: (id: string) => void;
Expand Down
2 changes: 2 additions & 0 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import updateUserDataRoutes from "./routes/update-user-data";
import contactsRoutes from "./routes/contacts";
import sendMail from "./routes/reply/replyRoutes";
import { fetchEmails } from "./routes/receive-email";
import getLastMailsSentRoutes from "./routes/last-mails-sent";

dotenv.config();

Expand Down Expand Up @@ -62,6 +63,7 @@ app
app.use(removeUserRoutes);
app.use(updateUserDataRoutes);
app.use(sendMail);
app.use(getLastMailsSentRoutes);
return app;
})
.use(
Expand Down
39 changes: 39 additions & 0 deletions server/routes/last-mails-sent/get/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Elysia } from "elysia";
import { MongoClient } from "mongodb";
import { errorSchema } from "../../../schemas/errors/errorSchema";

const MONGO_URI = process.env.MONGO_URI || "";
const DB_NAME = process.env.MONGO_DATABASE || "";

const client = new MongoClient(MONGO_URI);
await client.connect();
const db = client.db(DB_NAME);

const lastSentMail = new Elysia();

lastSentMail.get(
"/get-sent-emails",
async () => {
const sentEmailsCollection = db.collection("sent_emails");

const sentEmails = await sentEmailsCollection.find().toArray();

return {
emails: sentEmails,
};
},
{
response: {
401: errorSchema,
500: errorSchema,
},
detail: {
summary: "Récupérer les emails envoyés",
description:
"Cette route permet de récupérer la liste des emails envoyés et enregistrés dans la collection 'sent_emails' de MongoDB.",
tags: ["Emails"],
},
}
);

export default lastSentMail;
Loading

0 comments on commit c380eed

Please sign in to comment.