diff --git a/packages/app/pages/mandataires/enquetes/import-[id].js b/packages/app/pages/mandataires/enquetes/import-[id].js new file mode 100644 index 0000000000..8773ee017c --- /dev/null +++ b/packages/app/pages/mandataires/enquetes/import-[id].js @@ -0,0 +1,24 @@ +import { BoxWrapper } from "@emjpm/ui"; +import React, { useContext } from "react"; + +import { EnqueteImportPanel } from "../../../src/components/EnqueteImport"; +import { LayoutMandataire } from "../../../src/components/Layout"; +import { UserContext } from "../../../src/components/UserContext"; +import { withAuthSync } from "../../../src/util/auth"; + +const ImportEnquetePage = () => { + const user = useContext(UserContext); + return ( + + + + + + ); +}; + +ImportEnquetePage.getInitialProps = async ({ query }) => { + return { id: Number(query.id) }; +}; + +export default withAuthSync(ImportEnquetePage); diff --git a/packages/app/pages/mandataires/import-mesures.js b/packages/app/pages/mandataires/import-mesures.js deleted file mode 100644 index c2ecb17911..0000000000 --- a/packages/app/pages/mandataires/import-mesures.js +++ /dev/null @@ -1,20 +0,0 @@ -import { BoxWrapper } from "@emjpm/ui"; -import React, { useContext } from "react"; - -import { LayoutMandataire } from "../../src/components/Layout"; -import { MandataireMesureImport } from "../../src/components/MandataireMesureImport"; -import { UserContext } from "../../src/components/UserContext"; -import { withAuthSync } from "../../src/util/auth"; - -const ImportMesures = () => { - const user = useContext(UserContext); - return ( - - - - - - ); -}; - -export default withAuthSync(ImportMesures); diff --git a/packages/app/src/components/EnqueteImport/DocumentLink.js b/packages/app/src/components/EnqueteImport/DocumentLink.js new file mode 100644 index 0000000000..77dece9f12 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/DocumentLink.js @@ -0,0 +1,25 @@ +import React from "react"; +import { Flex } from "rebass"; + +const DocumentLink = ({ children, document }) => ( + + + {children} + + +); + +export { DocumentLink }; diff --git a/packages/app/src/components/EnqueteImport/EnqueteImportErrors.js b/packages/app/src/components/EnqueteImport/EnqueteImportErrors.js new file mode 100644 index 0000000000..b943277ba4 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/EnqueteImportErrors.js @@ -0,0 +1,25 @@ +import { Card, Heading3 } from "@emjpm/ui"; +import React from "react"; +import { Flex, Text } from "rebass"; + +import { importErrorsWrapperStyle } from "./style"; + +const EnqueteImportErrors = props => { + const { errors } = props; + + return ( + + Détail des erreurs par ligne + {errors.map(({ line = 0, message }) => ( + + + LIGNE {line} + + {message} + + ))} + + ); +}; + +export { EnqueteImportErrors }; diff --git a/packages/app/src/components/EnqueteImport/EnqueteImportPanel.js b/packages/app/src/components/EnqueteImport/EnqueteImportPanel.js new file mode 100644 index 0000000000..9ae2532571 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/EnqueteImportPanel.js @@ -0,0 +1,58 @@ +import { LoaderCircle } from "@styled-icons/boxicons-regular/LoaderCircle"; +import React, { Fragment } from "react"; +import { Box } from "rebass"; + +import { EnqueteImportResult } from "./EnqueteImportResult"; +import { SingleImportFilePicker } from "./SingleImportFilePicker"; +import { useEnqueteImportManager } from "./useEnqueteImportManager.hook"; + +export const EnqueteImportPanel = ({ + // mandataireUserId & serviceId are mutually exclusive + mandataireUserId, + serviceId +}) => { + const { + importEnqueteFile, + importSummary, + importEnqueteFileWithAntennesMap, + reset, + enquetesImportLoading + } = useEnqueteImportManager({ + mandataireUserId, + serviceId + }); + + if (enquetesImportLoading) { + return ( + + Traitement du fichier en cours, veuillez patienter (cela peut + prendre jusqu'à 2mn pour les gros fichiers)... + + ); + } + + if (importSummary && !importSummary.unexpectedError) { + return ( + reset()} + importSummary={importSummary} + serviceId={serviceId} + onSubmitAntennesMap={({ antennesMap }) => importEnqueteFileWithAntennesMap(antennesMap)} + /> + ); + } + + return ( + + {importSummary && importSummary.unexpectedError && ( + + Erreur innatendue. Veuillez ré-essayer. + + )} + importEnqueteFile(file)} + /> + + ); +}; diff --git a/packages/app/src/components/EnqueteImport/EnqueteImportResult.js b/packages/app/src/components/EnqueteImport/EnqueteImportResult.js new file mode 100644 index 0000000000..b6def7de01 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/EnqueteImportResult.js @@ -0,0 +1,37 @@ +import { Button, Heading2, Text } from "@emjpm/ui"; +import React from "react"; +import { Flex } from "rebass"; + +import { EnqueteImportErrors } from "./EnqueteImportErrors"; +import { ServiceEnqueteImportResultStyle } from "./style"; + +const EnqueteImportResult = ({ + reset, + importSummary: { creationNumber, updateNumber, errors } +}) => { + return ( +
+ + + {`Résultat de l'import`} + {errors.length ? ( + {`Erreur lors de l'import des enquetes (${ + errors.length + } erreurs sur ${errors.length + + creationNumber + + updateNumber} enquetes). Aucune enquete n'a été importée.`} + ) : ( + {`${creationNumber + + updateNumber} enquetes ont été importées (${creationNumber} nouvelles et ${updateNumber} mises à jour).`} + )} + + + + {!!errors.length && } +
+ ); +}; + +export { EnqueteImportResult }; diff --git a/packages/app/src/components/EnqueteImport/EnqueteImportResults.js b/packages/app/src/components/EnqueteImport/EnqueteImportResults.js new file mode 100644 index 0000000000..f66af695f1 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/EnqueteImportResults.js @@ -0,0 +1,34 @@ +import { Button, Heading2, Text } from "@emjpm/ui"; +import React, { Fragment } from "react"; +import { Flex } from "rebass"; + +import { EnqueteImportErrors } from "./EnqueteImportErrors"; +import { ServiceEnqueteImportResultStyle } from "./style"; + +const EnqueteImportResults = props => { + const { errors, enquetes, reset } = props; + + return ( + + + + {`Résultat de l'import`} + {`${enquetes.length} enquetes vont être importées ou mises à jour. Vous allez recevoir un email dans quelques instants.`} + {`${errors.length} enquetes ne seront pas importées ou mises à jour. Les erreurs sont indiquées ci-dessous.`} + + + + {errors.length > 0 && } + + ); +}; + +export { EnqueteImportResults }; diff --git a/packages/app/src/components/EnqueteImport/SingleImportFilePicker.js b/packages/app/src/components/EnqueteImport/SingleImportFilePicker.js new file mode 100644 index 0000000000..0db4ae6992 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/SingleImportFilePicker.js @@ -0,0 +1,23 @@ +import { Input } from "@emjpm/ui"; +import React from "react"; +import { Box } from "rebass"; + +const SingleImportFilePicker = ({ onFileChosen, placeholder }) => { + return ( + + { + const file = event.currentTarget.files[0]; + onFileChosen(file); + }} + placeholder={placeholder} + /> + + ); +}; + +export { SingleImportFilePicker }; diff --git a/packages/app/src/components/EnqueteImport/fileReader.js b/packages/app/src/components/EnqueteImport/fileReader.js new file mode 100644 index 0000000000..a8ac45e89b --- /dev/null +++ b/packages/app/src/components/EnqueteImport/fileReader.js @@ -0,0 +1,29 @@ +function readFileAsBinaryString(file, cb, err) { + if (file) { + const reader = new FileReader(); + + const isExcel = file.name.endsWith(".xls") || file.name.endsWith(".xlsx"); + + if (isExcel) { + reader.readAsBinaryString(file); + } else { + reader.readAsText(file); + } + reader.onload = () => { + const base64str = btoa(reader.result); + if (cb) { + cb({ base64str, file }); + } + }; + reader.onerror = () => { + console.error("unable to parse file"); + if (err) { + err(new Error("Unable to parse file")); + } + }; + } +} + +export const fileReader = { + readFileAsBinaryString +}; diff --git a/packages/app/src/components/EnqueteImport/index.js b/packages/app/src/components/EnqueteImport/index.js new file mode 100644 index 0000000000..ce2f9f326a --- /dev/null +++ b/packages/app/src/components/EnqueteImport/index.js @@ -0,0 +1 @@ +export { EnqueteImportPanel } from "./EnqueteImportPanel"; diff --git a/packages/app/src/components/EnqueteImport/mutations.js b/packages/app/src/components/EnqueteImport/mutations.js new file mode 100644 index 0000000000..2f93c8d266 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/mutations.js @@ -0,0 +1,23 @@ +import gql from "graphql-tag"; + +export const UPLOAD_ENQUETES_EXCEL_FILE = gql` + mutation upload_enquetes_file( + $name: String! + $type: String! + $base64str: String! + $antennesMap: String + $serviceId: Int + $mandataireUserId: Int + ) { + upload_enquetes_file( + name: $name + type: $type + base64str: $base64str + antennesMap: $antennesMap + serviceId: $serviceId + mandataireUserId: $mandataireUserId + ) { + data + } + } +`; diff --git a/packages/app/src/components/EnqueteImport/style.js b/packages/app/src/components/EnqueteImport/style.js new file mode 100644 index 0000000000..bc52290423 --- /dev/null +++ b/packages/app/src/components/EnqueteImport/style.js @@ -0,0 +1,17 @@ +const ServiceEnqueteImportStyle = {}; + +const ServiceEnqueteImportResultStyle = { + border: "1px solid", + borderRadius: "3px" +}; + +const importErrorsWrapperStyle = { + bg: "cardSecondary", + borderRadius: "5px 0 0 5px", + m: "2", + p: "5", + maxHeight: "500px", + overflow: "auto" +}; + +export { ServiceEnqueteImportStyle, ServiceEnqueteImportResultStyle, importErrorsWrapperStyle }; diff --git a/packages/app/src/components/EnqueteImport/useEnqueteImportManager.hook.js b/packages/app/src/components/EnqueteImport/useEnqueteImportManager.hook.js new file mode 100644 index 0000000000..6885cc28db --- /dev/null +++ b/packages/app/src/components/EnqueteImport/useEnqueteImportManager.hook.js @@ -0,0 +1,54 @@ +import { useMutation } from "@apollo/react-hooks"; +import { useState } from "react"; + +import { UPLOAD_ENQUETES_EXCEL_FILE } from "../EnqueteImport/mutations"; +import { fileReader } from "./fileReader"; + +function useEnqueteImportManager({ mandataireUserId, serviceId }) { + const [importSummary, setImportSummary] = useState(); + const [uploadFile, { loading: enquetesImportLoading }] = useMutation(UPLOAD_ENQUETES_EXCEL_FILE); + + function importEnqueteFile({ file, antennesMap }) { + fileReader.readFileAsBinaryString(file, ({ file, base64str }) => { + const { name, type } = file; + uploadFile({ + variables: { + base64str, + name, + serviceId, + antennesMap: antennesMap ? JSON.stringify(antennesMap) : undefined, + type, + mandataireUserId + } + }) + .then( + ({ + data: { + upload_enquetes_file: { data } + } + }) => { + const importSummary = JSON.parse(data); + setImportSummary(importSummary); + } + ) + .catch(() => { + setImportSummary({ + unexpectedError: true + }); + }); + }); + } + + function reset() { + setImportSummary(undefined); + } + + return { + importEnqueteFile, + importSummary, + enquetesImportLoading, + reset + }; +} + +export { useEnqueteImportManager };