diff --git a/src/pages/ocotillo/well-inventory-bulk-import/index.tsx b/src/pages/ocotillo/well-inventory-bulk-import/index.tsx new file mode 100644 index 00000000..1e7df771 --- /dev/null +++ b/src/pages/ocotillo/well-inventory-bulk-import/index.tsx @@ -0,0 +1,255 @@ +import { + Box, + Button, + Stack, + Typography, + Card, + Alert, + Chip, + List, + ListItem, + ListItemText, + Divider, +} from '@mui/material' +import { LoadingButton } from '@mui/lab' +import { Create } from '@refinedev/mui' +import { useNotification, useDataProvider } from '@refinedev/core' +import { useState } from 'react' +import FileUploadIcon from '@mui/icons-material/FileUpload' +import InfoIcon from '@mui/icons-material/Info' + +interface UploadResult { + validation_errors: any[] + summary: { + total_rows_processed: number + total_rows_imported: number + validation_errors_or_warnings: number + } + wells: string[] +} + +export const WellInventoryBulkImport: React.FC = () => { + const [selectedFile, setSelectedFile] = useState(null) + const [isSubmitting, setIsSubmitting] = useState(false) + const [uploadResult, setUploadResult] = useState(null) + const { open: openNotification } = useNotification() + const dataProvider = useDataProvider() + const provider = dataProvider('ocotillo') + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (file) { + setSelectedFile(file) + } + } + + const handleSubmit = async () => { + if (!selectedFile) { + openNotification({ + message: 'No file selected', + description: 'Please select a CSV file to upload.', + type: 'error', + }) + return + } + + setIsSubmitting(true) + + try { + const formData = new FormData() + formData.append('file', selectedFile) + + const result = await provider.custom({ + url: 'well-inventory-csv', + method: 'post', + payload: formData, + headers: {}, + }) + + if (result?.data) { + setUploadResult(result.data as UploadResult) + } + + openNotification({ + message: 'Upload successful', + description: 'The well inventory file has been imported successfully.', + type: 'success', + }) + setSelectedFile(null) + // Reset file input + const fileInput = document.getElementById('csv-input') as HTMLInputElement + if (fileInput) { + fileInput.value = '' + } + } catch (error: any) { + console.error('Error uploading file:', error) + const errorMessage = error.message || 'An error occurred while uploading the file.' + + openNotification({ + message: 'Upload failed', + description: errorMessage, + type: 'error', + }) + setUploadResult(null) + } finally { + setIsSubmitting(false) + } + } + + const handleReset = () => { + setSelectedFile(null) + setUploadResult(null) + const fileInput = document.getElementById('csv-input') as HTMLInputElement + if (fileInput) { + fileInput.value = '' + } + } + + return ( + + Well Inventory Bulk Import + + } + saveButtonProps={{ + children: 'Upload', + onClick: handleSubmit, + disabled: !selectedFile || isSubmitting, + loading: isSubmitting, + }} + > + + {!uploadResult && ( + <> + + + Upload a CSV file to bulk import well inventory data. + + + + + + + + )} + + {uploadResult && ( + + + + + Upload Completed Successfully! + + + The well inventory file has been imported successfully. + + + + + + Import Summary + + + + + + + + + {uploadResult.wells && uploadResult.wells.length > 0 && ( + + + Imported Wells ({uploadResult.wells.length}) + + + {uploadResult.wells.map((well, index) => ( +
+ + + + {index < uploadResult.wells.length - 1 && } +
+ ))} +
+
+ )} + + {uploadResult.validation_errors && + uploadResult.validation_errors.length > 0 && ( + + }> + + Validation Warnings + + + {uploadResult.validation_errors.map((error, index) => ( + + + + ))} + + + + )} + + + + +
+
+ )} +
+
+ ) +} + diff --git a/src/providers/access-control-provider.ts b/src/providers/access-control-provider.ts index 0fbe3678..6b29a248 100644 --- a/src/providers/access-control-provider.ts +++ b/src/providers/access-control-provider.ts @@ -62,6 +62,7 @@ const defineUserAbility = (groups: string[]) => { can('list', 'ocotillo.apps') can('list', 'ocotillo.water-chemistry-import') + can('list', 'ocotillo.well-inventory-bulk-import') can('list', 'ocotillo.hydrograph-corrector') } diff --git a/src/providers/ocotillo-data-provider.ts b/src/providers/ocotillo-data-provider.ts index 57463e7f..46285b6c 100644 --- a/src/providers/ocotillo-data-provider.ts +++ b/src/providers/ocotillo-data-provider.ts @@ -213,11 +213,13 @@ export const ocotilloDataProvider: DataProvider = { } }, custom: async ({ url, method, payload, headers }) => { + const isFormData = payload instanceof FormData + const config: AxiosRequestConfig = { url: `${API_URL}/${url}`, method: method || 'GET', headers: { - 'Content-Type': 'application/json', + ...(isFormData ? {} : { 'Content-Type': 'application/json' }), ...headers, }, } @@ -226,12 +228,20 @@ export const ocotilloDataProvider: DataProvider = { config.data = payload } - const response = await axiosInstance(config) + try { + const response = await axiosInstance(config) - if (response.status < 200 || response.status > 299) throw response + if (response.status < 200 || response.status > 299) throw response - return { data: response.data } + return { data: response.data } + } catch (error: any) { + /** + * TODO: Add better error handling for bulk import based on API Pydantic validation errors + */ + throw error + } }, + update: async ({ resource, id, variables }) => { resource = cleanResourceName(resource) diff --git a/src/resources/ocotillo.tsx b/src/resources/ocotillo.tsx index 2e71e466..3f0514d6 100644 --- a/src/resources/ocotillo.tsx +++ b/src/resources/ocotillo.tsx @@ -15,6 +15,7 @@ import { Workspaces, MoreVertOutlined, LibraryBooksOutlined, + UploadFile, } from '@mui/icons-material' let tables: { @@ -341,6 +342,16 @@ let ocotillo = [ icon: , }, }, + { + name: 'well-inventory-bulk-import', + list: '/ocotillo/well-inventory-bulk-import', + meta: { + label: 'Well Inventory Bulk Import', + parent: 'ocotillo.apps', + nestedLevel: 2, + icon: , + }, + }, { name: 'forms', icon: , diff --git a/src/routes/ocotillo.tsx b/src/routes/ocotillo.tsx index 3814d9a3..8d72236f 100644 --- a/src/routes/ocotillo.tsx +++ b/src/routes/ocotillo.tsx @@ -69,6 +69,7 @@ import { GroundwaterLevelForm } from '@/pages/ocotillo/groundwater-level-form/st import { WellInventoryForm } from '@/pages/ocotillo/well-inventory-form' import { LexiconList } from '@/pages/ocotillo/lexicon' import { WaterChemistryApp } from '@/pages/ocotillo/water-chemistry-app' +import { WellInventoryBulkImport } from '@/pages/ocotillo/well-inventory-bulk-import' import { WellScreenCreate, WellScreenEdit, @@ -161,6 +162,9 @@ export const OcotilloRoutes = () => { } /> + + } /> + // Forms } />