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",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"jspdf": "^1.5.3",
|
||||||
|
"jspdf-autotable": "^3.0.2",
|
||||||
"react": "^16.7.0",
|
"react": "^16.7.0",
|
||||||
|
"react-date-picker": "^7.1.1",
|
||||||
|
"react-datepicker": "^2.0.0",
|
||||||
"react-dom": "^16.7.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": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"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.
|
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`.
|
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>
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="bg-light">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<!--
|
||||||
@ -37,5 +40,8 @@
|
|||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,32 +1,56 @@
|
|||||||
.App {
|
.form-signin {
|
||||||
text-align: center;
|
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 {
|
.my-modal {
|
||||||
animation: App-logo-spin infinite 20s linear;
|
position: absolute;
|
||||||
height: 40vmin;
|
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 {
|
.backdrop {
|
||||||
background-color: #282c34;
|
width: 100%;
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
display: flex;
|
position: fixed;
|
||||||
flex-direction: column;
|
z-index: 100;
|
||||||
align-items: center;
|
left: 0;
|
||||||
justify-content: center;
|
top: 0;
|
||||||
font-size: calc(10px + 2vmin);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-link {
|
@media (min-width: 600px) {
|
||||||
color: #61dafb;
|
.my-modal {
|
||||||
}
|
width: 500px;
|
||||||
|
left: calc(50% - 250px);
|
||||||
@keyframes App-logo-spin {
|
}
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,63 @@
|
|||||||
import React, { Component } from 'react';
|
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 './App.css';
|
||||||
|
|
||||||
import Login from './components/Login/Login'
|
|
||||||
|
|
||||||
class App extends Component {
|
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() {
|
render() {
|
||||||
|
const loginRoute = !this.state.user && <Route exact path="/login" render={(props) => <Login {...props} onLoggedUserChanged={(user) => this.loggedUserChanged(user)} />} />;
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<BrowserRouter>
|
||||||
<header className="App-header">
|
<>
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<Header user={this.state.user} onLoggedUserChanged={(user) => this.loggedUserChanged(user)} />
|
||||||
<p>
|
<div className="App container">
|
||||||
Edit <code>src/App.js</code> and save to reload.
|
<Switch>
|
||||||
</p>
|
{ loginRoute }
|
||||||
<a
|
{ this.state.user && <Route exact path="/products" render={(props) => <Products {...props} logout={() => this.loggedUserChanged(null)} />} /> }
|
||||||
className="App-link"
|
{ this.state.user && <Route exact path="/companies" render={(props) => <Companies {...props} logout={() => this.loggedUserChanged(null)} />} /> }
|
||||||
href="https://reactjs.org"
|
{ this.state.user && <Route exact path="/expiryPositions" render={(props) => <ExpiryPositions {...props} logout={() => this.loggedUserChanged(null)} />} /> }
|
||||||
target="_blank"
|
{ this.state.user && <Route exact path="/reports" render={(props) => <Reports {...props} logout={() => this.loggedUserChanged(null)} />} /> }
|
||||||
rel="noopener noreferrer"
|
{ this.state.user && <Route exact path="/reports/:id" render={(props) => <ShowReport {...props} logout={() => this.loggedUserChanged(null)} />} /> }
|
||||||
>
|
<Route path="/" component={ Home } />
|
||||||
Learn React
|
</Switch>
|
||||||
</a>
|
</div>
|
||||||
</header>
|
</>
|
||||||
<Login></Login>
|
</BrowserRouter>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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() {
|
export function authHeader() {
|
||||||
// return authorization header with basic auth credentials
|
// return authorization header with basic auth credentials
|
||||||
let user = JSON.parse(localStorage.getItem('user'));
|
let user = JSON.parse(localStorage.getItem('user'));
|
||||||
|
|
||||||
if (user && user.token) {
|
if (user && user.Token) {
|
||||||
return { 'Authorization': 'bearer ' + user.token };
|
return { 'Authorization': 'bearer ' + user.Token, 'Content-Type': 'application/json' };
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return { 'Content-Type': 'application/json' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleResponse(response, onError = null) {
|
export function handleResponse(response) {
|
||||||
return response.text().then(text => {
|
return response.text().then(text => {
|
||||||
const data = text && JSON.parse(text);
|
const data = text && JSON.parse(text);
|
||||||
if (!response.ok || !data.status) {
|
if (!response.ok || !data.status) {
|
||||||
if (onError) {
|
const error = {
|
||||||
onError(response);
|
message: (data && data.message) || response.statusText,
|
||||||
|
logout: response.status === 401
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = (data && data.message) || response.statusText;
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
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 = {
|
export const userService = {
|
||||||
login,
|
login,
|
||||||
register,
|
register
|
||||||
logout
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function login(username, password) {
|
async function login(username, password) {
|
||||||
@ -13,13 +12,14 @@ async function login(username, password) {
|
|||||||
body: JSON.stringify({ 'email': username, password })
|
body: JSON.stringify({ 'email': username, password })
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(baseUrl + `/login`, requestOptions);
|
try {
|
||||||
const data = await handleResponse(response, onLoginError);
|
const response = await fetch(baseUrl + `/login`, requestOptions);
|
||||||
// login successful if there's a user in the response
|
const data = await handleResponse(response);
|
||||||
if (data.account) {
|
return formatResponse(true, data.account);
|
||||||
localStorage.setItem('user', JSON.stringify(data.account));
|
}
|
||||||
|
catch(error) {
|
||||||
|
return formatResponse(false, error);
|
||||||
}
|
}
|
||||||
return data.account;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function register(username, password) {
|
async function register(username, password) {
|
||||||
@ -29,23 +29,12 @@ async function register(username, password) {
|
|||||||
body: JSON.stringify({ 'email': username, password })
|
body: JSON.stringify({ 'email': username, password })
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(baseUrl + `/register`, requestOptions);
|
try {
|
||||||
const data = await handleResponse(response, onLoginError);
|
const response = await fetch(baseUrl + `/register`, requestOptions);
|
||||||
// login successful if there's a user in the response
|
const data = await handleResponse(response);
|
||||||
if (data.account) {
|
return formatResponse(true, data);
|
||||||
localStorage.setItem('user', JSON.stringify(data.account));
|
|
||||||
}
|
}
|
||||||
return data.account;
|
catch(error) {
|
||||||
}
|
return formatResponse(false, error);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"ExpiryDatesManager/server/model"
|
"ExpiryDatesManager/server/model"
|
||||||
"ExpiryDatesManager/server/utils"
|
"ExpiryDatesManager/server/utils"
|
||||||
@ -15,6 +17,23 @@ var GetCompanies = func(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Respond(w, resp)
|
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) {
|
var CreateOrUpdateCompany = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
company := &model.Company{}
|
company := &model.Company{}
|
||||||
|
@ -1,12 +1,28 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"ExpiryDatesManager/server/model"
|
"ExpiryDatesManager/server/model"
|
||||||
"ExpiryDatesManager/server/utils"
|
"ExpiryDatesManager/server/utils"
|
||||||
"net/http"
|
"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) {
|
var GetPositions = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
data := model.GetPositions()
|
data := model.GetPositions()
|
||||||
@ -15,6 +31,23 @@ var GetPositions = func(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Respond(w, resp)
|
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) {
|
var CreateOrUpdatePosition = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
position := &model.ProductExpiryPosition{}
|
position := &model.ProductExpiryPosition{}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"ExpiryDatesManager/server/model"
|
"ExpiryDatesManager/server/model"
|
||||||
@ -25,6 +26,23 @@ var GetProduct = func(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Respond(w, resp)
|
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) {
|
var CreateOrUpdateProduct = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
product := &model.Product{}
|
product := &model.Product{}
|
||||||
|
@ -1,29 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"ExpiryDatesManager/server/model"
|
"ExpiryDatesManager/server/model"
|
||||||
"ExpiryDatesManager/server/utils"
|
"ExpiryDatesManager/server/utils"
|
||||||
"net/http"
|
"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) {
|
var CreateOrUpdateReportPosition = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
reportPosition := &model.ReportPosition{}
|
reportPosition := &model.ReportPosition{}
|
||||||
|
@ -9,6 +9,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CreateReport struct {
|
||||||
|
Report model.Report
|
||||||
|
ReportPositions []model.ReportPosition
|
||||||
|
}
|
||||||
|
|
||||||
var GetReportsForCompany = func(w http.ResponseWriter, r *http.Request) {
|
var GetReportsForCompany = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
@ -24,23 +29,73 @@ var GetReportsForCompany = func(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Respond(w, resp)
|
utils.Respond(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
var CreateOrUpdateReport = func(w http.ResponseWriter, r *http.Request) {
|
var GetReport = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
report := &model.Report{}
|
reportID, err := strconv.ParseUint(vars["reportId"], 10, 0)
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(report)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Respond(w, utils.Message(false, "Error while decoding request body"))
|
utils.Respond(w, utils.Message(false, "Error while decoding request body"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := report.CreateOrUpdate()
|
report := model.GetReport(uint(reportID))
|
||||||
if data.ID == 0 {
|
positions := model.GetReportPositionsForReport(uint(reportID))
|
||||||
utils.Respond(w, utils.Message(false, "Database error"))
|
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := model.GetReportPositionsForReport(uint(reportID))
|
||||||
resp := utils.Message(true, "success")
|
resp := utils.Message(true, "success")
|
||||||
resp["data"] = data
|
resp["data"] = data
|
||||||
utils.Respond(w, resp)
|
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() {
|
func main() {
|
||||||
fmt.Printf("hello, world\n")
|
fmt.Printf("hello, world\n")
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
router.Use(middleware.JwtAuthentication)
|
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/login", controllers.Authenticate).Methods("POST")
|
||||||
router.HandleFunc("/api/register", controllers.CreateAccount).Methods("POST")
|
router.HandleFunc("/api/register", controllers.CreateAccount).Methods("POST")
|
||||||
router.HandleFunc("/api/products/{code}", controllers.GetProduct).Methods("GET")
|
router.HandleFunc("/api/products/{code}", controllers.GetProduct).Methods("GET")
|
||||||
router.HandleFunc("/api/products", controllers.CreateOrUpdateProduct).Methods("POST")
|
router.HandleFunc("/api/products", controllers.CreateOrUpdateProduct).Methods("POST")
|
||||||
router.HandleFunc("/api/products", controllers.GetProducts).Methods("GET")
|
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.GetCompanies).Methods("GET")
|
||||||
router.HandleFunc("/api/companies", controllers.CreateOrUpdateCompany).Methods("POST")
|
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", controllers.CreateOrUpdateReport).Methods("POST")
|
||||||
router.HandleFunc("/api/reports/{companyId}", controllers.GetReportsForCompany).Methods("GET")
|
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.GetPositions).Methods("GET")
|
||||||
router.HandleFunc("/api/expiryPositions", controllers.CreateOrUpdatePosition).Methods("POST")
|
router.HandleFunc("/api/expiryPositions", controllers.CreateOrUpdatePosition).Methods("POST")
|
||||||
router.HandleFunc("/api/reportPositions", controllers.CreateOrUpdatePosition).Methods("POST")
|
router.HandleFunc("/api/expiryPositions/{id}", controllers.DeletePosition).Methods("DELETE")
|
||||||
router.HandleFunc("/api/reportPositions", controllers.GetReportsPositionsForReport).Methods("GET")
|
router.HandleFunc("/api/expiryPositions/{companyId}", controllers.GetPositionsForCompany).Methods("GET")
|
||||||
router.Handle("/", http.FileServer(http.Dir("../client/build")))
|
router.Handle("/", http.FileServer(http.Dir("../client/build")))
|
||||||
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("../client/build/static"))))
|
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"))))
|
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) {
|
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
|
notAuth := []string{"/api/register", "/api/login", "/static", "/", ""} //List of endpoints that doesn't require auth
|
||||||
requestPath := r.URL.Path //current request path
|
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{}) {
|
func Login(email, password string) (map[string]interface{}) {
|
||||||
|
|
||||||
account := &Account{}
|
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 != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
return utils.Message(false, "Email address not found")
|
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!
|
if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { //Password does not match!
|
||||||
return utils.Message(false, "Invalid login credentials. Please try again")
|
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
|
//Worked! Logged In
|
||||||
account.Password = ""
|
account.Password = ""
|
||||||
|
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jinzhu/gorm"
|
"sort"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Company struct {
|
type Company struct {
|
||||||
gorm.Model
|
ID uint `gorm:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,9 +17,20 @@ func GetCompanies() ([]*Company) {
|
|||||||
comapnies := make([]*Company, 0)
|
comapnies := make([]*Company, 0)
|
||||||
GetDB().Find(&comapnies)
|
GetDB().Find(&comapnies)
|
||||||
|
|
||||||
|
sort.Slice(comapnies, func(i, j int) bool {
|
||||||
|
return comapnies[i].Name < comapnies[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
return comapnies
|
return comapnies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteCompany(id uint) (err error) {
|
||||||
|
|
||||||
|
company := &Company{}
|
||||||
|
company.ID = id
|
||||||
|
return GetDB().Delete(&company).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (company *Company) CreateOrUpdate() (*Company) {
|
func (company *Company) CreateOrUpdate() (*Company) {
|
||||||
|
|
||||||
GetDB().Save(company)
|
GetDB().Save(company)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jinzhu/gorm"
|
"sort"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Product struct {
|
type Product struct {
|
||||||
gorm.Model
|
ID uint `gorm:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
Name string
|
Name string
|
||||||
Company Company `json:",omitempty"`
|
Company Company `json:",omitempty"`
|
||||||
CompanyID uint
|
CompanyID uint
|
||||||
@ -17,9 +20,20 @@ func GetProducts() ([]*Product) {
|
|||||||
products := make([]*Product, 0)
|
products := make([]*Product, 0)
|
||||||
GetDB().Find(&products)
|
GetDB().Find(&products)
|
||||||
|
|
||||||
|
sort.Slice(products, func(i, j int) bool {
|
||||||
|
return products[i].Name < products[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
return products
|
return products
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteProduct(id uint) (err error) {
|
||||||
|
|
||||||
|
product := &Product{}
|
||||||
|
product.ID = id
|
||||||
|
return GetDB().Delete(&product).Error
|
||||||
|
}
|
||||||
|
|
||||||
func GetProduct(barcode string) (*Product) {
|
func GetProduct(barcode string) (*Product) {
|
||||||
|
|
||||||
product := &Product{}
|
product := &Product{}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
"github.com/jinzhu/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProductExpiryPosition struct {
|
type ProductExpiryPosition struct {
|
||||||
gorm.Model
|
ID uint `gorm:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
ExpiryDate time.Time
|
ExpiryDate time.Time
|
||||||
BatchNumber string
|
BatchNumber string
|
||||||
Product Product
|
Product Product
|
||||||
ProductID uint
|
ProductID uint
|
||||||
|
Used bool `gorm:"default:false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPositions() ([]*ProductExpiryPosition) {
|
func GetPositions() ([]*ProductExpiryPosition) {
|
||||||
@ -18,12 +21,42 @@ func GetPositions() ([]*ProductExpiryPosition) {
|
|||||||
positions := make([]*ProductExpiryPosition, 0)
|
positions := make([]*ProductExpiryPosition, 0)
|
||||||
GetDB().Find(&positions)
|
GetDB().Find(&positions)
|
||||||
|
|
||||||
|
sort.Slice(positions, func(i, j int) bool {
|
||||||
|
return positions[i].ExpiryDate.Before(positions[j].ExpiryDate)
|
||||||
|
})
|
||||||
|
|
||||||
return positions
|
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) {
|
func (position *ProductExpiryPosition) CreateOrUpdate() (*ProductExpiryPosition) {
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jinzhu/gorm"
|
"sort"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Report struct {
|
type Report struct {
|
||||||
gorm.Model
|
ID uint `gorm:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
Company Company
|
Company Company
|
||||||
CompanyID uint
|
CompanyID uint
|
||||||
}
|
}
|
||||||
@ -17,10 +20,32 @@ func (report *Report) CreateOrUpdate() (*Report) {
|
|||||||
return 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) {
|
func GetReportsForCompany(companyID uint) ([]*Report) {
|
||||||
|
|
||||||
reports := make([]*Report, 0)
|
reports := make([]*Report, 0)
|
||||||
if GetDB().Where("companyID = ?", companyID).Find(&reports).RecordNotFound() {
|
if GetDB().Where("company_id = ?", companyID).Find(&reports).RecordNotFound() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jinzhu/gorm"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReportPosition struct {
|
type ReportPosition struct {
|
||||||
gorm.Model
|
ID uint `gorm:"primary_key"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
PositionNumber int
|
PositionNumber int
|
||||||
Quantity int
|
Quantity int
|
||||||
ProductExpiryPosition ProductExpiryPosition
|
ProductExpiryPosition ProductExpiryPosition
|
||||||
ProductExpiryPositionID uint
|
ProductExpiryPositionID uint
|
||||||
Report Report
|
|
||||||
ReportID uint
|
ReportID uint
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,8 +25,16 @@ func (reportPosition *ReportPosition) CreateOrUpdate() (*ReportPosition) {
|
|||||||
func GetReportPositionsForReport(reportID uint) ([]*ReportPosition) {
|
func GetReportPositionsForReport(reportID uint) ([]*ReportPosition) {
|
||||||
|
|
||||||
reportsPositions := make([]*ReportPosition, 0)
|
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
|
return reportsPositions
|
||||||
|
Loading…
Reference in New Issue
Block a user