@schneideress/dashboardframework 20.0.25 → 20.0.28

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.
@@ -3894,7 +3894,7 @@ class RAWidgetContainer {
3894
3894
  this.lockIconFooter = this.translateService.translate('Common.lockedfieldseditinfo');
3895
3895
  this.inapplicableIconTitle = this.translateService.translate('Common.InapplicableFilters');
3896
3896
  this.inapplicableIconFooter = this.translateService.translate('Common.InapplicableFiltersDesc');
3897
- if (this.dashboardInfo.templateName == 'Emission Report' || this.dashboardInfo.templateName == 'CostUsage Report' || this.dashboardInfo.templateName == 'Savings Report') {
3897
+ if (this.dashboardInfo.templateName == 'Emission Report' || this.dashboardInfo.templateName == 'CostUsage Report' || this.dashboardInfo.templateName == 'Savings Report' || this.dashboardInfo.templateName == 'Invoice Checklist') {
3898
3898
  this.hideTitle = true;
3899
3899
  this.showOnlyDropdown = true;
3900
3900
  }
@@ -4843,7 +4843,7 @@ class RADashboardArea {
4843
4843
  this.setEmptyDashboardConfig();
4844
4844
  this.setDownloadOnlyStateConfig();
4845
4845
  this.lastViewType = this.responsiveService.IsDesktopView ? 'desktop' : 'mobile';
4846
- const gridTemplates = ['Emission Report', 'CostUsage Report', 'Site Detail', 'Savings Report'];
4846
+ const gridTemplates = ['Emission Report', 'CostUsage Report', 'Site Detail', 'Savings Report', 'Invoice Checklist'];
4847
4847
  this.hasGrid = gridTemplates.includes(this.dashboardInfo.templateName);
4848
4848
  this.responsiveService.resized.subscribe((isDesktop) => {
4849
4849
  if (this.lastViewType != (isDesktop ? 'desktop' : 'mobile') || !isDesktop) {
@@ -4980,8 +4980,291 @@ class RADashboardArea {
4980
4980
  setTimeout(() => {
4981
4981
  this.currentLoadingCount--;
4982
4982
  }, 100);
4983
+ // For expanded view, override widget dimensions after data loads
4984
+ const isExpandedView = new URLSearchParams(window.location.search).get('expanded') === 'true';
4985
+ if (isExpandedView && widgetInfo && widgetInfo.widgetInstanceId) {
4986
+ this.overrideWidgetDimensions(widgetInfo);
4987
+ }
4983
4988
  // this.loadData()
4984
4989
  }
4990
+ /**
4991
+ * Override widget height and width for expanded view based on actual data content
4992
+ * Reinitializes gridster after updating dimensions
4993
+ */
4994
+ overrideWidgetDimensions(widgetInfo) {
4995
+ // Find the gridster item for this widget
4996
+ const gridsterItem = this.widgetList.find((item) => item.widgetInfo && item.widgetInfo.widgetInstanceId === widgetInfo.widgetInstanceId);
4997
+ if (gridsterItem && gridsterItem.widgetElement) {
4998
+ // Wait for DOM to update after data load - use multiple attempts to catch async rendering
4999
+ this.measureAndUpdateWidgetDimensions(gridsterItem, 0);
5000
+ }
5001
+ }
5002
+ /**
5003
+ * Measure widget content and update dimensions, with retry logic for async data loading
5004
+ */
5005
+ measureAndUpdateWidgetDimensions(gridsterItem, attempt = 0, maxAttempts = 8) {
5006
+ setTimeout(() => {
5007
+ // Get the actual rendered height of the widget content
5008
+ const widgetElement = gridsterItem.widgetElement;
5009
+ let element = null;
5010
+ // widgetElement is the ElementRef from widgetLoaded event (wcWrapper.nativeElement)
5011
+ if (widgetElement) {
5012
+ if (widgetElement.nativeElement) {
5013
+ element = widgetElement.nativeElement;
5014
+ }
5015
+ else if (widgetElement instanceof HTMLElement) {
5016
+ element = widgetElement;
5017
+ }
5018
+ }
5019
+ if (!element || this.gridcellHeight <= 0) {
5020
+ if (attempt < maxAttempts) {
5021
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5022
+ }
5023
+ return;
5024
+ }
5025
+ // Find the table body or grid content that contains the actual data rows
5026
+ const tbody = element.querySelector('tbody');
5027
+ const table = element.querySelector('table');
5028
+ const wcBody = element.querySelector('.wcBody');
5029
+ const wc = element.querySelector('.wc');
5030
+ const kendoGridContent = element.querySelector('.k-grid-content');
5031
+ const kendoGridTable = element.querySelector('.k-grid-table');
5032
+ // Measure the actual content height - try multiple strategies to get accurate measurement
5033
+ let contentHeight = 0;
5034
+ let measuredElement = null;
5035
+ // Strategy 1: Measure table body (most accurate for row count)
5036
+ if (tbody) {
5037
+ measuredElement = tbody;
5038
+ // Count actual rows and calculate height
5039
+ const rows = tbody.querySelectorAll('tr');
5040
+ if (rows.length > 0) {
5041
+ // Get height of first row to estimate
5042
+ const firstRow = rows[0];
5043
+ const rowHeight = firstRow.offsetHeight || 30; // Default row height if not available
5044
+ // Calculate total height based on row count
5045
+ const estimatedHeight = rows.length * rowHeight;
5046
+ // Use scrollHeight if available, otherwise use estimated
5047
+ contentHeight = Math.max(tbody.scrollHeight, estimatedHeight);
5048
+ }
5049
+ else {
5050
+ contentHeight = tbody.scrollHeight;
5051
+ }
5052
+ }
5053
+ // Strategy 2: Measure Kendo grid content
5054
+ else if (kendoGridContent) {
5055
+ measuredElement = kendoGridContent;
5056
+ contentHeight = kendoGridContent.scrollHeight;
5057
+ // Also check the table inside
5058
+ if (kendoGridTable) {
5059
+ const tableHeight = kendoGridTable.scrollHeight;
5060
+ contentHeight = Math.max(contentHeight, tableHeight);
5061
+ }
5062
+ }
5063
+ // Strategy 3: Measure table
5064
+ else if (table) {
5065
+ measuredElement = table;
5066
+ contentHeight = table.scrollHeight;
5067
+ }
5068
+ // Strategy 4: Measure widget content
5069
+ else if (wc) {
5070
+ measuredElement = wc;
5071
+ contentHeight = wc.scrollHeight;
5072
+ }
5073
+ // Strategy 5: Measure widget body
5074
+ else if (wcBody) {
5075
+ measuredElement = wcBody;
5076
+ contentHeight = wcBody.scrollHeight;
5077
+ }
5078
+ // Fallback: measure the wrapper
5079
+ else {
5080
+ measuredElement = element;
5081
+ contentHeight = element.scrollHeight;
5082
+ }
5083
+ // If scrollHeight seems constrained, try alternative measurements
5084
+ if (measuredElement) {
5085
+ // Check if scrollHeight is accurate (should be > clientHeight if content overflows)
5086
+ const scrollH = measuredElement.scrollHeight;
5087
+ const clientH = measuredElement.clientHeight;
5088
+ const offsetH = measuredElement.offsetHeight;
5089
+ // If scrollHeight equals clientHeight and seems too small, content might be constrained
5090
+ if (scrollH === clientH && scrollH < 200) {
5091
+ // Try offsetHeight
5092
+ contentHeight = Math.max(contentHeight, offsetH);
5093
+ }
5094
+ // For tables, also try measuring all rows directly
5095
+ if (tbody || table) {
5096
+ const allRows = (tbody || table)?.querySelectorAll('tr') || [];
5097
+ if (allRows.length > 0) {
5098
+ let totalRowHeight = 0;
5099
+ allRows.forEach((row) => {
5100
+ totalRowHeight += row.offsetHeight || 30;
5101
+ });
5102
+ // Use the maximum of scrollHeight and calculated row heights
5103
+ contentHeight = Math.max(contentHeight, totalRowHeight);
5104
+ }
5105
+ }
5106
+ }
5107
+ // Get header height
5108
+ const header = element.querySelector('.wcheader');
5109
+ const headerHeight = header ? header.offsetHeight : 46;
5110
+ // Calculate total height needed (content + header + padding)
5111
+ // Add sufficient padding to ensure no scrolling
5112
+ const padding = 40; // Padding for margins and spacing
5113
+ const totalHeight = contentHeight + headerHeight + padding;
5114
+ // Check if content is loaded (reasonable height)
5115
+ // For expanded view, we expect significant content
5116
+ const hasValidContent = contentHeight > 100 || (attempt >= 3 && contentHeight > 50);
5117
+ if (!hasValidContent && attempt < maxAttempts) {
5118
+ // Content not fully loaded, retry with longer delay
5119
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5120
+ return;
5121
+ }
5122
+ // Calculate rows needed based on grid cell height
5123
+ // Use Math.ceil to ensure we have enough space (round up)
5124
+ const calculatedRows = Math.ceil(totalHeight / this.gridcellHeight);
5125
+ // Add a small buffer (0.5 rows) to ensure no scrolling, then round up
5126
+ // This accounts for any rounding errors or minor spacing issues
5127
+ const bufferedRows = calculatedRows + 0.5;
5128
+ const newRows = Math.max(Math.ceil(bufferedRows), 4);
5129
+ // For expanded view, use full available width (no spacing needed since widgets stack vertically)
5130
+ const maxCols = this.responsiveService.maxColsDesktop || 12;
5131
+ const newCols = maxCols; // Full width
5132
+ // Update gridster item dimensions if changed
5133
+ const needsUpdate = gridsterItem.rows !== newRows || gridsterItem.cols !== newCols || gridsterItem.x !== 0;
5134
+ if (needsUpdate) {
5135
+ gridsterItem.rows = newRows;
5136
+ gridsterItem.cols = newCols;
5137
+ gridsterItem.x = 0; // Always start at the beginning of the row (full width)
5138
+ // Update widget info
5139
+ if (gridsterItem.widgetInfo) {
5140
+ gridsterItem.widgetInfo.height = newRows;
5141
+ gridsterItem.widgetInfo.width = newCols;
5142
+ gridsterItem.widgetInfo.position_x = 0; // Start at position 0 for full width
5143
+ }
5144
+ // Reposition widgets to prevent overlapping after dimension change
5145
+ this.repositionWidgetsAfterDimensionChange();
5146
+ // Force gridster to recalculate layout
5147
+ this.ngZone.run(() => {
5148
+ // Trigger change detection by updating the array reference
5149
+ this.widgetList = [...this.widgetList];
5150
+ // Call optionsChanged if API is available
5151
+ if (this.options && this.options.api) {
5152
+ this.options.api?.optionsChanged();
5153
+ }
5154
+ // Update area height after dimensions change
5155
+ setTimeout(() => {
5156
+ this.setAreaHeight();
5157
+ // Re-verify height after layout update to ensure accuracy
5158
+ // Check if there's still scrolling and adjust if needed
5159
+ if (attempt < maxAttempts - 1) {
5160
+ setTimeout(() => {
5161
+ // Re-measure to verify the height is correct
5162
+ const widgetEl = gridsterItem.widgetElement;
5163
+ let el = null;
5164
+ if (widgetEl) {
5165
+ el = widgetEl.nativeElement || widgetEl;
5166
+ }
5167
+ if (el) {
5168
+ const wcBody = el.querySelector('.wcBody');
5169
+ const tbody = el.querySelector('tbody');
5170
+ const contentEl = tbody || wcBody || el;
5171
+ // Check if there's scrolling (scrollHeight > clientHeight)
5172
+ if (contentEl && contentEl.scrollHeight > contentEl.clientHeight + 10) {
5173
+ // Still has scrolling, need more height
5174
+ const additionalHeight = contentEl.scrollHeight - contentEl.clientHeight;
5175
+ const additionalRows = Math.ceil(additionalHeight / this.gridcellHeight);
5176
+ if (additionalRows > 0) {
5177
+ gridsterItem.rows = gridsterItem.rows + additionalRows;
5178
+ if (gridsterItem.widgetInfo) {
5179
+ gridsterItem.widgetInfo.height = gridsterItem.rows;
5180
+ }
5181
+ this.widgetList = [...this.widgetList];
5182
+ if (this.options && this.options.api) {
5183
+ this.options.api?.optionsChanged();
5184
+ }
5185
+ setTimeout(() => this.setAreaHeight(), 100);
5186
+ }
5187
+ }
5188
+ }
5189
+ // Continue with normal retry logic
5190
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5191
+ }, 400);
5192
+ }
5193
+ }, 200);
5194
+ });
5195
+ }
5196
+ else if (attempt < maxAttempts - 1) {
5197
+ // Dimensions didn't change, but verify one more time that content is fully loaded
5198
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5199
+ }
5200
+ }, attempt === 0 ? 1500 : attempt < 3 ? 800 : 500); // Longer initial wait for data to fully render
5201
+ }
5202
+ /**
5203
+ * Reposition widgets to prevent overlapping after dimension changes in expanded view
5204
+ * When 2 widgets are in the same row, the 2nd widget moves to next row after the 1st widget's height
5205
+ */
5206
+ repositionWidgetsAfterDimensionChange() {
5207
+ if (this.widgetList.length === 0)
5208
+ return;
5209
+ // Sort widgets by their original Y position (row), then by X position (left to right)
5210
+ const sortedWidgets = [...this.widgetList].sort((a, b) => {
5211
+ const aY = a.y || 0;
5212
+ const bY = b.y || 0;
5213
+ if (aY !== bY) {
5214
+ return aY - bY;
5215
+ }
5216
+ // If same row, sort by X position (left to right)
5217
+ return (a.x || 0) - (b.x || 0);
5218
+ });
5219
+ // Group widgets by their original row (Y position) before repositioning
5220
+ const rowGroups = new Map();
5221
+ sortedWidgets.forEach(widget => {
5222
+ const originalY = widget.y || 0;
5223
+ if (!rowGroups.has(originalY)) {
5224
+ rowGroups.set(originalY, []);
5225
+ }
5226
+ rowGroups.get(originalY).push(widget);
5227
+ });
5228
+ // Sort rows by Y position
5229
+ const sortedRows = Array.from(rowGroups.entries()).sort((a, b) => a[0] - b[0]);
5230
+ let currentY = 0;
5231
+ // Process each original row
5232
+ sortedRows.forEach(([originalRowY, widgetsInRow]) => {
5233
+ // Sort widgets in this row by X position (left to right)
5234
+ widgetsInRow.sort((a, b) => (a.x || 0) - (b.x || 0));
5235
+ // Process widgets sequentially: first widget at currentY, subsequent widgets after previous widget's height
5236
+ // All widgets get x=0 since they take full width and stack vertically
5237
+ widgetsInRow.forEach((widget, index) => {
5238
+ if (index === 0) {
5239
+ // First widget in row: place at currentY
5240
+ widget.y = currentY;
5241
+ }
5242
+ else {
5243
+ // Subsequent widgets: place after the previous widget's bottom edge
5244
+ const previousWidget = widgetsInRow[index - 1];
5245
+ const previousWidgetHeight = previousWidget.rows || 0;
5246
+ const previousWidgetY = previousWidget.y || 0;
5247
+ widget.y = previousWidgetY + previousWidgetHeight;
5248
+ }
5249
+ // Set x to 0 for all widgets (full width, no horizontal spacing)
5250
+ widget.x = 0;
5251
+ // Update widget info
5252
+ if (widget.widgetInfo) {
5253
+ widget.widgetInfo.position_y = widget.y;
5254
+ widget.widgetInfo.position_x = 0; // Full width, start at position 0
5255
+ }
5256
+ });
5257
+ // Calculate where the next row should start
5258
+ // If multiple widgets in row, use the last widget's bottom edge
5259
+ // Otherwise, use the first widget's bottom edge
5260
+ if (widgetsInRow.length > 0) {
5261
+ const lastWidget = widgetsInRow[widgetsInRow.length - 1];
5262
+ const lastWidgetY = lastWidget.y || 0;
5263
+ const lastWidgetHeight = lastWidget.rows || 0;
5264
+ currentY = lastWidgetY + lastWidgetHeight;
5265
+ }
5266
+ });
5267
+ }
4985
5268
  isElementLoaded(el) {
4986
5269
  if (el) {
4987
5270
  let rect = el.getBoundingClientRect();
@@ -5122,6 +5405,8 @@ class RADashboardArea {
5122
5405
  };
5123
5406
  let _the = this;
5124
5407
  this.isSiteDetailDashboard = this.dashboardInfo?.templateName === 'Site Detail';
5408
+ // Check if expanded view is requested
5409
+ //const isExpandedView = new URLSearchParams(window.location.search).get('expanded') === 'true';
5125
5410
  this.dashboardService.getWidgetsByDashboardArea(widgetInfo, this.appConfig).subscribe(widgets => {
5126
5411
  if (this.isSiteDetailDashboard) {
5127
5412
  widgets = this.arrangeWidgetsInTwoColumnGrid(widgets);