mirror of
https://github.com/itflow-org/itflow
synced 2026-11-11 07:01:16 +00:00
91 lines
3.6 KiB
JavaScript
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); } } }
|
|
}
|
|
}
|
|
});
|
|
})();
|