Skip to content

Commit 882392f

Browse files
committed
feat: add capability to render row spanned data
Signed-off-by: snehaljha-sf <[email protected]> Signed-off-by: snehaljha-sf <[email protected]>
1 parent a4df8fb commit 882392f

File tree

3 files changed

+182
-40
lines changed

3 files changed

+182
-40
lines changed
Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
function toggleFilterDropdown() {
2-
const dropdown = document.getElementById('filter-dropdown');
3-
const chevronUp = document.getElementById('chevron-up');
4-
const chevronDown = document.getElementById('chevron-down');
1+
function toggleFilterDropdown(tableId) {
2+
const reportTable = document.getElementById(tableId);
3+
const dropdown = reportTable.querySelector('#filter-dropdown');
4+
const chevronUp = reportTable.querySelector('#chevron-up');
5+
const chevronDown = reportTable.querySelector('#chevron-down');
56

67
if (dropdown && chevronUp && chevronDown) {
78
dropdown.classList.toggle('show');
@@ -10,10 +11,11 @@ function toggleFilterDropdown() {
1011
}
1112
}
1213

13-
function filterAndSearchTable() {
14-
const table = document.getElementById('filterable-table-body');
15-
const checkboxes = document.querySelectorAll('.filter-checkbox');
16-
const searchInput = document.getElementById('name-search-input');
14+
function filterAndSearchTable(tableId) {
15+
const reportTable = document.getElementById(tableId);
16+
const table = reportTable.querySelector('#filterable-table-body');
17+
const checkboxes = reportTable.querySelectorAll('.filter-checkbox');
18+
const searchInput = reportTable.querySelector('#name-search-input');
1719
const searchText = searchInput.value.trim().toLowerCase();
1820
const filters = {};
1921
const rows = Array.from(table?.rows || []);
@@ -27,25 +29,30 @@ function filterAndSearchTable() {
2729
if (cb.checked) filters[key].push(cb.value);
2830
});
2931

30-
const noRowsMessage = document.getElementById('no-rows-message');
32+
const noRowsMessage = reportTable.querySelector('#no-rows-message');
3133

3234
// NEW: If any filter group has zero selected values → show no rows
33-
const activeFilterKeys = [...new Set([...checkboxes].map(cb => cb.getAttribute('data-filter-key')))];
35+
const activeFilterKeys = [...new Set([...checkboxes].map((cb) => cb.getAttribute('data-filter-key')))];
3436
const hasEmptyGroup = activeFilterKeys.some((key) => !filters[key] || filters[key].length === 0);
3537
if (hasEmptyGroup) {
3638
// Hide all rows and show no match message
3739
rows.forEach((row) => {
3840
if (row.id !== 'no-rows-message') row.style.display = 'none';
3941
});
4042
if (noRowsMessage) noRowsMessage.style.display = '';
41-
43+
4244
// Update visible row count
43-
const visibleRows = Array.from(table.rows).filter(row => row.style.display !== 'none' && row.id !== 'no-rows-message');
44-
document.getElementById('row-count').textContent = `Showing ${visibleRows.length} record${visibleRows.length !== 1 ? 's' : ''}`;
45+
const visibleRows = Array.from(table.rows).filter(
46+
(row) => row.style.display !== 'none' && row.id !== 'no-rows-message'
47+
);
48+
reportTable.querySelector('#row-count').textContent = `Showing ${visibleRows.length} record${
49+
visibleRows.length !== 1 ? 's' : ''
50+
}`;
4551
return;
4652
}
4753

4854
// Otherwise, apply filters and search
55+
let processedClasses = new Set();
4956
rows.forEach((row) => {
5057
if (row.id === 'no-rows-message') return;
5158

@@ -54,9 +61,7 @@ function filterAndSearchTable() {
5461
// Apply checkbox filters
5562
for (const key of Object.keys(filters)) {
5663
const selectedValues = filters[key];
57-
const cell = Array.from(row.cells).find(
58-
(c) => c.getAttribute('key') === key
59-
);
64+
const cell = Array.from(row.cells).find((c) => c.getAttribute('key') === key);
6065
const cellValue = cell?.getAttribute('value') || '';
6166
if (!selectedValues.includes(cellValue)) {
6267
show = false;
@@ -73,7 +78,11 @@ function filterAndSearchTable() {
7378
}
7479
}
7580

76-
row.style.display = show ? '' : 'none';
81+
// row.style.display = show ? '' : 'none';
82+
if (!processedClasses.has(row.classList[0])) {
83+
hideOrShowData(reportTable, row.classList[0], show);
84+
processedClasses.add(row.classList[0]);
85+
}
7786
if (show) visibleRowCount++;
7887
});
7988

@@ -82,10 +91,46 @@ function filterAndSearchTable() {
8291
}
8392

8493
// Update visible row count
85-
const visibleRows = Array.from(table.rows).filter(row => row.style.display !== 'none' && row.id !== 'no-rows-message');
86-
document.getElementById('row-count').textContent = `Showing ${visibleRows.length} record${visibleRows.length !== 1 ? 's' : ''}`;
94+
const visibleRows = Array.from(table.rows).filter(
95+
(row) => row.style.display !== 'none' && row.id !== 'no-rows-message'
96+
);
97+
reportTable.querySelector('#row-count').textContent = `Showing ${visibleRows.length} record${
98+
visibleRows.length !== 1 ? 's' : ''
99+
}`;
87100
}
88101

102+
function hideOrShowData(reportTable, rowClass, show) {
103+
const rows = Array.from(reportTable.querySelectorAll(`.${rowClass}`));
104+
rows.forEach((row) => {
105+
row.style.display = show ? '' : 'none';
106+
});
107+
}
108+
109+
document.addEventListener('DOMContentLoaded', () => {
110+
document.querySelectorAll('.collapsible-content').forEach((collapsibleContent) => {
111+
collapsibleContent.style.display = 'none';
112+
});
113+
114+
var coll = document.getElementsByClassName('collapsible');
115+
var i;
116+
117+
for (i = 0; i < coll.length; i++) {
118+
coll[i].addEventListener('click', function () {
119+
this.classList.toggle('active');
120+
var content = this.nextElementSibling;
121+
if (content.style.display === 'block') {
122+
content.style.display = 'none';
123+
} else {
124+
content.style.display = 'block';
125+
}
126+
});
127+
}
128+
129+
document.querySelectorAll('.rpt-table-container').forEach((tableContainer) => {
130+
filterAndSearchTable(tableContainer.id);
131+
});
132+
});
133+
89134
// Expose globally so HTML inline event handlers can access them
90135
window.toggleFilterDropdown = toggleFilterDropdown;
91136
window.filterAndSearchTable = filterAndSearchTable;

src/styles/reportGenerator.css

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@
103103
.slds-table th {
104104
position: relative;
105105
max-width: 250px;
106-
text-align: center;
106+
text-align: left;
107+
word-wrap: break-word;
108+
overflow-wrap: break-word;
109+
white-space: normal;
107110
}
108111

109112
.slds-table--bordered,
@@ -217,3 +220,51 @@ tbody tr td {
217220
html {
218221
background: #f8f8f8;
219222
}
223+
224+
.details-body {
225+
display: flex;
226+
flex-direction: row;
227+
gap: 10px;
228+
margin-top: 2rem;
229+
flex-wrap: wrap;
230+
}
231+
232+
.detail-row {
233+
justify-content: space-between;
234+
display: flex;
235+
flex-direction: row;
236+
gap: 10px;
237+
}
238+
239+
.slds-box {
240+
display: flex;
241+
flex-direction: column;
242+
gap: 10px;
243+
height: 14rem;
244+
min-width: 25rem;
245+
background-color: #fff;
246+
}
247+
248+
hr {
249+
margin: 1rem;
250+
}
251+
252+
.text-success {
253+
color: #2e844a;
254+
}
255+
256+
.text-error {
257+
color: #ea001e;
258+
}
259+
260+
.text-warning {
261+
color: #fe9339;
262+
}
263+
264+
.card-footer {
265+
margin-top: auto;
266+
}
267+
268+
td {
269+
border-left: 1px solid #e5e5e5;
270+
}

src/utils/reportGenerator/reportGenerator.ts

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import { Filter, HeaderColumn, ReportHeader, TableColumn, TableHeaderCell } from './reportInterfaces';
22

3+
let reportTableInstance = 0;
4+
let dataItemInstance = 0;
5+
const dataItemClass = 'data-row-';
36
export function generateHtmlTable<T>(
47
headerRows: HeaderColumn[],
58
columns: Array<TableColumn<T>>,
69
rows: T[],
710
reportHeader: ReportHeader[],
811
filters: Filter[] = [],
912
tableClass = 'slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped slds-table_col-bordered',
10-
ariaLabel = ''
13+
ariaLabel = '',
14+
indexedKey: string | undefined = undefined,
15+
showMigrationBanner = true
1116
): string {
1217
const transformedHeader: TableHeaderCell[][] = transform(headerRows);
18+
const tableId = `report-table-${reportTableInstance++}`;
1319

1420
const thead = `
1521
<thead>
@@ -58,11 +64,11 @@ export function generateHtmlTable<T>(
5864
id="name-search-input"
5965
class="search-input"
6066
placeholder="Search by Name"
61-
oninput="filterAndSearchTable()"
67+
oninput="filterAndSearchTable('${tableId}')"
6268
/>
6369
</div>
6470
65-
<div class="filter-toggle-button" onclick="toggleFilterDropdown()">
71+
<div class="filter-toggle-button" onclick="toggleFilterDropdown('${tableId}')">
6672
Filters
6773
<svg id="chevron-down" class="chevron-icon" xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
6874
<path fill-rule="none" stroke="currentColor" stroke-width="2" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708"/>
@@ -90,7 +96,7 @@ export function generateHtmlTable<T>(
9096
data-filter-key="${filter.key}"
9197
value="${option}"
9298
checked
93-
onclick="filterAndSearchTable()"
99+
onclick="filterAndSearchTable('${tableId}')"
94100
/>
95101
${option}
96102
</label>
@@ -115,10 +121,11 @@ export function generateHtmlTable<T>(
115121
const tbody = `
116122
<tbody id="filterable-table-body">
117123
${rows
118-
.map(
119-
(row) => `
120-
<tr>
121-
124+
.map((row) =>
125+
indexedKey
126+
? createIndexedRow(row, indexedKey, columns)
127+
: `
128+
<tr class="${dataItemClass}${dataItemInstance++}">
122129
${columns
123130
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
124131
/* eslint-disable @typescript-eslint/no-unsafe-call */
@@ -160,26 +167,30 @@ export function generateHtmlTable<T>(
160167
</div>
161168
`;
162169

163-
const migrationBanner = `
170+
const migrationBanner = showMigrationBanner
171+
? `
164172
<div class="migration-message">
165173
<svg fill="#8c4c00" width="20px" height="20px" viewBox="-3.2 -3.2 38.40 38.40" xmlns="http://www.w3.org/2000/svg" stroke="#8c4c00" stroke-width="0.0032">
166174
<path d="M31.082 27.5l-13.999-24.249c-0.237-0.352-0.633-0.58-1.083-0.58s-0.846 0.228-1.080 0.575l-0.003 0.005-14 24.249c-0.105 0.179-0.167 0.395-0.167 0.625 0 0.69 0.56 1.25 1.25 1.25h28c0.69 0 1.249-0.56 1.249-1.25 0-0.23-0.062-0.446-0.171-0.631zM4.165 26.875l11.835-20.499 11.834 20.499zM14.75 12v8.994c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25V12c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25zM15.12 23.619c-0.124 0.106-0.22 0.24-0.278 0.394-0.051 0.143-0.08 0.308-0.08 0.48s0.029 0.337 0.083 0.491c0.144 0.3 0.38 0.536 0.671 0.676 0.143 0.051 0.308 0.080 0.48 0.080s0.337-0.029 0.49-0.083c0.156-0.071 0.288-0.166 0.4-0.281 0.224-0.225 0.363-0.536 0.363-0.878 0-0.687-0.557-1.244-1.244-1.244-0.343 0-0.653 0.139-0.878 0.363z"/>
167175
</svg>
168176
<span>High level description of what actions were taken as part of the migration will come here</span>
169177
</div>
170-
`;
178+
`
179+
: '';
171180

172181
return `
173-
${migrationBanner}
174-
${pageHeader}
175-
${filterAndSearchPanel}
176-
<div class="table-container">
177-
<table class="${tableClass}" aria-label="${ariaLabel}">
178-
${thead}
179-
${tbody}
180-
</table>
181-
<script src="./reportGeneratorUtility.js" defer></script>
182-
<link rel="stylesheet" href="./reportGenerator.css">
182+
<div class="rpt-table-container" id="${tableId}">
183+
${migrationBanner}
184+
${pageHeader}
185+
${filterAndSearchPanel}
186+
<div class="table-container">
187+
<table class="${tableClass}" aria-label="${ariaLabel}">
188+
${thead}
189+
${tbody}
190+
</table>
191+
<script src="./reportGeneratorUtility.js" defer></script>
192+
<link rel="stylesheet" href="./reportGenerator.css">
193+
</div>
183194
</div>
184195
`;
185196
}
@@ -213,5 +224,40 @@ function transform(columnInput): TableHeaderCell[][] {
213224
}
214225
});
215226

227+
if (row2.length === 0) {
228+
return [row1] as unknown as TableHeaderCell[][];
229+
}
230+
216231
return [row1, row2] as unknown as TableHeaderCell[][];
217232
}
233+
234+
function createIndexedRow<T>(row: T, indexedKey: string, columns: Array<TableColumn<T>>): string {
235+
let rows = '';
236+
const indexedTill = row[indexedKey].length;
237+
const dataRowClass = `${dataItemClass}${dataItemInstance++}`;
238+
for (let i = 0; i < indexedTill; i++) {
239+
rows += `
240+
<tr class="${dataRowClass}">
241+
242+
${columns
243+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
244+
/* eslint-disable @typescript-eslint/no-unsafe-call */
245+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
246+
.map((col) => {
247+
const skip: boolean = col.skip ? col.skip(row, i) : false;
248+
if (skip) return '';
249+
const key: string = col.key;
250+
const title: string = col.title ? col.title(row, i) : '';
251+
const value: string = col.filterValue(row, i).toString();
252+
const cellContent: string = col.cell(row, i);
253+
const style: string = col.styles ? col.styles(row, i) : '';
254+
const dataAttr: string = ['name', 'oldName'].includes(key) ? `data-name="${value.toLowerCase()}"` : '';
255+
const rowspan: string = col.rowspan ? `rowspan="${col.rowspan(row, i)}"` : '';
256+
return `<td ${dataAttr} title="${title}" key="${key}" value="${value}" style="${style}" ${rowspan}>${cellContent}</td>`;
257+
})
258+
.join('')}
259+
</tr>
260+
`;
261+
}
262+
return rows;
263+
}

0 commit comments

Comments
 (0)