From f0a45fe84065ef0d5a91a5b57fc05eec69a04ae8 Mon Sep 17 00:00:00 2001 From: Mihoub Date: Thu, 6 Jun 2024 08:59:57 +0200 Subject: [PATCH] feat(request): add request to get contributors name from scanR --- package-lock.json | 108 +++++++++++++++++- package.json | 6 +- src/api/contribution-api/getNames.tsx | 53 +++++++++ .../contribution-api/getNamesFromIdref.tsx | 40 +++++++ src/components/edit-modal/index.tsx | 5 +- .../contribution-production-card.tsx | 8 +- .../contributor-production-info.tsx | 6 +- .../contributor-requests.tsx | 50 ++++---- .../api-operation-page/export-to-xlsx.tsx | 43 +++++++ .../api-operation-page/external-links.tsx | 22 ++++ src/pages/api-operation-page/index.tsx | 7 +- .../api-operation-page/message-preview.tsx | 43 ++++++- .../api-operation-page/name-selector.tsx | 31 +++++ src/pages/api-operation-page/styles.scss | 16 +++ src/types/index.ts | 11 +- 15 files changed, 399 insertions(+), 50 deletions(-) create mode 100644 src/api/contribution-api/getNames.tsx create mode 100644 src/api/contribution-api/getNamesFromIdref.tsx create mode 100644 src/pages/api-operation-page/export-to-xlsx.tsx create mode 100644 src/pages/api-operation-page/external-links.tsx create mode 100644 src/pages/api-operation-page/name-selector.tsx diff --git a/package-lock.json b/package-lock.json index 5f47b6d..b7fec1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^5.2.1", "react-query": "^3.39.3", "react-router-dom": "^6.11.1", "react-select": "^5.8.0", "react-spinners": "^0.13.8", "react-toastify": "^10.0.5", - "sib-api-v3-sdk": "^8.5.0" + "sib-api-v3-sdk": "^8.5.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/node": "^20.12.7", @@ -3302,6 +3304,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3848,6 +3858,18 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3910,6 +3932,14 @@ "node": ">=6" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3999,6 +4029,17 @@ "node": ">=10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4641,6 +4682,14 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5766,6 +5815,14 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6277,6 +6334,17 @@ "source-map": "^0.5.6" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -6697,11 +6765,47 @@ "node": ">= 8" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -6729,4 +6833,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 991edea..8ea6195 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,14 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^5.2.1", "react-query": "^3.39.3", "react-router-dom": "^6.11.1", "react-select": "^5.8.0", "react-spinners": "^0.13.8", "react-toastify": "^10.0.5", - "sib-api-v3-sdk": "^8.5.0" + "sib-api-v3-sdk": "^8.5.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/node": "^20.12.7", @@ -43,4 +45,4 @@ "typescript": "^5.2.2", "vite": "^5.0.4" } -} +} \ No newline at end of file diff --git a/src/api/contribution-api/getNames.tsx b/src/api/contribution-api/getNames.tsx new file mode 100644 index 0000000..6fd9a1c --- /dev/null +++ b/src/api/contribution-api/getNames.tsx @@ -0,0 +1,53 @@ +import { useQuery } from "@tanstack/react-query"; +import { postHeaders } from "../../config/api"; + +const NameFromScanr = (id) => { + const url = + "https://scanr.enseignementsup-recherche.gouv.fr/api/scanr-publications/_search"; + + const fetchContributions = async () => { + const body = { + _source: ["authors"], + query: { + bool: { + filter: [ + { + term: { + "id.keyword": id, + }, + }, + ], + }, + }, + }; + + const response = await fetch(url, { + method: "POST", + headers: postHeaders, + body: JSON.stringify(body), + }); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }; + + const { data, isLoading, isError, refetch } = useQuery( + [url, id], + fetchContributions + ); + const fullName = + data?.hits?.hits[0]?._source?.authors.map((author) => author.fullName) || + []; + const firstName = + data?.hits?.hits[0]?._source?.authors.map((author) => author.firstName) || + []; + + const lastName = + data?.hits?.hits[0]?._source?.authors.map((author) => author.lastName) || + []; + + return { fullName, firstName, lastName, isLoading, isError, refetch }; +}; + +export default NameFromScanr; diff --git a/src/api/contribution-api/getNamesFromIdref.tsx b/src/api/contribution-api/getNamesFromIdref.tsx new file mode 100644 index 0000000..c2ad7c1 --- /dev/null +++ b/src/api/contribution-api/getNamesFromIdref.tsx @@ -0,0 +1,40 @@ +import { useQuery } from "@tanstack/react-query"; +import { postHeaders } from "../../config/api"; + +const NameFromIdref = (id) => { + const url = + "https://scanr.enseignementsup-recherche.gouv.fr/api/scanr-persons/_search"; + + const fetchContributions = async () => { + const body = { + _source: ["id", "fullName"], + query: { + bool: { + filter: [ + { + term: { + id: id, + }, + }, + ], + }, + }, + }; + + const response = await fetch(url, { + method: "POST", + headers: postHeaders, + body: JSON.stringify(body), + }); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }; + + const { data, refetch } = useQuery([url, id], fetchContributions); + const fullNameFromIdref = data?.hits?.hits[0]?._source?.fullName || ""; + return { fullNameFromIdref, refetch }; +}; + +export default NameFromIdref; diff --git a/src/components/edit-modal/index.tsx b/src/components/edit-modal/index.tsx index 0ad3552..8b61ff2 100644 --- a/src/components/edit-modal/index.tsx +++ b/src/components/edit-modal/index.tsx @@ -36,9 +36,8 @@ const EditModal: React.FC = ({ } const isDevelopment = import.meta.env.VITE_HEADER_TAG === "Development"; const url = isDevelopment - ? `${window.location.origin}/api/${basePath}/${data._id}` - : `https://your-production-url/api/${basePath}/${data._id}`; - + ? `https://scanr-api.dataesr.ovh/${basePath}/${data._id}` + : `${window.location.origin}/api/${basePath}/${data._id}`; const [inputs, setInputs] = useState({ team: [user], status: "treated", diff --git a/src/pages/api-operation-page/contribution-production-card.tsx b/src/pages/api-operation-page/contribution-production-card.tsx index 1072c9b..5e7fe56 100644 --- a/src/pages/api-operation-page/contribution-production-card.tsx +++ b/src/pages/api-operation-page/contribution-production-card.tsx @@ -15,9 +15,11 @@ import StaffProductionActions from "./staff-production-action"; const ContributionProductionItem = ({ data, refetch, + setDataList, }: { data: Contribute_Production; refetch; + setDataList; }) => { const renderAccordion = () => ( @@ -66,7 +68,11 @@ const ContributionProductionItem = ({ return ( - + diff --git a/src/pages/api-operation-page/contributor-production-info.tsx b/src/pages/api-operation-page/contributor-production-info.tsx index 1e74b33..0cbc761 100644 --- a/src/pages/api-operation-page/contributor-production-info.tsx +++ b/src/pages/api-operation-page/contributor-production-info.tsx @@ -4,11 +4,15 @@ import MessagePreview from "./message-preview"; const ContributorProductionInfo = ({ data, refetch, + setDataList, }: { data: Contribute_Production; refetch; + setDataList; }) => { - return ; + return ( + + ); }; export default ContributorProductionInfo; diff --git a/src/pages/api-operation-page/contributor-requests.tsx b/src/pages/api-operation-page/contributor-requests.tsx index f8a4aa4..34fdf98 100644 --- a/src/pages/api-operation-page/contributor-requests.tsx +++ b/src/pages/api-operation-page/contributor-requests.tsx @@ -1,10 +1,17 @@ -import { Col, Link, Text } from "@dataesr/dsfr-plus"; -import React, { useState } from "react"; +import { Col, Row, Text } from "@dataesr/dsfr-plus"; +import React, { ReactNode, useState } from "react"; import { Production } from "../../types"; +import { ExternalLinks } from "./external-links"; +import SelectWithNames from "./name-selector"; const ContributorRequests: React.FC<{ - data: { productions: Production[] }; -}> = ({ data }) => { + data: { + id: any; + name: ReactNode; + productions: Production[]; + }; + setDataList; +}> = ({ data, setDataList }) => { const [copiedId, setCopiedId] = useState(null); const copyToClipboard = (text: string) => { @@ -46,32 +53,17 @@ const ContributorRequests: React.FC<{ )} - - - scanR - - - Paysage - - - dataESR - - + + + + + + ))} diff --git a/src/pages/api-operation-page/export-to-xlsx.tsx b/src/pages/api-operation-page/export-to-xlsx.tsx new file mode 100644 index 0000000..64dc746 --- /dev/null +++ b/src/pages/api-operation-page/export-to-xlsx.tsx @@ -0,0 +1,43 @@ +import { Button, Text } from "@dataesr/dsfr-plus"; +import * as XLSX from "xlsx"; +import { AiOutlineDelete } from "react-icons/ai"; + +const ExcelExportButton = ({ dataList, setDataList }) => { + const handleExportClick = () => { + const dataToExport = dataList.map((item) => ({ + person_id: item.person_id || "", + publi_id: item.publi_id || "", + full_name: item.fullName || "", + first_name: item.first_name || "", + last_name: item.last_name || "", + })); + const worksheet = XLSX.utils.json_to_sheet(dataToExport); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1"); + XLSX.writeFile(workbook, "export.xlsx"); + }; + const handleRemoveClick = (index: number) => { + setDataList((prevDataList) => prevDataList.filter((_, i) => i !== index)); + }; + + return ( +
+
    + {Array.isArray(dataList) && + dataList.map((item, index) => ( +
  • + + + Publication: {item.publi_id} - Nom validé: {item.fullName} + +
  • + ))} +
+ +
+ ); +}; + +export default ExcelExportButton; diff --git a/src/pages/api-operation-page/external-links.tsx b/src/pages/api-operation-page/external-links.tsx new file mode 100644 index 0000000..4859e51 --- /dev/null +++ b/src/pages/api-operation-page/external-links.tsx @@ -0,0 +1,22 @@ +import { Link } from "@dataesr/dsfr-plus"; + +export const ExternalLinks = ({ productionId, name }) => ( + <> + + scanR + + + Google + + +); diff --git a/src/pages/api-operation-page/index.tsx b/src/pages/api-operation-page/index.tsx index 40abf23..0b04e8a 100644 --- a/src/pages/api-operation-page/index.tsx +++ b/src/pages/api-operation-page/index.tsx @@ -15,6 +15,7 @@ import TopPaginationButtons from "../../components/pagination/top-buttons"; import ContributionProductionItem from "./contribution-production-card"; import ContributionData from "../../api/contribution-api/getData"; import { buildURL } from "../../api/utils/buildURL"; +import ExcelExportButton from "./export-to-xlsx"; const ContributionPage: React.FC = () => { const [reload] = useState(0); @@ -23,7 +24,6 @@ const ContributionPage: React.FC = () => { const [query, setQuery] = useState(""); const [page, setPage] = useState(1); const [, setData] = useState(null); - const location = useLocation(); useEffect(() => { @@ -61,7 +61,8 @@ const ContributionPage: React.FC = () => { useEffect(() => { setData(fetchedData); }, [fetchedData]); - + const [dataList, setDataList] = useState([]); + console.log(dataList); const meta = (fetchedData as { meta: any })?.meta; const maxPage = meta ? Math.ceil(meta?.total / 10) : 1; const contrib: Contribute_Production[] = ( @@ -126,8 +127,10 @@ const ContributionPage: React.FC = () => { ))} + { const [showModal, setShowModal] = useState(false); const [copySuccess, setCopySuccess] = useState(""); + const { fullNameFromIdref: fetchedData } = NameFromIdref(data.id); + const copyToClipboard = useCallback((text, successMessage) => { navigator.clipboard.writeText(text).then(() => { setCopySuccess(successMessage); @@ -55,7 +62,7 @@ const MessagePreview = ({ onClick={() => copyToClipboard(data.id, "ID copié")} className={"fr-icon-user-line"} > - ID de l'objet concerné: {data.id} + ID de la personne concerné: {data.id}{" "} {copySuccess === "ID copié" && ( )} - - {data.name ? `Nom: ${data.name}` : "Nom non renseigné"} + + IdRef + + copyToClipboard(fetchedData, "Nom copié")} + > + {fetchedData ? `${fetchedData}` : "Nom inéxistant sur scanR"} + {copySuccess === "Nom copié" && ( + + {copySuccess} + + )} {data.email && ( - + diff --git a/src/pages/api-operation-page/name-selector.tsx b/src/pages/api-operation-page/name-selector.tsx new file mode 100644 index 0000000..460a2b5 --- /dev/null +++ b/src/pages/api-operation-page/name-selector.tsx @@ -0,0 +1,31 @@ +import NameFromScanr from "../../api/contribution-api/getNames"; + +export default function SelectWithNames({ productionId, setDataList, idRef }) { + const { fullName, firstName, lastName } = NameFromScanr(productionId); + const handleChange = (event) => { + const selectedName = event.target.value; + const selectedIndex = fullName.indexOf(selectedName); + + setDataList((prevState) => [ + ...prevState, + { + fullName: selectedName, + person_id: idRef, + publi_id: productionId, + first_name: firstName[selectedIndex], + last_name: lastName[selectedIndex], + }, + ]); + }; + + return ( + + ); +} diff --git a/src/pages/api-operation-page/styles.scss b/src/pages/api-operation-page/styles.scss index 57d400d..1f15728 100644 --- a/src/pages/api-operation-page/styles.scss +++ b/src/pages/api-operation-page/styles.scss @@ -1,6 +1,7 @@ .contributorProductionSideInfo { display: flex; justify-content: center; + justify-content: space-evenly; } .contributorProductionSide { display: flex; @@ -18,3 +19,18 @@ .textInCard { text-align: center; } +.basket { + position: fixed; + right: 0; + top: 0; + padding: 20px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin: 20px; + z-index: 1000; + background-color: white; + border-radius: 20px; + border: 2px solid black; +} diff --git a/src/types/index.ts b/src/types/index.ts index 8ada623..305f717 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,15 +5,15 @@ export type Contribution = { responseFrom: string; modified_at: string; team: any; - _id: any; + _id: string; status: string; tags: Array; message: string; created_at: string | number | Date; - fonction: any; - organisation: any; - email: any; - name: any; + fonction: string; + organisation: string; + email: string; + name: string; type: string; id: string; comment: string; @@ -83,6 +83,7 @@ export type EditModalProps = { }; export type Production = { + name: string; id: string; treated: boolean; };