API & Client - sport administration & refactor - #15

This commit is contained in:
Sam
2019-09-22 23:03:56 +02:00
parent a10128f13e
commit 1f8de2eccc
30 changed files with 518 additions and 673 deletions

View File

@ -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)
: []

View File

@ -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>
))}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View 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)

View File

@ -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)

View File

@ -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>
)
}

View File

@ -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)
)

View File

@ -170,6 +170,12 @@ label {
list-style-type: square;
}
.admin-message {
color: #7c7c7d;
font-size: 0.9em;
font-style: italic;
}
.card {
text-align: left;
}