Skip to content

Instantly share code, notes, and snippets.

@michaelbaudino
Last active August 30, 2021 08:57
Show Gist options
  • Save michaelbaudino/08ac99bddbe579f875bfdf72d709ea45 to your computer and use it in GitHub Desktop.
Save michaelbaudino/08ac99bddbe579f875bfdf72d709ea45 to your computer and use it in GitHub Desktop.
Harvest improvements
function numberFormat(number, decimals, decPoint, thousandsSep) { // Shamelessly copied from http://locutus.io/php/number_format/
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep;
var dec = (typeof decPoint === 'undefined') ? '.' : decPoint;
var s = '';
function toFixedFix(n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
}
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
function arrayOf(nodes) {
return Array.prototype.slice.call(nodes);
}
class Invoice {
constructor() {
this.node = document.querySelector("form#edit_invoice_form");
this.rows = arrayOf(this.node.querySelectorAll("#invoice_item_rows > tr")).map(row => new InvoiceRow(row, this));
this.i18n = JSON.parse(document.querySelector("script#locale-data-island").innerHTML);
this.rows.map(row => row.addMergeButton());
}
rowsWithNotes(notes) {
return this.rows.filter(row => row.notes == notes);
}
}
class InvoiceRow {
constructor(node, invoice) {
this.node = node;
this.invoice = invoice;
}
get controlColumn() {
return this.node.querySelector("td.control");
}
get notesField() {
return this.node.querySelector(`td.description textarea[name="invoice[line_items_attributes][${this.number}][description]"]`);
}
get quantityField() {
return this.node.querySelector(`td.quantity input[name="invoice[line_items_attributes][${this.number}][quantity]"]`);
}
get dayEntries() {
return arrayOf(this.controlColumn.querySelectorAll(`[name="invoice[line_items_attributes][${this.number}][day_entry_ids][]"]`));
}
get number() {
return this.node.id.replace("item-row-", "");
}
get notes() {
return this.notesField.value;
}
get quantity() {
return this.parseFloat(this.quantityField.value);
}
set quantity(value) {
this.quantityField.value = this.formatFloat(value);
}
get allRowsWithSameNotes() {
return this.invoice.rowsWithNotes(this.notes);
}
get similarRows() {
return this.allRowsWithSameNotes.filter(row => row !== this);
}
addMergeButton() {
if (this.similarRows.length > 0) {
this.mergeButton = new MergeButton(this);
}
}
addDayEntry(dayEntry) {
this.controlColumn.appendChild(dayEntry);
}
mergeIn(targetRow) {
this.dayEntries.forEach(dayEntry => {
dayEntry.name = `invoice[line_items_attributes][${targetRow.number}][day_entry_ids][]`
targetRow.addDayEntry(dayEntry)
});
targetRow.quantity = targetRow.quantity + this.quantity;
this.destroy();
targetRow.flash("#ff7");
invoice.recompute_amounts_and_totals();
}
destroy() {
this.invoice.rows = this.invoice.rows.filter(row => row !== this);
this.node.parentNode.removeChild(this.node);
}
parseFloat(localizedString) {
var { decimal_separator, thousands_separator } = this.invoice.i18n;
var neutralString = localizedString.replace(thousands_separator, "").replace(decimal_separator, ".");
return parseFloat(neutralString);
}
formatFloat(float) {
var { decimal_separator, thousands_separator } = this.invoice.i18n;
return numberFormat(float, 2, decimal_separator, thousands_separator);
}
flash(color) {
var animation = [
{backgroundColor: color},
{backgroundColor: "#fff", easing: "ease-in"}
];
var options = {
duration: 2000,
delay: 0
};
this.quantityField.animate(animation, options);
this.notesField.animate(animation, options);
}
}
class MergeButton {
constructor(row) {
this.row = row;
this.node = document.createElement("a");
this.node.title = "Merge similar items with the same `Notes` field and mark them all as invoiced.";
this.node.onmouseover = this.handleMouseOver.bind(this);
this.node.onmouseout = this.handleMouseOut.bind(this);
this.node.onclick = this.handleClick.bind(this);
this.node.style["width"] = "16px";
this.node.style["height"] = "16px";
this.node.style["background-image"] = `url(${this.iconBase64Content})`;
this.node.style["background-repeat"] = "no-repeat";
this.node.style["background-position"] = "center";
this.node.style["padding"] = "6px 2px";
this.node.style["opacity"] = 0.5;
this.node.style["display"] = "block";
this.row.controlColumn.appendChild(this.node)
}
handleMouseOver() {
this.row.allRowsWithSameNotes.forEach(row => { row.notesField.style["background-color"] = "#ffe" });
this.node.style["opacity"] = 0.6;
}
handleMouseOut() {
this.row.allRowsWithSameNotes.forEach(row => { row.notesField.style["background-color"] = "#fff" });
this.node.style["opacity"] = 0.5;
}
handleClick(event) {
this.row.similarRows.forEach(similarRow => {
similarRow.mergeIn(this.row);
});
this.destroy();
}
destroy() {
this.node.onmouseout();
this.node.parentNode.removeChild(this.node);
delete this.row.mergeButton;
}
get iconBase64Content() {
return `
data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDQ2Mi4zNjkgNDYyLjM2OSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDYyLjM2OSA0NjIuMzY5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTAsMzcxLjgyNXYtNTkuMDA3YzYxLjk1OSwwLDkyLjkwNS0zMC42OTQsMTIyLjgzNy02MC4zNzhjNy4yODgtNy4yMzUsMTQuNjY1LTE0LjUxOCwyMi40MTktMjEuMjU2ICAgYy03Ljc1NS02LjcyNy0xNS4xMzEtMTQuMDE0LTIyLjQxOS0yMS4yNTVDOTIuOTA1LDE4MC4yNDksNjEuOTU5LDE0OS41NTEsMCwxNDkuNTUxVjkwLjU0NGM4Ni4yNjIsMCwxMzEuNDIsNDQuNzg5LDE2NC4zODEsNzcuNDk2ICAgYzE5LjU1LDE5LjM5NCwzMi4xMTYsMzEuMjI3LDQ1LjUyMSwzMy41MmMxLjU5OS0wLjA4NSwzLjE2Ni0wLjE5Miw0LjgxMi0wLjE5MnYwLjYyNmwwLDBoMTQxLjg4MmwtMjQuMjQ2LTYxLjM1NCAgIGMtMC44MTYtMi4wNTctMC4xNTQtNC40MDYsMS42MTktNS43MzNjMS43NjItMS4zMyw0LjE5Ny0xLjMyNCw1Ljk0NiwwLjAyNmwxMjAuNTQ0LDkyLjY3MmMxLjIwNiwwLjkzMSwxLjkwOSwyLjM3NCwxLjkwOSwzLjg5OCAgIGMwLDEuNTI0LTAuNzAzLDIuOTY4LTEuOTA5LDMuODk2bC0xMjAuNTQ0LDkyLjY3NWMtMC44ODEsMC42NzQtMS45MzgsMS4wMTctMi45OTEsMS4wMTdjLTEuMDQsMC0yLjA4LTAuMzMxLTIuOTU1LTAuOTkzICAgYy0xLjc3My0xLjMyNC0yLjQyMy0zLjY3Ny0xLjYxOS01LjczM2wyNC4yNDYtNjEuMzY1SDIxNC41NGMtMC4wNDcsMC0wLjA5Mi0wLjAxMi0wLjEzMy0wLjAxMmMtMS41MzEsMC0zLjAxNS0wLjA5NS00LjUwNC0wLjE3MiAgIGMtMTMuNDA2LDIuMjg3LTI1Ljk2NiwxNC4xMjEtNDUuNTIxLDMzLjUyQzEzMS40MiwzMjcuMDM0LDg2LjI2MiwzNzEuODI1LDAsMzcxLjgyNXoiIGZpbGw9IiMwMDAwMDAiLz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K
`; // Author must be credited: https://www.flaticon.com/free-icon/arrows-merge-pointing-to-right_32232
}
}
new Invoice();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment