Commit 11c0941c authored by Tamás Danis's avatar Tamás Danis

remove transaction

parent 4c152553
......@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Product;
use App\Transaction;
use Carbon\Carbon;
use Exception;
use function foo\func;
use Illuminate\Http\Request;
......@@ -50,11 +51,25 @@ class TransactionController extends Controller {
] );
}
public function delete( Request $request ) {
$transaction = Transaction::find( $request['id'] );
$transaction->delete();
return response()->json( [
"success" => true,
"data" => $transaction
] );
}
public function get( Request $request ) {
$token = $request->input( 'token' );
$transactions = Transaction::whereHas( 'user', function ( $query ) use ( $token ) {
$query->where( 'auth_token', '=', $token );
} )->with( 'products' )->orderByDesc( 'id' )->get()->toArray();
} )->whereMonth( 'created_at', Carbon::now()->month )
->whereYear( 'created_at', Carbon::now()->year )
->with( 'products' )
->orderByDesc( 'created_at' )
->get()->toArray();
return response()->json( [
"success" => true,
......
......@@ -2,12 +2,11 @@
namespace App\Http\Controllers;
use Hash;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\Request;
use App\User;
use JWTAuth;
use JWTAuthException;
use function MongoDB\BSON\toJSON;
class UserController extends Controller {
private function getToken( $email, $password ) {
......
......@@ -18,4 +18,10 @@ class Transaction extends Model {
public function products() {
return $this->hasMany( 'App\Product' );
}
public function delete() {
$this->products()->delete();
return parent::delete();
}
}
<?php
use App\Product;
use Faker\Generator as Faker;
/** @var TYPE_NAME $factory */
$factory->define( Product::class, function ( Faker $faker ) {
return [
'name' => $faker->word,
'price' => $faker->randomFloat( 2, 0.5, 25 )
];
} );
<?php
use App\Transaction;
use Faker\Generator as Faker;
/** @var TYPE_NAME $factory */
$factory->define( Transaction::class, function ( Faker $faker ) {
return [
'user_id' => 1,
'description' => $faker->word,
'created_at' => $faker->dateTimeBetween( '-20 days' )
];
} );
......@@ -2,15 +2,14 @@
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
// $this->call(UsersTableSeeder::class);
}
class DatabaseSeeder extends Seeder {
/**
* Seed the application's database.
*
* @return void
*/
public function run() {
$this->call( UsersTableSeeder::class );
$this->call( TransactionsTableSeeder::class );
}
}
<?php
use App\Product;
use App\Transaction;
use Illuminate\Database\Seeder;
class TransactionsTableSeeder extends Seeder {
/**
* Run the database seeds.
*
* @return void
*/
public function run() {
factory( Transaction::class, 50 )->create()->each( function ( $transaction ) {
for ( $i = 0; $i < rand( 1, 6 ); $i ++ ) {
$transaction->products()->save( factory( Product::class )->make() );
}
} );
}
}
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UsersTableSeeder extends Seeder {
/**
* Run the database seeds.
*
* @return void
*/
public function run() {
DB::table( 'users' )->insert( [
'name' => 'danistomi',
'email' => 'danistomi@gmail.com',
'password' => HASH::make( 'asdasd' ),
'max_limit' => 300,
'first_name' => 'Tamás',
'last_name' => 'Danis'
] );
}
}
......@@ -7,7 +7,8 @@ export const LOGOUT = 'LOGOUT';
export const UPDATE_USER = 'UPDATE_USER';
export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS';
export const FETCH_TRANSACTIONS_SUCCESS = 'FETCH_TRANSACTIONS_SUCCESS';
export const FETCH_TRANSACTIONS_ERROR = 'FETCH_TRANSACTIONS_ERROR';
export const DELETE_TRANSACTION = 'DELETE_TRANSACTION';
export const DELETE_TRANSACTION_ERROR = 'DELETE_TRANSACTION_ERROR';
export const SAVE_TRANSACTION_SUCCESS = 'SAVE_TRANSACTION_SUCCESS';
export const SAVE_TRANSACTION_ERROR = 'SAVE_TRANSACTION_ERROR';
......@@ -14,6 +14,18 @@ export function fetchTransactions() {
});
}
export function deleteTransaction(id) {
return dispatch =>
API.deleteTransaction({ id })
.then(data => {
if (data.success)
dispatch({ type: types.DELETE_TRANSACTION, transaction: data.data });
})
.catch(error => {
dispatch({ type: types.DELETE_TRANSACTION_ERROR, data: error });
});
}
export function saveTransaction(data) {
return dispatch =>
API.saveTransaction(data)
......
......@@ -4,6 +4,7 @@ const loginUrl = '/api/user/login';
const registerUrl = '/api/user/register';
const updateUserUrl = 'api/user/update?token=';
const saveTransactionUrl = `/api/transaction/save?token=`;
const deleteTransactionUrl = `/api/transaction/delete?token=`;
const fetchTransactionUrl = `/api/transactions?token=`;
const getToken = () => JSON.parse(localStorage.getItem('auth_user')).auth_token;
......@@ -72,6 +73,22 @@ class API {
});
}
static deleteTransaction(data) {
const url = `${deleteTransactionUrl}${getToken()}`;
const payload = JSON.stringify({
...data
});
return new Promise(resolve => {
axios.post(url, payload)
.then(response => {
resolve(response.data);
})
.catch(error => {
resolve(error);
});
});
}
static fetchTransactions() {
const url = `${fetchTransactionUrl}${getToken()}`;
return new Promise(resolve => {
......
......@@ -7,8 +7,8 @@ import { LOGIN, REGISTER } from '../../data/routes';
const Auth = () => {
return (
<main className="login-register">
<div className="row margin-auto">
<div className="col-md-12" style={{ minWidth: '500px' }}>
<div className="row margin-auto" style={{ width: '100%', maxWidth: '420px' }}>
<div className="col-md-12" style={{ width: '100%' }}>
<Switch>
<Route path={LOGIN} component={Login}/>
<Route path={REGISTER} component={Register}/>
......
......@@ -3,8 +3,12 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Redirect, Route, Switch } from 'react-router-dom';
import Dashboard from './Main/Dashboard';
import { DASHBOARD, GROUPS, SETTINGS } from '../../data/routes';
import Groups from './Main/Groups';
import {
DASHBOARD,
// GROUPS,
SETTINGS
} from '../../data/routes';
// import Groups from './Main/Groups';
import Settings from './Main/Settings';
const Main = ({ loggedIn }) => {
......@@ -16,7 +20,7 @@ const Main = ({ loggedIn }) => {
<div className="col-md-12">
<Switch>
<Route exact path={DASHBOARD} component={Dashboard}/>
<Route path={GROUPS} component={Groups}/>
{/* <Route path={GROUPS} component={Groups}/> */}
<Route path={SETTINGS} component={Settings}/>
</Switch>
</div>
......
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
ResponsiveContainer,
AreaChart,
CartesianGrid,
XAxis,
YAxis,
Legend,
Tooltip,
ReferenceLine,
Area
} from 'recharts';
import moment from 'moment';
const SpendingChart = ({ transactions, user }) => {
const countPrice = transaction => {
let res = 0;
transaction.products.forEach(product => {
res += product.price;
});
return Math.round(res * 100) / 100;
};
let prevPrice = 0;
const transactionsMod = transactions.slice().reverse().map(transaction => {
prevPrice += countPrice(transaction);
return {
...transaction,
date: moment(transaction.created_at).format('MM-DD'),
price: prevPrice
};
});
const color = prevPrice < user.max_limit ? '#8884d8' : '#f44242';
return (
<ResponsiveContainer height={250} width="100%">
<AreaChart data={transactionsMod}>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.8}/>
<stop offset="95%" stopColor={color} stopOpacity={0.1}/>
</linearGradient>
</defs>
<CartesianGrid/>
<XAxis dataKey="date"/>
<Tooltip formatter={(value, name, properties) =>
`${Math.round(value * 100) / 100}€ (${properties.payload.description})`
}/>
<YAxis/>
<Legend/>
{prevPrice > user.max_limit &&
<ReferenceLine
y={user.max_limit}
label="Max"
stroke="red"
strokeDasharray="3 3"
/>}
<Area
type="linear"
dataKey="price"
stroke={color}
fillOpacity={1}
fill="url(#colorUv)"
/>
</AreaChart>
</ResponsiveContainer>
);
};
SpendingChart.propTypes = {
transactions: PropTypes.array.isRequired,
user: PropTypes.object.isRequired
};
const mapStateToProps = ({ transactions, auth }) => ({
transactions,
user: auth.user
});
export default connect(mapStateToProps)(SpendingChart);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Field, FieldArray, formValueSelector, reduxForm } from 'redux-form';
import { Field, FieldArray, formValueSelector, reduxForm, reset } from 'redux-form';
import moment from 'moment';
import InputField from '../../../common/form/InputField';
import { required } from '../../../../validation/validation';
......@@ -59,17 +59,20 @@ const selector = formValueSelector('transactionForm');
const mapStateToProps = state => ({
initialValues: {
date: moment().format('YYYY-MM-DD'),
products:[{}]
products: [{}]
},
data: {
date: selector(state, 'date'),
description: selector(state, 'description'),
price: selector(state, 'price')
date: selector(state, 'date')
// description: selector(state, 'description'),
// price: selector(state, 'price')
}
});
const mapDispatchToProps = dispatch => ({
onSubmit: values => dispatch(transactionActions.saveTransaction(values))
onSubmit: values => {
dispatch(transactionActions.saveTransaction(values));
dispatch(reset('transactionForm'));
}
}
);
......
......@@ -35,7 +35,7 @@ const RenderProducts = ({ fields }) => {
/>
</div>
<div className="col-md-2 d-flex flex-column mb-3">
<ButtonField className="mt-auto" value="Remove" onClick={() => handleRemove(index)}/>
<ButtonField className="mt-auto btn-danger" value="Remove" onClick={() => handleRemove(index)}/>
</div>
</div>
</div>
......@@ -43,7 +43,10 @@ const RenderProducts = ({ fields }) => {
)}
<div className="row">
<div className="col-md-12">
<ButtonField className="float-right" value="Add product" onClick={handleAdd}/>
<ButtonField
className="float-right"
value="Add product"
onClick={handleAdd}/>
</div>
</div>
</>
......
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ProductsArea from './ProductsArea';
import * as transactionActions from '../../../../../actions/transactionActions';
const TransactionListItem = ({ transaction }) => {
const TransactionListItem = ({ transaction, deleteTransaction }) => {
const handleClick = () => deleteTransaction(transaction.id);
const countPrice = () => {
let res = 0;
transaction.products.forEach(product => {
res += product.price;
});
return res;
return Math.round(res * 100) / 100;
};
return (
<li className="list-group-item d-flex justify-content-between align-items-center">
{transaction.description}
<ProductsArea products={transaction.products}/>
<span className="badge badge-primary">{countPrice()}&euro;</span>
<div>
<span className="badge badge-primary mr-3">{countPrice()}&euro;</span>
<button
type="button"
className="btn btn-danger"
onClick={handleClick}
>
<i className="fa fa-close"/>
</button>
</div>
</li>
);
};
TransactionListItem.propTypes = {
transaction: PropTypes.object.isRequired
transaction: PropTypes.object.isRequired,
deleteTransaction: PropTypes.func.isRequired
};
export default TransactionListItem;
const mapDispatchToProps = dispatch => ({
deleteTransaction: id => dispatch(transactionActions.deleteTransaction(id))
});
export default connect(null, mapDispatchToProps)(TransactionListItem);
import React from 'react';
import SpendingChart from './DashBoard/SpendingChart';
import TransactionForm from './DashBoard/TransactionForm';
import TransactionList from './DashBoard/TransactionList';
const Dashboard = () => (
<>
<h1 className="pt-3">Dashboard</h1>
<SpendingChart/>
<TransactionForm/>
<h2>History</h2>
<TransactionList/>
......
import { GROUPS, LOGIN, DASHBOARD, REGISTER, SETTINGS } from './routes';
import {
// GROUPS,
LOGIN,
DASHBOARD,
REGISTER,
SETTINGS
} from './routes';
export const authNavItems = [
{
title: 'Login',
route: LOGIN
},
{
title: 'Register',
route: REGISTER
}
{
title: 'Login',
route: LOGIN
},
{
title: 'Register',
route: REGISTER
}
];
export const navItems = [
{
title: 'Main',
route: DASHBOARD
},
{
title: 'Groups',
route: GROUPS
},
{
title: 'Settings',
route: SETTINGS
}
{
title: 'Main',
route: DASHBOARD
},
// {
// title: 'Groups',
// route: GROUPS
// },
{
title: 'Settings',
route: SETTINGS
}
];
......@@ -2,6 +2,7 @@ import * as types from '../actions/actionTypes';
import initialState from './initialState';
const transaction = (state = initialState.transactions, action) => {
// console.log(action.type);
switch (action.type) {
case types.FETCH_TRANSACTIONS:
return action.transactions;
......@@ -12,8 +13,12 @@ const transaction = (state = initialState.transactions, action) => {
case types.SAVE_TRANSACTION_SUCCESS:
return [
action.data,
...state,
...state
];
case types.DELETE_TRANSACTION:
return state.filter(
transaction => transaction.id !== action.transaction.id
);
default:
return state;
}
......
......@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spending Report</title>
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
......@@ -15,6 +16,7 @@
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
......
......@@ -27,9 +27,10 @@ Route::group( [ 'middleware' => [ 'jwt.auth', 'api-header' ] ], function () {
return response()->json( $response, 201 );
} );
Route::post('user/update', 'UserController@update');
Route::post( 'user/update', 'UserController@update' );
Route::post( 'transaction/save', 'TransactionController@save' );
Route::post( 'transaction/delete', 'TransactionController@delete' );
Route::get( 'transactions', 'TransactionController@get' );
} );
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment