Skip to content

Commit

Permalink
Merge 6a780ba into 6debf66
Browse files Browse the repository at this point in the history
  • Loading branch information
CalamityC committed Jan 18, 2024
2 parents 6debf66 + 6a780ba commit a56e38c
Show file tree
Hide file tree
Showing 34 changed files with 10,959 additions and 17 deletions.
3 changes: 1 addition & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
module.exports = {
'root': true,
'globals': {
'gettext': true,
'ngettext': true,
'interpolate': true,
'process': true,
'require': true
'require': true,
},
'env': {
'browser': true,
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ dist

rdmo/management/static

rdmo/projects/static/projects/js/projects.js
rdmo/projects/static/projects/fonts

screenshots
4 changes: 3 additions & 1 deletion rdmo/accounts/serializers/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class Meta:
'id',
'groups',
'role',
'memberships'
'memberships',
'is_superuser',
'is_staff'
]
if settings.USER_API:
fields += [
Expand Down
8 changes: 8 additions & 0 deletions rdmo/accounts/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.contrib.auth import get_user_model

from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

from django_filters.rest_framework import DjangoFilterBackend
Expand Down Expand Up @@ -40,3 +43,8 @@ def get_queryset(self):
'role__member', 'role__manager',
'role__editor', 'role__reviewer',
'memberships')

@action(detail=False, permission_classes=(IsAuthenticated, ))
def current(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
46 changes: 46 additions & 0 deletions rdmo/core/assets/js/components/SearchAndFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import PropTypes from 'prop-types'

const TextField = ({ value, onChange, placeholder }) => {
return (
<div className="form-group mb-0">
<div className="input-group">
<input type="text" className="form-control" placeholder={placeholder}
value={ value } onChange={e => onChange(e.target.value)}></input>
<span className="input-group-btn">
<button className="btn btn-default" onClick={() => onChange('')}>
<span className="fa fa-times"></span>
</button>
</span>
</div>
</div>
)
}

TextField.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string.isRequired
}

const Select = ({ value, options, onChange, placeholder }) => {
return (
<div className="form-group mb-0">
<select className="form-control" value={value} onChange={e => onChange(e.target.value)}>
<option value="">{placeholder}</option>
{
options.map((option, index) => <option value={option} key={index}>{option}</option>)
}
</select>
</div>
)
}

Select.propTypes = {
value: PropTypes.string,
options: PropTypes.array.isRequired,
onChange: PropTypes.func,
placeholder: PropTypes.string
}

export { TextField, Select }
133 changes: 133 additions & 0 deletions rdmo/core/assets/js/components/Table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'

const Table = ({
cellFormatters,
data,
headerFormatters,
initialRows = 25,
rowsToLoad = 10,
sortableColumns,
/* order of elements in 'visibleColumns' corresponds to order of columns in table */
visibleColumns,
}) => {
const [displayedRows, setDisplayedRows] = useState(initialRows)
const [sortColumn, setSortColumn] = useState(null)
const [sortOrder, setSortOrder] = useState('asc')

const loadMore = () => {
setDisplayedRows(prev => prev + rowsToLoad)
}

const handleHeaderClick = column => {
if (sortableColumns.includes(column)) {
if (sortColumn === column) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
} else {
setSortColumn(column)
setSortOrder('asc')
}
}
}

const sortedData = () => {
if (sortColumn) {
const sorted = [...data]

sorted.sort((a, b) => {
let valueA = a[sortColumn]
let valueB = b[sortColumn]
const sortRawContent = (
headerFormatters[sortColumn]?.sortRawContent ?? true
)

if (!sortRawContent) {
valueA = formatCellContent(a, sortColumn, a[sortColumn])
valueB = formatCellContent(b, sortColumn, b[sortColumn])
}

if (sortOrder === 'asc') {
return valueA.localeCompare ? valueA.localeCompare(valueB) : valueA - valueB
} else {
return valueB.localeCompare ? valueB.localeCompare(valueA) : valueB - valueA
}
})

return sorted
}
return data
}

const renderHeaders = () => {
return (
<thead className="thead-dark">
<tr>
{visibleColumns.map(column => {
const headerFormatter = headerFormatters[column]
const columnHeaderContent = headerFormatter && headerFormatter.render ? headerFormatter.render(column) : column

return (
<th key={column} onClick={() => handleHeaderClick(column)}>
{columnHeaderContent}
{sortColumn === column && (
<span className="sort-icon">
{sortOrder === 'asc' ? <i className="fa fa-sort-asc" /> : <i className="fa fa-sort-desc" />}
</span>
)}
</th>
)
})}
</tr>
</thead>
)
}

const formatCellContent = (row, column, content) => {
if (cellFormatters && cellFormatters[column] && typeof cellFormatters[column] === 'function') {
return cellFormatters[column](content, row)
}
return content
}

const renderRows = () => {
const sortedRows = sortedData().slice(0, displayedRows)

return (
<tbody>
{sortedRows.map((row, index) => (
<tr key={index}>
{visibleColumns.map(column => (
<td key={column}>{formatCellContent(row, column, row[column])}</td>
))}
</tr>
))}
</tbody>
)
}

return (
<div className="table-container">
<table className="table table-borderless">
{renderHeaders()}
{renderRows()}
</table>
{displayedRows < data.length && (
<button onClick={loadMore} className="load-more-btn">
{gettext('Load More')}
</button>
)}
</div>
)
}

Table.propTypes = {
cellFormatters: PropTypes.object,
data: PropTypes.arrayOf(PropTypes.object).isRequired,
headerFormatters: PropTypes.object,
initialRows: PropTypes.number,
rowsToLoad: PropTypes.number,
sortableColumns: PropTypes.arrayOf(PropTypes.string),
visibleColumns: PropTypes.arrayOf(PropTypes.string),
}

export default Table
2 changes: 2 additions & 0 deletions rdmo/core/assets/js/utils/language.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// take the language from the <head> of the django template
export default document.querySelector('meta[name="language"]').content
2 changes: 2 additions & 0 deletions rdmo/core/assets/js/utils/siteId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// take the site_id from the <head> of the django template
export default Number(document.querySelector('meta[name="site_id"]').content)
2 changes: 2 additions & 0 deletions rdmo/core/assets/js/utils/staticUrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// take the staticurl from the <head> of the django template
export default document.querySelector('meta[name="staticurl"]').content.replace(/\/+$/, '')
4 changes: 4 additions & 0 deletions rdmo/core/templates/core/base_head.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{% load static %}
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}

<title>{{ request.site.name }}</title>

<meta charset="UTF-8" />
Expand All @@ -10,5 +13,6 @@
<meta name='baseurl' content="{% url 'home' %}" />
<meta name='staticurl' content="{% static '' %}" />
<meta name='site_id' content="{{ settings.SITE_ID }}" />
<meta name='language' content="{{ LANGUAGE_CODE }}">

<link rel="shortcut icon" href="{% static 'core/img/favicon.png' %}" type="image/png" />
13 changes: 13 additions & 0 deletions rdmo/core/templates/core/projects_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends 'core/base.html' %}

{% block content %}

<div class="container">
<div class="row">
<div class="page col-md-12">
{% block page %}{% endblock %}
</div>
</div>
</div>

{% endblock %}
26 changes: 14 additions & 12 deletions rdmo/management/assets/js/store/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,21 @@ export default function configureStore() {
const updateConfigFromLocalStorage = () => {
const ls = {...localStorage}
Object.entries(ls).forEach(([lsPath, lsValue]) => {
const path = lsPath.replace('rdmo.management.config.', '')
let value
switch(lsValue) {
case 'true':
value = true
break
case 'false':
value = false
break
default:
value = lsValue
if (lsPath.startsWith('rdmo.management.config.')) {
const path = lsPath.replace('rdmo.management.config.', '')
let value
switch(lsValue) {
case 'true':
value = true
break
case 'false':
value = false
break
default:
value = lsValue
}
store.dispatch(configActions.updateConfig(path, value))
}
store.dispatch(configActions.updateConfig(path, value))
})
}

Expand Down
5 changes: 5 additions & 0 deletions rdmo/projects/assets/js/actions/configActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { UPDATE_CONFIG } from './types'

export function updateConfig(path, value) {
return {type: UPDATE_CONFIG, path, value}
}
54 changes: 54 additions & 0 deletions rdmo/projects/assets/js/actions/projectsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

import ProjectsApi from '../api/ProjectsApi'
import { FETCH_PROJECTS_ERROR, FETCH_PROJECTS_INIT, FETCH_PROJECTS_SUCCESS }
from './types'

// export function fetchAllProjects() {
// return function(dispatch) {
// dispatch(fetchProjectsInit())
// const action = (dispatch) => ProjectsApi.fetchProjects(true)
// .then(projects => {
// dispatch(fetchProjectsSuccess({ projects }))})

// return dispatch(action)
// .catch(error => dispatch(fetchProjectsError(error)))
// }
// }

export function fetchAllProjects() {
return function(dispatch) {
dispatch(fetchProjectsInit())
const action = (dispatch) =>
ProjectsApi.fetchProjects(true)
.then(projects => {
const fetchProgressPromises = projects.map(project =>
ProjectsApi.fetchProgress(project.id)
.then(progress => ({ ...project, progress: {count: progress.count, total: progress.total} }))
)

return Promise.all(fetchProgressPromises)
.then(updatedProjects => {
dispatch(fetchProjectsSuccess({ projects: updatedProjects }))
})
})

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

export function fetchProjectsInit() {
return {type: FETCH_PROJECTS_INIT}
}

export function fetchProjectsSuccess(projects) {
return {type: FETCH_PROJECTS_SUCCESS, projects}
}

export function fetchProjectsError(error) {
return {type: FETCH_PROJECTS_ERROR, error}
}

// export function updateConfig(path, value) {
// return {type: 'projects/updateConfig', path, value}
// }
7 changes: 7 additions & 0 deletions rdmo/projects/assets/js/actions/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const FETCH_PROJECTS_SUCCESS = 'projects/fetchProjectsSuccess'
export const FETCH_PROJECTS_ERROR = 'projects/fetchProjectsError'
export const FETCH_PROJECTS_INIT = 'projects/fetchProjectsInit'
export const FETCH_CURRENT_USER_INIT = 'currentUser/fetchCurrentUserInit'
export const FETCH_CURRENT_USER_SUCCESS = 'currentUser/fetchCurrentUserSuccess'
export const FETCH_CURRENT_USER_ERROR = 'currentUser/fetchCurrentUserError'
export const UPDATE_CONFIG = 'config/updateConfig'
Loading

0 comments on commit a56e38c

Please sign in to comment.