Compare commits
78 Commits
feature/ma
...
develop
Author | SHA1 | Date | |
---|---|---|---|
8b63a9d5df | |||
8eaa3b9039 | |||
b3f0199036 | |||
6efe5a897c | |||
2ea1b38454 | |||
161eb42942 | |||
296c89ce20 | |||
d2e8a3f7ef | |||
4071893ed5 | |||
1859efb98c | |||
0e001cf680 | |||
b753b59080 | |||
01d77b22c3 | |||
54c44afd47 | |||
d4d3baba31 | |||
d082dae44c | |||
f33d7f1437 | |||
65dcd7f453 | |||
5f8cf9abbf | |||
f3d71fd081 | |||
d3e73ba739 | |||
bbac3e67bc | |||
f36a0bfa92 | |||
45207d6500 | |||
c92b1bed11 | |||
ffbae85e11 | |||
100e7d2d8b | |||
280d5b8bb8 | |||
9b9a64fb52 | |||
f913d52842 | |||
a4c676ee8e | |||
088fab2d51 | |||
3be01f1240 | |||
8d6465de71 | |||
89c1c4f64c | |||
f9d26ed888 | |||
d69ace6292 | |||
cf2453c4c6 | |||
e9cc86d064 | |||
94e103b365 | |||
adb025c308 | |||
0efdbc5378 | |||
abfaab8743 | |||
d0159b3fb6 | |||
efe9b6340d | |||
7ca6a9827a | |||
e641759ff7 | |||
e25c788123 | |||
c617cf2cab | |||
4d9faf7406 | |||
ebcddbbfa5 | |||
f19403f0ea | |||
a915d33a54 | |||
d30a8951f6 | |||
408a1e71b7 | |||
11d73abae4 | |||
062d623649 | |||
53a78fa09e | |||
d813bf4c89 | |||
d6f895c535 | |||
df85d72217 | |||
0599d39690 | |||
247ece861e | |||
156d5099d5 | |||
b83b38e2b9 | |||
9dd489e6c4 | |||
d63c5dbffe | |||
79bea27c14 | |||
4aea098e3d | |||
406ae3aae0 | |||
994604ddbf | |||
c71c015616 | |||
1f4b2aa2f5 | |||
2262e0ae49 | |||
eca1cacfeb | |||
c799c62621 | |||
658a55b810 | |||
b50d841862 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -81,4 +81,5 @@ tags
|
|||||||
|
|
||||||
# End of https://www.gitignore.io/api/vim,node
|
# End of https://www.gitignore.io/api/vim,node
|
||||||
|
|
||||||
/build
|
/dist
|
||||||
|
|
||||||
|
10
config/helpers.js
Normal file
10
config/helpers.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var _root = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
|
function root(args) {
|
||||||
|
args = Array.prototype.slice.call(arguments, 0);
|
||||||
|
return path.join.apply(path, [_root].concat(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.root = root;
|
119
config/webpack.common.js
Normal file
119
config/webpack.common.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const helpers = require('./helpers');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
'polyfills': './src/polyfills.ts',
|
||||||
|
'vendor': './src/vendor.ts',
|
||||||
|
'app': './src/main.ts',
|
||||||
|
'styles': './src/main.scss'
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [{
|
||||||
|
enforce: 'pre',
|
||||||
|
test: /webpack\.config\.js$/,
|
||||||
|
include: helpers.root('src', 'app'),
|
||||||
|
loader: 'eslint-loader',
|
||||||
|
options: {
|
||||||
|
useEslintrc: false,
|
||||||
|
emitWarning: true,
|
||||||
|
emitError: true,
|
||||||
|
failOnWarning: true,
|
||||||
|
failOnError: true,
|
||||||
|
baseConfig: 'webpack',
|
||||||
|
rules: {
|
||||||
|
indent: ['error', 4]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
// Javascript
|
||||||
|
enforce: 'pre',
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
include: helpers.root('src', 'app'),
|
||||||
|
loader: 'eslint-loader',
|
||||||
|
options: {
|
||||||
|
useEslintrc: false,
|
||||||
|
emitWarning: false,
|
||||||
|
emitError: true,
|
||||||
|
failOnWarning: false,
|
||||||
|
failOnError: true,
|
||||||
|
baseConfig: 'angular',
|
||||||
|
rules: {
|
||||||
|
indent: ['error', 4]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'angular',
|
||||||
|
'html',
|
||||||
|
'security',
|
||||||
|
'this',
|
||||||
|
'jquery',
|
||||||
|
'promise'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// }, {
|
||||||
|
// test: /\.jsx?$/,
|
||||||
|
// exclude: /node_modules/,
|
||||||
|
// loader: 'babel-loader'
|
||||||
|
}, {
|
||||||
|
test: /\.ts$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'awesome-typescript-loader',
|
||||||
|
options: { configFileName: helpers.root('src', 'tsconfig.json') }
|
||||||
|
},
|
||||||
|
'angular2-template-loader'
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
test: /\.html$/,
|
||||||
|
include: helpers.root('src'),
|
||||||
|
loader: 'html-loader'
|
||||||
|
}, {
|
||||||
|
test: /\.css$/,
|
||||||
|
//include: helpers.root('src'),
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'resolve-url-loader'
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'resolve-url-loader',
|
||||||
|
'sass-loader?sourceMap'
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
|
||||||
|
loader: 'file-loader?name=assets/[name].[hash].[ext]'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
// Workaround for angular/angular#11580
|
||||||
|
new webpack.ContextReplacementPlugin(
|
||||||
|
// The (\\|\/) piece accounts for path separators in *nix and Windows
|
||||||
|
/angular(\\|\/)core(\\|\/)@angular/,
|
||||||
|
helpers.root('./src'), // location of your src
|
||||||
|
{} // a map of your routes
|
||||||
|
),
|
||||||
|
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'src/index.ejs'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
};
|
34
config/webpack.dev.js
Normal file
34
config/webpack.dev.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
|
||||||
|
const webpackMerge = require('webpack-merge');
|
||||||
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
const commonConfig = require('./webpack.common.js');
|
||||||
|
const helpers = require('./helpers');
|
||||||
|
|
||||||
|
module.exports = webpackMerge(commonConfig, {
|
||||||
|
devtool: 'cheap-module-eval-source-map',
|
||||||
|
|
||||||
|
mode: 'development',
|
||||||
|
|
||||||
|
output: {
|
||||||
|
path: helpers.root('dist'),
|
||||||
|
publicPath: '/',
|
||||||
|
filename: '[name].js',
|
||||||
|
chunkFilename: '[id].chunk.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new ExtractTextPlugin('[name].css')
|
||||||
|
],
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:5000',
|
||||||
|
secure: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
historyApiFallback: true,
|
||||||
|
stats: 'minimal'
|
||||||
|
}
|
||||||
|
});
|
41
config/webpack.prod.js
Normal file
41
config/webpack.prod.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* jshint esversion: 6 */
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const webpackMerge = require('webpack-merge');
|
||||||
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
const commonConfig = require('./webpack.common.js');
|
||||||
|
const helpers = require('./helpers');
|
||||||
|
|
||||||
|
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
|
||||||
|
|
||||||
|
module.exports = webpackMerge(commonConfig, {
|
||||||
|
devtool: 'source-map',
|
||||||
|
|
||||||
|
mode: 'production',
|
||||||
|
|
||||||
|
output: {
|
||||||
|
path: helpers.root('dist'),
|
||||||
|
publicPath: '/',
|
||||||
|
filename: '[name].[hash].js',
|
||||||
|
chunkFilename: '[id].[hash].chunk.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
|
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
|
||||||
|
mangle: {
|
||||||
|
keep_fnames: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new ExtractTextPlugin('[name].[hash].css'),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env': {
|
||||||
|
'ENV': JSON.stringify(ENV)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new webpack.LoaderOptionsPlugin({
|
||||||
|
htmlLoader: {
|
||||||
|
minimize: false // workaround for ng2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
123
package.json
123
package.json
@ -4,68 +4,89 @@
|
|||||||
"repository": "https://git.lahouze.org/xals/accountant",
|
"repository": "https://git.lahouze.org/xals/accountant",
|
||||||
"license": "AGPL-1.0",
|
"license": "AGPL-1.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@angular/cli": "^6.0.7",
|
||||||
|
"@angular/compiler-cli": "^6.0.3",
|
||||||
|
"@ngtools/webpack": "^6.1.0-beta.1",
|
||||||
|
"angular2-template-loader": "^0.6.2",
|
||||||
|
"awesome-typescript-loader": "^5.0.0",
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "^8.0.1",
|
"babel-eslint": "^8.2.2",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.4",
|
||||||
"css-loader": "^0.28.5",
|
"copy-webpack-plugin": "^4.5.1",
|
||||||
"ejs-loader": "^0.3.0",
|
"css-loader": "^0.28.10",
|
||||||
"eslint": "^4.10.0",
|
"ejs-loader": "^0.3.1",
|
||||||
|
"eslint": "^4.18.2",
|
||||||
"eslint-config-angular": "^0.5.0",
|
"eslint-config-angular": "^0.5.0",
|
||||||
"eslint-config-webpack": "^1.2.5",
|
"eslint-config-webpack": "^1.2.5",
|
||||||
"eslint-loader": "^1.9.0",
|
"eslint-loader": "^2.0.0",
|
||||||
"eslint-plugin-angular": "^3.1.0",
|
"eslint-plugin-angular": "^3.3.0",
|
||||||
"eslint-plugin-html": "^3.2.0",
|
"eslint-plugin-html": "^4.0.2",
|
||||||
"eslint-plugin-jquery": "^1.2.1",
|
"eslint-plugin-jquery": "^1.3.0",
|
||||||
"eslint-plugin-promise": "^3.6.0",
|
"eslint-plugin-promise": "^3.7.0",
|
||||||
"eslint-plugin-security": "^1.4.0",
|
"eslint-plugin-security": "^1.4.0",
|
||||||
"eslint-plugin-this": "^0.2.2",
|
"eslint-plugin-this": "^0.2.2",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"file-loader": "^1.1.5",
|
"file-loader": "^1.1.11",
|
||||||
"html-loader": "^0.5.1",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^2.30.1",
|
"html-webpack-plugin": "^3.0.6",
|
||||||
"htmllint-loader": "^2.1.4",
|
"htmllint-loader": "^2.1.4",
|
||||||
"imports-loader": "^0.7.1",
|
"imports-loader": "^0.8.0",
|
||||||
"less": "^3.0.0-alpha.3",
|
"less": "^3.0.1",
|
||||||
"less-loader": "^4.0.5",
|
"less-loader": "^4.1.0",
|
||||||
"loglevel": "^1.5.1",
|
"loglevel": "^1.6.1",
|
||||||
"ngtemplate-loader": "^2.0.1",
|
"ngtemplate-loader": "^2.0.1",
|
||||||
"node-sass": "^4.5.3",
|
"node-sass": "^4.7.2",
|
||||||
"sass-loader": "^6.0.6",
|
"null-loader": "^0.1.1",
|
||||||
"style-loader": "^0.19.0",
|
"raw-loader": "^0.5.1",
|
||||||
"ts-loader": "^3.1.0",
|
"resolve-url-loader": "^2.3.0",
|
||||||
"typescript": "^2.4.2",
|
"sass-loader": "^7.0.2",
|
||||||
"url-loader": "^0.6.2",
|
"script-ext-html-webpack-plugin": "^2.0.1",
|
||||||
"webpack": "^3.8.1",
|
"style-loader": "^0.21.0",
|
||||||
"webpack-dev-server": "^2.9.3"
|
"ts-loader": "^4.0.1",
|
||||||
|
"typescript": "~2.7.2",
|
||||||
|
"typescript-eslint-parser": "^15.0.0",
|
||||||
|
"url-loader": "^1.0.1",
|
||||||
|
"webpack": "^4.8.0",
|
||||||
|
"webpack-cli": "^3.0.1",
|
||||||
|
"webpack-dev-server": "^3.1.0",
|
||||||
|
"webpack-merge": "^4.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^4.4.6",
|
"@angular/animations": "^6.0.3",
|
||||||
"@angular/common": "^4.4.6",
|
"@angular/common": "^6.0.3",
|
||||||
"@angular/compiler": "^4.4.6",
|
"@angular/compiler": "^6.0.3",
|
||||||
"@angular/core": "^4.4.6",
|
"@angular/core": "^6.0.3",
|
||||||
"@angular/forms": "^4.4.6",
|
"@angular/forms": "^6.0.3",
|
||||||
"@angular/http": "^4.4.6",
|
"@angular/http": "^6.0.3",
|
||||||
"@angular/platform-browser": "^4.4.6",
|
"@angular/platform-browser": "^6.0.3",
|
||||||
"@angular/platform-browser-dynamic": "^4.4.6",
|
"@angular/platform-browser-dynamic": "^6.0.3",
|
||||||
"@angular/router": "^4.4.6",
|
"@angular/router": "^6.0.3",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.2",
|
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
||||||
"@nsalaun/ng-logger": "^2.0.2",
|
"@nsalaun/ng-logger": "^5.0.0",
|
||||||
"@types/c3": "^0.4.45",
|
"@swimlane/ngx-charts": "^8.0.2",
|
||||||
"@types/node": "^8.0.47",
|
"@types/d3": "^5.0.0",
|
||||||
"angular2-text-mask": "^8.0.3",
|
"@types/geojson": "^7946.0.3",
|
||||||
"base64util": "^1.0.2",
|
"@types/node": "^10.3.2",
|
||||||
"bootstrap": "4.0.0-beta",
|
"@types/underscore": "^1.8.8",
|
||||||
"c3": "^0.4.17",
|
"angular2-text-mask": "^9.0.0",
|
||||||
|
"base64util": "^2.0.0-f",
|
||||||
|
"bootstrap": "4.1.1",
|
||||||
|
"d3": "^5.0.0",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.3.1",
|
||||||
"moment": "^2.19.1",
|
"jqwidgets-scripts": "^5.7.2",
|
||||||
"ngx-toastr": "^6.5.0",
|
"moment": "^2.21.0",
|
||||||
"reflect-metadata": "^0.1.10",
|
"ng2-nvd3": "^2.0.0",
|
||||||
"rxjs": "^5.5.2",
|
"ngx-toastr": "^8.3.0",
|
||||||
"zone.js": "^0.8.17"
|
"reflect-metadata": "^0.1.12",
|
||||||
|
"rxjs": "^6.2.0",
|
||||||
|
"rxjs-compat": "^6.2.0",
|
||||||
|
"underscore": "^1.8.3",
|
||||||
|
"zone.js": "^0.8.20"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "rm -Rf dist && webpack --config config/webpack.prod.js --progress --profile --bail",
|
||||||
"dev": "webpack-dev-server --debug --devtool eval --config webpack.config.js --progress --colors --hot --content-base build"
|
"test": "karma start",
|
||||||
|
"dev": "webpack-dev-server --config config/webpack.dev.js --inline --progress --colors --hot --info --debug --devtool eval-cheap-module-source-map"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
import { AccountBalances } from './accountBalances';
|
import { AccountBalances } from './accountBalances';
|
||||||
|
|
||||||
export class Account {
|
export class Account {
|
||||||
id: number;
|
public id: number;
|
||||||
name: string;
|
public name: string;
|
||||||
authorized_overdraft: number;
|
public authorized_overdraft: number;
|
||||||
|
|
||||||
balances: AccountBalances;
|
public balances: AccountBalances;
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
this.authorized_overdraft = 0;
|
this.authorized_overdraft = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
export class AccountBalances {
|
export class AccountBalances {
|
||||||
current: number;
|
public current: number;
|
||||||
pointed: number;
|
public pointed: number;
|
||||||
future: number;
|
public future: number;
|
||||||
}
|
}
|
||||||
|
20
src/accounts/accountDeleteModal.component.html
Normal file
20
src/accounts/accountDeleteModal.component.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title" id="modal-title">{{ title() }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body" id="modal-body">
|
||||||
|
<p>
|
||||||
|
Do you really want to delete account #{{ account.id }} with name:<br/>
|
||||||
|
{{ account.name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-danger" (click)="submit()">
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" (click)="cancel()">
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -7,28 +7,7 @@ import { Account } from './account';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'account-delete-modal',
|
selector: 'account-delete-modal',
|
||||||
template: `
|
templateUrl: './accountDeleteModal.component.html'
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title" id="modal-title">{{ title() }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
<p>
|
|
||||||
Do you really want to delete account #{{ account.id }} with name:<br/>
|
|
||||||
{{ account.name }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-danger" (click)="submit()">
|
|
||||||
Yes
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn btn-default" (click)="cancel()">
|
|
||||||
No
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class AccountDeleteModalComponent {
|
export class AccountDeleteModalComponent {
|
||||||
@Input() account: Account
|
@Input() account: Account
|
||||||
|
17
src/accounts/accountEditModal.component.html
Normal file
17
src/accounts/accountEditModal.component.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title" id="modal-title">{{ title() }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body" id="modal-body">
|
||||||
|
<account-form [account]="account" (submit)="submit()" #accountForm="accountForm"></account-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-primary" [disabled]="!accountForm.form.valid" (click)="submit()">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" (click)="cancel()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -8,25 +8,7 @@ import { AccountFormComponent } from './accountForm.component';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'account-edit-modal',
|
selector: 'account-edit-modal',
|
||||||
template: `
|
templateUrl: './accountEditModal.component.html'
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title" id="modal-title">{{ title() }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
<account-form [account]="account" (submit)="submit()" #accountForm="accountForm"></account-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-primary" [disabled]="!accountForm.form.valid" (click)="submit()">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn btn-default" (click)="cancel()">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class AccountEditModalComponent {
|
export class AccountEditModalComponent {
|
||||||
@Input() account: Account;
|
@Input() account: Account;
|
||||||
|
47
src/accounts/accountForm.component.html
Normal file
47
src/accounts/accountForm.component.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<form novalidate
|
||||||
|
(keyup.enter)="submit()" [formGroup]="form">
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-4 control-label" for="name">
|
||||||
|
Account name
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8"
|
||||||
|
[class.has-danger]="name.errors">
|
||||||
|
<input class="form-control"
|
||||||
|
id="name" formControlName="name"
|
||||||
|
placeholder="Account name">
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="name.errors">
|
||||||
|
<p *ngIf="name.errors.required">The account name is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-4 control-label" for="authorized-overdraft">
|
||||||
|
Authorized overdraft
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8"
|
||||||
|
[class.has-danger]="authorizedOverdraft.errors">
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control"
|
||||||
|
id="authorized-overdraft" formControlName="authorizedOverdraft"
|
||||||
|
placeholder="Authorized overdraft">
|
||||||
|
|
||||||
|
<div class="input-group-addon">.00€</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="authorizedOverdraft.errors">
|
||||||
|
<p *ngIf="authorizedOverdraft.errors.required">
|
||||||
|
The authorized overdraft is required.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p *ngIf="authorizedOverdraft.errors.max">
|
||||||
|
The authorized overdraft must be less than or equal to 0.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
@ -7,54 +7,7 @@ import { Account } from './account';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'account-form',
|
selector: 'account-form',
|
||||||
exportAs: 'accountForm',
|
exportAs: 'accountForm',
|
||||||
template: `
|
templateUrl: './accountForm.component.html'
|
||||||
<form novalidate
|
|
||||||
(keyup.enter)="submit()" [formGroup]="form">
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 control-label" for="name">
|
|
||||||
Account name
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-danger]="name.errors">
|
|
||||||
<input class="form-control"
|
|
||||||
id="name" formControlName="name"
|
|
||||||
placeholder="Account name">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="name.errors">
|
|
||||||
<p *ngIf="name.errors.required">The account name is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 control-label" for="authorized-overdraft">
|
|
||||||
Authorized overdraft
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-danger]="authorizedOverdraft.errors">
|
|
||||||
<div class="input-group">
|
|
||||||
<input class="form-control"
|
|
||||||
id="authorized-overdraft" formControlName="authorizedOverdraft"
|
|
||||||
placeholder="Authorized overdraft">
|
|
||||||
|
|
||||||
<div class="input-group-addon">.00€</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="authorizedOverdraft.errors">
|
|
||||||
<p *ngIf="authorizedOverdraft.errors.required">
|
|
||||||
The authorized overdraft is required.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p *ngIf="authorizedOverdraft.errors.max">
|
|
||||||
The authorized overdraft must be less than or equal to 0.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class AccountFormComponent implements OnInit {
|
export class AccountFormComponent implements OnInit {
|
||||||
public form: FormGroup;
|
public form: FormGroup;
|
||||||
|
27
src/accounts/accountList.component.html
Normal file
27
src/accounts/accountList.component.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<div class="row">
|
||||||
|
<table class="table table-sm table-striped table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nom du compte</th>
|
||||||
|
<th>Solde courant</th>
|
||||||
|
<th>Solde pointé</th>
|
||||||
|
<th>Découvert autorisé</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">
|
||||||
|
<button class="btn btn-success" (click)="add()">
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr *ngFor="let account of accounts"
|
||||||
|
[account-row]="account" (needsReload)="load()">
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
@ -13,35 +13,7 @@ import { AccountEditModalComponent } from './accountEditModal.component';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'account-list',
|
selector: 'account-list',
|
||||||
template: `
|
templateUrl: './accountList.component.html',
|
||||||
<div class="row">
|
|
||||||
<table class="table table-sm table-striped table-condensed table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Nom du compte</th>
|
|
||||||
<th>Solde courant</th>
|
|
||||||
<th>Solde pointé</th>
|
|
||||||
<th>Découvert autorisé</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colspan="5">
|
|
||||||
<button class="btn btn-success" (click)="add()">
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr *ngFor="let account of accounts"
|
|
||||||
[account-row]="account" (needsReload)="load()">
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
})
|
})
|
||||||
export class AccountListComponent implements OnInit {
|
export class AccountListComponent implements OnInit {
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
|
41
src/accounts/accountRow.component.html
Normal file
41
src/accounts/accountRow.component.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<td>
|
||||||
|
<a [routerLink]="[account.id, 'operations']">{{ account.name }}</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span (ngClass)="valueClass(accountBalances?.current)">
|
||||||
|
{{ accountBalances?.current | currency:'EUR':'symbol' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span (ngClass)="valueClass(accountBalances?.pointed)">
|
||||||
|
{{ accountBalances?.pointed | currency:'EUR':'symbol' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ account.authorized_overdraft | currency:'EUR':'symbol' }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<!-- Edit account. -->
|
||||||
|
<button type="button" class="btn btn-success"
|
||||||
|
(click)="modify()">
|
||||||
|
<span class="fa fa-pencil-square-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Delete account, with confirm. -->
|
||||||
|
<button type="button" class="btn btn-danger"
|
||||||
|
(click)="confirmDelete()">
|
||||||
|
<span class="fa fa-trash-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Open account scheduler. -->
|
||||||
|
<a class="btn btn-secondary"
|
||||||
|
[hidden]="!account.id"
|
||||||
|
[routerLink]="[account.id, 'scheduler']">
|
||||||
|
<span class="fa fa-clock-o"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
@ -20,54 +20,13 @@ import { AccountEditModalComponent } from './accountEditModal.component';
|
|||||||
"[class.warning]": "warning",
|
"[class.warning]": "warning",
|
||||||
"[class.danger]": "danger"
|
"[class.danger]": "danger"
|
||||||
},
|
},
|
||||||
template: `
|
templateUrl: './accountRow.component.html'
|
||||||
<td>
|
|
||||||
<a [routerLink]="['/account', account.id, 'operations']">{{ account.name }}</a>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<span (ngClass)="valueClass(account, accountBalances?.current)">
|
|
||||||
{{ accountBalances?.current | currency:"EUR":true }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<span (ngClass)="valueClass(account, accountBalances?.pointed)">
|
|
||||||
{{ accountBalances?.pointed | currency:"EUR":true }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{{ account.authorized_overdraft | currency:"EUR":true }}</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<!-- Edit account. -->
|
|
||||||
<button type="button" class="btn btn-success"
|
|
||||||
(click)="modify()">
|
|
||||||
<span class="fa fa-pencil-square-o"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Delete account, with confirm. -->
|
|
||||||
<button type="button" class="btn btn-secondary"
|
|
||||||
(click)="confirmDelete()">
|
|
||||||
<span class="fa fa-trash-o"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Open account scheduler. -->
|
|
||||||
<a class="btn btn-secondary"
|
|
||||||
[hidden]="!account.id"
|
|
||||||
[routerLink]="['/account', account.id, 'scheduler']">
|
|
||||||
<span class="fa fa-clock-o"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class AccountRowComponent implements OnInit {
|
export class AccountRowComponent implements OnInit {
|
||||||
@Input('account-row') account: Account;
|
@Input('account-row') account: Account;
|
||||||
@Output() needsReload: EventEmitter<void> = new EventEmitter<void>();
|
@Output() needsReload: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
private accountBalances: AccountBalances;
|
public accountBalances: AccountBalances;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
export class DailyBalance {
|
export class DailyBalance {
|
||||||
operation_date: string;
|
public operation_date: string;
|
||||||
balance: number;
|
public balance: number;
|
||||||
expenses: number;
|
public expenses: number;
|
||||||
revenues: number;
|
public revenues: number;
|
||||||
}
|
}
|
||||||
|
8
src/app.component.html
Normal file
8
src/app.component.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<!-- Navbar -->
|
||||||
|
<nav class="navbar fixed-top navbar-dark bg-dark">
|
||||||
|
<a class="navbar-brand" routerLink="/accounts"> Accountant</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
@ -4,15 +4,6 @@ import { Component } from '@angular/core';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'accountant',
|
selector: 'accountant',
|
||||||
template: `
|
templateUrl: './app.component.html'
|
||||||
<!-- Navbar -->
|
|
||||||
<nav class="navbar fixed-top navbar-dark bg-dark">
|
|
||||||
<a class="navbar-brand" routerLink="/accounts"> Accountant</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class AppComponent { }
|
export class AppComponent { }
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2:
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
import 'zone.js';
|
|
||||||
import 'reflect-metadata';
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
@ -32,7 +30,8 @@ import { ApiBaseURL, LogLevel } from './app.config';
|
|||||||
}
|
}
|
||||||
], {
|
], {
|
||||||
enableTracing: true,
|
enableTracing: true,
|
||||||
useHash: true
|
useHash: true,
|
||||||
|
onSameUrlNavigation: 'reload'
|
||||||
}),
|
}),
|
||||||
LoginModule,
|
LoginModule,
|
||||||
NgLoggerModule.forRoot(LogLevel),
|
NgLoggerModule.forRoot(LogLevel),
|
||||||
|
@ -40,7 +40,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
let loginService = this.injector.get(LoginService);
|
let loginService = this.injector.get(LoginService);
|
||||||
|
|
||||||
if(request.url == loginService.url) {
|
if(request.url == loginService.url) {
|
||||||
this.logger.log("Login URL, do not handle.");
|
this.logger.log('Login URL, do not handle.');
|
||||||
|
|
||||||
return next.handle(request);
|
return next.handle(request);
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
|
|
||||||
return observable.catch(
|
return observable.catch(
|
||||||
(error, caught): Observable<any> => {
|
(error, caught): Observable<any> => {
|
||||||
this.logger.error("Error", error, caught);
|
this.logger.error('Error', error, caught);
|
||||||
|
|
||||||
if(!(error instanceof HttpErrorResponse) || error.status != 401) {
|
if(!(error instanceof HttpErrorResponse) || error.status != 401) {
|
||||||
return Observable.throw(error);
|
return Observable.throw(error);
|
||||||
@ -74,12 +74,12 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!this.observable) {
|
if(!this.observable) {
|
||||||
this.logger.log("No current login observable.")
|
this.logger.log('No current login observable.');
|
||||||
this.observable = loginService.login();
|
this.observable = loginService.login();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.observable.flatMap((token: Token): Observable<HttpEvent<any>> => {
|
return this.observable.flatMap((token: Token): Observable<HttpEvent<any>> => {
|
||||||
this.logger.log("Logged in, access_token:", token.access_token);
|
this.logger.log('Logged in, access_token:', token.access_token);
|
||||||
this.observable = null;
|
this.observable = null;
|
||||||
return this.intercept(request, next, ++pass);
|
return this.intercept(request, next, ++pass);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
module.exports = function($httpProvider, $storageProvider) {
|
|
||||||
// Define interceptors.
|
|
||||||
$httpProvider.interceptors.push(function($storage) {
|
|
||||||
return {
|
|
||||||
request: function(config) {
|
|
||||||
var access_token = $storage.session.get('access_token');
|
|
||||||
|
|
||||||
if (access_token) {
|
|
||||||
//var tokenType = $storage.get('token_type');
|
|
||||||
var tokenType = 'Bearer';
|
|
||||||
var authorization = tokenType + ' ' + access_token;
|
|
||||||
config.headers.authorization = authorization;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure storage
|
|
||||||
// Set global prefix for stored keys
|
|
||||||
$storageProvider.setPrefix('accountant');
|
|
||||||
|
|
||||||
// Change the default storage engine
|
|
||||||
// Defaults to 'local'
|
|
||||||
$storageProvider.setDefaultStorageEngine('session');
|
|
||||||
|
|
||||||
// Change the enabled storage engines
|
|
||||||
// Defaults to ['memory', 'cookie', 'session', 'local']
|
|
||||||
$storageProvider.setEnabledStorageEngines(['local', 'session']);
|
|
||||||
};
|
|
@ -1,38 +0,0 @@
|
|||||||
<!-- vim: set tw=80 ts=2 sw=2 sts=2: -->
|
|
||||||
<div class="modal top am-fade" tabindex="-1" role="dialog" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title" id="modal-title">Authentification requise</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="form-horizontal" ng-submit="$login()">
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email" class="col-sm-4 control-label">Adresse email</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="text" class="form-control" id="email" ng-model="email"
|
|
||||||
placeholder="Nom d'utilisateur">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password" class="col-sm-4 control-label">Mot de passe</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="password" class="form-control" id="password"
|
|
||||||
ng-model="password" placeholder="Mot de passe">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<input class="btn btn-primary" type="submit" value="OK"/>
|
|
||||||
|
|
||||||
<button class="btn btn-default" type="button" ng-click="$hide()">
|
|
||||||
Annuler
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2:
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
|
||||||
export class Login {
|
export class Login {
|
||||||
email: string;
|
public email: string;
|
||||||
password: string;
|
public password: string;
|
||||||
}
|
}
|
||||||
|
29
src/login/loginForm.component.html
Normal file
29
src/login/loginForm.component.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<form novalidate (keyup.enter)="submit()" [formGroup]="form">
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="email" class="col-sm-4 control-label">Adresse email</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8"
|
||||||
|
[class.has-danger]="email.errors">
|
||||||
|
<input type="text" class="form-control" id="email"
|
||||||
|
formControlName="email" placeholder="Nom d'utilisateur">
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="email.errors">
|
||||||
|
<p *ngIf="email.errors.required">The email is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label for="password" class="col-sm-4 control-label">Mot de passe</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8"
|
||||||
|
[class.has-danger]="password.errors">
|
||||||
|
<input type="password" class="form-control" id="password"
|
||||||
|
formControlName="password" placeholder="Mot de passe">
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="password.errors">
|
||||||
|
<p *ngIf="password.errors.required">The password is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -7,37 +7,7 @@ import { Login } from './login';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'login-form',
|
selector: 'login-form',
|
||||||
exportAs: 'loginForm',
|
exportAs: 'loginForm',
|
||||||
template: `
|
templateUrl: './loginForm.component.html'
|
||||||
<form novalidate (keyup.enter)="submit()" [formGroup]="form">
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="email" class="col-sm-4 control-label">Adresse email</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-danger]="email.errors">
|
|
||||||
<input type="text" class="form-control" id="email"
|
|
||||||
formControlName="email" placeholder="Nom d'utilisateur">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="email.errors">
|
|
||||||
<p *ngIf="email.errors.required">The email is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="password" class="col-sm-4 control-label">Mot de passe</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-danger]="password.errors">
|
|
||||||
<input type="password" class="form-control" id="password"
|
|
||||||
formControlName="password" placeholder="Mot de passe">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="password.errors">
|
|
||||||
<p *ngIf="password.errors.required">The password is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export class LoginFormComponent {
|
export class LoginFormComponent {
|
||||||
|
17
src/login/loginModal.component.html
Normal file
17
src/login/loginModal.component.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title" id="modal-title">Authentification requise</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body" id="modal-body">
|
||||||
|
<login-form (submit)="submit()" #loginForm="loginForm"></login-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-succes" [disabled]="!loginForm.form.valid" (click)="submit()">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" (click)="cancel()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -2,31 +2,15 @@
|
|||||||
import { Component, Input, ViewChild } from '@angular/core';
|
import { Component, Input, ViewChild } from '@angular/core';
|
||||||
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
//import { jqxWindowComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxwindow';
|
||||||
|
import { jqxButtonComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons';
|
||||||
|
|
||||||
import { Login } from './login';
|
import { Login } from './login';
|
||||||
import { LoginFormComponent } from './loginForm.component';
|
import { LoginFormComponent } from './loginForm.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'login-modal',
|
selector: 'login-modal',
|
||||||
template: `
|
templateUrl: './loginModal.component.html'
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title" id="modal-title">Authentification requise</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
<login-form (submit)="submit()" #loginForm="loginForm"></login-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-primary" [disabled]="!loginForm.form.valid" (click)="submit()">
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn btn-default" (click)="cancel()">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class LoginModalComponent {
|
export class LoginModalComponent {
|
||||||
@ViewChild('loginForm') loginForm: LoginFormComponent;
|
@ViewChild('loginForm') loginForm: LoginFormComponent;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
export class Token {
|
export class Token {
|
||||||
access_token: string;
|
public access_token: string;
|
||||||
refresh_token: string;
|
public refresh_token: string;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ $fa-font-path: '~font-awesome/fonts';
|
|||||||
|
|
||||||
@import '~bootstrap/scss/bootstrap';
|
@import '~bootstrap/scss/bootstrap';
|
||||||
|
|
||||||
@import '~c3/c3';
|
@import '~jqwidgets-scripts/jqwidgets/styles/jqx.base';
|
||||||
|
|
||||||
@import '~ngx-toastr/toastr';
|
@import '~ngx-toastr/toastr';
|
||||||
|
|
||||||
@ -15,11 +15,3 @@ $fa-font-path: '~font-awesome/fonts';
|
|||||||
.stroke {
|
.stroke {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c3-ygrid-line.zeroline line {
|
|
||||||
stroke: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c3-ygrid-line.overdraft line {
|
|
||||||
stroke: #FF0000;
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
|
||||||
|
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
|
@ -1,177 +1,203 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2:
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import * as c3 from 'c3';
|
|
||||||
|
|
||||||
import {
|
import { Component, ViewChild, Input, OnInit } from '@angular/core';
|
||||||
Component, ElementRef,
|
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||||
Inject, Input, Output, EventEmitter,
|
|
||||||
OnInit, OnChanges
|
import { Logger } from '@nsalaun/ng-logger';
|
||||||
} from '@angular/core';
|
import { jqxChartComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxchart';
|
||||||
|
|
||||||
import { Account } from '../accounts/account';
|
import { Account } from '../accounts/account';
|
||||||
|
import { DailyBalance } from './dailyBalance';
|
||||||
import { DailyBalanceService } from '../accounts/dailyBalance.service';
|
import { DailyBalanceService } from '../accounts/dailyBalance.service';
|
||||||
|
|
||||||
class DateRange {
|
|
||||||
minDate: Date;
|
|
||||||
maxDate: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'balance-chart',
|
selector: 'balance-chart',
|
||||||
template: '<div></div>'
|
template: `
|
||||||
|
<jqxChart #balanceChart
|
||||||
|
[width]="'100%'"
|
||||||
|
[height]="400"
|
||||||
|
[title]="'Balance evolution'"
|
||||||
|
[description]="''"
|
||||||
|
[source]="data"
|
||||||
|
[xAxis]="xAxis"
|
||||||
|
[valueAxis]="valueAxis"
|
||||||
|
[seriesGroups]="seriesGroups"
|
||||||
|
(onRangeSelectionChanged)="select($event)">
|
||||||
|
</jqxChart>
|
||||||
|
`
|
||||||
})
|
})
|
||||||
export class BalanceChartComponent implements OnInit, OnChanges {
|
export class BalanceChartComponent implements OnInit {
|
||||||
@Input() account: Account;
|
private _account: Account;
|
||||||
@Output() onUpdate: EventEmitter<DateRange> = new EventEmitter<DateRange>();
|
@ViewChild('balanceChart') chart: jqxChartComponent;
|
||||||
|
|
||||||
private chart: c3.ChartAPI;
|
public data;
|
||||||
private balances: number[];
|
|
||||||
|
public xAxis: any = {
|
||||||
|
type: 'date',
|
||||||
|
dataField: 'operation_date',
|
||||||
|
displayText: 'Date',
|
||||||
|
baseUnit: 'day',
|
||||||
|
|
||||||
|
bands: [{
|
||||||
|
fillColor: 'blue',
|
||||||
|
opacity: 0.10
|
||||||
|
}],
|
||||||
|
|
||||||
|
rangeSelector: {
|
||||||
|
size: 80,
|
||||||
|
padding: { /*left: 0, right: 0,*/top: 0, bottom: 0 },
|
||||||
|
backgroundColor: 'white',
|
||||||
|
dataField: 'balance',
|
||||||
|
//baseUnit: 'month',
|
||||||
|
baseUnit: 'day',
|
||||||
|
gridLines: { visible: false },
|
||||||
|
serieType: 'line',
|
||||||
|
//labels: {
|
||||||
|
// formatFunction: (value: any): any => {
|
||||||
|
// return this.months[value.getMonth()] + '\'' + value.getFullYear().toString().substring(2);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public valueAxis: any = {
|
||||||
|
title: {
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public seriesGroups: any = [{
|
||||||
|
type: 'stackedcolumn',
|
||||||
|
valueAxis: {
|
||||||
|
title: { text: 'Expenses/revenues' },
|
||||||
|
position: 'right',
|
||||||
|
visible: true,
|
||||||
|
gridLines: { visible: false },
|
||||||
|
labels: {
|
||||||
|
horizontalAlignment: 'left',
|
||||||
|
formatSettings: {
|
||||||
|
sufix: '€',
|
||||||
|
decimalPlaces: 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
dataField: 'expenses',
|
||||||
|
displayText: 'Expenses',
|
||||||
|
fillColor: 'tomato'
|
||||||
|
}, {
|
||||||
|
dataField: 'revenues',
|
||||||
|
displayText: 'Revenues',
|
||||||
|
fillColor: 'yellowgreen'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
type: 'stepline',
|
||||||
|
valueAxis: {
|
||||||
|
title: { text: 'Balance' },
|
||||||
|
//gridLines: { visible: false },
|
||||||
|
labels: {
|
||||||
|
formatSettings: {
|
||||||
|
sufix: '€',
|
||||||
|
decimalPlaces: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
dataField: 'balance',
|
||||||
|
displayText: 'Balance',
|
||||||
|
fillColor: 'steelblue'
|
||||||
|
}],
|
||||||
|
bands: [{
|
||||||
|
minValue: 0, maxValue: 0, fillColor: 'orange', lineWidth: 1
|
||||||
|
}, {
|
||||||
|
minValue: 0, maxValue: 0, fillColor: 'red', lineWidth: 1
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private elementRef: ElementRef,
|
private router: Router,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private logger: Logger,
|
||||||
private dailyBalanceService: DailyBalanceService,
|
private dailyBalanceService: DailyBalanceService,
|
||||||
) {
|
) {
|
||||||
|
this.data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData(account: Account) {
|
loadData() {
|
||||||
|
this.logger.info('Loading data.');
|
||||||
|
|
||||||
|
let accountId = this.activatedRoute.snapshot.paramMap.get('accountId');
|
||||||
|
|
||||||
this.dailyBalanceService.query(
|
this.dailyBalanceService.query(
|
||||||
account.id
|
+accountId
|
||||||
).subscribe((results) => {
|
).subscribe((results) => {
|
||||||
var headers: any[][] = [['date', 'balances', 'expenses', 'revenues']];
|
this.data = results;
|
||||||
|
|
||||||
var rows = results.map(function(result) {
|
let lastResult = results[results.length -1];
|
||||||
return [
|
|
||||||
result.operation_date,
|
this.updateXBands(results[0].operation_date, lastResult.operation_date);
|
||||||
result.balance,
|
|
||||||
result.expenses,
|
|
||||||
result.revenues
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.chart.unload();
|
|
||||||
|
|
||||||
this.chart.load({
|
|
||||||
rows: headers.concat(rows)
|
|
||||||
});
|
|
||||||
|
|
||||||
var x: any;
|
|
||||||
|
|
||||||
x = this.chart.x();
|
|
||||||
|
|
||||||
var balances = x.balances;
|
|
||||||
|
|
||||||
this.onUpdate.emit({
|
|
||||||
minDate: balances[0],
|
|
||||||
maxDate: balances[balances.length - 1]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
var tomorrow = moment().endOf('day').valueOf();
|
|
||||||
|
|
||||||
this.chart = c3.generate({
|
|
||||||
bindto: this.elementRef.nativeElement.children[0],
|
|
||||||
size: {
|
|
||||||
height: 450,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
x: 'date',
|
|
||||||
rows: [],
|
|
||||||
axes: {
|
|
||||||
expenses: 'y2',
|
|
||||||
revenues: 'y2'
|
|
||||||
},
|
|
||||||
type: 'bar',
|
|
||||||
types: {
|
|
||||||
balances: 'area'
|
|
||||||
},
|
|
||||||
groups: [
|
|
||||||
['expenses', 'revenues']
|
|
||||||
],
|
|
||||||
// Disable for the moment because there is an issue when
|
|
||||||
// using subchart line is not refreshed after subset
|
|
||||||
// selection.
|
|
||||||
//regions: {
|
|
||||||
// balances: [{
|
|
||||||
// start: tomorrow,
|
|
||||||
// style: 'dashed'
|
|
||||||
// }]
|
|
||||||
//}
|
|
||||||
},
|
|
||||||
regions: [{
|
|
||||||
start: tomorrow,
|
|
||||||
}],
|
|
||||||
axis: {
|
|
||||||
x: {
|
|
||||||
type: 'timeseries',
|
|
||||||
tick: {
|
|
||||||
format: '%Y-%m-%d',
|
|
||||||
rotate: 50,
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
y: {
|
|
||||||
label: {
|
|
||||||
text: 'Amount',
|
|
||||||
position: 'outer-middle'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y2: {
|
|
||||||
show: true,
|
|
||||||
label: {
|
|
||||||
text: 'Amount',
|
|
||||||
position: 'outer-middle'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
x: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
show: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
format: {
|
|
||||||
value: function(value, ratio, id, index) {
|
|
||||||
return value + '€';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
subchart: {
|
|
||||||
show: true,
|
|
||||||
onbrush: (domain) => {
|
|
||||||
this.onUpdate.emit({minDate: domain[0], maxDate: domain[1]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
setLines(account: Account) {
|
setLines(account: Account) {
|
||||||
if(this.chart) {
|
if (account) {
|
||||||
this.chart.ygrids([
|
this.seriesGroups[1].bands[1].minValue = account.authorized_overdraft;
|
||||||
{ value: 0, axis: 'y2' },
|
this.seriesGroups[1].bands[1].maxValue = account.authorized_overdraft;
|
||||||
{ value: 0, axis: 'y', class: 'zeroline'},
|
}
|
||||||
]);
|
}
|
||||||
|
|
||||||
if(account) {
|
ngOnInit() {
|
||||||
this.chart.ygrids.add({
|
this.activatedRoute.queryParamMap.subscribe((params: ParamMap) => {
|
||||||
value: account.authorized_overdraft,
|
let fromDay = params.get('from');
|
||||||
axis: 'y',
|
let toDay = params.get('to');
|
||||||
class: 'overdraft'
|
|
||||||
|
this.xAxis.minValue = moment(fromDay).toDate();
|
||||||
|
this.xAxis.maxValue = moment(toDay).toDate();
|
||||||
|
|
||||||
|
if(this.chart && this.chart.host) {
|
||||||
|
this.chart.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set account(account: Account) {
|
||||||
|
this._account = account;
|
||||||
|
|
||||||
|
this.setLines(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
get account(): Account {
|
||||||
|
return this._account;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateXBands(minDate, maxDate) {
|
||||||
|
if(moment(maxDate) > moment()) {
|
||||||
|
if(moment(minDate) < moment()) {
|
||||||
|
this.xAxis.bands[0].minValue = moment().toDate();
|
||||||
|
} else {
|
||||||
|
this.xAxis.bands[0].minValue = moment(minDate).toDate();
|
||||||
|
}
|
||||||
|
this.xAxis.bands[0].maxValue = moment(maxDate).toDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select(event: any) {
|
||||||
|
let args = event.args;
|
||||||
|
|
||||||
|
this.updateXBands(args.minValue, args.maxValue);
|
||||||
|
|
||||||
|
let accountId = this.activatedRoute.snapshot.paramMap.get('accountId');
|
||||||
|
|
||||||
|
this.router.navigate(['account', accountId, 'operations'], {
|
||||||
|
queryParams: {
|
||||||
|
from: moment(args.minValue).format('YYYY-MM-DD'),
|
||||||
|
to: moment(args.maxValue).format('YYYY-MM-DD')
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ngOnChanges(changes) {
|
|
||||||
if('account' in changes && changes.account.currentValue) {
|
|
||||||
this.loadData(changes.account.currentValue);
|
|
||||||
this.setLines(changes.account.currentValue);
|
|
||||||
} else {
|
|
||||||
this.setLines(this.account);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ export class CategoryService {
|
|||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
query(id: number, minDate: Date = null, maxDate: Date = null): Observable<Category[]> {
|
query(id: number, minDate: Date|string = null, maxDate: Date|string = null): Observable<Category[]> {
|
||||||
let params: HttpParams = new HttpParams();
|
let params: HttpParams = new HttpParams();
|
||||||
|
|
||||||
if(minDate) {
|
if(minDate) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
export class Category {
|
export class Category {
|
||||||
category: string;
|
public category: string;
|
||||||
expenses: number;
|
public expenses: number;
|
||||||
revenues: number;
|
public revenues: number;
|
||||||
|
public income: number;
|
||||||
}
|
}
|
||||||
|
@ -1,107 +1,141 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
import * as c3 from 'c3';
|
import { Component, ViewChild, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
|
||||||
import {
|
import { Logger } from '@nsalaun/ng-logger';
|
||||||
Component, ElementRef,
|
import { jqxChartComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxchart';
|
||||||
Inject, Input, Output,
|
|
||||||
OnInit, OnChanges
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { Account } from '../accounts/account';
|
import * as _ from 'underscore';
|
||||||
|
|
||||||
|
import { Category } from './category';
|
||||||
import { CategoryService } from './category.service';
|
import { CategoryService } from './category.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'category-chart',
|
selector: 'category-chart',
|
||||||
template: '<div></div>'
|
template: `
|
||||||
|
<jqxChart #categoryChart
|
||||||
|
[width]="'100%'"
|
||||||
|
[height]="400"
|
||||||
|
[title]="'Categories'"
|
||||||
|
[description]="''"
|
||||||
|
[showLegend]="false"
|
||||||
|
[seriesGroups]="seriesGroups">
|
||||||
|
</jqxChart>
|
||||||
|
`
|
||||||
})
|
})
|
||||||
export class CategoryChartComponent implements OnInit, OnChanges {
|
export class CategoryChartComponent implements OnInit {
|
||||||
@Input() minDate: Date;
|
@ViewChild('categoryChart') chart: jqxChartComponent;
|
||||||
@Input() maxDate: Date;
|
|
||||||
@Input() account: Account;
|
|
||||||
|
|
||||||
chart: c3.ChartAPI;
|
public seriesGroups: any = [{
|
||||||
|
type: 'donut',
|
||||||
|
source: [],
|
||||||
|
//showLabels: true,
|
||||||
|
series: [{
|
||||||
|
dataField: 'value',
|
||||||
|
displayText: 'category',
|
||||||
|
initialAngle: 90,
|
||||||
|
radius: 130,
|
||||||
|
innerRadius: 90,
|
||||||
|
formatSettings: { sufix: '€', decimalPlaces: 2 },
|
||||||
|
radiusDataField: 'category',
|
||||||
|
colorFunction: (value, itemIndex, series, group) => {
|
||||||
|
if(group.source[itemIndex].type === 'expenses') {
|
||||||
|
return 'tomato';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'yellowgreen';
|
||||||
|
},
|
||||||
|
opacity: 0.5
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
type: 'donut',
|
||||||
|
source: [],
|
||||||
|
showLabels: true,
|
||||||
|
series: [{
|
||||||
|
dataField: 'value',
|
||||||
|
displayText: 'name',
|
||||||
|
initialAngle: 90,
|
||||||
|
labelRadius: 50,
|
||||||
|
radius: 85,
|
||||||
|
innerRadius: 75,
|
||||||
|
formatSettings: { sufix: '€', decimalPlaces: 2 },
|
||||||
|
colorFunction: (value, itemIndex, series, group) => {
|
||||||
|
if(group.source[itemIndex].name === 'Expenses') {
|
||||||
|
return 'tomato';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'yellowgreen';
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private elementRef: ElementRef,
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private logger: Logger,
|
||||||
private categoryService: CategoryService,
|
private categoryService: CategoryService,
|
||||||
) {}
|
) {
|
||||||
|
//this.data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
let accountId = this.activatedRoute.snapshot.paramMap.get('accountId');
|
||||||
|
let fromDay = this.activatedRoute.snapshot.queryParamMap.get('from');
|
||||||
|
let toDay = this.activatedRoute.snapshot.queryParamMap.get('to');
|
||||||
|
|
||||||
loadData(account: Account) {
|
|
||||||
this.categoryService.query(
|
this.categoryService.query(
|
||||||
account.id,
|
+accountId,
|
||||||
this.minDate,
|
fromDay,
|
||||||
this.maxDate
|
toDay
|
||||||
).subscribe((results) => {
|
).subscribe((results: Category[]) => {
|
||||||
var expenses=[],
|
let expenses = _.filter(results, function(item: Category) {
|
||||||
revenues=[],
|
return item.expenses < 0;
|
||||||
colors={},
|
}).map(function(item: Category) {
|
||||||
names={};
|
return {
|
||||||
|
category: item.category,
|
||||||
var revenuesColor = 'green',
|
value: -item.expenses,
|
||||||
expensesColor = 'orange';
|
type: 'expenses'
|
||||||
|
|
||||||
for(let result of results) {
|
|
||||||
if(result.revenues > 0) {
|
|
||||||
var revenuesName = 'revenues-' + result.category;
|
|
||||||
|
|
||||||
revenues.push([revenuesName, result.revenues]);
|
|
||||||
names[revenuesName] = result.category;
|
|
||||||
colors[revenuesName] = revenuesColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result.expenses < 0) {
|
|
||||||
var expensesName = 'expenses-' + result.category;
|
|
||||||
|
|
||||||
expenses.splice(0, 0, [expensesName, -result.expenses]);
|
|
||||||
names[expensesName] = result.category;
|
|
||||||
colors[expensesName] = expensesColor;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.chart.unload();
|
|
||||||
|
|
||||||
this.chart.load({
|
|
||||||
columns: revenues.concat(expenses),
|
|
||||||
names: names,
|
|
||||||
colors: colors
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expenses = _.sortBy(expenses, 'value').reverse();
|
||||||
|
|
||||||
|
let revenues = _.filter(results, function(item: Category) {
|
||||||
|
return item.revenues > 0;
|
||||||
|
}).map(function(item: Category) {
|
||||||
|
return {
|
||||||
|
category: item.category,
|
||||||
|
value: item.revenues,
|
||||||
|
type: 'revenues'
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
revenues = _.sortBy(revenues, 'value');
|
||||||
|
|
||||||
|
this.seriesGroups[0].source = expenses.concat(revenues);
|
||||||
|
|
||||||
|
let totals = [
|
||||||
|
{name: 'Expenses', value: 0},
|
||||||
|
{name: 'Revenues', value: 0}
|
||||||
|
];
|
||||||
|
|
||||||
|
results.forEach(function(item: Category) {
|
||||||
|
totals[0].value -= item.expenses;
|
||||||
|
totals[1].value += item.revenues;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.seriesGroups[1].source = totals;
|
||||||
|
|
||||||
|
if(this.chart && this.chart.host) {
|
||||||
|
this.chart.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.chart = c3.generate({
|
this.activatedRoute.queryParamMap.subscribe(() => {this.loadData();});
|
||||||
bindto: this.elementRef.nativeElement.children[0],
|
|
||||||
data: {
|
|
||||||
columns: [],
|
|
||||||
type: 'donut',
|
|
||||||
order: null,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
format: {
|
|
||||||
value: function(value, ratio, id, index) {
|
|
||||||
return value + '€';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
donut: {
|
|
||||||
label: {
|
|
||||||
format: function(value) {
|
|
||||||
return value + '€';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ngOnChanges(changes) {
|
this.loadData();
|
||||||
if('account' in changes && changes.account.currentValue) {
|
|
||||||
this.loadData(changes.account.currentValue);
|
|
||||||
} else if (this.account) {
|
|
||||||
this.loadData(this.account);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
9
src/operations/dailyBalance.ts
Normal file
9
src/operations/dailyBalance.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
|
export class DailyBalance {
|
||||||
|
public operation_date: string;
|
||||||
|
public expenses: number;
|
||||||
|
public revenues: number;
|
||||||
|
public income: number;
|
||||||
|
public balance: number;
|
||||||
|
}
|
@ -2,37 +2,39 @@
|
|||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { NgLoggerModule, Level } from '@nsalaun/ng-logger';
|
import { NgLoggerModule, Level } from '@nsalaun/ng-logger';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||||
import { ToastrModule } from 'ngx-toastr';
|
import { ToastrModule } from 'ngx-toastr';
|
||||||
import { TextMaskModule } from 'angular2-text-mask';
|
import { TextMaskModule } from 'angular2-text-mask';
|
||||||
|
import { jqxChartComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxchart';
|
||||||
|
|
||||||
import { BalanceChartComponent } from './balanceChart.component';
|
import { BalanceChartComponent } from './balanceChart.component';
|
||||||
import { CategoryChartComponent } from './categoryChart.component';
|
import { CategoryChartComponent } from './categoryChart.component';
|
||||||
import { OperationRowComponent } from './operationRow.component';
|
import { OperationRowComponent } from './operationRow.component';
|
||||||
import { CategoryService } from './category.service';
|
import { CategoryService } from './category.service';
|
||||||
import { OperationService } from './operation.service';
|
import { OperationService } from './operation.service';
|
||||||
|
import { OperationEditComponent } from './operationEdit.component';
|
||||||
import { OperationListComponent } from './operationList.component';
|
import { OperationListComponent } from './operationList.component';
|
||||||
import { OperationDeleteModalComponent } from './operationDeleteModal.component';
|
import { OperationDeleteModalComponent } from './operationDeleteModal.component';
|
||||||
import { OperationFormComponent } from './operationForm.component';
|
import { OperationRoutes } from './operation.states';
|
||||||
import { OperationEditModalComponent } from './operationEditModal.component'
|
|
||||||
import { OperationListState } from './operation.states'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ReactiveFormsModule,
|
FormsModule,
|
||||||
RouterModule.forChild([
|
RouterModule.forChild(
|
||||||
OperationListState
|
OperationRoutes
|
||||||
]),
|
),
|
||||||
NgLoggerModule,
|
NgLoggerModule,
|
||||||
ToastrModule,
|
ToastrModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
|
NgxChartsModule,
|
||||||
TextMaskModule
|
TextMaskModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
@ -40,17 +42,16 @@ import { OperationListState } from './operation.states'
|
|||||||
OperationService,
|
OperationService,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
jqxChartComponent,
|
||||||
BalanceChartComponent,
|
BalanceChartComponent,
|
||||||
CategoryChartComponent,
|
CategoryChartComponent,
|
||||||
OperationRowComponent,
|
OperationRowComponent,
|
||||||
|
OperationEditComponent,
|
||||||
OperationListComponent,
|
OperationListComponent,
|
||||||
OperationDeleteModalComponent,
|
OperationDeleteModalComponent,
|
||||||
OperationFormComponent,
|
|
||||||
OperationEditModalComponent,
|
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
OperationDeleteModalComponent,
|
OperationDeleteModalComponent,
|
||||||
OperationEditModalComponent,
|
|
||||||
OperationListComponent,
|
OperationListComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
import { OperationListComponent } from './operationList.component';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
export const OperationListState = {
|
import { OperationListComponent } from './operationList.component';
|
||||||
path: 'account/:accountId/operations',
|
import { OperationEditComponent } from './operationEdit.component';
|
||||||
component: OperationListComponent
|
|
||||||
}
|
export const OperationRoutes: Routes = [{
|
||||||
|
path: 'accounts/:accountId/operations',
|
||||||
|
component: OperationListComponent,
|
||||||
|
}, {
|
||||||
|
path: 'accounts/:accountId/operations/new',
|
||||||
|
component: OperationEditComponent,
|
||||||
|
}, {
|
||||||
|
path: 'accounts/:accountId/operations/:operationId/edit',
|
||||||
|
component: OperationEditComponent,
|
||||||
|
}];
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2:
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
|
||||||
export class Operation {
|
export class Operation {
|
||||||
id: number;
|
public id: number;
|
||||||
operation_date: string;
|
public operation_date: string;
|
||||||
label: string;
|
public label: string;
|
||||||
value: number;
|
public value: number;
|
||||||
category: string;
|
public category: string;
|
||||||
scheduled_operation_id: number;
|
public scheduled_operation_id: number;
|
||||||
account_id: number;
|
public account_id: number;
|
||||||
balance: number;
|
public balance: number;
|
||||||
confirmed: boolean;
|
public confirmed: boolean;
|
||||||
pointed: boolean;
|
public pointed: boolean;
|
||||||
cancelled: boolean
|
public canceled: boolean
|
||||||
}
|
}
|
||||||
|
22
src/operations/operationDeleteModal.component.html
Normal file
22
src/operations/operationDeleteModal.component.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2 : -->
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title" id="modal-title">Delete Operation #{{ operation.id }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body" id="modal-body">
|
||||||
|
<p>
|
||||||
|
Do you really want to delete operation #{{ operation.id }} with label:<br/>
|
||||||
|
{{ operation.label }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-danger" (click)="submit()">
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" (click)="cancel()">
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
@ -7,28 +7,7 @@ import { Operation } from './operation';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'operation-delete-modal',
|
selector: 'operation-delete-modal',
|
||||||
template: `
|
templateUrl: './operationDeleteModal.component.html'
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title" id="modal-title">Delete Operation #{{ operation.id }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
<p>
|
|
||||||
Do you really want to delete operation #{{ operation.id }} with label:<br/>
|
|
||||||
{{ operation.label }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-danger" (click)="submit()">
|
|
||||||
Yes
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn btn-default" (click)="cancel()">
|
|
||||||
No
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class OperationDeleteModalComponent {
|
export class OperationDeleteModalComponent {
|
||||||
@Input() operation: Operation
|
@Input() operation: Operation
|
||||||
|
79
src/operations/operationEdit.component.html
Normal file
79
src/operations/operationEdit.component.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2 :-->
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<form novalidate (keyup.enter)="submit()" #form="ngForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="operation-date">Date</label>
|
||||||
|
|
||||||
|
<input class="form-control"
|
||||||
|
[class.has-danger]="operationDate.errors"
|
||||||
|
id="operation-date" name="operationDate"
|
||||||
|
[(ngModel)]="operation.operation_date" #operationDate="ngModel"
|
||||||
|
[textMask]="{mask: dateMask}"
|
||||||
|
placeholder="Operation date" required>
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="operationDate.errors">
|
||||||
|
<small class="form-text" *ngIf="operationDate.errors.required">
|
||||||
|
The operation date is required.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="label">Label</label>
|
||||||
|
|
||||||
|
<input class="form-control"
|
||||||
|
[class.has-danger]="label.errors"
|
||||||
|
id="label" name="label"
|
||||||
|
[(ngModel)]="operation.label" #label="ngModel"
|
||||||
|
placeholder="Label" required>
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="label.errors">
|
||||||
|
<small class="form-text" *ngIf="label.errors.required">
|
||||||
|
The operation label is required.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="value">Montant</label>
|
||||||
|
|
||||||
|
<input class="form-control"
|
||||||
|
[class.has-errors]="value.errors"
|
||||||
|
id="value" name="value"
|
||||||
|
[(ngModel)]="operation.value" #value="ngModel"
|
||||||
|
type="number" placeholder="Value" required>
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="value.errors">
|
||||||
|
<small class="form-text" *ngIf="value.errors.required">
|
||||||
|
The operation value is required.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="category">Catégorie</label>
|
||||||
|
|
||||||
|
<input class="form-control"
|
||||||
|
[class.has-errors]="category.errors"
|
||||||
|
id="category" name="category"
|
||||||
|
[(ngModel)]="operation.category" #category="ngModel"
|
||||||
|
placeholder="Category" required>
|
||||||
|
|
||||||
|
<div class="help-block text-danger" *ngIf="category.errors">
|
||||||
|
<small class="form-text" *ngIf="category.errors.required">
|
||||||
|
The operation category is required.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" [disabled]="!form.valid" (click)="submit()">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default" (click)="cancel()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
86
src/operations/operationEdit.component.ts
Normal file
86
src/operations/operationEdit.component.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
|
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router, Params } from '@angular/router';
|
||||||
|
|
||||||
|
import { Logger } from '@nsalaun/ng-logger';
|
||||||
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
|
import { Operation } from './operation';
|
||||||
|
import { OperationService } from './operation.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'operation-edit',
|
||||||
|
templateUrl: './operationEdit.component.html'
|
||||||
|
})
|
||||||
|
export class OperationEditComponent {
|
||||||
|
public operation: Operation = new Operation();
|
||||||
|
|
||||||
|
//dateMask = [/\d{4}/, '-', /0[1-9]|1[0-2]/, '-', /[0-2]\d|3[0-1]/];
|
||||||
|
dateMask = ['2', '0', /\d/, /\d/, '-', /[0-1]/, /\d/, '-', /[0-3]/, /\d/];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private location: Location,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private logger: Logger,
|
||||||
|
private toastrService: ToastrService,
|
||||||
|
private operationService: OperationService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.route.paramMap.subscribe((params: Params) => {
|
||||||
|
let operationId = params.get('operationId');
|
||||||
|
|
||||||
|
if (operationId) {
|
||||||
|
this.logger.info('Loading operation with id', operationId);
|
||||||
|
|
||||||
|
// Load Operation
|
||||||
|
this.operationService.get(
|
||||||
|
+operationId
|
||||||
|
).subscribe((operation: Operation) => {
|
||||||
|
this.operation = operation;
|
||||||
|
|
||||||
|
this.logger.info(operation);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.logger.info('Initialize new operation');
|
||||||
|
|
||||||
|
let accountId = params.get('accountId');
|
||||||
|
|
||||||
|
this.operation = new Operation();
|
||||||
|
this.operation.account_id = +accountId;
|
||||||
|
|
||||||
|
this.logger.info(this.operation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(): void {
|
||||||
|
this.save(this.operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save an operation and return a promise.
|
||||||
|
*/
|
||||||
|
save(operation) {
|
||||||
|
operation.confirmed = true;
|
||||||
|
|
||||||
|
return this.operationService.create(operation).subscribe(
|
||||||
|
(operation) => {
|
||||||
|
this.toastrService.success('Operation #' + operation.id + ' saved.');
|
||||||
|
|
||||||
|
this.location.back();
|
||||||
|
}, (result) => {
|
||||||
|
this.toastrService.error(
|
||||||
|
'Error while saving operation: ' + result.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(): void {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2:
|
|
||||||
import { Component, Input, ViewChild } from '@angular/core';
|
|
||||||
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
|
|
||||||
import { Operation } from './operation';
|
|
||||||
import { OperationFormComponent } from './operationForm.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'operation-edit-modal',
|
|
||||||
template: `
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 class="modal-title" id="modal-title">{{ title() }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
<operation-form [operation]="operation" (submit)="submit()" #operationForm="operationForm"></operation-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-primary" [disabled]="!operationForm.form.valid" (click)="submit()">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn btn-default" (click)="cancel()">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class OperationEditModalComponent {
|
|
||||||
@Input() operation: Operation;
|
|
||||||
@ViewChild('operationForm') operationForm: OperationFormComponent;
|
|
||||||
|
|
||||||
valid: boolean = false;
|
|
||||||
|
|
||||||
constructor(private activeModal: NgbActiveModal) {}
|
|
||||||
|
|
||||||
title(): string {
|
|
||||||
if(this.operation.id) {
|
|
||||||
return "Operation #" + this.operation.id;
|
|
||||||
} else {
|
|
||||||
return "New operation";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
submit(): void {
|
|
||||||
let formModel = this.operationForm.form.value;
|
|
||||||
let operation = Object.assign({}, this.operation);
|
|
||||||
|
|
||||||
operation.id = this.operation.id;
|
|
||||||
operation.operation_date = formModel.operationDate;
|
|
||||||
operation.label = formModel.label;
|
|
||||||
operation.value = formModel.value;
|
|
||||||
operation.category = formModel.category;
|
|
||||||
|
|
||||||
this.activeModal.close(operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel(): void {
|
|
||||||
this.activeModal.dismiss("closed");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
|
||||||
import { Component, OnInit, OnChanges, Input, Output, EventEmitter } from '@angular/core';
|
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
||||||
|
|
||||||
import { Operation } from './operation';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'operation-form',
|
|
||||||
exportAs: 'operationForm',
|
|
||||||
template: `
|
|
||||||
<form novalidate (keyup.enter)="submit()" [formGroup]="form">
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 control-label" for="operation-date">Date</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-danger]="operationDate.errors">
|
|
||||||
<input class="form-control"
|
|
||||||
id="operation-date" formControlName="operationDate"
|
|
||||||
[textMask]="{mask: dateMask}"
|
|
||||||
placeholder="Operation date">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="operationDate.errors">
|
|
||||||
<p *ngIf="operationDate.errors.required">The operation date is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 control-label" for="label">Label</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-danger]="label.errors">
|
|
||||||
<input class="form-control"
|
|
||||||
id="label" formControlName="label"
|
|
||||||
placeholder="Label">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="label.errors">
|
|
||||||
<p *ngIf="label.errors.required">The operation label is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 control-label" for="value">Montant</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-errors]="value.errors">
|
|
||||||
<input class="form-control"
|
|
||||||
id="value" formControlName="value"
|
|
||||||
type="number" placeholder="Value">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="value.errors">
|
|
||||||
<p *ngIf="value.errors.required">The operation value is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 control-label" for="category">Catégorie</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8"
|
|
||||||
[class.has-errors]="category.errors">
|
|
||||||
<input class="form-control"
|
|
||||||
id="category" formControlName="category"
|
|
||||||
placeholder="Category">
|
|
||||||
|
|
||||||
<div class="help-block text-danger" *ngIf="category.errors">
|
|
||||||
<p *ngIf="category.errors.required">The operation category is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class OperationFormComponent implements OnInit {
|
|
||||||
public form: FormGroup;
|
|
||||||
@Input() operation: Operation;
|
|
||||||
@Output() submitEventEmitter: EventEmitter<void> = new EventEmitter<void>();
|
|
||||||
|
|
||||||
//dateMask = [/\d{4}/, '-', /0[1-9]|1[0-2]/, '-', /[0-2]\d|3[0-1]/];
|
|
||||||
dateMask = ['2', '0', /\d/, /\d/, '-', /[0-1]/, /\d/, '-', /[0-3]/, /\d/];
|
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.form = this.formBuilder.group({
|
|
||||||
operationDate: ['', Validators.required],
|
|
||||||
label: ['', Validators.required],
|
|
||||||
value: ['', Validators.required],
|
|
||||||
category: ['', Validators.required],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.form.patchValue({
|
|
||||||
operationDate: this.operation.operation_date,
|
|
||||||
label: this.operation.label,
|
|
||||||
value: this.operation.value,
|
|
||||||
category: this.operation.category,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
if(this.form.valid) {
|
|
||||||
this.submitEventEmitter.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get operationDate() {
|
|
||||||
return this.form.get('operationDate');
|
|
||||||
}
|
|
||||||
|
|
||||||
get label() {
|
|
||||||
return this.form.get('label');
|
|
||||||
}
|
|
||||||
|
|
||||||
get value() {
|
|
||||||
return this.form.get('value');
|
|
||||||
}
|
|
||||||
|
|
||||||
get category() {
|
|
||||||
return this.form.get('category');
|
|
||||||
}
|
|
||||||
}
|
|
46
src/operations/operationList.component.html
Normal file
46
src/operations/operationList.component.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2 : -->
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<balance-chart [account]="account"></balance-chart>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<category-chart></category-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Date d'op.</th>
|
||||||
|
<th>Libellé de l'opération</th>
|
||||||
|
<th>Montant</th>
|
||||||
|
<th>Solde</th>
|
||||||
|
<th>Catégorie</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">
|
||||||
|
<a class="btn btn-success"
|
||||||
|
[routerLink]="['new']">
|
||||||
|
Ajouter
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr [operation-row]="operation"
|
||||||
|
[account]="account"
|
||||||
|
(needsReload)="loadData()"
|
||||||
|
*ngFor="let operation of operations">
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -1,8 +1,9 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
import { Component, Inject, OnInit } from '@angular/core';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router, Params } from '@angular/router';
|
||||||
|
|
||||||
import { Logger } from '@nsalaun/ng-logger';
|
import { Logger } from '@nsalaun/ng-logger';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@ -12,65 +13,14 @@ import { Account } from '../accounts/account';
|
|||||||
import { AccountService } from '../accounts/account.service';
|
import { AccountService } from '../accounts/account.service';
|
||||||
import { Operation } from './operation';
|
import { Operation } from './operation';
|
||||||
import { OperationService } from './operation.service';
|
import { OperationService } from './operation.service';
|
||||||
import { OperationEditModalComponent } from './operationEditModal.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'operation-list',
|
selector: 'operation-list',
|
||||||
template: `
|
templateUrl: './operationList.component.html'
|
||||||
<div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<balance-chart (onUpdate)="onUpdate($event)"
|
|
||||||
[account]="account"></balance-chart>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
|
||||||
<category-chart
|
|
||||||
[minDate]="minDate"
|
|
||||||
[maxDate]="maxDate"
|
|
||||||
[account]="account"></category-chart>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-condensed table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>Date d'op.</th>
|
|
||||||
<th>Libellé de l'opération</th>
|
|
||||||
<th>Montant</th>
|
|
||||||
<th>Solde</th>
|
|
||||||
<th>Catégorie</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colspan="6">
|
|
||||||
<button class="btn btn-success" (click)="add()">
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr [operation-row]="operation"
|
|
||||||
[account]="account"
|
|
||||||
(needsReload)="load(minDate, maxDate)"
|
|
||||||
*ngFor="let operation of operations">
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class OperationListComponent implements OnInit {
|
export class OperationListComponent implements OnInit {
|
||||||
private account: Account;
|
private account: Account;
|
||||||
private minDate: Date;
|
public operations: Operation[];
|
||||||
private maxDate: Date;
|
|
||||||
private operations: Operation[];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private toastrService: ToastrService,
|
private toastrService: ToastrService,
|
||||||
@ -78,73 +28,49 @@ export class OperationListComponent implements OnInit {
|
|||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.accountService.get(
|
this.route.queryParamMap.subscribe((params: Params) => {
|
||||||
+this.route.snapshot.paramMap.get('accountId')
|
if (params.get('from') && params.get('to')) {
|
||||||
).subscribe(account => {
|
this.loadData();
|
||||||
this.account = account
|
} else {
|
||||||
|
this.router.navigate([], {
|
||||||
|
queryParams: {
|
||||||
|
from: moment().startOf('month').format('YYYY-MM-DD'),
|
||||||
|
to: moment().endOf('month').format('YYYY-MM-DD')
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Add an empty operation.
|
|
||||||
*/
|
|
||||||
add() {
|
|
||||||
var operation = new Operation();
|
|
||||||
operation.account_id = this.account.id;
|
|
||||||
|
|
||||||
// FIXME Alexis Lahouze 2017-06-15 i18n
|
|
||||||
const modal = this.ngbModal.open(OperationEditModalComponent, {
|
|
||||||
size: 'lg'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.componentInstance.operation = operation;
|
this.route.paramMap.subscribe((params: Params) => {
|
||||||
|
let accountId = params.get('accountId');
|
||||||
|
|
||||||
modal.result.then((operation: Operation) => {
|
this.accountService.get(
|
||||||
this.save(operation);
|
+accountId
|
||||||
}, (reason) => {
|
).subscribe(account => {
|
||||||
|
this.account = account;
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load operations.
|
* Load operations.
|
||||||
*/
|
*/
|
||||||
load(minDate, maxDate) {
|
loadData() {
|
||||||
this.minDate = minDate;
|
let accountId = this.route.snapshot.paramMap.get('accountId');
|
||||||
this.maxDate = maxDate;
|
let fromDay = this.route.snapshot.queryParamMap.get('from');
|
||||||
|
let toDay = this.route.snapshot.queryParamMap.get('to');
|
||||||
|
|
||||||
return this.operationService.query(
|
return this.operationService.query(
|
||||||
this.account.id,
|
+accountId,
|
||||||
minDate,
|
fromDay,
|
||||||
maxDate
|
toDay
|
||||||
).subscribe((operations: Operation[]) => {
|
).subscribe((operations: Operation[]) => {
|
||||||
this.operations = operations.reverse();
|
this.operations = operations.reverse();
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Save an operation and return a promise.
|
|
||||||
*/
|
|
||||||
save(operation) {
|
|
||||||
operation.confirmed = true;
|
|
||||||
|
|
||||||
return this.operationService.create(operation).subscribe(
|
|
||||||
(operation) => {
|
|
||||||
this.toastrService.success('Operation #' + operation.id + ' saved.');
|
|
||||||
|
|
||||||
this.load(this.minDate, this.maxDate);
|
|
||||||
}, (result) => {
|
|
||||||
this.toastrService.error(
|
|
||||||
'Error while saving operation: ' + result.message
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
onUpdate(dateRange) {
|
|
||||||
this.load(dateRange.minDate, dateRange.maxDate);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
51
src/operations/operationRow.component.html
Normal file
51
src/operations/operationRow.component.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2 : -->
|
||||||
|
<td>{{ operation.id }}</td>
|
||||||
|
|
||||||
|
<td>{{ operation.operation_date | date:"yyyy-MM-dd" }}</td>
|
||||||
|
|
||||||
|
<td>{{ operation.label }}</td>
|
||||||
|
|
||||||
|
<td>{{ operation.value | currency:'EUR':'symbol' }}</td>
|
||||||
|
|
||||||
|
<td [class.text-warning]="operation.balance < 0"
|
||||||
|
[class.text-danger]="operation.balance < account.authorized_overdraft">
|
||||||
|
{{ operation.balance | currency:'EUR':'symbol' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ operation.category }}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<!-- Edit operation, for non-canceled operation. -->
|
||||||
|
<a class="btn btn-success"
|
||||||
|
*ngIf="!operation.canceled"
|
||||||
|
[routerLink]="[operation.id, 'edit']"
|
||||||
|
title="edit">
|
||||||
|
<span class="fa fa-pencil-square-o"></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Toggle pointed operation, for non-canceled operations. -->
|
||||||
|
<button type="button" class="btn btn-secondary"
|
||||||
|
*ngIf="!operation.canceled"
|
||||||
|
(click)="togglePointed(operation)"
|
||||||
|
[class.active]="operation.pointed" title="point">
|
||||||
|
<span class="fa" [class.fa-check-square-o]="operation.pointed"
|
||||||
|
[class.fa-square-o]="!operation.pointed"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Toggle canceled operation. -->
|
||||||
|
<button type="button" class="btn btn-warning"
|
||||||
|
(click)="toggleCanceled(operation)"
|
||||||
|
*ngIf="operation.scheduled_operation_id"
|
||||||
|
[class.active]="operation.canceled" title="cancel">
|
||||||
|
<span class="fa fa-remove"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Delete operation, with confirm. -->
|
||||||
|
<button type="button" class="btn btn-danger"
|
||||||
|
(click)="confirmDelete(operation)"
|
||||||
|
*ngIf="operation.id && !operation.scheduled_operation_id">
|
||||||
|
<span class="fa fa-trash-o"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
@ -1,7 +1,9 @@
|
|||||||
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
// vim: set tw=80 ts=2 sw=2 sts=2 :
|
||||||
import { CurrencyPipe } from '@angular/common';
|
import { CurrencyPipe } from '@angular/common';
|
||||||
import { Component, Inject, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, Inject, Input } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { Logger } from '@nsalaun/ng-logger';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
@ -9,7 +11,6 @@ import { Account } from '../accounts/account';
|
|||||||
import { Operation } from './operation';
|
import { Operation } from './operation';
|
||||||
import { OperationService } from './operation.service';
|
import { OperationService } from './operation.service';
|
||||||
import { OperationDeleteModalComponent } from './operationDeleteModal.component';
|
import { OperationDeleteModalComponent } from './operationDeleteModal.component';
|
||||||
import { OperationEditModalComponent } from './operationEditModal.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tr[operation-row]',
|
selector: 'tr[operation-row]',
|
||||||
@ -20,80 +21,31 @@ import { OperationEditModalComponent } from './operationEditModal.component';
|
|||||||
"[class.warning]": "operation.balance < 0",
|
"[class.warning]": "operation.balance < 0",
|
||||||
"[class.danger]": "operation.balance < account.authorized_overdraft"
|
"[class.danger]": "operation.balance < account.authorized_overdraft"
|
||||||
},
|
},
|
||||||
template: `
|
templateUrl: './operationRow.component.html'
|
||||||
<td>{{ operation.id }}</td>
|
|
||||||
|
|
||||||
<td>{{ operation.operation_date | date:"yyyy-MM-dd" }}</td>
|
|
||||||
|
|
||||||
<td>{{ operation.label }}</td>
|
|
||||||
|
|
||||||
<td>{{ operation.value | currency:'EUR':true }}</td>
|
|
||||||
|
|
||||||
<td [class.text-warning]="operation.balance < 0"
|
|
||||||
[class.text-danger]="operation.balance < account.authorized_overdraft">
|
|
||||||
{{ operation.balance | currency:'EUR':true }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{{ operation.category }}</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<!-- Edit operation, for non-canceled operation. -->
|
|
||||||
<button type="button" class="btn btn-success"
|
|
||||||
*ngIf="!operation.canceled"
|
|
||||||
(click)="modify(operation)" title="edit">
|
|
||||||
<span class="fa fa-pencil-square-o"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Toggle pointed operation, for non-canceled operations. -->
|
|
||||||
<button type="button" class="btn btn-secondary"
|
|
||||||
*ngIf="!operation.canceled"
|
|
||||||
(click)="togglePointed(operation)"
|
|
||||||
[class.active]="operation.pointed" title="point">
|
|
||||||
<span class="fa" [class.fa-check-square-o]="operation.pointed"
|
|
||||||
[class.fa-square-o]="!operation.pointed"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Toggle canceled operation. -->
|
|
||||||
<button type="button" class="btn btn-warning"
|
|
||||||
(click)="toggleCanceled(operation)"
|
|
||||||
*ngIf="operation.scheduled_operation_id"
|
|
||||||
[class.active]="operation.canceled" title="cancel">
|
|
||||||
<span class="fa fa-remove"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Delete operation, with confirm. -->
|
|
||||||
<button type="button" class="btn btn-danger"
|
|
||||||
(click)="confirmDelete(operation)"
|
|
||||||
*ngIf="operation.id && !operation.scheduled_operation_id">
|
|
||||||
<span class="fa fa-trash-o"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
`
|
|
||||||
})
|
})
|
||||||
export class OperationRowComponent {
|
export class OperationRowComponent {
|
||||||
@Input('operation-row') operation: Operation;
|
@Input('operation-row') operation: Operation = new Operation();
|
||||||
@Input() account: Account;
|
@Input() account: Account = new Account();
|
||||||
@Output() needsReload: EventEmitter<void> = new EventEmitter<void>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private operationService: OperationService,
|
private operationService: OperationService,
|
||||||
private toastrService: ToastrService,
|
private toastrService: ToastrService,
|
||||||
|
private logger: Logger,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
togglePointed(operation, rowform) {
|
togglePointed(operation, rowform) {
|
||||||
operation.pointed = !operation.pointed;
|
operation.pointed = !operation.pointed;
|
||||||
|
|
||||||
this.save(operation);
|
this.save(operation);
|
||||||
};
|
}
|
||||||
|
|
||||||
toggleCanceled(operation) {
|
toggleCanceled(operation) {
|
||||||
operation.canceled = !operation.canceled;
|
operation.canceled = !operation.canceled;
|
||||||
|
|
||||||
this.save(operation);
|
this.save(operation);
|
||||||
};
|
}
|
||||||
|
|
||||||
save(operation) {
|
save(operation) {
|
||||||
operation.confirmed = true;
|
operation.confirmed = true;
|
||||||
@ -101,7 +53,8 @@ export class OperationRowComponent {
|
|||||||
return this.operationService.update(operation).subscribe((operation) => {
|
return this.operationService.update(operation).subscribe((operation) => {
|
||||||
this.toastrService.success('Operation #' + operation.id + ' saved.');
|
this.toastrService.success('Operation #' + operation.id + ' saved.');
|
||||||
|
|
||||||
this.needsReload.emit();
|
this.logger.info('Reload route', this.router.url);
|
||||||
|
this.router.navigateByUrl(this.router.url);
|
||||||
}, (result) => {
|
}, (result) => {
|
||||||
this.toastrService.error(
|
this.toastrService.error(
|
||||||
'Error while saving operation: ' + result.message
|
'Error while saving operation: ' + result.message
|
||||||
@ -114,13 +67,11 @@ export class OperationRowComponent {
|
|||||||
|
|
||||||
modal.componentInstance.operation = this.operation;
|
modal.componentInstance.operation = this.operation;
|
||||||
|
|
||||||
var id = operation.id;
|
|
||||||
|
|
||||||
modal.result.then((operation: Operation) => {
|
modal.result.then((operation: Operation) => {
|
||||||
this.delete(operation);
|
this.delete(operation);
|
||||||
}, (reason) => {
|
}, () => {
|
||||||
})
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
delete(operation) {
|
delete(operation) {
|
||||||
var id = operation.id;
|
var id = operation.id;
|
||||||
@ -128,26 +79,13 @@ export class OperationRowComponent {
|
|||||||
return this.operationService.delete(operation).subscribe(() => {
|
return this.operationService.delete(operation).subscribe(() => {
|
||||||
this.toastrService.success('Operation #' + id + ' deleted.');
|
this.toastrService.success('Operation #' + id + ' deleted.');
|
||||||
|
|
||||||
this.needsReload.emit();
|
this.logger.info('Reload route', this.router.url);
|
||||||
|
this.router.navigateByUrl(this.router.url);
|
||||||
}, (result) => {
|
}, (result) => {
|
||||||
this.toastrService.error(
|
this.toastrService.error(
|
||||||
'An error occurred while trying to delete operation #' +
|
'An error occurred while trying to delete operation #' +
|
||||||
id + ':<br />' + result
|
id + ':<br />' + result
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
modify(operation) {
|
|
||||||
// FIXME Alexis Lahouze 2017-06-15 i18n
|
|
||||||
const modal = this.ngbModal.open(OperationEditModalComponent, {
|
|
||||||
size: 'lg'
|
|
||||||
});
|
|
||||||
|
|
||||||
modal.componentInstance.operation = operation;
|
|
||||||
|
|
||||||
modal.result.then((operation: Operation) => {
|
|
||||||
this.save(operation);
|
|
||||||
}, (reason) => {
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
3
src/polyfills.ts
Normal file
3
src/polyfills.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import 'core-js/es6';
|
||||||
|
import 'core-js/es7/reflect';
|
||||||
|
import 'zone.js/dist/zone';
|
@ -8,6 +8,9 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"lib": [ "es2015", "dom" ],
|
"lib": [ "es2015", "dom" ],
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"suppressImplicitAnyIndexErrors": true
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"typeRoots": [
|
||||||
|
"../node_modules/@types"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
13
src/vendor.ts
Normal file
13
src/vendor.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Angular
|
||||||
|
import '@angular/platform-browser';
|
||||||
|
import '@angular/platform-browser-dynamic';
|
||||||
|
import '@angular/core';
|
||||||
|
import '@angular/common';
|
||||||
|
import '@angular/http';
|
||||||
|
import '@angular/router';
|
||||||
|
|
||||||
|
// RxJS
|
||||||
|
import 'rxjs';
|
||||||
|
|
||||||
|
// Other vendors for example jQuery, Lodash or Bootstrap
|
||||||
|
// You can import js, ts, css, sass, ...
|
@ -1,136 +1 @@
|
|||||||
/* jshint esversion: 6 */
|
module.exports = require('./config/webpack.dev.js');
|
||||||
const path = require('path');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
context: path.resolve(__dirname, 'src'),
|
|
||||||
entry: {
|
|
||||||
"main": [
|
|
||||||
'./main.ts'
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
'./main.scss'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
devtool: 'source-map',
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.html'],
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [{
|
|
||||||
enforce: 'pre',
|
|
||||||
test: /webpack\.config\.js$/,
|
|
||||||
include: path.resolve(__dirname),
|
|
||||||
loader: 'eslint-loader',
|
|
||||||
options: {
|
|
||||||
useEslintrc: false,
|
|
||||||
emitWarning: true,
|
|
||||||
emitError: true,
|
|
||||||
failOnWarning: true,
|
|
||||||
failOnError: true,
|
|
||||||
baseConfig: 'webpack',
|
|
||||||
rules: {
|
|
||||||
indent: ['error', 4]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
// Javascript
|
|
||||||
enforce: 'pre',
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
//include: path.resolve(__dirname, 'src'),
|
|
||||||
loader: 'eslint-loader',
|
|
||||||
options: {
|
|
||||||
useEslintrc: false,
|
|
||||||
emitWarning: false,
|
|
||||||
emitError: true,
|
|
||||||
failOnWarning: false,
|
|
||||||
failOnError: true,
|
|
||||||
baseConfig: 'angular',
|
|
||||||
rules: {
|
|
||||||
indent: ['error', 4]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
'angular',
|
|
||||||
'html',
|
|
||||||
'security',
|
|
||||||
'this',
|
|
||||||
'jquery',
|
|
||||||
'promise'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'babel-loader'
|
|
||||||
}, {
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'ts-loader'
|
|
||||||
}, {
|
|
||||||
test: /\.html$/,
|
|
||||||
use: [
|
|
||||||
'ngtemplate-loader?relativeTo=/accountant-ui/src',
|
|
||||||
'html-loader'
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
test: /\.less$/,
|
|
||||||
use: [
|
|
||||||
'style-loader',
|
|
||||||
'css-loader',
|
|
||||||
'less-loader',
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
test: /\.css$/,
|
|
||||||
use: [
|
|
||||||
'style-loader',
|
|
||||||
'css-loader',
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [
|
|
||||||
'style-loader',
|
|
||||||
'css-loader',
|
|
||||||
'sass-loader',
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
|
|
||||||
loader: 'url-loader?limit=100000'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.ProvidePlugin({
|
|
||||||
"window.jQuery": "jquery"
|
|
||||||
}),
|
|
||||||
new webpack.ContextReplacementPlugin(
|
|
||||||
/angular(\\|\/)core(\\|\/)@angular/,
|
|
||||||
path.resolve(__dirname, './')
|
|
||||||
),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
title: 'Accountant',
|
|
||||||
template: 'index.ejs',
|
|
||||||
hash: false,
|
|
||||||
inject: true,
|
|
||||||
compile: true,
|
|
||||||
minify: false,
|
|
||||||
chunks: 'all'
|
|
||||||
})
|
|
||||||
],
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, 'build'),
|
|
||||||
filename: '[name].bundle.js',
|
|
||||||
chunkFilename: '[name].chunk.js'
|
|
||||||
//publicPath: 'js'
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://localhost:5000',
|
|
||||||
secure: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hot: true,
|
|
||||||
noInfo: false,
|
|
||||||
quiet: false,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
Reference in New Issue
Block a user