Files
itflow/js/client_overview.js

91 lines
3.6 KiB
JavaScript

/*
* ITFlow - client overview (agent/client_overview.php).
*
* Saves the quick notes when you click away, but only if they changed, and
* says so; draws the invoiced-and-paid chart from window.itflowClientOverviewData.
*/
(function () {
'use strict';
/* Quick notes */
const notes = document.getElementById('clientNotes');
const status = document.querySelector('[data-notes-status]');
if (notes && !notes.readOnly && typeof itflowPost === 'function') {
let saved = notes.value;
notes.addEventListener('blur', function () {
if (notes.value === saved) {
return;
}
const value = notes.value;
if (status) {
status.textContent = 'Saving...';
}
itflowPost('ajax.php', {
client_set_notes: 'TRUE',
csrf_token: notes.dataset.csrf,
client_id: notes.dataset.clientId,
notes: value
}, function () {
saved = value;
if (status) {
status.textContent = 'Saved';
}
}, function () {
if (status) {
status.textContent = 'Not saved - try again';
}
});
});
}
/* Invoiced and paid, last 12 months */
const data = window.itflowClientOverviewData;
const canvas = document.getElementById('clientMonthly');
if (!data || !data.monthly || !canvas || typeof Chart === 'undefined') {
return;
}
const base = typeof itflowChartDefaults === 'function' ? itflowChartDefaults() : { grid: 'rgba(0,0,0,.1)' };
const root = getComputedStyle(document.body);
const cssVar = function (name, fallback) {
return (root.getPropertyValue(name) || '').trim() || fallback;
};
const alpha = function (color, a) {
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
return m ? 'rgba(' + parseInt(m[1], 16) + ',' + parseInt(m[2], 16) + ',' + parseInt(m[3], 16) + ',' + a + ')' : color;
};
let money;
try {
money = new Intl.NumberFormat(undefined, { style: 'currency', currency: data.currency || 'USD', maximumFractionDigits: 0 });
} catch (e) {
money = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 });
}
const short = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });
new Chart(canvas, {
type: 'bar',
data: {
labels: data.monthly.labels,
datasets: [
{ label: 'Invoiced', data: data.monthly.invoiced, backgroundColor: alpha(cssVar('--itflow-accent', '#0d6efd'), 0.75), borderRadius: 3, maxBarThickness: 22 },
{ label: 'Paid', data: data.monthly.paid, backgroundColor: alpha(cssVar('--bs-success', '#198754'), 0.8), borderRadius: 3, maxBarThickness: 22 }
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 10, boxHeight: 10, usePointStyle: true, padding: 14 } },
tooltip: { callbacks: { label: function (c) { return c.dataset.label + ': ' + money.format(c.parsed.y); } } }
},
scales: {
x: { grid: { display: false }, ticks: { maxRotation: 0, autoSkipPadding: 10 } },
y: { beginAtZero: true, grid: { color: base.grid }, border: { display: false }, ticks: { maxTicksLimit: 5, callback: function (v) { return short.format(v); } } }
}
}
});
})();