WEB-528 Improve the data visualization in Data Tables (#2942)
This commit is contained in:
@@ -199,15 +199,16 @@ export class Datatables {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert snake_case to Title Case (e.g., "created_at" -> "Created At")
|
if (columnName.includes('_')) {
|
||||||
// For non-CODELOOKUP fields, only filter standalone "cd" if it appears as an artifact
|
return (
|
||||||
return (
|
columnName
|
||||||
columnName
|
.split('_')
|
||||||
.split('_')
|
.filter((word) => word.trim() && word.toLowerCase() !== 'cd')
|
||||||
.filter((word) => word.trim() && word.toLowerCase() !== 'cd') // Remove empty strings and standalone "cd" artifacts
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
.join(' ') || columnName
|
||||||
.join(' ') || columnName
|
);
|
||||||
); // Fallback to original if all filtered out
|
}
|
||||||
|
return columnName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCodeLookupValue(columnHeader: any, id: number): string {
|
public getCodeLookupValue(columnHeader: any, id: number): string {
|
||||||
|
|||||||
+30
-28
@@ -32,33 +32,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-t-10">
|
<mat-card class="data-table-card m-t-10">
|
||||||
<table #dataTable mat-table [dataSource]="datatableData" class="mat-elevation-z1 m-b-25">
|
<mat-card-content>
|
||||||
@for (datatableColumn of datatableColumns; track datatableColumn; let i = $index) {
|
<table #dataTable mat-table [dataSource]="datatableData" class="mat-elevation-z2 m-b-25">
|
||||||
<ng-container [matColumnDef]="datatableColumn">
|
@for (datatableColumn of datatableColumns; track datatableColumn; let i = $index) {
|
||||||
@if (i === 0) {
|
<ng-container [matColumnDef]="datatableColumn">
|
||||||
<th mat-header-cell *matHeaderCellDef></th>
|
@if (i === 0) {
|
||||||
<td mat-cell *matCellDef="let data">
|
<th mat-header-cell *matHeaderCellDef class="checkbox-column"></th>
|
||||||
<mat-checkbox
|
<td mat-cell *matCellDef="let data">
|
||||||
class="center"
|
<mat-checkbox
|
||||||
(click)="$event.stopPropagation()"
|
class="center"
|
||||||
(change)="$event ? itemToggle(data) : null"
|
(click)="$event.stopPropagation()"
|
||||||
[checked]="selection.isSelected(data)"
|
(change)="$event ? itemToggle(data) : null"
|
||||||
>
|
[checked]="selection.isSelected(data)"
|
||||||
</mat-checkbox>
|
>
|
||||||
</td>
|
</mat-checkbox>
|
||||||
}
|
</td>
|
||||||
@if (i > 0) {
|
}
|
||||||
<th mat-header-cell class="right" *matHeaderCellDef>{{ getInputName(datatableColumn) }}</th>
|
@if (i > 0) {
|
||||||
<td mat-cell class="right" *matCellDef="let data" [ngClass]="isToDelete(data)">
|
<th mat-header-cell class="right" *matHeaderCellDef>{{ getInputName(datatableColumn) }}</th>
|
||||||
{{ formatValue(data, datatableColumn) }}
|
<td mat-cell class="right" *matCellDef="let data" [ngClass]="isToDelete(data)">
|
||||||
</td>
|
{{ formatValue(data, datatableColumn) }}
|
||||||
}
|
</td>
|
||||||
</ng-container>
|
}
|
||||||
}
|
</ng-container>
|
||||||
|
}
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="datatableColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="datatableColumns"></tr>
|
||||||
<tr mat-row *matRowDef="let row; columns: datatableColumns"></tr>
|
<tr mat-row *matRowDef="let row; columns: datatableColumns" class="data-row"></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+53
@@ -9,12 +9,65 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-table-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
|
||||||
|
|
||||||
|
.mat-card-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: rgb(0 0 0 / 4%);
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-bottom: 2px solid rgb(0 0 0 / 12%);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-row {
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(0 0 0 / 3%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-column {
|
||||||
|
max-width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-select {
|
||||||
|
max-width: 60px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tobe-deleted {
|
.tobe-deleted {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: $orange;
|
color: $orange;
|
||||||
|
opacity: 0.7;
|
||||||
|
background-color: rgb(255 152 0 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-checkbox {
|
||||||
|
.mat-checkbox-frame {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -16,6 +16,7 @@ import {
|
|||||||
MatRow
|
MatRow
|
||||||
} from '@angular/material/table';
|
} from '@angular/material/table';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||||
import { Datatables } from 'app/core/utils/datatables';
|
import { Datatables } from 'app/core/utils/datatables';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
import { DateFormatPipe } from 'app/pipes/date-format.pipe';
|
import { DateFormatPipe } from 'app/pipes/date-format.pipe';
|
||||||
@@ -36,6 +37,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
FaIconComponent,
|
FaIconComponent,
|
||||||
|
MatCard,
|
||||||
|
MatCardContent,
|
||||||
MatTable,
|
MatTable,
|
||||||
MatColumnDef,
|
MatColumnDef,
|
||||||
MatHeaderCellDef,
|
MatHeaderCellDef,
|
||||||
|
|||||||
+78
-68
@@ -21,76 +21,86 @@
|
|||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
@if (dataObject.data.length > 0) {
|
@if (dataObject.data.length > 0) {
|
||||||
<div class="m-t-10">
|
<mat-card class="data-display-card m-t-20">
|
||||||
@for (columnHeader of dataObject.columnHeaders; track columnHeader; let i = $index) {
|
<mat-card-content>
|
||||||
<div class="flex-fill" [ngClass]="setAttributeClass(columnHeader.columnName)">
|
<div class="data-grid">
|
||||||
<div class="mat-body-strong left flex-40">
|
@for (columnHeader of dataObject.columnHeaders; track columnHeader; let i = $index) {
|
||||||
{{ datatables.toDisplayLabel(columnHeader.columnName) }}
|
<div class="data-item" [ngClass]="setAttributeClass(columnHeader.columnName)">
|
||||||
</div>
|
<div class="data-label mat-body-strong">
|
||||||
<div class="right flex-60">
|
{{ datatables.toDisplayLabel(columnHeader.columnName) }}
|
||||||
@switch (getColumnType(columnHeader.columnDisplayType, columnHeader.columnType)) {
|
</div>
|
||||||
@case ('DATE') {
|
<div class="data-value">
|
||||||
<span>
|
@switch (getColumnType(columnHeader.columnDisplayType, columnHeader.columnType)) {
|
||||||
{{ dataObject.data[0].row[i] | dateFormat }}
|
@case ('DATE') {
|
||||||
</span>
|
<span>
|
||||||
}
|
{{ dataObject.data[0].row[i] | dateFormat }}
|
||||||
@case ('DATETIME') {
|
|
||||||
<span>
|
|
||||||
{{ dataObject.data[0].row[i] | datetimeFormat }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
@case ('INTEGER') {
|
|
||||||
<span>
|
|
||||||
{{ dataObject.data[0].row[i] }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
@case ('DECIMAL') {
|
|
||||||
<span>
|
|
||||||
{{ dataObject.data[0].row[i] | formatNumber }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
@case ('CODELOOKUP') {
|
|
||||||
<span>
|
|
||||||
{{ datatables.getCodeLookupValue(columnHeader, dataObject.data[0].row[i]) }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
@case ('TEXT') {
|
|
||||||
<span class="long-text">
|
|
||||||
{{ dataObject.data[0].row[i] }}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
@case ('JSON') {
|
|
||||||
<textarea
|
|
||||||
cdkTextareaAutosize="true"
|
|
||||||
cdkAutosizeMaxRows="20"
|
|
||||||
cdkAutosizeMinRows="1"
|
|
||||||
class="json-textarea"
|
|
||||||
[innerHTML]="dataObject.data[0].row[i].value | prettyPrint"
|
|
||||||
>
|
|
||||||
</textarea>
|
|
||||||
}
|
|
||||||
@default {
|
|
||||||
<span>
|
|
||||||
@if (isValidUrl(dataObject.data[0].row[i])) {
|
|
||||||
<span class="m-r-5">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
matTooltip="{{ 'tooltips.View Link' | translate }}"
|
|
||||||
(click)="openSite(dataObject.data[0].row[i])"
|
|
||||||
matTooltipPosition="right"
|
|
||||||
class="small-icon"
|
|
||||||
>
|
|
||||||
<fa-icon icon="eye" size="lg"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
{{ dataObject.data[0].row[i] }}
|
@case ('DATETIME') {
|
||||||
</span>
|
<span>
|
||||||
}
|
{{ dataObject.data[0].row[i] | datetimeFormat }}
|
||||||
}
|
</span>
|
||||||
</div>
|
}
|
||||||
|
@case ('INTEGER') {
|
||||||
|
<span>
|
||||||
|
{{ dataObject.data[0].row[i] }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@case ('DECIMAL') {
|
||||||
|
<span>
|
||||||
|
{{ dataObject.data[0].row[i] | formatNumber }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@case ('CODELOOKUP') {
|
||||||
|
<span>
|
||||||
|
{{ datatables.getCodeLookupValue(columnHeader, dataObject.data[0].row[i]) }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@case ('TEXT') {
|
||||||
|
<span class="long-text">
|
||||||
|
{{ dataObject.data[0].row[i] }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@case ('JSON') {
|
||||||
|
<textarea
|
||||||
|
cdkTextareaAutosize="true"
|
||||||
|
cdkAutosizeMaxRows="20"
|
||||||
|
cdkAutosizeMinRows="1"
|
||||||
|
class="json-textarea"
|
||||||
|
[innerHTML]="dataObject.data[0].row[i].value | prettyPrint"
|
||||||
|
>
|
||||||
|
</textarea>
|
||||||
|
}
|
||||||
|
@default {
|
||||||
|
<div class="default-value">
|
||||||
|
@if (columnHeader.columnName === 'created_at' || columnHeader.columnName === 'updated_at') {
|
||||||
|
<span>
|
||||||
|
{{ dataObject.data[0].row[i] | datetimeFormat }}
|
||||||
|
</span>
|
||||||
|
} @else if (isValidUrl(dataObject.data[0].row[i])) {
|
||||||
|
<span class="m-r-5">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
matTooltip="{{ 'tooltips.View Link' | translate }}"
|
||||||
|
(click)="openSite(dataObject.data[0].row[i])"
|
||||||
|
matTooltipPosition="right"
|
||||||
|
class="small-icon"
|
||||||
|
>
|
||||||
|
<fa-icon icon="eye" size="lg"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
{{ dataObject.data[0].row[i] }}
|
||||||
|
} @else {
|
||||||
|
{{ dataObject.data[0].row[i] }}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
</mat-card-content>
|
||||||
</div>
|
</mat-card>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+79
-1
@@ -10,6 +10,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-display-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 100%));
|
||||||
|
gap: 20px;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: rgb(0 0 0 / 2%);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-height: 70px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(0 0 0 / 4%);
|
||||||
|
border-left-color: var(--primary-color, #3f51b5);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: rgb(0 0 0 / 54%);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-value {
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
color: rgb(0 0 0 / 87%);
|
||||||
|
word-break: break-word;
|
||||||
|
min-height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
span,
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.table-name {
|
.table-name {
|
||||||
padding-left: 2%;
|
padding-left: 2%;
|
||||||
}
|
}
|
||||||
@@ -20,11 +81,14 @@
|
|||||||
|
|
||||||
.system-defined {
|
.system-defined {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
|
background-color: rgb(63 81 181 / 8%);
|
||||||
|
border-left-color: #3f51b5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.long-text {
|
.long-text {
|
||||||
word-break: break-all;
|
word-break: break-word;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-icon {
|
.small-icon {
|
||||||
@@ -35,4 +99,18 @@
|
|||||||
.json-textarea {
|
.json-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgb(0 0 0 / 12%);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.data-grid {
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -12,6 +12,7 @@ import { NgClass } from '@angular/common';
|
|||||||
import { MatButton, MatIconButton } from '@angular/material/button';
|
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { MatDivider } from '@angular/material/divider';
|
import { MatDivider } from '@angular/material/divider';
|
||||||
|
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||||
@@ -28,6 +29,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
FaIconComponent,
|
FaIconComponent,
|
||||||
MatDivider,
|
MatDivider,
|
||||||
|
MatCard,
|
||||||
|
MatCardContent,
|
||||||
NgClass,
|
NgClass,
|
||||||
CdkTextareaAutosize,
|
CdkTextareaAutosize,
|
||||||
MatIconButton,
|
MatIconButton,
|
||||||
@@ -170,6 +173,15 @@ export class DatatableSingleRowComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColumnType(columnDisplayType: string, columnType: string) {
|
getColumnType(columnDisplayType: string, columnType: string) {
|
||||||
|
if (
|
||||||
|
columnType &&
|
||||||
|
(columnType.toLowerCase().includes('timestamp') ||
|
||||||
|
columnType.toLowerCase() === 'created_at' ||
|
||||||
|
columnType.toLowerCase() === 'updated_at')
|
||||||
|
) {
|
||||||
|
return 'DATETIME';
|
||||||
|
}
|
||||||
|
|
||||||
switch (columnDisplayType) {
|
switch (columnDisplayType) {
|
||||||
case 'DATE': {
|
case 'DATE': {
|
||||||
return columnDisplayType;
|
return columnDisplayType;
|
||||||
|
|||||||
Reference in New Issue
Block a user