Move frontend in separate directory.

This commit is contained in:
Alexis Lahouze 2016-04-12 10:55:08 +02:00
parent 2f717393fc
commit 88f9163dc1
13 changed files with 1432 additions and 5 deletions

View File

@ -1,3 +1,3 @@
{
"directory": "accountant/frontend/static/bower_components"
"directory": "accountant-ui/bower_components"
}

View File

@ -9,8 +9,8 @@ module.exports = function(grunt) {
accountant: {
frontend: {
app: require('./bower.json'),
src: 'accountant/frontend/static',
dist: 'dist'
src: 'accountant-ui',
dist: 'accountant-ui_dist'
}
},

View File

@ -0,0 +1,7 @@
.italic {
font-style: italic
}
.stroke {
text-decoration: line-through
}

96
accountant-ui/index.html Normal file
View 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">&nbsp;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>

View 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
View 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);
}])
;

View 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);
});
}]);

View 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();
}]);

View 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>

View 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>

View 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&eacute; de l'op&eacute;ration</th>
<th class="col-md-1">Montant</th>
<th class="col-md-1">Solde</th>
<th class="col-md-2">Cat&eacute;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>

View 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&eacute;but</th>
<th class="col-md-1">Date de fin</th>
<th class="col-md-1">Jour</th>
<th class="col-md-1">Fr&eacute;q.</th>
<th>Libell&eacute; de l'op&eacute;ration</th>
<th class="col-md-1">Montant</th>
<th class="col-md-2">Cat&eacute;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>

View File

@ -6,8 +6,8 @@
],
"license": "AGPL",
"main": [
"accountant/frontend/static/index.html",
"accountant/frontend/static/js/app.js"
"accountant-ui/index.html",
"accountant-ui/js/app.js"
],
"ignore": [
"**/.*",