Admin: sports edition
This commit is contained in:
@ -7,25 +7,43 @@ export const setData = (target, data) => ({
|
||||
target,
|
||||
})
|
||||
|
||||
export const setError = (target, error) => ({
|
||||
export const setError = message => ({
|
||||
type: 'SET_ERROR',
|
||||
error,
|
||||
target,
|
||||
message,
|
||||
})
|
||||
|
||||
export function getData(target) {
|
||||
export function getData(target, id = null) {
|
||||
return function(dispatch) {
|
||||
if (id !== null && isNaN(id)) {
|
||||
return dispatch(setError(target, `${target}: Incorrect id`))
|
||||
}
|
||||
return mpwoApi
|
||||
.getData(target)
|
||||
.getData(target, id)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setData(target, ret.data))
|
||||
} else {
|
||||
dispatch(setError(target, ret.message))
|
||||
dispatch(setError(`${target}: ${ret.status}`))
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
throw error
|
||||
})
|
||||
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
||||
}
|
||||
}
|
||||
|
||||
export function updateData(target, data) {
|
||||
return function(dispatch) {
|
||||
if (isNaN(data.id)) {
|
||||
return dispatch(setError(target, `${target}: Incorrect id`))
|
||||
}
|
||||
return mpwoApi
|
||||
.updateData(target, data)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setData(target, ret.data))
|
||||
} else {
|
||||
dispatch(setError(`${target}: ${ret.status}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
|
||||
export default function AdminPage(props) {
|
||||
|
||||
const { target, data } = props
|
||||
const error = data.error
|
||||
const results = data.data
|
||||
const tbKeys = []
|
||||
if (results.length > 0) {
|
||||
Object.keys(results[0]).map(key => tbKeys.push(key))
|
||||
}
|
||||
const title = target.charAt(0).toUpperCase() + target.slice(1)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>mpwo - Admin</title>
|
||||
</Helmet>
|
||||
{error && (
|
||||
<code>{error}</code>
|
||||
)}
|
||||
<h1 className="page-title">
|
||||
Administration - {title}
|
||||
</h1>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-2" />
|
||||
<div className="col-md-8 card">
|
||||
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
{tbKeys.map(
|
||||
tbKey => <th key={tbKey} scope="col">{tbKey}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ results.map((result, idx) => (
|
||||
<tr key={idx}>
|
||||
{ Object.keys(result).map(key => {
|
||||
if (key === 'id') {
|
||||
return <th key={key} scope="row">{result[key]}</th>
|
||||
}
|
||||
return <td key={key}>{result[key]}</td>
|
||||
}) }
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="col-md-2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
37
mpwo_client/src/components/Admin/AdminSport.jsx
Normal file
37
mpwo_client/src/components/Admin/AdminSport.jsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { getData } from '../../actions/index'
|
||||
import AdminDetail from './generic/AdminDetail'
|
||||
|
||||
class AdminSports extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadSport(
|
||||
this.props.location.pathname.replace('/admin/sport/', '')
|
||||
)
|
||||
}
|
||||
render() {
|
||||
const { sports } = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AdminDetail
|
||||
results={sports}
|
||||
target="sports"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadSport: sportId => {
|
||||
dispatch(getData('sports', sportId))
|
||||
},
|
||||
})
|
||||
)(AdminSports)
|
@ -2,18 +2,20 @@ import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { getData } from '../../actions/index'
|
||||
import AdminPage from './AdminPage'
|
||||
import AdminPage from './generic/AdminPage'
|
||||
|
||||
class AdminSports extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadSport()
|
||||
this.props.loadSports()
|
||||
}
|
||||
render() {
|
||||
const { sports } = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AdminPage
|
||||
data={sports}
|
||||
detailLink="sport"
|
||||
target="sports"
|
||||
/>
|
||||
</div>
|
||||
@ -27,7 +29,7 @@ export default connect(
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadSport: () => {
|
||||
loadSports: () => {
|
||||
dispatch(getData('sports'))
|
||||
},
|
||||
})
|
||||
|
130
mpwo_client/src/components/Admin/generic/AdminDetail.jsx
Normal file
130
mpwo_client/src/components/Admin/generic/AdminDetail.jsx
Normal file
@ -0,0 +1,130 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { updateData } from '../../../actions/index'
|
||||
|
||||
class AdminDetail extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
isInEdition: false,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
message,
|
||||
onDataUpdate,
|
||||
results,
|
||||
target,
|
||||
} = this.props
|
||||
const { isInEdition } = this.state
|
||||
const title = target.charAt(0).toUpperCase() + target.slice(1)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>mpwo - Admin</title>
|
||||
</Helmet>
|
||||
<h1 className="page-title">
|
||||
Administration - {title}
|
||||
</h1>
|
||||
{message ? (
|
||||
<code>{message}</code>
|
||||
) : (
|
||||
results.length === 1 && (
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-2" />
|
||||
<div className="col-md-8 card">
|
||||
<div className="card-body">
|
||||
<form onSubmit={event =>
|
||||
event.preventDefault()}
|
||||
>
|
||||
{ results.map(result => (
|
||||
Object.keys(result).map(key => (
|
||||
<div className="form-group" key={key}>
|
||||
<label>
|
||||
{key}:
|
||||
<input
|
||||
className="form-control input-lg"
|
||||
name={key}
|
||||
readOnly={key === 'id' || !isInEdition}
|
||||
defaultValue={result[key]}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
))}
|
||||
{isInEdition ? (
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={event => {
|
||||
onDataUpdate(event, target)
|
||||
this.setState({ isInEdition: false })
|
||||
}
|
||||
}
|
||||
value="Submit"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={event => {
|
||||
event.target.form.reset()
|
||||
this.setState({ isInEdition: false })
|
||||
}}
|
||||
value="Cancel"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={() => this.setState({ isInEdition: true })}
|
||||
value="Edit"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-danger btn-lg btn-block"
|
||||
value="Delete"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
<Link to={`/admin/${target}`}>Back to the list</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
message: state.message,
|
||||
}),
|
||||
dispatch => ({
|
||||
onDataUpdate: (e, target) => {
|
||||
const data = [].slice
|
||||
.call(e.target.form.elements)
|
||||
.reduce(function(map, obj) {
|
||||
if (obj.name) {
|
||||
map[obj.name] = obj.value
|
||||
}
|
||||
return map
|
||||
}, {})
|
||||
dispatch(updateData(target, data))
|
||||
},
|
||||
})
|
||||
)(AdminDetail)
|
67
mpwo_client/src/components/Admin/generic/AdminPage.jsx
Normal file
67
mpwo_client/src/components/Admin/generic/AdminPage.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function AdminPage(props) {
|
||||
|
||||
const { data, detailLink, target } = props
|
||||
const { error } = data
|
||||
const results = data.data
|
||||
const tbKeys = []
|
||||
if (results.length > 0) {
|
||||
Object.keys(results[0]).map(key => tbKeys.push(key))
|
||||
}
|
||||
const title = target.charAt(0).toUpperCase() + target.slice(1)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>mpwo - Admin</title>
|
||||
</Helmet>
|
||||
<h1 className="page-title">
|
||||
Administration - {title}
|
||||
</h1>
|
||||
{error ? (
|
||||
<code>{error}</code>
|
||||
) : (
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-2" />
|
||||
<div className="col-md-8 card">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
{tbKeys.map(
|
||||
tbKey => <th key={tbKey} scope="col">{tbKey}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ results.map((result, idx) => (
|
||||
<tr key={idx}>
|
||||
{ Object.keys(result).map(key => {
|
||||
if (key === 'id') {
|
||||
return (
|
||||
<th key={key} scope="row">
|
||||
<Link to={`/admin/${detailLink}/${result[key]}`}>
|
||||
{result[key]}
|
||||
</Link>
|
||||
</th>
|
||||
)
|
||||
}
|
||||
return <td key={key}>{result[key]}</td>
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="col-md-2" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux'
|
||||
import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
|
||||
import AdminMenu from './AdminMenu'
|
||||
import AdminSport from './AdminSport'
|
||||
import AdminSports from './AdminSports'
|
||||
import AccessDenied from './../Others/AccessDenied'
|
||||
import NotFound from './../Others/NotFound'
|
||||
@ -22,7 +23,8 @@ class Admin extends React.Component {
|
||||
user.isAdmin ? (
|
||||
<Switch>
|
||||
<Route exact path="/admin" component={AdminMenu} />
|
||||
<Route path="/admin/sports" component={AdminSports} />
|
||||
<Route exact path="/admin/sports" component={AdminSports} />
|
||||
<Route path="/admin/sport" component={AdminSport} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
) : (
|
||||
|
@ -2,8 +2,8 @@ import { apiUrl } from '../utils'
|
||||
|
||||
export default class MpwoApi {
|
||||
|
||||
static getData(target) {
|
||||
const request = new Request(`${apiUrl}${target}`, {
|
||||
static getData(target, id = null) {
|
||||
const request = new Request(`${apiUrl}${target}${id ? `/${id}` : ''}`, {
|
||||
method: 'GET',
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
@ -14,4 +14,18 @@ export default class MpwoApi {
|
||||
.then(response => response.json())
|
||||
.catch(error => error)
|
||||
}
|
||||
|
||||
static updateData(target, data) {
|
||||
const request = new Request(`${apiUrl}${target}/${data.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${window.localStorage.getItem('authToken')}`,
|
||||
}),
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
return fetch(request)
|
||||
.then(response => response.json())
|
||||
.catch(error => error)
|
||||
}
|
||||
}
|
||||
|
@ -13,13 +13,6 @@ const handleDataAndError = (state, type, action) => {
|
||||
return {
|
||||
...state,
|
||||
data: action.data[action.target],
|
||||
error: null,
|
||||
}
|
||||
case 'SET_ERROR':
|
||||
return {
|
||||
...state,
|
||||
data: { ...initial[type].data },
|
||||
error: action.error,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
@ -76,9 +69,11 @@ const message = (state = initial.message, action) => {
|
||||
case 'PROFILE_ERROR':
|
||||
case 'PROFILE_UPDATE_ERROR':
|
||||
case 'PICTURE_ERROR':
|
||||
case 'SET_ERROR':
|
||||
return action.message
|
||||
case 'LOGOUT':
|
||||
case 'PROFILE_SUCCESS':
|
||||
case 'SET_RESULTS':
|
||||
case '@@router/LOCATION_CHANGE':
|
||||
return ''
|
||||
default:
|
||||
|
@ -1,6 +1,5 @@
|
||||
const emptyData = {
|
||||
data: [],
|
||||
error: null,
|
||||
}
|
||||
|
||||
export default {
|
||||
|
Reference in New Issue
Block a user