Last active
August 30, 2021 08:57
-
-
Save michaelbaudino/08ac99bddbe579f875bfdf72d709ea45 to your computer and use it in GitHub Desktop.
Harvest improvements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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