Continued refactoring.
This commit is contained in:
184
frontend/static/js/accounts.js
Normal file
184
frontend/static/js/accounts.js
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
Foobar 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/>.
|
||||
*/
|
||||
// Account object
|
||||
function account() {
|
||||
this.id=ko.observable();
|
||||
this.name=ko.observable();
|
||||
this.authorized_overdraft=ko.observable();
|
||||
this.future=ko.observable();
|
||||
this.current=ko.observable();
|
||||
this.pointed=ko.observable();
|
||||
};
|
||||
|
||||
// The AccountController.
|
||||
var AccountController = function($scope, $http, $rootScope, $window) {
|
||||
// Account store and selection
|
||||
$scope.account = null;
|
||||
$scope.accounts = [];
|
||||
|
||||
$scope.savedAccount = null;
|
||||
$scope.editingAccount = null;
|
||||
$scope.removedAccount = null;
|
||||
|
||||
$scope.accountClass = function(account) {
|
||||
if(account == $scope.account) {
|
||||
return "active";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.valueClass = function(account, value) {
|
||||
if(!account) {
|
||||
return
|
||||
}
|
||||
|
||||
if(value < account.authorized_overdraft) {
|
||||
return "text-error";
|
||||
} else if(value < 0) {
|
||||
return "text-warning";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addAccount = function() {
|
||||
$scope.editingAccount = {
|
||||
id: null,
|
||||
name: null,
|
||||
authorized_overdraft: null
|
||||
};
|
||||
|
||||
angular.element("#edit-account").modal();
|
||||
};
|
||||
|
||||
$scope.show = function() {
|
||||
console.debug("show");
|
||||
};
|
||||
|
||||
$scope.editAccount = function(account) {
|
||||
if(account) {
|
||||
$scope.editingAccount = account;
|
||||
$scope.savedAccount = angular.copy(account);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.cancelEditAccount = function(account, modalScope) {
|
||||
if($scope.savedAccount) {
|
||||
account.name = $scope.savedAccount.name;
|
||||
account.authorized_overdraft = $scope.savedAccount.authorized_overdraft;
|
||||
}
|
||||
|
||||
$scope.editingAccount = null;
|
||||
$scope.savedAccount = null;
|
||||
|
||||
if(modalScope && modalScope.dismiss) {
|
||||
modalScope.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.saveAccount = function(account, modalScope) {
|
||||
// Ajax call to save the entry.
|
||||
var type;
|
||||
var url = "/api/accounts";
|
||||
|
||||
if(account.id) {
|
||||
url += "/" + account.id;
|
||||
}
|
||||
|
||||
$http.put(url, angular.toJson(account)).success(function(data) {
|
||||
$.pnotify({
|
||||
type: "success",
|
||||
title: "Save",
|
||||
text: data
|
||||
});
|
||||
|
||||
$scope.editingAccount = null;
|
||||
$scope.savedAccount = null;
|
||||
|
||||
if(modalScope && modalScope.dismiss) {
|
||||
modalScope.dismiss();
|
||||
}
|
||||
|
||||
// Reload accounts to update solds.
|
||||
$scope.loadAccounts();
|
||||
});
|
||||
};
|
||||
|
||||
// Function to remove of an entry.
|
||||
$scope.accountRemove = function(account, modalScope) {
|
||||
$http.delete("/api/accounts/" + account.id).success(function (data) {
|
||||
$.pnotify({
|
||||
type: "success",
|
||||
title: "Save",
|
||||
text: data
|
||||
});
|
||||
|
||||
// Reload accounts to update solds.
|
||||
$scope.loadAccounts();
|
||||
|
||||
if(modalScope && modalScope.dismiss) {
|
||||
modalScope.dismiss();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Function to load accounts
|
||||
$scope.loadAccounts = function() {
|
||||
$http.get("/api/accounts").success($scope.loadAccounts_success);
|
||||
};
|
||||
|
||||
$scope.loadAccounts_success = function (data) {
|
||||
// Update accounts
|
||||
$scope.accounts = angular.fromJson(data);
|
||||
|
||||
var accountToSelect = null
|
||||
|
||||
// Reset selected account to the new instance corresponding to the old one.
|
||||
if($scope.account) {
|
||||
// Find the new instance of the previously selected account.
|
||||
angular.forEach($scope.accounts, function(account) {
|
||||
if(account.id == $scope.account.id) {
|
||||
accountToSelect = account;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set selected account to first one if not yet selected
|
||||
if(!accountToSelect && $scope.accounts.length > 0){
|
||||
accountToSelect = $scope.accounts[0];
|
||||
}
|
||||
|
||||
// Reset to account to select
|
||||
$scope.account = accountToSelect;
|
||||
|
||||
$scope.$emit("accountsLoadedEvent", {account: $scope.account});
|
||||
};
|
||||
|
||||
// Callback function to select a new account.
|
||||
$scope.selectAccount = function(account) {
|
||||
if(account) {
|
||||
$scope.account = account;
|
||||
|
||||
$scope.$emit("accountsLoadedEvent", {account: $scope.account});
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$on("entrySavedEvent", $scope.loadAccounts);
|
||||
$rootScope.$on("entryRemovedEvent", $scope.loadAccounts);
|
||||
$rootScope.$on("operationSavedEvent", $scope.loadAccounts);
|
||||
$rootScope.$on("operationRemovedEvent", $scope.loadAccounts);
|
||||
|
||||
$scope.loadAccounts();
|
||||
};
|
||||
|
410
frontend/static/js/entries.js
Normal file
410
frontend/static/js/entries.js
Normal file
@ -0,0 +1,410 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
Foobar 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/>.
|
||||
*/
|
||||
var EntryController = function($scope, $http, $rootScope, $filter) {
|
||||
// Entry store and selection
|
||||
$scope.entries = [];
|
||||
$scope.selectedItem = null;
|
||||
|
||||
$scope.account = null;
|
||||
|
||||
// Placeholder for saved value to cancel entry edition
|
||||
$scope.savedItem = null;
|
||||
|
||||
$scope.entriesLoaded = function(entries) {
|
||||
var entriesReversed = entries.slice().reverse();
|
||||
|
||||
var categories = [];
|
||||
|
||||
var chartValues = [];
|
||||
|
||||
var pieChartValuesTmp = {};
|
||||
var pieChartValues = [];
|
||||
|
||||
angular.forEach(entriesReversed, function(entry) {
|
||||
var category = entry.category;
|
||||
var value = entry.value ? Number(entry.value) : null;
|
||||
var sold = entry.sold ? Number(entry.sold) : null;
|
||||
var operation_date = entry.operation_date;
|
||||
|
||||
if(operation_date && sold) {
|
||||
chartValues.push([entry.operation_date, sold]);
|
||||
}
|
||||
|
||||
if(category && category != '') {
|
||||
if(categories.indexOf(category) == -1) {
|
||||
categories.push(category);
|
||||
}
|
||||
|
||||
if(value && value < 0.0) {
|
||||
var oldValue = 0.0;
|
||||
|
||||
if(pieChartValuesTmp[category]) {
|
||||
oldValue = pieChartValuesTmp[category];
|
||||
}
|
||||
|
||||
pieChartValuesTmp[category] = oldValue - value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Second pass: transform to an array readable by jqplot.
|
||||
angular.forEach(pieChartValuesTmp, function(value, key) {
|
||||
pieChartValues.push([key, value]);
|
||||
});
|
||||
|
||||
$scope.categories = categories;
|
||||
|
||||
$scope.drawChart({account: $scope.account, entries: chartValues}, "#entries-chart-placeholder");
|
||||
$scope.drawPieChart(pieChartValues, "#expense-categories-chart-placeholder");
|
||||
};
|
||||
|
||||
$scope.getAccountStatus = function(account, month) {
|
||||
if(account != null && month != null) {
|
||||
$http.get("/api/accounts/" + account.id + "/" + month.year + "/" + month.month).
|
||||
success($scope.getAccountStatus_success);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getAccountStatus_success = function(status) {
|
||||
$scope.accountStatus = status;
|
||||
};
|
||||
|
||||
// Function to load entries from server for a specific account and month.
|
||||
$scope.loadEntries = function(account, month) {
|
||||
if(account) {
|
||||
$scope.account = account;
|
||||
}
|
||||
|
||||
// Clean up selected entry.
|
||||
$scope.selectedItem = null;
|
||||
$scope.savedItem = null;
|
||||
|
||||
if(account && month) {
|
||||
$http.get("/api/entries/" + account.id + "/" + month.year + "/" + month.month).success($scope.loadEntries_success);
|
||||
} else {
|
||||
$scope.loadEntries_success(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Load entries success callback
|
||||
$scope.loadEntries_success = function(data) {
|
||||
var entries = [{
|
||||
id: null,
|
||||
pointed: false,
|
||||
operation_date: null,
|
||||
label: null,
|
||||
value: null,
|
||||
sold: null,
|
||||
pointedsold: null,
|
||||
category: null,
|
||||
account_id: null,
|
||||
state: 'new',
|
||||
canceled: false,
|
||||
scheduled_operation_id: null
|
||||
}];
|
||||
|
||||
if(data) {
|
||||
entries = entries.concat(angular.fromJson(data));
|
||||
}
|
||||
|
||||
$scope.entries = entries;
|
||||
|
||||
$scope.$emit("entriesLoadedEvent", {entries: entries});
|
||||
};
|
||||
|
||||
// Returns the CSS class for a pointed entry.
|
||||
$scope.pointedEntryClass = function(entry) {
|
||||
if(entry.pointed) {
|
||||
return "active";
|
||||
}
|
||||
};
|
||||
|
||||
// Returns the CSS class for an entry row.
|
||||
$scope.entryRowClass = function(entry) {
|
||||
if($scope.isSaved(entry)) {
|
||||
cssclass="";
|
||||
} else {
|
||||
cssclass="italic";
|
||||
}
|
||||
|
||||
if(entry.canceled) {
|
||||
cssclass += " stroke";
|
||||
}
|
||||
|
||||
if(entry.sold < $scope.account.authorized_overdraft) {
|
||||
cssclass += " error";
|
||||
} else if (entry.sold < 0) {
|
||||
cssclass += " warning";
|
||||
}
|
||||
|
||||
return cssclass;
|
||||
};
|
||||
|
||||
// Returns the CSS class for an entry sold.
|
||||
$scope.entryValueClass = function(sold) {
|
||||
if(sold && sold < $scope.account.authorized_overdraft) {
|
||||
return 'text-error';
|
||||
} else if (sold && sold < 0) {
|
||||
return 'text-warning';
|
||||
}
|
||||
};
|
||||
|
||||
// Starts editing an entry
|
||||
$scope.editEntry = function(entry) {
|
||||
// Cancel previous editing.
|
||||
if($scope.selectedItem) {
|
||||
$scope.cancelEditEntry($scope.selectedItem);
|
||||
}
|
||||
|
||||
// Save current entry values.
|
||||
$scope.savedItem = angular.copy(entry);
|
||||
$scope.selectedItem = entry;
|
||||
|
||||
// Enter edit state.
|
||||
entry.state='edit';
|
||||
};
|
||||
|
||||
// Returns true if the entry is a new one.
|
||||
$scope.isNew = function(entry) {
|
||||
return !$scope.isSaved(entry) && (entry.state === 'edit' || entry.state === 'new');
|
||||
};
|
||||
|
||||
// Returns true if the entry is in editing state.
|
||||
$scope.isEditing = function(entry) {
|
||||
return entry.state === 'edit' || entry.state === 'new';
|
||||
};
|
||||
|
||||
// Returns true if the entry is in displaying state.
|
||||
$scope.isDisplaying = function(entry) {
|
||||
return !entry.state || entry.state === 'display';
|
||||
};
|
||||
|
||||
// Returns true if the entry is a scheduled one.
|
||||
$scope.isSaved = function(entry) {
|
||||
return entry.id != null;
|
||||
}
|
||||
|
||||
$scope.iconSaveClass = function(entry) {
|
||||
if(!$scope.isSaved(entry)) {
|
||||
return "icon-plus";
|
||||
} else if ($scope.isEditing(entry)) {
|
||||
return "icon-ok";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.iconCancelClass = function(entry) {
|
||||
if($scope.isNew(entry)) {
|
||||
return "icon-remove";
|
||||
} else if ($scope.isEditing(entry)) {
|
||||
return "icon-ban-circle";
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel current editing entry or clears field if a new one.
|
||||
$scope.cancelEditEntry = function(entry) {
|
||||
if ($scope.savedItem == null) {
|
||||
// We are cancelling the new entry, just reset it.
|
||||
entry.id = null; // id should not change, but just in case...
|
||||
entry.pointed = false;
|
||||
entry.operation_date = null;
|
||||
entry.label = null;
|
||||
entry.value = null;
|
||||
entry.account_id = $scope.account.id; // account_id should not change, but just in case...
|
||||
entry.category = null;
|
||||
entry.canceled = false;
|
||||
entry.scheduled_operation_id = null;
|
||||
|
||||
// Reset state to new.
|
||||
entry.state = 'new';
|
||||
} else {
|
||||
// Reset selected item fields to saved item ones.
|
||||
entry.id = $scope.savedItem.id; // id should not change, but just in case...
|
||||
entry.pointed = $scope.savedItem.pointed;
|
||||
entry.operation_date = $scope.savedItem.operation_date;
|
||||
entry.label = $scope.savedItem.label;
|
||||
entry.value = $scope.savedItem.value;
|
||||
entry.account_id = $scope.savedItem.account_id; // account_id should not change, but just in case...
|
||||
entry.category = $scope.savedItem.category;
|
||||
entry.canceled = $scope.savedItem.canceled;
|
||||
entry.scheduled_operation_id = $scope.savedItem.scheduled_operation_id;
|
||||
|
||||
// Reset saved and selected items to null.
|
||||
$scope.savedItem = null;
|
||||
$scope.selectedItem = null;
|
||||
|
||||
// Enter display state.
|
||||
entry.state = 'display';
|
||||
}
|
||||
};
|
||||
|
||||
// Points an entry.
|
||||
$scope.pointEntry = function(entry) {
|
||||
if(entry.pointed) {
|
||||
entry.pointed = false;
|
||||
} else {
|
||||
entry.pointed = true;
|
||||
}
|
||||
|
||||
// Save the entry if not new.
|
||||
if(!$scope.isNew(entry)) {
|
||||
$scope.saveEntry(entry);
|
||||
}
|
||||
};
|
||||
|
||||
// Saves an entry.
|
||||
$scope.saveEntry = function(entry) {
|
||||
// Affects the account ID if not (should never happen)
|
||||
if(!entry.account_id) {
|
||||
entry.account_id = $scope.account.id;
|
||||
}
|
||||
|
||||
// Prepare the Ajax call to save the entry.
|
||||
var type;
|
||||
var url = "/api/entries";
|
||||
|
||||
if($scope.isSaved(entry)) {
|
||||
url += "/" + entry.id;
|
||||
}
|
||||
|
||||
// Ajax call to save an entry
|
||||
$http.put(url, angular.toJson(entry)).success(function(data) {
|
||||
$.pnotify({
|
||||
type: "success",
|
||||
title: "Save",
|
||||
text: data
|
||||
});
|
||||
|
||||
// $scope.savedItem = null;
|
||||
|
||||
// Send the "entry saved" event.
|
||||
$scope.$emit("entrySavedEvent", entry);
|
||||
});
|
||||
};
|
||||
|
||||
// Removes an entry.
|
||||
$scope.removeEntry = function(entry, modalScope) {
|
||||
// Cancel current editing.
|
||||
if (!$scope.isNew(entry)) {
|
||||
$http.delete("/api/entries/" + entry.id).success(function (result) {
|
||||
$.pnotify({
|
||||
type: "success",
|
||||
title: "Delete",
|
||||
text: result
|
||||
});
|
||||
|
||||
// Send the "entry removed" event.
|
||||
$scope.$emit("entryRemovedEvent", entry);
|
||||
|
||||
$scope.closeModal(modalScope);
|
||||
}).error(function (data) {
|
||||
$.pnotify({
|
||||
type: "error",
|
||||
title: "Delete",
|
||||
text: data
|
||||
});
|
||||
|
||||
$scope.closeModal(modalScope);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.closeModal = function(modalScope) {
|
||||
// Close the modal dialog
|
||||
if(modalScope && modalScope.dismiss) {
|
||||
modalScope.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to draw the sold evolution chart.
|
||||
$scope.drawChart = function(data, elementId) {
|
||||
// Clear previous chart
|
||||
//var element = angular.element(elementId);
|
||||
//element.html("");
|
||||
//element.css("height", "0px");
|
||||
|
||||
var entries = data.entries;
|
||||
|
||||
//if(entries && entries.length > 1) {
|
||||
// Prepare for today vertical line.
|
||||
var today = new Date();
|
||||
today.setHours(0);
|
||||
today.setMinutes(0);
|
||||
|
||||
// Find first and last days to set limits of the x axis.
|
||||
var day = 24 * 60 * 60 * 1000;
|
||||
var firstDate = $filter('date')(new Date(Date.parse(entries[0][0]).valueOf() - day), 'yyyy-MM-dd');
|
||||
var lastDate = $filter('date')(new Date(Date.parse(entries[entries.length -1][0]).valueOf() + day), 'yyyy-MM-dd');
|
||||
//var firstDate = new Date(Date.parse(entries[0][0]).valueOf() - day);
|
||||
//var lastDate = new Date(Date.parse(entries[entries.length -1][0]).valueOf() + day);
|
||||
|
||||
// Plot chart, and store it in a window parameter for resize callback (need to be done better than it...)
|
||||
var chart = nv.models.lineChart()
|
||||
.x(function(d) { return d3.time.format("%Y-%m-%d").parse(d[0]); })
|
||||
.y(function(d) { return new Number(d[1]); });
|
||||
|
||||
chart.lines.interpolate("monotone");
|
||||
|
||||
chart.xAxis.axisLabel("Date").tickFormat(function(d) {
|
||||
return d3.time.format("%Y-%m-%d")(new Date(d));
|
||||
});
|
||||
chart.xAxis.scale().range([firstDate, lastDate]);
|
||||
|
||||
chart.yAxis.axisLabel("Solde").tickFormat(d3.format('.02f'));
|
||||
|
||||
// FIXME add vertical line for today
|
||||
graph = d3.select(elementId + " svg").datum([
|
||||
{ color: "orange", key: "Zero", values:[
|
||||
[firstDate, "0"],
|
||||
[lastDate, "0"]
|
||||
]},
|
||||
{ color: "red", key: "Authorized overdraft", values : [
|
||||
[firstDate, new Number($scope.account.authorized_overdraft)],
|
||||
[lastDate, new Number($scope.account.authorized_overdraft)]
|
||||
]},
|
||||
{ color: "darkblue", key: "Sold evolution", values: entries},
|
||||
]).transition().duration(1200).call(chart);
|
||||
|
||||
nv.utils.windowResize(chart.update);
|
||||
};
|
||||
|
||||
// Function to draw the expense category pie chart.
|
||||
$scope.drawPieChart = function(entries, elementId) {
|
||||
//if(entries && entries.length > 1) {
|
||||
var chart = nv.models.pieChart()
|
||||
.x(function(d) { return d[0]; })
|
||||
.y(function(d) { return d[1]; })
|
||||
.showLabels(true);
|
||||
|
||||
d3.select(elementId + " svg").datum([{key: "Expenses", values: entries}]).transition().duration(1200).call(chart);
|
||||
|
||||
nv.utils.windowResize(chart.update);
|
||||
//}
|
||||
};
|
||||
|
||||
$rootScope.$on("monthsLoadedEvent", function(event, args){
|
||||
$scope.loadEntries(args.account, args.month);
|
||||
});
|
||||
|
||||
$rootScope.$on("monthsLoadedEvent", function(event, args){
|
||||
$scope.getAccountStatus(args.account, args.month);
|
||||
});
|
||||
|
||||
$scope.$on("entriesLoadedEvent", function(event, args) {
|
||||
$scope.entriesLoaded(args.entries);
|
||||
});
|
||||
};
|
||||
|
116
frontend/static/js/months.js
Normal file
116
frontend/static/js/months.js
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
Foobar 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/>.
|
||||
*/
|
||||
// Month object
|
||||
function month() {
|
||||
this.year = null;
|
||||
this.month = null;
|
||||
}
|
||||
|
||||
|
||||
var MonthController = function($scope, $http, $rootScope) {
|
||||
// Month store and selection
|
||||
$scope.months = null;
|
||||
$scope.month = null;
|
||||
$scope.account = null;
|
||||
|
||||
$scope.monthClass = function(month) {
|
||||
if(month == $scope.month) {
|
||||
return "active";
|
||||
}
|
||||
};
|
||||
|
||||
// Function to load months
|
||||
$scope.loadMonths = function(account) {
|
||||
if(account) {
|
||||
$scope.account = account;
|
||||
}
|
||||
|
||||
if($scope.account) {
|
||||
$http.get("/api/accounts/" + $scope.account.id + "/months").success($scope.loadMonths_success);
|
||||
} else {
|
||||
$scope.$emit("monthsLoadedEvent", {account: null, month: null});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.loadMonths_success = function (data) {
|
||||
// Update months
|
||||
$scope.months = angular.fromJson(data);
|
||||
|
||||
var monthToSelect = null;
|
||||
|
||||
var today = new Date();
|
||||
var currentYear = today.getFullYear().toString();
|
||||
var currentMonth = (today.getMonth() + 1 < 10 ? "0" : "") + (today.getMonth() + 1);
|
||||
var found = false;
|
||||
|
||||
var lastIndex = 0;
|
||||
|
||||
// Add current month if not present.
|
||||
angular.forEach($scope.months, function(month) {
|
||||
if(!found) {
|
||||
if (month.year == currentYear && month.month == currentMonth) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (parseInt(month.year) < parseInt(currentYear) || (month.year == currentYear && parseInt(month.month) < parseInt(currentMonth))) {
|
||||
lastIndex++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(!found) {
|
||||
$scope.months.splice(lastIndex, 0, {year: currentYear, month: currentMonth});
|
||||
}
|
||||
|
||||
// Find the new instance of the previously selected month.
|
||||
angular.forEach($scope.months, function(month) {
|
||||
// Reset selected month to the new instance corresponding to the old one
|
||||
if($scope.month) {
|
||||
if(month.year == $scope.month.year && month.month == $scope.month.month) {
|
||||
monthToSelect = month;
|
||||
}
|
||||
}
|
||||
|
||||
if(!monthToSelect && month.year == currentYear && month.month == currentMonth) {
|
||||
monthToSelect = month;
|
||||
}
|
||||
});
|
||||
|
||||
// Set selected month to the last one if not yet selected.
|
||||
if(!monthToSelect && $scope.months.length > 0) {
|
||||
monthToSelect = $scope.months[$scope.months.length - 1];
|
||||
}
|
||||
|
||||
// Reset to month to select
|
||||
$scope.month = monthToSelect;
|
||||
|
||||
$scope.$emit("monthsLoadedEvent", {account: $scope.account, month: $scope.month});
|
||||
};
|
||||
|
||||
// Callback function to select a new month.
|
||||
$scope.selectMonth = function(month) {
|
||||
if(month) {
|
||||
$scope.month = month;
|
||||
$scope.$emit("monthsLoadedEvent", {account: $scope.account, month: month});
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$on("accountsLoadedEvent", function(event, args) {
|
||||
$scope.loadMonths(args.account);
|
||||
});
|
||||
};
|
||||
|
197
frontend/static/js/scheduler.js
Normal file
197
frontend/static/js/scheduler.js
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
Foobar 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/>.
|
||||
*/
|
||||
var SchedulerController = function($scope, $http, $rootScope, $filter) {
|
||||
// Operations store and selection
|
||||
$scope.operations = [];
|
||||
$scope.selectedOperation = null;
|
||||
|
||||
$scope.account = null;
|
||||
|
||||
// Placeholder for saved value to cancel entry edition
|
||||
$scope.savedOperation = null;
|
||||
|
||||
$scope.loadOperations = function(account) {
|
||||
// Clean up selected entry.
|
||||
$scope.selectedOperation = null;
|
||||
$scope.savedOperation = null;
|
||||
|
||||
if(account) {
|
||||
$scope.account = account;
|
||||
|
||||
$http.get("/api/scheduled_operations/" + account.id).success($scope.loadOperations_success);
|
||||
} else {
|
||||
$scope.loadOperations_success(null);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.loadOperations_success = function(data) {
|
||||
var operations = [{
|
||||
id: null,
|
||||
start_date: null,
|
||||
stop_date: null,
|
||||
day: null,
|
||||
frequency: null,
|
||||
label: null,
|
||||
account_id: null,
|
||||
category: null,
|
||||
state: "edit"
|
||||
}];
|
||||
|
||||
if(data) {
|
||||
operations = operations.concat(angular.fromJson(data));
|
||||
}
|
||||
|
||||
$scope.operations = operations;
|
||||
|
||||
$scope.$emit("operationsLoadedEvent", {operations: operations});
|
||||
};
|
||||
|
||||
$scope.iconSaveClass = function(operation) {
|
||||
if($scope.isNew(operation)) {
|
||||
return "icon-plus";
|
||||
} else if ($scope.isEditing(operation)) {
|
||||
return "icon-ok";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.iconCancelClass = function(operation) {
|
||||
if($scope.isNew(operation)) {
|
||||
return "icon-remove";
|
||||
} else if ($scope.isEditing(operation)) {
|
||||
return "icon-ban-circle";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isNew = function(operation) {
|
||||
return !operation.id;
|
||||
};
|
||||
|
||||
// Returns true if the entry is in editing state.
|
||||
$scope.isEditing = function(operation) {
|
||||
return operation.state === 'edit';
|
||||
};
|
||||
|
||||
$scope.isDisplaying = function(operation) {
|
||||
return operation.id && (!operation.state || operation.state === 'display');
|
||||
};
|
||||
|
||||
$scope.saveOperation = function(operation) {
|
||||
if(!operation.account_id) {
|
||||
operation.account_id = $scope.account.id;
|
||||
}
|
||||
|
||||
// prepare the Ajax xall to save the sceduled operation.
|
||||
var type;
|
||||
var url = "/api/scheduled_operations";
|
||||
|
||||
if(!$scope.isNew(operation)) {
|
||||
url += "/" + operation.id;
|
||||
}
|
||||
|
||||
$http.put(url, angular.toJson(operation)).success(function(data) {
|
||||
$.pnotify({
|
||||
type: "success",
|
||||
title: "Save",
|
||||
text: data
|
||||
});
|
||||
|
||||
$scope.$emit("operationSavedEvent", operation);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editOperation = function(operation) {
|
||||
// Cancel previous editing.
|
||||
if($scope.selectedOperation) {
|
||||
$scope.cancelEditOperation($scope.selectedItem);
|
||||
}
|
||||
|
||||
// Save current entry values.
|
||||
$scope.savedOperation = angular.copy(operation);
|
||||
$scope.selectedOperation = operation;
|
||||
|
||||
// Enter edit state.
|
||||
operation.state='edit';
|
||||
};
|
||||
|
||||
$scope.cancelEditOperation = function(operation) {
|
||||
if ($scope.isNew(operation)) {
|
||||
operation.id = null;
|
||||
operation.start_date = null;
|
||||
operation.stop_date = null;
|
||||
operation.day = null;
|
||||
operation.frequency = null;
|
||||
operation.label = null;
|
||||
operation.value = null;
|
||||
operation.category = null;
|
||||
operation.account_id = $scope.account.id;
|
||||
} else if ($scope.isEditing(operation)) {
|
||||
if($scope.savedOperation) {
|
||||
operation.id = $scope.savedOperation.id;
|
||||
operation.start_date = $scope.savedOperation.start_date;
|
||||
operation.stop_date = $scope.savedOperation.stop_date;
|
||||
operation.day = $scope.savedOperation.day;
|
||||
operation.frequency = $scope.savedOperation.frequency;
|
||||
operation.label = $scope.savedOperation.label;
|
||||
operation.value = $scope.savedOperation.value;
|
||||
operation.category = $scope.savedOperation.category;
|
||||
operation.account_id = $scope.savedOperation.account_id;
|
||||
}
|
||||
|
||||
$scope.savedOperation = null;
|
||||
$scope.selectedOperation = null;
|
||||
|
||||
operation.state = 'display';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeOperation = function(operation, modalScope) {
|
||||
// Cancel current editing.
|
||||
if (!$scope.isNew(operation)) {
|
||||
$http.delete("/api/scheduled_operations/" + operation.id).success(function (result) {
|
||||
$.pnotify({
|
||||
type: "success",
|
||||
title: "Delete",
|
||||
text: result
|
||||
});
|
||||
|
||||
// Send the "entry removed" event.
|
||||
$scope.$emit("operationRemovedEvent", operation);
|
||||
|
||||
$scope.closeModal(modalScope);
|
||||
}).error(function (data) {
|
||||
$.pnotify({
|
||||
type: "error",
|
||||
title: "Delete",
|
||||
text: data
|
||||
});
|
||||
|
||||
$scope.closeModal(modalScope);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.closeModal = function(modalScope) {
|
||||
// Close the modal dialog
|
||||
if(modalScope && modalScope.dismiss) {
|
||||
modalScope.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.$on("accountsLoadedEvent", function(event, args){
|
||||
$scope.loadOperations(args.account);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user