Skip to content

Commit

Permalink
* create API function uploadProject
Browse files Browse the repository at this point in the history
* write uploadProject actions
* keep track of file state in Projects with useState , useEffect
* use FileUploadButton with useDropzone
  • Loading branch information
CalamityC committed Mar 15, 2024
1 parent d0c7f48 commit 75ee37f
Show file tree
Hide file tree
Showing 7 changed files with 2,948 additions and 2,566 deletions.
5,219 changes: 2,766 additions & 2,453 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions rdmo/core/assets/js/components/FileUploadButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,31 @@ import React from 'react'
import PropTypes from 'prop-types'
import { useDropzone } from 'react-dropzone'

const FileUploadButton = ({ acceptedTypes, buttonText, onFileDrop }) => {
const FileUploadButton = ({ acceptedTypes, buttonProps, buttonText, setUploadFile }) => {
const { getRootProps, getInputProps } = useDropzone({
accept: acceptedTypes,
onDrop: (acceptedFiles) => {
if (onFileDrop) {
onFileDrop(acceptedFiles)
onDrop: acceptedFiles => {
if (acceptedFiles.length > 0) {
setUploadFile(acceptedFiles[0])
}
},
}
})

return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<button className="btn btn-link">
<button className="btn" {...buttonProps}>
<i className="fa fa-download" aria-hidden="true"></i> {buttonText}
</button>
</div>
)
}

FileUploadButton.propTypes = {
acceptedTypes: PropTypes.string.isRequired,
acceptedTypes: PropTypes.arrayOf(PropTypes.string),
buttonProps: PropTypes.object,
buttonText: PropTypes.string.isRequired,
onFileDrop: PropTypes.func,
setUploadFile: PropTypes.func.isRequired
}

export default FileUploadButton
27 changes: 26 additions & 1 deletion rdmo/projects/assets/js/actions/projectsActions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ProjectsApi from '../api/ProjectsApi'
import { FETCH_PROJECTS_ERROR, FETCH_PROJECTS_INIT, FETCH_PROJECTS_SUCCESS }
import { FETCH_PROJECTS_ERROR, FETCH_PROJECTS_INIT, FETCH_PROJECTS_SUCCESS,
UPLOAD_PROJECT_ERROR, UPLOAD_PROJECT_INIT, UPLOAD_PROJECT_SUCCESS }
from './types'

export function fetchAllProjects() {
Expand All @@ -26,3 +27,27 @@ export function fetchProjectsSuccess(projects) {
export function fetchProjectsError(error) {
return {type: FETCH_PROJECTS_ERROR, error}
}

export function uploadProject(url, file) {
return function(dispatch) {
dispatch(uploadProjectInit())

return ProjectsApi.uploadProject(url, file)
.then(project => dispatch(uploadProjectSuccess(project)))
.catch(error => {
dispatch(uploadProjectError(error))
})
}
}

export function uploadProjectInit() {
return {type: UPLOAD_PROJECT_INIT}
}

export function uploadProjectSuccess(project) {
return {type: UPLOAD_PROJECT_SUCCESS, project}
}

export function uploadProjectError(error) {
return {type: UPLOAD_PROJECT_ERROR, error}
}
3 changes: 3 additions & 0 deletions rdmo/projects/assets/js/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ export const FETCH_PROJECTS_ERROR = 'projects/fetchProjectsError'
export const FETCH_PROJECTS_INIT = 'projects/fetchProjectsInit'
export const FETCH_PROJECTS_SUCCESS = 'projects/fetchProjectsSuccess'
export const UPDATE_CONFIG = 'config/updateConfig'
export const UPLOAD_PROJECT_ERROR = 'import/uploadProjectError'
export const UPLOAD_PROJECT_INIT = 'import/uploadProjectInit'
export const UPLOAD_PROJECT_SUCCESS = 'import/uploadProjectSuccess'
32 changes: 32 additions & 0 deletions rdmo/projects/assets/js/api/ProjectsApi.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Cookies from 'js-cookie'
import BaseApi from 'rdmo/core/assets/js/api/BaseApi'
import { encodeParams } from 'rdmo/core/assets/js/utils/api'
import baseUrl from 'rdmo/core/assets/js/utils/baseUrl'

class ProjectsApi extends BaseApi {

Expand All @@ -12,6 +14,36 @@ class ProjectsApi extends BaseApi {
}
})
}

static uploadProject(url, file) {
var formData = new FormData()
formData.append('method', 'upload_file')
formData.append('uploaded_file', file)
return fetch(baseUrl + url, {
method: 'POST',
headers: {
'X-CSRFToken': Cookies.get('csrftoken'),
},
body: formData
}).catch(error => {
throw new Error(`API error: ${error.message}`)
}).then(response => {
if (response.ok) {
if (response.url) {
window.open(response.url, '_blank')
} else {
throw new Error('Response does not contain a URL')
}
} else if (response.status == 400) {
return response.json().then(errors => {
throw new Error(`Validation error: ${JSON.stringify(errors)}`)
})
} else {
throw new Error(`API error: ${response.statusText} (${response.status})`)
}
})
}

}

export default ProjectsApi
52 changes: 30 additions & 22 deletions rdmo/projects/assets/js/components/main/Projects.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import Table from '../helper/Table'
import Link from 'rdmo/core/assets/js/components/Link'
Expand All @@ -13,6 +13,14 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
const { currentUser } = currentUserObject
const { myProjects } = config

const [uploadFile, setUploadFile] = useState(null)

useEffect(() => {
if (uploadFile) {
handleImport(uploadFile)
}
}, [uploadFile])

const displayedRows = get(config, 'table.rows')

const currentUserId = currentUser.id
Expand All @@ -30,30 +38,31 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
{ hour12: false } :
{ hour12: true }

const dateOptions = {
...langOptions,
day: 'numeric',
month: 'short',
year: 'numeric',
hour: 'numeric',
minute: 'numeric'
}

const viewLinkText = myProjects ? gettext('View all projects') : gettext('View my projects')
const headline = myProjects ? gettext('My projects') : gettext('All projects')

const handleViewClick = () => {
const handleView = () => {
configActions.updateConfig('myProjects', !myProjects)
myProjects ? configActions.deleteConfig('params.user') : configActions.updateConfig('params.user', currentUserId)
projectsActions.fetchAllProjects()()
}

const handleNewClick = () => {
const handleNew= () => {
window.location.href = `${baseUrl}/projects/create`
}

const handleImportClick = () => {
console.log('Import button clicked')
}

const dateOptions = {
...langOptions,
day: 'numeric',
month: 'short',
year: 'numeric',
hour: 'numeric',
minute: 'numeric'
const handleImport = (file) => {
projectsActions.uploadProject('/projects/import/', file)
setUploadFile(null)
}

const getParentPath = (parentId, pathArray = []) => {
Expand Down Expand Up @@ -158,22 +167,22 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
}
}

const buttonProps={'className': 'btn btn-link'}

return (
<>
<div className="mb-10" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 className="ml-10 mt-0">{headline}</h2>
<div className="icon-container ml-auto">
<button className="btn btn-link mr-10" onClick={handleNewClick}>
<button className="btn btn-link mr-10" onClick={handleNew}>
<i className="fa fa-plus" aria-hidden="true"></i> {gettext('New project')}
</button>
<FileUploadButton
acceptedTypes="application/xml"
acceptedTypes={['application/xml', 'text/xml']}
buttonProps={buttonProps}
buttonText={gettext('Import project')}
onFileDrop={handleImportClick}
setUploadFile={setUploadFile}
/>
{/* <button className="btn btn-link" onClick={handleImportClick}>
<i className="fa fa-download" aria-hidden="true"></i> {gettext('Import project')}
</button> */}
</div>
</div>
<span>{displayedRows>projects.length ? projects.length : displayedRows} {gettext('of')} {projects.length} {gettext('projects are displayed')}</span>
Expand All @@ -185,14 +194,13 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
onChange={updateSearchString}
onSearch={projectsActions.fetchAllProjects}
placeholder={gettext('Search projects')}
delay={300}
/>
</div>

</div>
{isManager &&
<div className="mb-10">
<Link className="element-link mb-20" onClick={handleViewClick}>
<Link className="element-link mb-20" onClick={handleView}>
{viewLinkText}
</Link>
</div>
Expand Down
Loading

0 comments on commit 75ee37f

Please sign in to comment.