kanaui 5.0.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6d1a8dc2490699d189f74a1c0e895e29563a342eee594a0739d0dc5b47bafd4
4
- data.tar.gz: 83b3fe017d28bb1c44c2ff1641ead0c64a2c8fab62607d533f415f2609f651ae
3
+ metadata.gz: 0c74b8f72b208b13b20e079654c00f793b3d0c663cd33f9dbb4957aa6fd420e6
4
+ data.tar.gz: 859e32a01514363bb64f4c3772c0d6b70b2ed72fb0daa0d4bda8facef7bba877
5
5
  SHA512:
6
- metadata.gz: fa06748ad56e379a637fcc1348af0fdbc922d0c0a3923ec88abeaf7d56b0994c5bcae3a5b02e354e11927e74015f7764a3ea334a568f831a633d98d9a64d1946
7
- data.tar.gz: 0a89abd1fccd25272ece9b7c01a725f64f177d41574e35c7611f80f3ba57f49ab7411426231a4495425549d9684c873fdfd2029a77bb25e9ffebbe703e949f38
6
+ metadata.gz: 52219b23e1cdd624809a26bc71ab8ea903d3c649ff5e5e6d3fe74538c850341680a189d9e50f1c19097df907430b49dc27009f63319a27f110635b3a4afa6589
7
+ data.tar.gz: 2606b237d12c8e5f4ecf5bcca19bed63ef34eb38c0adb862f4924f3e3d6afe898fdccf53474d328d49cbaa9026d38253f88e4306f5049937c9fd46ccb4601336
@@ -68,26 +68,24 @@
68
68
  return dataset.name;
69
69
  }));
70
70
 
71
- // Create legend container at the top
71
+ // Create legend container to the right of the chart area
72
72
  var legendContainer = svg
73
73
  .append("g")
74
74
  .attr("class", "chart-legend")
75
- .attr("transform", "translate(" + (self.width - 100) + ", -25)");
75
+ .attr("transform", "translate(" + (self.width + self.margin_left + 15) + ", 10)");
76
76
 
77
77
  // Calculate total values for legend
78
78
  var legendData = datasets.map(function (dataset, index) {
79
79
  var latestValue = dataset.values[dataset.values.length - 1];
80
- var totalCount = dataset.values.length;
81
80
  return {
82
81
  name: dataset.name,
83
82
  value: latestValue ? latestValue.y : 0,
84
- count: totalCount,
85
83
  color: self.color(dataset.name),
86
84
  index: index,
87
85
  };
88
86
  });
89
87
 
90
- // Create legend items
88
+ // Create legend items as a vertical column
91
89
  var legendItems = legendContainer
92
90
  .selectAll(".legend-item")
93
91
  .data(legendData)
@@ -95,34 +93,30 @@
95
93
  .append("g")
96
94
  .attr("class", "legend-item");
97
95
 
98
- var xOffset = 0;
99
- legendItems.each(function (d, i) {
96
+ var yOffset = 0;
97
+ legendItems.each(function (d) {
100
98
  var legendItem = d3.select(this);
101
99
 
102
- // Add colored circle
103
100
  legendItem
104
101
  .append("circle")
105
- .attr("cx", xOffset + 6)
106
- .attr("cy", 0)
102
+ .attr("cx", 6)
103
+ .attr("cy", yOffset)
107
104
  .attr("r", 6)
108
105
  .style("fill", d.color);
109
106
 
110
- // Add text label
111
107
  var labelText =
112
- helper.formatSeriesName(d.name) + " (" + d.count + "): " + d3.format(",.2f")(d.value);
108
+ helper.formatSeriesName(d.name, self.reportName) + ": " + helper.formatValue(d.value);
113
109
  legendItem
114
110
  .append("text")
115
- .attr("x", xOffset + 18)
116
- .attr("y", 0)
111
+ .attr("x", 18)
112
+ .attr("y", yOffset)
117
113
  .attr("dy", "0.35em")
118
114
  .style("font-size", "0.875rem")
119
115
  .style("font-weight", "500")
120
116
  .style("fill", "#6B7280")
121
117
  .text(labelText);
122
118
 
123
- // Calculate width for next item
124
- var textWidth = this.getBBox().width;
125
- xOffset += textWidth + 40; // Add spacing between items
119
+ yOffset += 22;
126
120
  });
127
121
 
128
122
  // Render data lines
@@ -59,15 +59,16 @@
59
59
  var infoBox = info.node().getBBox();
60
60
  var infoTitleBox = infoTitle.node().getBBox();
61
61
  var margin = 40;
62
+ var minWidth = Math.max(box.width, infoTitleBox.width) + margin;
62
63
 
63
64
  info.attr("height", infoBox.height + box.height + 7);
64
- if (infoBox.width < box.width) {
65
- info.attr("width", box.width + margin);
66
- infoTitleBg.attr("width", box.width + margin);
65
+ if (infoBox.width < minWidth) {
66
+ info.attr("width", minWidth);
67
+ infoTitleBg.attr("width", minWidth);
67
68
 
68
69
  $("#mouseover_canvas #info-title").attr(
69
70
  "dx",
70
- (box.width + margin) / 2 - infoTitleBox.width / 2
71
+ minWidth / 2 - infoTitleBox.width / 2
71
72
  );
72
73
  }
73
74
  };
@@ -81,7 +82,9 @@
81
82
  });
82
83
 
83
84
  function mousemove(event) {
84
- var mouseX = d3.pointer(event, this)[0];
85
+ var pointer = d3.pointer(event, this);
86
+ var mouseX = pointer[0];
87
+ var mouseY = pointer[1];
85
88
 
86
89
  $("#mouseover_canvas .chart_values").detach().remove();
87
90
  $("#mouseover_canvas .chart_circles").detach().remove();
@@ -115,11 +118,7 @@
115
118
  .attr("cy", y(d.y))
116
119
  .style("fill", self.color(name));
117
120
 
118
- var canvasPosition = x(x0) > self.width / 2 ? 50 : self.width / 2;
119
-
120
- canvas.attr("transform", "translate(" + canvasPosition + ",0)");
121
-
122
- canvas.select("#info-title").text(d.date);
121
+ canvas.select("#info-title").text(helper.formatDate(d.date));
123
122
 
124
123
  elementsForLegend.push({ element: element, d: d });
125
124
  });
@@ -150,11 +149,22 @@
150
149
  .text(
151
150
  element.d === undefined
152
151
  ? element.element.name
153
- : helper.formatValueDisplay(element.element.name, element.d)
152
+ : helper.formatValueDisplay(element.element.name, element.d, self.reportName)
154
153
  );
155
154
 
156
155
  addInfoDimensions(text);
157
156
  });
157
+
158
+ // Position tooltip to follow the cursor; flip left when near the right edge.
159
+ var tooltipOffset = 15;
160
+ var tooltipWidth = info.node().getBBox().width;
161
+ var tooltipHeight = info.node().getBBox().height;
162
+ var localX = (mouseX + tooltipOffset + tooltipWidth > self.width)
163
+ ? mouseX - tooltipWidth - tooltipOffset
164
+ : mouseX + tooltipOffset;
165
+ var canvasX = localX + self.margin_left;
166
+ var canvasY = Math.max(0, mouseY - 150);
167
+ canvas.attr("transform", "translate(" + canvasX + "," + canvasY + ")");
158
168
  }
159
169
  },
160
170
  };
@@ -1,9 +1,11 @@
1
1
  (function (Kiddo, d3) {
2
2
  Kiddo.Helper = function () {
3
+ var formatValue = function (d) {
4
+ return d % 1 === 0 ? d3.format(",d")(d) : d3.format(",.2f")(d);
5
+ };
3
6
  var formatCurrency = function (d) {
4
7
  return "$" + formatValue(d);
5
8
  };
6
- var formatValue = d3.format(",.2f");
7
9
 
8
10
  var humanizeSegment = function (segment) {
9
11
  segment = String(segment || "")
@@ -30,33 +32,75 @@
30
32
  .join(" ");
31
33
  };
32
34
 
33
- var formatSeriesName = function (name) {
35
+ var formatSeriesName = function (name, reportName) {
34
36
  return String(name || "")
35
37
  .split(/\s*::\s*/)
38
+ .filter(function (segment) {
39
+ // Remove numeric-only segments (tenant record id)
40
+ return !/^\d+$/.test(segment.trim());
41
+ })
36
42
  .map(function (segment) {
37
43
  var parts = segment.split(/\s*:\s*/);
38
44
  if (parts.length > 1) {
39
- return humanizeSegment(parts[0]) + " (" + humanizeSegment(parts.slice(1).join(": ")) + ")";
40
- }
45
+ var qualifier = humanizeSegment(parts.slice(1).join(": "));
46
+ var label = (reportName && parts[0].trim().toLowerCase() === "count")
47
+ ? reportName
48
+ : humanizeSegment(parts[0]);
41
49
 
50
+ // For count-based reports, a numeric qualifier is usually tenant id
51
+ // and should not be displayed in UI labels.
52
+ if (reportName && parts[0].trim().toLowerCase() === "count" && /^\d+$/.test(qualifier)) {
53
+ return label;
54
+ }
55
+
56
+ return label + " (" + qualifier + ")";
57
+ }
42
58
  return humanizeSegment(segment);
43
59
  })
44
60
  .filter(function (segment) {
45
61
  return segment.length > 0;
46
62
  })
47
- .join(" / ");
63
+ .join(" : ");
64
+ };
65
+
66
+ // Extracts just the qualifier part (e.g. "EUR" from "count: EUR :: 1") for compact tooltip labels
67
+ var formatSeriesLabel = function (name) {
68
+ var segments = String(name || "")
69
+ .split(/\s*::\s*/)
70
+ .filter(function (segment) {
71
+ return !/^\d+$/.test(segment.trim());
72
+ });
73
+
74
+ if (segments.length === 0) return humanizeSegment(name);
75
+
76
+ var parts = segments[0].split(/\s*:\s*/);
77
+ if (parts.length > 1) {
78
+ return humanizeSegment(parts.slice(1).join(": "));
79
+ }
80
+ return humanizeSegment(segments[0]);
81
+ };
82
+
83
+ var parseDateFn = d3.timeParse("%Y-%m-%d");
84
+ var formatDateFn = d3.timeFormat("%b %d, %Y");
85
+ var formatDate = function (dateStr) {
86
+ var parsed = parseDateFn(dateStr);
87
+ return parsed ? formatDateFn(parsed) : dateStr;
48
88
  };
49
89
 
50
90
  return {
51
- parseDate: d3.timeParse("%Y-%m-%d"),
91
+ parseDate: parseDateFn,
52
92
  bisectDate: d3.bisector(function (d) {
53
93
  return d.x;
54
94
  }).left,
55
95
  formatCurrency: formatCurrency,
56
96
  formatValue: formatValue,
57
97
  formatSeriesName: formatSeriesName,
58
- formatValueDisplay: function (name, d) {
59
- return formatSeriesName(name) + ": " + formatValue(d.y); // Add currency boolean on backend later -- formatCurrency(d.y); }
98
+ formatDate: formatDate,
99
+ formatValueDisplay: function (name, d, reportName) {
100
+ var seriesLabel = reportName
101
+ ? formatSeriesName(name, reportName)
102
+ : formatSeriesLabel(name);
103
+ return seriesLabel + ": " + formatValue(d.y);
60
104
  },
61
105
  };
62
106
  };
@@ -19,6 +19,7 @@
19
19
  raw_height: raw_height,
20
20
  width: raw_width - margin_left - margin_right,
21
21
  height: raw_height - margin_top - margin_bottom,
22
+ reportName: $("#chartAnchor").data("report-name") || "",
22
23
  };
23
24
  };
24
25
  })((window.Kiddo = window.Kiddo || {}), d3);
@@ -103,11 +103,40 @@
103
103
 
104
104
  .kenui-analytics-dashboard-index .well ul {
105
105
  list-style-type: none;
106
- margin-top: 0.625rem;
106
+ margin: 0.625rem 0 0;
107
107
  background: #fafafa;
108
- width: fit-content;
109
- padding: 0.625rem;
108
+ width: 100%;
109
+ padding: 1rem 1.25rem;
110
110
  border-radius: 0.5rem;
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ gap: 0.5rem 1.25rem;
114
+ align-items: flex-start;
115
+ }
116
+
117
+ /* Smoothing option links (Weekly/Monthly average/sum) render as inline pills */
118
+ .kenui-analytics-dashboard-index .well > ul > li.smoothing-option {
119
+ display: inline-flex;
120
+ }
121
+
122
+ .kenui-analytics-dashboard-index .well > ul > li.smoothing-option > a {
123
+ display: inline-flex;
124
+ align-items: center;
125
+ background: #ffffff;
126
+ border: 0.0625rem solid #d5d7da;
127
+ border-radius: 999px;
128
+ padding: 0.375rem 0.75rem;
129
+ text-decoration: none;
130
+ color: #414651;
131
+ font-weight: 500;
132
+ font-size: 0.8125rem;
133
+ line-height: 1.25rem;
134
+ transition: all 0.15s ease-in-out;
135
+ }
136
+
137
+ .kenui-analytics-dashboard-index .well > ul > li.smoothing-option > a:hover {
138
+ border-color: #1570ef;
139
+ color: #1570ef;
111
140
  }
112
141
 
113
142
  .kenui-analytics-dashboard-index .well ul li a {
@@ -168,6 +197,26 @@
168
197
  background: #ffffff;
169
198
  }
170
199
 
200
+ .kenui-analytics-dashboard-index .chart-title-container {
201
+ width: auto;
202
+ height: auto;
203
+ display: block;
204
+ justify-content: unset;
205
+ align-items: unset;
206
+ position: static;
207
+ margin-top: 15px;
208
+ }
209
+
210
+ .kenui-analytics-dashboard-index .chart-title {
211
+ font-weight: 600;
212
+ font-size: 1.25rem;
213
+ line-height: 1.75rem;
214
+ color: #111827;
215
+ white-space: normal;
216
+ transform: none;
217
+ text-align: left;
218
+ }
219
+
171
220
  .kenui-analytics-dashboard-index #chartAnchor svg {
172
221
  background: #ffffff;
173
222
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -619,20 +668,32 @@
619
668
  .kenui-analytics-dashboard-index .advanced-controls {
620
669
  margin: 0.625rem 0;
621
670
  padding: 0;
671
+ flex: 1 1 100%;
622
672
  }
623
673
 
624
674
  .kenui-analytics-dashboard-index .advanced-controls .form-horizontal {
625
- background: #fafafa;
675
+ background: #ffffff;
626
676
  border: 0.0625rem solid #e9eaeb;
627
677
  border-radius: 0.5rem;
628
- padding: 0.625rem;
678
+ padding: 1rem 1.25rem;
629
679
  margin-top: 0.625rem;
680
+ display: grid;
681
+ grid-template-columns: repeat(2, minmax(0, 1fr));
682
+ gap: 1rem 1.5rem;
683
+ align-items: start;
684
+ }
685
+
686
+ .kenui-analytics-dashboard-index .advanced-controls .form-horizontal > .form-group:last-of-type {
687
+ grid-column: 1 / -1;
688
+ justify-self: end;
689
+ margin-top: 0;
630
690
  }
631
691
 
632
692
  .kenui-analytics-dashboard-index .advanced-controls fieldset {
633
693
  border: none;
634
694
  margin: 0;
635
695
  padding: 0;
696
+ min-width: 0;
636
697
  }
637
698
 
638
699
  .kenui-analytics-dashboard-index .advanced-controls legend {
@@ -647,10 +708,10 @@
647
708
  }
648
709
 
649
710
  .kenui-analytics-dashboard-index .advanced-controls .form-group {
650
- margin-bottom: 1rem;
711
+ margin-bottom: 0.75rem;
651
712
  display: flex;
652
713
  flex-direction: column;
653
- align-items: center;
714
+ align-items: stretch;
654
715
  }
655
716
 
656
717
  .kenui-analytics-dashboard-index .advanced-controls .control-label {
@@ -675,7 +736,7 @@
675
736
 
676
737
  .kenui-analytics-dashboard-index .advanced-controls select.form-control {
677
738
  height: 6rem;
678
- min-width: 16rem;
739
+ min-width: 0;
679
740
  width: 100%;
680
741
  }
681
742
 
@@ -715,18 +776,19 @@
715
776
  }
716
777
 
717
778
  /* Current Analytics Query Section */
718
- .kenui-analytics-dashboard-index .well ul li:has(.query-label) {
719
- background: #f8f9fa;
779
+ .kenui-analytics-dashboard-index .well ul li.query-block {
780
+ background: #ffffff;
720
781
  border: 0.0625rem solid #e9eaeb;
721
782
  border-radius: 0.5rem;
722
- padding: 0.625rem;
723
- margin: 0.625rem 0;
783
+ padding: 0.75rem 1rem;
784
+ margin: 0;
724
785
  display: flex;
725
786
  flex-direction: column;
726
- gap: 0.75rem;
787
+ gap: 0.5rem;
788
+ flex: 1 1 100%;
727
789
  }
728
790
 
729
- .kenui-analytics-dashboard-index .well ul li:has(.query-label) a {
791
+ .kenui-analytics-dashboard-index .well ul li.query-block a {
730
792
  color: #1570ef;
731
793
  text-decoration: none;
732
794
  font-weight: 500;
@@ -739,12 +801,12 @@
739
801
  transition: color 0.15s ease-in-out;
740
802
  }
741
803
 
742
- .kenui-analytics-dashboard-index .well ul li:has(.query-label) a:hover {
804
+ .kenui-analytics-dashboard-index .well ul li.query-block a:hover {
743
805
  color: #0d5bb8;
744
806
  text-decoration: underline;
745
807
  }
746
808
 
747
- .kenui-analytics-dashboard-index .well ul li:has(.query-label) pre {
809
+ .kenui-analytics-dashboard-index .well ul li.query-block pre {
748
810
  background: #ffffff;
749
811
  border: 0.0625rem solid #d5d7da;
750
812
  border-radius: 0.375rem;
@@ -760,7 +822,7 @@
760
822
  box-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.1);
761
823
  }
762
824
 
763
- .kenui-analytics-dashboard-index .well ul li:has(.query-label) .query-label {
825
+ .kenui-analytics-dashboard-index .well ul li.query-block .query-label {
764
826
  font-weight: 500;
765
827
  font-size: 0.875rem;
766
828
  line-height: 1.25rem;
@@ -770,6 +832,10 @@
770
832
 
771
833
  /* Responsive adjustments for smaller screens */
772
834
  @media (max-width: 768px) {
835
+ .kenui-analytics-dashboard-index .advanced-controls .form-horizontal {
836
+ grid-template-columns: 1fr;
837
+ }
838
+
773
839
  .kenui-analytics-dashboard-index .advanced-controls .form-group {
774
840
  flex-direction: column;
775
841
  align-items: flex-start;
@@ -785,7 +851,7 @@
785
851
  width: 100%;
786
852
  }
787
853
 
788
- .kenui-analytics-dashboard-index .well ul li:has(.query-label) pre {
854
+ .kenui-analytics-dashboard-index .well ul li.query-block pre {
789
855
  font-size: 0.75rem;
790
856
  padding: 0.5rem 0.75rem;
791
857
  }
@@ -146,7 +146,7 @@ module Kanaui
146
146
  groups[field_name] = params["group_#{field_name}"]
147
147
  end
148
148
 
149
- filter_query = ''
149
+ filter_query = +''
150
150
  filters.each do |k, v|
151
151
  next if v.blank?
152
152
 
@@ -158,8 +158,7 @@ module Kanaui
158
158
  groups.each do |k, v|
159
159
  next if v.blank?
160
160
 
161
- # TODO: Make "no other" configurable
162
- query << "^dimension:#{k}(#{v.join('|')}|-)"
161
+ query << "^dimension:#{k}(#{v.join('|')})"
163
162
  end
164
163
 
165
164
  # Template variables
@@ -18,9 +18,15 @@ module Kanaui
18
18
  end
19
19
 
20
20
  def reports(start_date, end_date, name, smooth, sql_only, format, options = {})
21
- path = "#{KILLBILL_ANALYTICS_PREFIX}/reports?format=#{format}&startDate=#{start_date}&endDate=#{end_date}&name=#{name}"
22
- path = "#{path}&smooth=#{smooth}" if smooth
23
- path = "#{path}&sqlOnly=true" if sql_only.present?
21
+ query = {
22
+ 'format' => format,
23
+ 'startDate' => start_date,
24
+ 'endDate' => end_date,
25
+ 'name' => name
26
+ }
27
+ query['smooth'] = smooth if smooth
28
+ query['sqlOnly'] = 'true' if sql_only.present?
29
+ path = "#{KILLBILL_ANALYTICS_PREFIX}/reports?#{query.to_query}"
24
30
  response = KillBillClient::API.get path, {}, options
25
31
  response.body
26
32
  end
@@ -150,6 +150,11 @@
150
150
  <div class="">
151
151
  <% if params[:name] %>
152
152
 
153
+ <div class="chart-title-container mb-2">
154
+ <h2 class="chart-title" id="chart-title">
155
+ <%= @report['reportPrettyName'] %>
156
+ </h2>
157
+ </div>
153
158
  <div id="date-controls" style="display: none;">
154
159
  <div class="custom-tabs my-4">
155
160
  <% @available_start_dates.each do |key, value| %>
@@ -158,14 +163,7 @@
158
163
  </div>
159
164
  </div>
160
165
  <div id="loading-spinner"></div>
161
- <div class="d-flex w-100 align-items-center">
162
- <div class="chart-title-container">
163
- <h2 class="chart-title" id="chart-title">
164
- <%= @raw_name.titleize %>
165
- </h2>
166
- </div>
167
- <div id="chartAnchor" data-reports-path="<%= kanaui_engine.reports_path(params.to_h) %>" class="w-100 h-100"></div>
168
- </div>
166
+ <div id="chartAnchor" data-reports-path="<%= kanaui_engine.reports_path(params.to_h) %>" data-report-name="<%= @report['reportPrettyName'] %>" class="w-100"></div>
169
167
 
170
168
  <hr>
171
169
 
@@ -207,16 +205,16 @@
207
205
  <% 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) %>
208
206
  <% 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) %>
209
207
  <% if params[:smooth] != 'AVERAGE_WEEKLY' && at_least_two_weeks %>
210
- <li><%= link_to 'Weekly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_WEEKLY')) %></li>
208
+ <li class="smoothing-option"><%= link_to 'Weekly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_WEEKLY')) %></li>
211
209
  <% end %>
212
210
  <% if params[:smooth] != 'AVERAGE_MONTHLY' && at_least_two_months %>
213
- <li><%= link_to 'Monthly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_MONTHLY')) %></li>
211
+ <li class="smoothing-option"><%= link_to 'Monthly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_MONTHLY')) %></li>
214
212
  <% end %>
215
213
  <% if params[:smooth] != 'SUM_WEEKLY' && at_least_two_weeks %>
216
- <li><%= link_to 'Weekly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_WEEKLY')) %></li>
214
+ <li class="smoothing-option"><%= link_to 'Weekly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_WEEKLY')) %></li>
217
215
  <% end %>
218
216
  <% if params[:smooth] != 'SUM_MONTHLY' && at_least_two_months %>
219
- <li><%= link_to 'Monthly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_MONTHLY')) %></li>
217
+ <li class="smoothing-option"><%= link_to 'Monthly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_MONTHLY')) %></li>
220
218
  <% end %>
221
219
  <% end %>
222
220
  <% filter_fields = ((@report['schema'] || {})['fields'] || []).select { |field| !field['distinctValues'].blank? && field['dataType'] =~ /char/ } # To ignore tenant_record_id %>
@@ -259,14 +257,14 @@
259
257
  </div>
260
258
  <% end %>
261
259
  </li>
262
- <li>
260
+ <li class="query-block">
263
261
  <div class="query-label">
264
262
  Current Analytics query:&nbsp;<%= link_to '<i class="fa fa-question-circle"></i>'.html_safe, 'http://docs.killbill.io/latest/userguide_analytics.html#_dashboard_api', :target => '_blank' %>
265
263
  </div>
266
264
  <pre><%= params[:name] -%></pre>
267
265
  </li>
268
266
  <% end %>
269
- <li><%= link_to t('kanaui.dashboard.advanced_controls.sql_query'), kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %></li>
267
+ <li class="smoothing-option"><%= link_to t('kanaui.dashboard.advanced_controls.sql_query'), kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %></li>
270
268
  </ul>
271
269
  </div>
272
270
  </div>
@@ -318,4 +316,32 @@
318
316
  // Safe to navigate
319
317
  window.location.href = url;
320
318
  }
319
+
320
+ // Persist the open/closed state of #advanced-controls across page reloads
321
+ (function () {
322
+ var STORAGE_KEY = 'kanaui.advancedControls.open';
323
+ var panel = document.getElementById('advanced-controls');
324
+ if (!panel) {
325
+ return;
326
+ }
327
+ var toggle = document.querySelector('[data-bs-toggle="collapse"][href="#advanced-controls"]');
328
+
329
+ // Restore previous state before Bootstrap initializes
330
+ try {
331
+ if (localStorage.getItem(STORAGE_KEY) === '1') {
332
+ panel.classList.add('show');
333
+ if (toggle) {
334
+ toggle.setAttribute('aria-expanded', 'true');
335
+ toggle.classList.remove('collapsed');
336
+ }
337
+ }
338
+ } catch (e) { /* localStorage may be unavailable */ }
339
+
340
+ panel.addEventListener('shown.bs.collapse', function () {
341
+ try { localStorage.setItem(STORAGE_KEY, '1'); } catch (e) {}
342
+ });
343
+ panel.addEventListener('hidden.bs.collapse', function () {
344
+ try { localStorage.setItem(STORAGE_KEY, '0'); } catch (e) {}
345
+ });
346
+ })();
321
347
  <% end %>
@@ -128,13 +128,7 @@
128
128
  }
129
129
 
130
130
  document.querySelectorAll('[data-kanaui-tooltip]').forEach(function (el) {
131
- el.addEventListener('click', function (event) {
132
- event.stopPropagation();
133
- if (activeTooltipTrigger === el && tooltip.classList.contains('is-visible')) {
134
- hideTooltip();
135
- return;
136
- }
137
-
131
+ el.addEventListener('mouseenter', function () {
138
132
  var rect = el.getBoundingClientRect();
139
133
  var left = window.pageXOffset + rect.right + 12;
140
134
  var top = window.pageYOffset + rect.top - 2;
@@ -153,10 +147,10 @@
153
147
  tooltip.classList.add('is-flipped');
154
148
  }
155
149
  });
156
- });
157
150
 
158
- document.addEventListener('click', function () {
159
- hideTooltip();
151
+ el.addEventListener('mouseleave', function () {
152
+ hideTooltip();
153
+ });
160
154
  });
161
155
 
162
156
  document.addEventListener('keydown', function (event) {
@@ -37,7 +37,7 @@ en:
37
37
  source_query: "SQL Query"
38
38
  source_query_help: "Don't add a trailing ;"
39
39
  source_configuration_error: "Either Source Table Name or SQL Query is required."
40
- source_name: "Source Name"
40
+ source_name: "Remote Database Name"
41
41
  source_name_help: "Must match a database configuration entry in the Analytics plugin"
42
42
  tooltips:
43
43
  report_name: "Unique identifier for your report."
@@ -59,7 +59,7 @@ en:
59
59
  pretty_name: "Pretty Name"
60
60
  type: "Type"
61
61
  source: "Source"
62
- source_name: "Source Name"
62
+ source_name: "Remote Database Name"
63
63
  source_query: "Source Query"
64
64
  refresh_procedure: "Refresh Procedure"
65
65
  refresh_frequency: "Refresh Frequency"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kanaui
4
- VERSION = '5.0.1'
4
+ VERSION = '5.0.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kanaui
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kill Bill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-20 00:00:00.000000000 Z
11
+ date: 2026-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bootstrap-datepicker-rails