Skip to content

Commit

Permalink
Add breadcrumb, overview, progress, buttons and navigation to interview
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenklar committed Mar 15, 2024
1 parent f51c16e commit 8591118
Show file tree
Hide file tree
Showing 16 changed files with 472 additions and 29 deletions.
51 changes: 51 additions & 0 deletions rdmo/projects/assets/js/interview/actions/pageActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import PagesApi from '../api/PagesApi'
import ProjectsApi from '../api/ProjectsApi'
import ValuesApi from '../api/ValuesApi'

import { FETCH_PAGE_SUCCESS, FETCH_PAGE_ERROR, UPLOAD_FILE_SUCCESS, UPLOAD_FILE_ERROR, JUMP, PREV, NEXT } from './types'

export function fetchPage() {
return (dispatch) => Promise.all([
PagesApi.fetchContinue(12),
ProjectsApi.fetchProject(12),
ProjectsApi.fetchOverview(12),
ProjectsApi.fetchProgress(12),
ProjectsApi.fetchNavigation(12, 1)
]).then(([page, project, overview, progress, navigation]) => dispatch(fetchPageSuccess({
page, project, overview, progress, navigation
})))
}

export function fetchPageSuccess(page) {
return {type: FETCH_PAGE_SUCCESS, page}
}

export function fetchPageError(errors) {
return {type: FETCH_PAGE_ERROR, errors}
}

export function uploadFile(projectId, valueId, file) {
return (dispatch) => ValuesApi.uploadFile(projectId, valueId, file).then((value) => {
dispatch(uploadFileSuccess(value))
})
}

export function uploadFileSuccess(value) {
return {type: UPLOAD_FILE_SUCCESS, value}
}

export function uploadFileError(value) {
return {type: UPLOAD_FILE_ERROR, value}
}

export function jump(section, page = null) {
return {type: JUMP, section, page}
}

export function prev() {
return {type: PREV}
}

export function next() {
return {type: NEXT}
}
10 changes: 10 additions & 0 deletions rdmo/projects/assets/js/interview/actions/types.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export const FETCH_CONFIG_SUCCESS = 'config/fetchConfigSuccess'
export const FETCH_CONFIG_ERROR = 'config/fetchConfigError'
export const UPDATE_CONFIG = 'config/updateConfig'

export const FETCH_PAGE_SUCCESS = 'page/fetchPageSuccess'
export const FETCH_PAGE_ERROR = 'page/fetchPageError'

export const UPLOAD_FILE_SUCCESS = 'page/uploadFileSuccess'
export const UPLOAD_FILE_ERROR = 'page/uploadFileError'

export const JUMP = 'page/jump'
export const NEXT = 'page/next'
export const PREV = 'page/prev'
15 changes: 15 additions & 0 deletions rdmo/projects/assets/js/interview/api/PagesApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import BaseApi from 'rdmo/core/assets/js/api/BaseApi'

class ProjectsApi extends BaseApi {

static fetchPage(projectId, page_id) {
return this.get(`/api/v1/projects/projects/${projectId}/pages/${page_id}`)
}

static fetchContinue(projectId) {
return this.get(`/api/v1/projects/projects/${projectId}/pages/continue/`)
}

}

export default ProjectsApi
23 changes: 23 additions & 0 deletions rdmo/projects/assets/js/interview/api/ProjectsApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import BaseApi from 'rdmo/core/assets/js/api/BaseApi'

class ProjectsApi extends BaseApi {

static fetchProject(projectId) {
return this.get(`/api/v1/projects/projects/${projectId}/`)
}

static fetchOverview(projectId) {
return this.get(`/api/v1/projects/projects/${projectId}/overview/`)
}

static fetchNavigation(projectId, page_id) {
return this.get(`/api/v1/projects/projects/${projectId}/navigation/${page_id}`)
}

static fetchProgress(projectId) {
return this.get(`/api/v1/projects/projects/${projectId}/progress/`)
}

}

export default ProjectsApi
39 changes: 39 additions & 0 deletions rdmo/projects/assets/js/interview/api/ValuesApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Cookies from 'js-cookie'

import BaseApi from 'rdmo/core/assets/js/api/BaseApi'

import baseUrl from 'rdmo/core/assets/js/utils/baseUrl'

class ValuesApi extends BaseApi {

static uploadFile(projectId, valueId, file) {
const url = `/api/v1/projects/projects/${projectId}/values/${valueId}/file/`

var formData = new FormData()
formData.append('method', 'upload_file')
formData.append('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) {
return response.json()
} else if (response.status == 400) {
// return response.json().then(errors => {
// throw new ValidationError(errors)
// })
} else {
// throw new ApiError(response.statusText, response.status)
}
})
}

}

export default ValuesApi
30 changes: 30 additions & 0 deletions rdmo/projects/assets/js/interview/components/Breadcrump.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'

const Breadcrump = ({ project, page, onJump }) => {
return (
<ul className="interview-breadcrumb breadcrumb">
<li>
<a href="">{gettext('My Projects')}</a>
</li>
<li>
<a href="">
{project.title}
</a>
</li>
<li>
<a href="" onClick={() => onJump(page.section.id)}>
{page.section.title}
</a>
</li>
</ul>
)
}

Breadcrump.propTypes = {
project: PropTypes.object.isRequired,
page: PropTypes.object.isRequired,
onJump: PropTypes.func.isRequired
}

export default Breadcrump
34 changes: 34 additions & 0 deletions rdmo/projects/assets/js/interview/components/Buttons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import PropTypes from 'prop-types'

const Buttons = ({ currentPage, onPrev, onNext }) => {
return (
<>
<div className="interview-buttons">
<div className="pull-right">
<button type="button" onClick={onNext} disabled={!currentPage.next_page}
className="btn btn-default btn-xs">
{gettext('Proceed')}
{/* TODO: handle */}
{/*{gettext('Skip')}*/}
</button>
</div>

<div>
<button type="button" onClick={onPrev} disabled={!currentPage.prev_page}
className="btn btn-default btn-xs">
{gettext('Skip')}
</button>
</div>
</div>
</>
)
}

Buttons.propTypes = {
currentPage: PropTypes.object.isRequired,
onPrev: PropTypes.func.isRequired,
onNext: PropTypes.func.isRequired
}

export default Buttons
71 changes: 71 additions & 0 deletions rdmo/projects/assets/js/interview/components/Navigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

const Navigation = ({ currentPage, navigation, onJump }) => {

const handleJump = (event, section, page) => {
event.preventDefault()
onJump(section, page)
}

return (
<>
<h2>{gettext('Navigation')}</h2>

<ul className="list-unstyled interview-navigation">
{
navigation.map((section, sectionIndex) => (
<li key={sectionIndex}>
<a href="" onClick={event => handleJump(event, section)}>
{section.title}
</a>
{
section.pages && (
<ul className="list-unstyled">
{
section.pages.map((page, pageIndex) => (
<li key={pageIndex} className={classNames({'active': page.id == currentPage.id})}>
{
page.show ? (
<a href="" onClick={event => handleJump(event, section, page)}>
<span>{page.title}</span>
{
page.count > 0 && page.count == page.total && (
<span>
<i className="fa fa-check" aria-hidden="true"></i>
</span>
)
}
{
page.count > 0 && page.count != page.total && (
<span dangerouslySetInnerHTML={{
__html: interpolate(gettext('(%s of %s)'), [page.count, page.total])}} />
)
}
</a>
) : (
<span className="text-muted">{page.title}</span>
)
}
</li>
))
}
</ul>
)
}
</li>
))
}
</ul>
</>
)
}

Navigation.propTypes = {
currentPage: PropTypes.object.isRequired,
navigation: PropTypes.array.isRequired,
onJump: PropTypes.func.isRequired
}

export default Navigation
43 changes: 43 additions & 0 deletions rdmo/projects/assets/js/interview/components/Overview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'

import baseUrl from 'rdmo/core/assets/js/utils/baseUrl'

const Overview = ({ project }) => {

const projectsUrl = `${baseUrl}/projects/`
const projectUrl = `${baseUrl}/projects/${project.id}`

return (
<>
<h2>{gettext('Overview')}</h2>

<div className="interview-overview">
<ul className="list-unstyled">
<li>
{gettext('Project')}: <a href={projectUrl}>{project.title}</a>
</li>
<li>
{/* TODO: get catalog title from catalog api */}
{gettext('Catalog')}: {project.catalog}
</li>
</ul>

<ul className="list-unstyled">
<li>
<a href="#" onClick={() => window.location.reload()}>{gettext('Reload page')}</a>
</li>
<li>
<a href={projectsUrl}>{gettext('Back to my projects')}</a>
</li>
</ul>
</div>
</>
)
}

Overview.propTypes = {
project: PropTypes.object.isRequired
}

export default Overview
30 changes: 30 additions & 0 deletions rdmo/projects/assets/js/interview/components/Progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'

const Progress = ({ progress }) => {
const low = progress.ratio <= 0.25
const width = progress.ratio * 100
const label = interpolate(gettext('%s of %s'), [progress.count, progress.total])

return (
<>
<h2>{gettext('Progress')}</h2>

<div className="interview-progress">
{low && <div className="interview-progress-count" dangerouslySetInnerHTML={{ __html: label }} />}

<div className="progress">
<div className="progress-bar" role="progressbar" style={{width: `${width}%`}}>
{!low && <span dangerouslySetInnerHTML={{ __html: label }} />}
</div>
</div>
</div>
</>
)
}

Progress.propTypes = {
progress: PropTypes.object.isRequired
}

export default Progress
Loading

0 comments on commit 8591118

Please sign in to comment.