diff --git a/app/assets/javascripts/kanaui/dashboard.js b/app/assets/javascripts/kanaui/dashboard.js index bdff069..f7f8d20 100644 --- a/app/assets/javascripts/kanaui/dashboard.js +++ b/app/assets/javascripts/kanaui/dashboard.js @@ -1,11 +1,11 @@ -$(document).ready(function() { - var spinOptions = { - top: '150px', - lines: 10, - length: 8, - width: 4, - radius: 8, - speed: 1 - } - $('#loading-spinner').spin(spinOptions); +$(document).ready(function () { + var spinOptions = { + top: "150px", + lines: 10, + length: 8, + width: 4, + radius: 8, + speed: 1, + }; + $("#loading-spinner").spin(spinOptions); }); diff --git a/app/assets/javascripts/kanaui/kiddo/axes.js b/app/assets/javascripts/kanaui/kiddo/axes.js index 7f1b812..73a4271 100644 --- a/app/assets/javascripts/kanaui/kiddo/axes.js +++ b/app/assets/javascripts/kanaui/kiddo/axes.js @@ -1,20 +1,18 @@ -;(function(Kiddo, d3){ - Kiddo.Axes = function(){ +(function (Kiddo, d3) { + Kiddo.Axes = function () { var self = this; - var makeXAxis = function(){ - return d3.svg.axis() - .scale(self.x) - .orient("bottom") - .ticks(6); - } + var makeXAxis = function () { + return d3.svg.axis().scale(self.x).orient("bottom").ticks(6); + }; - var makeYAxis = function(){ - return d3.svg.axis() + var makeYAxis = function () { + return d3.svg + .axis() .scale(self.y) .orient("left") - .tickFormat(d3.format(',d')); - } + .tickFormat(d3.format(",d")); + }; var xAxis = makeXAxis(); var yAxis = makeYAxis(); @@ -22,34 +20,43 @@ return { x: xAxis, y: yAxis, - render: function(svg, yTitle){ - svg.append("g") - .attr("class", "grid") - .attr("transform", "translate(" + self.margin_left + "," + self.height + ")") - .call(makeXAxis().tickSize(-self.height, 0, 0).tickFormat("")); - - svg.append("g") - .attr("class", "grid") - .attr("transform", "translate(" + self.margin_left + ",0)") - .call(makeYAxis().tickSize(-self.width, 0, 0).tickFormat("")); - - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(" + self.margin_left + "," + self.height + ")") - .call(xAxis); - - svg.append("g") - .attr("class", "y axis") - .attr("transform", "translate(" + self.margin_left + ",0)") - .call(yAxis); - //.append("text") - //.attr("transform", "rotate(-90)") - //.attr("y", 6) - //.attr("dy", ".71em") - //.style("text-anchor", "end") - //.text(yTitle); - - } + render: function (svg, yTitle) { + svg + .append("g") + .attr("class", "grid") + .attr( + "transform", + "translate(" + self.margin_left + "," + self.height + ")" + ) + .call(makeXAxis().tickSize(-self.height, 0, 0).tickFormat("")); + + svg + .append("g") + .attr("class", "grid") + .attr("transform", "translate(" + self.margin_left + ",0)") + .call(makeYAxis().tickSize(-self.width, 0, 0).tickFormat("")); + + svg + .append("g") + .attr("class", "x axis") + .attr( + "transform", + "translate(" + self.margin_left + "," + self.height + ")" + ) + .call(xAxis); + + svg + .append("g") + .attr("class", "y axis") + .attr("transform", "translate(" + self.margin_left + ",0)") + .call(yAxis); + //.append("text") + //.attr("transform", "rotate(-90)") + //.attr("y", 6) + //.attr("dy", ".71em") + //.style("text-anchor", "end") + //.text(yTitle); + }, }; }; -})(window.Kiddo = window.Kiddo || {}, d3) +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/kiddo/charts/line_chart.js b/app/assets/javascripts/kanaui/kiddo/charts/line_chart.js index 66c9a93..b75b749 100644 --- a/app/assets/javascripts/kanaui/kiddo/charts/line_chart.js +++ b/app/assets/javascripts/kanaui/kiddo/charts/line_chart.js @@ -1,72 +1,156 @@ -;(function(Kiddo, d3){ - Kiddo.LineChart = function(){ +(function (Kiddo, d3) { + Kiddo.LineChart = function () { var self = this; this.x = d3.time.scale().range([0, this.width]); this.y = d3.scale.linear().range([this.height, 0]); - var valueline = d3.svg.line() - .x(function(d) { return self.x(d.x); }) - .y(function(d) { return self.y(d.y); }); + var valueline = d3.svg + .line() + .x(function (d) { + return self.x(d.x); + }) + .y(function (d) { + return self.y(d.y); + }) + .interpolate("monotone"); // Smooth line interpolation var axes = Kiddo.Axes.apply(this); var helper = new Kiddo.Helper(); - self.color = d3.scale.category10(); + // Custom blue color theme matching the image + var blueColors = [ + "#1565C0", + "#1976D2", + "#2196F3", + "#42A5F5", + "#64B5F6", + "#90CAF9", + "#BBDEFB", + "#E3F2FD", + ]; + self.color = d3.scale.ordinal().range(blueColors); return { - render: function(svg, json){ + render: function (svg, json) { var title = json.title, datasets = json.data; // Scale the range of the data before rendering axes - var allValues = datasets.reduce(function(result, element){ + var allValues = datasets.reduce(function (result, element) { return result.concat(element.values); }, []); - var x_domain = d3.extent(allValues, function(d){ + var x_domain = d3.extent(allValues, function (d) { return new Date(d.x); }); self.x.domain(x_domain); - var y_domain = [d3.min(datasets, function (datum) { - return d3.min(datum.values, function (d) { - return d.y; - }); - }), - d3.max(datasets, function (datum) { - return d3.max(datum.values, function (d) { - return d.y; - }); - })]; + var y_domain = [ + d3.min(datasets, function (datum) { + return d3.min(datum.values, function (d) { + return d.y; + }); + }), + d3.max(datasets, function (datum) { + return d3.max(datum.values, function (d) { + return d.y; + }); + }), + ]; self.y.domain(y_domain); + // Render axes first axes.render(svg, title); self.color.domain(d3.keys(datasets)); - datasets.forEach(function(dataset){ + // Create legend container at the top + var legendContainer = svg + .append("g") + .attr("class", "chart-legend") + .attr("transform", "translate(" + (self.width - 100) + ", -25)"); + + // Calculate total values for legend + var legendData = datasets.map(function (dataset, index) { + var latestValue = dataset.values[dataset.values.length - 1]; + var totalCount = dataset.values.length; + return { + name: dataset.name, + value: latestValue ? latestValue.y : 0, + count: totalCount, + color: self.color(dataset.name), + index: index, + }; + }); + + // Create legend items + var legendItems = legendContainer + .selectAll(".legend-item") + .data(legendData) + .enter() + .append("g") + .attr("class", "legend-item"); + + var xOffset = 0; + legendItems.each(function (d, i) { + var legendItem = d3.select(this); + + // Add colored circle + legendItem + .append("circle") + .attr("cx", xOffset + 6) + .attr("cy", 0) + .attr("r", 6) + .style("fill", d.color); + + // Add text label + var labelText = + d.name + " :: " + d.count + ": " + d3.format(",.2f")(d.value); + legendItem + .append("text") + .attr("x", xOffset + 18) + .attr("y", 0) + .attr("dy", "0.35em") + .style("font-size", "0.875rem") + .style("font-weight", "500") + .style("fill", "#6B7280") + .text(labelText); + + // Calculate width for next item + var textWidth = this.getBBox().width; + xOffset += textWidth + 40; // Add spacing between items + }); + + // Render data lines + datasets.forEach(function (dataset) { var data = dataset.values, name = dataset.name; - data.forEach(function(d) { - d.date = d.x.split('T')[0]; // Support both date and date/times + data.forEach(function (d) { + d.date = d.x.split("T")[0]; // Support both date and date/times d.x = helper.parseDate(d.date); d.y = +d.y; }); - svg.append('path') - .attr('class', 'line') - .attr('d', valueline(data)) + svg + .append("path") + .attr("class", "line") + .attr("d", valueline(data)) .attr("transform", "translate(" + self.margin_left + ",0)") - .style("stroke", function() { return self.color(name); }); + .style("stroke", function () { + return self.color(name); + }) + .style("stroke-width", "0.125rem") + .style("fill", "none") + .style("opacity", 0.9); }); self.datasets = datasets; Kiddo.Utils.MouseOver.apply(self).render(svg, self.x, self.y); - } - } + }, + }; }; -})(window.Kiddo = window.Kiddo || {}, d3) +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/kiddo/charts/pie_chart.js b/app/assets/javascripts/kanaui/kiddo/charts/pie_chart.js index 9cf7cb7..e297828 100644 --- a/app/assets/javascripts/kanaui/kiddo/charts/pie_chart.js +++ b/app/assets/javascripts/kanaui/kiddo/charts/pie_chart.js @@ -1,51 +1,84 @@ -;(function(Kiddo, d3){ - Kiddo.PieChart = function(){ +(function (Kiddo, d3) { + Kiddo.PieChart = function () { var self = this; var radius = Math.min(this.width, this.height) / 2; - var color = d3.scale.category10(); - var arc = d3.svg.arc() + // Custom blue color theme - matching the line chart + var blueColors = [ + "#1565C0", + "#1976D2", + "#2196F3", + "#42A5F5", + "#64B5F6", + "#90CAF9", + "#BBDEFB", + "#E3F2FD", + ]; + var color = d3.scale.ordinal().range(blueColors); + + var arc = d3.svg + .arc() .outerRadius(radius - 10) .innerRadius(0); - var pie = d3.layout.pie() + var pie = d3.layout + .pie() .sort(null) - .value(function(d) { return d.value; }); + .value(function (d) { + return d.value; + }); return { - render: function(svg, json){ + render: function (svg, json) { var data = json.data; - svg.attr('transform', "translate(" + self.width / 2 + "," + self.height / 2 + ")"); + svg.attr( + "transform", + "translate(" + self.width / 2 + "," + self.height / 2 + ")" + ); - data.forEach(function(d){ + data.forEach(function (d) { d.value = +d.value; }); - var g = svg.selectAll(".arc") + var g = svg + .selectAll(".arc") .data(pie(data)) - .enter().append("g") + .enter() + .append("g") .attr("class", "arc"); g.append("path") .attr("d", arc) - .style("fill", function(d) { return color(d.data.value); }); + .style("fill", function (d, i) { + return color(i); + }) // Use index for consistent coloring + .style("stroke", "#ffffff") + .style("stroke-width", "1px"); // Add white borders for better separation - var colorCircle = function(value){ - return( - '' + var colorCircle = function (value, index) { + return ( + '' ); - } + }; g.append("foreignObject") .attr("width", 200) .attr("height", 150) .attr("dy", ".35em") .attr("x", 250) - .attr("y", function(d, i) { return 50 * i - 200; }) - .html(function(d) { return colorCircle(d.data.value) + d.data.label + ": " + d.data.value; }) + .attr("y", function (d, i) { + return 50 * i - 200; + }) + .html(function (d, i) { + return ( + colorCircle(d.data.value, i) + d.data.label + ": " + d.data.value + ); + }) .attr("class", "chart_values"); - } - } + }, + }; }; -})(window.Kiddo = window.Kiddo || {}, d3) +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js b/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js index 417b930..e472b41 100644 --- a/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js +++ b/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js @@ -1,46 +1,58 @@ -;(function(Kiddo, d3){ +(function (Kiddo, d3) { Kiddo.Utils = Kiddo.Utils || {}; - Kiddo.Utils.MouseOver = function(){ + Kiddo.Utils.MouseOver = function () { var self = this; var helper = new Kiddo.Helper(); return { - render: function(svg, x, y){ - var focus = svg.append("g") - .attr("class", "focus") - .style("display", "none"); + render: function (svg, x, y) { + var focus = svg + .append("g") + .attr("class", "focus") + .style("display", "none"); - var canvas = svg.append("g") + var canvas = svg + .append("g") .attr("id", "mouseover_canvas") .style("display", "none"); - var info = canvas.append("rect") + var info = canvas + .append("rect") .attr("class", "information") .attr("width", self.width / 2); // The magic: - svg.append("rect") + svg + .append("rect") .attr("class", "overlay") .attr("width", self.width) .attr("height", self.height) - .attr('transform', 'translate(' + self.margin_left + ',0)') - .on("mouseover", function() { focus.style("display", null); canvas.style("display", null); }) - .on("mouseout", function() { focus.style("display", "none"); canvas.style("display", "none"); }) + .attr("transform", "translate(" + self.margin_left + ",0)") + .on("mouseover", function () { + focus.style("display", null); + canvas.style("display", null); + }) + .on("mouseout", function () { + focus.style("display", "none"); + canvas.style("display", "none"); + }) .on("mousemove", mousemove); - var infoTitleBg = canvas.append("rect") + var infoTitleBg = canvas + .append("rect") .attr("class", "info-title__bg") .attr("width", self.width) .attr("height", 30); - var infoTitle = canvas.append("text") + var infoTitle = canvas + .append("text") .attr("dy", "1em") .attr("dx", 0) .attr("class", "info-title") .attr("id", "info-title"); - var addInfoDimensions = function(element) { + var addInfoDimensions = function (element) { // On mouseover, element is the current label_idx var box = element.node().getBBox(); // infoBox is the .information rect @@ -53,12 +65,16 @@ info.attr("width", box.width + margin); infoTitleBg.attr("width", box.width + margin); - $('#mouseover_canvas #info-title').attr("dx", (box.width + margin) / 2 - infoTitleBox.width / 2); + $("#mouseover_canvas #info-title").attr( + "dx", + (box.width + margin) / 2 - infoTitleBox.width / 2 + ); } }; - self.datasets.forEach(function(element, index){ - focus.append("circle") + self.datasets.forEach(function (element, index) { + focus + .append("circle") .attr("r", 4.5) .attr("id", "circle_" + index) .attr("transform", "translate(" + self.margin_left + ",0)"); @@ -67,14 +83,14 @@ function mousemove() { var _this = this; - $('#mouseover_canvas .chart_values').detach().remove(); - $('#mouseover_canvas .chart_circles').detach().remove(); + $("#mouseover_canvas .chart_values").detach().remove(); + $("#mouseover_canvas .chart_circles").detach().remove(); info.attr("height", infoTitleBg.node().getBBox().height + 10); info.attr("width", 1); infoTitle.attr("width", 1); var elementsForLegend = []; - self.datasets.forEach(function(element, index){ + self.datasets.forEach(function (element, index) { var data = element.values; var name = element.name; @@ -84,58 +100,63 @@ d1 = data[i]; if (d0 !== undefined && d1 !== undefined) { - var d = x0 - d0.x > d1.x - x0 ? d1 : d0; + var d = x0 - d0.x > d1.x - x0 ? d1 : d0; } else if (d0 !== undefined) { - var d = d0; + var d = d0; } else if (d1 !== undefined) { - var d = d1; + var d = d1; } else { - return; + return; } - focus.select("#circle_" + index) + focus + .select("#circle_" + index) .attr("cx", x(d.x)) .attr("cy", y(d.y)) .style("fill", self.color(name)); var canvasPosition = x(x0) > self.width / 2 ? 50 : self.width / 2; - canvas - .attr("transform", "translate(" + canvasPosition + ",0)"); + canvas.attr("transform", "translate(" + canvasPosition + ",0)"); - canvas.select("#info-title") - .text(d.date); + canvas.select("#info-title").text(d.date); - elementsForLegend.push({element: element, d: d}); + elementsForLegend.push({ element: element, d: d }); }); elementsForLegend.sort(function (a, b) { - return a.d.y > b.d.y ? -1 : (a.d.y < b.d.y ? 1 : 0); + return a.d.y > b.d.y ? -1 : a.d.y < b.d.y ? 1 : 0; }); // Limit the number of legend items (document largest values only) - elementsForLegend.slice(0,10).forEach(function(element, index) { - canvas.append("circle") - .attr("r", 5.5) - .attr("cx", 15) - .attr("cy", (index + 2) * 25) - .attr("class", "chart_circles") - .style("fill", self.color(element.element.name)) - .style("stroke", "black") - - var text = canvas.append("text") - .attr("y", (index + 2) * 25) - .attr("x", 25) - .attr("cx", 25) - .attr("dy", ".35em") - .attr("class", "chart_values") - .attr("id", "label_" + index) - .text(element.d === undefined ? element.element.name : helper.formatValueDisplay(element.element.name, element.d)); - - addInfoDimensions(text); + elementsForLegend.slice(0, 10).forEach(function (element, index) { + canvas + .append("circle") + .attr("r", 5.5) + .attr("cx", 15) + .attr("cy", (index + 2) * 25) + .attr("class", "chart_circles") + .style("fill", self.color(element.element.name)) + .style("stroke", "black"); + + var text = canvas + .append("text") + .attr("y", (index + 2) * 25) + .attr("x", 25) + .attr("cx", 25) + .attr("dy", ".35em") + .attr("class", "chart_values") + .attr("id", "label_" + index) + .text( + element.d === undefined + ? element.element.name + : helper.formatValueDisplay(element.element.name, element.d) + ); + + addInfoDimensions(text); }); } - } + }, }; }; -})(window.Kiddo = window.Kiddo || {}, d3) +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/kiddo/helper.js b/app/assets/javascripts/kanaui/kiddo/helper.js index 8530f61..967a282 100644 --- a/app/assets/javascripts/kanaui/kiddo/helper.js +++ b/app/assets/javascripts/kanaui/kiddo/helper.js @@ -1,16 +1,20 @@ -;(function(Kiddo, d3){ - Kiddo.Helper = function(){ - var formatCurrency = function(d) { return "$" + formatValue(d); }; +(function (Kiddo, d3) { + Kiddo.Helper = function () { + var formatCurrency = function (d) { + return "$" + formatValue(d); + }; var formatValue = d3.format(",.2f"); return { parseDate: d3.time.format("%Y-%m-%d").parse, - bisectDate: d3.bisector(function(d) { return d.x; }).left, + bisectDate: d3.bisector(function (d) { + return d.x; + }).left, formatCurrency: formatCurrency, formatValue: formatValue, - formatValueDisplay: function(name, d) { + formatValueDisplay: function (name, d) { return name + ": " + formatValue(d.y); // Add currency boolean on backend later -- formatCurrency(d.y); } - } - } + }, + }; }; -})(window.Kiddo = window.Kiddo || {}, d3) +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js b/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js index 8dc9ea0..85b8332 100644 --- a/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js +++ b/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js @@ -1,11 +1,13 @@ -;(function(d3, $, window, document, undefined){ - $(document).ready(function(){ - if($('#chartAnchor').length == 0) { return; } +(function (d3, $, window, document, undefined) { + $(document).ready(function () { + if ($("#chartAnchor").length == 0) { + return; + } - d3.json($('#chartAnchor').data('reports-path'), function(error, json){ - $('#loading-spinner').remove(); + d3.json($("#chartAnchor").data("reports-path"), function (error, json) { + $("#loading-spinner").remove(); - var renderer = new Kiddo.Renderer('#chartAnchor'); + var renderer = new Kiddo.Renderer("#chartAnchor"); if (error) { ajaxErrorAlert(error); @@ -14,34 +16,36 @@ var data = json[0]; - if (data === undefined || - data.data === undefined || - data.data.length == 0) { + if ( + data === undefined || + data.data === undefined || + data.data.length == 0 + ) { return renderer.noData(); } - var render = function(type){ - switch(type){ - case 'COUNTERS': + var render = function (type) { + switch (type) { + case "COUNTERS": renderer.pieChart(data); break; - case 'TIMELINE': + case "TIMELINE": renderer.lineChart(data); // Date controls only make sense for timelines - $('#date-controls').show(); + $("#date-controls").show(); break; - case 'TABLE': + case "TABLE": renderer.table(data); break; default: - console.log('No such type implemented: ' + type); + console.log("No such type implemented: " + type); renderer.noData(); } }; - try{ + try { render(data.type); - } catch (ex){ + } catch (ex) { console.log(ex); renderer.noData(); } diff --git a/app/assets/javascripts/kanaui/kiddo/renderer.js b/app/assets/javascripts/kanaui/kiddo/renderer.js index f9dfc4f..cff1985 100644 --- a/app/assets/javascripts/kanaui/kiddo/renderer.js +++ b/app/assets/javascripts/kanaui/kiddo/renderer.js @@ -1,39 +1,47 @@ -;(function(Kiddo, d3){ - Kiddo.Renderer = function(selector){ +(function (Kiddo, d3) { + Kiddo.Renderer = function (selector) { this.element = d3.select(selector); var settings = Kiddo.Settings.apply(this); var helper = new Kiddo.Helper(); var svg = this.element - .append('svg') - .attr('width', settings.raw_width) - .attr('height', settings.raw_height) - .attr('style', 'overflow: visible') - .append('g') - .attr('transform', 'translate(' + settings.margin_left + ',' + settings.margin_top + ')'); + .append("svg") + .attr("width", settings.raw_width) + .attr("height", settings.raw_height) + .attr("style", "overflow: visible") + .append("g") + .attr( + "transform", + "translate(" + settings.margin_left + "," + settings.margin_top + ")" + ); return { - lineChart: function(data){ + lineChart: function (data) { var chart = Kiddo.LineChart.apply(settings); chart.render(svg, data); }, - pieChart: function(data){var chart = Kiddo.PieChart.apply(settings); + pieChart: function (data) { + var chart = Kiddo.PieChart.apply(settings); chart.render(svg, data); }, - table: function(data){ + table: function (data) { svg.node().parentNode.remove(); - new ReportsDataTables(null).buildTable(data['data'][0], $(selector)); + new ReportsDataTables(null).buildTable(data["data"][0], $(selector)); }, - noData: function(){ - svg.append('text') - .attr('class', 'chart-info') - .attr('transform', 'translate(' + settings.width / 2 + ',' + settings.height / 2 + ')') - .text('No data to display.'); - } + noData: function () { + svg + .append("text") + .attr("class", "chart-info") + .attr( + "transform", + "translate(" + settings.width / 2 + "," + settings.height / 2 + ")" + ) + .text("No data to display."); + }, }; }; -})(window.Kiddo = window.Kiddo || {}, d3); +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/kiddo/settings.js b/app/assets/javascripts/kanaui/kiddo/settings.js index 15fb194..c061ee1 100644 --- a/app/assets/javascripts/kanaui/kiddo/settings.js +++ b/app/assets/javascripts/kanaui/kiddo/settings.js @@ -1,10 +1,13 @@ -;(function(Kiddo, d3){ - Kiddo.Settings = function(){ - var margin_top = 30, +(function (Kiddo, d3) { + Kiddo.Settings = function () { + var margin_top = 30, margin_bottom = 40, margin_left = 50, margin_right = 150, - raw_width = parseInt(this.element.node().getBoundingClientRect().width, 10), + raw_width = parseInt( + this.element.node().getBoundingClientRect().width, + 10 + ), raw_height = 500; return { @@ -15,7 +18,7 @@ raw_width: raw_width, raw_height: raw_height, width: raw_width - margin_left - margin_right, - height: raw_height - margin_top - margin_bottom - } + height: raw_height - margin_top - margin_bottom, + }; }; -})(window.Kiddo = window.Kiddo || {}, d3) +})((window.Kiddo = window.Kiddo || {}), d3); diff --git a/app/assets/javascripts/kanaui/reports.dataTables.js b/app/assets/javascripts/kanaui/reports.dataTables.js index 1fc277e..9ef85f1 100644 --- a/app/assets/javascripts/kanaui/reports.dataTables.js +++ b/app/assets/javascripts/kanaui/reports.dataTables.js @@ -1,127 +1,153 @@ function ReportsDataTables(reports) { - this.reports = reports; + this.reports = reports; } -ReportsDataTables.prototype.build = function(data, id, wrapper) { - log.debug('Building dataTable for id ' + id); - log.trace(data); - - var dataTableWrapper = $('

' + data['name'] + '

'); - wrapper.append(dataTableWrapper); - - var dataTable = $('
'); - dataTableWrapper.append(dataTable); - - var aaData = []; - for (var i in data['values']) { - aaData.push([data.values[i]['x'], data.values[i]['y']]) - } - - dataTable.DataTable({ - "aaData": aaData, - "aoColumns": [ - { "sTitle": "Date" }, - { "sTitle": "Value" }, - ] +ReportsDataTables.prototype.build = function (data, id, wrapper) { + log.debug("Building dataTable for id " + id); + log.trace(data); + + var dataTableWrapper = $( + '

' + + data["name"] + + "

" + ); + wrapper.append(dataTableWrapper); + + var dataTable = $( + '
' + ); + dataTableWrapper.append(dataTable); + + var aaData = []; + for (var i in data["values"]) { + aaData.push([data.values[i]["x"], data.values[i]["y"]]); + } + + dataTable.DataTable({ + aaData: aaData, + aoColumns: [{ sTitle: "Date" }, { sTitle: "Value" }], + }); +}; + +ReportsDataTables.prototype.buildTable = function (data, wrapper) { + var id = data["name"]; + var dataTableWrapper = $( + '
' + ); + wrapper.append(dataTableWrapper); + + var dataTable = $( + '
' + ); + dataTableWrapper.append(dataTable); + + var aaData = []; + for (var i in data["values"]) { + aaData.push(data["values"][i]); + } + + var aoColumns = []; + var columnsVisible = []; + for (var i in data["header"]) { + var isVisible = isColumnVisible(data["header"][i]); + aoColumns.push({ + sTitle: data["header"][i], + name: data["header"][i], + visible: isVisible, }); -} + if (isVisible) { + columnsVisible.push(data["header"][i]); + } + } -ReportsDataTables.prototype.buildTable = function(data, wrapper) { - var id = data['name']; - var dataTableWrapper = $('
'); - wrapper.append(dataTableWrapper); + dataTable.DataTable({ + aaData: aaData, + aoColumns: aoColumns, + scrollX: true, + sDom: 'C<"clear">lfrtip', + }); - var dataTable = $('
'); - dataTableWrapper.append(dataTable); + dataTable.on("column-visibility.dt", function (e, settings, column, state) { + setColumnVisible(settings.aoColumns[column].name, state); + }); - var aaData = []; - for (var i in data['values']) { - aaData.push(data['values'][i]) - } + $("#copy-url").click(function (e) { + var pathPlusParams = $(this).data("reports-path"); + var sPageURL = decodeURIComponent(pathPlusParams.substring(1)).split("?"); + var params = sPageURL[1].split("&"); - var aoColumns = []; - var columnsVisible = []; - for (var i in data['header']) { - var isVisible = isColumnVisible(data['header'][i]); - aoColumns.push({ "sTitle": data['header'][i], "name": data['header'][i], "visible": isVisible }) - if (isVisible) { - columnsVisible.push(data['header'][i]); - } + var columnsVisible = $("#visible-table-columns").val(); + var placeholder = $("#url-placeholder"); + + var urlToShare = window.location.origin + "/" + sPageURL[0] + "?"; + for (var i in params) { + var keyValue = params[i].split("="); + if (keyValue[0] == "columns") { + continue; + } + urlToShare += "&" + params[i]; } - dataTable.DataTable({ - "aaData": aaData, - "aoColumns": aoColumns, - "scrollX": true, - "sDom": 'C<"clear">lfrtip' - }); + placeholder.val(urlToShare + "&columns=" + columnsVisible); + placeholder.removeClass("hidden"); + placeholder.select(); - dataTable.on( 'column-visibility.dt', function ( e, settings, column, state ) { - setColumnVisible(settings.aoColumns[column].name, state); - }); + document.execCommand("Copy"); + placeholder.addClass("hidden"); + alert("URL copied into the clipboard!"); + }); - $("#copy-url").click(function(e){ - var pathPlusParams = $(this).data("reports-path"); - var sPageURL = decodeURIComponent(pathPlusParams.substring(1)).split('?'); - var params = sPageURL[1].split('&'); - - var columnsVisible = $("#visible-table-columns").val(); - var placeholder = $("#url-placeholder"); - - var urlToShare = window.location.origin + "/" + sPageURL[0] + "?"; - for (var i in params) { - var keyValue = params[i].split('='); - if (keyValue[0] == 'columns') { - continue; - } - urlToShare += "&" + params[i]; - } - - placeholder.val(urlToShare + "&columns=" + columnsVisible); - placeholder.removeClass("hidden"); - placeholder.select(); - - document.execCommand("Copy"); - placeholder.addClass("hidden"); - alert("URL copied into the clipboard!") - }); + $("#visible-table-columns").val(columnsVisible.join()); +}; - $("#visible-table-columns").val(columnsVisible.join()); -} - -ReportsDataTables.prototype.buildCSVURL = function(position) { - return this.reports.buildDataURL(position, 'csv'); -} +ReportsDataTables.prototype.buildCSVURL = function (position) { + return this.reports.buildDataURL(position, "csv"); +}; function setColumnVisible(column, isVisible) { - var columnsVisible = $("#visible-table-columns").val(); - if (columnsVisible == undefined || columnsVisible == null || columnsVisible.length == 0) { - columnsVisible = []; - } else { - columnsVisible = (columnsVisible).split(","); - } - - var columnIndex = columnsVisible.indexOf(column); - if (isVisible && columnIndex == -1) { - columnsVisible.push(column); - } else if (!isVisible && columnIndex != -1) { - columnsVisible.splice(columnIndex, 1); - } - - $("#visible-table-columns").val(columnsVisible.join()); + var columnsVisible = $("#visible-table-columns").val(); + if ( + columnsVisible == undefined || + columnsVisible == null || + columnsVisible.length == 0 + ) { + columnsVisible = []; + } else { + columnsVisible = columnsVisible.split(","); + } + + var columnIndex = columnsVisible.indexOf(column); + if (isVisible && columnIndex == -1) { + columnsVisible.push(column); + } else if (!isVisible && columnIndex != -1) { + columnsVisible.splice(columnIndex, 1); + } + + $("#visible-table-columns").val(columnsVisible.join()); } function isColumnVisible(column) { - var columnsVisible = $("#visible-table-columns").val(); - if ((columnsVisible == undefined || columnsVisible == null || columnsVisible.length == 0) && column != 'tenant_record_id') { - return true; - } - columnsVisible = (columnsVisible).split(","); - - for (var i in columnsVisible) { - if (columnsVisible[i] == column) { - return true; - } + var columnsVisible = $("#visible-table-columns").val(); + if ( + (columnsVisible == undefined || + columnsVisible == null || + columnsVisible.length == 0) && + column != "tenant_record_id" + ) { + return true; + } + columnsVisible = columnsVisible.split(","); + + for (var i in columnsVisible) { + if (columnsVisible[i] == column) { + return true; } + } - return false; + return false; } diff --git a/app/assets/stylesheets/kanaui/kanaui.css b/app/assets/stylesheets/kanaui/kanaui.css index e8b0f4c..fc3cd73 100644 --- a/app/assets/stylesheets/kanaui/kanaui.css +++ b/app/assets/stylesheets/kanaui/kanaui.css @@ -4,3 +4,770 @@ * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_tree . */ + +.kenui-analytics-dashboard-index .analytics-header { + display: flex; + align-items: start; + justify-content: space-between; + padding: 0; + border-bottom: 0.0625rem solid #e9eaeb; +} + +.kenui-analytics-dashboard-index .analytics-header h2 { + font-weight: 600; + font-size: 1.125rem; + line-height: 2.5rem; + color: #414651; + margin-bottom: 1.25rem; +} + +.kenui-analytics-dashboard-index .trigger-invoice-box button { + margin-left: 0.3125rem; + padding: 0.5rem 1rem; +} + +.kenui-analytics-dashboard-index .refresh-button { + font-weight: 500; + font-size: 1rem; + line-height: 1.5rem; +} + +.kenui-analytics-dashboard-index .refresh-button img { + filter: brightness(0) saturate(100%) invert(78%) sepia(47%) saturate(491%) + hue-rotate(170deg) brightness(100%) contrast(102%); +} + +.kenui-analytics-dashboard-index .custom-date-input-wrapper { + padding: 0 0.625rem !important; + border: 0.0625rem solid #d5d7da !important; + border-radius: 0.375rem; + cursor: pointer; + max-width: 8.5rem; +} + +.kenui-analytics-dashboard-index .custom-date-input-wrapper svg { + margin-right: 0.5rem; + flex-shrink: 0; +} + +.kenui-analytics-dashboard-index .custom-date-input-wrapper input { + border: none; + outline: none; + box-shadow: none; + padding: 0 !important; + font-size: 1rem; + color: #6b7280; + height: 2.5rem; +} + +.kenui-analytics-dashboard-index .custom-date-input-wrapper input::placeholder { + color: #717680; +} + +.kenui-analytics-dashboard-index .custom-date-input-wrapper input.form-control { + border: none; + box-shadow: none; + background-color: transparent; +} + +.kenui-analytics-dashboard-index select.form-select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + display: block; + height: 2.5rem; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + background-color: #fff; + background-clip: padding-box; + border: 0.0625rem solid #ced4da; + border-radius: 0.375rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 0.625rem center; + background-size: 1rem 1rem; + padding-right: 1.875rem; + max-width: 10rem; +} + +.kenui-analytics-dashboard-index select.form-select:focus { + box-shadow: none; + outline: none; + border-color: #1570ef; +} + +.kenui-analytics-dashboard-index .well ul { + list-style-type: none; + margin-top: 0.625rem; + background: #fafafa; + width: fit-content; + padding: 0.625rem; + border-radius: 0.5rem; +} + +.kenui-analytics-dashboard-index .well ul li a { + color: #717680; + font-weight: 400; + font-size: 0.875rem; + line-height: 1.25rem; + text-decoration: underline; + text-decoration-style: solid; + text-decoration-skip-ink: auto; + padding: 0.625rem; +} + +.kenui-analytics-dashboard-index .analytics-header b, +.kenui-analytics-dashboard-index .analytics-header b a { + text-decoration: none; + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + color: #414651; +} + +.kenui-analytics-dashboard-index .custom-tabs { + background-color: #fafafa; + border: 0.0625rem solid #e9eaeb; + border-radius: 0.375rem; + display: flex; + align-items: center; + gap: 0.25rem; + margin-bottom: 1.5rem; + padding: 0.25rem; + width: fit-content; + height: 2.5rem; +} + +.kenui-analytics-dashboard-index .custom-tabs a.custom-tab { + background: transparent; + border: none; + font-weight: 500; + font-size: 0.875rem; + color: #717680; + padding: 0.375rem 0.75rem; + border-radius: 0.25rem; + cursor: pointer; + height: 2rem; + display: flex; + align-items: center; + text-decoration: none; +} + +.kenui-analytics-dashboard-index .custom-tabs .activelink { + color: #414651 !important; + background-color: #ffffff !important; + box-shadow: 0 0.0625rem 0.188rem rgba(10, 13, 18, 0.1); +} + +.kenui-analytics-dashboard-index #chartAnchor { + background: #ffffff; +} + +.kenui-analytics-dashboard-index #chartAnchor svg { + background: #ffffff; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +.kenui-analytics-dashboard-index .grid line { + stroke: transparent; + stroke-width: 0.0625rem; + stroke-opacity: 1; + shape-rendering: crispEdges; +} + +.kenui-analytics-dashboard-index .grid:nth-of-type(2) line { + stroke: #f3f4f6; + stroke-opacity: 0.6; +} + +.kenui-analytics-dashboard-index .grid path { + stroke: none; + fill: none; +} + +.kenui-analytics-dashboard-index .axis line, +.kenui-analytics-dashboard-index .axis path { + stroke: #f5f5f5; + stroke-width: 0.0625rem; + shape-rendering: crispEdges; +} + +.kenui-analytics-dashboard-index .y.axis path { + stroke: transparent !important; + stroke-width: 0.0625rem; +} + +.kenui-analytics-dashboard-index .y.axis line, +.kenui-analytics-dashboard-index .y.axis path { + stroke: #f5f5f5; + stroke-width: 0.0625rem; +} + +.kenui-analytics-dashboard-index .axis text { + font-size: 0.75rem; + fill: #6b7280; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +.kenui-analytics-dashboard-index .line { + fill: none; + stroke-width: 0.125rem; + stroke-linecap: round; + stroke-linejoin: round; +} + +.kenui-analytics-dashboard-index .chart-legend { + display: flex; + align-items: center; + justify-content: flex-end; +} + +.kenui-analytics-dashboard-index .chart-legend circle { + stroke: none; +} + +.kenui-analytics-dashboard-index .information { + fill: #ffffff; + stroke: #f5f5f5; + stroke-width: 0.0625rem; + filter: drop-shadow(0 0.25rem 0.375rem rgba(0, 0, 0, 0.1)); + rx: 0.25rem; +} + +.kenui-analytics-dashboard-index .info-title__bg { + fill: #f9fafb; + stroke: #f5f5f5; + stroke-width: 0.0625rem; + rx: 0.25rem 0.25rem 0 0; +} + +.kenui-analytics-dashboard-index .info-title { + font-size: 1rem; + font-weight: 600; + fill: #374151; +} + +/* app/views/kanaui/reports/edit.html.erb */ + +.kanaui-reports-edit .new-report-title { + display: flex; + align-items: center; + gap: 1rem; + font-weight: 600; + font-size: 1.125rem; + line-height: 1.75rem; + letter-spacing: 0; + color: #1b1c1e; + padding: 1.5rem 0; +} + +.kanaui-reports-edit .icon-container { + display: inline-flex; + justify-content: center; + align-items: center; + border: 0.0625rem solid #d5d7da; + border-radius: 0.375rem; + width: 2.5rem; + height: 2.5rem; + padding: 0.25rem; +} + +.kanaui-reports-edit .icon-container img { + width: 1rem; + height: 1rem; +} + +.kanaui-reports-edit .form-control { + border-radius: 6px; +} + +.kanaui-reports-edit .form-group select.form-control { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: no-repeat; + background-position: right 0.625rem center; + background-size: 1rem 1rem; + padding-right: 1.875rem; +} + +.kanaui-reports-edit select.form-control:focus { + border-color: #1570ef !important; + box-shadow: none; + outline: none; +} + +.kanaui-reports-edit .control-label { + font-size: 0.875rem !important; + font-weight: 500 !important; + line-height: 1.25rem !important; + color: #414651 !important; +} + +.kanaui-reports-edit textarea.form-control { + height: 10rem !important; +} + +.kanaui-reports-edit textarea.form-control:focus { + border-color: #1570ef !important; + outline: 0 !important; + box-shadow: none !important; +} + +.kanaui-reports-edit .form-group span { + font-weight: 400; + font-size: 0.875rem; + line-height: 1.25rem; + color: #535862; +} + +/* app/views/kanaui/reports/index.html.erb */ + +.kanaui-reports-index .configured-reports { + max-width: 80rem; + width: 100%; +} + +.kanaui-reports-index .configured-reports .configured-reports-header { + display: flex; + align-items: start; + justify-content: space-between; + padding: 0; + border-bottom: 0.0625rem solid #e9eaeb; +} + +.kanaui-reports-index .configured-reports .configured-reports-header h2 { + font-weight: 600; + font-size: 1.125rem; + line-height: 1.75rem; + color: #414651; + margin-bottom: 1.25rem; +} + +.kanaui-reports-index th { + padding: 0.375rem 1.625rem 0.375rem 0.75rem; + font-weight: 500; + font-size: 0.875rem; + line-height: 1.125rem; + letter-spacing: 0; + color: #717680; + text-transform: capitalize; +} + +.kanaui-reports-index td { + font-weight: 500 !important; + font-size: 0.875rem !important; + padding: 1rem 0.5rem !important; + line-height: 1.125rem !important; + text-align: start; + color: #535862 !important; + text-transform: capitalize !important; +} + +.kanaui-reports-index td a { + color: #175cd3 !important; +} + +.kanaui-reports-index .table-button { + background-color: transparent !important; + border: none !important; + padding: 0 !important; + flex-direction: row-reverse; + text-transform: capitalize; + gap: 0.0625rem; + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + letter-spacing: 0; + margin-left: 0.75rem; + text-decoration: none; +} + +.kanaui-reports-index .delete-button { + color: #d92d20 !important; +} + +.kanaui-reports-index .edit-button { + color: #535862 !important; +} + +.kanaui-reports-index .refresh-button { + color: #175cd3 !important; +} + +.kanaui-reports-index .modal-content { + border-radius: 1rem; + min-width: 40.5rem; +} + +.kanaui-reports-index .modal-body { + padding-bottom: 0.375rem !important; + padding-top: 1.25rem !important; +} + +.kanaui-reports-index .modal-header { + padding: 1.5rem 1.5rem 1rem 1.5rem !important; +} + +.kanaui-reports-index .modal-footer { + padding: 1rem 1.5rem 1.5rem 1.5rem !important; +} + +.kanaui-reports-index .modal-title { + font-weight: 600; + font-size: 1.125rem; + line-height: 1.75rem; + color: #181d27; +} + +.kanaui-reports-index .close-button { + background: transparent; + padding: 0; + margin-top: -1.25rem; +} + +.kanaui-reports-index .close-button:hover { + background: transparent; + padding: 0; +} + +.kanaui-reports-index .modal-header .icon-container { + display: inline-flex; + justify-content: center; + align-items: center; + border: 0.0625rem solid #d5d7da; + border-radius: 0.375rem; + width: 2.5rem; + height: 2.5rem; + padding: 0.25rem; +} + +.kanaui-reports-index .modal-header .icon-container img { + width: 1.25rem; + height: 1.25rem; +} + +.kanaui-reports-index .dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: none !important; +} + +.kanaui-reports-index p { + margin-bottom: 0 !important; +} + +.kanaui-reports-index pre { + padding: 0.625rem; + background: #f4f4f4; + border-radius: 0.5rem; + overflow: auto; + white-space: pre-wrap; + word-wrap: break-word; +} + +/* app/views/kanaui/reports/new.html.erb */ + +.kanaui-reports-new .new-report-title { + display: flex; + align-items: center; + gap: 1rem; + font-weight: 600; + font-size: 1.125rem; + line-height: 1.75rem; + letter-spacing: 0; + color: #1b1c1e; + padding: 1.5rem 0; +} + +.kanaui-reports-new .icon-container { + display: inline-flex; + justify-content: center; + align-items: center; + border: 0.0625rem solid #d5d7da; + border-radius: 0.375rem; + width: 2.5rem; + height: 2.5rem; + padding: 0.25rem; +} + +.kanaui-reports-new .icon-container img { + width: 1rem; + height: 1rem; +} + +.kanaui-reports-new .form-control { + border-radius: 0.375rem; +} + +.kanaui-reports-new .form-group select.form-control { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: no-repeat; + background-position: right 0.625rem center; + background-size: 1rem 1rem; + padding-right: 1.875rem; +} + +.kanaui-reports-new select.form-control:focus { + border-color: #1570ef !important; + box-shadow: none; + outline: none; +} + +.kanaui-reports-new .control-label { + font-size: 0.875rem !important; + font-weight: 500 !important; + line-height: 1.25rem !important; + color: #414651 !important; +} + +.kanaui-reports-new textarea.form-control { + height: 10rem !important; +} + +.kanaui-reports-new textarea.form-control:focus { + border-color: #1570ef !important; + outline: 0 !important; + box-shadow: none !important; +} + +.kanaui-reports-new .form-group span { + font-weight: 400; + font-size: 0.875rem; + line-height: 1.25rem; + color: #535862; +} + +/* app/views/kanaui/settings/index.html.erb */ + +.kanaui-settings-index .analytics-settings-title { + display: flex; + align-items: center; + gap: 1rem; + font-weight: 600; + font-size: 1.125rem; + line-height: 1.75rem; + letter-spacing: 0; + color: #1b1c1e; + padding: 1.5rem 0; +} + +.kanaui-settings-index .icon-container { + display: inline-flex; + justify-content: center; + align-items: center; + border: 0.0625rem solid #d5d7da; + border-radius: 0.375rem; + width: 2.5rem; + height: 2.5rem; + padding: 0.25rem; +} + +.kanaui-settings-index .icon-container img { + width: 1rem; + height: 1rem; +} + +.kanaui-settings-index .form-control { + border-radius: 0.375rem; +} + +.kanaui-settings-index .control-label { + font-size: 0.875rem !important; + font-weight: 500 !important; + line-height: 1.25rem !important; + color: #414651 !important; +} + +.kenui-analytics-dashboard-index li.advanced-controls span { + color: #717680; + font-weight: 400; + font-size: 0.875rem; + line-height: 1.25rem; + text-decoration-style: solid; + text-decoration-skip-ink: auto; + padding: 0.625rem; +} + +/* Advanced Controls - Slicing & Dicing Section */ +.kenui-analytics-dashboard-index .advanced-controls { + margin: 0.625rem 0; + padding: 0; +} + +.kenui-analytics-dashboard-index .advanced-controls .form-horizontal { + background: #fafafa; + border: 0.0625rem solid #e9eaeb; + border-radius: 0.5rem; + padding: 0.625rem; + margin-top: 0.625rem; +} + +.kenui-analytics-dashboard-index .advanced-controls fieldset { + border: none; + margin: 0; + padding: 0; +} + +.kenui-analytics-dashboard-index .advanced-controls legend { + font-weight: 600; + font-size: 1rem; + line-height: 1.5rem; + color: #414651; + margin: 0; + padding: 0; + border: none; + width: auto; +} + +.kenui-analytics-dashboard-index .advanced-controls .form-group { + margin-bottom: 1rem; + display: flex; + flex-direction: column; + align-items: center; +} + +.kenui-analytics-dashboard-index .advanced-controls .control-label { + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + color: #414651; + margin: 0; + min-width: 100%; + flex-shrink: 0; +} + +.url-placeholder-input { + max-width: 10rem; +} + +.kenui-analytics-dashboard-index .advanced-controls .col-sm-10 { + min-width: 100%; + flex: 1; + padding: 0; +} + +.kenui-analytics-dashboard-index .advanced-controls select.form-control { + height: 6rem; + min-width: 16rem; + width: 100%; +} + +.kenui-analytics-dashboard-index .advanced-controls select.form-control:focus { + border-color: #1570ef; + box-shadow: 0 0 0 0.125rem rgba(21, 112, 239, 0.25); + outline: none; +} + +.kenui-analytics-dashboard-index .advanced-controls .col-sm-offset-2 { + margin-left: 0; + margin-top: 0.625rem; +} + +.kenui-analytics-dashboard-index .advanced-controls .btn-default { + background-color: #1570ef; + border: 0.0625rem solid #1570ef; + color: #ffffff; + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0.5rem 0.625rem; + border-radius: 0.375rem; + transition: all 0.15s ease-in-out; + cursor: pointer; +} + +.kenui-analytics-dashboard-index .advanced-controls .btn-default:hover { + background-color: #0d5bb8; + border-color: #0d5bb8; + color: #ffffff; +} + +.kenui-analytics-dashboard-index .advanced-controls .btn-default:focus { + box-shadow: 0 0 0 0.125rem rgba(21, 112, 239, 0.25); + outline: none; +} + +/* Current Analytics Query Section */ +.kenui-analytics-dashboard-index .well ul li:has(.query-label) { + background: #f8f9fa; + border: 0.0625rem solid #e9eaeb; + border-radius: 0.5rem; + padding: 0.625rem; + margin: 0.625rem 0; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.kenui-analytics-dashboard-index .well ul li:has(.query-label) a { + color: #1570ef; + text-decoration: none; + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0; + display: inline-flex; + align-items: center; + gap: 0.5rem; + transition: color 0.15s ease-in-out; +} + +.kenui-analytics-dashboard-index .well ul li:has(.query-label) a:hover { + color: #0d5bb8; + text-decoration: underline; +} + +.kenui-analytics-dashboard-index .well ul li:has(.query-label) pre { + background: #ffffff; + border: 0.0625rem solid #d5d7da; + border-radius: 0.375rem; + padding: 0.625rem; + margin: 0; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.8125rem; + line-height: 1.4; + color: #374151; + overflow-x: auto; + white-space: pre-wrap; + word-wrap: break-word; + box-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.1); +} + +.kenui-analytics-dashboard-index .well ul li:has(.query-label) .query-label { + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + color: #414651; + margin: 0; +} + +/* Responsive adjustments for smaller screens */ +@media (max-width: 768px) { + .kenui-analytics-dashboard-index .advanced-controls .form-group { + flex-direction: column; + align-items: flex-start; + gap: 0.625rem; + } + + .kenui-analytics-dashboard-index .advanced-controls .control-label { + min-width: auto; + width: 100%; + } + + .kenui-analytics-dashboard-index .advanced-controls .col-sm-10 { + width: 100%; + } + + .kenui-analytics-dashboard-index .well ul li:has(.query-label) pre { + font-size: 0.75rem; + padding: 0.5rem 0.75rem; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/kanaui/reports.css b/app/assets/stylesheets/kanaui/reports.css index 06d0b74..2da896a 100644 --- a/app/assets/stylesheets/kanaui/reports.css +++ b/app/assets/stylesheets/kanaui/reports.css @@ -57,9 +57,23 @@ fill: #202020; } -.chart-title{ - padding: 20px; - text-indent: 30px; +.chart-title-container { + height: 31.25rem; + width: 1.25rem; + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +.chart-title { + font-weight: 500; + font-size: 0.75rem; + line-height: 1.125rem; + text-align: center; + color: #535862; + white-space: nowrap; + transform: rotate(-90deg); } .nav-element.current{ diff --git a/app/controllers/kanaui/dashboard_controller.rb b/app/controllers/kanaui/dashboard_controller.rb index 24bf770..bdfd1c5 100644 --- a/app/controllers/kanaui/dashboard_controller.rb +++ b/app/controllers/kanaui/dashboard_controller.rb @@ -19,6 +19,22 @@ def index @reports = JSON.parse(raw_reports) @report = current_report(@reports) || {} + # If no report name is provided, redirect to the default (second) report + if @raw_name.blank? && @reports.is_a?(Array) && @reports[1].present? + default_name = @reports[1]['reportName'] + query_params = { start_date: @start_date, + end_date: @end_date, + name: default_name, + smooth: params[:smooth], + sql_only: params[:sql_only], + format: params[:format] } + + query_params[:fake] = params[:fake] unless params[:fake].blank? + query_params[:type] = params[:type] unless params[:type].blank? + + redirect_to dashboard_index_path(query_params) and return + end + query = build_slice_and_dice_query # get columns visibility from query parameters to be used by tables diff --git a/app/views/kanaui/dashboard/index.html.erb b/app/views/kanaui/dashboard/index.html.erb index c1da0ee..ba9ea43 100644 --- a/app/views/kanaui/dashboard/index.html.erb +++ b/app/views/kanaui/dashboard/index.html.erb @@ -1,193 +1,316 @@ -
- -
-
-
-
- - <% if params[:name] %> -
- <%= form_tag kanaui_engine.dashboard_index_path, :class => 'form-horizontal', :method => :get do %> - - - - - -
-
- From: -
-
-
-
- To: -
-
- <% ((@report['variables'] || {})['fields'] || []).each do |definition| %> -
-
- <%= definition['name'].titleize %>: - <% # TODO Not fully implemented server side %> - <% if definition['dataType'] == 'date' %> - <% # TODO datepicker breaks :hover %> - - <% else %> - - <% end %> -
-
- <% end %> -
-
- <%= submit_tag 'Refresh', :class => 'btn btn-default' %> -
-
- <% end %> -
- <% end %> -
-
-

<%= link_to 'Available Reports', kanaui_engine.url_for(:controller => :reports) %>

- -
-
-
-
-
-
<%= link_to 'Settings', kanaui_engine.url_for(:controller => :settings) %>
-
-
-
- -
-
-
- <% if params[:name] %> -

- <%= @raw_name.titleize %> -

-
-
- -
-
- <% if @report['reportType'] == 'TABLE' %> - - Copy URL - - <% end %> - <%= link_to 'Download raw data', kanaui_engine.reports_path(params.to_h.merge(:format => 'csv')), class: 'btn btn-default' %> -
- -
-
-
    - <% if @report['reportType'] == 'TIMELINE' %> - <% at_least_two_months = params[:start_date].blank? || params[:end_date].blank? || (params[:end_date].to_date.beginning_of_month - 1.month > params[:start_date].to_date) %> - <% at_least_two_weeks = params[:start_date].blank? || params[:end_date].blank? || (params[:end_date].to_date.beginning_of_week - 1.week > params[:start_date].to_date) %> - <% if params[:smooth] != 'AVERAGE_WEEKLY' && at_least_two_weeks %> -
  • <%= link_to 'Weekly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_WEEKLY')) %>
  • - <% end %> - <% if params[:smooth] != 'AVERAGE_MONTHLY' && at_least_two_months %> -
  • <%= link_to 'Monthly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_MONTHLY')) %>
  • - <% end %> - <% if params[:smooth] != 'SUM_WEEKLY' && at_least_two_weeks %> -
  • <%= link_to 'Weekly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_WEEKLY')) %>
  • - <% end %> - <% if params[:smooth] != 'SUM_MONTHLY' && at_least_two_months %> -
  • <%= link_to 'Monthly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_MONTHLY')) %>
  • +
    + <%= render partial: 'kaui/components/breadcrumb/breadcrumb', locals: { + breadcrumbs: [ + { label: 'Settings', href: '/' }, + { label: "Analytics", href: '#' } + ] + } %> + +
    + <%= render template: 'kaui/layouts/kaui_setting_sidebar', + locals: { + identifier: "Settings", + controller: params[:controller], + menu_items: [ + { label: 'Account', path: kaui_engine.admin_tenant_path(session[:tenant_id]), controller_suffix: "#{session[:tenant_id]}", icon_path: 'kaui/setting/account.svg' }, + { label: 'Tenants', path: kaui_engine.admin_tenants_path, controller_suffix: 'admin_tenants', icon_path: 'kaui/setting/tenants.svg' }, + { label: 'Users', path: kaui_engine.admin_allowed_users_path, controller_suffix: 'admin_allowed_users', icon_path: 'kaui/setting/users.svg' }, + { label: 'Tags', path: kaui_engine.tags_path, controller_suffix: 'tags', icon_path: 'kaui/setting/tags.svg' }, + { label: 'Tag Definitions', path: kaui_engine.tag_definitions_path, controller_suffix: 'tag_definitions', icon_path: 'kaui/setting/tag-definitions.svg' }, + { label: 'Custom Fields', path: kaui_engine.custom_fields_path, controller_suffix: 'custom_fields', icon_path: 'kaui/setting/custom-field.svg' }, + { label: 'Plugin Manager', path: "/kpm", controller_suffix: 'kpm', icon_path: 'kaui/setting/plugins.svg' }, + { label: 'E-notifications', path: "/kenui", controller_suffix: 'kenui', icon_path: 'kaui/setting/e-notifications.svg' }, + { label: 'Analytics', path: "/analytics", controller_suffix: 'analytics', icon_path: 'kaui/setting/analytics.svg' }, + ] + } + %> + +
    +
    +
    +

    Analytics

    + + +
    + <%= link_to 'Available Reports', kanaui_engine.url_for(:controller => :reports) %>: + <% if Rails.env.development? && @reports.empty? %> +
      + + +
    + <% else %> + - - - - - - -
    - Filters - <% filter_fields.each do |field| %> -
    - <%= label_tag "filter_#{field['name']}", field['name'], :class => 'col-sm-2 control-label' %> -
    - <%= select_tag "filter_#{field['name']}", options_for_select(field['distinctValues']), :multiple => true, :class => 'form-control' %> -
    -
    - <% end %> -
    -
    - Dimensions to plot - <% filter_fields.each do |field| %> -
    - <%= label_tag "group_#{field['name']}", field['name'], :class => 'col-sm-2 control-label' %> -
    - <%= select_tag "group_#{field['name']}", options_for_select(field['distinctValues']), :multiple => true, :class => 'form-control' %> -
    -
    - <% end %> -
    + + <% end %> + +
    + + +
    + <% if params[:name] %> +
    + <%= form_tag kanaui_engine.dashboard_index_path, :class => 'form-vertical d-flex', :method => :get do %> +
    + + + + + +
    +
    +
    + From: +
    + + + + + + + + +
    +
    +
    + To: +
    + + + + + + -
    -
    - <%= submit_tag 'Refresh', :class => 'btn btn-default' %> + +
    + <% ((@report['variables'] || {})['fields'] || []).each do |definition| %> +
    +
    + <%= definition['name'].titleize %>: + <% # TODO Not fully implemented server side %> + <% if definition['dataType'] == 'date' %> + <% # TODO datepicker breaks :hover %> + + <% else %> + + <% end %> +
    +
    + <% end %>
    - <% end %> - -
  • Current Analytics query: <%= link_to ''.html_safe, 'http://docs.killbill.io/latest/userguide_analytics.html#_dashboard_api', :target => '_blank' %> -
    <%= params[:name] -%>
    -
  • + + + <%= render "kaui/components/button/button", { + label: "Refresh", + icon: "kaui/setting/reset.svg", + variant: "outline-secondary d-inline-flex align-items-center gap-1", + type: "submit", + html_class: "btn btn-xs kaui-dropdown custom-hover py-2 px-3 refresh-button", + } %> + + <%= link_to kanaui_engine.url_for(:controller => :reports), class: 'text-decoration-none' do %> + <%= render "kaui/components/button/button", { + label: "Configure reports", + variant: "outline-secondary d-inline-flex align-items-center gap-1", + type: "button", + html_class: "btn btn-xs kaui-dropdown custom-hover py-2 px-3 refresh-button", + } %> + <% end %> +
    +
    + <% end %> +
    <% end %> -
  • <%= link_to 'SQL query', kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %>
  • -
+
+ +
+ +
+
+ +
+
+
+ <% if params[:name] %> + + +
+
+
+

+ <%= @raw_name.titleize %> +

+
+
+
+ +
+ +
+ <%= link_to kanaui_engine.url_for(controller: :settings), class: 'text-decoration-none' do %> + <%= render "kaui/components/button/button", { + label: "Settings", + variant: "outline-secondary d-inline-flex align-items-center gap-1", + type: "button", + html_class: "btn btn-xs kaui-dropdown custom-hover py-2 px-3 refresh-button" + } %> + <% end %> + <% if @report['reportType'] == 'TABLE' %> + + Copy URL + + <% end %> + <%= link_to kanaui_engine.reports_path(params.to_h.merge(:format => 'csv')), class: 'text-decoration-none' do %> + <%= render "kaui/components/button/button", { + label: "Download raw data", + variant: "outline-secondary d-inline-flex align-items-center gap-1", + type: "button", + html_class: "btn btn-xs kaui-dropdown custom-hover py-2 px-3 refresh-button" + } %> + <% end %> +
+ +
+
+
    + <% if @report['reportType'] == 'TIMELINE' %> + <% at_least_two_months = params[:start_date].blank? || params[:end_date].blank? || (params[:end_date].to_date.beginning_of_month - 1.month > params[:start_date].to_date) %> + <% at_least_two_weeks = params[:start_date].blank? || params[:end_date].blank? || (params[:end_date].to_date.beginning_of_week - 1.week > params[:start_date].to_date) %> + <% if params[:smooth] != 'AVERAGE_WEEKLY' && at_least_two_weeks %> +
  • <%= link_to 'Weekly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_WEEKLY')) %>
  • + <% end %> + <% if params[:smooth] != 'AVERAGE_MONTHLY' && at_least_two_months %> +
  • <%= link_to 'Monthly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_MONTHLY')) %>
  • + <% end %> + <% if params[:smooth] != 'SUM_WEEKLY' && at_least_two_weeks %> +
  • <%= link_to 'Weekly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_WEEKLY')) %>
  • + <% end %> + <% if params[:smooth] != 'SUM_MONTHLY' && at_least_two_months %> +
  • <%= link_to 'Monthly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_MONTHLY')) %>
  • + <% end %> + <% end %> + <% filter_fields = ((@report['schema'] || {})['fields'] || []).select { |field| !field['distinctValues'].blank? && field['dataType'] =~ /char/ } # To ignore tenant_record_id %> + <% unless filter_fields.empty? %> +
  • + Slicing & Dicing: + <%= form_tag kanaui_engine.dashboard_index_path(params.to_h), :method => :get, :class => 'form-horizontal' do %> + + + + + + + +
    + Filters + <% filter_fields.each do |field| %> +
    + <%= label_tag "filter_#{field['name']}", field['name'], :class => 'control-label' %> +
    + <%= select_tag "filter_#{field['name']}", options_for_select(field['distinctValues']), :multiple => true, :class => 'form-control' %> +
    +
    + <% end %> +
    +
    + Dimensions to plot + <% filter_fields.each do |field| %> +
    + <%= label_tag "group_#{field['name']}", field['name'], :class => 'control-label' %> +
    + <%= select_tag "group_#{field['name']}", options_for_select(field['distinctValues']), :multiple => true, :class => 'form-control' %> +
    +
    + <% end %> +
    + +
    + <%= submit_tag 'Refresh', :class => 'btn btn-default' %> +
    + <% end %> +
  • +
  • +
    + Current Analytics query: <%= link_to ''.html_safe, 'http://docs.killbill.io/latest/userguide_analytics.html#_dashboard_api', :target => '_blank' %> +
    +
    <%= params[:name] -%>
    +
  • + <% end %> +
  • <%= link_to 'SQL query', kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %>
  • +
+
+
+ <% end %> +
+
- <% end %> + +
- <%= javascript_tag do %> - $(document).ready(function() { - $('.calendar-icon').click(function() { - $('.form-container').toggle(); - }); - }); -<% end %> - - +<% end %> \ No newline at end of file diff --git a/app/views/kanaui/reports/_form.html.erb b/app/views/kanaui/reports/_form.html.erb index afe379d..ccfaf55 100644 --- a/app/views/kanaui/reports/_form.html.erb +++ b/app/views/kanaui/reports/_form.html.erb @@ -1,63 +1,76 @@ <%= form_tag @report[:reportName].blank? ? url_for(:controller => :reports, :action => :create) : report_path(@report[:reportName]), :method => (@report[:reportName].blank? ? :post : :put), :class => 'form-horizontal' do %> -
- <%= label_tag :report_name, 'Report name', :class => 'col-sm-3 control-label' %> -
- <%= text_field_tag :report_name, @report[:reportName], :class => 'form-control', :disabled => !@report[:reportName].blank?, :read_only => !@report[:reportName].blank? %> -
-
-
- <%= label_tag :report_pretty_name, 'Pretty name', :class => 'col-sm-3 control-label' %> -
- <%= text_field_tag :report_pretty_name, @report[:reportPrettyName], :class => 'form-control' %> -
-
-
- <%= label_tag :report_type, 'Type', :class => 'col-sm-3 control-label' %> -
- <%= select_tag :report_type, options_for_select(%w(TIMELINE COUNTERS TABLE), @report[:reportType] || 'TABLE'), :class => 'form-control' %> -
-
-
- <%= label_tag :source_table_name, 'Source', :class => 'col-sm-3 control-label' %> -
- <%= text_field_tag :source_table_name, @report[:sourceTableName], :class => 'form-control' %> -
-
-
- <%= label_tag :source_name, 'Source name', :class => 'col-sm-3 control-label' %> -
- <%= text_field_tag :source_name, @report[:sourceName], :class => 'form-control' %> - Must match a database configuration entry in the Analytics plugin -
-
-
- <%= label_tag :source_query, 'SQL query', :class => 'col-sm-3 control-label' %> -
- <%= text_area_tag :source_query, @report[:sourceQuery], rows: 10, cols: 25, :class => 'form-control' %> - Don't add a trailing ; -
-
-
- <%= label_tag :refresh_procedure_name, 'Refresh procedure', :class => 'col-sm-3 control-label' %> -
- <%= text_field_tag :refresh_procedure_name, @report[:refreshProcedureName], :class => 'form-control' %> -
-
-
- <%= label_tag :refresh_frequency, 'Refresh frequency', :class => 'col-sm-3 control-label' %> -
- <%= select_tag :refresh_frequency, options_for_select(%w(HOURLY DAILY), @report[:refreshFrequency]), :class => 'form-control', :include_blank => true %> -
-
-
- <%= label_tag :refresh_hour_of_day_gmt, 'Refresh hour (GMT)', :class => 'col-sm-3 control-label' %> -
- <%= number_field_tag :refresh_hour_of_day_gmt, @report[:refreshHourOfDayGmt], {:min => 1, :max => 23, :class => 'form-control'} %> -
-
-
-
- <%= submit_tag 'Save', :class => 'btn btn-default' %> -
+
+ <%= label_tag :report_name, 'Report name', :class => 'col-sm-3 control-label' %> +
+ <%= text_field_tag :report_name, @report[:reportName], :class => 'form-control', :disabled => !@report[:reportName].blank?, :read_only => !@report[:reportName].blank? %>
+
+
+ <%= label_tag :report_pretty_name, 'Pretty name', :class => 'col-sm-3 control-label' %> +
+ <%= text_field_tag :report_pretty_name, @report[:reportPrettyName], :class => 'form-control' %> +
+
+
+ <%= label_tag :report_type, 'Type', :class => 'col-sm-3 control-label' %> +
+ <%= select_tag :report_type, options_for_select(%w(TIMELINE COUNTERS TABLE), @report[:reportType] || 'TABLE'), :class => 'form-control' %> +
+
+
+ <%= label_tag :source_table_name, 'Source', :class => 'col-sm-3 control-label' %> +
+ <%= text_field_tag :source_table_name, @report[:sourceTableName], :class => 'form-control' %> +
+
+
+ <%= label_tag :source_name, 'Source name', :class => 'col-sm-3 control-label' %> +
+ <%= text_field_tag :source_name, @report[:sourceName], :class => 'form-control' %> + Must match a database configuration entry in the Analytics plugin +
+
+
+ <%= label_tag :source_query, 'SQL query', :class => 'col-sm-3 control-label' %> +
+ <%= text_area_tag :source_query, @report[:sourceQuery], rows: 10, cols: 25, :class => 'form-control' %> + Don't add a trailing ; +
+
+
+ <%= label_tag :refresh_procedure_name, 'Refresh procedure', :class => 'col-sm-3 control-label' %> +
+ <%= text_field_tag :refresh_procedure_name, @report[:refreshProcedureName], :class => 'form-control' %> +
+
+
+ <%= label_tag :refresh_frequency, 'Refresh frequency', :class => 'col-sm-3 control-label' %> +
+ <%= select_tag :refresh_frequency, options_for_select(%w(HOURLY DAILY), @report[:refreshFrequency]), :class => 'form-control', :include_blank => true %> +
+
+
+ <%= label_tag :refresh_hour_of_day_gmt, 'Refresh hour (GMT)', :class => 'col-sm-3 control-label' %> +
+ <%= number_field_tag :refresh_hour_of_day_gmt, @report[:refreshHourOfDayGmt], {:min => 1, :max => 23, :class => 'form-control'} %> +
+
+ +
+ <%= render "kaui/components/button/button", { + label: 'Close', + variant: "outline-secondary d-inline-flex align-items-center gap-1", + type: "button", + html_class: "kaui-button custom-hover mx-2", + html_options: { + onclick: "window.history.back();" + } + } %> + <%= render "kaui/components/button/button", { + label: 'Save', + variant: "outline-secondary d-inline-flex align-items-center gap-1", + type: "submit", + html_class: "kaui-dropdown custom-hover" + } %> +
<% end %> diff --git a/app/views/kanaui/reports/_reports_table.html.erb b/app/views/kanaui/reports/_reports_table.html.erb index 04729a3..ce29499 100644 --- a/app/views/kanaui/reports/_reports_table.html.erb +++ b/app/views/kanaui/reports/_reports_table.html.erb @@ -1,4 +1,4 @@ - +
@@ -15,7 +15,7 @@ - <% reports.each do |report| %> + <% reports.each do |report| %> @@ -24,18 +24,45 @@ @@ -49,12 +76,12 @@ - <% end %> + <% end %>
Name
<%= report[:reportName] %> <%= report[:reportPrettyName] %><%= report[:sourceName] %> <% unless report[:sourceQuery].blank? %> - + Show SQL + + + <% end %> <%= report[:refreshProcedureName] %> - <%= link_to ''.html_safe, refresh_report_path(report[:reportName]), :method => :put %> - <%= link_to ''.html_safe, edit_report_path(report[:reportName]) %> - <%= link_to ' '.html_safe, report_path(report[:reportName]), :method => :delete %> + <%= link_to 'Delete', report_path(report[:reportName]), :method => :delete, :class => 'delete-button table-button' %> + <%= link_to 'Refresh', refresh_report_path(report[:reportName]), :method => :put, :class => 'refresh-button table-button' %> + <%= link_to 'Edit', edit_report_path(report[:reportName]), :class => 'edit-button table-button' %>
diff --git a/app/views/kanaui/reports/edit.html.erb b/app/views/kanaui/reports/edit.html.erb index 425568d..349081a 100644 --- a/app/views/kanaui/reports/edit.html.erb +++ b/app/views/kanaui/reports/edit.html.erb @@ -1,10 +1,17 @@ -
-
-
-

Update report

- <%= render 'form' %> +
+
+
+
+ + + + + + + Update report +
+ <%= render 'form' %> +
- -
-
+
\ No newline at end of file diff --git a/app/views/kanaui/reports/index.html.erb b/app/views/kanaui/reports/index.html.erb index f726618..02a970e 100644 --- a/app/views/kanaui/reports/index.html.erb +++ b/app/views/kanaui/reports/index.html.erb @@ -1,17 +1,33 @@ -