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
@@ -15,8 +15,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="layout-row">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,35 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container m-b-20 layout-row align-end gap-20px">
|
||||
<div #buttonCreateCode>
|
||||
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CODE'">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Create Code' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
<div class="table-header">
|
||||
<div class="table-title-group">
|
||||
<h2 class="table-title">
|
||||
<i class="fas fa-list-ul title-icon"></i>
|
||||
{{ 'labels.heading.Manage Codes' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<div #filter class="table-search">
|
||||
<mat-form-field appearance="outline">
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="table-buttons">
|
||||
<div #buttonCreateCode>
|
||||
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CODE'">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Create Code' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div #tableCodes class="mat-elevation-z8">
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<div #tableCodes class="table-container">
|
||||
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Code Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let code">{{ code.name }}</td>
|
||||
@@ -55,7 +65,7 @@
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use '../../shared/styles/list-layout' as *;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
@@ -14,7 +16,6 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: CONFIGURE THESE CLASSES GLOBALLY
|
||||
.system-defined {
|
||||
color: #32cd32;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,26 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<div class="cc-container">
|
||||
<!-- Header -->
|
||||
<div class="cc-header">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="cc-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Manage Codes' | translate }}
|
||||
</button>
|
||||
<h2 class="cc-header__title">{{ 'labels.heading.Create Code' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="cc-card">
|
||||
<form [formGroup]="codeForm" (ngSubmit)="submit()">
|
||||
<mat-card-content>
|
||||
<div #codeFormRef class="layout-column">
|
||||
<mat-form-field>
|
||||
<!-- Code Details Section -->
|
||||
<div class="cc-section">
|
||||
<div class="cc-section__header">
|
||||
<i class="fas fa-code"></i>
|
||||
<h3>{{ 'labels.inputs.Code' | translate }} Details</h3>
|
||||
</div>
|
||||
<div class="cc-grid" #codeFormRef>
|
||||
<mat-form-field appearance="outline" class="cc-field cc-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Code Name' | translate }}</mat-label>
|
||||
<input matInput required formControlName="name" />
|
||||
@if (codeForm.controls.name.hasError('required')) {
|
||||
@@ -22,18 +36,26 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
|
||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||
<button type="button" mat-raised-button [routerLink]="['../']">
|
||||
<!-- Actions -->
|
||||
<div class="cc-actions">
|
||||
<button type="button" mat-stroked-button [routerLink]="['../']" class="cc-actions__cancel">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!codeForm.valid" *mifosxHasPermission="'CREATE_CODE'">
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
<button
|
||||
type="submit"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[disabled]="!codeForm.valid"
|
||||
*mifosxHasPermission="'CREATE_CODE'"
|
||||
class="cc-actions__submit"
|
||||
>
|
||||
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #templateCodeFormRef let-data let-popover="popover">
|
||||
|
||||
@@ -6,6 +6,128 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-700: #5f6368;
|
||||
$gray-900: #202124;
|
||||
|
||||
.cc-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
// Header
|
||||
.cc-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cc-back {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 32px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-500 !important;
|
||||
|
||||
i { margin-right: 6px; }
|
||||
|
||||
&:hover { color: $primary !important; border-color: $primary !important; }
|
||||
}
|
||||
|
||||
// Card
|
||||
.cc-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Section
|
||||
.cc-section {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid
|
||||
.cc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.cc-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
.cc-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 24px;
|
||||
border-top: 1px solid $gray-200;
|
||||
background: $gray-50;
|
||||
|
||||
&__cancel {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-700 !important;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
|
||||
i { margin-right: 6px; font-size: 12px; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,26 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<div class="ec-container">
|
||||
<!-- Header -->
|
||||
<div class="ec-header">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="ec-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Code' | translate }}
|
||||
</button>
|
||||
<h2 class="ec-header__title">{{ 'labels.heading.Edit Code' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="ec-card">
|
||||
<form [formGroup]="codeForm" (ngSubmit)="submit()">
|
||||
<mat-card-content>
|
||||
<div class="layout-column">
|
||||
<mat-form-field>
|
||||
<!-- Code Details Section -->
|
||||
<div class="ec-section">
|
||||
<div class="ec-section__header">
|
||||
<i class="fas fa-code"></i>
|
||||
<h3>{{ 'labels.inputs.Code' | translate }} Details</h3>
|
||||
</div>
|
||||
<div class="ec-grid">
|
||||
<mat-form-field appearance="outline" class="ec-field ec-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Code Name' | translate }}</mat-label>
|
||||
<input matInput required formControlName="name" />
|
||||
@if (codeForm.controls.name.hasError('required')) {
|
||||
@@ -22,21 +36,24 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
|
||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||
<button type="button" mat-raised-button [routerLink]="['../']">
|
||||
<!-- Actions -->
|
||||
<div class="ec-actions">
|
||||
<button type="button" mat-stroked-button [routerLink]="['../']" class="ec-actions__cancel">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
type="submit"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
[disabled]="!codeForm.valid || codeForm.pristine"
|
||||
*mifosxHasPermission="'UPDATE_CODE'"
|
||||
class="ec-actions__submit"
|
||||
>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,128 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-700: #5f6368;
|
||||
$gray-900: #202124;
|
||||
|
||||
.ec-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
// Header
|
||||
.ec-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-back {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 32px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-500 !important;
|
||||
|
||||
i { margin-right: 6px; }
|
||||
|
||||
&:hover { color: $primary !important; border-color: $primary !important; }
|
||||
}
|
||||
|
||||
// Card
|
||||
.ec-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Section
|
||||
.ec-section {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid
|
||||
.ec-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.ec-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
.ec-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 24px;
|
||||
border-top: 1px solid $gray-200;
|
||||
background: $gray-50;
|
||||
|
||||
&__cancel {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-700 !important;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
|
||||
i { margin-right: 6px; font-size: 12px; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,146 +6,190 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container m-b-20 layout-row align-end layout-xs-column gap-20px">
|
||||
<button mat-raised-button color="primary" (click)="addCodeValueRow()" *mifosxHasPermission="'CREATE_CODEVALUE'">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add Code Value' | translate }}
|
||||
</button>
|
||||
|
||||
@if (!codeData.systemDefined) {
|
||||
<span>
|
||||
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CODE'">
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Edit Code' | translate }}
|
||||
<div class="vc-container">
|
||||
<!-- Header -->
|
||||
<div class="vc-header">
|
||||
<div class="vc-header__left">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="vc-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Manage Codes' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
<h2 class="vc-header__title">{{ codeData.name }}</h2>
|
||||
</div>
|
||||
<div class="vc-header__actions">
|
||||
@if (!codeData.systemDefined) {
|
||||
<button mat-flat-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CODE'" class="vc-header__btn">
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Edit Code' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (!codeData.systemDefined) {
|
||||
<button mat-stroked-button color="warn" (click)="delete()" *mifosxHasPermission="'DELETE_CODEVALUE'" class="vc-header__btn-delete">
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Delete Code' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!codeData.systemDefined) {
|
||||
<span>
|
||||
<button mat-raised-button color="warn" (click)="delete()" *mifosxHasPermission="'DELETE_CODEVALUE'">
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Delete Code' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<!-- Code Details Card -->
|
||||
<div class="vc-card">
|
||||
<div class="vc-section">
|
||||
<div class="vc-section__header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<h3>{{ 'labels.inputs.Code' | translate }} Details</h3>
|
||||
</div>
|
||||
<div class="vc-details">
|
||||
<div class="vc-row">
|
||||
<span class="vc-row__label">{{ 'labels.inputs.Code Name' | translate }}</span>
|
||||
<span class="vc-row__value">{{ codeData.name }}</span>
|
||||
</div>
|
||||
<div class="vc-row">
|
||||
<span class="vc-row__label">{{ 'labels.inputs.System Defined' | translate }}</span>
|
||||
<span class="vc-row__value">
|
||||
@if (codeData.systemDefined) {
|
||||
<span class="vc-bool vc-bool--yes"><i class="fas fa-check"></i> Yes</span>
|
||||
} @else {
|
||||
<span class="vc-bool vc-bool--no"><i class="fas fa-times"></i> No</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<mat-card-title class="m-t-20 m-l-20"
|
||||
><h4>{{ codeData.name }}</h4></mat-card-title
|
||||
>
|
||||
<!-- Code Values Card -->
|
||||
<div class="vc-card vc-card--values">
|
||||
<div class="vc-section">
|
||||
<div class="vc-section__header">
|
||||
<i class="fas fa-list"></i>
|
||||
<h3>{{ 'labels.inputs.Code' | translate }} Values</h3>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
class="vc-add-btn"
|
||||
(click)="addCodeValueRow()"
|
||||
*mifosxHasPermission="'CREATE_CODEVALUE'"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add Code Value' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="codeValuesForm">
|
||||
<mat-card-content>
|
||||
@for (codeValue of codeValues.controls; track codeValue; let i = $index) {
|
||||
<div formArrayName="codeValues">
|
||||
<div class="flex-fill layout-row-wrap gap-2percent responsive-column" [formGroupName]="i">
|
||||
<mat-form-field class="flex-fill flex-20">
|
||||
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
|
||||
<input required matInput formControlName="name" placeholder="{{ 'labels.inputs.name' | translate }}" />
|
||||
@if (codeValues.at(i).controls.name.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-fill flex-22">
|
||||
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="description"
|
||||
placeholder="{{ 'labels.inputs.Description' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-fill flex-18">
|
||||
<mat-label>{{ 'labels.inputs.Position' | translate }}</mat-label>
|
||||
<input
|
||||
required
|
||||
matInput
|
||||
formControlName="position"
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="{{ 'labels.inputs.Position' | translate }}"
|
||||
/>
|
||||
@if (codeValues.at(i).controls.position.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="active-wrapper flex-8">
|
||||
<mat-checkbox formControlName="isActive" class="active">{{
|
||||
'labels.inputs.Active' | translate
|
||||
}}</mat-checkbox>
|
||||
</div>
|
||||
<div class="options-wrapper gap-2percent flex-22">
|
||||
@if (codeValueRowStatus[i] === 'edit') {
|
||||
<span>
|
||||
<form [formGroup]="codeValuesForm">
|
||||
<div class="vc-values-table">
|
||||
<!-- Table Header -->
|
||||
<div class="vc-values-header">
|
||||
<div class="vc-values-col vc-values-col--name">{{ 'labels.inputs.name' | translate }}</div>
|
||||
<div class="vc-values-col vc-values-col--desc">{{ 'labels.inputs.Description' | translate }}</div>
|
||||
<div class="vc-values-col vc-values-col--pos">{{ 'labels.inputs.Position' | translate }}</div>
|
||||
<div class="vc-values-col vc-values-col--active">{{ 'labels.inputs.Active' | translate }}</div>
|
||||
<div class="vc-values-col vc-values-col--actions">Actions</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Rows -->
|
||||
@for (codeValue of codeValues.controls; track codeValue; let i = $index) {
|
||||
<div formArrayName="codeValues" class="vc-values-row" [class.vc-values-row--editing]="codeValueRowStatus[i] !== 'disabled'">
|
||||
<div [formGroupName]="i" class="vc-values-row__inner">
|
||||
<div class="vc-values-col vc-values-col--name">
|
||||
<mat-form-field appearance="outline" class="vc-inline-field">
|
||||
<input required matInput formControlName="name" placeholder="{{ 'labels.inputs.name' | translate }}" />
|
||||
@if (codeValues.at(i).controls.name.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="vc-values-col vc-values-col--desc">
|
||||
<mat-form-field appearance="outline" class="vc-inline-field">
|
||||
<input matInput formControlName="description" placeholder="{{ 'labels.inputs.Description' | translate }}" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="vc-values-col vc-values-col--pos">
|
||||
<mat-form-field appearance="outline" class="vc-inline-field">
|
||||
<input required matInput formControlName="position" type="number" min="0" placeholder="{{ 'labels.inputs.Position' | translate }}" />
|
||||
@if (codeValues.at(i).controls.position.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Position' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="vc-values-col vc-values-col--active">
|
||||
<mat-checkbox formControlName="isActive">{{ 'labels.inputs.Active' | translate }}</mat-checkbox>
|
||||
</div>
|
||||
<div class="vc-values-col vc-values-col--actions">
|
||||
@if (codeValueRowStatus[i] === 'edit') {
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="vc-action-btn"
|
||||
[disabled]="!codeValues.at(i).valid || codeValues.at(i).pristine"
|
||||
(click)="updateCodeValue(i)"
|
||||
*mifosxHasPermission="'UPDATE_CODEVALUE'"
|
||||
>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
@if (codeValueRowStatus[i] === 'edit') {
|
||||
<button mat-button type="button" (click)="disableRow(i)">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (codeValueRowStatus[i] === 'new') {
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
type="button"
|
||||
title="{{ 'tooltips.Add' | translate }}"
|
||||
[disabled]="!codeValuesForm.get('codeValues').at(i).valid"
|
||||
matTooltip="{{ 'tooltips.Add' | translate }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="addCodeValue(i)"
|
||||
>
|
||||
<fa-icon icon="plus" size="lg"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
@if (codeValueRowStatus[i] === 'disabled') {
|
||||
<button mat-button type="button" class="vc-action-btn" (click)="disableRow(i)">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (codeValueRowStatus[i] === 'new') {
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
type="button"
|
||||
title="{{ 'tooltips.Add' | translate }}"
|
||||
[disabled]="!codeValuesForm.get('codeValues').at(i).valid"
|
||||
matTooltip="{{ 'tooltips.Add' | translate }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="addCodeValue(i)"
|
||||
>
|
||||
<fa-icon icon="plus" size="lg"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
@if (codeValueRowStatus[i] === 'disabled') {
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
title="{{ 'tooltips.Edit' | translate }}"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'tooltips.Edit' | translate }}"
|
||||
matTooltipPosition="above"
|
||||
(click)="enableRow(i)"
|
||||
>
|
||||
<fa-icon icon="edit" size="lg"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
title="{{ 'tooltips.Edit' | translate }}"
|
||||
color="warn"
|
||||
title="{{ 'tooltips.Delete' | translate }}"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'tooltips.Edit' | translate }}"
|
||||
matTooltip="{{ 'tooltips.Delete' | translate }}"
|
||||
*mifosxHasPermission="'DELETE_CODEVALUE'"
|
||||
matTooltipPosition="above"
|
||||
(click)="enableRow(i)"
|
||||
(click)="codeValueRowStatus[i] === 'new' ? removeNewCodeValue(i) : deleteCodeValue(i)"
|
||||
>
|
||||
<fa-icon icon="edit" size="lg"></fa-icon>
|
||||
<fa-icon icon="trash" size="lg"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
color="warn"
|
||||
title="{{ 'tooltips.Delete' | translate }}"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'tooltips.Delete' | translate }}"
|
||||
*mifosxHasPermission="'DELETE_CODEVALUE'"
|
||||
matTooltipPosition="above"
|
||||
(click)="codeValueRowStatus[i] === 'new' ? removeNewCodeValue(i) : deleteCodeValue(i)"
|
||||
>
|
||||
<fa-icon icon="trash" size="lg"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</mat-card-content>
|
||||
</form>
|
||||
</mat-card>
|
||||
}
|
||||
|
||||
<!-- Empty State -->
|
||||
@if (codeValues.controls.length === 0) {
|
||||
<div class="vc-values-empty">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<p>No code values defined yet</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,19 +6,279 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@media (width >= 992px) {
|
||||
.active-wrapper {
|
||||
position: relative;
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-700: #5f6368;
|
||||
$gray-900: #202124;
|
||||
|
||||
.active {
|
||||
padding: 0 0 17.5px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
.vc-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
// Header
|
||||
.vc-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
}
|
||||
|
||||
&__btn-delete {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-back {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 32px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-500 !important;
|
||||
|
||||
i { margin-right: 6px; }
|
||||
|
||||
&:hover { color: $primary !important; border-color: $primary !important; }
|
||||
}
|
||||
|
||||
// Card
|
||||
.vc-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
& + & {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// Section
|
||||
.vc-section {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.options-wrapper {
|
||||
padding: 18px 0 0;
|
||||
// Code Details rows
|
||||
.vc-details {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.vc-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
&__label {
|
||||
flex: 0 0 40%;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #5f6368;
|
||||
}
|
||||
|
||||
&__value {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #202124;
|
||||
}
|
||||
}
|
||||
|
||||
// Boolean indicator
|
||||
.vc-bool {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
|
||||
i { font-size: 11px; }
|
||||
|
||||
&--yes { color: #16a34a; }
|
||||
&--no { color: #dc2626; }
|
||||
}
|
||||
|
||||
// Add button in section header
|
||||
.vc-add-btn {
|
||||
margin-left: auto !important;
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 32px !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
}
|
||||
|
||||
// Code Values Table
|
||||
.vc-values-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.vc-values-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 24px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid $border;
|
||||
min-width: 700px;
|
||||
|
||||
.vc-values-col {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #64748b;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-values-row {
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
min-width: 700px;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:nth-child(even) { background: #fafbfc; }
|
||||
&:hover { background: #f8fafc; }
|
||||
|
||||
&--editing {
|
||||
background: #eff6ff !important;
|
||||
}
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-values-col {
|
||||
&--name { flex: 0 0 22%; padding-right: 8px; }
|
||||
&--desc { flex: 0 0 26%; padding-right: 8px; }
|
||||
&--pos { flex: 0 0 14%; padding-right: 8px; }
|
||||
&--active { flex: 0 0 12%; padding-right: 8px; }
|
||||
&--actions {
|
||||
flex: 0 0 26%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-inline-field {
|
||||
width: 100%;
|
||||
|
||||
::ng-deep {
|
||||
.mat-mdc-form-field-flex { height: 36px; }
|
||||
|
||||
.mat-mdc-form-field-infix {
|
||||
padding: 4px 0;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.mdc-notched-outline {
|
||||
.mdc-notched-outline__leading { border-radius: 6px 0 0 6px; }
|
||||
.mdc-notched-outline__trailing { border-radius: 0 6px 6px 0; }
|
||||
}
|
||||
|
||||
input { font-size: 13px; }
|
||||
}
|
||||
}
|
||||
|
||||
.vc-action-btn {
|
||||
border-radius: 6px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
}
|
||||
|
||||
// Empty state
|
||||
.vc-values-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 24px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
font-size: 28px;
|
||||
color: #cbd5e1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (max-width: 768px) {
|
||||
.vc-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
&__actions {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,8 +8,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row gap-20px">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="layout-row gap-20px">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<div class="tab-container mat-typography">
|
||||
<div class="layout-row align-start-center">
|
||||
<div class="search-box flex-40">
|
||||
<mat-form-field class="flex-90">
|
||||
<mat-form-field appearance="outline" class="flex-90">
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input
|
||||
matInput
|
||||
placeholder="{{ 'labels.text.FilterByLoanIdOrError' | translate }}"
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row gap-20px">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
+2
-1
@@ -8,8 +8,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="layout-row m-b-10">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter By Version' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
|
||||
<div class="container">
|
||||
<div class="layout-row">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-form-field appearance="outline" class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,26 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<div class="ar-container">
|
||||
<!-- Header -->
|
||||
<div class="ar-header">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="ar-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Manage Roles and Permission' | translate }}
|
||||
</button>
|
||||
<h2 class="ar-header__title">{{ 'labels.buttons.Add Role' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="ar-card">
|
||||
<form [formGroup]="roleForm" (ngSubmit)="submit()">
|
||||
<mat-card-content>
|
||||
<div class="layout-column">
|
||||
<mat-form-field>
|
||||
<!-- Role Details Section -->
|
||||
<div class="ar-section">
|
||||
<div class="ar-section__header">
|
||||
<i class="fas fa-key"></i>
|
||||
<h3>{{ 'labels.inputs.Role' | translate }}</h3>
|
||||
</div>
|
||||
<div class="ar-grid">
|
||||
<mat-form-field appearance="outline" class="ar-field ar-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
|
||||
<input matInput required formControlName="name" />
|
||||
@if (roleForm.controls.name.hasError('required')) {
|
||||
@@ -22,7 +36,7 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-form-field appearance="outline" class="ar-field ar-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
|
||||
<textarea matInput required formControlName="description"></textarea>
|
||||
@if (roleForm.controls.description.hasError('required')) {
|
||||
@@ -33,16 +47,17 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
|
||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||
<button type="button" mat-raised-button [routerLink]="['../']">
|
||||
<!-- Actions -->
|
||||
<div class="ar-actions">
|
||||
<button type="button" mat-stroked-button [routerLink]="['../']" class="ar-actions__cancel">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!roleForm.valid">
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
<button type="submit" mat-flat-button color="primary" [disabled]="!roleForm.valid" class="ar-actions__submit">
|
||||
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,128 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-700: #5f6368;
|
||||
$gray-900: #202124;
|
||||
|
||||
.ar-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
// Header
|
||||
.ar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ar-back {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 32px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-500 !important;
|
||||
|
||||
i { margin-right: 6px; }
|
||||
|
||||
&:hover { color: $primary !important; border-color: $primary !important; }
|
||||
}
|
||||
|
||||
// Card
|
||||
.ar-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Section
|
||||
.ar-section {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid
|
||||
.ar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.ar-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
.ar-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 24px;
|
||||
border-top: 1px solid $gray-200;
|
||||
background: $gray-50;
|
||||
|
||||
&__cancel {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-700 !important;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
|
||||
i { margin-right: 6px; font-size: 12px; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,31 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<div class="er-container">
|
||||
<!-- Header -->
|
||||
<div class="er-header">
|
||||
<button mat-stroked-button [routerLink]="['../../']" class="er-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Manage Roles and Permission' | translate }}
|
||||
</button>
|
||||
<h2 class="er-header__title">{{ 'labels.text.Edit Role' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="er-card">
|
||||
<form [formGroup]="roleForm" (ngSubmit)="submit()">
|
||||
<mat-card-content>
|
||||
<div class="layout-column">
|
||||
<mat-form-field>
|
||||
<!-- Role Details Section -->
|
||||
<div class="er-section">
|
||||
<div class="er-section__header">
|
||||
<i class="fas fa-key"></i>
|
||||
<h3>{{ 'labels.inputs.Role' | translate }}</h3>
|
||||
</div>
|
||||
<div class="er-grid">
|
||||
<mat-form-field appearance="outline" class="er-field er-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Role Name' | translate }}</mat-label>
|
||||
<input matInput disabled formControlName="name" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-form-field appearance="outline" class="er-field er-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Role Description' | translate }}</mat-label>
|
||||
<input matInput required formControlName="description" />
|
||||
@if (roleForm.controls.description.hasError('required')) {
|
||||
@@ -27,16 +41,17 @@
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
|
||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||
<!-- Actions -->
|
||||
<div class="er-actions">
|
||||
<button type="button" mat-stroked-button [routerLink]="['../../']" class="er-actions__cancel">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!roleForm.valid || roleForm.pristine">
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
<button type="submit" mat-flat-button color="primary" [disabled]="!roleForm.valid || roleForm.pristine" class="er-actions__submit">
|
||||
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,128 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-700: #5f6368;
|
||||
$gray-900: #202124;
|
||||
|
||||
.er-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1rem;
|
||||
// Header
|
||||
.er-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.roleName {
|
||||
line-height: 3rem;
|
||||
.er-back {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 32px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-500 !important;
|
||||
|
||||
i { margin-right: 6px; }
|
||||
|
||||
&:hover { color: $primary !important; border-color: $primary !important; }
|
||||
}
|
||||
|
||||
// Card
|
||||
.er-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Section
|
||||
.er-section {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid
|
||||
.er-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.er-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
.er-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 24px;
|
||||
border-top: 1px solid $gray-200;
|
||||
background: $gray-50;
|
||||
|
||||
&__cancel {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
border-color: $border !important;
|
||||
color: $gray-700 !important;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
|
||||
i { margin-right: 6px; font-size: 12px; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,35 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container m-b-20 layout-row layout-align-end gap-20px" [routerLink]="['add']">
|
||||
<div #buttonAddRole class="in-block">
|
||||
<button mat-raised-button color="primary">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add Role' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div #filter class="layout-row gap-20px">
|
||||
<mat-form-field class="flex-fill">
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
<div class="table-header">
|
||||
<div class="table-title-group">
|
||||
<h2 class="table-title">
|
||||
<i class="fas fa-key title-icon"></i>
|
||||
{{ 'labels.inputs.Manage Roles and Permission' | translate }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<div #filter class="table-search">
|
||||
<mat-form-field appearance="outline">
|
||||
<i matPrefix class="fas fa-search"></i>
|
||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="table-buttons">
|
||||
<div #buttonAddRole>
|
||||
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['add']">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add Role' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div #tableRolesandPermissions class="mat-elevation-z8">
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<div #tableRolesandPermissions class="table-container">
|
||||
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let role">{{ role.name }}</td>
|
||||
@@ -63,7 +73,7 @@
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use '../../shared/styles/list-layout' as *;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
|
||||
@@ -6,145 +6,253 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="layout-row align-end gap-2px responsive-column container m-b-0">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="editRoles(); backupCheckValues()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'"
|
||||
>
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.text.Edit Role' | translate }}
|
||||
</button>
|
||||
|
||||
@if (!isRoleEnable(rolePermissionService.disabled)) {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="danger"
|
||||
(click)="disableRolesConfirmation()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'"
|
||||
>
|
||||
<fa-icon icon="lock" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Disable' | translate }} {{ 'labels.inputs.Role' | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (isRoleEnable(rolePermissionService.disabled)) {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="enableRolesConfirmation()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'"
|
||||
>
|
||||
<fa-icon icon="lock-open" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Enable' | translate }} {{ 'labels.inputs.Role' | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
(click)="deleteRole()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'"
|
||||
>
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Delete' | translate }} {{ 'labels.inputs.Role' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="container m-b-0">
|
||||
<mat-card class="mat-elevation-z8 role-details-card">
|
||||
<mat-card-content>
|
||||
<div class="layout-row-wrap">
|
||||
<div class="flex-40 flex-lt-md-50 header">
|
||||
<b>{{ 'labels.inputs.Name' | translate }}:</b>
|
||||
</div>
|
||||
|
||||
<div class="flex-60 flex-lt-md-50">
|
||||
{{ rolePermissionService.name }}
|
||||
</div>
|
||||
|
||||
<div class="flex-40 flex-lt-md-50 header">
|
||||
<b>{{ 'labels.inputs.Role Description' | translate }}:</b>
|
||||
</div>
|
||||
|
||||
<div class="flex-60 flex-lt-md-50">
|
||||
{{ rolePermissionService.description }}
|
||||
</div>
|
||||
<div class="vr-container">
|
||||
<!-- Top Bar -->
|
||||
<div class="vr-topbar">
|
||||
<div class="vr-topbar__left">
|
||||
<i class="fas fa-key vr-topbar__icon"></i>
|
||||
<div>
|
||||
<h2 class="vr-topbar__title">{{ rolePermissionService.name }}</h2>
|
||||
@if (rolePermissionService.description) {
|
||||
<span class="vr-topbar__desc">{{ rolePermissionService.description }}</span>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vr-topbar__actions">
|
||||
<button mat-flat-button color="primary" class="vr-btn vr-btn--edit"
|
||||
(click)="editRoles(); backupCheckValues()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'">
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.text.Edit Role' | translate }}
|
||||
</button>
|
||||
@if (!isRoleEnable(rolePermissionService.disabled)) {
|
||||
<button mat-stroked-button class="vr-btn vr-btn--disable"
|
||||
(click)="disableRolesConfirmation()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'">
|
||||
<fa-icon icon="lock" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Disable' | translate }} {{ 'labels.inputs.Role' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (isRoleEnable(rolePermissionService.disabled)) {
|
||||
<button mat-flat-button color="primary" class="vr-btn vr-btn--enable"
|
||||
(click)="enableRolesConfirmation()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'">
|
||||
<fa-icon icon="lock-open" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Enable' | translate }} {{ 'labels.inputs.Role' | translate }}
|
||||
</button>
|
||||
}
|
||||
<button mat-stroked-button class="vr-btn vr-btn--delete"
|
||||
(click)="deleteRole()"
|
||||
[disabled]="rolePermissionService.name === 'Super user'">
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Delete' | translate }} {{ 'labels.inputs.Role' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container m-b-20">
|
||||
<mat-card>
|
||||
<ng-container>
|
||||
<div class="permission-selected layout-row">
|
||||
<div class="flex-75">
|
||||
<h3>
|
||||
{{ 'labels.buttons.Permissions' | translate }}: <strong>{{ formatName(previousGrouping) }}</strong>
|
||||
</h3>
|
||||
<!-- Permissions Card -->
|
||||
<div class="vr-card">
|
||||
<div class="vr-card__header">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<h3>{{ 'labels.buttons.Permissions' | translate }}: <strong>{{ formatName(previousGrouping) }}</strong></h3>
|
||||
@if (!isDisabled) {
|
||||
<div class="vr-card__header-actions">
|
||||
<button mat-flat-button color="primary" class="vr-btn-sm" (click)="selectAll()">
|
||||
{{ 'labels.buttons.Select All' | translate }}
|
||||
</button>
|
||||
<button mat-stroked-button class="vr-btn-sm vr-btn-sm--outline" (click)="deselectAll()">
|
||||
{{ 'labels.buttons.Deselect All' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!isDisabled) {
|
||||
<div class="flex-25 layout-row layout-xs-column layout-align-end gap-5px select-deselect">
|
||||
<button mat-raised-button color="primary" (click)="selectAll()">
|
||||
{{ 'labels.buttons.Select All' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="default" (click)="deselectAll()">
|
||||
{{ 'labels.buttons.Deselect All' | translate }}
|
||||
</button>
|
||||
<div class="vr-permissions">
|
||||
<!-- Grouped Category List (Left) -->
|
||||
<div class="vr-categories">
|
||||
@for (group of roleGroups; track group.name) {
|
||||
<div class="vr-group">
|
||||
<div class="vr-group__header" (click)="toggleGroup(group)">
|
||||
<i class="fas {{ group.icon }} vr-group__icon"></i>
|
||||
<span class="vr-group__name">{{ group.name }}</span>
|
||||
<span class="vr-group__count">{{ getGroupPermCount(group) }}</span>
|
||||
@if (!isDisabled) {
|
||||
<button class="vr-group__select-btn vr-group__select-btn--check" (click)="selectAllGroup(group); $event.stopPropagation()" title="Select all in {{ group.name }}">
|
||||
<i class="fas fa-check-double"></i>
|
||||
</button>
|
||||
<button class="vr-group__select-btn vr-group__select-btn--clear" (click)="deselectAllGroup(group); $event.stopPropagation()" title="Deselect all in {{ group.name }}">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
<i class="fas vr-group__chevron" [class.fa-chevron-down]="group.expanded" [class.fa-chevron-right]="!group.expanded"></i>
|
||||
</div>
|
||||
@if (group.expanded) {
|
||||
<div class="vr-group__items">
|
||||
@for (cat of group.categories; track cat) {
|
||||
<div class="vr-category-item"
|
||||
[class.vr-category-item--active]="selectedItem === cat"
|
||||
(click)="showPermissions(cat)">
|
||||
<span class="vr-category-item__name">{{ formatName(cat) }}</span>
|
||||
@if (!isDisabled) {
|
||||
<button class="vr-category-item__sel" (click)="selectAllCategory(cat); $event.stopPropagation()" title="Select all">
|
||||
<i class="fas fa-check"></i>
|
||||
</button>
|
||||
<button class="vr-category-item__desel" (click)="deselectAllCategory(cat); $event.stopPropagation()" title="Deselect all">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
<span class="vr-category-item__count">{{ getCatPermCount(cat) }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="layout-row">
|
||||
<div class="layout-column flex-30">
|
||||
<mat-list>
|
||||
@for (grouping of groupings; track grouping) {
|
||||
<mat-list-item
|
||||
[ngClass]="selectedItem === grouping ? 'active' : 'inactive'"
|
||||
(click)="showPermissions(grouping)"
|
||||
>
|
||||
<span class="grouping-name">
|
||||
{{ formatName(grouping) }}
|
||||
</span>
|
||||
</mat-list-item>
|
||||
}
|
||||
</mat-list>
|
||||
</div>
|
||||
<!-- Permission Checkboxes (Right) -->
|
||||
<div class="vr-checkboxes">
|
||||
<form [formGroup]="formGroup" (submit)="submit()">
|
||||
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
|
||||
<div class="flex-70 layout-column list-permission">
|
||||
<form [formGroup]="formGroup" (submit)="submit()">
|
||||
@for (permission of permissions.permissions; track permission) {
|
||||
<div class="display-permissions">
|
||||
<div formArrayName="roster">
|
||||
<div [formGroupName]="permission.id">
|
||||
<mat-checkbox name="cp" id="{{ permission.code }}" formControlName="selected">
|
||||
{{ permissionName(permission.code) }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<!-- Sub-grouped view for large categories -->
|
||||
@if (permissionSubGroups.length > 0) {
|
||||
@for (subGroup of permissionSubGroups; track subGroup.entity) {
|
||||
<div class="vr-subgroup">
|
||||
<div class="vr-subgroup__header" (click)="toggleSubGroup(subGroup)">
|
||||
<i class="fas" [class.fa-chevron-down]="subGroup.expanded" [class.fa-chevron-right]="!subGroup.expanded"></i>
|
||||
<span class="vr-subgroup__name">{{ getEntityDisplayName(subGroup.entity) }}</span>
|
||||
<span class="vr-subgroup__count">{{ getSubGroupCount(subGroup) }}</span>
|
||||
@if (!isDisabled) {
|
||||
<button type="button" class="vr-subgroup__select" (click)="selectAllSubGroup(subGroup); $event.stopPropagation()" title="Select all">
|
||||
<i class="fas fa-check-double"></i>
|
||||
</button>
|
||||
<button type="button" class="vr-subgroup__deselect" (click)="deselectAllSubGroup(subGroup); $event.stopPropagation()" title="Deselect all">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@if (subGroup.expanded) {
|
||||
<div class="vr-subgroup__columns">
|
||||
<div class="vr-subgroup__col">
|
||||
<div class="vr-subgroup__col-label">
|
||||
<span>Maker</span>
|
||||
@if (!isDisabled && subGroup.makerPerms.length > 0) {
|
||||
<button type="button" class="vr-col-btn vr-col-btn--select" (click)="selectAllMaker(subGroup.makerPerms)" title="Select all maker">
|
||||
<i class="fas fa-check"></i> All
|
||||
</button>
|
||||
<button type="button" class="vr-col-btn vr-col-btn--clear" (click)="deselectAllMaker(subGroup.makerPerms)" title="Deselect all maker">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@for (permission of subGroup.makerPerms; track permission) {
|
||||
<div class="vr-checkbox-item" formArrayName="roster">
|
||||
<div [formGroupName]="permission.id">
|
||||
<mat-checkbox name="cp" id="{{ permission.code }}" formControlName="selected">
|
||||
{{ permissionName(permission.code) }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (subGroup.makerPerms.length === 0) {
|
||||
<span class="vr-subgroup__empty">—</span>
|
||||
}
|
||||
</div>
|
||||
<div class="vr-subgroup__col vr-subgroup__col--checker">
|
||||
<div class="vr-subgroup__col-label vr-subgroup__col-label--checker">
|
||||
<span>Checker</span>
|
||||
@if (!isDisabled && subGroup.checkerPerms.length > 0) {
|
||||
<button type="button" class="vr-col-btn vr-col-btn--select-warn" (click)="selectAllChecker(subGroup.checkerPerms)" title="Select all checker">
|
||||
<i class="fas fa-check"></i> All
|
||||
</button>
|
||||
<button type="button" class="vr-col-btn vr-col-btn--clear" (click)="deselectAllChecker(subGroup.checkerPerms)" title="Deselect all checker">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@for (permission of subGroup.checkerPerms; track permission) {
|
||||
<div class="vr-checkbox-item" formArrayName="roster">
|
||||
<div [formGroupName]="permission.id">
|
||||
<mat-checkbox name="cp" id="{{ permission.code }}" formControlName="selected">
|
||||
{{ permissionName(permission.code) }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (subGroup.checkerPerms.length === 0) {
|
||||
<span class="vr-subgroup__empty">—</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Flat maker/checker columns for small categories -->
|
||||
<div class="vr-flat-columns">
|
||||
<div class="vr-flat-col">
|
||||
<div class="vr-flat-col__label">
|
||||
<span>Maker</span>
|
||||
@if (!isDisabled && flatMakerPerms.length > 0) {
|
||||
<button type="button" class="vr-col-btn vr-col-btn--select" (click)="selectAllMaker(flatMakerPerms)" title="Select all maker">
|
||||
<i class="fas fa-check"></i> All
|
||||
</button>
|
||||
<button type="button" class="vr-col-btn vr-col-btn--clear" (click)="deselectAllMaker(flatMakerPerms)" title="Deselect all">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@for (permission of flatMakerPerms; track permission) {
|
||||
<div class="vr-checkbox-item" formArrayName="roster">
|
||||
<div [formGroupName]="permission.id">
|
||||
<mat-checkbox name="cp" id="{{ permission.code }}" formControlName="selected">
|
||||
{{ permissionName(permission.code) }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="vr-flat-col vr-flat-col--checker">
|
||||
<div class="vr-flat-col__label vr-flat-col__label--checker">
|
||||
<span>Checker</span>
|
||||
@if (!isDisabled && flatCheckerPerms.length > 0) {
|
||||
<button type="button" class="vr-col-btn vr-col-btn--select-warn" (click)="selectAllChecker(flatCheckerPerms)" title="Select all checker">
|
||||
<i class="fas fa-check"></i> All
|
||||
</button>
|
||||
<button type="button" class="vr-col-btn vr-col-btn--clear" (click)="deselectAllChecker(flatCheckerPerms)" title="Deselect all">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@for (permission of flatCheckerPerms; track permission) {
|
||||
<div class="vr-checkbox-item" formArrayName="roster">
|
||||
<div [formGroupName]="permission.id">
|
||||
<mat-checkbox name="cp" id="{{ permission.code }}" formControlName="selected">
|
||||
{{ permissionName(permission.code) }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (flatCheckerPerms.length === 0) {
|
||||
<span class="vr-subgroup__empty">No checker permissions</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@if (!isDisabled) {
|
||||
<div class="layout-row layout-align-center gap-10px layout-xs-column">
|
||||
<mat-card-actions>
|
||||
<button type="button" mat-raised-button (click)="cancel(); restoreCheckboxes()">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="submit()">
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
<div class="vr-card__footer">
|
||||
<button type="button" mat-stroked-button class="vr-btn-action vr-btn-action--cancel" (click)="cancel(); restoreCheckboxes()">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-flat-button color="primary" class="vr-btn-action vr-btn-action--submit" (click)="submit()">
|
||||
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,108 +6,620 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use '../../../../assets/styles/colours' as *;
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-700: #5f6368;
|
||||
$gray-900: #202124;
|
||||
|
||||
.container {
|
||||
padding: 1rem;
|
||||
// Container
|
||||
.vr-container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
mat-card {
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba($black, 0.1);
|
||||
border: 1px solid var(--border-color, $mid-grey);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease;
|
||||
// Top Bar
|
||||
.vr-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
mat-card-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.layout-row {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.role-details-card {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.display-permissions {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
&__icon {
|
||||
font-size: 18px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
mat-list-item {
|
||||
cursor: pointer;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.mat-list-base .mat-list-item .mat-list-item-content {
|
||||
cursor: pointer;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.grouping-name {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.list-permission {
|
||||
padding-left: 24px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.permission-selected {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
height: auto;
|
||||
min-height: 40px;
|
||||
padding-bottom: 8px;
|
||||
|
||||
h3 {
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 12px;
|
||||
color: $gray-500;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.inactive {
|
||||
transition: all 0.2s ease-in-out;
|
||||
// Top bar buttons
|
||||
.vr-btn {
|
||||
border-radius: 8px !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 34px !important;
|
||||
|
||||
&--edit {
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
}
|
||||
|
||||
&--disable {
|
||||
border-color: #f97316 !important;
|
||||
color: #f97316 !important;
|
||||
|
||||
&:hover {
|
||||
background: rgba(#f97316, 0.05) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--enable {
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
}
|
||||
|
||||
&--delete {
|
||||
border-color: #ef4444 !important;
|
||||
color: #ef4444 !important;
|
||||
|
||||
&:hover {
|
||||
background: rgba(#ef4444, 0.05) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Section Cards
|
||||
.vr-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 24px;
|
||||
border-top: 1px solid $gray-200;
|
||||
background: $gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
// Role Details
|
||||
.vr-details {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.vr-detail-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&__label {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
// Permissions Split Layout
|
||||
.vr-permissions {
|
||||
display: flex;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
// Category List (Left)
|
||||
.vr-categories {
|
||||
width: 240px;
|
||||
min-width: 200px;
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid $border;
|
||||
overflow-y: auto;
|
||||
background: #fafbfc;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
// Role Group
|
||||
.vr-group {
|
||||
border-bottom: 1px solid #f1f3f5;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
user-select: none;
|
||||
|
||||
&:hover { background: #f0f4f8; }
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 12px;
|
||||
color: $accent;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__name {
|
||||
flex: 1;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: $accent;
|
||||
border-radius: 10px;
|
||||
padding: 1px 7px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__select-btn {
|
||||
background: none;
|
||||
border: 1px solid $border;
|
||||
border-radius: 4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 8px;
|
||||
transition: all 0.15s;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--check {
|
||||
color: $accent;
|
||||
&:hover { background: rgba($accent, 0.1); border-color: $accent; }
|
||||
}
|
||||
|
||||
&--clear {
|
||||
color: $gray-500;
|
||||
&:hover { background: rgba(#ef4444, 0.05); border-color: #ef4444; color: #ef4444; }
|
||||
}
|
||||
}
|
||||
|
||||
&__chevron {
|
||||
font-size: 9px;
|
||||
color: $gray-500;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
&__items {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.vr-category-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 7px 14px 7px 38px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: $gray-700;
|
||||
cursor: pointer;
|
||||
border-left: 3px solid transparent;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&__name {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__sel, &__desel {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 7px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
&:hover &__sel, &:hover &__desel { opacity: 1; }
|
||||
|
||||
&__sel {
|
||||
color: $accent;
|
||||
&:hover { background: rgba($accent, 0.1); border-color: $accent; }
|
||||
}
|
||||
|
||||
&__desel {
|
||||
color: $gray-500;
|
||||
&:hover { background: rgba(#ef4444, 0.05); border-color: #ef4444; color: #ef4444; }
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 10px;
|
||||
color: $gray-500;
|
||||
background: #f1f3f5;
|
||||
border-radius: 8px;
|
||||
padding: 1px 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.01);
|
||||
background-color: $light-grey;
|
||||
background: #f0f4f8;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #f0f9ff;
|
||||
border-left: 3px solid $accent;
|
||||
color: $accent;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
div {
|
||||
line-height: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
// Permission Checkboxes (Right)
|
||||
.vr-checkboxes {
|
||||
flex: 1;
|
||||
padding: 12px 20px;
|
||||
overflow-y: auto;
|
||||
max-height: none;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
align-content: start;
|
||||
|
||||
&.header {
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 0;
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-groups for large categories
|
||||
.vr-subgroup {
|
||||
border: 1px solid #f1f3f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
& + & { margin-top: 6px; }
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f8fafc;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
user-select: none;
|
||||
|
||||
&:hover { background: #f0f4f8; }
|
||||
|
||||
> i { font-size: 9px; color: $gray-500; width: 12px; }
|
||||
}
|
||||
|
||||
&__name {
|
||||
flex: 1;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: $accent;
|
||||
background: rgba($accent, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 1px 7px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__select, &__deselect {
|
||||
background: none;
|
||||
border: 1px solid $border;
|
||||
border-radius: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 9px;
|
||||
transition: all 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__select {
|
||||
color: $accent;
|
||||
&:hover { background: rgba($accent, 0.1); border-color: $accent; }
|
||||
}
|
||||
|
||||
&__deselect {
|
||||
color: $gray-500;
|
||||
&:hover { background: rgba(#ef4444, 0.05); border-color: #ef4444; color: #ef4444; }
|
||||
}
|
||||
|
||||
&__columns {
|
||||
display: flex;
|
||||
border-top: 1px solid #f1f3f5;
|
||||
}
|
||||
|
||||
&__col {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
|
||||
&--checker {
|
||||
border-left: 1px solid #f1f3f5;
|
||||
background: #fdfefe;
|
||||
}
|
||||
}
|
||||
|
||||
&__col-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: $accent;
|
||||
padding: 2px 0 6px;
|
||||
border-bottom: 1px solid #f1f3f5;
|
||||
margin-bottom: 4px;
|
||||
|
||||
span { flex: 1; }
|
||||
|
||||
&--checker { color: #f59e0b; }
|
||||
}
|
||||
|
||||
&__empty {
|
||||
font-size: 11px;
|
||||
color: $gray-500;
|
||||
padding: 6px 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Column select/deselect buttons
|
||||
.vr-col-btn {
|
||||
background: none;
|
||||
border: 1px solid $border;
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--select {
|
||||
color: $accent;
|
||||
&:hover { background: rgba($accent, 0.1); border-color: $accent; }
|
||||
}
|
||||
|
||||
&--select-warn {
|
||||
color: #f59e0b;
|
||||
&:hover { background: rgba(#f59e0b, 0.1); border-color: #f59e0b; }
|
||||
}
|
||||
|
||||
&--clear {
|
||||
color: $gray-500;
|
||||
&:hover { background: rgba(#ef4444, 0.05); border-color: #ef4444; color: #ef4444; }
|
||||
}
|
||||
}
|
||||
|
||||
// Flat maker/checker columns for small categories
|
||||
.vr-flat-columns {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vr-flat-col {
|
||||
flex: 1;
|
||||
padding: 4px 0;
|
||||
|
||||
&--checker {
|
||||
border-left: 1px solid #f1f3f5;
|
||||
padding-left: 16px;
|
||||
background: #fdfefe;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: $accent;
|
||||
padding: 2px 0 6px;
|
||||
border-bottom: 1px solid #f1f3f5;
|
||||
margin-bottom: 4px;
|
||||
|
||||
span { flex: 1; }
|
||||
|
||||
&--checker { color: #f59e0b; }
|
||||
}
|
||||
}
|
||||
|
||||
.vr-checkbox-item {
|
||||
padding: 3px 0;
|
||||
|
||||
::ng-deep .mat-mdc-checkbox {
|
||||
.mdc-label {
|
||||
font-size: 11.5px;
|
||||
color: $gray-700;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-card-actions {
|
||||
padding: 16px 0 0;
|
||||
margin: 0;
|
||||
// Select/Deselect buttons in header
|
||||
.vr-btn-sm {
|
||||
border-radius: 6px !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 28px !important;
|
||||
padding: 0 12px !important;
|
||||
min-width: auto !important;
|
||||
|
||||
button {
|
||||
margin-right: 8px;
|
||||
&--outline {
|
||||
border-color: $border !important;
|
||||
color: $gray-700 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
// Footer action buttons
|
||||
.vr-btn-action {
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
height: 36px !important;
|
||||
|
||||
&--cancel {
|
||||
border-color: $border !important;
|
||||
color: $gray-700 !important;
|
||||
}
|
||||
|
||||
&--submit {
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
|
||||
i { margin-right: 6px; font-size: 12px; }
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (max-width: 768px) {
|
||||
.vr-topbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
&__actions {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.vr-permissions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vr-categories {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid $border;
|
||||
max-height: 220px;
|
||||
}
|
||||
|
||||
.vr-checkboxes {
|
||||
width: 100%;
|
||||
|
||||
form {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.vr-detail-row {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&__label {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dial
|
||||
import { DisableDialogComponent } from '../../../shared/disable-dialog/disable-dialog.component';
|
||||
import { EnableDialogComponent } from '../../../shared/enable-dialog/enable-dialog.component';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { NgClass } from '@angular/common';
|
||||
import { MatList, MatListItem } from '@angular/material/list';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@@ -40,10 +37,6 @@ import { AuthService } from 'app/zitadel/auth.service';
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
MatList,
|
||||
MatListItem,
|
||||
NgClass,
|
||||
MatDivider,
|
||||
MatCheckbox
|
||||
]
|
||||
})
|
||||
@@ -66,6 +59,65 @@ export class ViewRoleComponent implements OnInit {
|
||||
groupings: string[] = [];
|
||||
/** Stores the selected role */
|
||||
selectedItem = '';
|
||||
/** Grouped categories for sidebar */
|
||||
roleGroups: { name: string; icon: string; categories: string[]; expanded: boolean }[] = [];
|
||||
/** Mapping of category groupings to role groups */
|
||||
private categoryToGroupMap: { [key: string]: string } = {
|
||||
// Special & Authorisation
|
||||
'special': 'Special & Authorisation',
|
||||
'authorisation': 'Special & Authorisation',
|
||||
|
||||
// Configuration & Admin
|
||||
'configuration': 'Configuration & Admin',
|
||||
'organisation': 'Configuration & Admin',
|
||||
'datatable': 'Configuration & Admin',
|
||||
'jobs': 'Configuration & Admin',
|
||||
|
||||
// Portfolio — Client, Loan, Savings, FD, RD, Guarantor etc.
|
||||
'portfolio': 'Portfolio',
|
||||
'portfolio_group': 'Portfolio — Groups & Centers',
|
||||
'portfolio_center': 'Portfolio — Groups & Centers',
|
||||
|
||||
// Transactions
|
||||
'transaction_loan': 'Transactions — Loan',
|
||||
'transaction_savings': 'Transactions — Savings',
|
||||
'transaction_shareaccount': 'Transactions — Share Account',
|
||||
|
||||
// Accounting & Finance
|
||||
'accounting': 'Accounting & Finance',
|
||||
'loan_provisioning': 'Accounting & Finance',
|
||||
'loan_reschedule': 'Accounting & Finance',
|
||||
|
||||
// Cash Management
|
||||
'cash_mgmt': 'Cash Management',
|
||||
|
||||
// Reporting
|
||||
'report': 'Reporting & Analytics',
|
||||
|
||||
// Share Account
|
||||
'shareaccount': 'Share Account',
|
||||
|
||||
// Heading Menu (Top Navigation Bar)
|
||||
'heading_menu': 'Heading Menu',
|
||||
|
||||
// Left Panel Menu (Sidebar)
|
||||
'sidebar_menu': 'Left Panel Menu',
|
||||
};
|
||||
private groupIcons: { [key: string]: string } = {
|
||||
'Special & Authorisation': 'fa-star',
|
||||
'Configuration & Admin': 'fa-cog',
|
||||
'Portfolio': 'fa-briefcase',
|
||||
'Portfolio — Groups & Centers': 'fa-users',
|
||||
'Transactions — Loan': 'fa-exchange-alt',
|
||||
'Transactions — Savings': 'fa-piggy-bank',
|
||||
'Transactions — Share Account': 'fa-share-alt',
|
||||
'Accounting & Finance': 'fa-calculator',
|
||||
'Cash Management': 'fa-cash-register',
|
||||
'Reporting & Analytics': 'fa-chart-bar',
|
||||
'Share Account': 'fa-chart-pie',
|
||||
'Heading Menu': 'fa-th-list',
|
||||
'Left Panel Menu': 'fa-bars',
|
||||
};
|
||||
/** Checks if its disabled */
|
||||
isDisabled: Boolean = true;
|
||||
/** Checks if there is any change in data */
|
||||
@@ -88,6 +140,18 @@ export class ViewRoleComponent implements OnInit {
|
||||
permissions: {
|
||||
permissions: { code: string; id: number }[];
|
||||
} = { permissions: [] };
|
||||
/** Sub-grouped permissions for large categories */
|
||||
permissionSubGroups: {
|
||||
entity: string;
|
||||
makerPerms: { code: string; id: number }[];
|
||||
checkerPerms: { code: string; id: number }[];
|
||||
expanded: boolean;
|
||||
}[] = [];
|
||||
/** Flat maker/checker split for small categories */
|
||||
flatMakerPerms: { code: string; id: number }[] = [];
|
||||
flatCheckerPerms: { code: string; id: number }[] = [];
|
||||
/** Threshold for sub-grouping */
|
||||
subGroupThreshold = 30;
|
||||
/** Add role zitadel */
|
||||
|
||||
/**
|
||||
@@ -114,6 +178,7 @@ export class ViewRoleComponent implements OnInit {
|
||||
};
|
||||
this.createForm();
|
||||
this.groupRules();
|
||||
this.buildRoleGroups();
|
||||
this.selectedItem = 'special';
|
||||
this.showPermissions('special');
|
||||
this.route.params.subscribe((routeParams: any) => {
|
||||
@@ -170,6 +235,285 @@ export class ViewRoleComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds role groups from flat groupings
|
||||
*/
|
||||
buildRoleGroups() {
|
||||
const groupMap: { [key: string]: string[] } = {};
|
||||
for (const g of this.groupings) {
|
||||
const groupName = this.categoryToGroupMap[g] || 'Other';
|
||||
if (!groupMap[groupName]) {
|
||||
groupMap[groupName] = [];
|
||||
}
|
||||
groupMap[groupName].push(g);
|
||||
}
|
||||
const order = [
|
||||
'Special & Authorisation',
|
||||
'Configuration & Admin',
|
||||
'Portfolio',
|
||||
'Portfolio — Groups & Centers',
|
||||
'Transactions — Loan',
|
||||
'Transactions — Savings',
|
||||
'Transactions — Share Account',
|
||||
'Accounting & Finance',
|
||||
'Cash Management',
|
||||
'Reporting & Analytics',
|
||||
'Share Account',
|
||||
'Heading Menu',
|
||||
'Left Panel Menu',
|
||||
'Other'
|
||||
];
|
||||
this.roleGroups = order
|
||||
.filter(name => groupMap[name])
|
||||
.map(name => ({
|
||||
name,
|
||||
icon: this.groupIcons[name] || 'fa-folder',
|
||||
categories: groupMap[name],
|
||||
expanded: name === 'Special & Authorisation'
|
||||
}));
|
||||
}
|
||||
|
||||
toggleGroup(group: any) {
|
||||
group.expanded = !group.expanded;
|
||||
}
|
||||
|
||||
selectAllGroup(group: any) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const cat of group.categories) {
|
||||
const perms = this.tempPermissionUIData[cat]?.permissions || [];
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllGroup(group: any) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const cat of group.categories) {
|
||||
const perms = this.tempPermissionUIData[cat]?.permissions || [];
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectAllCategory(cat: string) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
const perms = this.tempPermissionUIData[cat]?.permissions || [];
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: true });
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllCategory(cat: string) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
const perms = this.tempPermissionUIData[cat]?.permissions || [];
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: false });
|
||||
}
|
||||
}
|
||||
|
||||
getGroupPermCount(group: any): number {
|
||||
let count = 0;
|
||||
for (const cat of group.categories) {
|
||||
count += this.tempPermissionUIData[cat]?.permissions?.length || 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
getCatPermCount(cat: string): number {
|
||||
return this.tempPermissionUIData[cat]?.permissions?.length || 0;
|
||||
}
|
||||
|
||||
/** Entity display names */
|
||||
private entityNames: { [key: string]: string } = {
|
||||
'CLIENT': 'Client', 'CLIENTIMAGE': 'Client Image', 'CLIENTIDENTIFIER': 'Client Identifier',
|
||||
'CLIENTNOTE': 'Client Note', 'CLIENTCHARGE': 'Client Charge',
|
||||
'LOAN': 'Loan', 'LOANNOTE': 'Loan Note', 'LOANCHARGE': 'Loan Charge',
|
||||
'LOANTRANSACTIONNOTE': 'Loan Transaction Note',
|
||||
'SAVINGSACCOUNT': 'Savings Account', 'SAVINGSACCOUNTCHARGE': 'Savings Account Charge',
|
||||
'SAVINGNOTE': 'Savings Note',
|
||||
'FIXEDDEPOSITACCOUNT': 'Fixed Deposit Account',
|
||||
'RECURRINGDEPOSITACCOUNT': 'Recurring Deposit Account',
|
||||
'SHAREACCOUNT': 'Share Account',
|
||||
'GROUP': 'Group', 'GROUPNOTE': 'Group Note',
|
||||
'CENTER': 'Center',
|
||||
'GUARANTOR': 'Guarantor', 'COLLATERAL': 'Collateral', 'DOCUMENT': 'Document',
|
||||
'ACCOUNTTRANSFER': 'Account Transfer', 'STANDINGINSTRUCTION': 'Standing Instruction',
|
||||
'RESCHEDULELOAN': 'Reschedule Loan',
|
||||
'JOURNALENTRY': 'Journal Entry',
|
||||
'TELLER': 'Teller', 'CASHIER': 'Cashier',
|
||||
'ROLE': 'Role', 'USER': 'User', 'PERMISSION': 'Permission',
|
||||
'CODE': 'Code', 'CODEVALUE': 'Code Value',
|
||||
'OFFICE': 'Office', 'OFFICETRANSACTION': 'Office Transaction',
|
||||
'STAFF': 'Staff', 'STAFFIMAGE': 'Staff Image',
|
||||
'CHARGE': 'Charge', 'FUND': 'Fund',
|
||||
'LOANPRODUCT': 'Loan Product', 'SAVINGSPRODUCT': 'Savings Product',
|
||||
'FIXEDDEPOSITPRODUCT': 'Fixed Deposit Product', 'RECURRINGDEPOSITPRODUCT': 'Recurring Deposit Product',
|
||||
'SHAREPRODUCT': 'Share Product',
|
||||
'CURRENCY': 'Currency', 'DATATABLE': 'Data Table', 'CALENDAR': 'Calendar',
|
||||
'CONFIGURATION': 'Configuration', 'AUDIT': 'Audit',
|
||||
'REPORT': 'Report', 'HOOK': 'Hook', 'TEMPLATE': 'Template',
|
||||
'PAYMENTTYPE': 'Payment Type', 'HOLIDAY': 'Holiday', 'WORKINGDAYS': 'Working Days',
|
||||
'GLACCOUNT': 'GL Account', 'GLCLOSURE': 'GL Closure',
|
||||
'FINANCIALACTIVITYACCOUNT': 'Financial Activity Account',
|
||||
'TAXCOMPONENT': 'Tax Component', 'TAXGROUP': 'Tax Group',
|
||||
'PRODUCTMIX': 'Product Mix', 'EXTERNALSERVICES': 'External Services',
|
||||
'SCHEDULER': 'Scheduler', 'MEETING': 'Meeting',
|
||||
'INTERESTRATECHART': 'Interest Rate Chart', 'FLOATINGRATE': 'Floating Rate',
|
||||
'ACCOUNTNUMBERFORMAT': 'Account Number Format', 'ACCOUNTINGRULE': 'Accounting Rule',
|
||||
'ENTITYMAPPING': 'Entity Mapping', 'PROVISION': 'Provisioning',
|
||||
'REPORTMAILINGJOB': 'Report Mailing Job',
|
||||
'PASSWORD_PREFERENCES': 'Password Preferences', 'ADDRESS': 'Address',
|
||||
'CACHE': 'Cache',
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts entity name from permission code (e.g., CREATE_CLIENT -> CLIENT)
|
||||
*/
|
||||
private extractEntity(code: string): string {
|
||||
// Common action prefixes to strip
|
||||
const actions = [
|
||||
'CREATE', 'READ', 'UPDATE', 'DELETE', 'APPROVE', 'REJECT', 'ACTIVATE', 'DEACTIVATE',
|
||||
'CLOSE', 'DEPOSIT', 'WITHDRAWAL', 'WITHDRAW', 'DISBURSE', 'DISBURSETOSAVINGS',
|
||||
'DISBURSALUNDO', 'DISBURSALLASTUNDO', 'REPAYMENT', 'ADJUST', 'WAIVE', 'WAIVEINTERESTPORTION',
|
||||
'WRITEOFF', 'UNDOWRITEOFF', 'UNDOAPPROVAL', 'UNDOTRANSACTION', 'UNDO',
|
||||
'CLOSEASRESCHEDULED', 'FORECLOSURE', 'REOPEN', 'REACTIVATE',
|
||||
'POSTINTEREST', 'CALCULATEINTEREST', 'APPLYANNUALFEE', 'PREMATURECLOSE',
|
||||
'UPDATEDEPOSITAMOUNT', 'WITHDRAWWITHHELD', 'UPDATELOANOFFICER', 'REMOVELOANOFFICER',
|
||||
'BULKREASSIGN', 'REFUNDBYCASH', 'REFUNDBYTRANSFER',
|
||||
'RECOVERGUARANTEES', 'RECOVERYPAYMENT',
|
||||
'CREATESCHEDULEEXCEPTIONS', 'DELETESCHEDULEEXCEPTIONS',
|
||||
'UPDATERUNNINGBALANCE', 'DEFINEOPENINGBALANCE', 'REVERSE',
|
||||
'REGISTER', 'DEREGISTER', 'ENABLE', 'DISABLE', 'PERMISSIONS',
|
||||
'INACTIVATE', 'PAY', 'PROPOSETRANSFER', 'ACCEPTTRANSFER', 'REJECTTRANSFER',
|
||||
'WITHDRAWTRANSFER', 'ASSIGNSTAFF', 'UNASSIGNSTAFF', 'ASSIGNROLE', 'UPDATEROLE',
|
||||
'DISASSOCIATECLIENTS', 'ASSOCIATECLIENTS', 'TRANSFERCLIENTS',
|
||||
'ASSOCIATEGROUPS', 'DISASSOCIATEGROUPS',
|
||||
'ALLOCATECASHIER', 'ALLOCATECASHTOCASHIER', 'SETTLECASHFROMCASHIER',
|
||||
'EXECUTEJOB', 'EXECUTE', 'DIVIDEND',
|
||||
'APPLYADDITIONALSHARES', 'APPROVEADDITIONALSHARES', 'REJECTADDITIONALSHARES', 'REDEEMSHARES',
|
||||
];
|
||||
|
||||
// Try to match action_entity pattern
|
||||
for (const action of actions) {
|
||||
if (code.startsWith(action + '_')) {
|
||||
const entity = code.substring(action.length + 1);
|
||||
// Remove _CHECKER suffix
|
||||
return entity.replace(/_CHECKER$/, '');
|
||||
}
|
||||
}
|
||||
// Fallback: return the code itself
|
||||
return code.replace(/_CHECKER$/, '');
|
||||
}
|
||||
|
||||
private isChecker(code: string): boolean {
|
||||
return code.endsWith('_CHECKER');
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits permissions into maker/checker for flat display
|
||||
*/
|
||||
buildFlatMakerChecker() {
|
||||
const perms = this.permissions.permissions;
|
||||
this.flatMakerPerms = perms.filter(p => !this.isChecker(p.code));
|
||||
this.flatCheckerPerms = perms.filter(p => this.isChecker(p.code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds sub-groups for permissions in the current category
|
||||
*/
|
||||
buildSubGroups() {
|
||||
const perms = this.permissions.permissions;
|
||||
this.buildFlatMakerChecker();
|
||||
|
||||
if (perms.length <= this.subGroupThreshold) {
|
||||
this.permissionSubGroups = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const groups: { [entity: string]: { maker: { code: string; id: number }[]; checker: { code: string; id: number }[] } } = {};
|
||||
for (const p of perms) {
|
||||
const entity = this.extractEntity(p.code);
|
||||
if (!groups[entity]) {
|
||||
groups[entity] = { maker: [], checker: [] };
|
||||
}
|
||||
if (this.isChecker(p.code)) {
|
||||
groups[entity].checker.push(p);
|
||||
} else {
|
||||
groups[entity].maker.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
this.permissionSubGroups = Object.keys(groups).map(entity => ({
|
||||
entity,
|
||||
makerPerms: groups[entity].maker,
|
||||
checkerPerms: groups[entity].checker,
|
||||
expanded: false
|
||||
}));
|
||||
}
|
||||
|
||||
getEntityDisplayName(entity: string): string {
|
||||
return this.entityNames[entity] || entity.replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
toggleSubGroup(subGroup: any) {
|
||||
subGroup.expanded = !subGroup.expanded;
|
||||
}
|
||||
|
||||
selectAllSubGroup(subGroup: any) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const p of [...subGroup.makerPerms, ...subGroup.checkerPerms]) {
|
||||
roster.at(p.id).patchValue({ selected: true });
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllSubGroup(subGroup: any) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const p of [...subGroup.makerPerms, ...subGroup.checkerPerms]) {
|
||||
roster.at(p.id).patchValue({ selected: false });
|
||||
}
|
||||
}
|
||||
|
||||
getSubGroupCount(subGroup: any): number {
|
||||
return subGroup.makerPerms.length + subGroup.checkerPerms.length;
|
||||
}
|
||||
|
||||
selectAllMaker(perms: { code: string; id: number }[]) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: true });
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllMaker(perms: { code: string; id: number }[]) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: false });
|
||||
}
|
||||
}
|
||||
|
||||
selectAllChecker(perms: { code: string; id: number }[]) {
|
||||
const dialogRef = this.dialog.open(EnableDialogComponent, {
|
||||
data: { enableContext: this.translateService.instant('labels.text.all Checker permissions for this section. This grants approval authority') }
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response?.enable) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deselectAllChecker(perms: { code: string; id: number }[]) {
|
||||
const roster = this.formGroup.get('roster') as FormArray;
|
||||
for (const p of perms) {
|
||||
roster.at(p.id).patchValue({ selected: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the permission for selected role
|
||||
* @param grouping Selected Role
|
||||
@@ -178,6 +522,7 @@ export class ViewRoleComponent implements OnInit {
|
||||
this.permissions = this.tempPermissionUIData[grouping];
|
||||
this.selectedItem = grouping;
|
||||
this.previousGrouping = grouping;
|
||||
this.buildSubGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,587 +6,234 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<div class="layout-row responsive-column gap-20px">
|
||||
<div class="flex-50">
|
||||
<mat-nav-list>
|
||||
<div #datatables>
|
||||
<mat-list-item [routerLink]="['data-tables']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['data-tables']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="table" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage Data tables' | translate }}
|
||||
@if (arrowBooleans[0]) {
|
||||
<p matLine [routerLink]="['data-tables']" class="menu-explanation">
|
||||
{{ 'labels.text.Add new extra fields to any entity' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[0]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(0); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[0]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(0); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
<div #codes>
|
||||
<mat-list-item matListIcon [routerLink]="['codes']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['codes']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="list-ul" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div>
|
||||
{{ 'labels.heading.Manage Codes' | translate }}
|
||||
@if (arrowBooleans[1]) {
|
||||
<p matLine [routerLink]="['codes']" class="menu-explanation">
|
||||
{{ 'labels.text.Codes are used to define drop down values' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[1]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(1); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[1]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(1); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
<div #rolesandpermission>
|
||||
<mat-list-item [routerLink]="['roles-and-permissions']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['roles-and-permissions']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="key" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage Roles and Permissions' | translate }}
|
||||
@if (arrowBooleans[2]) {
|
||||
<p matLine [routerLink]="['roles-and-permissions']" class="menu-explanation">
|
||||
{{ 'labels.text.Define or modify roles and associated permissions' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[2]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(2); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[2]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(2); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
<div #makerCheckerTable>
|
||||
<mat-list-item [routerLink]="['configure-mc-tasks']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['configure-mc-tasks']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="sitemap" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine [routerLink]="['configure-mc-tasks']">
|
||||
{{ 'labels.heading.Configure Maker Checker Tasks' | translate }}
|
||||
@if (arrowBooleans[3]) {
|
||||
<p matLine [routerLink]="['configure-mc-tasks']" class="menu-explanation">
|
||||
{{ 'labels.text.Define or modify Maker Checker tasks' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[3]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(3); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[3]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(3); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
<mat-list-item [routerLink]="['hooks']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['hooks']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="anchor" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage Hooks' | translate }}
|
||||
@if (arrowBooleans[4]) {
|
||||
<p matLine [routerLink]="['hooks']" class="menu-explanation">
|
||||
{{ 'labels.text.Define Hooks' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[4]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(4); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[4]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(4); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['entity-to-entity-mapping']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['entity-to-entity-mapping']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="road" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Entity to Entity Mapping' | translate }}
|
||||
@if (arrowBooleans[5]) {
|
||||
<p matLine [routerLink]="['entity-to-entity-mapping']" class="menu-explanation">
|
||||
{{ 'labels.text.Define or modify entity to entity mappings' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[5]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(5); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[5]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(5); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [ngClass]="{ 'disabled-item': isDisabled }">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="file-alt" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage Surveys' | translate }}
|
||||
@if (arrowBooleans[6]) {
|
||||
<p matLine class="menu-explanation">
|
||||
{{ 'labels.text.Manage Surveys' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[6]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(6); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[6]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(6); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['external-events']" *mifosxHasPermission="'READ_EXTERNAL_EVENT_CONFIGURATION'">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['external-events']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="anchor" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage External Events' | translate }}
|
||||
@if (arrowBooleans[7]) {
|
||||
<p matLine [routerLink]="['external-events']" class="menu-explanation">
|
||||
{{ 'labels.text.External Events configuration, to enable or disable' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[7]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(7); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[7]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(7); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['system-information']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['system-information']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="info-circle" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.System Information' | translate }}
|
||||
@if (arrowBooleans[15]) {
|
||||
<p matLine [routerLink]="['system-information']" class="menu-explanation">
|
||||
{{ 'labels.text.View system version, server and licensing information' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[15]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(15); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[15]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(15); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
<mat-nav-list>
|
||||
<mat-list-item [routerLink]="['audit-trails']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['audit-trails']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="money-check" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Audit Trails' | translate }}
|
||||
@if (arrowBooleans[8]) {
|
||||
<p matLine [routerLink]="['audit-trails']" class="menu-explanation">
|
||||
{{ 'labels.text.Audit logs of all the activities' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[8]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(8); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[8]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(8); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<div #manageReports>
|
||||
<mat-list-item [routerLink]="['reports']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['reports']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="file-word" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage Reports' | translate }}
|
||||
@if (arrowBooleans[9]) {
|
||||
<p matLine [routerLink]="['reports']" class="menu-explanation">
|
||||
{{ 'labels.text.Add new report and classify reports' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[9]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(9); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[9]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(9); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
|
||||
<mat-list-item [routerLink]="['manage-jobs']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['manage-jobs']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="clock" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Manage Jobs' | translate }}
|
||||
@if (arrowBooleans[10]) {
|
||||
<p matLine [routerLink]="['manage-jobs']" class="menu-explanation">
|
||||
{{ 'labels.text.Manage Schedule and Workflow jobs, modify jobs' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[10]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(10); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[10]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(10); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['configurations']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['configurations']">
|
||||
<mat-icon>
|
||||
<fa-icon icon="cogs" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Configurations' | translate }}
|
||||
@if (arrowBooleans[11]) {
|
||||
<p matLine [routerLink]="['configurations']" class="menu-explanation">
|
||||
{{ 'labels.text.Global configurations, Cache and Business Date' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[11]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(11); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[11]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(11); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['account-number-preferences']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['account-number-preferences']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="key" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.Account Number Preferences' | translate }}
|
||||
@if (arrowBooleans[12]) {
|
||||
<p matLine [routerLink]="['account-number-preferences']" class="menu-explanation">
|
||||
{{ 'labels.text.Preferences for generating account numbers for client' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[12]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(12); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[12]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(12); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['external-services']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['external-services']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="cog" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.External Services' | translate }}
|
||||
@if (arrowBooleans[13]) {
|
||||
<p matLine [routerLink]="['external-services']" class="menu-explanation">
|
||||
{{ 'labels.text.External Services Configuration' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[13]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(13); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[13]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(13); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item [routerLink]="['about-us']">
|
||||
<div class="menu-list-item-content">
|
||||
<div class="menu-left-section" [routerLink]="['about-us']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="users" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<div matLine>
|
||||
{{ 'labels.heading.About Us' | translate }}
|
||||
@if (arrowBooleans[16]) {
|
||||
<p matLine [routerLink]="['about-us']" class="menu-explanation">
|
||||
{{ 'labels.text.Learn about the Mifos Initiative and our mission' | translate }}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-right-section">
|
||||
@if (!arrowBooleans[16]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(16); $event.stopPropagation()"
|
||||
icon="arrow-down"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
@if (arrowBooleans[16]) {
|
||||
<fa-icon
|
||||
(click)="arrowBooleansToggle(16); $event.stopPropagation()"
|
||||
icon="arrow-up"
|
||||
size="md"
|
||||
></fa-icon>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
<div class="sys-container">
|
||||
<!-- Page header -->
|
||||
<div class="sys-header">
|
||||
<div class="sys-header__icon">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
</mat-card>
|
||||
<div>
|
||||
<h1 class="sys-header__title">{{ 'labels.heading.System' | translate }}</h1>
|
||||
<p class="sys-header__sub">Manage data tables, codes, roles, configurations and system settings</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Grid -->
|
||||
<div class="sys-grid">
|
||||
<!-- Manage Data Tables -->
|
||||
<div #datatables>
|
||||
<a class="sys-card" [routerLink]="['data-tables']">
|
||||
<div class="sys-card__icon sys-card__icon--blue">
|
||||
<i class="fas fa-table"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Data tables' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Add new extra fields to any entity' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Audit Trails -->
|
||||
<a class="sys-card" [routerLink]="['audit-trails']">
|
||||
<div class="sys-card__icon sys-card__icon--slate">
|
||||
<i class="fas fa-money-check"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Audit Trails' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Audit logs of all the activities' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- Manage Codes -->
|
||||
<div #codes>
|
||||
<a class="sys-card" [routerLink]="['codes']">
|
||||
<div class="sys-card__icon sys-card__icon--purple">
|
||||
<i class="fas fa-list-ul"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Codes' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Codes are used to define drop down values' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Manage Reports -->
|
||||
<div #manageReports>
|
||||
<a class="sys-card" [routerLink]="['reports']">
|
||||
<div class="sys-card__icon sys-card__icon--indigo">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Reports' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Add new report and classify reports' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Manage Roles and Permissions -->
|
||||
<div #rolesandpermission>
|
||||
<a class="sys-card" [routerLink]="['roles-and-permissions']">
|
||||
<div class="sys-card__icon sys-card__icon--amber">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Roles and Permissions' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Define or modify roles and associated permissions' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Manage Jobs -->
|
||||
<a class="sys-card" [routerLink]="['manage-jobs']">
|
||||
<div class="sys-card__icon sys-card__icon--teal">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Jobs' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Manage Schedule and Workflow jobs, modify jobs' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- Configure Maker Checker Tasks -->
|
||||
<div #makerCheckerTable>
|
||||
<a class="sys-card" [routerLink]="['configure-mc-tasks']">
|
||||
<div class="sys-card__icon sys-card__icon--green">
|
||||
<i class="fas fa-sitemap"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Configure Maker Checker Tasks' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Define or modify Maker Checker tasks' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Configurations -->
|
||||
<div #configurations>
|
||||
<a class="sys-card" [routerLink]="['configurations']">
|
||||
<div class="sys-card__icon sys-card__icon--orange">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Configurations' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Global configurations, Cache and Business Date' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Manage Hooks -->
|
||||
<a class="sys-card" [routerLink]="['hooks']">
|
||||
<div class="sys-card__icon sys-card__icon--cyan">
|
||||
<i class="fas fa-anchor"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Hooks' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Define Hooks' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- Account Number Preferences -->
|
||||
<a class="sys-card" [routerLink]="['account-number-preferences']">
|
||||
<div class="sys-card__icon sys-card__icon--emerald">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Account Number Preferences' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Preferences for generating account numbers for client' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- Entity to Entity Mapping -->
|
||||
<a class="sys-card" [routerLink]="['entity-to-entity-mapping']">
|
||||
<div class="sys-card__icon sys-card__icon--rose">
|
||||
<i class="fas fa-road"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Entity to Entity Mapping' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Define or modify entity to entity mappings' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- External Services -->
|
||||
<a class="sys-card" [routerLink]="['external-services']">
|
||||
<div class="sys-card__icon sys-card__icon--indigo">
|
||||
<i class="fas fa-cog"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.External Services' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.External Services Configuration' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- Manage Surveys (disabled) -->
|
||||
<div class="sys-card sys-card--disabled">
|
||||
<div class="sys-card__icon sys-card__icon--slate">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage Surveys' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Manage Surveys' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</div>
|
||||
|
||||
<!-- About Us -->
|
||||
<a class="sys-card" [routerLink]="['about-us']">
|
||||
<div class="sys-card__icon sys-card__icon--blue">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.About Us' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.Learn about the Mifos Initiative and our mission' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- Manage External Events -->
|
||||
<a class="sys-card" [routerLink]="['external-events']" *mifosxHasPermission="'READ_EXTERNAL_EVENT_CONFIGURATION'">
|
||||
<div class="sys-card__icon sys-card__icon--purple">
|
||||
<i class="fas fa-anchor"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.Manage External Events' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.External Events configuration, to enable or disable' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
|
||||
<!-- System Information -->
|
||||
<a class="sys-card" [routerLink]="['system-information']">
|
||||
<div class="sys-card__icon sys-card__icon--teal">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
</div>
|
||||
<div class="sys-card__body">
|
||||
<h3 class="sys-card__title">{{ 'labels.heading.System Information' | translate }}</h3>
|
||||
<p class="sys-card__desc">{{ 'labels.text.View system version, server and licensing information' | translate }}</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right sys-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popover Templates (kept for configuration wizard compatibility) -->
|
||||
<ng-template #templateDatatables let-popover="popover">
|
||||
<h2>{{ 'labels.heading.Manage Data Tables' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.Mifos X data-tables allow the user to add custom fields' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepDatatables()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepDatatables()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepDatatables()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepDatatables()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -594,15 +241,9 @@
|
||||
<h2>{{ 'labels.heading.Manage Codes' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.Selection you can create new drop-downs and edit' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepCodes()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepCodes()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepCodes()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepCodes()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -610,31 +251,19 @@
|
||||
<h2>{{ 'labels.heading.Manage Roles and Permissions' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.Roles and Permissions allow the administrator to control access' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepRolesandPermission()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepRolesandPermission()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepRolesandPermission()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepRolesandPermission()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #templateMakerCheckerTable let-popover="popover">
|
||||
<h2>{{ 'labels.heading.Configure Maker-Checker Tasks' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.“Maker-Checker” principle requires every tasks' | translate }}</p>
|
||||
<p class="mw300">{{ 'labels.text."Maker-Checker" principle requires every tasks' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepMakerCheckerTable()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepMakerCheckerTable()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepMakerCheckerTable()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepMakerCheckerTable()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -642,15 +271,9 @@
|
||||
<h2>{{ 'labels.heading.Configuration' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.Configurations page allows you to further customize' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepConfigurations()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepConfigurations()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepConfigurations()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepConfigurations()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -658,15 +281,9 @@
|
||||
<h2>{{ 'labels.heading.Manage Scheduler Jobs' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.Scheduler Jobs can set batch jobs' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepSchedulerJobs()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepSchedulerJobs()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepSchedulerJobs()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepSchedulerJobs()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -674,14 +291,8 @@
|
||||
<h2>{{ 'labels.heading.Manage Reports' | translate }}</h2>
|
||||
<p class="mw300">{{ 'labels.text.Add customized reports and edit core reports' | translate }}</p>
|
||||
<div class="layout-row align-end gap-2px responsive-column">
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
||||
{{ 'labels.buttons.Close' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepManageReports()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepManageReports()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">{{ 'labels.buttons.Close' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); previousStepManageReports()">{{ 'labels.buttons.Back' | translate }}</button>
|
||||
<button mat-raised-button color="primary" (click)="popover.close(); nextStepManageReports()">{{ 'labels.buttons.Next' | translate }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -6,7 +6,167 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.disabled-item {
|
||||
pointer-events: none; // Prevent clicks
|
||||
opacity: 0.5; // Greyed out effect
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8fafc;
|
||||
$gray-400: #94a3b8;
|
||||
$gray-500: #64748b;
|
||||
$gray-700: #334155;
|
||||
$gray-900: #0f172a;
|
||||
|
||||
// ── Container ──
|
||||
.sys-container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
// ── Page Header ──
|
||||
.sys-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
|
||||
&__icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #0d3d78, #1a5276);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 13px;
|
||||
color: $gray-500;
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Card Grid ──
|
||||
.sys-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Individual Card ──
|
||||
.sys-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: $gray-50;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
|
||||
.sys-card__arrow {
|
||||
color: $gray-700;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--blue { background: rgba(59, 130, 246, 0.1); color: #2563eb; }
|
||||
&--amber { background: rgba(245, 158, 11, 0.1); color: #d97706; }
|
||||
&--green { background: rgba(34, 197, 94, 0.1); color: #16a34a; }
|
||||
&--emerald { background: rgba(16, 185, 129, 0.1); color: #059669; }
|
||||
&--purple { background: rgba(139, 92, 246, 0.1); color: #7c3aed; }
|
||||
&--indigo { background: rgba(99, 102, 241, 0.1); color: #4f46e5; }
|
||||
&--slate { background: rgba(100, 116, 139, 0.1); color: #475569; }
|
||||
&--orange { background: rgba(249, 115, 22, 0.1); color: #ea580c; }
|
||||
&--teal { background: rgba(20, 184, 166, 0.1); color: #0d9488; }
|
||||
&--cyan { background: rgba(6, 182, 212, 0.1); color: #0891b2; }
|
||||
&--rose { background: rgba(244, 63, 94, 0.1); color: #e11d48; }
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
margin: 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 11.5px;
|
||||
color: $gray-400;
|
||||
margin: 3px 0 0;
|
||||
line-height: 1.35;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
font-size: 11px;
|
||||
color: #cbd5e1;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dark mode ──
|
||||
:host-context(.dark-theme) {
|
||||
.sys-header {
|
||||
&__title { color: #e2e8f0; }
|
||||
&__sub { color: #94a3b8; }
|
||||
}
|
||||
|
||||
.sys-card {
|
||||
background: #1e1e1e;
|
||||
border-color: #333;
|
||||
|
||||
&:hover {
|
||||
background: #262626;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
&__title { color: #e2e8f0; }
|
||||
&__desc { color: #64748b; }
|
||||
&__arrow { color: #444; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,6 @@ import { Router, RouterLink } from '@angular/router';
|
||||
/** Custom Services */
|
||||
import { ConfigurationWizardService } from '../configuration-wizard/configuration-wizard.service';
|
||||
import { PopoverService } from '../configuration-wizard/popover/popover.service';
|
||||
import { MatNavList, MatListItem } from '@angular/material/list';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatLine } from '@angular/material/grid-list';
|
||||
import { NgClass } from '@angular/common';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -25,13 +20,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
templateUrl: './system.component.html',
|
||||
styleUrls: ['./system.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatNavList,
|
||||
MatListItem,
|
||||
MatIcon,
|
||||
FaIconComponent,
|
||||
MatLine,
|
||||
NgClass
|
||||
...STANDALONE_SHARED_IMPORTS
|
||||
]
|
||||
})
|
||||
export class SystemComponent implements AfterViewInit {
|
||||
|
||||
Reference in New Issue
Block a user