Move frontend in separate directory.
This commit is contained in:
parent
2f717393fc
commit
88f9163dc1
2
.bowerrc
2
.bowerrc
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"directory": "accountant/frontend/static/bower_components"
|
"directory": "accountant-ui/bower_components"
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ module.exports = function(grunt) {
|
|||||||
accountant: {
|
accountant: {
|
||||||
frontend: {
|
frontend: {
|
||||||
app: require('./bower.json'),
|
app: require('./bower.json'),
|
||||||
src: 'accountant/frontend/static',
|
src: 'accountant-ui',
|
||||||
dist: 'dist'
|
dist: 'accountant-ui_dist'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
7
accountant-ui/css/main.css
Normal file
7
accountant-ui/css/main.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.italic {
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
.stroke {
|
||||||
|
text-decoration: line-through
|
||||||
|
}
|
96
accountant-ui/index.html
Normal file
96
accountant-ui/index.html
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<!--
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2: -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<title>Accountant</title>
|
||||||
|
|
||||||
|
<!-- build:css(accountant-ui) css/vendor.css -->
|
||||||
|
<!-- bower:css -->
|
||||||
|
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
|
||||||
|
<link rel="stylesheet" href="bower_components/bootstrap-additions/dist/bootstrap-additions.css" />
|
||||||
|
<link rel="stylesheet" href="bower_components/angular-xeditable/dist/css/xeditable.css" />
|
||||||
|
<link rel="stylesheet" href="bower_components/angular-ui-notification/dist/angular-ui-notification.css" />
|
||||||
|
<link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />
|
||||||
|
<!-- endbower -->
|
||||||
|
<!-- endbuild -->
|
||||||
|
|
||||||
|
<!-- Custom styles -->
|
||||||
|
<!-- build:css(.tmp) css/main.css -->
|
||||||
|
<!-- include: "type": "css", "files": "css/*.css" -->
|
||||||
|
<link href="css/main.css" rel="stylesheet" type="text/css">
|
||||||
|
<!-- /include -->
|
||||||
|
<!-- endbuild -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<!-- htmllint attr-bans="false" -->
|
||||||
|
<body style="padding-bottom: 50px; padding-top: 70px" ng-app="accountant">
|
||||||
|
<!-- htmllint attr-bans="$previous" -->
|
||||||
|
<!-- Navbar -->
|
||||||
|
<nav class="navbar navbar-fixed-top navbar-inverse">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<!-- Brand -->
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" href="#/accounts"> Accountant</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid" ng-controller="MainController">
|
||||||
|
<div ng-view></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- build:js(accountant-ui) js/vendor.js -->
|
||||||
|
<!-- bower:js -->
|
||||||
|
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||||
|
<script src="bower_components/moment/moment.js"></script>
|
||||||
|
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
|
||||||
|
<script src="bower_components/angular/angular.js"></script>
|
||||||
|
<script src="bower_components/angular-resource/angular-resource.js"></script>
|
||||||
|
<script src="bower_components/angular-route/angular-route.js"></script>
|
||||||
|
<script src="bower_components/angular-strap/dist/angular-strap.js"></script>
|
||||||
|
<script src="bower_components/angular-strap/dist/angular-strap.tpl.js"></script>
|
||||||
|
<script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script>
|
||||||
|
<script src="bower_components/angular-ui-notification/dist/angular-ui-notification.js"></script>
|
||||||
|
<script src="bower_components/highcharts-ng/dist/highcharts-ng.js"></script>
|
||||||
|
<script src="bower_components/highstock-release/highstock.js"></script>
|
||||||
|
<script src="bower_components/highstock-release/highcharts-more.js"></script>
|
||||||
|
<script src="bower_components/highstock-release/modules/exporting.js"></script>
|
||||||
|
<script src="bower_components/angular-http-auth/src/http-auth-interceptor.js"></script>
|
||||||
|
<script src="bower_components/meanie-angular-storage/release/meanie-angular-storage.js"></script>
|
||||||
|
<script src="bower_components/bootbox/bootbox.js"></script>
|
||||||
|
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
|
||||||
|
<script src="bower_components/ngBootbox/dist/ngBootbox.js"></script>
|
||||||
|
<!-- endbower -->
|
||||||
|
<!-- endbuild -->
|
||||||
|
|
||||||
|
<!-- Custom Javascript libraries -->
|
||||||
|
<!-- build:js({.tmp,accountant-ui}) js/scripts.js -->
|
||||||
|
<!-- include: "type": "js", "files": "js/*.js" -->
|
||||||
|
<script src="js/accounts.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
<script src="js/operations.js"></script>
|
||||||
|
<script src="js/scheduler.js"></script>
|
||||||
|
<!-- /include -->
|
||||||
|
<!-- endbuild -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
159
accountant-ui/js/accounts.js
Normal file
159
accountant-ui/js/accounts.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('accountant.accounts', [
|
||||||
|
'ngResource',
|
||||||
|
'ui-notification',
|
||||||
|
'xeditable',
|
||||||
|
'ngBootbox'
|
||||||
|
])
|
||||||
|
|
||||||
|
.config(['$resourceProvider', function($resourceProvider) {
|
||||||
|
// Keep trailing slashes to avoid redirect by flask..
|
||||||
|
$resourceProvider.defaults.stripTrailingSlashes = false;
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('Account', ['$resource', function($resource) {
|
||||||
|
var Account = $resource(
|
||||||
|
'/api/account/:id', {
|
||||||
|
id: '@id'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Account.prototype.getSolds = function() {
|
||||||
|
var Solds = $resource('/api/account/:id/solds', {id: this.id});
|
||||||
|
|
||||||
|
this.solds = Solds.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
Account.prototype.getBalance = function(begin, end) {
|
||||||
|
var Balance = $resource(
|
||||||
|
'/api/account/:id/balance', {
|
||||||
|
id: this.id,
|
||||||
|
begin: begin.format('YYYY-MM-DD'),
|
||||||
|
end: end.format('YYYY-MM-DD')
|
||||||
|
});
|
||||||
|
|
||||||
|
this.balance = Balance.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
return Account;
|
||||||
|
}])
|
||||||
|
|
||||||
|
.controller(
|
||||||
|
'AccountController', [
|
||||||
|
'$scope', '$ngBootbox', 'Account', 'Notification',
|
||||||
|
function($scope, $ngBootbox, Account, Notification) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the class for an account current value compared to authorized
|
||||||
|
* overdraft.
|
||||||
|
*/
|
||||||
|
$scope.rowClass = function(account) {
|
||||||
|
if(!account || !account.authorized_overdraft || !account.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(account.current < account.authorized_overdraft) {
|
||||||
|
return 'danger';
|
||||||
|
} else if(account.current < 0) {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the class for a value compared to account authorized overdraft.
|
||||||
|
*/
|
||||||
|
$scope.valueClass = function(account, value) {
|
||||||
|
if(!account || !value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value < account.authorized_overdraft) {
|
||||||
|
return 'text-danger';
|
||||||
|
} else if(value < 0) {
|
||||||
|
return 'text-warning';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add an empty account.
|
||||||
|
*/
|
||||||
|
$scope.add = function() {
|
||||||
|
var account = new Account({
|
||||||
|
authorized_overdraft: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert account at the begining of the array.
|
||||||
|
$scope.accounts.splice(0, 0, account);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancel account edition. Remove it from array if a new one.
|
||||||
|
*/
|
||||||
|
$scope.cancelEdit = function(rowform, account, $index) {
|
||||||
|
if(!account.id) {
|
||||||
|
// Account not saved, just remove it from array.
|
||||||
|
$scope.accounts.splice($index, 1);
|
||||||
|
} else {
|
||||||
|
rowform.$cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save account.
|
||||||
|
*/
|
||||||
|
$scope.save = function(account) {
|
||||||
|
//var account = $scope.accounts[$index];
|
||||||
|
|
||||||
|
//account = angular.merge(account, $data);
|
||||||
|
|
||||||
|
return account.$save().then(function(data) {
|
||||||
|
Notification.success('Account #' + data.id + ' saved.');
|
||||||
|
|
||||||
|
// TODO Alexis Lahouze 2016-03-08 Update solds
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete an account.
|
||||||
|
*/
|
||||||
|
$scope.delete = function(account, $index) {
|
||||||
|
var id = account.id;
|
||||||
|
|
||||||
|
$ngBootbox.confirm(
|
||||||
|
'Voulez-vous supprimer le compte \'' + account.name + '\' ?',
|
||||||
|
function(result) {
|
||||||
|
if(result) {
|
||||||
|
account.$delete().then(function() {
|
||||||
|
Notification.success('Account #' + id + ' deleted.');
|
||||||
|
|
||||||
|
// Remove account from array.
|
||||||
|
$scope.accounts.splice($index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load accounts.
|
||||||
|
$scope.accounts = Account.query();
|
||||||
|
}]);
|
154
accountant-ui/js/app.js
Normal file
154
accountant-ui/js/app.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('accountant', [
|
||||||
|
'accountant.accounts',
|
||||||
|
'accountant.operations',
|
||||||
|
'accountant.scheduler',
|
||||||
|
'ngRoute',
|
||||||
|
'ngBootbox',
|
||||||
|
'http-auth-interceptor',
|
||||||
|
'Storage.Service'
|
||||||
|
])
|
||||||
|
|
||||||
|
.factory('sessionInjector', ['$storage', function($storage) {
|
||||||
|
var sessionInjector = {
|
||||||
|
request : function(config) {
|
||||||
|
var token = $storage.get('token');
|
||||||
|
|
||||||
|
if(token) {
|
||||||
|
var token_type = $storage.get('token_type');
|
||||||
|
var authorization = token_type + ' ' + token;
|
||||||
|
config.headers.Authorization = authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return sessionInjector;
|
||||||
|
}])
|
||||||
|
|
||||||
|
.config(['$httpProvider', function($httpProvider) {
|
||||||
|
// Define interceptors.
|
||||||
|
$httpProvider.interceptors.push('sessionInjector');
|
||||||
|
}])
|
||||||
|
|
||||||
|
.config(['$routeProvider', function($routeProvider) {
|
||||||
|
// Defining template and controller in function of route.
|
||||||
|
$routeProvider
|
||||||
|
.when('/account/:accountId/operations', {
|
||||||
|
templateUrl: 'views/operations.html',
|
||||||
|
controller: 'OperationController',
|
||||||
|
controllerAs: 'operationsCtrl'
|
||||||
|
})
|
||||||
|
.when('/account/:accountId/scheduler', {
|
||||||
|
templateUrl: 'views/scheduler.html',
|
||||||
|
controller: 'SchedulerController',
|
||||||
|
controllerAs: 'schedulerCtrl'
|
||||||
|
})
|
||||||
|
.when('/accounts', {
|
||||||
|
templateUrl: 'views/accounts.html',
|
||||||
|
controller: 'AccountController',
|
||||||
|
controllerAs: 'accountsCtrl'
|
||||||
|
})
|
||||||
|
.otherwise({
|
||||||
|
redirectTo: '/accounts'
|
||||||
|
});
|
||||||
|
|
||||||
|
}])
|
||||||
|
|
||||||
|
.config(['$storageProvider', function($storageProvider) {
|
||||||
|
// 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']);
|
||||||
|
}])
|
||||||
|
|
||||||
|
.run(function(editableOptions) {
|
||||||
|
editableOptions.theme = 'bs3'; // bootstrap3 theme. Can be also 'bs2', 'default'
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller('MainController', [
|
||||||
|
'$scope', '$rootScope', '$http', 'authService', '$storage', '$ngBootbox',
|
||||||
|
function($scope, $rootScope, $http, authService, $storage, $ngBootbox) {
|
||||||
|
$scope.dialogShown = false;
|
||||||
|
|
||||||
|
$scope.showLoginForm = function() {
|
||||||
|
// First, if there are registered credentials, use them
|
||||||
|
if($scope.dialogShown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.dialogShown = true;
|
||||||
|
|
||||||
|
$storage.clear();
|
||||||
|
|
||||||
|
$ngBootbox.customDialog({
|
||||||
|
title: 'Authentification requise',
|
||||||
|
templateUrl: 'views/login.tmpl.html',
|
||||||
|
buttons: {
|
||||||
|
login: {
|
||||||
|
label: 'Login',
|
||||||
|
className: 'btn-primary',
|
||||||
|
callback: function() {
|
||||||
|
$scope.dialogShown = false;
|
||||||
|
|
||||||
|
var email = $('#email').val();
|
||||||
|
var password = $('#password').val();
|
||||||
|
$http.post(
|
||||||
|
'/api/user/login',
|
||||||
|
{
|
||||||
|
'email': email,
|
||||||
|
'password': password
|
||||||
|
}
|
||||||
|
).success(function(result) {
|
||||||
|
// TODO Alexis Lahouze 2015-08-28 Handle callback.
|
||||||
|
// Call to /api/login to retrieve the token
|
||||||
|
$storage.set('token_type', result.token_type);
|
||||||
|
$storage.set('token', result.token);
|
||||||
|
$storage.set('expiration_date', result.expiration_date);
|
||||||
|
|
||||||
|
authService.loginConfirmed();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: 'Annuler',
|
||||||
|
className: 'btn-default',
|
||||||
|
callback: function() {
|
||||||
|
authService.loginCancelled(null, 'Login cancelled by user action.');
|
||||||
|
$scope.dialogShown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$rootScope.$on('event:auth-loginRequired', $scope.showLoginForm);
|
||||||
|
}])
|
||||||
|
|
||||||
|
;
|
466
accountant-ui/js/operations.js
Normal file
466
accountant-ui/js/operations.js
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('accountant.operations', [
|
||||||
|
'accountant.accounts',
|
||||||
|
'ngRoute',
|
||||||
|
'ngResource',
|
||||||
|
'ngBootbox',
|
||||||
|
'ui-notification',
|
||||||
|
'mgcrea.ngStrap',
|
||||||
|
'highcharts-ng',
|
||||||
|
])
|
||||||
|
|
||||||
|
.config(['$resourceProvider', function($resourceProvider) {
|
||||||
|
// Keep trailing slashes to avoid redirect by flask..
|
||||||
|
$resourceProvider.defaults.stripTrailingSlashes = false;
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('Operation', [ '$resource', function($resource) {
|
||||||
|
return $resource(
|
||||||
|
'/api/operation/:id', {
|
||||||
|
id: '@id'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('OHLC', [ '$resource', '$routeParams',
|
||||||
|
function($resource, $routeParams) {
|
||||||
|
return $resource(
|
||||||
|
'/api/account/:account_id/ohlc', {
|
||||||
|
account_id: $routeParams.accountId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('Category', [ '$resource', '$routeParams',
|
||||||
|
function($resource, $routeParams) {
|
||||||
|
return $resource(
|
||||||
|
'/api/account/:account_id/category', {
|
||||||
|
account_id: $routeParams.accountId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('Balance', [ '$resource', '$routeParams',
|
||||||
|
function($resource, $routeParams) {
|
||||||
|
return $resource(
|
||||||
|
'/api/account/:account_id/balance', {
|
||||||
|
account_id: $routeParams.accountId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Controller for category chart.
|
||||||
|
*/
|
||||||
|
.controller(
|
||||||
|
'CategoryChartController', [
|
||||||
|
'$rootScope', '$scope', '$http', 'Category', 'Balance',
|
||||||
|
function($rootScope, $scope, $http, Category, Balance) {
|
||||||
|
|
||||||
|
var colors = Highcharts.getOptions().colors;
|
||||||
|
$scope.revenueColor = colors[2];
|
||||||
|
$scope.expenseColor = colors[3];
|
||||||
|
|
||||||
|
// Configure pie chart for categories.
|
||||||
|
$scope.config = {
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
type: 'pie',
|
||||||
|
animation: {
|
||||||
|
duration: 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
pie: {
|
||||||
|
startAngle: -90
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
allowPointSelect: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
valueDecimals: 2,
|
||||||
|
valueSuffix: '€'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
title: {
|
||||||
|
text: 'Categories'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: 'Répartition dépenses/recettes'
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: 'Value',
|
||||||
|
data: [],
|
||||||
|
innerSize: '33%',
|
||||||
|
size: '60%',
|
||||||
|
dataLabels: {
|
||||||
|
formatter: function() {
|
||||||
|
return this.point.name;
|
||||||
|
},
|
||||||
|
distance: -40
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'Value',
|
||||||
|
data: [],
|
||||||
|
innerSize: '66%',
|
||||||
|
size: '60%',
|
||||||
|
dataLabels: {
|
||||||
|
formatter: function() {
|
||||||
|
return this.point.name !== null && this.percentage >= 2.5 ? this.point.name : null;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.brightenColor = function(color) {
|
||||||
|
var brightness = 0.2;
|
||||||
|
|
||||||
|
return Highcharts.Color(color).brighten(brightness).get();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load categories, mainly to populate the pie chart.
|
||||||
|
$scope.load = function(begin, end) {
|
||||||
|
$scope.config.loading = true;
|
||||||
|
|
||||||
|
Category.query({
|
||||||
|
begin: begin.format('YYYY-MM-DD'),
|
||||||
|
end: end.format('YYYY-MM-DD')
|
||||||
|
}, function(data) {
|
||||||
|
var expenses = [], revenues = [];
|
||||||
|
|
||||||
|
var expenseColor = $scope.brightenColor($scope.expenseColor);
|
||||||
|
var revenueColor = $scope.brightenColor($scope.revenueColor);
|
||||||
|
|
||||||
|
angular.forEach(angular.fromJson(data), function(category) {
|
||||||
|
expenses.push({
|
||||||
|
name: category.category,
|
||||||
|
y: -category.expenses,
|
||||||
|
color: expenseColor
|
||||||
|
});
|
||||||
|
|
||||||
|
revenues.push({
|
||||||
|
name: category.category,
|
||||||
|
y: category.revenues,
|
||||||
|
color: revenueColor
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note: expenses and revenues must be in the same order than in series[0].
|
||||||
|
$scope.config.series[1].data = revenues.concat(expenses);
|
||||||
|
|
||||||
|
$scope.config.loading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get account balance.
|
||||||
|
*/
|
||||||
|
$scope.getBalance = function(begin, end) {
|
||||||
|
Balance.get({
|
||||||
|
begin: begin.format('YYYY-MM-DD'),
|
||||||
|
end: end.format('YYYY-MM-DD')
|
||||||
|
}, function(balance) {
|
||||||
|
// Update pie chart subtitle with Balance.
|
||||||
|
$scope.config.subtitle = {
|
||||||
|
text: 'Balance: ' + balance.balance
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.config.series[0].data = [{
|
||||||
|
name: 'Revenues',
|
||||||
|
y: balance.revenues,
|
||||||
|
color: $scope.revenueColor
|
||||||
|
}, {
|
||||||
|
name: 'Expenses',
|
||||||
|
y: -balance.expenses,
|
||||||
|
color: $scope.expenseColor,
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reload categories and account status on range selection.
|
||||||
|
$rootScope.$on('rangeSelectedEvent', function(e, args) {
|
||||||
|
$scope.load(args.begin, args.end);
|
||||||
|
$scope.getBalance(args.begin, args.end);
|
||||||
|
});
|
||||||
|
}])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Controller for the sold chart.
|
||||||
|
*/
|
||||||
|
.controller(
|
||||||
|
'SoldChartController', [
|
||||||
|
'$rootScope', '$scope', '$http', 'OHLC',
|
||||||
|
function($rootScope, $scope, $http, OHLC) {
|
||||||
|
// Configure chart for operations.
|
||||||
|
$scope.config = {
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
zoomType: 'x'
|
||||||
|
},
|
||||||
|
rangeSelector: {
|
||||||
|
buttons: [{
|
||||||
|
type: 'month',
|
||||||
|
count: 1,
|
||||||
|
text: '1m'
|
||||||
|
}, {
|
||||||
|
type: 'month',
|
||||||
|
count: 3,
|
||||||
|
text: '3m'
|
||||||
|
}, {
|
||||||
|
type: 'month',
|
||||||
|
count: 6,
|
||||||
|
text: '6m'
|
||||||
|
}, {
|
||||||
|
type: 'year',
|
||||||
|
count: 1,
|
||||||
|
text: '1y'
|
||||||
|
}, {
|
||||||
|
type: 'all',
|
||||||
|
text: 'All'
|
||||||
|
}],
|
||||||
|
selected: 0,
|
||||||
|
},
|
||||||
|
navigator: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
crosshairs: true,
|
||||||
|
shared: true,
|
||||||
|
valueDecimals: 2,
|
||||||
|
valueSuffix: '€'
|
||||||
|
},
|
||||||
|
scrollbar: {
|
||||||
|
liveRedraw: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
type: 'ohlc',
|
||||||
|
name: 'Sold',
|
||||||
|
data: [],
|
||||||
|
dataGrouping : {
|
||||||
|
units : [[
|
||||||
|
'week', // unit name
|
||||||
|
[1] // allowed multiples
|
||||||
|
], [
|
||||||
|
'month',
|
||||||
|
[1, 2, 3, 4, 6]
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
title: {
|
||||||
|
text: 'Sold evolution'
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
dateTimeLabelFormats: {
|
||||||
|
month: '%e. %b',
|
||||||
|
year: '%Y'
|
||||||
|
},
|
||||||
|
minRange: 3600 * 1000 * 24 * 14, // 2 weeks
|
||||||
|
events: {
|
||||||
|
afterSetExtremes: function(e) {
|
||||||
|
$scope.$emit('rangeSelectedEvent', {
|
||||||
|
begin: moment.utc(e.min), end: moment.utc(e.max)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentMin: moment.utc().startOf('month'),
|
||||||
|
currentMax: moment.utc().endOf('month')
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
plotLines: [{
|
||||||
|
color: 'orange',
|
||||||
|
width: 2,
|
||||||
|
value: 0.0
|
||||||
|
}, {
|
||||||
|
color: 'red',
|
||||||
|
width: 2,
|
||||||
|
value: 0.0
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
useHighStocks: true
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.loadSolds = function() {
|
||||||
|
$scope.config.loading = true;
|
||||||
|
|
||||||
|
OHLC.query({}, function(data) {
|
||||||
|
$scope.config.series[0].data = [];
|
||||||
|
|
||||||
|
angular.forEach(data, function(operation) {
|
||||||
|
$scope.config.series[0].data.push([
|
||||||
|
moment.utc(operation.operation_date).valueOf(),
|
||||||
|
operation.open, operation.high, operation.low, operation.close
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$emit('rangeSelectedEvent', {
|
||||||
|
begin: $scope.config.xAxis.currentMin,
|
||||||
|
end: $scope.config.xAxis.currentMax
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.config.loading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reload solds when an operation is saved.
|
||||||
|
$rootScope.$on('operationSavedEvent', function() {
|
||||||
|
$scope.loadSolds();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reload solds when an operation is deleted.
|
||||||
|
$rootScope.$on('operationDeletedEvent', function() {
|
||||||
|
$scope.loadSolds();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update authorized overdraft on account loading.
|
||||||
|
$rootScope.$on('accountLoadedEvent', function(e, account) {
|
||||||
|
$scope.config.yAxis.plotLines[1].value = account.authorized_overdraft;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select beginning and end of month.
|
||||||
|
$scope.loadSolds();
|
||||||
|
}])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Controller for the operations.
|
||||||
|
*/
|
||||||
|
.controller(
|
||||||
|
'OperationController', [
|
||||||
|
'$scope', '$rootScope', '$routeParams', '$ngBootbox', 'Notification', 'Account', 'Operation',
|
||||||
|
function($scope, $rootScope, $routeParams, $ngBootbox, Notification, Account, Operation) {
|
||||||
|
// List of operations.
|
||||||
|
$scope.operations = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add an empty operation.
|
||||||
|
*/
|
||||||
|
$scope.add = function() {
|
||||||
|
var operation = new Operation({
|
||||||
|
account_id: $routeParams.accountId
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.operations.splice(0, 0, operation);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load operations.
|
||||||
|
*/
|
||||||
|
$scope.load = function(begin, end) {
|
||||||
|
$scope.operations = Operation.query({
|
||||||
|
account_id: $routeParams.accountId,
|
||||||
|
begin: begin.format('YYYY-MM-DD'),
|
||||||
|
end: end.format('YYYY-MM-DD')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancel edition.
|
||||||
|
*/
|
||||||
|
$scope.cancelEdit = function(operation, rowform, $index) {
|
||||||
|
if(!operation.id) {
|
||||||
|
$scope.operations.splice($index, 1);
|
||||||
|
} else {
|
||||||
|
rowform.$cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Toggle pointed indicator for an operation.
|
||||||
|
*/
|
||||||
|
$scope.togglePointed = function(operation, rowform) {
|
||||||
|
operation.pointed = !operation.pointed;
|
||||||
|
|
||||||
|
// Save operation if not editing it.
|
||||||
|
if(!rowform.$visible) {
|
||||||
|
$scope.save(operation);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Toggle cancel indicator for an operation.
|
||||||
|
*/
|
||||||
|
$scope.toggleCanceled = function(operation) {
|
||||||
|
operation.canceled = !operation.canceled;
|
||||||
|
|
||||||
|
$scope.save(operation);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save an operation and emit operationSavedEvent.
|
||||||
|
*/
|
||||||
|
$scope.save = function($data, $index) {
|
||||||
|
// Check if $data is already a resource.
|
||||||
|
var operation;
|
||||||
|
|
||||||
|
if($data.$save) {
|
||||||
|
operation = $data;
|
||||||
|
} else {
|
||||||
|
operation = $scope.operations[$index];
|
||||||
|
operation = angular.merge(operation, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.confirmed = true;
|
||||||
|
|
||||||
|
return operation.$save().then(function(data) {
|
||||||
|
Notification.success('Operation #' + data.id + ' saved.');
|
||||||
|
|
||||||
|
$scope.$emit('operationSavedEvent', data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete an operation and emit operationDeletedEvent.
|
||||||
|
*/
|
||||||
|
$scope.delete = function(operation, $index) {
|
||||||
|
var id = operation.id;
|
||||||
|
|
||||||
|
$ngBootbox.confirm(
|
||||||
|
'Voulez-vous supprimer l\'opération \\\'' + operation.label + '\\\' ?',
|
||||||
|
function(result) {
|
||||||
|
if(result) {
|
||||||
|
operation.$delete().then(function() {
|
||||||
|
Notification.success('Operation #' + id + ' deleted.');
|
||||||
|
|
||||||
|
// Remove operation from array.
|
||||||
|
$scope.operation.splice($index, 1);
|
||||||
|
|
||||||
|
$scope.$emit('operationDeletedEvent', operation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.account = Account.get({
|
||||||
|
id: $routeParams.accountId
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reload operations on rangeSelectedEvent.
|
||||||
|
*/
|
||||||
|
$rootScope.$on('rangeSelectedEvent', function(e, args) {
|
||||||
|
$scope.load(args.begin, args.end);
|
||||||
|
});
|
||||||
|
}]);
|
120
accountant-ui/js/scheduler.js
Normal file
120
accountant-ui/js/scheduler.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// vim: set tw=80 ts=2 sw=2 sts=2:
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('accountant.scheduler', [
|
||||||
|
'ngRoute',
|
||||||
|
'ngBootbox',
|
||||||
|
'ui-notification',
|
||||||
|
'mgcrea.ngStrap'
|
||||||
|
])
|
||||||
|
|
||||||
|
.config(['$resourceProvider', function($resourceProvider) {
|
||||||
|
// Keep trailing slashes to avoid redirect by flask..
|
||||||
|
$resourceProvider.defaults.stripTrailingSlashes = false;
|
||||||
|
}])
|
||||||
|
|
||||||
|
.factory('ScheduledOperation', ['$resource', function($resource) {
|
||||||
|
return $resource(
|
||||||
|
'/api/scheduled_operation/:id', {
|
||||||
|
id: '@id'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}])
|
||||||
|
|
||||||
|
.controller(
|
||||||
|
'SchedulerController', [
|
||||||
|
'$scope', '$rootScope', '$routeParams', '$ngBootbox', 'Notification', 'ScheduledOperation',
|
||||||
|
function($scope, $rootScope, $routeParams, $ngBootbox, Notification, ScheduledOperation) {
|
||||||
|
// Operation store.
|
||||||
|
$scope.operations = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a new operation at the beginning of th array.
|
||||||
|
*/
|
||||||
|
$scope.add = function() {
|
||||||
|
var operation = new ScheduledOperation({
|
||||||
|
account_id: $routeParams.accountId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert new operation at the beginning of the array.
|
||||||
|
$scope.operations.splice(0, 0, operation);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load operations.
|
||||||
|
*/
|
||||||
|
$scope.load = function() {
|
||||||
|
$scope.operations = ScheduledOperation.query({
|
||||||
|
account_id: $routeParams.accountId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save operation.
|
||||||
|
*/
|
||||||
|
$scope.save = function($data, $index) {
|
||||||
|
var operation;
|
||||||
|
|
||||||
|
if($data.$save) {
|
||||||
|
operation = $data;
|
||||||
|
} else {
|
||||||
|
operation = $scope.operations[$index];
|
||||||
|
operation = angular.merge(operation, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation.$save().then(function(data) {
|
||||||
|
Notification.success('Operation #' + data.id + ' saved.');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancel operation edition. Delete if new.
|
||||||
|
*/
|
||||||
|
$scope.cancelEdit = function(operation, rowform, $index) {
|
||||||
|
if(!operation.id) {
|
||||||
|
$scope.operations.splice($index, 1);
|
||||||
|
} else {
|
||||||
|
rowform.$cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete operation.
|
||||||
|
*/
|
||||||
|
$scope.delete = function(operation, $index) {
|
||||||
|
var id = operation.id;
|
||||||
|
|
||||||
|
$ngBootbox.confirm(
|
||||||
|
'Voulez-vous supprimer l\'operation planifiée \\\'' + operation.label + '\\\' ?',
|
||||||
|
function(result) {
|
||||||
|
if(result) {
|
||||||
|
operation.$delete().then(function() {
|
||||||
|
Notification.success('Operation #' + id + ' deleted.');
|
||||||
|
|
||||||
|
// Remove account from array.
|
||||||
|
$scope.operations.splice($index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load operations on controller initialization.
|
||||||
|
$scope.load();
|
||||||
|
}]);
|
114
accountant-ui/views/accounts.html
Normal file
114
accountant-ui/views/accounts.html
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<!--
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2: -->
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nom du compte</th>
|
||||||
|
<th class="col-md-1">Solde courant</th>
|
||||||
|
<th class="col-md-1">Solde pointé</th>
|
||||||
|
<th class="col-md-1">Découvert autorisé</th>
|
||||||
|
<th class="col-md-1">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">
|
||||||
|
<button class="btn btn-success btn-success"
|
||||||
|
ng-click="add()">Ajouter</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="{{ account.id }}"
|
||||||
|
class="form-inline" ng-class="rowClass(account)"
|
||||||
|
ng-repeat="account in accounts | orderBy:'name'" ng-init="account.getSolds()">
|
||||||
|
<td>
|
||||||
|
<span editable-text="account.name"
|
||||||
|
e-placeholder="Nom du compte"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-name="name" e-form="rowform" e-required>
|
||||||
|
<a href="#/account/{{ account.id }}/operations">{{ account.name }}</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span ng-class="valueClass(account, account.solds.current)">
|
||||||
|
{{ account.solds.current | currency : "€" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span ng-class="valueClass(account, account.solds.pointed)">
|
||||||
|
{{ account.solds.pointed | currency : "€" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-number="account.authorized_overdraft"
|
||||||
|
e-max="0"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-name="authorized_overdraft" e-form="rowform">
|
||||||
|
{{ account.authorized_overdraft | currency : "€" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<form editable-form name="rowform"
|
||||||
|
onaftersave="save(account)" shown="!account.id">
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<!-- Edit account. -->
|
||||||
|
<button type="button" class="btn btn-success"
|
||||||
|
ng-if="!rowform.$visible"
|
||||||
|
ng-click="rowform.$show()">
|
||||||
|
<span class="fa fa-pencil-square-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Save account. -->
|
||||||
|
<button type="submit" class="btn btn-success"
|
||||||
|
ng-if="rowform.$visible">
|
||||||
|
<span class="fa fa-floppy-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Cancel account edition. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="rowform.$visible"
|
||||||
|
ng-click="cancelEdit(rowform, account, $index)">
|
||||||
|
<span class="fa fa-times"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Delete account, with confirm. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="account.id"
|
||||||
|
ng-click="delete(account, $index)">
|
||||||
|
<span class="fa fa-trash-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Open account scheduler. -->
|
||||||
|
<a class="btn btn-default"
|
||||||
|
ng-if="account.id"
|
||||||
|
href="#/account/{{ account.id }}/scheduler">
|
||||||
|
<span class="fa fa-clock-o"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
17
accountant-ui/views/login.tmpl.html
Normal file
17
accountant-ui/views/login.tmpl.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2: -->
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
|
152
accountant-ui/views/operations.html
Normal file
152
accountant-ui/views/operations.html
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<!--
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2: -->
|
||||||
|
<div>
|
||||||
|
<!-- Chart row -->
|
||||||
|
<div class="row">
|
||||||
|
<!-- Sold evolution chart placeholder -->
|
||||||
|
<div class="col-md-8" ng-controller="SoldChartController">
|
||||||
|
<highchart id="sold-chart" config="config"></highchart>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category piechart -->
|
||||||
|
<div class="col-md-4" ng-controller="CategoryChartController">
|
||||||
|
<highchart id="categories-chart" config="config"></highchart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-md-1">Date d'op.</th>
|
||||||
|
<th>Libellé de l'opération</th>
|
||||||
|
<th class="col-md-1">Montant</th>
|
||||||
|
<th class="col-md-1">Solde</th>
|
||||||
|
<th class="col-md-2">Catégorie</th>
|
||||||
|
<th class="col-md-2">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">
|
||||||
|
<button class="btn btn-success" ng-click="add()">
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="{{ operation.id }}" class="form-inline"
|
||||||
|
ng-class="{stroke: operation.canceled, italic: !operation.confirmed, warning: operation.sold < 0, danger: operation.sold < account.authorized_overdraft}"
|
||||||
|
ng-repeat="operation in operations | orderBy:['-operation_date', '-value', 'label']">
|
||||||
|
<td>
|
||||||
|
<span editable-text="operation.operation_date"
|
||||||
|
e-data-date-format="yyyy-MM-dd" e-bs-datepicker
|
||||||
|
e-timezone="UTC"
|
||||||
|
e-class="input-sm" e-style="width: 100%"
|
||||||
|
e-name="operation_date" e-form="rowform" e-required>
|
||||||
|
{{ operation.operation_date | date:"yyyy-MM-dd" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-text="operation.label"
|
||||||
|
e-placeholder="Libellé de l'opération"
|
||||||
|
e-class="input-sm" e-style="width: 100%"
|
||||||
|
e-name="label" e-form="rowform" e-required>
|
||||||
|
{{ operation.label }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-number="operation.value"
|
||||||
|
e-class="input-sm" e-style="width: 100%"
|
||||||
|
e-name="value" e-form="rowform" e-required>
|
||||||
|
{{ operation.value | currency:"€" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td ng-class="{'text-warning': operation.sold < 0, 'text-danger': operation.sold < account.authorized_overdraft}">
|
||||||
|
{{ operation.sold | currency:"€" }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-text="operation.category"
|
||||||
|
e-placeholder="Catégorie"
|
||||||
|
e-class="input-sm" e-style="width: 100%"
|
||||||
|
e-name="category" e-form="rowform" e-required>
|
||||||
|
{{ operation.category }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<form editable-form name="rowform"
|
||||||
|
onbeforesave="save($data, $index)"
|
||||||
|
shown="!operation.id">
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<!-- Save current operation, for editing and non-confirmed non-canceled operation. -->
|
||||||
|
<button type="submit" class="btn btn-success"
|
||||||
|
ng-if="!operation.canceled && (!operation.confirmed || rowform.$visible)"
|
||||||
|
title="Save">
|
||||||
|
<span class="fa fa-floppy-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Edit operation, for non-canceled and non-editing operation -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="!operation.canceled && !rowform.$visible"
|
||||||
|
ng-click="rowform.$show()" title="edit">
|
||||||
|
<span class="fa fa-pencil-square-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Cancel edition, for editing operation. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="rowform.$visible"
|
||||||
|
ng-click="cancelEdit(operation, rowform)">
|
||||||
|
<span class="fa fa-times"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Toggle pointed operation, for non-canceled operations. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="!operation.canceled"
|
||||||
|
ng-click="togglePointed(operation, rowform)"
|
||||||
|
ng-class="{active: operation.pointed}" title="point">
|
||||||
|
<span ng-class="{'fa fa-check-square-o': operation.pointed, 'fa fa-square-o': !operation.pointed}"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Toggle canceled operation, for non-editing operations. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-click="toggleCanceled(operation)"
|
||||||
|
ng-if="operation.scheduled_operation_id && !rowform.$visible"
|
||||||
|
ng-class="{active: operation.canceled}" title="cancel">
|
||||||
|
<span class="fa fa-remove"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Delete operation, with confirm. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="operation.id && !operation.scheduled_operation_id"
|
||||||
|
ng-click="delete(operation, $index)">
|
||||||
|
<span class="fa fa-trash-o"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
142
accountant-ui/views/scheduler.html
Normal file
142
accountant-ui/views/scheduler.html
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<!--
|
||||||
|
This file is part of Accountant.
|
||||||
|
|
||||||
|
Accountant is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Accountant is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with Accountant. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<!-- vim: set tw=80 ts=2 sw=2 sts=2: -->
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-md-1">Date de début</th>
|
||||||
|
<th class="col-md-1">Date de fin</th>
|
||||||
|
<th class="col-md-1">Jour</th>
|
||||||
|
<th class="col-md-1">Fréq.</th>
|
||||||
|
<th>Libellé de l'opération</th>
|
||||||
|
<th class="col-md-1">Montant</th>
|
||||||
|
<th class="col-md-2">Catégorie</th>
|
||||||
|
<th class="col-md-1">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8">
|
||||||
|
<button class="btn btn-success" ng-click="add()">
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr id="{{ operation.id }}" class="form-inline"
|
||||||
|
ng-repeat="operation in operations">
|
||||||
|
<td class="col-md-1">
|
||||||
|
<span editable-text="operation.start_date"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-bs-datepicker e-data-date-format="yyyy-MM-dd"
|
||||||
|
e-name="start_date" e-form="rowform" e-required>
|
||||||
|
{{ operation.start_date | date: "yyyy-MM-dd" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-text="operation.stop_date"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-bs-datepicker e-data-date-format="yyyy-MM-dd"
|
||||||
|
e-name="stop_date" e-form="rowform" e-required>
|
||||||
|
{{ operation.stop_date | date: "yyyy-MM-dd" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-number="operation.day"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-name="day" e-form="rowform" e-required>
|
||||||
|
{{ operation.day }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-number="operation.frequency"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-name="frequency" e-form="rowform" e-required>
|
||||||
|
{{ operation.frequency }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-text="operation.label"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-placeholder="Libellé de l'opération"
|
||||||
|
e-name="label" e-form="rowform" e-required>
|
||||||
|
{{ operation.label }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-number="operation.value"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-name="value" e-form="rowform" e-required>
|
||||||
|
{{ operation.value | currency : "€" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span editable-text="operation.category"
|
||||||
|
e-style="width: 100%"
|
||||||
|
e-name="category" e-form="rowform">
|
||||||
|
{{ operation.category }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<form editable-form name="rowform"
|
||||||
|
onbeforesave="save($data, $index)"
|
||||||
|
shown="!operation.id">
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<!-- Save current operation -->
|
||||||
|
<button type="submit" class="btn btn-success"
|
||||||
|
ng-if="rowform.$visible" title="Save">
|
||||||
|
<span class="fa fa-floppy-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Edit operation. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="!rowform.$visible"
|
||||||
|
ng-click="rowform.$show()" title="edit">
|
||||||
|
<span class="fa fa-pencil-square-o"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Cancel edit. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="rowform.$visible"
|
||||||
|
ng-click="cancelEdit(operation, rowform, $index)"
|
||||||
|
title="Cancel">
|
||||||
|
<span class="fa fa-times"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Remove operation. -->
|
||||||
|
<button type="button" class="btn btn-default"
|
||||||
|
ng-if="operation.id"
|
||||||
|
ng-click="delete(operation, $index)"
|
||||||
|
title="remove">
|
||||||
|
<span class="fa fa-trash"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
@ -6,8 +6,8 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"main": [
|
"main": [
|
||||||
"accountant/frontend/static/index.html",
|
"accountant-ui/index.html",
|
||||||
"accountant/frontend/static/js/app.js"
|
"accountant-ui/js/app.js"
|
||||||
],
|
],
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"**/.*",
|
"**/.*",
|
||||||
|
Loading…
Reference in New Issue
Block a user