Browse Source

Refine installed items page styling and handle dialog results

- Update table type chips, update column, and filter panel to match design
- Add afterClosed handling to viewItemDetails and viewUpdateDetails dialogs
- Reload table data when items are updated or deleted from detail dialog
pull/15508/head
Igor Kulikov 6 months ago
parent
commit
4c7ca5bfd6
  1. 69
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.html
  2. 166
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.scss
  3. 16
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts
  4. 4
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html
  5. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

69
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.html

@ -29,7 +29,7 @@
@if (isCheckingUpdates) { @if (isCheckingUpdates) {
<mat-spinner diameter="18"></mat-spinner> <mat-spinner diameter="18"></mat-spinner>
} @else { } @else {
<mat-icon>sync</mat-icon> <mat-icon>update</mat-icon>
} }
{{ 'iot-hub.check-for-updates' | translate }} {{ 'iot-hub.check-for-updates' | translate }}
</button> </button>
@ -38,10 +38,22 @@
<!-- Filter + Search row --> <!-- Filter + Search row -->
<div class="tb-installed-toolbar"> <div class="tb-installed-toolbar">
<div class="tb-installed-filter-area"> <div class="tb-installed-filter-area">
<button mat-stroked-button color="primary" class="tb-installed-filter-btn" (click)="toggleFilter()"> <button mat-stroked-button color="primary" class="tb-installed-filter-btn" [matMenuTriggerFor]="filterMenu">
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
{{ 'iot-hub.filter' | translate }} {{ 'iot-hub.filter' | translate }}
</button> </button>
<mat-menu #filterMenu="matMenu" class="tb-installed-filter-menu" xPosition="after">
@for (type of allItemTypes; track type; let last = $last) {
<div mat-menu-item class="tb-installed-filter-item" (click)="$event.stopPropagation(); toggleTypeFilter(type)">
<mat-checkbox [checked]="isTypeFilterActive(type)">
{{ getItemTypeLabel(type) }}
</mat-checkbox>
</div>
@if (!last) {
<mat-divider></mat-divider>
}
}
</mat-menu>
@if (hasActiveFilters()) { @if (hasActiveFilters()) {
<mat-chip-set class="tb-installed-filter-chips"> <mat-chip-set class="tb-installed-filter-chips">
@for (type of activeTypeFilters; track type) { @for (type of activeTypeFilters; track type) {
@ -65,18 +77,7 @@
</div> </div>
</div> </div>
<!-- Filter dropdown --> <!-- Table + Paginator wrapped in border -->
@if (filterOpen) {
<div class="tb-installed-filter-dropdown">
@for (type of allItemTypes; track type) {
<mat-checkbox [checked]="isTypeFilterActive(type)" (change)="toggleTypeFilter(type)">
{{ getItemTypeLabel(type) }}
</mat-checkbox>
}
</div>
}
<!-- Table -->
<div class="tb-installed-table-container"> <div class="tb-installed-table-container">
<table mat-table [dataSource]="dataSource" matSort matSortActive="createdTime" matSortDirection="desc"> <table mat-table [dataSource]="dataSource" matSort matSortActive="createdTime" matSortDirection="desc">
<!-- Name --> <!-- Name -->
@ -110,18 +111,20 @@
<!-- Updates --> <!-- Updates -->
<ng-container matColumnDef="updates"> <ng-container matColumnDef="updates">
<th mat-header-cell *matHeaderCellDef>{{ 'iot-hub.updates' | translate }}</th> <th mat-header-cell *matHeaderCellDef>@if (updatesChecked) { {{ 'iot-hub.updates' | translate }} }</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
@if (updatesChecked) { @if (updatesChecked) {
@if (getPublishedVersionInfo(item); as pubInfo) { @if (getPublishedVersionInfo(item); as pubInfo) {
@if (pubInfo.publishedVersionId !== item.itemVersionId) { @if (pubInfo.publishedVersionId !== item.itemVersionId) {
<span class="tb-installed-update-available"> <div class="tb-installed-update-row">
<mat-icon>arrow_upward</mat-icon> <button class="tb-installed-version-btn" (click)="viewUpdateDetails(pubInfo, item)">
v{{ pubInfo.publishedVersion }} {{ 'iot-hub.available' | translate }} <mat-icon>arrow_upward</mat-icon>
</span> v{{ pubInfo.publishedVersion }} {{ 'iot-hub.available' | translate }}
<button mat-stroked-button color="warn" class="tb-installed-update-btn" (click)="updateItem(item, pubInfo)"> </button>
{{ 'iot-hub.update' | translate }} <button mat-stroked-button color="warn" class="tb-installed-update-btn" (click)="updateItem(item, pubInfo)">
</button> {{ 'iot-hub.update' | translate }}
</button>
</div>
} @else { } @else {
<span class="tb-installed-up-to-date"> <span class="tb-installed-up-to-date">
<mat-icon>check</mat-icon> <mat-icon>check</mat-icon>
@ -146,7 +149,7 @@
<button mat-icon-button (click)="viewItemDetails(item)" matTooltip="{{ 'iot-hub.view-item-details' | translate }}"> <button mat-icon-button (click)="viewItemDetails(item)" matTooltip="{{ 'iot-hub.view-item-details' | translate }}">
<mat-icon>info_outline</mat-icon> <mat-icon>info_outline</mat-icon>
</button> </button>
<button mat-icon-button (click)="openEntity(item)" matTooltip="{{ 'iot-hub.open-installed-entity' | translate }}"> <button mat-icon-button (click)="openEntity(item)" matTooltip="{{ (item.itemType === 'SOLUTION_TEMPLATE' ? 'iot-hub.goto-main-dashboard' : 'iot-hub.open-item-type') | translate:{ type: getItemTypeLabel(item.itemType) } }}">
<mat-icon>open_in_new</mat-icon> <mat-icon>open_in_new</mat-icon>
</button> </button>
<button mat-icon-button (click)="deleteItem(item)" matTooltip="{{ 'iot-hub.remove' | translate }}"> <button mat-icon-button (click)="deleteItem(item)" matTooltip="{{ 'iot-hub.remove' | translate }}">
@ -171,15 +174,15 @@
{{ 'iot-hub.no-installed-items' | translate }} {{ 'iot-hub.no-installed-items' | translate }}
</div> </div>
} }
</div>
<!-- Paginator --> <!-- Paginator inside border -->
<mat-paginator <mat-paginator
[length]="totalElements" [length]="totalElements"
[pageSize]="pageSize" [pageSize]="pageSize"
[pageIndex]="pageIndex" [pageIndex]="pageIndex"
[pageSizeOptions]="[10, 25, 50]" [pageSizeOptions]="[10, 25, 50]"
(page)="onPageChange($event)" (page)="onPageChange($event)"
showFirstLastButtons> showFirstLastButtons>
</mat-paginator> </mat-paginator>
</div>
</div> </div>

166
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.scss

@ -24,7 +24,7 @@
.tb-installed-items-page { .tb-installed-items-page {
background: white; background: white;
border-radius: 8px; border-radius: 8px;
padding: 40px 40px 24px; padding: 40px 40px 40px;
} }
// Back button // Back button
@ -41,7 +41,7 @@
} }
.tb-installed-title { .tb-installed-title {
font-size: 40px; font-size: 36px;
font-weight: 500; font-weight: 500;
line-height: 48px; line-height: 48px;
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
@ -89,6 +89,26 @@
} }
} }
// Filter menu panel
::ng-deep .tb-installed-filter-menu {
.mat-mdc-menu-content {
padding: 0;
}
.tb-installed-filter-item {
height: 40px;
padding: 0 16px;
mat-checkbox {
width: 100%;
}
}
mat-divider {
margin: 0;
}
}
// Search field // Search field
.tb-installed-search { .tb-installed-search {
width: 320px; width: 320px;
@ -125,92 +145,136 @@
} }
} }
// Filter dropdown
.tb-installed-filter-dropdown {
display: flex;
flex-direction: column;
width: 260px;
padding: 8px 0;
background: white;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
mat-checkbox {
display: block;
padding: 8px 16px;
&:hover { // Table + paginator container — unified border
background: rgba(0, 0, 0, 0.04);
}
}
}
// Table
.tb-installed-table-container { .tb-installed-table-container {
border: 1px solid rgba(0, 0, 0, 0.12); border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px; border-radius: 8px;
overflow: hidden; overflow: hidden;
table { table {
width: 100%; width: 100%;
} }
// Header cells — Design: 16px Medium, rgba(0,0,0,0.87), tracking 0.25
th.mat-mdc-header-cell {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
// Body cells — Design: 14px Regular, rgba(0,0,0,0.76), tracking 0.2
td.mat-mdc-cell {
font-size: 14px;
font-weight: 400;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.76);
}
::ng-deep .mat-mdc-paginator {
border-top: none;
}
} }
// Type cell with colored icon + text // Type chip — Design: pill, h-32, rounded-16, px-12 py-6, gap-4, bg at 6% opacity
.tb-installed-type { .tb-installed-type {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
height: 32px;
padding: 6px 12px;
border-radius: 16px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 400;
line-height: 20px;
letter-spacing: 0.2px;
} }
.tb-installed-type-icon { .tb-installed-type-icon {
font-size: 16px; font-size: 18px;
width: 16px; width: 18px;
height: 16px; height: 18px;
color: inherit !important;
--mat-icon-color: currentColor;
} }
.tb-type-widget { color: #2e7d32; } .tb-type-widget { color: rgb(32, 115, 61); background: rgba(46, 166, 88, 0.06); }
.tb-type-dashboard { color: #1565c0; } .tb-type-dashboard { color: rgb(61, 76, 166); background: rgba(61, 76, 166, 0.06); }
.tb-type-solution-template { color: #2666a9; } .tb-type-solution-template { color: rgb(0, 137, 186); background: rgba(0, 137, 186, 0.06); }
.tb-type-calc-field { color: #006d92; } .tb-type-calc-field { color: rgb(40, 120, 148); background: rgba(40, 120, 148, 0.06); }
.tb-type-rule-chain { color: #95694b; } .tb-type-rule-chain { color: rgb(178, 121, 29); background: rgba(179, 121, 29, 0.06); }
.tb-type-device { color: #4b8a79; } .tb-type-device { color: rgb(24, 146, 110); background: rgba(24, 146, 110, 0.06); }
// Updates column — Design: 14px Medium, tracking 0.25, gap-8, icons 18px
// Update row — flex container for version btn + update btn
.tb-installed-update-row {
display: flex;
align-items: center;
gap: 12px;
}
// Updates column // "vX.X.X Available" button — Design: border rgba(0,0,0,0.12), rounded-4, px-12 py-6, gap-8
.tb-installed-update-available { .tb-installed-version-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 8px;
font-size: 13px; font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.76); color: rgba(0, 0, 0, 0.76);
margin-right: 8px; background: none;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-family: inherit;
mat-icon { mat-icon {
font-size: 16px; font-size: 18px;
width: 16px; width: 18px;
height: 16px; height: 18px;
color: inherit !important;
--mat-icon-color: currentColor;
}
&:hover {
background: rgba(0, 0, 0, 0.04);
} }
} }
.tb-installed-update-btn { // "Update" button — Design: same height as available btn, border matches text color
font-size: 12px; .tb-installed-update-btn.mat-mdc-outlined-button {
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
padding: 6px 12px;
height: auto;
--mat-button-outlined-outline-color: var(--mat-button-outlined-label-text-color);
} }
// "Up to date" — Design: 14px Medium, #198038, gap-8, py-6
.tb-installed-up-to-date { .tb-installed-up-to-date {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 8px;
font-size: 13px; font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
color: #198038; color: #198038;
padding: 6px 0;
mat-icon { mat-icon {
font-size: 16px; font-size: 18px;
width: 16px; width: 18px;
height: 16px; height: 18px;
color: inherit !important;
--mat-icon-color: currentColor;
} }
} }

16
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.ts

@ -204,14 +204,20 @@ export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit {
viewItemDetails(item: IotHubInstalledItem): void { viewItemDetails(item: IotHubInstalledItem): void {
this.iotHubApiService.getVersionInfo(item.itemVersionId, {ignoreLoading: true}).subscribe(versionView => { this.iotHubApiService.getVersionInfo(item.itemVersionId, {ignoreLoading: true}).subscribe(versionView => {
this.dialog.open(TbIotHubItemDetailDialogComponent, { const dialogRef = this.dialog.open(TbIotHubItemDetailDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
autoFocus: false,
data: { data: {
item: versionView, item: versionView,
iotHubApiService: this.iotHubApiService, iotHubApiService: this.iotHubApiService,
installedItem: item installedItem: item
} as IotHubItemDetailDialogData } as IotHubItemDetailDialogData
}); });
dialogRef.afterClosed().subscribe(result => {
if (result === 'updated' || result === 'deleted') {
this.loadData();
}
});
}); });
} }
@ -271,14 +277,20 @@ export class TbIotHubInstalledItemsComponent implements OnInit, AfterViewInit {
viewUpdateDetails(publishedInfo: ItemPublishedVersionInfo, installedItem: IotHubInstalledItem): void { viewUpdateDetails(publishedInfo: ItemPublishedVersionInfo, installedItem: IotHubInstalledItem): void {
this.iotHubApiService.getVersionInfo(publishedInfo.publishedVersionId, {ignoreLoading: true}).subscribe(versionView => { this.iotHubApiService.getVersionInfo(publishedInfo.publishedVersionId, {ignoreLoading: true}).subscribe(versionView => {
this.dialog.open(TbIotHubItemDetailDialogComponent, { const dialogRef = this.dialog.open(TbIotHubItemDetailDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
autoFocus: false,
data: { data: {
item: versionView, item: versionView,
iotHubApiService: this.iotHubApiService, iotHubApiService: this.iotHubApiService,
installedItem installedItem
} as IotHubItemDetailDialogData } as IotHubItemDetailDialogData
}); });
dialogRef.afterClosed().subscribe(result => {
if (result === 'updated' || result === 'deleted') {
this.loadData();
}
});
}); });
} }

4
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-detail-dialog.component.html

@ -64,7 +64,7 @@
</span> </span>
<a class="dlg-info-link" (click)="openEntityDetails()"> <a class="dlg-info-link" (click)="openEntityDetails()">
<mat-icon>open_in_new</mat-icon> <mat-icon>open_in_new</mat-icon>
{{ 'iot-hub.open-item-type-details' | translate:{ type: getTypeLabel() } }} {{ 'iot-hub.open-item-type' | translate:{ type: getTypeLabel() } }}
</a> </a>
</div> </div>
} }
@ -175,7 +175,7 @@
</span> </span>
<a class="dlg-info-link" (click)="openEntityDetails()"> <a class="dlg-info-link" (click)="openEntityDetails()">
<mat-icon>open_in_new</mat-icon> <mat-icon>open_in_new</mat-icon>
{{ 'iot-hub.open-item-type-details' | translate:{ type: getTypeLabel() } }} {{ 'iot-hub.open-item-type' | translate:{ type: getTypeLabel() } }}
</a> </a>
</div> </div>
} }

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3868,7 +3868,7 @@
"installed-item-remove-error": "Failed to remove '{{name}}'", "installed-item-remove-error": "Failed to remove '{{name}}'",
"open-installed-entity": "Open installed entity", "open-installed-entity": "Open installed entity",
"view-item-details": "View item details", "view-item-details": "View item details",
"open-item-type-details": "Open {{type}} details", "open-item-type": "Open {{type}}",
"back-to-marketplace": "Back to Marketplace", "back-to-marketplace": "Back to Marketplace",
"device-install-connection": "Connection: {{type}}", "device-install-connection": "Connection: {{type}}",
"device-install-entity-error": "Failed to create {{name}}: {{error}}", "device-install-entity-error": "Failed to create {{name}}: {{error}}",

Loading…
Cancel
Save