API & Client - sport administration & refactor - #15
This commit is contained in:
@ -74,7 +74,7 @@ class ActivityDisplay extends React.Component {
|
||||
} = this.props
|
||||
const { coordinates, displayModal } = this.state
|
||||
const [activity] = activities
|
||||
const title = activity ? activity.title : 'Activity'
|
||||
const title = activity ? activity.title : t('activities:Activity')
|
||||
const [sport] = activity
|
||||
? sports.filter(s => s.id === activity.sport_id)
|
||||
: []
|
||||
|
@ -5,7 +5,8 @@ import { capitalize } from '../../utils/index'
|
||||
|
||||
const menuItems = ['application', 'sports', 'users']
|
||||
|
||||
export default function AdminMenu() {
|
||||
export default function AdminMenu(props) {
|
||||
const { t } = props
|
||||
return (
|
||||
<div>
|
||||
<ul className="admin-items">
|
||||
@ -16,7 +17,7 @@ export default function AdminMenu() {
|
||||
pathname: `/admin/${item}`,
|
||||
}}
|
||||
>
|
||||
{capitalize(item)}
|
||||
{t(`administration:${capitalize(item)}`)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
|
@ -1,36 +0,0 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { getOrUpdateData } from '../../../actions'
|
||||
import AdminDetail from '../generic/AdminDetail'
|
||||
|
||||
class AdminSports extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadSport(this.props.match.params.sportId)
|
||||
}
|
||||
componentWillUnmount() {
|
||||
// reload all Sports
|
||||
this.props.loadSport(null)
|
||||
}
|
||||
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(getOrUpdateData('getData', 'sports', { id: sportId }))
|
||||
},
|
||||
})
|
||||
)(AdminSports)
|
@ -1,42 +0,0 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
|
||||
import { getOrUpdateData } from '../../../actions'
|
||||
import AdminPage from '../generic/AdminPage'
|
||||
import AdminSport from './AdminSport'
|
||||
import AdminSportsAdd from './AdminSportsAdd'
|
||||
import NotFound from '../../Others/NotFound'
|
||||
|
||||
class AdminSports extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadSports()
|
||||
}
|
||||
render() {
|
||||
const { sports } = this.props
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/admin/sports"
|
||||
render={() => <AdminPage data={sports} target="sports" />}
|
||||
/>
|
||||
<Route exact path="/admin/sports/add" component={AdminSportsAdd} />
|
||||
<Route exact path="/admin/sports/:sportId" component={AdminSport} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
sports: state.sports,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadSports: () => {
|
||||
dispatch(getOrUpdateData('getData', 'sports'))
|
||||
},
|
||||
})
|
||||
)(AdminSports)
|
@ -1,75 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { addData } from '../../../actions/index'
|
||||
import { history } from '../../../index'
|
||||
|
||||
class AdminSportsAdd extends React.Component {
|
||||
componentDidMount() {}
|
||||
|
||||
render() {
|
||||
const { message, onAddSport } = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Admin - Add Sport</title>
|
||||
</Helmet>
|
||||
<h1 className="page-title">Administration - Sport</h1>
|
||||
{message && <code>{message}</code>}
|
||||
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-2" />
|
||||
<div className="col-md-8">
|
||||
<div className="card">
|
||||
<div className="card-header">Add a sport</div>
|
||||
<div className="card-body">
|
||||
<form onSubmit={event => event.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
Label:
|
||||
<input
|
||||
name="label"
|
||||
className="form-control input-lg"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={event => onAddSport(event)}
|
||||
value="Submit"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push('/admin/sports')}
|
||||
value="Cancel"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
message: state.message,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
onAddSport: e => {
|
||||
const data = { label: e.target.form.label.value }
|
||||
dispatch(addData('sports', data))
|
||||
},
|
||||
})
|
||||
)(AdminSportsAdd)
|
121
fittrackee_client/src/components/Admin/Sports/index.jsx
Normal file
121
fittrackee_client/src/components/Admin/Sports/index.jsx
Normal file
@ -0,0 +1,121 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
import Message from '../../Common/Message'
|
||||
import { getOrUpdateData } from '../../../actions'
|
||||
import { history } from '../../../index'
|
||||
|
||||
class AdminSports extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadSports()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, sports, t, updateSport } = this.props
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - {t('administration:Administration')}</title>
|
||||
</Helmet>
|
||||
{message && <Message message={message} t={t} />}
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col card">
|
||||
<div className="card-body">
|
||||
{sports.length > 0 && (
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('administration:id')}</th>
|
||||
<th>{t('administration:Image')}</th>
|
||||
<th>{t('administration:Label')}</th>
|
||||
<th>{t('administration:Active')}</th>
|
||||
<th>{t('administration:Actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sports.map(sport => (
|
||||
<tr key={sport.id}>
|
||||
<th scope="row">{sport.id}</th>
|
||||
<td>
|
||||
<img
|
||||
className="admin-img"
|
||||
src={sport.img ? sport.img : '/img/photo.png'}
|
||||
alt="sport logo"
|
||||
/>
|
||||
</td>
|
||||
<td>{t(`sports:${sport.label}`)}</td>
|
||||
<td>
|
||||
{sport.is_active ? (
|
||||
<i
|
||||
className="fa fa-check-square-o custom-fa"
|
||||
aria-hidden="true"
|
||||
data-toggle="tooltip"
|
||||
/>
|
||||
) : (
|
||||
<i
|
||||
className="fa fa-square-o custom-fa"
|
||||
aria-hidden="true"
|
||||
data-toggle="tooltip"
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{sport._can_be_disabled ? (
|
||||
<input
|
||||
type="submit"
|
||||
className={`btn btn-${
|
||||
sport.is_active ? 'dark' : 'primary'
|
||||
} btn-sm`}
|
||||
value={
|
||||
sport.is_active
|
||||
? t('administration:Disable')
|
||||
: t('administration:Enable')
|
||||
}
|
||||
onClick={() =>
|
||||
updateSport(sport.id, !sport.is_active)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<span className="admin-message">
|
||||
{t('administration:activities exist')}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push('/admin/')}
|
||||
value={t('administration:Back')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadSports: () => {
|
||||
dispatch(getOrUpdateData('getData', 'sports'))
|
||||
},
|
||||
updateSport: (sportId, isActive) => {
|
||||
const data = { id: sportId, is_active: isActive }
|
||||
dispatch(getOrUpdateData('updateData', 'sports', data, false))
|
||||
},
|
||||
})
|
||||
)(AdminSports)
|
@ -1,141 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { deleteData, getOrUpdateData } from '../../../actions/index'
|
||||
import { history } from '../../../index'
|
||||
|
||||
class AdminDetail extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
isInEdition: false,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, onDataUpdate, onDataDelete, results, target } = this.props
|
||||
const { isInEdition } = this.state
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Admin</title>
|
||||
</Helmet>
|
||||
{message ? (
|
||||
<code>{message}</code>
|
||||
) : (
|
||||
results.length === 1 && (
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col card">
|
||||
<div className="card-body">
|
||||
<form onSubmit={event => event.preventDefault()}>
|
||||
{Object.keys(results[0])
|
||||
.filter(key => key.charAt(0) !== '_')
|
||||
.map(key => (
|
||||
<div className="form-group" key={key}>
|
||||
<label>
|
||||
{key}:
|
||||
{key === 'img' ? (
|
||||
<img
|
||||
src={
|
||||
results[0][key]
|
||||
? results[0][key]
|
||||
: '/img/photo.png'
|
||||
}
|
||||
alt="property"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
className="form-control input-lg"
|
||||
name={key}
|
||||
readOnly={key === 'id' || !isInEdition}
|
||||
defaultValue={results[0][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"
|
||||
disabled={!results[0]._can_be_deleted}
|
||||
onClick={event => onDataDelete(event, target)}
|
||||
title={
|
||||
results[0]._can_be_deleted
|
||||
? ''
|
||||
: "Can't be deleted, associated data exist"
|
||||
}
|
||||
value="Delete"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push(`/admin/${target}`)}
|
||||
value="Back to the list"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
message: state.message,
|
||||
}),
|
||||
dispatch => ({
|
||||
onDataDelete: (e, target) => {
|
||||
const id = e.target.form.id.value
|
||||
dispatch(deleteData(target, id))
|
||||
},
|
||||
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(getOrUpdateData('updateData', target, data))
|
||||
},
|
||||
})
|
||||
)(AdminDetail)
|
@ -1,94 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { history } from '../../../index'
|
||||
|
||||
export default function AdminPage(props) {
|
||||
const { data, target } = props
|
||||
const { error } = data
|
||||
const results = data.data
|
||||
const tbKeys = []
|
||||
if (results.length > 0) {
|
||||
Object.keys(results[0])
|
||||
.filter(key => key.charAt(0) !== '_')
|
||||
.map(key => tbKeys.push(key))
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Admin</title>
|
||||
</Helmet>
|
||||
{error ? (
|
||||
<code>{error}</code>
|
||||
) : (
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col card">
|
||||
<div className="card-body">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
{tbKeys.map(tbKey => (
|
||||
<th key={tbKey} scope="col">
|
||||
{tbKey}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.map((result, idx) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<tr key={idx}>
|
||||
{Object.keys(result)
|
||||
.filter(key => key.charAt(0) !== '_')
|
||||
.map(key => {
|
||||
if (key === 'id') {
|
||||
return (
|
||||
<th key={key} scope="row">
|
||||
<Link to={`/admin/${target}/${result[key]}`}>
|
||||
{result[key]}
|
||||
</Link>
|
||||
</th>
|
||||
)
|
||||
} else if (key === 'img') {
|
||||
return (
|
||||
<td key={key}>
|
||||
<img
|
||||
className="admin-img"
|
||||
src={
|
||||
result[key]
|
||||
? result[key]
|
||||
: '/img/photo.png'
|
||||
}
|
||||
alt="logo"
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
return <td key={key}>{result[key]}</td>
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={() => history.push(`/admin/${target}/add`)}
|
||||
value="Add a new item"
|
||||
/>
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-secondary btn-lg btn-block"
|
||||
onClick={() => history.push('/admin/')}
|
||||
value="Back"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,21 +1,22 @@
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link, Redirect, Route, Switch } from 'react-router-dom'
|
||||
|
||||
import AdminDashboard from './AdminDashboard'
|
||||
import AdminMenu from './AdminMenu'
|
||||
import AdminSports from './Sports/AdminSports'
|
||||
import AdminSports from './Sports'
|
||||
import AccessDenied from './../Others/AccessDenied'
|
||||
import NotFound from './../Others/NotFound'
|
||||
import { isLoggedIn } from '../../utils'
|
||||
|
||||
function Admin(props) {
|
||||
const { user } = props
|
||||
const { t, user } = props
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>FitTrackee - Admin</title>
|
||||
<title>FitTrackee - {t('administration:Administration')}</title>
|
||||
</Helmet>
|
||||
<div className="container dashboard">
|
||||
<div className="row">
|
||||
@ -27,11 +28,11 @@ function Admin(props) {
|
||||
pathname: '/admin/',
|
||||
}}
|
||||
>
|
||||
Administration
|
||||
{t('administration:Administration')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<AdminMenu />
|
||||
<AdminMenu t={t} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,8 +40,16 @@ function Admin(props) {
|
||||
{isLoggedIn() ? (
|
||||
user.admin ? (
|
||||
<Switch>
|
||||
<Route exact path="/admin" component={AdminDashboard} />
|
||||
<Route path="/admin/sports" component={AdminSports} />
|
||||
<Route
|
||||
exact
|
||||
path="/admin"
|
||||
render={() => <AdminDashboard t={t} />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/admin/sports"
|
||||
render={() => <AdminSports t={t} />}
|
||||
/>
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
) : (
|
||||
@ -56,6 +65,8 @@ function Admin(props) {
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
user: state.user,
|
||||
}))(Admin)
|
||||
export default withTranslation()(
|
||||
connect(state => ({
|
||||
user: state.user,
|
||||
}))(Admin)
|
||||
)
|
||||
|
@ -170,6 +170,12 @@ label {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
.admin-message {
|
||||
color: #7c7c7d;
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.card {
|
||||
text-align: left;
|
||||
}
|
||||
|
Reference in New Issue
Block a user