Commit 1fff9b2a authored by Rémy Huet's avatar Rémy Huet 💻

Add infrastructure gestion

parent 0b1a6505
Pipeline #31978 passed with stage
in 56 seconds
......@@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
import Login from './pages/Login';
import PageContainer from './components/Container/PageContainer';
import Categories from './pages/Categories';
import Infrastructures from './pages/Infrastructures';
function App(props) {
const { userIdentified } = props;
......@@ -17,6 +18,7 @@ function App(props) {
<>
<Route path="/login" component={Login} />
<PrivateRoute path="/categories/:action?/:category?" component={Categories} userIdentified={userIdentified} />
<PrivateRoute path="/infrastructures/:action?/:infrastructure?" component={Infrastructures} userIdentified={userIdentified} />
</>
</PageContainer>
</BrowserRouter>
......
import axios from 'axios';
/* eslint-disable import/no-unresolved */
import env from '../env';
/* eslint-enable import/no-unresolved */
export function fetchInfrastructures(token) {
return {
type: 'FETCH_INFRASTRUCTURES',
payload: axios.get(`${env.api_uri}/api/v1/infrastructures`,
{
headers: { Authorization: `Bearer ${token}` },
}),
};
}
export function deleteInfrastructure(token, id) {
return {
type: 'DELETE_INFRASTRUCTURE',
payload: axios.delete(`${env.api_uri}/api/v1/infrastructures/${id}`,
{
headers: { Authorization: `Bearer ${token}` },
}),
};
}
export function createInfrastructure(token, data) {
return {
type: 'CREATE_INFRASTRUCTURE',
payload: axios.post(`${env.api_uri}/api/v1/infrastructures`, data,
{
headers: { Authorization: `Bearer ${token}` },
}),
};
}
export function updateInfrastructure(token, id, data) {
return {
type: 'UPDATE_INFRASTRUCTURE',
payload: axios.put(`${env.api_uri}/api/v1/infrastructures/${id}`, data,
{
headers: { Authorization: `Bearer ${token}` },
}),
};
}
......@@ -15,6 +15,9 @@ function MenuItems({
<Menu.Item as={Link} to="/categories" name="/categories" active={activeItem === '/categories'} onClick={onClick}>
Categories
</Menu.Item>
<Menu.Item as={Link} to="/infrastructures" name="/infrastructures" active={activeItem === '/infrastructures'} onClick={onClick}>
Infrastructures
</Menu.Item>
{!userIdentified && (
<Menu.Item as={Link} to="/login" position="right" onClick={onClick}>
<Button inverted={!fixed}>
......
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { submit } from 'redux-form';
import { Link } from 'react-router-dom';
import { Confirm, Header, Button } from 'semantic-ui-react';
import InfrastructureForm from '../../forms/Infrastructures';
import { createInfrastructure } from '../../actions/infrastructuresActions';
function InfrastructureCreate({ dispatch, userToken, callback }) {
const handleFormSubmit = (values) => {
dispatch(createInfrastructure(userToken, values)).then(callback).catch(callback);
};
return (
<Confirm
open
cancelButton={<Button as={Link} to="/infrastructures">Retour</Button>}
confirmButton={<Button primary onClick={() => dispatch(submit('infrastructure'))}> OK </Button>}
header={<Header as="h1">Créer une catégorie</Header>}
content={<InfrastructureForm onSubmit={handleFormSubmit} />}
/>
);
}
InfrastructureCreate.propTypes = {
dispatch: PropTypes.func.isRequired,
userToken: PropTypes.string.isRequired,
callback: PropTypes.func.isRequired,
};
export default connect(store => ({
userToken: store.user.token,
}))(InfrastructureCreate);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Confirm, Button, Header } from 'semantic-ui-react';
import { deleteInfrastructure } from '../../actions/infrastructuresActions';
function InfrastructureDelete({
name, id, userToken, dispatch, callback,
}) {
return (
<Confirm
open
cancelButton={<Button as={Link} to="/infrastructures">Retour</Button>}
confirmButton={(
<Button
onClick={
() => dispatch(deleteInfrastructure(userToken, id)).then(callback).catch(callback)
}
content="Oui"
/>
)}
header={<Header as="h1">Supprimer</Header>}
content={(
<>
<Header as="h3">{`Êtes-vous sûr de vouloir supprimer l'infrastructure ${name} ?`}</Header>
<p>La catégorie doit être vide</p>
</>
)}
/>
);
}
InfrastructureDelete.propTypes = {
name: PropTypes.string.isRequired,
id: PropTypes.number.isRequired,
userToken: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
callback: PropTypes.func.isRequired,
};
export default connect(store => ({
userToken: store.user.token,
}))(InfrastructureDelete);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Confirm, Button, Header } from 'semantic-ui-react';
import { submit } from 'redux-form';
import InfrastructureForm from '../../forms/Infrastructures';
import { updateInfrastructure } from '../../actions/infrastructuresActions';
function InfrastructureEdit({
id, name, description, dispatch, callback, userToken,
}) {
const handleFormSubmit = (values) => {
dispatch(updateInfrastructure(userToken, id, values)).then(callback).catch(callback);
};
return (
<Confirm
open
cancelButton={<Button as={Link} to={`/infrastructures/show/${id}`}>Retour</Button>}
confirmButton={<Button primary onClick={() => (dispatch(submit('infrastructure')))}> OK </Button>}
header={<Header as="h1">Éditer</Header>}
content={
<InfrastructureForm onSubmit={handleFormSubmit} defaultValues={{ name, description }} />
}
/>
);
}
InfrastructureEdit.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
callback: PropTypes.func.isRequired,
userToken: PropTypes.string.isRequired,
};
export default connect(store => ({
userToken: store.user.token,
}))(InfrastructureEdit);
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Card, Button } from 'semantic-ui-react';
function CategoryIndex({
name, userType, id,
}) {
return (
<Card raised>
<Card.Content>
<Card.Header>
{name}
</Card.Header>
</Card.Content>
<Card.Content extra>
<Button.Group>
<Button primary as={Link} to={`/infrastructures/show/${id}`}>Voir</Button>
{userType === 'Responsable' && (
<>
<Button.Or text="ou" />
<Button negative as={Link} to={`/infrastructures/delete/${id}`}>Supprimer</Button>
</>)}
</Button.Group>
</Card.Content>
</Card>
);
}
CategoryIndex.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
userType: PropTypes.string.isRequired,
};
export default connect(store => ({
userType: store.user.user.type,
}))(CategoryIndex);
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import {
Confirm, Button, Header,
} from 'semantic-ui-react';
export default function CategoryShow({
name, id, description,
}) {
return (
<Confirm
open
cancelButton={<Button as={Link} to={`/infrastructures/edit/${id}`} color="orange">Éditer</Button>}
confirmButton={<Button primary as={Link} to="/infrastructures">Fermer</Button>}
header={<Header as="h1">{name}</Header>}
content={description}
/>
);
}
CategoryShow.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};
import React from 'react';
import { TextArea } from 'semantic-ui-react';
export default function CustomTextArea({ currentValue, input, ...rest }) {
return (
<TextArea
onChange={(event, data) => input.onChange(data.value)}
value={currentValue}
{...rest}
/>
);
}
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import {
Form, Input, Container, Segment,
} from 'semantic-ui-react';
import TextArea from './CustomTextArea';
class InfrastructureForm extends Component {
componentDidMount() {
const { initialize, defaultValues } = this.props;
initialize(defaultValues);
}
render() {
const { handleSubmit, defaultValues } = this.props;
return (
<Container>
<Segment>
<Form onSubmit={handleSubmit}>
<Form.Field>
<Field name="name" component={Input} placeholder="Nom" />
</Form.Field>
<Form.Field>
<Field name="description" defaultValue={defaultValues.description} component={TextArea} placeholder="Description" />
</Form.Field>
</Form>
</Segment>
</Container>
);
}
}
InfrastructureForm.propTypes = {
handleSubmit: PropTypes.func.isRequired,
defaultValues: PropTypes.shape({
name: PropTypes.string,
description: PropTypes.string,
}),
initialize: PropTypes.func.isRequired,
};
InfrastructureForm.defaultProps = {
defaultValues: {
name: null,
description: null,
},
};
export default reduxForm({
form: 'infrastructure',
})(InfrastructureForm);
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import {
Dimmer, Loader, Container, Card, Button, Divider, Message,
} from 'semantic-ui-react';
import PropTypes from 'prop-types';
import { fetchInfrastructures } from '../actions/infrastructuresActions';
import InfrastructureIndex from '../components/Infrastructure/Index';
import InfrastructureShow from '../components/Infrastructure/Show';
import InfrastructureEdit from '../components/Infrastructure/Edit';
import InfrastructureDelete from '../components/Infrastructure/Delete';
import InfrastructureCreate from '../components/Infrastructure/Create';
function Infrastructures({
infrastructuresFetched,
infrastructuresFetching,
infrastructures,
dispatch,
userToken,
match,
history,
infrastructuresAction,
infrastructuresActionInfo,
infrastructuresActionSuccess,
}) {
const { params } = match;
// Map all infrastructures in array of <Infrastructure />
const index = infrastructures.map(({ id, name }) => (
<InfrastructureIndex
key={id}
id={id}
name={name}
/>));
// Get single infrastructure attributes
let inf;
const { name = null, id = null, description = null } = (params.infrastructure
&& (inf = infrastructures.find(
i => i.id === parseInt(params.infrastructure, 10),
)))
? inf
: {};
if (!infrastructuresFetched && !infrastructuresFetching) {
dispatch(fetchInfrastructures(userToken));
}
return (
<Container style={{ margin: '1em' }}>
{infrastructuresFetching && (
<Dimmer active>
<Loader>Chargement en cours</Loader>
</Dimmer>)}
{infrastructuresAction && infrastructuresActionSuccess !== null && (
<Message
floated="center"
floating
header={infrastructuresActionInfo}
positive={infrastructuresActionSuccess}
negative={!infrastructuresActionSuccess}
onDismiss={() => dispatch({ type: 'INFRASTRUCTURE_MESSAGE_DISMISS' })}
/>
)}
<Button floated="right" style={{ marginBottom: '1em' }} color="orange" icon="redo" onClick={() => dispatch(fetchInfrastructures(userToken))} />
<Button floated="right" style={{ marginBottom: '1em' }} positive icon="plus" as={Link} to="/infrastructures/create" />
<Divider clearing />
<Card.Group centered>
{index}
</Card.Group>
{params.action && ({
show: (
<InfrastructureShow
name={name}
id={id}
description={description}
/>
),
edit: (
<InfrastructureEdit
id={id}
name={name}
description={description}
callback={() => history.push('/infrastructures')}
/>
),
delete: (
<InfrastructureDelete
id={id}
name={name}
callback={() => history.push('/infrastructures')}
/>
),
create: (
<InfrastructureCreate
callback={() => history.push('/infrastructures')}
/>
),
}[params.action])}
</Container>
);
}
Infrastructures.propTypes = {
infrastructuresFetched: PropTypes.bool.isRequired,
infrastructuresFetching: PropTypes.bool.isRequired,
infrastructures: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
}),
).isRequired,
dispatch: PropTypes.func.isRequired,
userToken: PropTypes.string.isRequired,
infrastructuresAction: PropTypes.string,
infrastructuresActionInfo: PropTypes.string,
infrastructuresActionSuccess: PropTypes.bool,
match: PropTypes.shape({
params: PropTypes.object,
}).isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
};
Infrastructures.defaultProps = {
infrastructuresAction: null,
infrastructuresActionInfo: null,
infrastructuresActionSuccess: null,
};
export default connect(store => ({
infrastructuresFetched: store.infrastructures.fetched,
infrastructuresFetching: store.infrastructures.fetching,
infrastructures: store.infrastructures.infrastructures,
infrastructuresAction: store.infrastructures.action,
infrastructuresActionSuccess: store.infrastructures.success,
infrastructuresActionInfo: store.infrastructures.info,
userToken: store.user.token,
}))(Infrastructures);
......@@ -3,9 +3,11 @@ import { reducer as formReducer } from 'redux-form';
import user from './userReducer';
import categories from './categoriesReducer';
import infrastructures from './infrastructuresReducer';
export default combineReducers({
user,
categories,
infrastructures,
form: formReducer,
});
const initialState = {
fetching: false,
fetched: false,
action: null,
success: null,
info: null,
infrastructures: [],
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case ('FETCH_INFRASTRUCTURES_PENDING'): {
return {
...state,
fetching: true,
fetched: false,
action: null,
success: null,
info: null,
};
}
case ('FETCH_INFRASTRUCTURES_FULFILLED'): {
return {
...state,
fetching: false,
fetched: true,
infrastructures: action.payload.data,
};
}
case ('DELETE_INFRASTRUCTURE_PENDING'): {
return {
...state,
action: 'DELETE',
success: null,
};
}
case ('DELETE_INFRASTRUCTURE_FULFILLED'): {
return {
...state,
infrastructures: state.infrastructures.filter(
infrastructure => infrastructure.id !== action.payload.data.id,
),
success: true,
info: 'Infrastructure supprimée avec succès',
};
}
case ('DELETE_INFRASTRUCTURE_REJECTED'): {
return {
...state,
success: false,
info: 'Impossible de supprimer l\'infrastructure',
};
}
case ('CREATE_INFRASTRUCTURE_PENDING'): {
return {
...state,
action: 'CREATE',
success: null,
};
}
case ('CREATE_INFRASTRUCTURE_FULFILLED'): {
return {
...state,
infrastructures: [...state.infrastructures, action.payload.data],
success: true,
info: 'Infrastructure créée avec succès',
};
}
case ('CREATE_INFRASTRUCTURE_REJECTED'): {
return {
...state,
success: false,
info: 'Impossible de créer l\'infrastructure',
};
}
case ('UPDATE_INFRASTRUCTURE_PENDING'): {
return {
...state,
action: 'UPDATE',
success: null,
};
}
case ('UPDATE_INFRASTRUCTURE_FULFILLED'): {
return {
...state,
infrastructures: state.infrastructures.map(
infrastructure => (infrastructure.id === action.payload.data.id
? action.payload.data
: infrastructure
),
),
success: true,
info: 'Infrastructure mise à jour avec succès',
};
}
case ('UPDATE_INFRASTRUCTURE_REJECTED'): {
return {
...state,
success: false,
info: 'Impossible de mettre à jour l\'infrastructure',
};
}
case ('INFRASTRUCTURE_MESSAGE_DISMISS'): {
return {
...state,
action: null,
success: null,
info: null,
};
}
default: {
return state;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment