Skip to content

Commit

Permalink
Merge branch 'pending-invitations' into 865-write-projects-overview-i…
Browse files Browse the repository at this point in the history
…n-react
  • Loading branch information
CalamityC committed Apr 4, 2024
2 parents 90b052e + 64ac5be commit 2a5620d
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 5 deletions.
11 changes: 11 additions & 0 deletions rdmo/core/assets/js/hooks/useModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useState } from 'react'

const useModal = () => {
const [show, setShow] = useState(false)
const open = () => setShow(true)
const close = () => setShow(false)

return [show, open, close]
}

export default useModal
25 changes: 25 additions & 0 deletions 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,
FETCH_INVITATIONS_ERROR, FETCH_INVITATIONS_INIT, FETCH_INVITATIONS_SUCCESS,
UPLOAD_PROJECT_ERROR, UPLOAD_PROJECT_INIT, UPLOAD_PROJECT_SUCCESS }
from './types'

Expand Down Expand Up @@ -28,6 +29,30 @@ export function fetchProjectsError(error) {
return {type: FETCH_PROJECTS_ERROR, error}
}

export function fetchInvitations(userId) {
return function(dispatch) {
dispatch(fetchInvitationsInit())
const action = (dispatch) => ProjectsApi.fetchInvites(userId)
.then(invites => {
dispatch(fetchInvitationsSuccess({ invites }))})

return dispatch(action)
.catch(error => dispatch(fetchInvitationsError(error)))
}
}

export function fetchInvitationsInit() {
return {type: FETCH_INVITATIONS_INIT}
}

export function fetchInvitationsSuccess(invites) {
return {type: FETCH_INVITATIONS_SUCCESS, invites}
}

export function fetchInvitationsError(error) {
return {type: FETCH_INVITATIONS_ERROR, error}
}

export function uploadProject(url, file) {
return function(dispatch) {
dispatch(uploadProjectInit())
Expand Down
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 @@ -5,6 +5,9 @@ export const FETCH_CURRENT_USER_SUCCESS = 'currentUser/fetchCurrentUserSuccess'
export const FETCH_PROJECTS_ERROR = 'projects/fetchProjectsError'
export const FETCH_PROJECTS_INIT = 'projects/fetchProjectsInit'
export const FETCH_PROJECTS_SUCCESS = 'projects/fetchProjectsSuccess'
export const FETCH_INVITATIONS_ERROR = 'projects/fetchInvitationsError'
export const FETCH_INVITATIONS_INIT = 'projects/fetchInvitationsInit'
export const FETCH_INVITATIONS_SUCCESS = 'projects/fetchInvitationsSuccess'
export const UPDATE_CONFIG = 'config/updateConfig'
export const UPLOAD_PROJECT_ERROR = 'import/uploadProjectError'
export const UPLOAD_PROJECT_INIT = 'import/uploadProjectInit'
Expand Down
56 changes: 56 additions & 0 deletions rdmo/projects/assets/js/api/ProjectsApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,62 @@ class ProjectsApi extends BaseApi {
})
}

// static fetchInvites(userId) {
// return fetch(`/api/v1/projects/invites/?user=${userId}`).then(response => {
// if (response.ok) {
// return response.json()
// } else {
// throw new Error(response.statusText)
// }
// }).then(invites => {
// const projectPromises = invites.map(invite => {
// return fetch(`/api/v1/projects/projects/${invite.project}`).then(response => {
// if (response.ok) {
// return response.json()
// } else {
// throw new Error(response.statusText)
// }
// })
// })
// return Promise.all(projectPromises)
// })
// }

static fetchInvites(userId) {
return fetch(`/api/v1/projects/invites/?user=${userId}`)
.then(response => {
if (response.ok) {
return response.json()
} else {
throw new Error(response.statusText)
}
})
.then(invites => {
const projectRequests = invites.map(invite => {
return fetch(`/api/v1/projects/projects/${invite.project}/`)
.then(response => {
if (response.ok) {
return response.json()
} else {
throw new Error(response.statusText)
}
})
.then(project => {
invite.project_title = project.title
return invite
})
.catch(error => {
throw new Error(`Error fetching project: ${error.message}`)
})
})
return Promise.all(projectRequests)
})
.catch(error => {
throw new Error(`API error: ${error.message}`)
})
}


static uploadProject(url, file) {
var formData = new FormData()
formData.append('method', 'upload_file')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Modal } from 'react-bootstrap'

const columnStyle = { color: '#666', width: '25%', paddingLeft: '10px' }
const tableStyle = { width: '100%' }

const PendingInvitationsModal = ({ invitations, onClose, show }) => {
const baseUrl = window.location.origin

return (
<Modal bsSize="large" show={show} onHide={onClose} className="element-modal">
<Modal.Header closeButton>
<h2 className="modal-title">{gettext('Pending invitations')}</h2>
</Modal.Header>
<Modal.Body>
{ <table style={tableStyle}>
<tbody>
{invitations?.map(item => (
<tr key={item.id}>
<td style={columnStyle}>
<b>{item.project_title}</b>
{/* <a href={`${baseUrl}/projects/${item.project}`} target="_blank" rel="noopener noreferrer">{item.project_title}</a>
{' '.repeat(maxProjectTitleLength - item.project_title.length)} */}
{/* <a href={`${baseUrl}/projects/join/${item.project}`} target="_blank" rel="noopener noreferrer"><b>{item.project_title}</b></a> */}
</td>
{/* <td style={columnStyle}>{gettext(item.role)}</td> */}
<td style={columnStyle}>{gettext(item.role).charAt(0).toUpperCase() + gettext(item.role).slice(1)}</td>
<td style={columnStyle}>
<button className="btn btn-link" onClick={() => { window.location.href = `${baseUrl}/projects/join/${item.project}` }}>{gettext('Accept')}</button>
</td>
<td style={columnStyle}>
<button className="btn btn-link" onClick={() => { window.location.href = `${baseUrl}/projects/cancel/${item.project}` }}>{gettext('Decline')}</button>
</td>
</tr>
))}
</tbody>
</table> }
</Modal.Body>
<Modal.Footer>
<button type="button" className="btn btn-default" onClick={onClose}>
{gettext('Close')}
</button>
</Modal.Footer>
</Modal>
)
// return (
// <div className="mb-20">
// <table style={tableStyle}>
// <tbody>
// <td style={columnStyle} colSpan="4"><h5>{gettext('Pending invitations')}</h5></td>
// {invitations.map(item => (
// <tr key={item.id}>
// <td style={columnStyle}>
// <b>{item.project_title}</b>
// {/* <a href={`${baseUrl}/projects/${item.project}`} target="_blank" rel="noopener noreferrer">{item.project_title}</a>
// {' '.repeat(maxProjectTitleLength - item.project_title.length)} */}
// {/* <a href={`${baseUrl}/projects/join/${item.project}`} target="_blank" rel="noopener noreferrer"><b>{item.project_title}</b></a> */}
// </td>
// {/* <td style={columnStyle}>{gettext(item.role)}</td> */}
// <td style={columnStyle}>{gettext(item.role).charAt(0).toUpperCase() + gettext(item.role).slice(1)}</td>
// <td style={columnStyle}>
// <button className="btn btn-link" onClick={() => { window.location.href = `${baseUrl}/projects/join/${item.project}` }}>{gettext('Accept')}</button>
// </td>
// <td style={columnStyle}>
// <button className="btn btn-link" onClick={() => { window.location.href = `${baseUrl}/projects/cancel/${item.project}` }}>{gettext('Decline')}</button>
// </td>
// </tr>
// ))}
// </tbody>
// </table>
// </div>
// )
}

PendingInvitationsModal.propTypes = {
invitations: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
project_title: PropTypes.string.isRequired,
project: PropTypes.number.isRequired,
role: PropTypes.string.isRequired,
})),
onClose: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
}

export default PendingInvitationsModal
2 changes: 1 addition & 1 deletion rdmo/projects/assets/js/components/helper/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const Table = ({
{sortedRows.map((row, index) => (
<tr key={index}>
{visibleColumns.map((column, index) => (
<td key={column}style={{ width: columnWidths[index] }}>
<td key={column} style={{ width: columnWidths[index] }}>
{formatCellContent(row, column, row[column])}
</td>
))}
Expand Down
20 changes: 17 additions & 3 deletions rdmo/projects/assets/js/components/main/Projects.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import React, {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import Table from '../helper/Table'
import PendingInvitationsModal from '../helper/PendingInvitationsModal'
import Link from 'rdmo/core/assets/js/components/Link'
import { SearchField } from 'rdmo/core/assets/js/components/SearchAndFilter'
import FileUploadButton from 'rdmo/core/assets/js/components/FileUploadButton'
import useModal from 'rdmo/core/assets/js/hooks/useModal'
import language from 'rdmo/core/assets/js/utils/language'
import userIsManager from '../../utils/userIsManager'
import { getTitlePath } from '../../utils/getProjectTitlePath'
import { DATE_OPTIONS, HEADER_FORMATTERS, SORTABLE_COLUMNS } from '../../utils/constants'
import { get } from 'lodash'
import { get, isEmpty } from 'lodash'

const Projects = ({ config, configActions, currentUserObject, projectsActions, projectsObject }) => {
const { projects } = projectsObject
const { invites, projects } = projectsObject
const { currentUser } = currentUserObject
const { myProjects } = config

const [showModal, openModal, closeModal] = useModal()

const [showTopButton, setShowTopButton] = useState(false)

useEffect(() => {
Expand Down Expand Up @@ -146,6 +150,11 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
<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">
{ !isEmpty(invites) && myProjects &&
<button className="btn btn-link mr-10" onClick={openModal}>
{gettext('Pending invitations')}
</button>
}
<button className="btn btn-link mr-10" onClick={handleNew}>
<i className="fa fa-plus" aria-hidden="true"></i> {gettext('New project')}
</button>
Expand All @@ -171,7 +180,7 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
{isManager &&
<div className="mb-10">
<Link className="element-link mb-20" onClick={handleView}>
{viewLinkText}
{viewLinkText}
</Link>
</div>
}
Expand All @@ -188,6 +197,11 @@ const Projects = ({ config, configActions, currentUserObject, projectsActions, p
sortableColumns={SORTABLE_COLUMNS}
visibleColumns={visibleColumns}
/>
<PendingInvitationsModal
invitations={invites}
show={showModal}
onClose={closeModal}
/>
</>
)
}
Expand Down
10 changes: 9 additions & 1 deletion rdmo/projects/assets/js/reducers/projectsReducer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FETCH_PROJECTS_ERROR, FETCH_PROJECTS_INIT, FETCH_PROJECTS_SUCCESS } from '../actions/types'
import { FETCH_PROJECTS_ERROR, FETCH_PROJECTS_INIT, FETCH_PROJECTS_SUCCESS,
FETCH_INVITATIONS_ERROR, FETCH_INVITATIONS_INIT, FETCH_INVITATIONS_SUCCESS
} from '../actions/types'

const initialState = {
projects: []
Expand All @@ -12,6 +14,12 @@ export default function projectsReducer(state = initialState, action) {
return {...state, ...action.projects}
case FETCH_PROJECTS_ERROR:
return {...state, errors: action.error.errors}
case FETCH_INVITATIONS_INIT:
return {...state, ...action.invites}
case FETCH_INVITATIONS_SUCCESS:
return {...state, ...action.invites}
case FETCH_INVITATIONS_ERROR:
return {...state, errors: action.error.errors}
default:
return state
}
Expand Down
1 change: 1 addition & 0 deletions rdmo/projects/assets/js/store/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default function configureStore() {
store.dispatch(configActions.updateConfig('params.user', currentUser.id))
}
store.dispatch(projectsActions.fetchAllProjects())
store.dispatch(projectsActions.fetchInvitations(currentUser.id))
})
})

Expand Down

0 comments on commit 2a5620d

Please sign in to comment.