Dokończenie frontendu i poprawki na serwerze.

This commit is contained in:
Ewelina Wizła 2019-01-13 21:09:49 +01:00
parent 85c43b34a4
commit f766b1e2af
44 changed files with 2815 additions and 762 deletions

1372
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,15 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"jspdf": "^1.5.3",
"jspdf-autotable": "^3.0.2",
"react": "^16.7.0",
"react-date-picker": "^7.1.1",
"react-datepicker": "^2.0.0",
"react-dom": "^16.7.0",
"react-scripts": "2.1.2"
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.2",
"react-select": "^2.2.0"
},
"scripts": {
"start": "react-scripts start",

BIN
client/public/Helvetica.ttf Normal file

Binary file not shown.

View File

@ -22,9 +22,12 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
<title>React App</title>
</head>
<body>
<body class="bg-light">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
@ -37,5 +40,8 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -1,32 +1,56 @@
.App {
text-align: center;
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
.my-modal {
position: absolute;
z-index: 500;
/* background-color: white;
width: 70%;
border: 1px solid #ccc;
box-shadow: 1px 1px 1px black;
padding: 16px; */
left: 10%;
box-sizing: border-box;
transition: all 0.3s ease-out;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
.backdrop {
width: 100%;
height: 100%;
position: fixed;
z-index: 100;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
@media (min-width: 600px) {
.my-modal {
width: 500px;
left: calc(50% - 250px);
}
}

View File

@ -1,29 +1,63 @@
import React, { Component } from 'react';
import logo from './logo.svg';
import {
BrowserRouter,
Route,
Switch
} from 'react-router-dom';
import Header from './components/Layout/Header/Header';
import Home from './components/Pages/Home/Home';
import Login from './components/Pages/Login/Login';
import Companies from './components/Pages/Companies/Companies';
import Products from './components/Pages/Products/Products';
import ExpiryPositions from './components/Pages/ExpiryPositions/ExpiryPositions';
import Reports from './components/Pages/Reports/Reports';
import ShowReport from './components/Pages/Reports/ShowReport';
import './App.css';
import Login from './components/Login/Login'
class App extends Component {
state = {
user: null
}
componentDidMount = () => {
const user = JSON.parse(localStorage.getItem('user'));
if (user) {
this.setState({
user: user
});
}
}
loggedUserChanged = (user) => {
localStorage.setItem('user', JSON.stringify(user));
this.setState({
user: user
});
}
render() {
const loginRoute = !this.state.user && <Route exact path="/login" render={(props) => <Login {...props} onLoggedUserChanged={(user) => this.loggedUserChanged(user)} />} />;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<Login></Login>
</div>
<BrowserRouter>
<>
<Header user={this.state.user} onLoggedUserChanged={(user) => this.loggedUserChanged(user)} />
<div className="App container">
<Switch>
{ loginRoute }
{ this.state.user && <Route exact path="/products" render={(props) => <Products {...props} logout={() => this.loggedUserChanged(null)} />} /> }
{ this.state.user && <Route exact path="/companies" render={(props) => <Companies {...props} logout={() => this.loggedUserChanged(null)} />} /> }
{ this.state.user && <Route exact path="/expiryPositions" render={(props) => <ExpiryPositions {...props} logout={() => this.loggedUserChanged(null)} />} /> }
{ this.state.user && <Route exact path="/reports" render={(props) => <Reports {...props} logout={() => this.loggedUserChanged(null)} />} /> }
{ this.state.user && <Route exact path="/reports/:id" render={(props) => <ShowReport {...props} logout={() => this.loggedUserChanged(null)} />} /> }
<Route path="/" component={ Home } />
</Switch>
</div>
</>
</BrowserRouter>
);
}
}

View File

@ -0,0 +1,34 @@
import React, { Component } from 'react';
import {
Link,
NavLink
} from 'react-router-dom';
class Header extends Component {
render () {
const userManagement = this.props.user
? <Link to='/' onClick={() => this.props.onLoggedUserChanged(null)} className="ml-2" ><button type="button" className="btn btn-outline-primary">Wyloguj</button></Link>
: <Link to='/login' className="ml-2" ><button type="button" className="btn btn-outline-primary">Zaloguj</button></Link>
return (
<header className={'d-flex flex-column flex-md-row p-3 px-md-4 mb-3 bg-white border-bottom box-shadow'}>
<div className={'container d-flex justify-content-between'}>
<NavLink to='/' activeClassName={'font-weight-bold'}><h5 className={'my-0 mr-md-auto'}>Główna</h5></NavLink>
<nav className={'my-2 my-md-0 md-3'}>
{ this.props.user && <NavLink to='/products' activeClassName={'font-weight-bold'} className={'p-2 text-dark'}>Produkty</NavLink> }
{ this.props.user && <NavLink to='/companies' activeClassName={'font-weight-bold'} className={'p-2 text-dark'}>Dostawcy</NavLink> }
{ this.props.user && <NavLink to='/expiryPositions' activeClassName={'font-weight-bold'} className={'p-2 text-dark'}>Terminy przydatności</NavLink> }
{ this.props.user && <NavLink to='/reports' activeClassName={'font-weight-bold'} className={'p-2 text-dark'}>Raporty</NavLink> }
{userManagement}
</nav>
</div>
</header>
);
}
}
export default Header;

View File

@ -0,0 +1,7 @@
import React from 'react';
const Backdrop = (props) => (
props.show ? <div className='backdrop' onClick={props.clicked}></div> : null
);
export default Backdrop;

View File

@ -0,0 +1,49 @@
import React, { Component } from "react";
import Backdrop from './Backdrop';
class Modal extends Component {
shouldComponentUpdate( nextProps, nextState) {
return nextProps.show !== this.props.show || nextProps.children !== this.props.children;
}
onSubmit = (event) => {
event.preventDefault();
this.props.onSave();
}
render() {
return (
<>
<Backdrop show={this.props.show} clicked={this.props.modalClosed}/>
<div
className={'my-modal'}
style={{
transform:this.props.show ? 'translateY(0)' : 'translateY(-100vh)',
opacity: this.props.show ? '1' : '0'
}}>
<div className='modal-dialog' role='document'>
<form className='modal-content' onSubmit={(event) => this.onSubmit(event)}>
<div className='modal-header'>
<h5 className='modal-title'>{this.props.title}</h5>
<button type='button' className='close' onClick={this.props.modalClosed} aria-label='Close'>
<span aria-hidden='true'>&times;</span>
</button>
</div>
<div className='modal-body'>
{this.props.children}
</div>
<div className='modal-footer'>
<button type='button' className='btn btn-secondary' onClick={this.props.modalClosed}>Anuluj</button>
<button type='submit' className='btn btn-primary' onClick={this.props.modalClosed}>Zapisz</button>
</div>
</form>
</div>
</div>
</>
)
}
}
export default Modal;

View File

@ -1,54 +0,0 @@
import React, { Component } from "react";
import { userService } from "../../services/userService";
class Login extends Component {
state = {
login: "",
pass: ""
};
onLoginChanged = event => {
this.setState({
login: event.target.value
});
};
onPassChanged = event => {
this.setState({
pass: event.target.value
});
};
onSubmitClick = event => {
event.preventDefault(); // nie odswieza strony
userService.login(this.state.login, this.state.pass).then(result => {
console.log(result);
});
};
render() {
return (
<div className="Login">
<form onSubmit={event => this.onSubmitClick(event)}>
<input
onChange={event => this.onLoginChanged(event)}
value={this.state.login}
placeholder="Adres e-mail"
type="text"
className=""
/>
<input
onChange={this.onPassChanged}
value={this.state.pass}
placeholder="Hasło"
type="password"
/>
<button type="submit">
Zaloguj
</button>
</form>
</div>
);
}
}
export default Login;

View File

@ -0,0 +1,118 @@
import React, { Component } from 'react';
import { companyService } from '../../../services/companyService';
import Modal from '../../Layout/Modal/Modal';
import { compare } from '../../../helpers/sortHelper';
class Companies extends Component {
state = {
name: '',
companies: [],
error: null,
showModal: false
}
componentDidMount = () => {
this.getCompanies();
}
onNameChange = event => {
this.setState({
name: event.target.value
});
};
getCompanies = () => {
const self = this;
companyService.getCompanies().then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
companies: response.data.data.sort((a, b) => compare(a.Name, b.Name))
})
} else {
self.setState({
error: response.data
})
}
})
}
closeModal = () => { this.setState({showModal: false})}
showModal = () => {
this.setState({showModal: true});
this.nameInput.focus()
}
addCompany = () => {
if (this.state.name !== '') {
const self = this;
companyService.addOrUpdate({Name: this.state.name}).then(response => {
this.setState({showModal: false});
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getCompanies();
}
})
}
this.setState({name: ''});
}
deleteCompany = (companyId) => {
const self = this;
companyService.deleteCompany(companyId).then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getCompanies();
} else {
self.setState({
error: response.data
})
}
})
}
render() {
const error = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{this.state.error}
</div>);
const companyItems = this.state.companies.map(company => {
return <li className='list-group-item' key={company.ID}>
<p>{company.Name}</p>
<div className="text-right">
<span onClick={() => this.deleteCompany(company.ID)} className="btn btn-secondary mr-3">Usuń</span>
</div>
</li>
})
return (
<>
<button onClick={() => this.showModal()} type='button' className='btn btn-primary mb-2'>Dodaj</button>
<Modal title='Dodaj dostawcę' onSave={this.addCompany} show={this.state.showModal} modalClosed={this.closeModal}>
<input
type='text'
ref={(input) => { this.nameInput = input; }}
className='form-control'
placeholder='Nazwa'
onChange={this.onNameChange}
value={this.state.name}
required
autoFocus />
</Modal>
{error}
<ul className='list-group'>
{companyItems}
</ul>
</>
);
}
}
export default Companies;

View File

@ -0,0 +1,20 @@
import React from 'react';
const ExpiryPosition = (props) => {
return (
<div className='card'>
<div className='card-body'>
<h5 className='card-title'>{props.name}</h5>
<p className='card-text'>{props.code}</p>
<h6 className='card-subtitle mb-2 text-muted'>{props.companyName}</h6>
<h6 className='card-subtitle mb-2 text-muted'>{props.expiryDate.substring(0, 10)}</h6>
<h6 className='card-subtitle mb-2 text-muted'>{props.batchNumber}</h6>
<div className="text-right">
<span onClick={() => props.deleteExpiryPosition(props.id)} className="btn btn-secondary mr-3">Usuń</span>
</div>
</div>
</div>
)
}
export default ExpiryPosition;

View File

@ -0,0 +1,315 @@
import React, { Component } from 'react';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import Select from 'react-select';
import ExpiryPosition from './ExpiryPosition';
import { expiryPositionService } from '../../../services/expiryPositionService';
import { productService } from '../../../services/productService';
import { companyService } from '../../../services/companyService';
import Modal from '../../Layout/Modal/Modal';
class ExpiryPositions extends Component {
state = {
name: '',
code: '',
products: [],
companies: [],
companyId: 'undefined',
batchNumber: '',
expiryDate: new Date(),
expiryPositions: [],
product: 'undefined',
showModal: false,
showProductModal: false,
error: null
}
closeProductModal = () => { this.setState({showProductModal: false})}
showProductModal = () => {
this.setState({showProductModal: true});
this.nameInput.focus();
}
closeModal = () => { this.setState({showModal: false, showProductModal: false})}
showModal = () => {
this.setState({showModal: true});
}
onDateChange = expiryDate => this.setState({ expiryDate })
componentDidMount = () => {
this.getCompanies();
this.getProducts();
this.getExpiryPositions();
}
onBatchNumberChange = event => {
this.setState({
batchNumber: event.target.value
});
};
getProducts = () => {
const self = this;
productService.getProducts().then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
products: response.data.data
})
if (!this.state.product) {
self.setState({
product: response.data.data[0]
})
}
} else {
self.setState({
error: response.data
})
}
})
}
getExpiryPositions = () => {
const self = this;
expiryPositionService.getExpiryPositions().then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
expiryPositions: response.data.data
})
} else {
self.setState({
error: response.data
})
}
})
}
addExpiryPosition = () => {
if (this.state.batchNumber !== '' && this.state.product) {
const self = this;
expiryPositionService.addOrUpdate({BatchNumber: this.state.batchNumber, ExpiryDate: this.state.expiryDate, ProductID: this.state.product.ID}).then(response => {
this.setState({
showModal: false
})
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getExpiryPositions();
}
})
}
this.setState({
batchNumber: '',
expiryDate: new Date(),
product: this.state.products[0]
});
}
deleteExpiryPosition = (expiryPositionId) => {
const self = this;
expiryPositionService.deleteExpiryPosition(expiryPositionId).then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getExpiryPositions();
} else {
self.setState({
error: response.data
})
}
})
}
clearModal = () => {
this.setState({
batchNumber: '',
expiryDate: new Date(),
product: this.state.products[0]
});
}
clearProductModal = () => {
this.setState({
name: '',
code: new Date(),
companyId: this.state.companies[0] && this.state.companies[0].ID
});
this.getCompanies();
}
getCompanies = () => {
const self = this;
companyService.getCompanies().then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
companies: response.data.data,
companyId: response.data.data[0] && response.data.data[0].ID
})
} else {
self.setState({
error: response.data
})
}
})
}
handleCompanyChange = (event) => {
this.setState({companyId: parseInt(event.target.value)});
}
addProduct = () => {
if (this.state.name !== '' && this.state.code !== '' && this.state.companyId) {
const self = this;
productService.addOrUpdate({Name: this.state.name, Code: this.state.code, CompanyID: this.state.companyId}).then(response => {
this.closeProductModal()
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getProducts();
this.setState({
product: response.data.data
})
}
})
}
this.setState({
name: '',
code: '',
companyId: this.state.companies[0] && this.state.companies[0].ID
});
}
onNameChange = event => {
this.setState({
name: event.target.value
});
};
onCodeChange = event => {
this.setState({
code: event.target.value
});
};
onOptionChange = product => {
this.setState({
product: product
});
};
render() {
const error = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{this.state.error}
</div>);
const expiryPositionItems = this.state.expiryPositions.map(expiryPosition => {
return (
<li className='list-group-item' key={expiryPosition.ID}>
<ExpiryPosition
name={expiryPosition.Product.Name}
companyName={expiryPosition.Product.Company.Name}
code={expiryPosition.Product.Code}
id={expiryPosition.ID}
deleteExpiryPosition={(id) => this.deleteExpiryPosition(id)}
batchNumber={expiryPosition.BatchNumber}
expiryDate={expiryPosition.ExpiryDate}/>
</li>
)
});
const selectItems = this.state.companies.map(company => {
return (
<option key={company.ID} value={company.ID}>{company.Name}</option>
)
});
return (
<>
<button onClick={() => this.showModal()} type='button' className='btn btn-primary mb-2'>Dodaj</button>
{<Modal title='Dodaj pozycję' onSave={this.addExpiryPosition} show={this.state.showModal} modalClosed={this.closeModal}>
<div className='text-center'>
<DatePicker
selected={this.state.expiryDate}
onChange={this.onDateChange}
inline
/>
</div>
<input
type='number'
className='form-control mb-2'
placeholder='Numer partii'
onChange={this.onBatchNumberChange}
value={this.state.batchNumber}
required
autoFocus />
<div className='text-center mb-2'>
<label>Wybierz produkt</label>
</div>
<Select
value={this.state.product}
className='mb-2'
onChange={this.onOptionChange}
options={this.state.products}
getOptionLabel={(option) => option.Name}
getOptionValue={(option) => option.ID}
placeholder={'Wyszukaj po nazwie'}
maxMenuHeight='200'
menuShouldScrollIntoView='false'
/>
<Select
value={this.state.product}
className='mb-2'
onChange={this.onOptionChange}
options={this.state.products}
getOptionLabel={(option) => option.Code}
getOptionValue={(option) => option.ID}
placeholder={'Wyszukaj po kodzie towaru'}
maxMenuHeight='200'
menuShouldScrollIntoView='false'
/>
<button onClick={() => this.showProductModal()} type='button' className='btn btn-primary mb-2'>Dodaj produkt</button>
</Modal>}
<Modal title='Dodaj Produkt' onSave={this.addProduct} show={this.state.showProductModal} modalClosed={this.closeProductModal}>
<input
type='text'
ref={(input) => { this.nameInput = input; }}
className='form-control'
placeholder='Nazwa'
onChange={this.onNameChange}
value={this.state.name}
required
autoFocus />
<input
type='number'
className='form-control'
placeholder='Kod'
onChange={this.onCodeChange}
value={this.state.code}
required
autoFocus />
<label>Wybierz dostawcę</label>
<select value={this.state.companyId} onChange={this.handleCompanyChange} className='form-control'>
{selectItems}
</select>
</Modal>
{error}
<ul className='list-group'>
{expiryPositionItems}
</ul>
</>
);
}
}
export default ExpiryPositions;

View File

@ -0,0 +1,11 @@
import React from "react";
const Home = (props) => {
return (
<div>
Witaj!
</div>
)
}
export default Home;

View File

@ -0,0 +1,126 @@
import React, { Component } from 'react';
import { userService } from '../../../services/userService';
class Login extends Component {
state = {
login: '',
pass: '',
register: false,
error: null,
warning: null
};
onLoginChanged = event => {
this.setState({
login: event.target.value
});
};
onPassChanged = event => {
this.setState({
pass: event.target.value
});
};
onLoginClick = event => {
event.preventDefault(); // nie odswieza strony
const self = this;
userService.login(this.state.login, this.state.pass).then(response => {
if (response.success) {
self.setState({
error: null,
warning: null
});
self.props.onLoggedUserChanged(response.data);
self.props.history.push("/");
} else {
self.setState({
error: response.data,
warning: null,
login: '',
pass: ''
});
}
});
};
onRegisterClick = event => {
event.preventDefault(); // nie odswieza strony
const self = this;
userService.register(this.state.login, this.state.pass).then(response => {
if (response.success) {
self.setState({
warning: 'Konto utworzone, poczekaj na aktywację',
error: null,
login: '',
pass: ''
});
} else {
self.setState({
error: response.data,
warning: null,
login: '',
pass: ''
});
}
});
};
onSwitchClick = () => {
this.setState({
register: !this.state.register,
error: null,
warning: null
})
}
render() {
const label = this.state.register ? 'Zarejestruj się' : 'Zaloguj się';
const labelError = this.state.register ? 'Rejestracja nie powiodła się' : 'Logowanie nie powiodło się';
const labelSubmitButton = this.state.register ? 'Załóż konto' : 'Zaloguj';
const labelSwitchButton = this.state.register ? 'Zaloguj' : 'Załóż konto';
const onClickHandler = this.state.register ? (event) => this.onRegisterClick(event) : (event) => this.onLoginClick(event);
const errorMessage = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{labelError} - {this.state.error}
</div>);
const waitForActivationMessage = ( this.state.warning &&
<div className='alert alert-warning' role='alert'>
{labelError} - {this.state.warning}
</div>);
return (
<div className='text-center'>
<form className='form-signin' onSubmit={onClickHandler}>
<h1 className='h3 mb-3 font-weight-normal'>{label}</h1>
<input
type='email'
id='inputEmail'
className='form-control'
placeholder='Email address'
onChange={this.onLoginChanged}
value={this.state.login}
required
autoFocus />
<input
type='password'
id='inputPassword'
className='form-control mb-3'
placeholder='Password'
onChange={this.onPassChanged}
value={this.state.pass}
required />
<button className='btn btn-lg btn-primary btn-block mb-3' type='submit'>{labelSubmitButton}</button>
<p className='mb-3'>lub</p>
<button className='btn btn-lg btn-primary btn-block mb-3' onClick={this.onSwitchClick} type='button'>{labelSwitchButton}</button>
{errorMessage}
{waitForActivationMessage}
</form>
</div>
);
}
}
export default Login;

View File

@ -0,0 +1,18 @@
import React from 'react';
const Product = (props) => {
return (
<div className='card'>
<div className='card-body'>
<h5 className='card-title'>{props.name}</h5>
<h6 className='card-subtitle mb-2 text-muted'>{props.companyName}</h6>
<p className='card-text'>{props.code}</p>
<div className="text-right">
<span onClick={() => props.deleteProduct(props.id)} className="btn btn-secondary mr-3">Usuń</span>
</div>
</div>
</div>
)
}
export default Product;

View File

@ -0,0 +1,178 @@
import React, { Component } from 'react';
import Product from './Product';
import { companyService } from '../../../services/companyService';
import { productService } from '../../../services/productService';
import Modal from '../../Layout/Modal/Modal';
class Products extends Component {
state = {
name: '',
code: '',
products: [],
companies: [],
companyId: 'undefined',
error: null,
showModal: false
}
closeModal = () => { this.setState({showModal: false})}
showModal = () => {
this.setState({showModal: true});
this.nameInput.focus();
}
componentDidMount = () => {
this.getCompanies();
this.getProducts();
}
onNameChange = event => {
this.setState({
name: event.target.value
});
};
onCodeChange = event => {
this.setState({
code: event.target.value
});
};
getCompanies = () => {
const self = this;
companyService.getCompanies().then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
companies: response.data.data,
companyId: response.data.data[0] && response.data.data[0].ID
})
} else {
self.setState({
error: response.data
})
}
})
}
getProducts = () => {
const self = this;
productService.getProducts().then(response => {
this.setState({showModal: false});
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
products: response.data.data
})
} else {
self.setState({
error: response.data
})
}
})
}
addProduct = () => {
this.setState({
error: null
});
if (this.state.name !== '' && this.state.code !== '' && this.state.companyId) {
const self = this;
productService.addOrUpdate({Name: this.state.name, Code: this.state.code, CompanyID: this.state.companyId}).then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getProducts();
}
})
}
this.setState({
name: '',
code: '',
companyId: this.state.companies[0] && this.state.companies[0].ID
});
}
deleteProduct = (productId) => {
this.setState({
error: null
});
const self = this;
productService.deleteProduct(productId).then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.getProducts();
} else {
self.setState({
error: response.data
})
}
})
}
handleChange = (event) => {
this.setState({companyId: parseInt(event.target.value)});
}
render() {
const error = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{this.state.error}
</div>);
const productItems = this.state.products.map(product => {
return (
<li className='list-group-item' key={product.ID}>
<Product name={product.Name} companyName={product.Company.Name} code={product.Code} id={product.ID} deleteProduct={(id) => this.deleteProduct(id)}/>
</li>
)
});
const selectItems = this.state.companies.map(company => {
return (
<option key={company.ID} value={company.ID}>{company.Name}</option>
)
});
return (
<>
<button onClick={() => this.showModal()} type='button' className='btn btn-primary mb-2'>Dodaj</button>
<Modal title='Dodaj Produkt' onSave={this.addProduct} show={this.state.showModal} modalClosed={this.closeModal}>
<input
ref={(input) => { this.nameInput = input; }}
type='text'
className='form-control'
placeholder='Nazwa'
onChange={this.onNameChange}
value={this.state.name}
required
autoFocus />
<input
type='number'
className='form-control'
placeholder='Kod'
onChange={this.onCodeChange}
value={this.state.code}
required
autoFocus />
<label>Wybierz dostawcę</label>
<select value={this.state.companyId} onChange={this.handleChange} className='form-control'>
{selectItems}
</select>
</Modal>
{error}
<ul className='list-group'>
{productItems}
</ul>
</>
);
}
}
export default Products;

View File

@ -0,0 +1,142 @@
import React, { Component } from 'react';
import ReportPosition from './ReportPosition';
import { reportService } from '../../../services/reportService';
import { expiryPositionService } from '../../../services/expiryPositionService';
class CreateReport extends Component {
state = {
reportPositions: [],
error: null,
values: {},
canSave: false
}
componentDidMount = () => {
this.getExpiryPositionsForContractor()
}
getExpiryPositionsForContractor = () => {
if (!this.props.company) {
return;
}
const self = this;
expiryPositionService.getExpiryPositionsForContractor(this.props.company.ID).then(response => {
if (response.logout) {
self.props.logout();
} else if (response.success) {
const positions = response.data.data.filter(position => position.ExpiryDate <= self.props.dateTo.toJSON());
if (!positions || positions.length === 0) {
this.props.hide();
return;
}
const values = {};
positions.forEach(position => { values[position.ID] = '' })
self.setState({
reportPositions: positions,
values: values
})
} else {
self.setState({
error: response.data
})
}
})
}
createReport = () => {
const reportPositions = this.state.reportPositions.filter(position => parseInt(this.state.values[position.ID]) > 0).map((position, index) => {
return {
PositionNumber: index+1,
Quantity: this.state.values[position.ID],
ProductExpiryPositionID: position.ID
}
});
const report = {
CompanyID: this.props.company.ID
};
const self = this;
reportService.createReport(report, reportPositions).then(response => {
if (response.logout) {
self.props.logout();
} else if (response.success) {
this.props.hide();
} else {
self.setState({
error: response.data
})
}
})
}
closeModal = () => { this.setState({showModal: false})}
showModal = () => {
this.setState({showModal: true});
}
onOptionChange = company => {
this.setState({
company: company
});
};
canSave = (values) => {
for (var name in values) {
if (values[name] && values[name] > 0) {
return true;
}
}
return false;
}
setItemValue = (id, value) => {
const newValues = {...this.state.values}
newValues[id] = parseInt(value);
this.setState({
values: newValues,
canSave: this.canSave(newValues)
})
}
onDateChange = expiryDate => this.setState({ expiryDate })
render() {
const error = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{this.state.error}
</div>);
const reportPositionItems = this.state.reportPositions.map((expiryPosition, index) => {
return (
<div key={expiryPosition.ID}>
<li className='list-group-item'>
<ReportPosition
index={index+1}
name={expiryPosition.Product.Name}
companyName={expiryPosition.Product.Company.Name}
code={expiryPosition.Product.Code}
id={expiryPosition.ID}
batchNumber={expiryPosition.BatchNumber}
expiryDate={expiryPosition.ExpiryDate}
value={this.state.values[expiryPosition.ID]}
setValue={(value) => this.setItemValue(expiryPosition.ID, value)}/>
</li>
</div>)
})
return (
<>
{error}
{this.state.canSave && <button onClick={() => this.createReport()} type='button' className='btn btn-primary mb-2'>Zapisz</button>}
<ul className='list-group'>
{reportPositionItems}
</ul>
</>
);
}
}
export default CreateReport;

View File

@ -0,0 +1,17 @@
import React from 'react';
const Report = (props) => {
return (
<div className='card'>
<div className='card-body'>
<h5 className='card-title'>Dostawca: {props.companyName}</h5>
<h6 className='card-subtitle mb-2 text-muted'>Utworzono {props.createdAt.substring(0, 19).replace("T", " ")}</h6>
<div className="text-right">
<span onClick={() => props.editReport(`/reports/${props.id}`)} className="btn btn-secondary mr-3">Otwórz</span>
</div>
</div>
</div>
)
}
export default Report;

View File

@ -0,0 +1,25 @@
import React from 'react';
const ReportPosition = (props) => {
const value = props.setValue
? <input
type='number'
className='form-control mb-2'
placeholder='Ilość'
onChange={(event) => props.setValue(event.target.value)}
value={props.value} />
: <h6 className='card-subtitle mb-2 text-muted'>Ilość: {props.value}</h6>
return (
<div className='card-body'>
<h5 className='card-title float-right'>{props.index}</h5>
<h5 className='card-title'>Produkt: {props.name}</h5>
<p className='card-text'>Kod: {props.code}</p>
<h6 className='card-subtitle mb-2 text-muted'>Dostawca: {props.companyName}</h6>
<h6 className='card-subtitle mb-2 text-muted'>Data przydatności: {props.expiryDate.substring(0, 10)}</h6>
<h6 className='card-subtitle mb-2 text-muted'>Numer partii: {props.batchNumber}</h6>
{value}
</div>
)
}
export default ReportPosition;

View File

@ -0,0 +1,150 @@
import React, { Component } from 'react';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import Select from 'react-select';
import Report from './Report';
import { reportService } from '../../../services/reportService';
import { companyService } from '../../../services/companyService';
import Modal from '../../Layout/Modal/Modal';
import { compare } from '../../../helpers/sortHelper';
import CreateReport from './CreateReport';
class Reports extends Component {
state = {
companies: [],
reports: [],
company: null,
error: null,
showModal: false,
expiryDate: new Date(),
create: false
}
componentDidMount = () => {
this.getCompanies();
}
onNameChange = event => {
this.setState({
name: event.target.value
});
};
getCompanies = () => {
const self = this;
companyService.getCompanies().then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
companies: response.data.data.sort((a, b) => compare(a.Name, b.Name))
})
} else {
self.setState({
error: response.data
})
}
})
}
getReports = () => {
if (!this.state.company) {
return;
}
const self = this;
reportService.getReports(this.state.company.ID).then(response => {
if (response.logout) {
this.props.logout();
} else if (response.success) {
self.setState({
reports: response.data.data
})
} else {
self.setState({
error: response.data
})
}
})
}
createReport = () => {
this.setState({
create: true
})
}
closeModal = () => { this.setState({showModal: false})}
showModal = () => {
this.setState({showModal: true});
}
onOptionChange = company => {
this.setState({
company: company
}, () => this.getReports());
};
onHideReport = () => {
this.setState({
create: false
}, () => this.getReports());
}
onDateChange = expiryDate => this.setState({ expiryDate })
redirectToTarget = (target) => {
this.props.history.push(target)
}
render() {
const error = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{this.state.error}
</div>);
const reportItems = this.state.reports.map(report => {
return <li className='list-group-item' key={report.ID}>
<Report id={report.ID} companyName={report.Company.Name} createdAt={report.CreatedAt} editReport={(target) => this.redirectToTarget(target)}/>
</li>
})
if (this.state.create)
{
return (<CreateReport key={this.state.company.ID} company={this.state.company} dateTo={this.state.expiryDate} hide={this.onHideReport}/>)
}
return (
<>
<Select
value={this.state.company}
className='mb-2'
onChange={this.onOptionChange}
options={this.state.companies}
getOptionLabel={(option) => option.Name}
getOptionValue={(option) => option.ID}
placeholder={'Wybierz dostawcę'}
/>
{ this.state.company && <button onClick={() => this.showModal()} type='button' className='btn btn-primary mb-2'>Dodaj</button>}
<Modal title='Dodaj dostawcę' onSave={this.createReport} show={this.state.showModal} modalClosed={this.closeModal}>
<div className='text-center'>
<DatePicker
selected={this.state.expiryDate}
onChange={this.onDateChange}
inline
/>
</div>
</Modal>
{error}
<ul className='list-group'>
{reportItems}
</ul>
</>
);
}
}
export default Reports;

View File

@ -0,0 +1,77 @@
import React, { Component } from 'react';
import ReportPosition from './ReportPosition';
import { reportService } from '../../../services/reportService';
import { pdfService } from '../../../services/pdfService';
class ShowReport extends Component {
state = {
reportPositions: [],
error: null
}
componentDidMount = () => {
this.getReportWithPositions()
}
getReportWithPositions = () => {
if (!this.props.match.params.id || !parseInt(this.props.match.params.id)) {
return;
}
const self = this;
reportService.getReportsWithPositions(this.props.match.params.id).then(response => {
if (response.logout) {
self.props.logout();
} else if (response.success) {
self.setState({
reportPositions: response.data.data
})
} else {
self.setState({
error: response.data
})
}
})
}
generatePdf = () => {
pdfService.generatePdf(this.state.reportPositions[0].ProductExpiryPosition.Product.Company.Name, this.state.reportPositions)
}
render() {
const error = ( this.state.error &&
<div className='alert alert-danger' role='alert'>
{this.state.error}
</div>);
const reportPositionItems = this.state.reportPositions.map((reportPosition) => {
return (
<div key={reportPosition.ID}>
<li className='list-group-item'>
<ReportPosition
index={reportPosition.PositionNumber}
name={reportPosition.ProductExpiryPosition.Product.Name}
companyName={reportPosition.ProductExpiryPosition.Product.Company.Name}
code={reportPosition.ProductExpiryPosition.Product.Code}
id={reportPosition.ID}
batchNumber={reportPosition.ProductExpiryPosition.BatchNumber}
expiryDate={reportPosition.ProductExpiryPosition.ExpiryDate}
value={reportPosition.Quantity}/>
</li>
</div>)
})
return (
<>
{error}
{this.state.reportPositions.length > 0 && <button onClick={this.generatePdf} type='button' className='btn btn-primary mb-2'>Pobierz pdf</button>}
<ul className='list-group'>
{reportPositionItems}
</ul>
</>
);
}
}
export default ShowReport;

View File

@ -1,28 +1,39 @@
export const baseUrl = 'http://localhost:8000/api';
export const baseUrl = `http://${window.location.hostname}:8000/api`;
export function authHeader() {
// return authorization header with basic auth credentials
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.token) {
return { 'Authorization': 'bearer ' + user.token };
if (user && user.Token) {
return { 'Authorization': 'bearer ' + user.Token, 'Content-Type': 'application/json' };
} else {
return {};
return { 'Content-Type': 'application/json' };
}
}
export function handleResponse(response, onError = null) {
export function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok || !data.status) {
if (onError) {
onError(response);
const error = {
message: (data && data.message) || response.statusText,
logout: response.status === 401
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
export function formatResponse(success, data) {
if (!success && data.message) {
data = data.message;
}
return {
success,
data,
logout: data.logout
}
}

View File

@ -0,0 +1,7 @@
export function compare(a,b) {
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
import { handleResponse, baseUrl, formatResponse, authHeader } from '../helpers/apiHelper'
export const companyService = {
getCompanies,
addOrUpdate,
deleteCompany
};
async function getCompanies() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/companies`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function addOrUpdate(company) {
const requestOptions = {
method: 'POST',
headers: authHeader(),
body: JSON.stringify(company)
};
try {
const response = await fetch(baseUrl + `/companies`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function deleteCompany(companyId) {
const requestOptions = {
method: 'DELETE',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/companies/${companyId}`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}

View File

@ -0,0 +1,75 @@
import { handleResponse, baseUrl, formatResponse, authHeader } from '../helpers/apiHelper'
export const expiryPositionService = {
getExpiryPositions,
getExpiryPositionsForContractor,
addOrUpdate,
deleteExpiryPosition
};
async function getExpiryPositions() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/expiryPositions`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function getExpiryPositionsForContractor(contractorId) {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/expiryPositions/${contractorId}`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function addOrUpdate(expiryPosition) {
const requestOptions = {
method: 'POST',
headers: authHeader(),
body: JSON.stringify(expiryPosition)
};
try {
const response = await fetch(baseUrl + `/expiryPositions`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function deleteExpiryPosition(expiryPositionId) {
const requestOptions = {
method: 'DELETE',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/expiryPositions/${expiryPositionId}`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}

View File

@ -0,0 +1,55 @@
import * as jsPDF from 'jspdf';
import 'jspdf-autotable';
import font from './Helvetica.json';
export const pdfService = {
generatePdf
};
function generatePdf(companyName, data) {
const columns = ["Numer pozycji","Nazwa produktu", "Kod produktu", "Ilość"];
let values = [];
data.forEach(position => {
const row = [position.PositionNumber, position.ProductExpiryPosition.Product.Name, position.ProductExpiryPosition.Product.Code, position.Quantity];
values.push(row);
})
const printDateTime = new Date();
printDateTime.setMinutes(printDateTime.getMinutes() - printDateTime.getTimezoneOffset());
const printDateTimeString = printDateTime.toJSON().substring(0, 19).replace("T", " ");
const createdDateTime = data[0].CreatedAt.substring(0, 19).replace("T", " ");
const doc = new jsPDF();
console.log(font.text);
doc.addFileToVFS('NewFont.ttf', font.text);
doc.addFont('NewFont.ttf', 'NewFont', 'normal');
doc.setFont('NewFont'); // set font
doc.setFontSize(20);
centeredText(doc, companyName, 15);
doc.setFontSize(10);
centeredText(doc, 'Wydrukowano: ' + printDateTimeString, 25);
centeredText(doc, 'Utworzono: ' + createdDateTime, 33);
doc.autoTable({
startY: 40,
head: [columns],
body: values,
styles: {font: 'NewFont', fontStyle: 'normal'},
});
let newY = doc.previousAutoTable.finalY + 20;
doc.setDrawColor(0, 0, 0)
doc.line(20, newY, 60, newY)
doc.text(32, newY + 10, "Wystawił/a");
doc.line(80, newY, 120, newY)
doc.text(91, newY + 10, "Potwierdził/a");
doc.line(140, newY, 180, newY)
doc.text(154, newY + 10, "Wysłał/a");
doc.save(companyName +'.pdf');
}
const centeredText = (doc, text, y) => {
var textWidth = doc.getStringUnitWidth(text) * doc.internal.getFontSize() / doc.internal.scaleFactor;
var textOffset = (doc.internal.pageSize.width - textWidth) / 2;
doc.text(textOffset, y, text);
}

View File

@ -0,0 +1,57 @@
import { handleResponse, baseUrl, formatResponse, authHeader } from '../helpers/apiHelper'
export const productService = {
getProducts,
addOrUpdate,
deleteProduct
};
async function getProducts() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/products`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function addOrUpdate(product) {
const requestOptions = {
method: 'POST',
headers: authHeader(),
body: JSON.stringify(product)
};
try {
const response = await fetch(baseUrl + `/products`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function deleteProduct(productId) {
const requestOptions = {
method: 'DELETE',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/products/${productId}`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}

View File

@ -0,0 +1,58 @@
import { handleResponse, baseUrl, formatResponse, authHeader } from '../helpers/apiHelper'
export const reportService = {
getReports,
getReportsWithPositions,
createReport
};
async function getReports(companyId) {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/reports/${companyId}`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function getReportsWithPositions(reportId) {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
try {
const response = await fetch(baseUrl + `/reportPositions/${reportId}`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}
async function createReport(report, positions) {
const requestOptions = {
method: 'POST',
headers: authHeader(),
body: JSON.stringify({ Report: report, ReportPositions: positions})
};
try {
const response = await fetch(baseUrl + `/reports`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
catch(error) {
return formatResponse(false, error);
}
}

View File

@ -1,9 +1,8 @@
import { handleResponse, baseUrl } from '../helpers/apiHelper'
import { handleResponse, baseUrl, formatResponse } from '../helpers/apiHelper'
export const userService = {
login,
register,
logout
register
};
async function login(username, password) {
@ -13,13 +12,14 @@ async function login(username, password) {
body: JSON.stringify({ 'email': username, password })
};
const response = await fetch(baseUrl + `/login`, requestOptions);
const data = await handleResponse(response, onLoginError);
// login successful if there's a user in the response
if (data.account) {
localStorage.setItem('user', JSON.stringify(data.account));
try {
const response = await fetch(baseUrl + `/login`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data.account);
}
catch(error) {
return formatResponse(false, error);
}
return data.account;
}
async function register(username, password) {
@ -29,23 +29,12 @@ async function register(username, password) {
body: JSON.stringify({ 'email': username, password })
};
const response = await fetch(baseUrl + `/register`, requestOptions);
const data = await handleResponse(response, onLoginError);
// login successful if there's a user in the response
if (data.account) {
localStorage.setItem('user', JSON.stringify(data.account));
try {
const response = await fetch(baseUrl + `/register`, requestOptions);
const data = await handleResponse(response);
return formatResponse(true, data);
}
return data.account;
}
function logout() {
// remove user from local storage to log user out
localStorage.removeItem('user');
}
function onLoginError(response) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
catch(error) {
return formatResponse(false, error);
}
}

View File

@ -1,6 +1,8 @@
package controllers
import (
"strconv"
"github.com/gorilla/mux"
"encoding/json"
"ExpiryDatesManager/server/model"
"ExpiryDatesManager/server/utils"
@ -15,6 +17,23 @@ var GetCompanies = func(w http.ResponseWriter, r *http.Request) {
utils.Respond(w, resp)
}
var DeleteCompany = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
companyID, err := strconv.ParseUint(vars["id"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while parsing request"))
return
}
err = model.DeleteCompany(uint(companyID))
if err != nil {
utils.Respond(w, utils.Message(false, "Nie udało się usunąć dostawcy"))
return
}
resp := utils.Message(true, "success")
utils.Respond(w, resp)
}
var CreateOrUpdateCompany = func(w http.ResponseWriter, r *http.Request) {
company := &model.Company{}

View File

@ -1,12 +1,28 @@
package controllers
import (
"strconv"
"github.com/gorilla/mux"
"encoding/json"
"ExpiryDatesManager/server/model"
"ExpiryDatesManager/server/utils"
"net/http"
)
var GetPositionsForCompany = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
companyID, err := strconv.ParseUint(vars["companyId"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while parsing request"))
return
}
data := model.GetPositionsForCompany(uint(companyID))
resp := utils.Message(true, "success")
resp["data"] = data
utils.Respond(w, resp)
}
var GetPositions = func(w http.ResponseWriter, r *http.Request) {
data := model.GetPositions()
@ -15,6 +31,23 @@ var GetPositions = func(w http.ResponseWriter, r *http.Request) {
utils.Respond(w, resp)
}
var DeletePosition = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
positionID, err := strconv.ParseUint(vars["id"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while parsing request"))
return
}
err = model.DeletePosition(uint(positionID))
if err != nil {
utils.Respond(w, utils.Message(false, "Nie udało się usunąć pozycji"))
return
}
resp := utils.Message(true, "success")
utils.Respond(w, resp)
}
var CreateOrUpdatePosition = func(w http.ResponseWriter, r *http.Request) {
position := &model.ProductExpiryPosition{}

View File

@ -1,6 +1,7 @@
package controllers
import (
"strconv"
"encoding/json"
"github.com/gorilla/mux"
"ExpiryDatesManager/server/model"
@ -25,6 +26,23 @@ var GetProduct = func(w http.ResponseWriter, r *http.Request) {
utils.Respond(w, resp)
}
var DeleteProduct = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
productID, err := strconv.ParseUint(vars["id"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while parsing request"))
return
}
err = model.DeleteProduct(uint(productID))
if err != nil {
utils.Respond(w, utils.Message(false, "Nie udało się usunąć produktu"))
return
}
resp := utils.Message(true, "success")
utils.Respond(w, resp)
}
var CreateOrUpdateProduct = func(w http.ResponseWriter, r *http.Request) {
product := &model.Product{}

View File

@ -1,29 +1,12 @@
package controllers
import (
"strconv"
"encoding/json"
"github.com/gorilla/mux"
"ExpiryDatesManager/server/model"
"ExpiryDatesManager/server/utils"
"net/http"
)
var GetReportsPositionsForReport = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
companyID, err := strconv.ParseUint(vars["reportId"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while decoding request body"))
return
}
data := model.GetReportPositionsForReport(uint(companyID))
resp := utils.Message(true, "success")
resp["data"] = data
utils.Respond(w, resp)
}
var CreateOrUpdateReportPosition = func(w http.ResponseWriter, r *http.Request) {
reportPosition := &model.ReportPosition{}

View File

@ -9,6 +9,11 @@ import (
"net/http"
)
type CreateReport struct {
Report model.Report
ReportPositions []model.ReportPosition
}
var GetReportsForCompany = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
@ -24,23 +29,73 @@ var GetReportsForCompany = func(w http.ResponseWriter, r *http.Request) {
utils.Respond(w, resp)
}
var CreateOrUpdateReport = func(w http.ResponseWriter, r *http.Request) {
report := &model.Report{}
err := json.NewDecoder(r.Body).Decode(report)
var GetReport = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
reportID, err := strconv.ParseUint(vars["reportId"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while decoding request body"))
return
}
data := report.CreateOrUpdate()
if data.ID == 0 {
utils.Respond(w, utils.Message(false, "Database error"))
report := model.GetReport(uint(reportID))
positions := model.GetReportPositionsForReport(uint(reportID))
resp := utils.Message(true, "success")
resp["data"] = positions
resp["report"] = report
utils.Respond(w, resp)
}
var GetReportWithPositions = func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
reportID, err := strconv.ParseUint(vars["reportId"], 10, 0)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while decoding request body"))
return
}
data := model.GetReportPositionsForReport(uint(reportID))
resp := utils.Message(true, "success")
resp["data"] = data
utils.Respond(w, resp)
}
var GetReports = func(w http.ResponseWriter, r *http.Request) {
data := model.GetReports()
resp := utils.Message(true, "success")
resp["data"] = data
utils.Respond(w, resp)
}
var CreateOrUpdateReport = func(w http.ResponseWriter, r *http.Request) {
createReport := &CreateReport{}
err := json.NewDecoder(r.Body).Decode(createReport)
if err != nil {
utils.Respond(w, utils.Message(false, "Error while decoding request body"))
return
}
data := createReport.Report.CreateOrUpdate()
if data.ID == 0 {
utils.Respond(w, utils.Message(false, "Database error with creating report"))
return
}
for _, element := range createReport.ReportPositions {
model.MarkAsUsed(element.ProductExpiryPositionID)
element.ReportID = data.ID
position := element.CreateOrUpdate()
if position.ID == 0 {
utils.Respond(w, utils.Message(false, "Database error with creating report position"))
return
}
}
resp := utils.Message(true, "success")
utils.Respond(w, resp)
}

View File

@ -12,21 +12,29 @@ import (
func main() {
fmt.Printf("hello, world\n")
router := mux.NewRouter()
router.Use(middleware.JwtAuthentication)
router.Methods("OPTIONS").HandlerFunc(
func(w http.ResponseWriter, r *http.Request){
})
router.HandleFunc("/api/login", controllers.Authenticate).Methods("POST")
router.HandleFunc("/api/register", controllers.CreateAccount).Methods("POST")
router.HandleFunc("/api/products/{code}", controllers.GetProduct).Methods("GET")
router.HandleFunc("/api/products", controllers.CreateOrUpdateProduct).Methods("POST")
router.HandleFunc("/api/products", controllers.GetProducts).Methods("GET")
router.HandleFunc("/api/products/{id}", controllers.DeleteProduct).Methods("DELETE")
router.HandleFunc("/api/companies", controllers.GetCompanies).Methods("GET")
router.HandleFunc("/api/companies", controllers.CreateOrUpdateCompany).Methods("POST")
router.HandleFunc("/api/companies/{id}", controllers.DeleteCompany).Methods("DELETE")
router.HandleFunc("/api/reports", controllers.CreateOrUpdateReport).Methods("POST")
router.HandleFunc("/api/reports/{companyId}", controllers.GetReportsForCompany).Methods("GET")
router.HandleFunc("/api/reportPositions/{reportId}", controllers.GetReportWithPositions).Methods("GET")
router.HandleFunc("/api/expiryPositions", controllers.GetPositions).Methods("GET")
router.HandleFunc("/api/expiryPositions", controllers.CreateOrUpdatePosition).Methods("POST")
router.HandleFunc("/api/reportPositions", controllers.CreateOrUpdatePosition).Methods("POST")
router.HandleFunc("/api/reportPositions", controllers.GetReportsPositionsForReport).Methods("GET")
router.HandleFunc("/api/expiryPositions/{id}", controllers.DeletePosition).Methods("DELETE")
router.HandleFunc("/api/expiryPositions/{companyId}", controllers.GetPositionsForCompany).Methods("GET")
router.Handle("/", http.FileServer(http.Dir("../client/build")))
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("../client/build/static"))))
router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("../client/build"))))

View File

@ -13,6 +13,19 @@ var JwtAuthentication = func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
headers := w.Header()
headers.Add("Access-Control-Allow-Origin", "*")
headers.Add("Vary", "Origin")
headers.Add("Vary", "Access-Control-Request-Method")
headers.Add("Vary", "Access-Control-Request-Headers")
headers.Add("Access-Control-Allow-Headers", "authorization,content-type")
headers.Add("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
if r.Method == "OPTIONS" {
next.ServeHTTP(w, r)
return
}
notAuth := []string{"/api/register", "/api/login", "/static", "/", ""} //List of endpoints that doesn't require auth
requestPath := r.URL.Path //current request path

View File

@ -80,7 +80,7 @@ func (account *Account) Create() (map[string] interface{}) {
func Login(email, password string) (map[string]interface{}) {
account := &Account{}
err := GetDB().Where("email = ? and active = ?", email, true).First(account).Error
err := GetDB().Where("email = ?", email).First(account).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return utils.Message(false, "Email address not found")
@ -92,6 +92,10 @@ func Login(email, password string) (map[string]interface{}) {
if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { //Password does not match!
return utils.Message(false, "Invalid login credentials. Please try again")
}
if !account.Active {
return utils.Message(false, "Account not activated. Wait for activation")
}
//Worked! Logged In
account.Password = ""

View File

@ -1,11 +1,14 @@
package model
import (
"github.com/jinzhu/gorm"
"sort"
"time"
)
type Company struct {
gorm.Model
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Name string
}
@ -14,9 +17,20 @@ func GetCompanies() ([]*Company) {
comapnies := make([]*Company, 0)
GetDB().Find(&comapnies)
sort.Slice(comapnies, func(i, j int) bool {
return comapnies[i].Name < comapnies[j].Name
})
return comapnies
}
func DeleteCompany(id uint) (err error) {
company := &Company{}
company.ID = id
return GetDB().Delete(&company).Error
}
func (company *Company) CreateOrUpdate() (*Company) {
GetDB().Save(company)

View File

@ -1,11 +1,14 @@
package model
import (
"github.com/jinzhu/gorm"
"sort"
"time"
)
type Product struct {
gorm.Model
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Name string
Company Company `json:",omitempty"`
CompanyID uint
@ -17,9 +20,20 @@ func GetProducts() ([]*Product) {
products := make([]*Product, 0)
GetDB().Find(&products)
sort.Slice(products, func(i, j int) bool {
return products[i].Name < products[j].Name
})
return products
}
func DeleteProduct(id uint) (err error) {
product := &Product{}
product.ID = id
return GetDB().Delete(&product).Error
}
func GetProduct(barcode string) (*Product) {
product := &Product{}

View File

@ -1,16 +1,19 @@
package model
import (
"sort"
"time"
"github.com/jinzhu/gorm"
)
type ProductExpiryPosition struct {
gorm.Model
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
ExpiryDate time.Time
BatchNumber string
Product Product
ProductID uint
Used bool `gorm:"default:false"`
}
func GetPositions() ([]*ProductExpiryPosition) {
@ -18,12 +21,42 @@ func GetPositions() ([]*ProductExpiryPosition) {
positions := make([]*ProductExpiryPosition, 0)
GetDB().Find(&positions)
sort.Slice(positions, func(i, j int) bool {
return positions[i].ExpiryDate.Before(positions[j].ExpiryDate)
})
return positions
}
func DeletePositions(positions []*ProductExpiryPosition) () {
func GetPositionsForCompany(companyID uint) ([]*ProductExpiryPosition) {
GetDB().Delete(positions)
positions := make([]*ProductExpiryPosition, 0)
GetDB().
Joins("JOIN products on products.id = product_expiry_positions.product_id").
Joins("JOIN companies on companies.id = products.company_id").
Where("companies.id = ? and product_expiry_positions.used = ?", companyID, false).
Group("product_expiry_positions.id").
Find(&positions)
sort.Slice(positions, func(i, j int) bool {
return positions[i].ExpiryDate.Before(positions[j].ExpiryDate)
})
return positions
}
func MarkAsUsed(ID uint) {
productExpiryPosition := &ProductExpiryPosition{}
productExpiryPosition.ID = ID
GetDB().Model(&productExpiryPosition).Update("used", true)
}
func DeletePosition(id uint) (err error) {
productExpiryPosition := &ProductExpiryPosition{}
productExpiryPosition.ID = id
return GetDB().Delete(&productExpiryPosition).Error
}
func (position *ProductExpiryPosition) CreateOrUpdate() (*ProductExpiryPosition) {

View File

@ -1,11 +1,14 @@
package model
import (
"github.com/jinzhu/gorm"
"sort"
"time"
)
type Report struct {
gorm.Model
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Company Company
CompanyID uint
}
@ -17,10 +20,32 @@ func (report *Report) CreateOrUpdate() (*Report) {
return report;
}
func GetReports() ([]*Report) {
reports := make([]*Report, 0)
GetDB().Find(&reports)
sort.Slice(reports, func(i, j int) bool {
return reports[j].CreatedAt.Before(reports[i].CreatedAt)
})
return reports
}
func GetReport(id uint) (*Report) {
report := &Report{}
if GetDB().Where("id = ?", id).First(report).RecordNotFound() {
return nil
}
return report
}
func GetReportsForCompany(companyID uint) ([]*Report) {
reports := make([]*Report, 0)
if GetDB().Where("companyID = ?", companyID).Find(&reports).RecordNotFound() {
if GetDB().Where("company_id = ?", companyID).Find(&reports).RecordNotFound() {
return nil
}

View File

@ -1,16 +1,17 @@
package model
import (
"github.com/jinzhu/gorm"
"time"
)
type ReportPosition struct {
gorm.Model
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
PositionNumber int
Quantity int
ProductExpiryPosition ProductExpiryPosition
ProductExpiryPositionID uint
Report Report
ReportID uint
}
@ -24,8 +25,16 @@ func (reportPosition *ReportPosition) CreateOrUpdate() (*ReportPosition) {
func GetReportPositionsForReport(reportID uint) ([]*ReportPosition) {
reportsPositions := make([]*ReportPosition, 0)
if GetDB().Where("reportID = ?", reportID).Find(&reportsPositions).RecordNotFound() {
return nil
if GetDB().
Joins("JOIN reports on reports.id = report_positions.report_id").
Joins("JOIN product_expiry_positions on product_expiry_positions.id = report_positions.product_expiry_position_id").
Joins("JOIN products on products.id = product_expiry_positions.product_id").
Joins("JOIN companies on companies.id = products.company_id").
Where("reports.id = ?", reportID).
Group("product_expiry_positions.id").
Find(&reportsPositions).RecordNotFound() {
return nil
}
return reportsPositions