diff --git a/client/src/api/contribution-api/getAllDatas.tsx b/client/src/api/contribution-api/getAllDatas.tsx new file mode 100644 index 0000000..49a7a74 --- /dev/null +++ b/client/src/api/contribution-api/getAllDatas.tsx @@ -0,0 +1,36 @@ +import { useQuery } from "@tanstack/react-query"; +import { postHeaders } from "../../config/api"; + +const routes = [ + "contacts", + "contribute", + "production", + "remove-user", + "update-user-data", +]; + +const fetchAllData = async (baseApiUrl) => { + const fetchPromises = routes.map(async (route) => { + const url = `${baseApiUrl}/${route}`; + const response = await fetch(url, { + headers: postHeaders, + }); + if (!response.ok) { + throw new Error(`Failed to fetch from ${route}`); + } + return response.json(); + }); + + return Promise.all(fetchPromises); +}; + +const ContributionAllDatas = (baseApiUrl) => { + const fetchContributions = () => fetchAllData(baseApiUrl); + const { data, isLoading, isError, refetch } = useQuery( + ["allContributions"], + fetchContributions + ); + return { data, isLoading, isError, refetch }; +}; + +export default ContributionAllDatas; diff --git a/client/src/api/utils/buildURL.tsx b/client/src/api/utils/buildURL.tsx index 33d0e12..81eae26 100644 --- a/client/src/api/utils/buildURL.tsx +++ b/client/src/api/utils/buildURL.tsx @@ -26,7 +26,7 @@ export const buildURL = ( const sorted = sort === "ASC" ? "sort=created_at" : "sort=-created_at"; const where: any = {}; - if (query.trim() !== "") { + if (typeof query === "string" && query.trim() !== "") { const isObjectId = /^[0-9a-fA-F]{24}$/.test(query); if (isObjectId) { @@ -57,6 +57,7 @@ export const buildURL = ( return `${baseApiUrl}/${baseUrl}?${sorted}&page=${page}&max_results=${max_results}${whereQuery}${fromAppQuery}`; }; + export const buildStatsURL = ( filter: string, sort: string = "ASC", diff --git a/client/src/pages/contact-contributionbyobject-page/components/search-section.tsx b/client/src/pages/contact-contributionbyobject-page/components/search-section.tsx index ce8eedb..103f580 100644 --- a/client/src/pages/contact-contributionbyobject-page/components/search-section.tsx +++ b/client/src/pages/contact-contributionbyobject-page/components/search-section.tsx @@ -11,12 +11,12 @@ const SearchSection: React.FC<{ onSearch={(value) => handleSearch(value || "")} isLarge buttonLabel="Rechercher" - placeholder="Rechercher par nom ou ID" + placeholder="Rechercher par nom, ID ou mot clé" />
{query - .filter((item) => item.trim() !== "") - .map((item, index) => ( + ?.filter((item) => item.trim() !== "") + ?.map((item, index) => ( +): 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 (productions?.length > 1) { + basePath = "/scanr-apioperations"; + } else if (objectId) { + basePath = "/scanr-contributionPage"; + } else if (fromApplication) { + basePath = "/scanr-contact"; + } else { + basePath = (basePathMap[collectionName] as string) || ""; + } + + return id + ? `${basePath}?page=1&query=${id}&searchInMessage=false&sort=DESC&status=choose` + : basePath; +} diff --git a/client/src/pages/home/components/item.tsx b/client/src/pages/home/components/item.tsx new file mode 100644 index 0000000..4843b91 --- /dev/null +++ b/client/src/pages/home/components/item.tsx @@ -0,0 +1,153 @@ +import { Badge, Col, Container, Link, Row, Text } from "@dataesr/dsfr-plus"; +import "./styles.scss"; +import { AllContributionsProps } from "../../../types"; +import { generateLinkFromAllDatas } from "./generate-links"; +import { + BadgeColor, + BadgeStatus, + StatusLabel, + typeIcon, + TypeLabel, +} from "../../../utils"; + +const AllContributions: React.FC = ({ + data, + query, +}) => { + const highlightQuery = (text: string, query: string) => { + if (!query) return text; + + const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const regex = new RegExp(`(${escapedQuery})`, "gi"); + + return text?.replace(regex, '$1'); + }; + + return ( + + {data.length === 0 ? ( +

Pas de résultat

+ ) : ( + data?.map((email, index) => { + const link = generateLinkFromAllDatas( + email.collectionName, + email.fromApplication, + email.id, + email.objectId, + email.productions + ); + const creationDate = new Date(email.created_at); + const formattedDate = creationDate.toLocaleDateString("fr-FR"); + const formattedTime = creationDate.toLocaleTimeString("fr-FR", { + hour: "2-digit", + minute: "2-digit", + }); + + let badgeContent = ""; + if (email.productions?.length > 1) { + badgeContent = "Lier des publications"; + } else if (email.objectId && !email.productions) { + badgeContent = "Contribution par objet"; + } else badgeContent = "Contact"; + + return ( + + + +
+ {badgeContent && ( + + {badgeContent} + + )} + {badgeContent === "Lier des publications" && ( + + scanR + + )} + {email.fromApplication && ( + + {email.fromApplication} + + )} + + {StatusLabel({ status: email.status })} + + {email?.objectType && ( + <> + + {TypeLabel({ type: email.objectType })} + + + scanR + + + )} + {email?.comment || + (email?.team?.length > 0 && ( + + {`Traité par ${email.team[0]}`} + + ))} +
+
+ + Contribution de{" "} + + {email?.name} - {email?.email} + + + + + Date de la contribution : {formattedDate} à{" "} + {formattedTime} + + + +
+ + +
+ ); + }) + )} +
+ ); +}; + +export default AllContributions; diff --git a/client/src/pages/home/components/styles.scss b/client/src/pages/home/components/styles.scss new file mode 100644 index 0000000..7397420 --- /dev/null +++ b/client/src/pages/home/components/styles.scss @@ -0,0 +1,40 @@ +.email-item { + padding: 1.5rem; + border-radius: 8px; + margin-top: 1rem; + border-bottom: 3px solid #f0f0f0; +} + +.email-content { + word-wrap: break-word; + overflow-wrap: break-word; + word-break: break-all; +} + +.copy-button { + border: none; + background: none; + cursor: pointer; +} + +.copied-text { + color: #4caf50; + margin-right: 0.5rem; +} + +@media (max-width: 768px) { + .email-item { + padding: 1rem; + } + .email-content { + font-size: 0.9rem; + } + .copy-icon { + display: inline-block; + margin-left: 0.5rem; + } +} +.highlight { + background-color: yellow; + font-weight: bold; +} diff --git a/client/src/pages/home/index.tsx b/client/src/pages/home/index.tsx index a0b0a45..fce2410 100644 --- a/client/src/pages/home/index.tsx +++ b/client/src/pages/home/index.tsx @@ -1,135 +1,104 @@ -import { Container, Row, Title } from "@dataesr/dsfr-plus"; -// import ContributionsGraphByYear from "../../components/graphs/contributions-by-year"; -// import ContributionsGraphByName from "../../components/graphs/contributions-by-name"; -// import ContributionsGraphByStatus from "../../components/graphs/by-status"; -// import ContributionsGraphByTags from "../../components/graphs/by-tags"; -// import ContributionsGraphByDomains from "../../components/graphs/by-domains"; -// import ContributionsGraphByTypes from "../../components/graphs/by-types"; -// import AdminTreatmentGraph from "../../components/graphs/treatment-by-admin"; -// import AdminResponseGraph from "../../components/graphs/response-by-admin"; -// import ContributionsGraphByProductions from "../../components/graphs/by-missing-productions"; -// import CommentsGraphByTeamMember from "../../components/graphs/comment-by-team"; -// import ContributionsGraphByYearAndType from "../../components/graphs/contribution-by-type"; -// import { useState } from "react"; -// import { contactUrl, contributionUrl } from "../../config/api"; -// import ContributionData from "../../api/contribution-api/getData"; +import { Col, Container, Title } from "@dataesr/dsfr-plus"; +import SearchSection from "../contact-contributionbyobject-page/components/search-section"; +import { useState, useEffect } from "react"; +import ContributionAllDatas from "../../api/contribution-api/getAllDatas"; +import AllContributions from "./components/item"; + +const isDevelopment = import.meta.env.VITE_HEADER_TAG === "Development"; +const prodUrl = import.meta.env.VITE_BASE_API_URL; + +const url = isDevelopment ? "http://localhost:3000/api" : `${prodUrl}/api`; const Home = () => { - // const [filter, setFilter] = useState("contributions"); - // const url = filter === "object" ? contributionUrl : contactUrl; - // const { data, isLoading, isError } = ContributionData(url); - // const contributions = data?.data as { data: [] }; + const [query, setQuery] = useState([]); + const [highlightedQuery, setHighlightedQuery] = useState(""); + const [filteredData, setFilteredData] = useState([]); + const { data, isLoading, isError } = ContributionAllDatas(url); + + useEffect(() => { + if (data && data.length > 0) { + const allItems = data.flatMap((item) => item.data || []); + const combinedQuery = highlightedQuery || query.join(" "); + + if (combinedQuery.trim() !== "") { + const filtered = allItems.filter((item) => { + return ( + item.name?.toLowerCase().includes(combinedQuery.toLowerCase()) || + item.email?.toLowerCase().includes(combinedQuery.toLowerCase()) || + item.message?.toLowerCase().includes(combinedQuery.toLowerCase()) + ); + }); + + const sortedFiltered = filtered.sort((a, b) => { + return ( + new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); + }); + + setFilteredData(sortedFiltered); + } else { + const twentyFourHoursAgo = new Date(); + twentyFourHoursAgo.setHours(twentyFourHoursAgo.getHours() - 24); + + const recentContributions = allItems.filter((item) => { + const createdAt = new Date(item.created_at); + return createdAt >= twentyFourHoursAgo; + }); + + const sortedRecentContributions = recentContributions.sort((a, b) => { + return ( + new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); + }); + + setFilteredData(sortedRecentContributions); + } + } + }, [highlightedQuery, query, data]); + + const handleSearch = (value: string) => { + const trimmedValue = value.trim(); + if (trimmedValue !== "") { + setQuery((prev) => [...prev, trimmedValue]); + setHighlightedQuery(trimmedValue); + } else { + setQuery([]); + setHighlightedQuery(""); + setFilteredData([]); + } + }; + + const handleRemoveQueryItem = (item: string) => { + setQuery(query.filter((q) => q !== item)); + if (item === highlightedQuery) { + setHighlightedQuery(""); + setFilteredData([]); + } + }; return ( Bienvenue sur le Guichet numérique du DISD - - {/* - - - - - - - - - - - - - - - - - - - - - - - - {url === contributionUrl && ( - - - - )} - - - - - - - - - - - - - - - - */} - + + + Sans filtre, voici plus bas les contributions sur les dernières 24h. + + + + {filteredData.length > 0 ? ( + + ) : ( + query.length > 0 && ( +

Aucun résultat correspondant à votre recherche.

+ ) + )} + + {isLoading &&

Loading...

} + {isError &&

Oops...

}
); }; diff --git a/client/src/pages/last-mails-sent/components/styles.scss b/client/src/pages/last-mails-sent/components/styles.scss index 941a5e8..0986bbc 100644 --- a/client/src/pages/last-mails-sent/components/styles.scss +++ b/client/src/pages/last-mails-sent/components/styles.scss @@ -1,8 +1,8 @@ .email-item { - background-color: #f1f4f8; padding: 1.5rem; border-radius: 8px; margin-top: 1rem; + border-bottom: 3px solid #f0f0f0; } .email-content { diff --git a/client/src/types/index.ts b/client/src/types/index.ts index 6af08c0..d8175f6 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -16,10 +16,21 @@ export interface Contribution { modified_at?: string; threads?: Thread[]; fromApplication: string; + emails?: any; } export interface LastMailsSentProps { - data: { emails: Contribution[] }; + data: { + length: number; + map( + arg0: (email: any, index: any) => import("react/jsx-runtime").JSX.Element + ): import("react").ReactNode; + emails: Contribution[]; + }; +} + +export interface AllContributionsProps { + data: Contribution[]; } export interface ContributorSummaryProps { contributions: Contribution[]; diff --git a/server/routes/contacts/get/index.ts b/server/routes/contacts/get/index.ts index 2360690..0665317 100644 --- a/server/routes/contacts/get/index.ts +++ b/server/routes/contacts/get/index.ts @@ -25,7 +25,7 @@ getContactRoutes.get( filters.fromApplication = fromApplication; } - const limit = parseInt(max_results as string, 10) || 20; + const limit = parseInt(max_results as string, 10) || 2000; const skip = (parseInt(page as string, 10) - 1) * limit; const sortField = sort.startsWith("-") ? sort.substring(1) : sort; const sortOrder = sort.startsWith("-") ? -1 : 1; diff --git a/server/routes/contributionObjectRoutes/get/index.ts b/server/routes/contributionObjectRoutes/get/index.ts index 8bac0ba..2ebc786 100644 --- a/server/routes/contributionObjectRoutes/get/index.ts +++ b/server/routes/contributionObjectRoutes/get/index.ts @@ -17,11 +17,11 @@ getContributionObjectRoutes.get( where = "{}", sort = "created_at", page = 1, - max_results = 20, + max_results = "", } = query; const filters = JSON.parse(where as string); - const limit = parseInt(max_results as string, 10) || 20; + const limit = parseInt(max_results as string, 10) || 2000; const skip = (parseInt(page as string, 10) - 1) * limit; const sortField = sort.startsWith("-") ? sort.substring(1) : sort; diff --git a/server/routes/productions/get/index.ts b/server/routes/productions/get/index.ts index af91722..cdd5963 100644 --- a/server/routes/productions/get/index.ts +++ b/server/routes/productions/get/index.ts @@ -16,11 +16,11 @@ getProductionsRoutes.get( where = "{}", sort = "created_at", page = 1, - max_results = 20, + max_results = "", } = query; const filters = JSON.parse(where as string); - const limit = parseInt(max_results as string, 10) || 20; + const limit = parseInt(max_results as string, 10) || 2000; const skip = (parseInt(page as string, 10) - 1) * limit; const sortField = sort.startsWith("-") ? sort.substring(1) : sort; diff --git a/server/routes/remove-user/get/index.ts b/server/routes/remove-user/get/index.ts index 72288e9..cdc1a7b 100644 --- a/server/routes/remove-user/get/index.ts +++ b/server/routes/remove-user/get/index.ts @@ -17,11 +17,11 @@ getRemoveUserRoutes.get( where = "{}", sort = "created_at", page = 1, - max_results = 20, + max_results = "", } = query; const filters = JSON.parse(where as string); - const limit = parseInt(max_results as string, 10) || 20; + const limit = parseInt(max_results as string, 10) || 2000; const skip = (parseInt(page as string, 10) - 1) * limit; const sortField = sort.startsWith("-") ? sort.substring(1) : sort; diff --git a/server/routes/update-user-data/get/index.ts b/server/routes/update-user-data/get/index.ts index ab271a0..7c63e29 100644 --- a/server/routes/update-user-data/get/index.ts +++ b/server/routes/update-user-data/get/index.ts @@ -17,11 +17,11 @@ getUpdateUserDataRoutes.get( where = "{}", sort = "created_at", page = 1, - max_results = 20, + max_results = "", } = query; const filters = JSON.parse(where as string); - const limit = parseInt(max_results as string, 10) || 20; + const limit = parseInt(max_results as string, 10) || 2000; const skip = (parseInt(page as string, 10) - 1) * limit; const sortField = sort.startsWith("-") ? sort.substring(1) : sort;