Dokończenie frontendu i poprawki na serwerze.
This commit is contained in:
parent
85c43b34a4
commit
f766b1e2af
1372
client/package-lock.json
generated
1372
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
BIN
client/public/Helvetica.ttf
Normal file
Binary file not shown.
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
34
client/src/components/Layout/Header/Header.js
Normal file
34
client/src/components/Layout/Header/Header.js
Normal 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;
|
7
client/src/components/Layout/Modal/Backdrop.js
Normal file
7
client/src/components/Layout/Modal/Backdrop.js
Normal 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;
|
49
client/src/components/Layout/Modal/Modal.js
Normal file
49
client/src/components/Layout/Modal/Modal.js
Normal 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'>×</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;
|
@ -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;
|
118
client/src/components/Pages/Companies/Companies.js
Normal file
118
client/src/components/Pages/Companies/Companies.js
Normal 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;
|
@ -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;
|
315
client/src/components/Pages/ExpiryPositions/ExpiryPositions.js
Normal file
315
client/src/components/Pages/ExpiryPositions/ExpiryPositions.js
Normal 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;
|
11
client/src/components/Pages/Home/Home.js
Normal file
11
client/src/components/Pages/Home/Home.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
const Home = (props) => {
|
||||
return (
|
||||
<div>
|
||||
Witaj!
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home;
|
126
client/src/components/Pages/Login/Login.js
Normal file
126
client/src/components/Pages/Login/Login.js
Normal 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;
|
18
client/src/components/Pages/Products/Product.js
Normal file
18
client/src/components/Pages/Products/Product.js
Normal 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;
|
178
client/src/components/Pages/Products/Products.js
Normal file
178
client/src/components/Pages/Products/Products.js
Normal 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;
|
142
client/src/components/Pages/Reports/CreateReport.js
Normal file
142
client/src/components/Pages/Reports/CreateReport.js
Normal 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;
|
17
client/src/components/Pages/Reports/Report.js
Normal file
17
client/src/components/Pages/Reports/Report.js
Normal 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;
|
25
client/src/components/Pages/Reports/ReportPosition.js
Normal file
25
client/src/components/Pages/Reports/ReportPosition.js
Normal 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;
|
150
client/src/components/Pages/Reports/Reports.js
Normal file
150
client/src/components/Pages/Reports/Reports.js
Normal 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;
|
77
client/src/components/Pages/Reports/ShowReport.js
Normal file
77
client/src/components/Pages/Reports/ShowReport.js
Normal 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;
|
@ -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
|
||||
}
|
||||
}
|
7
client/src/helpers/sortHelper.js
Normal file
7
client/src/helpers/sortHelper.js
Normal file
@ -0,0 +1,7 @@
|
||||
export function compare(a,b) {
|
||||
if (a < b)
|
||||
return -1;
|
||||
if (a > b)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
3
client/src/services/Helvetica.json
Normal file
3
client/src/services/Helvetica.json
Normal file
File diff suppressed because one or more lines are too long
57
client/src/services/companyService.js
Normal file
57
client/src/services/companyService.js
Normal 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);
|
||||
}
|
||||
}
|
75
client/src/services/expiryPositionService.js
Normal file
75
client/src/services/expiryPositionService.js
Normal 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);
|
||||
}
|
||||
}
|
55
client/src/services/pdfService.js
Normal file
55
client/src/services/pdfService.js
Normal 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);
|
||||
}
|
57
client/src/services/productService.js
Normal file
57
client/src/services/productService.js
Normal 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);
|
||||
}
|
||||
}
|
58
client/src/services/reportService.js
Normal file
58
client/src/services/reportService.js
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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{}
|
||||
|
@ -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{}
|
||||
|
@ -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{}
|
||||
|
@ -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{}
|
||||
|
@ -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)
|
||||
}
|
@ -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"))))
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 = ""
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user