feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API
- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview - Hide Financial & Classification section for non-individual entities in personal data and preview - Fix legal form column in clients list — fetch legalForm from client API for correct display - Add person/business icon before client name in list rows - Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API - Add registration number format hint (mat-hint) based on constitution type - Add spacing between verification row and constitution type dropdowns - Reduce table cell padding and prevent action buttons from being clipped - Add missing MPL-2.0 license headers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ab01111424
commit
b890d24bb7
@@ -6,249 +6,237 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container m-b-20 layout-row align-end">
|
||||
<button mat-raised-button color="primary" (click)="downloadCSV()">
|
||||
<fa-icon icon="file" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Download CSV' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-card class="container audit-filters-card">
|
||||
<mat-card-content>
|
||||
<div class="layout-row-wrap gap-8px responsive-column">
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Resource ID' | translate }}</mat-label>
|
||||
<input matInput [formControl]="resourceId" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Status' | translate }}</mat-label>
|
||||
<mat-select [formControl]="processingResult" (selectionChange)="applyFilter($event.value, 'processingResult')">
|
||||
@for (processingResult of auditTrailSearchTemplateData.processingResults; track processingResult) {
|
||||
<mat-option [value]="processingResult.id">
|
||||
{{ processingResult.processingResult }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.User' | translate }}</mat-label>
|
||||
<input matInput [formControl]="user" [matAutocomplete]="userNameAutocomplete" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Action' | translate }}</mat-label>
|
||||
<input matInput [formControl]="actionName" [matAutocomplete]="actionNameAutocomplete" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Entity' | translate }}</mat-label>
|
||||
<input matInput [formControl]="entityName" [matAutocomplete]="entityNameAutocomplete" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Checker' | translate }}</mat-label>
|
||||
<input matInput [formControl]="checker" [matAutocomplete]="checkerAutocomplete" />
|
||||
</mat-form-field>
|
||||
|
||||
<div class="flex-48 layout-row-wrap gap-8px">
|
||||
<mat-form-field class="flex-60" (click)="fromDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Maker From Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="fromDatePicker"
|
||||
[formControl]="fromDate"
|
||||
placeholder="yyyy-MM-dd"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-38">
|
||||
<mat-label>{{ 'labels.inputs.Time' | translate }}</mat-label>
|
||||
<input matInput type="time" step="1" [formControl]="fromTime" placeholder="HH:MM:SS" class="time-input" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="flex-48 layout-row-wrap gap-8px">
|
||||
<mat-form-field class="flex-60" (click)="toDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Maker To Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="toDatePicker"
|
||||
[formControl]="toDate"
|
||||
placeholder="yyyy-MM-dd"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-38">
|
||||
<mat-label>{{ 'labels.inputs.Time' | translate }}</mat-label>
|
||||
<input matInput type="time" step="1" [formControl]="toTime" placeholder="HH:MM:SS" class="time-input" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="flex-48 layout-row-wrap gap-8px">
|
||||
<mat-form-field class="flex-60" (click)="checkedFromDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Checker From Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="checkedFromDatePicker"
|
||||
[formControl]="checkedFromDate"
|
||||
placeholder="yyyy-MM-dd"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="checkedFromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #checkedFromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-38">
|
||||
<mat-label>{{ 'labels.inputs.Time' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="time"
|
||||
step="1"
|
||||
[formControl]="checkedFromTime"
|
||||
placeholder="HH:MM:SS"
|
||||
class="time-input"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="flex-48 layout-row-wrap gap-8px">
|
||||
<mat-form-field class="flex-60" (click)="checkedToDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Checked To Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="checkedToDatePicker"
|
||||
[formControl]="checkedToDate"
|
||||
placeholder="yyyy-MM-dd"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="checkedToDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #checkedToDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-38">
|
||||
<mat-label>{{ 'labels.inputs.Time' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="time"
|
||||
step="1"
|
||||
[formControl]="checkedToTime"
|
||||
placeholder="HH:MM:SS"
|
||||
class="time-input"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="container">
|
||||
<!-- Page Header -->
|
||||
<div class="at-page-header">
|
||||
<div class="at-page-header__left">
|
||||
<div class="at-page-header__icon"><i class="fas fa-clipboard-list"></i></div>
|
||||
<div>
|
||||
<h2 class="at-page-header__title">{{ 'labels.heading.Audit Trails' | translate }}</h2>
|
||||
<p class="at-page-header__sub">Track all system activities, approvals & changes</p>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<!-- Autocomplete data -->
|
||||
<mat-autocomplete autoActiveFirstOption #userNameAutocomplete="matAutocomplete" [displayWith]="displayUserName">
|
||||
@for (user of filteredUserData | async; track user) {
|
||||
<mat-option [value]="{ id: user.id, name: user.username }">
|
||||
{{ user.username }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #actionNameAutocomplete="matAutocomplete" [displayWith]="displayActionName">
|
||||
@for (action of filteredActionData | async; track action) {
|
||||
<mat-option [value]="action">
|
||||
{{ action }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #entityNameAutocomplete="matAutocomplete" [displayWith]="displayEntityName">
|
||||
@for (entity of filteredEntityData | async; track entity) {
|
||||
<mat-option [value]="entity">
|
||||
{{ entity }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #checkerAutocomplete="matAutocomplete" [displayWith]="displayUserName">
|
||||
@for (user of filteredCheckerData | async; track user) {
|
||||
<mat-option [value]="{ id: user.id, name: user.username }">
|
||||
{{ user.username }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<div class="container mat-elevation-z8 table-container">
|
||||
@if (isLoading) {
|
||||
<div>
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
<div class="at-page-header__actions">
|
||||
<button class="at-btn at-btn--outline" (click)="downloadCSV()">
|
||||
<fa-icon icon="file-csv"></fa-icon> Download CSV
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@if (!isLoading) {
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Trail ID' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.id }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="resourceId">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Resource ID' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.resourceId }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="processingResult">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Status' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.processingResult }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="maker">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Made By' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.maker }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actionName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Action' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.actionName | translateKey: 'auditTrail.actions' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="entityName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Entity' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.entityName | translateKey: 'auditTrail.entities' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="officeName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Office' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.officeName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="madeOnDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Made Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.madeOnDate | datetimeFormat }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="checker">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Checker' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.checker }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="checkedOnDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Checked Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.checkedOnDate | datetimeFormat }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="clientIp">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Client Ip' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.ip }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!isLoading) {
|
||||
<mat-paginator
|
||||
[length]="dataSource?.records$ | async"
|
||||
[pageSize]="10"
|
||||
[pageSizeOptions]="[10, 25, 50, 100]"
|
||||
showFirstLastButtons
|
||||
></mat-paginator>
|
||||
}
|
||||
<!-- Filters Section -->
|
||||
<div class="at-filters">
|
||||
<div class="at-filters__header" (click)="filtersOpen = !filtersOpen">
|
||||
<div class="at-filters__header-left">
|
||||
<i class="fas fa-sliders-h at-filters__icon"></i>
|
||||
<span class="at-filters__label">Filters</span>
|
||||
</div>
|
||||
<fa-icon [icon]="filtersOpen ? 'chevron-down' : 'chevron-right'" class="at-filters__toggle"></fa-icon>
|
||||
</div>
|
||||
|
||||
@if (filtersOpen) {
|
||||
<div class="at-filters__body">
|
||||
<!-- Row 1: Quick filters -->
|
||||
<div class="at-filters__row at-filters__row--3">
|
||||
<mat-form-field appearance="outline" class="at-ff">
|
||||
<mat-label>Resource ID</mat-label>
|
||||
<input matInput [formControl]="resourceId" placeholder="Enter ID" />
|
||||
<fa-icon icon="search" matPrefix class="at-ff__prefix"></fa-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="at-ff">
|
||||
<mat-label>Status</mat-label>
|
||||
<mat-select [formControl]="processingResult" (selectionChange)="applyFilter($event.value, 'processingResult')">
|
||||
@for (processingResult of auditTrailSearchTemplateData.processingResults; track processingResult) {
|
||||
<mat-option [value]="processingResult.id">{{ processingResult.processingResult }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="at-ff">
|
||||
<mat-label>User</mat-label>
|
||||
<input matInput [formControl]="user" [matAutocomplete]="userNameAutocomplete" placeholder="Search user..." />
|
||||
<fa-icon icon="user" matPrefix class="at-ff__prefix"></fa-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: Action / Entity / Checker -->
|
||||
<div class="at-filters__row at-filters__row--3">
|
||||
<mat-form-field appearance="outline" class="at-ff">
|
||||
<mat-label>Action</mat-label>
|
||||
<input matInput [formControl]="actionName" [matAutocomplete]="actionNameAutocomplete" placeholder="e.g. CREATE, UPDATE" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="at-ff">
|
||||
<mat-label>Entity</mat-label>
|
||||
<input matInput [formControl]="entityName" [matAutocomplete]="entityNameAutocomplete" placeholder="e.g. CLIENT, LOAN" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="at-ff">
|
||||
<mat-label>Checker</mat-label>
|
||||
<input matInput [formControl]="checker" [matAutocomplete]="checkerAutocomplete" placeholder="Search checker..." />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Row 3: Date ranges -->
|
||||
<div class="at-filters__date-section">
|
||||
<div class="at-filters__date-label"><i class="fas fa-calendar-alt"></i> Maker Date Range</div>
|
||||
<div class="at-filters__row at-filters__row--dates">
|
||||
<div class="at-date-group">
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--date" (click)="fromDatePicker.open()">
|
||||
<mat-label>From Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="fromDatePicker" [formControl]="fromDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--time">
|
||||
<mat-label>Time</mat-label>
|
||||
<input matInput type="time" step="1" [formControl]="fromTime" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<span class="at-date-separator">to</span>
|
||||
<div class="at-date-group">
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--date" (click)="toDatePicker.open()">
|
||||
<mat-label>To Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="toDatePicker" [formControl]="toDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--time">
|
||||
<mat-label>Time</mat-label>
|
||||
<input matInput type="time" step="1" [formControl]="toTime" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="at-filters__date-section">
|
||||
<div class="at-filters__date-label"><i class="fas fa-user-check"></i> Checker Date Range</div>
|
||||
<div class="at-filters__row at-filters__row--dates">
|
||||
<div class="at-date-group">
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--date" (click)="checkedFromDatePicker.open()">
|
||||
<mat-label>From Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="checkedFromDatePicker" [formControl]="checkedFromDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="checkedFromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #checkedFromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--time">
|
||||
<mat-label>Time</mat-label>
|
||||
<input matInput type="time" step="1" [formControl]="checkedFromTime" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<span class="at-date-separator">to</span>
|
||||
<div class="at-date-group">
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--date" (click)="checkedToDatePicker.open()">
|
||||
<mat-label>To Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="checkedToDatePicker" [formControl]="checkedToDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="checkedToDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #checkedToDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="at-ff at-ff--time">
|
||||
<mat-label>Time</mat-label>
|
||||
<input matInput type="time" step="1" [formControl]="checkedToTime" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Autocomplete data -->
|
||||
<mat-autocomplete autoActiveFirstOption #userNameAutocomplete="matAutocomplete" [displayWith]="displayUserName">
|
||||
@for (user of filteredUserData | async; track user) {
|
||||
<mat-option [value]="{ id: user.id, name: user.username }">{{ user.username }}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #actionNameAutocomplete="matAutocomplete" [displayWith]="displayActionName">
|
||||
@for (action of filteredActionData | async; track action) {
|
||||
<mat-option [value]="action">{{ action }}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #entityNameAutocomplete="matAutocomplete" [displayWith]="displayEntityName">
|
||||
@for (entity of filteredEntityData | async; track entity) {
|
||||
<mat-option [value]="entity">{{ entity }}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #checkerAutocomplete="matAutocomplete" [displayWith]="displayUserName">
|
||||
@for (user of filteredCheckerData | async; track user) {
|
||||
<mat-option [value]="{ id: user.id, name: user.username }">{{ user.username }}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-container">
|
||||
@if (isLoading) {
|
||||
<mat-progress-bar mode="indeterminate" class="table-loader"></mat-progress-bar>
|
||||
}
|
||||
@if (!isLoading) {
|
||||
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Trail ID' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.id }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="resourceId">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Resource ID' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.resourceId }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="processingResult">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Status' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">
|
||||
<span class="at-status" [class.at-status--success]="auditTrail.processingResult === 'SUCCESS'" [class.at-status--failure]="auditTrail.processingResult === 'FAILURE'" [class.at-status--awaiting]="auditTrail.processingResult === 'AWAITING_APPROVAL'">
|
||||
{{ auditTrail.processingResult }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="maker">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Made By' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.maker }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actionName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Action' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">
|
||||
<span class="at-action-badge">{{ auditTrail.actionName | translateKey: 'auditTrail.actions' }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="entityName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Entity' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.entityName | translateKey: 'auditTrail.entities' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="officeName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Office' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.officeName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="madeOnDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Made Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail" class="cell-mono">{{ auditTrail.madeOnDate | datetimeFormat }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="checker">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Checker' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail">{{ auditTrail.checker }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="checkedOnDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Checked Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail" class="cell-mono">{{ auditTrail.checkedOnDate | datetimeFormat }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="clientIp">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Client Ip' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrail" class="cell-mono">{{ auditTrail.ip }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (!isLoading) {
|
||||
<mat-paginator
|
||||
[length]="dataSource?.records$ | async"
|
||||
[pageSize]="10"
|
||||
[pageSizeOptions]="[10, 25, 50, 100]"
|
||||
showFirstLastButtons
|
||||
></mat-paginator>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,26 +6,290 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
table {
|
||||
@use '../../shared/styles/list-layout' as *;
|
||||
|
||||
$blue: #1353a0;
|
||||
$navy: #0f172a;
|
||||
$border: #e2e8f0;
|
||||
$muted: #64748b;
|
||||
|
||||
// ===== Page Header =====
|
||||
.at-page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 18px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
background: #eff6ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: $blue;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: $navy;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.at-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
border: none;
|
||||
|
||||
&--outline {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
color: $muted;
|
||||
|
||||
&:hover {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 4px rgb(19 83 160 / 25%);
|
||||
|
||||
&:hover {
|
||||
background: darken($blue, 6%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Filters =====
|
||||
.at-filters {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 18px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 11px 16px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid $border;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #f1f5f9;
|
||||
}
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 13px;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $navy;
|
||||
}
|
||||
|
||||
&__toggle {
|
||||
font-size: 12px;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
|
||||
&--3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
&--dates {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__date-section {
|
||||
margin-top: 4px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
&__date-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
i {
|
||||
color: $blue;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.at-ff {
|
||||
width: 100%;
|
||||
|
||||
.select-row:hover {
|
||||
cursor: pointer;
|
||||
&__prefix {
|
||||
font-size: 13px;
|
||||
color: $muted;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-38 {
|
||||
.time-input {
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.gap-8px {
|
||||
.at-date-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.at-ff--date {
|
||||
flex: 1.6;
|
||||
}
|
||||
|
||||
.at-ff--time {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.audit-filters-card {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
.at-date-separator {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $muted;
|
||||
text-align: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
// ===== Status Badge in Table =====
|
||||
.at-status {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
|
||||
&--success {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
&--failure {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
&--awaiting {
|
||||
background: #fffbeb;
|
||||
color: #d97706;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Table overflow =====
|
||||
.table-container {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
// ===== Date Cells =====
|
||||
.cell-mono {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
// ===== Action Badge =====
|
||||
.at-action-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: 6px;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
// ===== Responsive =====
|
||||
@media (max-width: 960px) {
|
||||
.at-filters__row--3 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.at-filters__row--dates {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.at-date-separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.at-filters__row--3 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.at-page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.at-date-group {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatRow,
|
||||
MatPaginator,
|
||||
AsyncPipe,
|
||||
DatetimeFormatPipe
|
||||
DatetimeFormatPipe,
|
||||
TranslatePipe
|
||||
]
|
||||
})
|
||||
export class AuditTrailsComponent implements OnInit, AfterViewInit {
|
||||
@@ -80,6 +81,8 @@ export class AuditTrailsComponent implements OnInit, AfterViewInit {
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
/** Filters panel state */
|
||||
filtersOpen = true;
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum date allowed. */
|
||||
|
||||
@@ -6,143 +6,116 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container layout-row align-between responsive-column">
|
||||
<mat-card class="flex-100">
|
||||
<mat-card-content class="flex-100">
|
||||
<div class="layout-row flex-100">
|
||||
<div class="flex-46">
|
||||
<div class="layout-row-wrap">
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.ID' | translate }}
|
||||
</div>
|
||||
<div class="va-container">
|
||||
<!-- Page Header -->
|
||||
<div class="va-page-header">
|
||||
<div class="va-title-group">
|
||||
<i class="fas fa-clipboard-list va-title-icon"></i>
|
||||
<h2 class="va-title">{{ 'labels.heading.Audit Trail' | translate }} #{{ auditTrailData.id }}</h2>
|
||||
</div>
|
||||
<button mat-stroked-button [routerLink]="['../']">
|
||||
<i class="fas fa-arrow-left va-back-icon"></i>
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.id }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Status' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.processingResult }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.User' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.maker }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Action' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.actionName | translateKey: 'auditTrail.actions' }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Entity' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.entityName | translateKey: 'auditTrail.entities' }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Resource ID' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.resourceId }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Date' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.madeOnDate | datetimeFormat }}
|
||||
</div>
|
||||
|
||||
@if (auditTrailData.officeName) {
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Office' | translate }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.officeName) {
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.officeName }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.savingsAccountNo) {
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Savings Account No' | translate }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.savingsAccountNo) {
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.savingsAccountNo }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.groupLevelName) {
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Group Level' | translate }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.groupLevelName) {
|
||||
<div class="flex-50">
|
||||
{{ auditTrailData.groupLevelName }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (auditTrailData.ip) {
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
{{ 'labels.inputs.Client Ip' | translate }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.ip) {
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.ip }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex-46 command">
|
||||
<table mat-table [dataSource]="dataSource" class="command-table" matSort>
|
||||
<ng-container matColumnDef="command">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Command' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrailCommand">{{ auditTrailCommand.command }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="commandValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Command Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrailCommand" class="column-value-wrapper">
|
||||
{{ auditTrailCommand.commandValue }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Details Card -->
|
||||
<div class="va-card">
|
||||
<div class="va-card-header">
|
||||
<i class="fas fa-info-circle va-card-header-icon"></i>
|
||||
<span>{{ 'labels.heading.Details' | translate }}</span>
|
||||
</div>
|
||||
<div class="va-card-body">
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.ID' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.id }}</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-row layout-align-center m-t-20 column-on-mobile">
|
||||
<button type="button" mat-raised-button color="primary" [routerLink]="['../']">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Status' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.processingResult }}</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.User' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.maker }}</div>
|
||||
</div>
|
||||
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Action' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.actionName | translateKey: 'auditTrail.actions' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Entity' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.entityName | translateKey: 'auditTrail.entities' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Resource ID' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.resourceId }}</div>
|
||||
</div>
|
||||
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Date' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.madeOnDate | datetimeFormat }}</div>
|
||||
</div>
|
||||
|
||||
@if (auditTrailData.officeName) {
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Office' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.officeName }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.savingsAccountNo) {
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Savings Account No' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.savingsAccountNo }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.groupLevelName) {
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Group Level' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.groupLevelName }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (auditTrailData.ip) {
|
||||
<div class="va-row">
|
||||
<div class="va-label">{{ 'labels.inputs.Client Ip' | translate }}</div>
|
||||
<div class="va-value">{{ auditTrailData.ip }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Command Data Card -->
|
||||
@if (auditTrailCommandsData.length > 0) {
|
||||
<div class="va-card">
|
||||
<div class="va-card-header">
|
||||
<i class="fas fa-terminal va-card-header-icon"></i>
|
||||
<span>{{ 'labels.inputs.Command' | translate }}</span>
|
||||
</div>
|
||||
<div class="va-card-body va-command-body">
|
||||
<table mat-table [dataSource]="dataSource" matSort class="va-command-table">
|
||||
<ng-container matColumnDef="command">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Command' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrailCommand" class="va-command-key">{{ auditTrailCommand.command }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="commandValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Command Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let auditTrailCommand" class="va-command-value">
|
||||
{{ auditTrailCommand.commandValue }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -6,25 +6,180 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.content {
|
||||
div {
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
$border: #e2e8f0;
|
||||
$text: #202124;
|
||||
$text-muted: #5f6368;
|
||||
$brand: #1353a0;
|
||||
$bg-header: #f8fafc;
|
||||
|
||||
// ===== Container =====
|
||||
.va-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
// ===== Page Header =====
|
||||
.va-page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.va-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.va-title-icon {
|
||||
font-size: 18px;
|
||||
color: $brand;
|
||||
}
|
||||
|
||||
.va-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.va-back-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// ===== Card =====
|
||||
.va-card {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.va-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 20px;
|
||||
background: $bg-header;
|
||||
border-bottom: 1px solid $border;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.va-card-header-icon {
|
||||
font-size: 13px;
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.va-card-body {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
// ===== Detail Rows =====
|
||||
.va-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
.va-label {
|
||||
flex: 0 0 40%;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.va-value {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: $text;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// ===== Command Table =====
|
||||
.va-command-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.va-command-table {
|
||||
width: 100%;
|
||||
background: #fafbfc;
|
||||
|
||||
::ng-deep {
|
||||
.mat-mdc-header-row {
|
||||
background: $bg-header;
|
||||
min-height: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.mat-mdc-header-cell {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: $text-muted;
|
||||
border-bottom: 1px solid $border;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.mat-mdc-row {
|
||||
min-height: 38px;
|
||||
height: 38px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-cell {
|
||||
padding: 0 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-value-wrapper {
|
||||
.va-command-key {
|
||||
font-family: 'Roboto Mono', 'DM Mono', monospace;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500;
|
||||
color: $brand;
|
||||
}
|
||||
|
||||
.va-command-value {
|
||||
word-break: break-all;
|
||||
font-size: 13px;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.command {
|
||||
background-color: #eee;
|
||||
}
|
||||
// ===== Responsive =====
|
||||
/* stylelint-disable-next-line media-feature-range-notation */
|
||||
@media (max-width: 600px) {
|
||||
.va-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.command-table {
|
||||
background-color: inherit;
|
||||
.va-page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.va-row {
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.va-label {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user