diff --git a/.bowerrc b/.bowerrc index d12db12..3bf1675 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "accountant/frontend/static/bower_components" + "directory": "accountant-ui/bower_components" } diff --git a/Gruntfile.js b/Gruntfile.js index 9d310ea..5bff1be 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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' } }, diff --git a/accountant-ui/css/main.css b/accountant-ui/css/main.css new file mode 100644 index 0000000..b71eb74 --- /dev/null +++ b/accountant-ui/css/main.css @@ -0,0 +1,7 @@ +.italic { + font-style: italic +} + +.stroke { + text-decoration: line-through +} diff --git a/accountant-ui/index.html b/accountant-ui/index.html new file mode 100644 index 0000000..0c36c2b --- /dev/null +++ b/accountant-ui/index.html @@ -0,0 +1,96 @@ + + + + + + + + + Accountant + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accountant-ui/js/accounts.js b/accountant-ui/js/accounts.js new file mode 100644 index 0000000..515211d --- /dev/null +++ b/accountant-ui/js/accounts.js @@ -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 . + */ +// 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(); +}]); diff --git a/accountant-ui/js/app.js b/accountant-ui/js/app.js new file mode 100644 index 0000000..628d9f2 --- /dev/null +++ b/accountant-ui/js/app.js @@ -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 . + */ +// 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); +}]) + +; diff --git a/accountant-ui/js/operations.js b/accountant-ui/js/operations.js new file mode 100644 index 0000000..72b2f4b --- /dev/null +++ b/accountant-ui/js/operations.js @@ -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 . + */ +// 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); + }); +}]); diff --git a/accountant-ui/js/scheduler.js b/accountant-ui/js/scheduler.js new file mode 100644 index 0000000..510dd92 --- /dev/null +++ b/accountant-ui/js/scheduler.js @@ -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 . + */ +// 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(); +}]); diff --git a/accountant-ui/views/accounts.html b/accountant-ui/views/accounts.html new file mode 100644 index 0000000..9920458 --- /dev/null +++ b/accountant-ui/views/accounts.html @@ -0,0 +1,114 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nom du compteSolde courantSolde pointéDécouvert autoriséActions
+ +
+ + {{ account.name }} + + + + {{ account.solds.current | currency : "€" }} + + + + {{ account.solds.pointed | currency : "€" }} + + + + {{ account.authorized_overdraft | currency : "€" }} + + +
+
+ + + + + + + + + + + + + + + + +
+
+
+
diff --git a/accountant-ui/views/login.tmpl.html b/accountant-ui/views/login.tmpl.html new file mode 100644 index 0000000..a381d06 --- /dev/null +++ b/accountant-ui/views/login.tmpl.html @@ -0,0 +1,17 @@ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ diff --git a/accountant-ui/views/operations.html b/accountant-ui/views/operations.html new file mode 100644 index 0000000..fdf1fdd --- /dev/null +++ b/accountant-ui/views/operations.html @@ -0,0 +1,152 @@ + + +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Date d'op.Libellé de l'opérationMontantSoldeCatégorieActions
+ +
+ + {{ operation.operation_date | date:"yyyy-MM-dd" }} + + + + {{ operation.label }} + + + + {{ operation.value | currency:"€" }} + + + {{ operation.sold | currency:"€" }} + + + {{ operation.category }} + + +
+
+ + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/accountant-ui/views/scheduler.html b/accountant-ui/views/scheduler.html new file mode 100644 index 0000000..2552387 --- /dev/null +++ b/accountant-ui/views/scheduler.html @@ -0,0 +1,142 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Date de débutDate de finJourFréq.Libellé de l'opérationMontantCatégorieActions
+ +
+ + {{ operation.start_date | date: "yyyy-MM-dd" }} + + + + {{ operation.stop_date | date: "yyyy-MM-dd" }} + + + + {{ operation.day }} + + + + {{ operation.frequency }} + + + + {{ operation.label }} + + + + {{ operation.value | currency : "€" }} + + + + {{ operation.category }} + + +
+
+ + + + + + + + + + + +
+
+
+
diff --git a/bower.json b/bower.json index 3127488..44eb582 100644 --- a/bower.json +++ b/bower.json @@ -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": [ "**/.*",