diff --git a/accountant/frontend/static/js/entries.js b/accountant/frontend/static/js/entries.js index 1aae133..fe7aab6 100644 --- a/accountant/frontend/static/js/entries.js +++ b/accountant/frontend/static/js/entries.js @@ -24,21 +24,298 @@ accountantApp ); }]) +.controller( + "CategoryChartController", [ + "$rootScope", "$scope", "$http", "$routeParams", + function($rootScope, $scope, $http, $routeParams) { + + 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) { + 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; + + $http.get("/api/categories", { + params: { + account: $routeParams.accountId, + begin: begin.format('YYYY-MM-DD'), + end: end.format('YYYY-MM-DD') + } + }).success(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 status. + */ + $scope.getAccountStatus = function(begin, end) { + $http.get("/api/accounts/" + $routeParams.accountId, { + params: { + begin: begin.format('YYYY-MM-DD'), + end: end.format('YYYY-MM-DD') + } + }).success(function(account) { + // Emit accountLoadedEvent. + $scope.$emit("accountLoadedEvent", account); + + // Update pie chart subtitle with Balance. + $scope.config.subtitle = { + text: "Balance: " + account.balance + }; + + $scope.config.series[0].data = [{ + name: "Revenues", + y: account.revenues, + color: $scope.revenueColor + }, { + name: "Expenses", + y: -account.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.getAccountStatus(args.begin, args.end); + }); +}]) + +.controller( + "SoldChartController", [ + "$rootScope", "$scope", "$http", "$routeParams", + function($rootScope, $scope, $http, $routeParams) { + + $scope.setExtremes = function(e) { + begin = moment.utc(e.min); + end = moment.utc(e.max); + + $scope.selectRange(begin, end); + }; + + // Configure chart for entries. + $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: $scope.setExtremes + }, + 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.selectRange = function(begin, end) { + $scope.$emit("rangeSelectedEvent", {begin: begin, end: end}); + }; + + $scope.loadSolds = function() { + $scope.config.loading = true; + + $http.get("/api/solds", { + params: { + account: $routeParams.accountId, + } + }).success(function(data) { + $scope.config.series[0].data = []; + + angular.forEach(data, function(entry) { + $scope.config.series[0].data.push([ + moment.utc(entry.operation_date).valueOf(), + entry.open, entry.high, entry.low, entry.close + ]); + }); + + $scope.$emit("rangeSelectedEvent", { + begin: $scope.config.xAxis.currentMin, + end: $scope.config.xAxis.currentMax + }); + + $scope.config.loading = false; + }); + }; + + // Reload solds when an entry is saved. + $rootScope.$on("entrySavedEvent", function(e, entry) { + $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( "EntryController", [ "$scope", "$http", "$rootScope", "$filter", "$routeParams", "notificationService", "Entries", function($scope, $http, $rootScope, $filter, $routeParams, notificationService, Entries) { - // Range for entries. - $scope.begin = moment.utc().startOf('month'); - $scope.end = moment.utc().endOf('month'); - // Entry store and selection $scope.entries = []; - $scope.categories = []; - - $scope.account = null; - // Function to reset the new entry. $scope.addEntry = function() { if(!$scope.inserted) { @@ -48,291 +325,16 @@ accountantApp } }; - $scope.setExtremes = function(e) { - begin = moment.utc(e.min); - end = moment.utc(e.max); - - $scope.selectRange(begin, end); - }; - - $scope.selectRange = function(begin, end) { - $scope.begin = begin; - $scope.end = end; - - $scope.$emit("rangeSelectedEvent", {begin: begin, end: end}); - }; - - // Configure pie chart for categories. - $scope.categoriesChartConfig = { - 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; - }, - } - }] - }; - - // Configure chart for entries. - $scope.soldChartConfig = { - 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: $scope.setExtremes - }, - currentMin: $scope.begin, - currentMax: $scope.end - }, - yAxis: { - plotLines: [{ - color: "orange", - width: 2, - value: 0.0 - }, { - color: "red", - width: 2, - value: 0.0 - }] - }, - useHighStocks: true - }; - - // Load categories, mainly to populate the pie chart. - $scope.loadCategories = function() { - $scope.categoriesChartConfig.loading = true; - - $http.get("/api/categories", { - params: { - account: $scope.account.id, - begin: $scope.begin.format('YYYY-MM-DD'), - end: $scope.end.format('YYYY-MM-DD') - } - }).success(function(data) { - var expenses = [], revenues = []; - var colors = Highcharts.getOptions().colors; - - var config = $scope.categoriesChartConfig; - - angular.forEach(angular.fromJson(data), function(category) { - brightness = 0.2; - - expenses.push({ - name: category.category, - y: -category.expenses, - color: Highcharts.Color(config.series[0].data[1].color).brighten(brightness).get() - }); - - revenues.push({ - name: category.category, - y: category.revenues, - color: Highcharts.Color(config.series[0].data[0].color).brighten(brightness).get() - }); - }); - - // Note: expenses and revenues must be in the same order than in series[0]. - config.series[1].data = revenues.concat(expenses); - - $scope.categoriesChartConfig.loading = false; - - $scope.loadSolds(); - }); - }; - - $scope.loadSolds = function() { - $scope.soldChartConfig.loading = true; - - $http.get("/api/solds", { - params: { - account: $scope.account.id, - } - }).success(function(data) { - var config = $scope.soldChartConfig; - - config.series[0].data = []; - - angular.forEach(data, function(entry) { - config.series[0].data.push([ - moment.utc(entry.operation_date).valueOf(), - entry.open, entry.high, entry.low, entry.close - ]); - }); - - $scope.soldChartConfig.loading = false; - - $scope.loadEntries(); - }); - }; - - /* - * Hook on account selected event to display account status. - */ - $rootScope.$on("accountSelectedEvent", function(event, args) { - $scope.getAccountStatus($routeParams.accountId); - }); - - $rootScope.$on("rangeSelectedEvent", function(event, args) { - $scope.getAccountStatus($routeParams.accountId); - }); - - /* - * Get account status. - */ - $scope.getAccountStatus = function(accountId) { - $scope.categoriesChartConfig.loading = true; - - $http.get("/api/accounts/" + accountId, { - params: { - begin: $scope.begin.format('YYYY-MM-DD'), - end: $scope.end.format('YYYY-MM-DD') - } - }).success(function(account) { - $scope.account = account; - $scope.categoriesChartConfig.loading = false; - - $scope.$emit("accountLoadedEvent", account); - }); - }; - - $rootScope.$on("accountLoadedEvent", $scope.loadCategories); - - $rootScope.$on("accountLoadedEvent", function(e, account) { - var colors = Highcharts.getOptions().colors; - - var config = $scope.categoriesChartConfig; - - // Update pie chart subtitle with Balance. - config.subtitle = { - text: "Balance: " + account.balance - }; - - config.series[0].data = [{ - name: "Revenues", - y: account.revenues, - color: colors[2] - }, { - name: "Expenses", - y: -account.expenses, - color: colors[3], - }]; - - $scope.soldChartConfig.yAxis.plotLines[1].value = account.authorized_overdraft; - }); - // Function to load entries from server for a specific account and month. - $scope.loadEntries = function() { + $scope.loadEntries = function(begin, end) { // Clean up selected entry. $scope.selectedItem = null; $scope.savedItem = null; $scope.entries = Entries.query({ - account: $scope.account.id, - begin: $scope.begin.format('YYYY-MM-DD'), - end: $scope.end.format('YYYY-MM-DD') + account: $routeParams.accountId, + begin: begin.format('YYYY-MM-DD'), + end: end.format('YYYY-MM-DD') }, function(data) { $scope.$emit("entriesLoadedEvent", {entries: data}); }); @@ -369,7 +371,7 @@ accountantApp }; /* - * Save an entry and emit entrySavedEvent, or entryCreatedEvent. + * Save an entry and emit entrySavedEvent. */ $scope.saveEntry = function($data, $index) { // Check if $data is already a resource. @@ -390,60 +392,20 @@ accountantApp promise = promise.then(function(data) { $scope.inserted = false; - notificationService.success("Entry #" + data.id + " created."); - }); - } else { - promise = promise.then(function(data) { - notificationService.success("Entry #" + data.id + " saved."); + return data; }); } - promise = promise.then(function(data) { - $scope.getAccountStatus($routeParams.accountId); + return promise.then(function(data) { + notificationService.success("Entry #" + data.id + " saved."); + $scope.$emit("entrySavedEvent", data); + + return data; }); - - return promise; }; - $scope.removeEntry = function(entry) { - $scope.removingEntry = entry; - $("#remove_entry").modal({ - keyboard: false, - }); - }; - - $scope.hideRemoveEntryPopup = function() { - $scope.removingEntry = null; - $("#remove_entry").modal("hide"); - }; - - // Removes an entry. - $scope.confirmRemoveEntry = function() { - // Cancel current editing. - if ($scope.removingEntry) { - $scope.removingEntry.$delete(function (result) { - // Send the "entry removed" event. - $scope.$emit("entryRemovedEvent", $scope.removingEntry); - - $scope.hideRemoveEntryPopup(); - }); - } - }; - - $rootScope.$on("entryRemovedEvent", function(e, entry) { - new PNotify({ - type: "success", - title: "Delete", - text: "Entry #" + entry.id + " deleted." - }); + // Reload entries on range selection. + $rootScope.$on("rangeSelectedEvent", function(e, args) { + $scope.loadEntries(args.begin, args.end); }); - - $scope.closeModal = function(modalScope) { - // Close the modal dialog - if(modalScope && modalScope.dismiss) { - modalScope.dismiss(); - } - }; - - $scope.getAccountStatus($routeParams.accountId); }]); diff --git a/accountant/frontend/static/templates/entries.html b/accountant/frontend/static/templates/entries.html index ad5f12a..21baec0 100644 --- a/accountant/frontend/static/templates/entries.html +++ b/accountant/frontend/static/templates/entries.html @@ -19,10 +19,14 @@
- +
+ +
- +
+ +