// Entry object
function entry(){
this.id=ko.observable();
this.value_date=ko.observable();
this.operation_date=ko.observable();
this.label=ko.observable();
this.value=ko.observable();
this.account_id=ko.observable();
this.sold=ko.observable();
this.pointedsold=ko.observable();
this.category=ko.observable();
}
// Util function to show a message in message placeholder.
function message(alertType, title, message) {
$(".alert").alert('close');
$("#message-placeholder").append('
' + title + '
' + message + '
');
}
var ListViewModel = function() {
var self = this;
// Account store and selection
self.account = ko.observable();
self.accounts = ko.observableArray([]);
// Month store and selection
self.months = ko.observableArray();
self.month = ko.observable();
// Entry store and selection
self.entries = ko.observableArray([]);
self.selectedItem = ko.observable();
// Placeholder for saved value to cancel entry edition
self.savedItem = null;
// Placeholder for entry to remove to be available in modal function "yes" click callback
self.itemToRemove = ko.observable();
// Returns the data for the categories by summing values with same category
self.expenseCategoriesChart = ko.computed(function() {
var entries=ko.utils.unwrapObservable(self.entries);
// First pass: get sum values for each category.
var chartValuesTmp = {};
$.each(entries, function(index, entry) {
var category = entry.category();
var value = entry.value() ? Number(entry.value()) : null;
if(category && value && value < 0.0) {
var oldValue = 0.0;
if(chartValuesTmp[category]) {
oldValue = chartValuesTmp[category];
}
chartValuesTmp[category] = oldValue - value
}
});
// Second pass: transform to an array readable by jqplot.
var chartValues = [];
$.each(chartValuesTmp, function(key, value) {
chartValues.push([key, value]);
});
return chartValues;
});
// Return the data for the sold chart.
self.entriesChart = ko.computed(function() {
// We assume that entries are sorted by value date descending.
var entries = ko.utils.unwrapObservable(self.entries).slice().reverse();
// First pass: get open, high, low and close values for each day.
var chartValuesTmp = {};
$.each(entries, function(index, entry) {
//var date = entry.value_date() ? entry.value_date().toString() : null;
var date = entry.value_date();
var value = entry.value ? Number(entry.value()) : null;
if(date && value) {
var values = {};
var sold = Number(entry.sold());
var open = Number((sold - value).toFixed(2));
values['open'] = open;
values['high'] = sold > open ? sold : open;
values['low'] = sold < open ? sold : open;
values['close'] = sold;
if(chartValuesTmp[date]) {
var oldValues = chartValuesTmp[date];
if(oldValues['high'] > values['high']) {
values['high'] = oldValues['high'];
}
if(oldValues['low'] < values['low']) {
values['low'] = oldValues['low'];
}
values['open'] = oldValues['open'];
}
chartValuesTmp[date] = values;
}
});
// Second pass: transform to an array readable by jqplot OHLC renderer.
var chartValues = [];
$.each(chartValuesTmp, function(key, value) {
chartValues.push([key, value['open'], value['high'], value['low'], value['close']]);
});
return chartValues;
}, self);
// Function to load entries from server for a specific account and month.
self.loadEntries = function(account, month) {
$.post("api/entry.php", {action: "get_entries", account: account.id, year: month.year, month: month.month}, function(data) {
// Clean up current entries.
self.entries.removeAll();
// Clean up selected entry.
self.selectedItem(null);
self.entries(ko.utils.arrayMap(data, ko.mapping.fromJS));
});
};
self.loadAccounts = function() {
$.post("api/entry.php", {action: "get_accounts"}).success(function (result) {
self.accounts(result);
if(self.account()) {
$.each(self.accounts(), function(index, account) {
if(self.account().id == account.id) {
self.account(account);
}
});
}
if(!self.account()){
self.account(result[0]);
}
self.loadMonths(self.account());
});
};
self.loadMonths = function(account){
$.post("api/entry.php", {action: "get_months", account: account.id}).success(function (result) {
self.months(result);
if(self.month()) {
$.each(self.months(), function(index, month) {
if(self.month().year == month.year && self.month().month == month.month) {
self.month(month);
}
});
}
if(!self.month()) {
self.month(result[result.length - 1]);
}
self.loadEntries(self.account(), self.month());
});
};
self.templateToUse = function (item) {
return self.selectedItem() === item ? 'editTmpl' : 'itemsTmpl';
};
self.edit = function(item) {
if(self.savedItem) {
self.cancel();
}
self.savedItem=ko.toJS(item);
self.selectedItem(item);
$("#value_date").datepicker().on('changeDate', function(ev){
self.selectedItem().value_date(ev.date.format(ev.currentTarget.dataset.dateFormat));
});
$("#operation_date").datepicker().on('changeDate', function(ev){
self.selectedItem().operation_date(ev.date.format(ev.currentTarget.dataset.dateFormat));
});
};
self.cancel = function() {
if(self.selectedItem() && self.savedItem) {
self.selectedItem().id(self.savedItem.id); // id should not change, but just in case...
self.selectedItem().operation_date(self.savedItem.operation_date);
self.selectedItem().value_date(self.savedItem.value_date);
self.selectedItem().label(self.savedItem.label);
self.selectedItem().value(self.savedItem.value);
self.selectedItem().account_id(self.savedItem.account_id); // account_id should not change, but just in case...
}
// This item was just added.
if(self.selectedItem() && !self.selectedItem().id()) {
self.entries.remove(self.selectedItem());
}
self.savedItem = null;
self.selectedItem(null);
};
self.add = function() {
var newEntry = new entry();
newEntry.account_id(self.account().id);
self.entries.unshift(newEntry);
self.edit(newEntry);
};
self.save = function() {
var item = ko.toJS(self.selectedItem());
$.post("api/entry.php", {action: "save_entry", entry:item}).success(function(data) {
message("success", "Save", data.message);
self.selectedItem(null);
self.loadAccounts();
}).error(function() {
message("error", "Error.", "Unexpected error.");
});
};
self.remove = function (item) {
if (item.id()) {
self.itemToRemove(item);
$('#remove-confirm').modal();
} else {
self.entries.remove(item);
}
};
self.confirmRemove = function() {
var item = self.itemToRemove();
$.post("api/entry.php", {action: "remove_entry", entry:item}).success(function (result) {
self.loadAccounts();
}).complete(function (result) {
self.itemToRemove(null);
$('#remove-confirm').modal('hide');
});
};
self.selectMonth = function(month) {
if(month) {
self.month(month);
self.loadEntries(self.account(), month);
}
};
self.selectAccount = function(account) {
if(account) {
self.account(account);
self.loadMonths(account);
}
};
};
drawChart = function(entries, element) {
// clear previous chart
$(element).html("");
if(entries && entries.length > 0) {
var chartValues = [[], []];
chartValues[0] = entries;
var today = new Date();
today.setHours(0);
today.setMinutes(0);
var day = 24 * 60 * 60 * 1000;
var firstDate = new Date(Date.parse(entries[0][0]).valueOf() - day).format('yyyy-mm-dd');
var lastDate = new Date(Date.parse(entries[entries.length -1][0]).valueOf() + day).format('yyyy-mm-dd');
// plot chart
window.chart = $.jqplot(element.id, chartValues, {
title: "Évolution du solde",
axes:{
xaxis:{
autoscale: true,
renderer:$.jqplot.DateAxisRenderer,
min: firstDate,
max: lastDate,
tickOptions: {formatString: "%F"}
},
yaxis: {
autoscale: true,
}
},
highlighter: {
show:true,
yvalues: 4,
formatString:'