From 06bc7ec2d3a67df11492642939583ac435576447 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Wed, 18 Dec 2024 16:06:20 +0100 Subject: [PATCH 01/18] Update docs with JSON import --- src/client/docs/import.md | 52 +++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/client/docs/import.md b/src/client/docs/import.md index 1e70e34b2..a0118bb9c 100644 --- a/src/client/docs/import.md +++ b/src/client/docs/import.md @@ -1,8 +1,8 @@ # Bohrdaten importieren -Mit der Import-Funktion können geologische Bohrdaten aus einer CSV-Datei importiert werden. +Mit der Import-Funktion können geologische Bohrdaten via CSV oder JSON Dateien importiert werden. -## Anleitung +## Anleitung CSV-Import ### Schritt 1: CSV-Datei vorbereiten @@ -34,7 +34,7 @@ Die CSV-Datei muss den folgenden Anforderungen und dem Format entsprechen, damit - Die Werte in den Spalten müssen den erwarteten Datentypen entsprechen (z.B. numerisch für Tiefe, Text für Namen, etc.). -## Bohrloch Datei Format +## Bohrloch Datei CSV Format Die zu importierenden Daten müssen gemäss obigen Anforderungen im CSV-Format vorliegen. Die erste Zeile wird als Spaltentitel/Spaltenname interpretiert, die restlichen Zeilen als Daten. @@ -80,7 +80,7 @@ Die zu importierenden Daten müssen gemäss obigen Anforderungen im CSV-Format v ## Validierung -### Fehlende Werte +### CSV-Import: Fehlende Werte Für jeden bereitgestellten Header CSV-Datei muss für jede Zeile ein entsprechender Wert angegeben werden, oder leer gelassen werden. @@ -90,6 +90,48 @@ Beim Importprozess der Bohrdaten wird eine Duplikatsvalidierung durchgeführt, u Duplikate werden nur innerhalb einer Arbeitsgruppe erkannt. Die Duplikaterkennung erfolgt anhand der Koordinaten mit einer Toleranz von +/- 2 Metern und der Gesamttiefe des Bohrlochs. -## Generelles +## Anmerkungen + +Es ist wichtig zu beachten, dass der Import beim ersten Fehler abgebrochen wird und keine teilweisen Importe stattfinden. Entweder werden alle Daten importiert, oder es findet kein Import statt. Der Import unterstützt keine Updates von bestehenden Daten. + + +## Anleitung JSON-Import + +### Schritt 1: JSON-Datei vorbereiten + +Zunächst sollte die JSON-Datei den Anforderungen und dem Format entsprechen, wie im Abschnitt [Format und Anforderungen an die JSON-Datei](#format-und-anforderungen-an-die-json-datei) beschrieben. + +### Schritt 2: Navigieren zum Import-Bereich + +1. In der Webapplikation anmelden. +2. Unten links auf die Schaltfläche _Importieren_ klicken. + +### Schritt 3: Bohrloch JSON-Datei selektieren + +1. Schaltfläche _Datei auswählen_ anklicken und die vorbereitete JSON-Datei auswählen. +2. Unter _Arbeitsgruppe_ die Arbeitsgruppe auswählen, in welche die Bohrdaten importiert werden sollen (neue Arbeitsgruppen können nur als "Admin-User" erstellt werden). + +### Schritt 4: Dateien hochladen + +1. Import-Prozess mit einem Klick auf _Importieren_ starten. +2. Warten, bis der Upload abgeschlossen ist und die Daten in der Anwendung verfügbar sind. + +## Format und Anforderungen an die JSON-Datei + +Die JSON-Datei muss den folgenden Anforderungen entsprechen, damit sie erfolgreich in die Webapplikation importiert werden kann: + +- Die Datei muss im JSON-Format vorliegen. +- Die Datei muss im UTF-8-Format gespeichert sein. +- Die JSON-Datei muss ein Array von Objekten enthalten. Jedes Objekt entspricht einem Bohrloch. Auch ein einzelnes Bohrloch muss als Array von einem Objekt definiert werden. +- Die JSON-Datei eines Bohrlochexports kann als valide Vorlage für den Import betrachtet werden. + +## Validierung + +### Duplikate + +Beim Importprozess der Bohrdaten wird eine Duplikatsvalidierung durchgeführt, um sicherzustellen, dass kein Bohrloch mehrmals in der Datei vorhanden ist oder bereits in der Datenbank existiert. +Duplikate werden nur innerhalb einer Arbeitsgruppe erkannt. Die Duplikaterkennung erfolgt anhand der Koordinaten mit einer Toleranz von +/- 2 Metern und der Gesamttiefe des Bohrlochs. + +## Anmerkungen Es ist wichtig zu beachten, dass der Import beim ersten Fehler abgebrochen wird und keine teilweisen Importe stattfinden. Entweder werden alle Daten importiert, oder es findet kein Import statt. Der Import unterstützt keine Updates von bestehenden Daten. From b83e471edaae975be9585487d8a3d5b85db9981a Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Wed, 18 Dec 2024 16:08:08 +0100 Subject: [PATCH 02/18] Translation text for JSON import --- src/client/public/locale/de/common.json | 2 ++ src/client/public/locale/en/common.json | 2 ++ src/client/public/locale/fr/common.json | 2 ++ src/client/public/locale/it/common.json | 2 ++ src/client/src/api/borehole.ts | 4 ++++ .../src/pages/detail/attachments/fileDropzone.jsx | 11 +++++++++-- 6 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/client/public/locale/de/common.json b/src/client/public/locale/de/common.json index cfa7a1987..3be4513e3 100644 --- a/src/client/public/locale/de/common.json +++ b/src/client/public/locale/de/common.json @@ -138,6 +138,7 @@ "dropZoneFileToLarge": "Eine oder mehrere Dateien sind zu gross. Max. 200 MB.", "dropZoneGeometryText": "CSV Datei mit Geometriedaten hier ablegen oder klicken, um sie hochzuladen", "dropZoneInvalidFileType": "Eine oder mehrere Dateien haben einen nicht unterstützten Dateityp", + "dropZoneJsonText": "Wählen Sie eine Borehole JSON Datei aus", "dropZoneLithologyText": "CSV Datei mit Lithologien hier ablegen oder klicken, um sie hochzuladen", "dropZoneMaxFilesToUploadReached": "Maximale Anzahl von Dateien zum Hochladen erreicht", "dropZoneMaximumFilesToSelectAtOnce": "Zu viele Dateien gleichzeitig ausgewählt", @@ -254,6 +255,7 @@ "identifier_value": "ID Code", "import": "Importieren", "importBoreholeAttachment": "Wählen Sie eine oder mehrere Datei(en) aus:", + "importBoreholeJson": "Wählen Sie eine JSON Datei aus:", "importDate": "Importdatum", "importedBy": "Importiert von", "importedData": "Importierte Daten", diff --git a/src/client/public/locale/en/common.json b/src/client/public/locale/en/common.json index 0b4161c45..044ce60e6 100644 --- a/src/client/public/locale/en/common.json +++ b/src/client/public/locale/en/common.json @@ -138,6 +138,7 @@ "dropZoneFileToLarge": "One ore more files are too large. Max. 200 MB.", "dropZoneGeometryText": "Drag and drop a CSV file with the geometry data here or click to upload", "dropZoneInvalidFileType": "One or more files have unsupported file type", + "dropZoneJsonText": "Select a borehole JSON file", "dropZoneLithologyText": "Drag and drop a CSV file with the lithologies here or click to upload", "dropZoneMaxFilesToUploadReached": "Maximum number of files to upload reached", "dropZoneMaximumFilesToSelectAtOnce": "To many files selected", @@ -254,6 +255,7 @@ "identifier_value": "ID Code", "import": "Import", "importBoreholeAttachment": "Select one or multiple file(s) to upload:", + "importBoreholeJson": "Select a JSON file:", "importDate": "Import date", "importedBy": "Imported by", "importedData": "Imported data", diff --git a/src/client/public/locale/fr/common.json b/src/client/public/locale/fr/common.json index e81e7c5a5..6ac4979b4 100644 --- a/src/client/public/locale/fr/common.json +++ b/src/client/public/locale/fr/common.json @@ -138,6 +138,7 @@ "dropZoneFileToLarge": "Un ou plusieurs fichiers sont trop volumineux. Max. 200 Mo.", "dropZoneGeometryText": "Déposez le fichier CSV avec les géométries ici ou cliquez pour les télécharger", "dropZoneInvalidFileType": "Un ou plusieurs fichiers ont un type de fichier non pris en charge", + "dropZoneJsonText": "Sélectionnez un fichier JSON de forage", "dropZoneLithologyText": "Déposez le fichier CSV avec les lithologies ici ou cliquez pour le télécharger", "dropZoneMaxFilesToUploadReached": "Nombre maximum de fichiers à télécharger atteint", "dropZoneMaximumFilesToSelectAtOnce": "Trop de fichiers sélectionnés en même temps", @@ -254,6 +255,7 @@ "identifier_value": "Code d'ID", "import": "Importer", "importBoreholeAttachment": "Sélectionnez un ou plusieurs fichiers à télécharger:", + "importBoreholeJson": "Sélectionnez un fichier JSON:", "importDate": "Date d'importation", "importedBy": "Importé par", "importedData": "Données importées", diff --git a/src/client/public/locale/it/common.json b/src/client/public/locale/it/common.json index b3491680e..d39499fdb 100644 --- a/src/client/public/locale/it/common.json +++ b/src/client/public/locale/it/common.json @@ -138,6 +138,7 @@ "dropZoneFileToLarge": "Uno o più file sono troppo grandi. Max. 200 MB.", "dropZoneGeometryText": "Trascina qui il file CSV con la geometria o clicca per caricarlo CSV", "dropZoneInvalidFileType": "Uno o più file hanno un tipo non supportato", + "dropZoneJsonText": "Seleziona un file JSON di trivellazione", "dropZoneLithologyText": "Trascina qui il file CSV con le litologie o clicca per caricarlo CSV", "dropZoneMaxFilesToUploadReached": "Raggiunto il numero massimo di file da caricare", "dropZoneMaximumFilesToSelectAtOnce": "Troppi file selezionati contemporaneamente", @@ -254,6 +255,7 @@ "identifier_value": "Codice di ID", "import": "Importare", "importBoreholeAttachment": "Seleziona uno o più file da scaricare:", + "importBoreholeJson": "Seleziona un file JSON:", "importDate": "Data di importazione", "importedBy": "Importato da", "importedData": "Dati importati", diff --git a/src/client/src/api/borehole.ts b/src/client/src/api/borehole.ts index 93427af00..1c481e53b 100644 --- a/src/client/src/api/borehole.ts +++ b/src/client/src/api/borehole.ts @@ -81,6 +81,10 @@ export const importBoreholes = async (workgroupId: string, combinedFormData: any return await upload(`import?workgroupId=${workgroupId}`, "POST", combinedFormData); }; +export const importBoreholesJson = async (workgroupId: string, combinedFormData: any) => { + return await upload(`upload/json?workgroupId=${workgroupId}`, "POST", combinedFormData); +}; + export const copyBorehole = async (boreholeId: GridRowSelectionModel, workgroupId: string | null) => { return await fetchApiV2(`borehole/copy?id=${boreholeId}&workgroupId=${workgroupId}`, "POST"); }; diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index fe93bdd68..e254514e5 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -11,9 +11,11 @@ import { theme } from "../../../AppTheme.ts"; * @param {Object} props - The component props. * @param {Function} props.onHandleFileChange - A callback function to handle file changes. The function receives the files array as an argument. * @param {string} props.defaultText - The default text to display in the dropzone. + * @param {string} props.disabledText - The text to display in the dropzone when disabled. * @param {number} props.maxFilesToSelectAtOnce - The maximum number of files that can be selected at once. * @param {number} props.maxFilesToUpload - The maximum number of files that can be uploaded. * @param {boolean} props.restrictAcceptedFileTypeToCsv - Whether to restrict accepted file types to CSV. + * @param {boolean} props.restrictAcceptedFileTypeToJson - Whether to restrict accepted file types to JSON. * @param {boolean} props.isDisabled - Whether the dropzone is disabled. * @param {string} props.dataCy - The data-cy attribute for testing. * @returns {JSX.Element} The rendered FileDropzone component. @@ -22,9 +24,11 @@ export const FileDropzone = props => { const { onHandleFileChange, defaultText, + disabledText, maxFilesToSelectAtOnce, maxFilesToUpload, restrictAcceptedFileTypeToCsv, + restrictAcceptedFileTypeToJson, isDisabled, dataCy, } = props; @@ -33,7 +37,7 @@ export const FileDropzone = props => { const [dropZoneText, setDropZoneText] = useState(null); const [dropZoneTextColor, setDropZoneTextColor] = useState(null); const defaultDropzoneTextColor = isDisabled ? "#9f9f9f" : "#2185d0"; - const initialDropzoneText = isDisabled ? t("dropZoneChooseBoreholeFilesFirst") : t(defaultText); + const initialDropzoneText = isDisabled ? t(disabledText) : t(defaultText); useEffect(() => { setDropZoneText(initialDropzoneText); @@ -113,7 +117,10 @@ export const FileDropzone = props => { onDropAccepted, maxFiles: maxFilesToSelectAtOnce || Infinity, maxSize: 209715200, - accept: restrictAcceptedFileTypeToCsv ? { "text/csv": [".csv"] } : "*", + accept: { + ...(restrictAcceptedFileTypeToCsv && { "text/csv": [".csv"] }), + ...(restrictAcceptedFileTypeToJson && { "application/json": [".json"] }), + }, noClick: isDisabled, noKeyboard: isDisabled, }); From 1565f0d992c0aaeac54939068dc7b82abba31e4e Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 09:46:03 +0100 Subject: [PATCH 03/18] Update import file dropzone text --- src/client/public/locale/de/common.json | 6 +++--- src/client/public/locale/en/common.json | 6 +++--- src/client/public/locale/fr/common.json | 6 +++--- src/client/public/locale/it/common.json | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/client/public/locale/de/common.json b/src/client/public/locale/de/common.json index 3be4513e3..8d6f426e8 100644 --- a/src/client/public/locale/de/common.json +++ b/src/client/public/locale/de/common.json @@ -132,13 +132,13 @@ "drilling_mud_type": "Bohrspülung Typ", "drilling_start_date": "Datum Bohrbeginn", "dropZoneAttachmentsText": "Datei(en) mit Anhängen hier ablegen oder klicken, um sie hochzuladen", - "dropZoneBoreholesText": "CSV Datei mit Bohrungen hier ablegen oder klicken, um sie hochzuladen", + "dropZoneBoreholeCsvText": "Wählen Sie eine Borehole CSV Datei aus", "dropZoneChooseBoreholeFilesFirst": "Wählen Sie zuerst eine CSV Datei mit Bohrungen aus", "dropZoneDefaultErrorMsg": "Beim Auswählen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", "dropZoneFileToLarge": "Eine oder mehrere Dateien sind zu gross. Max. 200 MB.", "dropZoneGeometryText": "CSV Datei mit Geometriedaten hier ablegen oder klicken, um sie hochzuladen", "dropZoneInvalidFileType": "Eine oder mehrere Dateien haben einen nicht unterstützten Dateityp", - "dropZoneJsonText": "Wählen Sie eine Borehole JSON Datei aus", + "dropZoneBoreholeJsonText": "Wählen Sie eine Borehole JSON Datei aus", "dropZoneLithologyText": "CSV Datei mit Lithologien hier ablegen oder klicken, um sie hochzuladen", "dropZoneMaxFilesToUploadReached": "Maximale Anzahl von Dateien zum Hochladen erreicht", "dropZoneMaximumFilesToSelectAtOnce": "Zu viele Dateien gleichzeitig ausgewählt", @@ -255,7 +255,7 @@ "identifier_value": "ID Code", "import": "Importieren", "importBoreholeAttachment": "Wählen Sie eine oder mehrere Datei(en) aus:", - "importBoreholeJson": "Wählen Sie eine JSON Datei aus:", + "importBoreholes": "Bohrungen importieren", "importDate": "Importdatum", "importedBy": "Importiert von", "importedData": "Importierte Daten", diff --git a/src/client/public/locale/en/common.json b/src/client/public/locale/en/common.json index 044ce60e6..dad8b40ed 100644 --- a/src/client/public/locale/en/common.json +++ b/src/client/public/locale/en/common.json @@ -132,13 +132,13 @@ "drilling_mud_type": "Drilling mud type", "drilling_start_date": "Drilling date: start", "dropZoneAttachmentsText": "Drag and drop file(s) with the appendixes here or click to upload", - "dropZoneBoreholesText": "Drag and drop a CSV file with the boreholes here or click to upload", + "dropZoneBoreholeCsvText": "Select a borehole CSV file", "dropZoneChooseBoreholeFilesFirst": "First, select a CSV file with boreholes", "dropZoneDefaultErrorMsg": "An error occurred during the selection. Please try again.", "dropZoneFileToLarge": "One ore more files are too large. Max. 200 MB.", "dropZoneGeometryText": "Drag and drop a CSV file with the geometry data here or click to upload", "dropZoneInvalidFileType": "One or more files have unsupported file type", - "dropZoneJsonText": "Select a borehole JSON file", + "dropZoneBoreholeJsonText": "Select a borehole JSON file", "dropZoneLithologyText": "Drag and drop a CSV file with the lithologies here or click to upload", "dropZoneMaxFilesToUploadReached": "Maximum number of files to upload reached", "dropZoneMaximumFilesToSelectAtOnce": "To many files selected", @@ -255,7 +255,7 @@ "identifier_value": "ID Code", "import": "Import", "importBoreholeAttachment": "Select one or multiple file(s) to upload:", - "importBoreholeJson": "Select a JSON file:", + "importBoreholes": "Import boreholes", "importDate": "Import date", "importedBy": "Imported by", "importedData": "Imported data", diff --git a/src/client/public/locale/fr/common.json b/src/client/public/locale/fr/common.json index 6ac4979b4..0a17324f9 100644 --- a/src/client/public/locale/fr/common.json +++ b/src/client/public/locale/fr/common.json @@ -132,13 +132,13 @@ "drilling_mud_type": "Type de boue de forage", "drilling_start_date": "Date de début du forage", "dropZoneAttachmentsText": "Déposez les fichiers avec les annexes ici ou cliquez pour les télécharger", - "dropZoneBoreholesText": "Déposez le fichier CSV avec les forages ici ou cliquez pour le télécharger", + "dropZoneBoreholeCsvText": "Sélectionnez un fichier CSV avec les forages", "dropZoneChooseBoreholeFilesFirst": "D'abord, sélectionnez un fichier CSV avec les forages", "dropZoneDefaultErrorMsg": "Une erreur s'est produite lors de la sélection. Veuillez réessayer.", "dropZoneFileToLarge": "Un ou plusieurs fichiers sont trop volumineux. Max. 200 Mo.", "dropZoneGeometryText": "Déposez le fichier CSV avec les géométries ici ou cliquez pour les télécharger", "dropZoneInvalidFileType": "Un ou plusieurs fichiers ont un type de fichier non pris en charge", - "dropZoneJsonText": "Sélectionnez un fichier JSON de forage", + "dropZoneBoreholeJsonText": "Sélectionnez un fichier JSON de forage", "dropZoneLithologyText": "Déposez le fichier CSV avec les lithologies ici ou cliquez pour le télécharger", "dropZoneMaxFilesToUploadReached": "Nombre maximum de fichiers à télécharger atteint", "dropZoneMaximumFilesToSelectAtOnce": "Trop de fichiers sélectionnés en même temps", @@ -255,7 +255,7 @@ "identifier_value": "Code d'ID", "import": "Importer", "importBoreholeAttachment": "Sélectionnez un ou plusieurs fichiers à télécharger:", - "importBoreholeJson": "Sélectionnez un fichier JSON:", + "importBoreholes": "Importer des forages", "importDate": "Date d'importation", "importedBy": "Importé par", "importedData": "Données importées", diff --git a/src/client/public/locale/it/common.json b/src/client/public/locale/it/common.json index d39499fdb..d0818e5ea 100644 --- a/src/client/public/locale/it/common.json +++ b/src/client/public/locale/it/common.json @@ -132,13 +132,13 @@ "drilling_mud_type": "Tipo di fango di perforazione", "drilling_start_date": "Data di inizio della perforazione", "dropZoneAttachmentsText": "Trascina qui i file con i appendici o clicca per caricarli", - "dropZoneBoreholesText": "Trascina qui il file CSV con i perforazioni o clicca per caricarlo CSV", + "dropZoneBoreholeCsvText": "Seleziona un file CSV con le perforazioni", "dropZoneChooseBoreholeFilesFirst": "Innanzitutto, seleziona un file CSV con i perforazioni", "dropZoneDefaultErrorMsg": "Si è verificato un errore durante la selezione. Riprova.", "dropZoneFileToLarge": "Uno o più file sono troppo grandi. Max. 200 MB.", "dropZoneGeometryText": "Trascina qui il file CSV con la geometria o clicca per caricarlo CSV", "dropZoneInvalidFileType": "Uno o più file hanno un tipo non supportato", - "dropZoneJsonText": "Seleziona un file JSON di trivellazione", + "dropZoneBoreholeJsonText": "Seleziona un file JSON di trivellazione", "dropZoneLithologyText": "Trascina qui il file CSV con le litologie o clicca per caricarlo CSV", "dropZoneMaxFilesToUploadReached": "Raggiunto il numero massimo di file da caricare", "dropZoneMaximumFilesToSelectAtOnce": "Troppi file selezionati contemporaneamente", @@ -255,7 +255,7 @@ "identifier_value": "Codice di ID", "import": "Importare", "importBoreholeAttachment": "Seleziona uno o più file da scaricare:", - "importBoreholeJson": "Seleziona un file JSON:", + "importBoreholes": "Importare perforazioni", "importDate": "Data di importazione", "importedBy": "Importato da", "importedData": "Dati importati", From c680fb9edcae976149b671c600eb2eee316d53c6 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 09:47:52 +0100 Subject: [PATCH 04/18] Enable JSON in ui --- src/api/Controllers/ImportController.cs | 10 +-- src/client/src/api/borehole.ts | 4 +- .../src/components/export/exportDialog.tsx | 6 +- .../pages/detail/attachments/fileDropzone.jsx | 25 +++--- .../sidePanelContent/importer/importModal.tsx | 90 +++++++++++++------ .../importer/importModalContent.tsx | 43 +++++++-- 6 files changed, 121 insertions(+), 57 deletions(-) diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index 5537347b0..92b8733c3 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -47,29 +47,29 @@ public ImportController(BdmsContext context, ILogger logger, L /// Receives an uploaded JSON file to import one or several (s). /// /// The of the new (s). - /// The containing the borehole JSON records that were uploaded. + /// The containing the borehole JSON records that were uploaded. /// The number of the newly created s. [HttpPost("json")] [Authorize(Policy = PolicyNames.Viewer)] [RequestSizeLimit(int.MaxValue)] [RequestFormLimits(MultipartBodyLengthLimit = MaxFileSize)] - public async Task> UploadJsonFileAsync(int workgroupId, IFormFile file) + public async Task> UploadJsonFileAsync(int workgroupId, IFormFile boreholesFile) { // Increase max allowed errors to be able to return more validation errors at once. ModelState.MaxAllowedErrors = 1000; logger.LogInformation("Import boreholes json to workgroup with id <{WorkgroupId}>", workgroupId); - if (file == null || file.Length == 0) return BadRequest("No file uploaded."); + if (boreholesFile == null || boreholesFile.Length == 0) return BadRequest("No file uploaded."); - if (!FileTypeChecker.IsJson(file)) return BadRequest("Invalid file type for borehole JSON."); + if (!FileTypeChecker.IsJson(boreholesFile)) return BadRequest("Invalid file type for borehole JSON."); try { List? boreholes; try { - using var stream = file.OpenReadStream(); + using var stream = boreholesFile.OpenReadStream(); boreholes = await JsonSerializer.DeserializeAsync>(stream, jsonImportOptions).ConfigureAwait(false); } catch (JsonException ex) diff --git a/src/client/src/api/borehole.ts b/src/client/src/api/borehole.ts index 1c481e53b..07144d7fb 100644 --- a/src/client/src/api/borehole.ts +++ b/src/client/src/api/borehole.ts @@ -77,12 +77,12 @@ export const updateBorehole = async (borehole: BoreholeV2) => { }; /* eslint-disable @typescript-eslint/no-explicit-any */ -export const importBoreholes = async (workgroupId: string, combinedFormData: any) => { +export const importBoreholesCsv = async (workgroupId: string, combinedFormData: any) => { return await upload(`import?workgroupId=${workgroupId}`, "POST", combinedFormData); }; export const importBoreholesJson = async (workgroupId: string, combinedFormData: any) => { - return await upload(`upload/json?workgroupId=${workgroupId}`, "POST", combinedFormData); + return await upload(`import/json?workgroupId=${workgroupId}`, "POST", combinedFormData); }; export const copyBorehole = async (boreholeId: GridRowSelectionModel, workgroupId: string | null) => { diff --git a/src/client/src/components/export/exportDialog.tsx b/src/client/src/components/export/exportDialog.tsx index d430ae885..6c65d96b5 100644 --- a/src/client/src/components/export/exportDialog.tsx +++ b/src/client/src/components/export/exportDialog.tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; import { Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material"; import { GridRowSelectionModel } from "@mui/x-data-grid"; -import { exportCSVBorehole, getAllBoreholes } from "../../api/borehole.ts"; +import { exportCSVBorehole, exportJsonBoreholes } from "../../api/borehole.ts"; import { downloadData } from "../../utils.ts"; import { CancelButton, ExportButton } from "../buttons/buttons.tsx"; @@ -15,8 +15,8 @@ export const ExportDialog = ({ isExporting, setIsExporting, selectionModel, file const { t } = useTranslation(); const exportJson = async () => { - const paginatedResponse = await getAllBoreholes(selectionModel, 1, selectionModel.length); - const jsonString = JSON.stringify(paginatedResponse.boreholes, null, 2); + const exportJsonResponse = await exportJsonBoreholes(selectionModel); + const jsonString = JSON.stringify(exportJsonResponse); downloadData(jsonString, `${fileName}.json`, "application/json"); setIsExporting(false); }; diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index e254514e5..852a626ad 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -11,11 +11,9 @@ import { theme } from "../../../AppTheme.ts"; * @param {Object} props - The component props. * @param {Function} props.onHandleFileChange - A callback function to handle file changes. The function receives the files array as an argument. * @param {string} props.defaultText - The default text to display in the dropzone. - * @param {string} props.disabledText - The text to display in the dropzone when disabled. * @param {number} props.maxFilesToSelectAtOnce - The maximum number of files that can be selected at once. * @param {number} props.maxFilesToUpload - The maximum number of files that can be uploaded. - * @param {boolean} props.restrictAcceptedFileTypeToCsv - Whether to restrict accepted file types to CSV. - * @param {boolean} props.restrictAcceptedFileTypeToJson - Whether to restrict accepted file types to JSON. + * @param {Array} props.acceptedFileTypes - The list of accepted file types. * @param {boolean} props.isDisabled - Whether the dropzone is disabled. * @param {string} props.dataCy - The data-cy attribute for testing. * @returns {JSX.Element} The rendered FileDropzone component. @@ -24,11 +22,9 @@ export const FileDropzone = props => { const { onHandleFileChange, defaultText, - disabledText, maxFilesToSelectAtOnce, maxFilesToUpload, - restrictAcceptedFileTypeToCsv, - restrictAcceptedFileTypeToJson, + acceptedFileTypes, isDisabled, dataCy, } = props; @@ -37,7 +33,7 @@ export const FileDropzone = props => { const [dropZoneText, setDropZoneText] = useState(null); const [dropZoneTextColor, setDropZoneTextColor] = useState(null); const defaultDropzoneTextColor = isDisabled ? "#9f9f9f" : "#2185d0"; - const initialDropzoneText = isDisabled ? t(disabledText) : t(defaultText); + const initialDropzoneText = isDisabled ? t("dropZoneChooseBoreholeFilesFirst") : t(defaultText); useEffect(() => { setDropZoneText(initialDropzoneText); @@ -97,12 +93,12 @@ export const FileDropzone = props => { [defaultDropzoneTextColor, defaultText, files.length, maxFilesToUpload, showErrorMsg, t], ); - // Is called when a accepted file is removed. + // Is called when an accepted file is removed. const removeFile = fileToRemove => { setFiles(prevFiles => prevFiles.filter(file => file !== fileToRemove)); }; - // IS called when the selected/dropped files are rejected + // Is called when the selected/dropped files are rejected const onDropRejected = useCallback( fileRejections => { const errorCode = fileRejections[0].errors[0].code; @@ -117,10 +113,13 @@ export const FileDropzone = props => { onDropAccepted, maxFiles: maxFilesToSelectAtOnce || Infinity, maxSize: 209715200, - accept: { - ...(restrictAcceptedFileTypeToCsv && { "text/csv": [".csv"] }), - ...(restrictAcceptedFileTypeToJson && { "application/json": [".json"] }), - }, + accept: + acceptedFileTypes.length > 0 + ? acceptedFileTypes.reduce((acc, type) => { + acc[type] = []; + return acc; + }, {}) + : "*", noClick: isDisabled, noKeyboard: isDisabled, }); diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx index 47e45a57a..e6bdf9440 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx @@ -1,7 +1,7 @@ -import { useContext } from "react"; +import { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button, Header, Icon, Modal, Segment } from "semantic-ui-react"; -import { importBoreholes } from "../../../../api/borehole.ts"; +import { importBoreholesCsv, importBoreholesJson } from "../../../../api/borehole.ts"; import { AlertContext } from "../../../../components/alert/alertContext.tsx"; import TranslationText from "../../../../components/legacyComponents/translationText.jsx"; import { capitalizeFirstLetter } from "../../../../utils.ts"; @@ -26,42 +26,74 @@ const ImportModal = ({ }: ImportModalProps) => { const { showAlert } = useContext(AlertContext); const { t } = useTranslation(); + const [fileType, setFileType] = useState(null); // Track file type const handleBoreholeImport = () => { const combinedFormData = new FormData(); if (selectedFile !== null) { - selectedFile.forEach((boreholeFile: string | Blob) => { - combinedFormData.append("boreholesFile", boreholeFile); + selectedFile.forEach((file: string | Blob) => { + combinedFormData.append("boreholesFile", file); }); } - importBoreholes(workgroup, combinedFormData).then(response => { - setCreating(false); - setModal(false); - setUpload(false); - (async () => { - if (response.ok) { - showAlert(`${await response.text()} ${t("boreholesImported")}.`, "success"); - refresh(); - } else { - const responseBody = await response.json(); - if (response.status === 400) { - if (responseBody.errors) { - // If response is of type ValidationProblemDetails, open validation error modal. - setErrorsResponse(responseBody); - setValidationErrorModal(true); - refresh(); + if (fileType == "csv") { + importBoreholesCsv(workgroup, combinedFormData).then(response => { + setCreating(false); + setModal(false); + setUpload(false); + (async () => { + if (response.ok) { + showAlert(`${await response.text()} ${t("boreholesImported")}.`, "success"); + refresh(); + } else { + const responseBody = await response.json(); + if (response.status === 400) { + if (responseBody.errors) { + // If response is of type ValidationProblemDetails, open validation error modal. + setErrorsResponse(responseBody); + setValidationErrorModal(true); + refresh(); + } else { + // If response is of type ProblemDetails, show error message. + showAlert(responseBody.detail, "error"); + } + } else if (response.status === 504) { + showAlert(t("boreholesImportLongRunning"), "error"); } else { - // If response is of type ProblemDetails, show error message. - showAlert(responseBody.detail, "error"); + showAlert(t("boreholesImportError"), "error"); } - } else if (response.status === 504) { - showAlert(t("boreholesImportLongRunning"), "error"); + } + })(); + }); + } else { + importBoreholesJson(workgroup, combinedFormData).then(response => { + setCreating(false); + setModal(false); + setUpload(false); + (async () => { + if (response.ok) { + showAlert(`${await response.text()} ${t("boreholesImported")}.`, "success"); + refresh(); } else { - showAlert(t("boreholesImportError"), "error"); + const responseBody = await response.json(); + if (response.status === 400) { + if (responseBody.errors) { + // If response is of type ValidationProblemDetails, open validation error modal. + setErrorsResponse(responseBody); + setValidationErrorModal(true); + refresh(); + } else { + // If response is of type ProblemDetails, show error message. + showAlert(responseBody.detail, "error"); + } + } else if (response.status === 504) { + showAlert(t("boreholesImportLongRunning"), "error"); + } else { + showAlert(t("boreholesImportError"), "error"); + } } - } - })(); - }); + })(); + }); + } }; const handleFormSubmit = async () => { @@ -89,7 +121,7 @@ const ImportModal = ({ - +

{capitalizeFirstLetter(t("workgroup"))}

diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index 98430ad7e..fc6a13ca2 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -23,14 +23,26 @@ const ExampleHeadings = (headings: string) => { ); }; -const ImportModalContent = ({ setSelectedFile }: ImportContentProps) => { +const ImportModalContent = ({ + setSelectedFile, + setFileType, +}: ImportContentProps & { setFileType: (type: string) => void }) => { const { t } = useTranslation(); const handleBoreholeFileChange = useCallback( (boreholeFileFromDropzone: Blob[]) => { setSelectedFile(boreholeFileFromDropzone); + setFileType("csv"); }, - [setSelectedFile], + [setSelectedFile, setFileType], + ); + + const handleJsonFileChange = useCallback( + (jsonFileFromDropzone: Blob[]) => { + setSelectedFile(jsonFileFromDropzone); + setFileType("json"); + }, + [setSelectedFile, setFileType], ); return ( @@ -41,7 +53,15 @@ const ImportModalContent = ({ setSelectedFile }: ImportContentProps) => {

-

{capitalizeFirstLetter(t("boreholes"))}

+

{capitalizeFirstLetter(t("importBoreholes"))}

+ +

{capitalizeFirstLetter(t("CSV"))}

{t("csvFormatExplanation")} @@ -61,14 +81,27 @@ const ImportModalContent = ({ setSelectedFile }: ImportContentProps) => { +

{capitalizeFirstLetter(t("JSON"))}

+ + + + ); }; From 7464233e702dafdb89a12f6165f126c6c466ceb0 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 11:55:45 +0100 Subject: [PATCH 05/18] Update json export method --- src/client/src/api/borehole.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/api/borehole.ts b/src/client/src/api/borehole.ts index 07144d7fb..7ab4e2fb4 100644 --- a/src/client/src/api/borehole.ts +++ b/src/client/src/api/borehole.ts @@ -69,7 +69,7 @@ export const getBoreholeById = async (id: number) => await fetchApiV2(`borehole/ export const exportJsonBoreholes = async (ids: number[] | GridRowSelectionModel) => { const idsQuery = ids.map(id => `ids=${id}`).join("&"); - return await fetchApiV2(`borehole/json?${idsQuery}`, "GET"); + return await fetchApiV2(`export/json?${idsQuery}`, "GET"); }; export const updateBorehole = async (borehole: BoreholeV2) => { From 5cc636146d236aece0203e43e4ca3459f25673dd Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 11:56:34 +0100 Subject: [PATCH 06/18] Avoid cycling reference in observation json objects --- src/api/Models/Observation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/Models/Observation.cs b/src/api/Models/Observation.cs index 07d126157..7e1e362a0 100644 --- a/src/api/Models/Observation.cs +++ b/src/api/Models/Observation.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; namespace BDMS.Models; @@ -107,6 +108,7 @@ public class Observation : IChangeTracking, IIdentifyable /// /// Gets or sets the 's borehole. /// + [JsonIgnore] public Borehole? Borehole { get; set; } /// From c5a600cb6bd22c66d20dff5039d106aee171e5fc Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 12:11:05 +0100 Subject: [PATCH 07/18] Update accepted file formats for json upload --- .../overview/sidePanelContent/importer/importModalContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index fc6a13ca2..34a115cbb 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -95,7 +95,7 @@ const ImportModalContent = ({ Date: Thu, 19 Dec 2024 13:19:34 +0100 Subject: [PATCH 08/18] Update order of translations --- src/client/public/locale/de/common.json | 2 +- src/client/public/locale/en/common.json | 2 +- src/client/public/locale/fr/common.json | 2 +- src/client/public/locale/it/common.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/public/locale/de/common.json b/src/client/public/locale/de/common.json index 8d6f426e8..dfb16023e 100644 --- a/src/client/public/locale/de/common.json +++ b/src/client/public/locale/de/common.json @@ -133,12 +133,12 @@ "drilling_start_date": "Datum Bohrbeginn", "dropZoneAttachmentsText": "Datei(en) mit Anhängen hier ablegen oder klicken, um sie hochzuladen", "dropZoneBoreholeCsvText": "Wählen Sie eine Borehole CSV Datei aus", + "dropZoneBoreholeJsonText": "Wählen Sie eine Borehole JSON Datei aus", "dropZoneChooseBoreholeFilesFirst": "Wählen Sie zuerst eine CSV Datei mit Bohrungen aus", "dropZoneDefaultErrorMsg": "Beim Auswählen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", "dropZoneFileToLarge": "Eine oder mehrere Dateien sind zu gross. Max. 200 MB.", "dropZoneGeometryText": "CSV Datei mit Geometriedaten hier ablegen oder klicken, um sie hochzuladen", "dropZoneInvalidFileType": "Eine oder mehrere Dateien haben einen nicht unterstützten Dateityp", - "dropZoneBoreholeJsonText": "Wählen Sie eine Borehole JSON Datei aus", "dropZoneLithologyText": "CSV Datei mit Lithologien hier ablegen oder klicken, um sie hochzuladen", "dropZoneMaxFilesToUploadReached": "Maximale Anzahl von Dateien zum Hochladen erreicht", "dropZoneMaximumFilesToSelectAtOnce": "Zu viele Dateien gleichzeitig ausgewählt", diff --git a/src/client/public/locale/en/common.json b/src/client/public/locale/en/common.json index dad8b40ed..fac4a89e3 100644 --- a/src/client/public/locale/en/common.json +++ b/src/client/public/locale/en/common.json @@ -133,12 +133,12 @@ "drilling_start_date": "Drilling date: start", "dropZoneAttachmentsText": "Drag and drop file(s) with the appendixes here or click to upload", "dropZoneBoreholeCsvText": "Select a borehole CSV file", + "dropZoneBoreholeJsonText": "Select a borehole JSON file", "dropZoneChooseBoreholeFilesFirst": "First, select a CSV file with boreholes", "dropZoneDefaultErrorMsg": "An error occurred during the selection. Please try again.", "dropZoneFileToLarge": "One ore more files are too large. Max. 200 MB.", "dropZoneGeometryText": "Drag and drop a CSV file with the geometry data here or click to upload", "dropZoneInvalidFileType": "One or more files have unsupported file type", - "dropZoneBoreholeJsonText": "Select a borehole JSON file", "dropZoneLithologyText": "Drag and drop a CSV file with the lithologies here or click to upload", "dropZoneMaxFilesToUploadReached": "Maximum number of files to upload reached", "dropZoneMaximumFilesToSelectAtOnce": "To many files selected", diff --git a/src/client/public/locale/fr/common.json b/src/client/public/locale/fr/common.json index 0a17324f9..a79be917b 100644 --- a/src/client/public/locale/fr/common.json +++ b/src/client/public/locale/fr/common.json @@ -133,12 +133,12 @@ "drilling_start_date": "Date de début du forage", "dropZoneAttachmentsText": "Déposez les fichiers avec les annexes ici ou cliquez pour les télécharger", "dropZoneBoreholeCsvText": "Sélectionnez un fichier CSV avec les forages", + "dropZoneBoreholeJsonText": "Sélectionnez un fichier JSON de forage", "dropZoneChooseBoreholeFilesFirst": "D'abord, sélectionnez un fichier CSV avec les forages", "dropZoneDefaultErrorMsg": "Une erreur s'est produite lors de la sélection. Veuillez réessayer.", "dropZoneFileToLarge": "Un ou plusieurs fichiers sont trop volumineux. Max. 200 Mo.", "dropZoneGeometryText": "Déposez le fichier CSV avec les géométries ici ou cliquez pour les télécharger", "dropZoneInvalidFileType": "Un ou plusieurs fichiers ont un type de fichier non pris en charge", - "dropZoneBoreholeJsonText": "Sélectionnez un fichier JSON de forage", "dropZoneLithologyText": "Déposez le fichier CSV avec les lithologies ici ou cliquez pour le télécharger", "dropZoneMaxFilesToUploadReached": "Nombre maximum de fichiers à télécharger atteint", "dropZoneMaximumFilesToSelectAtOnce": "Trop de fichiers sélectionnés en même temps", diff --git a/src/client/public/locale/it/common.json b/src/client/public/locale/it/common.json index d0818e5ea..71c8de633 100644 --- a/src/client/public/locale/it/common.json +++ b/src/client/public/locale/it/common.json @@ -133,12 +133,12 @@ "drilling_start_date": "Data di inizio della perforazione", "dropZoneAttachmentsText": "Trascina qui i file con i appendici o clicca per caricarli", "dropZoneBoreholeCsvText": "Seleziona un file CSV con le perforazioni", + "dropZoneBoreholeJsonText": "Seleziona un file JSON di trivellazione", "dropZoneChooseBoreholeFilesFirst": "Innanzitutto, seleziona un file CSV con i perforazioni", "dropZoneDefaultErrorMsg": "Si è verificato un errore durante la selezione. Riprova.", "dropZoneFileToLarge": "Uno o più file sono troppo grandi. Max. 200 MB.", "dropZoneGeometryText": "Trascina qui il file CSV con la geometria o clicca per caricarlo CSV", "dropZoneInvalidFileType": "Uno o più file hanno un tipo non supportato", - "dropZoneBoreholeJsonText": "Seleziona un file JSON di trivellazione", "dropZoneLithologyText": "Trascina qui il file CSV con le litologie o clicca per caricarlo CSV", "dropZoneMaxFilesToUploadReached": "Raggiunto il numero massimo di file da caricare", "dropZoneMaximumFilesToSelectAtOnce": "Troppi file selezionati contemporaneamente", From 4a81b32b8c046f8e784537f930be0f1640830b54 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 13:21:33 +0100 Subject: [PATCH 09/18] Refactor duplicated code in import modal --- .../sidePanelContent/importer/importModal.tsx | 84 +++++++------------ 1 file changed, 31 insertions(+), 53 deletions(-) diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx index e6bdf9440..441fd80dd 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx @@ -28,6 +28,34 @@ const ImportModal = ({ const { t } = useTranslation(); const [fileType, setFileType] = useState(null); // Track file type + const handleImportResponse = async (response: Response) => { + setCreating(false); + setModal(false); + setUpload(false); + + if (response.ok) { + showAlert(`${await response.text()} ${t("boreholesImported")}.`, "success"); + refresh(); + } else { + const responseBody = await response.json(); + if (response.status === 400) { + if (responseBody.errors) { + // If response is of type ValidationProblemDetails, open validation error modal. + setErrorsResponse(responseBody); + setValidationErrorModal(true); + refresh(); + } else { + // If response is of type ProblemDetails, show error message. + showAlert(responseBody.detail, "error"); + } + } else if (response.status === 504) { + showAlert(t("boreholesImportLongRunning"), "error"); + } else { + showAlert(t("boreholesImportError"), "error"); + } + } + }; + const handleBoreholeImport = () => { const combinedFormData = new FormData(); if (selectedFile !== null) { @@ -35,63 +63,13 @@ const ImportModal = ({ combinedFormData.append("boreholesFile", file); }); } - if (fileType == "csv") { + if (fileType === "csv") { importBoreholesCsv(workgroup, combinedFormData).then(response => { - setCreating(false); - setModal(false); - setUpload(false); - (async () => { - if (response.ok) { - showAlert(`${await response.text()} ${t("boreholesImported")}.`, "success"); - refresh(); - } else { - const responseBody = await response.json(); - if (response.status === 400) { - if (responseBody.errors) { - // If response is of type ValidationProblemDetails, open validation error modal. - setErrorsResponse(responseBody); - setValidationErrorModal(true); - refresh(); - } else { - // If response is of type ProblemDetails, show error message. - showAlert(responseBody.detail, "error"); - } - } else if (response.status === 504) { - showAlert(t("boreholesImportLongRunning"), "error"); - } else { - showAlert(t("boreholesImportError"), "error"); - } - } - })(); + handleImportResponse(response); }); } else { importBoreholesJson(workgroup, combinedFormData).then(response => { - setCreating(false); - setModal(false); - setUpload(false); - (async () => { - if (response.ok) { - showAlert(`${await response.text()} ${t("boreholesImported")}.`, "success"); - refresh(); - } else { - const responseBody = await response.json(); - if (response.status === 400) { - if (responseBody.errors) { - // If response is of type ValidationProblemDetails, open validation error modal. - setErrorsResponse(responseBody); - setValidationErrorModal(true); - refresh(); - } else { - // If response is of type ProblemDetails, show error message. - showAlert(responseBody.detail, "error"); - } - } else if (response.status === 504) { - showAlert(t("boreholesImportLongRunning"), "error"); - } else { - showAlert(t("boreholesImportError"), "error"); - } - } - })(); + handleImportResponse(response); }); } }; From d4a336c920eba1bd08b380a5268a26cc3410c714 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 15:12:38 +0100 Subject: [PATCH 10/18] Disable dropzone when other filetype is selected for import --- .../pages/detail/attachments/fileDropzone.jsx | 16 ++++++++++---- .../commons/actionsInterfaces.ts | 1 + .../sidePanelContent/importer/importModal.tsx | 2 +- .../importer/importModalContent.tsx | 22 +++++++++---------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index 852a626ad..b5cde12ed 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -16,6 +16,7 @@ import { theme } from "../../../AppTheme.ts"; * @param {Array} props.acceptedFileTypes - The list of accepted file types. * @param {boolean} props.isDisabled - Whether the dropzone is disabled. * @param {string} props.dataCy - The data-cy attribute for testing. + * @param {Function} props.setFileType - A callback function to set the file type. * @returns {JSX.Element} The rendered FileDropzone component. */ export const FileDropzone = props => { @@ -27,18 +28,17 @@ export const FileDropzone = props => { acceptedFileTypes, isDisabled, dataCy, + setFileType, } = props; const { t } = useTranslation(); const [files, setFiles] = useState([]); - const [dropZoneText, setDropZoneText] = useState(null); + const [dropZoneText, setDropZoneText] = useState(t(defaultText)); const [dropZoneTextColor, setDropZoneTextColor] = useState(null); const defaultDropzoneTextColor = isDisabled ? "#9f9f9f" : "#2185d0"; - const initialDropzoneText = isDisabled ? t("dropZoneChooseBoreholeFilesFirst") : t(defaultText); useEffect(() => { - setDropZoneText(initialDropzoneText); setDropZoneTextColor(defaultDropzoneTextColor); - }, [defaultDropzoneTextColor, initialDropzoneText]); + }, [defaultDropzoneTextColor]); // Set the color of the dropzone text to red and display an error message const showErrorMsg = useCallback( @@ -88,6 +88,13 @@ export const FileDropzone = props => { setDropZoneTextColor(defaultDropzoneTextColor); setDropZoneText(t(defaultText)); setFiles(prevFiles => [...prevFiles, ...acceptedFiles]); + // set filetype depending on acceptedFileTypes. if contains csv + if (acceptedFileTypes.includes("text/csv")) { + setFileType("csv"); + } + if (acceptedFileTypes.includes("application/json")) { + setFileType("json"); + } } }, [defaultDropzoneTextColor, defaultText, files.length, maxFilesToUpload, showErrorMsg, t], @@ -96,6 +103,7 @@ export const FileDropzone = props => { // Is called when an accepted file is removed. const removeFile = fileToRemove => { setFiles(prevFiles => prevFiles.filter(file => file !== fileToRemove)); + setFileType(null); }; // Is called when the selected/dropped files are rejected diff --git a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts index 98d55d4c8..502d59889 100644 --- a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts +++ b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts @@ -22,6 +22,7 @@ export interface NewBoreholeProps extends WorkgroupSelectProps { export interface ImportContentProps { setSelectedFile: React.Dispatch>; + fileType: string | null; } export interface ImportModalProps extends ImportContentProps { diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx index 441fd80dd..46313d403 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx @@ -99,7 +99,7 @@ const ImportModal = ({ - +

{capitalizeFirstLetter(t("workgroup"))}

diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index 34a115cbb..2faea3732 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -26,23 +26,21 @@ const ExampleHeadings = (headings: string) => { const ImportModalContent = ({ setSelectedFile, setFileType, + fileType, }: ImportContentProps & { setFileType: (type: string) => void }) => { const { t } = useTranslation(); - const handleBoreholeFileChange = useCallback( - (boreholeFileFromDropzone: Blob[]) => { - setSelectedFile(boreholeFileFromDropzone); - setFileType("csv"); + const handleCsvFileChange = useCallback( + (csvFileFromDropzone: Blob[]) => { + setSelectedFile(csvFileFromDropzone); }, - [setSelectedFile, setFileType], + [setSelectedFile], ); - const handleJsonFileChange = useCallback( (jsonFileFromDropzone: Blob[]) => { setSelectedFile(jsonFileFromDropzone); - setFileType("json"); }, - [setSelectedFile, setFileType], + [setSelectedFile], ); return ( @@ -80,13 +78,14 @@ const ImportModalContent = ({ )}

{capitalizeFirstLetter(t("JSON"))}

@@ -98,8 +97,9 @@ const ImportModalContent = ({ acceptedFileTypes={["application/json"]} maxFilesToSelectAtOnce={1} maxFilesToUpload={1} - isDisabled={false} + isDisabled={fileType === "csv"} dataCy={"import-jsonFile-input"} + setFileType={setFileType} /> From 18f547771aae1d78ab2c3d17d0f3c09df0647a4d Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 15:51:03 +0100 Subject: [PATCH 11/18] Clean up file --- .../src/pages/detail/attachments/fileDropzone.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index b5cde12ed..5f75768b9 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -97,7 +97,16 @@ export const FileDropzone = props => { } } }, - [defaultDropzoneTextColor, defaultText, files.length, maxFilesToUpload, showErrorMsg, t], + [ + defaultDropzoneTextColor, + defaultText, + files.length, + maxFilesToUpload, + showErrorMsg, + t, + acceptedFileTypes, + setFileType, + ], ); // Is called when an accepted file is removed. From a5814b485b94eb8ba8b93a09e3ebec4384ebefae Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 16:17:04 +0100 Subject: [PATCH 12/18] Update import modal content props --- .../overview/sidePanelContent/commons/actionsInterfaces.ts | 1 - .../overview/sidePanelContent/importer/importModalContent.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts index 502d59889..98d55d4c8 100644 --- a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts +++ b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts @@ -22,7 +22,6 @@ export interface NewBoreholeProps extends WorkgroupSelectProps { export interface ImportContentProps { setSelectedFile: React.Dispatch>; - fileType: string | null; } export interface ImportModalProps extends ImportContentProps { diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index 2faea3732..a0a1b5f86 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -27,7 +27,7 @@ const ImportModalContent = ({ setSelectedFile, setFileType, fileType, -}: ImportContentProps & { setFileType: (type: string) => void }) => { +}: ImportContentProps & { setFileType: (type: string) => void } & { fileType: string | null }) => { const { t } = useTranslation(); const handleCsvFileChange = useCallback( From d912a9273c912631567fb6dea5cf68e4b2aefa0c Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Thu, 19 Dec 2024 18:26:35 +0100 Subject: [PATCH 13/18] Update dropzone --- .../pages/detail/attachments/fileDropzone.jsx | 18 +++++++++++------- .../detail/form/borehole/geometryImport.jsx | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index 5f75768b9..13b74de42 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -88,12 +88,14 @@ export const FileDropzone = props => { setDropZoneTextColor(defaultDropzoneTextColor); setDropZoneText(t(defaultText)); setFiles(prevFiles => [...prevFiles, ...acceptedFiles]); - // set filetype depending on acceptedFileTypes. if contains csv - if (acceptedFileTypes.includes("text/csv")) { - setFileType("csv"); - } - if (acceptedFileTypes.includes("application/json")) { - setFileType("json"); + + if (setFileType) { + if (acceptedFileTypes.includes("text/csv")) { + setFileType("csv"); + } + if (acceptedFileTypes.includes("application/json")) { + setFileType("json"); + } } } }, @@ -112,7 +114,9 @@ export const FileDropzone = props => { // Is called when an accepted file is removed. const removeFile = fileToRemove => { setFiles(prevFiles => prevFiles.filter(file => file !== fileToRemove)); - setFileType(null); + if (setFileType) { + setFileType(null); + } }; // Is called when the selected/dropped files are rejected diff --git a/src/client/src/pages/detail/form/borehole/geometryImport.jsx b/src/client/src/pages/detail/form/borehole/geometryImport.jsx index 74c12fe14..ca2879de2 100644 --- a/src/client/src/pages/detail/form/borehole/geometryImport.jsx +++ b/src/client/src/pages/detail/form/borehole/geometryImport.jsx @@ -114,7 +114,7 @@ const GeometryImport = ({ boreholeId }) => { Date: Fri, 20 Dec 2024 09:05:35 +0100 Subject: [PATCH 14/18] Update translations --- src/client/public/locale/de/common.json | 4 ++-- src/client/public/locale/en/common.json | 4 ++-- src/client/public/locale/fr/common.json | 4 ++-- src/client/public/locale/it/common.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client/public/locale/de/common.json b/src/client/public/locale/de/common.json index dfb16023e..fa001f683 100644 --- a/src/client/public/locale/de/common.json +++ b/src/client/public/locale/de/common.json @@ -132,8 +132,8 @@ "drilling_mud_type": "Bohrspülung Typ", "drilling_start_date": "Datum Bohrbeginn", "dropZoneAttachmentsText": "Datei(en) mit Anhängen hier ablegen oder klicken, um sie hochzuladen", - "dropZoneBoreholeCsvText": "Wählen Sie eine Borehole CSV Datei aus", - "dropZoneBoreholeJsonText": "Wählen Sie eine Borehole JSON Datei aus", + "dropZoneBoreholeCsvText": "Wählen Sie eine CSV Datei aus", + "dropZoneBoreholeJsonText": "Wählen Sie eine JSON Datei aus", "dropZoneChooseBoreholeFilesFirst": "Wählen Sie zuerst eine CSV Datei mit Bohrungen aus", "dropZoneDefaultErrorMsg": "Beim Auswählen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", "dropZoneFileToLarge": "Eine oder mehrere Dateien sind zu gross. Max. 200 MB.", diff --git a/src/client/public/locale/en/common.json b/src/client/public/locale/en/common.json index fac4a89e3..1ed833010 100644 --- a/src/client/public/locale/en/common.json +++ b/src/client/public/locale/en/common.json @@ -132,8 +132,8 @@ "drilling_mud_type": "Drilling mud type", "drilling_start_date": "Drilling date: start", "dropZoneAttachmentsText": "Drag and drop file(s) with the appendixes here or click to upload", - "dropZoneBoreholeCsvText": "Select a borehole CSV file", - "dropZoneBoreholeJsonText": "Select a borehole JSON file", + "dropZoneBoreholeCsvText": "Select a CSV file", + "dropZoneBoreholeJsonText": "Select a JSON file", "dropZoneChooseBoreholeFilesFirst": "First, select a CSV file with boreholes", "dropZoneDefaultErrorMsg": "An error occurred during the selection. Please try again.", "dropZoneFileToLarge": "One ore more files are too large. Max. 200 MB.", diff --git a/src/client/public/locale/fr/common.json b/src/client/public/locale/fr/common.json index a79be917b..eee908fc3 100644 --- a/src/client/public/locale/fr/common.json +++ b/src/client/public/locale/fr/common.json @@ -132,8 +132,8 @@ "drilling_mud_type": "Type de boue de forage", "drilling_start_date": "Date de début du forage", "dropZoneAttachmentsText": "Déposez les fichiers avec les annexes ici ou cliquez pour les télécharger", - "dropZoneBoreholeCsvText": "Sélectionnez un fichier CSV avec les forages", - "dropZoneBoreholeJsonText": "Sélectionnez un fichier JSON de forage", + "dropZoneBoreholeCsvText": "Sélectionnez un fichier CSV", + "dropZoneBoreholeJsonText": "Sélectionnez un fichier JSON", "dropZoneChooseBoreholeFilesFirst": "D'abord, sélectionnez un fichier CSV avec les forages", "dropZoneDefaultErrorMsg": "Une erreur s'est produite lors de la sélection. Veuillez réessayer.", "dropZoneFileToLarge": "Un ou plusieurs fichiers sont trop volumineux. Max. 200 Mo.", diff --git a/src/client/public/locale/it/common.json b/src/client/public/locale/it/common.json index 71c8de633..6b0afb986 100644 --- a/src/client/public/locale/it/common.json +++ b/src/client/public/locale/it/common.json @@ -132,8 +132,8 @@ "drilling_mud_type": "Tipo di fango di perforazione", "drilling_start_date": "Data di inizio della perforazione", "dropZoneAttachmentsText": "Trascina qui i file con i appendici o clicca per caricarli", - "dropZoneBoreholeCsvText": "Seleziona un file CSV con le perforazioni", - "dropZoneBoreholeJsonText": "Seleziona un file JSON di trivellazione", + "dropZoneBoreholeCsvText": "Seleziona un file CSV", + "dropZoneBoreholeJsonText": "Seleziona un file JSON", "dropZoneChooseBoreholeFilesFirst": "Innanzitutto, seleziona un file CSV con i perforazioni", "dropZoneDefaultErrorMsg": "Si è verificato un errore durante la selezione. Riprova.", "dropZoneFileToLarge": "Uno o più file sono troppo grandi. Max. 200 MB.", From e7c0b6b7996f35143aecf394acec5c64927ede17 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Fri, 20 Dec 2024 09:07:02 +0100 Subject: [PATCH 15/18] Update prop description --- src/client/src/pages/detail/attachments/fileDropzone.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index 13b74de42..b49fdee43 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -16,7 +16,7 @@ import { theme } from "../../../AppTheme.ts"; * @param {Array} props.acceptedFileTypes - The list of accepted file types. * @param {boolean} props.isDisabled - Whether the dropzone is disabled. * @param {string} props.dataCy - The data-cy attribute for testing. - * @param {Function} props.setFileType - A callback function to set the file type. + * @param {Function} props.setFileType - A react SetStateAction to set the file type. * @returns {JSX.Element} The rendered FileDropzone component. */ export const FileDropzone = props => { From fcdf964e9836f2f827e39ed200d702f3bb5b2c17 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Fri, 20 Dec 2024 10:08:58 +0100 Subject: [PATCH 16/18] Add import modal content interface --- .../src/pages/detail/attachments/fileDropzone.jsx | 2 +- .../sidePanelContent/commons/actionsInterfaces.ts | 6 +----- .../sidePanelContent/importer/importModal.tsx | 2 +- .../importer/importModalContent.tsx | 14 +++++++------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/client/src/pages/detail/attachments/fileDropzone.jsx b/src/client/src/pages/detail/attachments/fileDropzone.jsx index b49fdee43..8b19a279d 100644 --- a/src/client/src/pages/detail/attachments/fileDropzone.jsx +++ b/src/client/src/pages/detail/attachments/fileDropzone.jsx @@ -115,7 +115,7 @@ export const FileDropzone = props => { const removeFile = fileToRemove => { setFiles(prevFiles => prevFiles.filter(file => file !== fileToRemove)); if (setFileType) { - setFileType(null); + setFileType(""); } }; diff --git a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts index 98d55d4c8..4a6850954 100644 --- a/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts +++ b/src/client/src/pages/overview/sidePanelContent/commons/actionsInterfaces.ts @@ -20,11 +20,7 @@ export interface NewBoreholeProps extends WorkgroupSelectProps { toggleDrawer: (open: boolean) => void; } -export interface ImportContentProps { - setSelectedFile: React.Dispatch>; -} - -export interface ImportModalProps extends ImportContentProps { +export interface ImportModalProps { modal: boolean; creating: boolean; selectedFile: Blob[] | null; diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx index 46313d403..afb577476 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModal.tsx @@ -26,7 +26,7 @@ const ImportModal = ({ }: ImportModalProps) => { const { showAlert } = useContext(AlertContext); const { t } = useTranslation(); - const [fileType, setFileType] = useState(null); // Track file type + const [fileType, setFileType] = useState(""); // Track file type const handleImportResponse = async (response: Response) => { setCreating(false); diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index a0a1b5f86..e7185ae15 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import React, { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Box, Stack } from "@mui/material/"; import { downloadCodelistCsv } from "../../../../api/fetchApiV2.js"; @@ -6,8 +6,12 @@ import { StackHalfWidth } from "../../../../components/styledComponents.ts"; import { capitalizeFirstLetter } from "../../../../utils.ts"; import Downloadlink from "../../../detail/attachments/downloadlink.jsx"; import { FileDropzone } from "../../../detail/attachments/fileDropzone.jsx"; -import { ImportContentProps } from "../commons/actionsInterfaces.js"; +interface ImportModalContentProps { + setSelectedFile: React.Dispatch>; + setFileType: (type: string) => void; + fileType: string; +} const ExampleHeadings = (headings: string) => { return ( { ); }; -const ImportModalContent = ({ - setSelectedFile, - setFileType, - fileType, -}: ImportContentProps & { setFileType: (type: string) => void } & { fileType: string | null }) => { +const ImportModalContent = ({ setSelectedFile, setFileType, fileType }: ImportModalContentProps) => { const { t } = useTranslation(); const handleCsvFileChange = useCallback( From 59d84446be133deb0eef093fb1c7159fe2371cc5 Mon Sep 17 00:00:00 2001 From: Frederic Stahel Date: Fri, 20 Dec 2024 10:10:33 +0100 Subject: [PATCH 17/18] Update title --- .../overview/sidePanelContent/importer/importModalContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index e7185ae15..af7ffaff3 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -59,7 +59,7 @@ const ImportModalContent = ({ setSelectedFile, setFileType, fileType }: ImportMo marginTop: "1em", }} /> -

{capitalizeFirstLetter(t("CSV"))}

+

{capitalizeFirstLetter("CSV")}

{t("csvFormatExplanation")} @@ -88,7 +88,7 @@ const ImportModalContent = ({ setSelectedFile, setFileType, fileType }: ImportMo setFileType={setFileType} /> -

{capitalizeFirstLetter(t("JSON"))}

+

{capitalizeFirstLetter("JSON")}

Date: Fri, 20 Dec 2024 13:13:28 +0100 Subject: [PATCH 18/18] Update file type titles --- .../overview/sidePanelContent/importer/importModalContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx index af7ffaff3..dc3717fd7 100644 --- a/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx +++ b/src/client/src/pages/overview/sidePanelContent/importer/importModalContent.tsx @@ -59,7 +59,7 @@ const ImportModalContent = ({ setSelectedFile, setFileType, fileType }: ImportMo marginTop: "1em", }} /> -

{capitalizeFirstLetter("CSV")}

+

{"CSV"}

{t("csvFormatExplanation")} @@ -88,7 +88,7 @@ const ImportModalContent = ({ setSelectedFile, setFileType, fileType }: ImportMo setFileType={setFileType} /> -

{capitalizeFirstLetter("JSON")}

+

{"JSON"}